diff --git a/python/core/qgsdataitem.sip b/python/core/qgsdataitem.sip index f7ac491fc5e..0faeafaac3d 100644 --- a/python/core/qgsdataitem.sip +++ b/python/core/qgsdataitem.sip @@ -49,7 +49,8 @@ class QgsDataItem : QObject Layer, Error, Favorites, - Project + Project, + Custom, }; @@ -153,6 +154,15 @@ Create new data item. :rtype: bool %End + virtual bool handleDoubleClick(); +%Docstring + Called when a user double clicks on the item. Subclasses should return true + if they have implemented a double-click handler and do not want the default + double-click behavior for items. +.. versionadded:: 3.0 + :rtype: bool +%End + virtual bool hasDragEnabled() const; %Docstring Returns true if the item may be dragged. diff --git a/python/plugins/processing/ProcessingPlugin.py b/python/plugins/processing/ProcessingPlugin.py index 13c0690d334..5e0986012e8 100755 --- a/python/plugins/processing/ProcessingPlugin.py +++ b/python/plugins/processing/ProcessingPlugin.py @@ -33,10 +33,14 @@ import sys from qgis.core import (QgsApplication, QgsProcessingUtils, - QgsProcessingModelAlgorithm) + QgsProcessingModelAlgorithm, + QgsDataItemProvider, + QgsDataProvider, + QgsDataItem, + QgsMimeDataUtils) from qgis.gui import (QgsOptionsWidgetFactory, QgsCustomDropHandler) -from qgis.PyQt.QtCore import Qt, QCoreApplication, QDir +from qgis.PyQt.QtCore import Qt, QCoreApplication, QDir, QFileInfo from qgis.PyQt.QtWidgets import QMenu, QAction from qgis.PyQt.QtGui import QIcon @@ -74,7 +78,10 @@ class ProcessingDropHandler(QgsCustomDropHandler): def handleFileDrop(self, file): if not file.lower().endswith('.model3'): return False + self.runAlg(file) + @staticmethod + def runAlg(file): alg = QgsProcessingModelAlgorithm() if not alg.fromFile(file): return False @@ -85,6 +92,73 @@ class ProcessingDropHandler(QgsCustomDropHandler): dlg.show() return True + def customUriProviderKey(self): + return 'processing' + + def handleCustomUriDrop(self, uri): + path = uri.uri + self.runAlg(path) + + +class ProcessingModelItem(QgsDataItem): + + def __init__(self, parent, name, path): + super(ProcessingModelItem, self).__init__(QgsDataItem.Custom, parent, name, path) + self.setState(QgsDataItem.Populated) # no children + self.setIconName(":/images/themes/default/processingModel.svg") + self.setToolTip(QDir.toNativeSeparators(path)) + + def hasDragEnabled(self): + return True + + def handleDoubleClick(self): + self.runModel() + return True + + def mimeUri(self): + u = QgsMimeDataUtils.Uri() + u.layerType = "custom" + u.providerKey = "processing" + u.name = self.name() + u.uri = self.path() + return u + + def runModel(self): + ProcessingDropHandler.runAlg(self.path()) + + def editModel(self): + dlg = ModelerDialog() + dlg.loadModel(self.path()) + dlg.show() + + def actions(self): + run_model_action = QAction(self.tr('&Run Model…'), self) + run_model_action.triggered.connect(self.runModel) + edit_model_action = QAction(self.tr('&Edit Model…'), self) + edit_model_action.triggered.connect(self.editModel) + return [run_model_action, edit_model_action] + + +class ProcessingDataItemProvider(QgsDataItemProvider): + + def __init__(self): + super(ProcessingDataItemProvider, self).__init__() + + def name(self): + return 'processing' + + def capabilities(self): + return QgsDataProvider.File + + def createDataItem(self, path, parentItem): + file_info = QFileInfo(path) + + if file_info.suffix().lower() == 'model3': + alg = QgsProcessingModelAlgorithm() + if alg.fromFile(path): + return ProcessingModelItem(parentItem, alg.name(), path) + return None + class ProcessingPlugin(object): @@ -95,6 +169,8 @@ class ProcessingPlugin(object): iface.registerOptionsWidgetFactory(self.options_factory) self.drop_handler = ProcessingDropHandler() iface.registerCustomDropHandler(self.drop_handler) + self.item_provider = ProcessingDataItemProvider() + QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider) self.locator_filter = AlgorithmLocatorFilter() iface.registerLocatorFilter(self.locator_filter) Processing.initialize() @@ -182,6 +258,7 @@ class ProcessingPlugin(object): self.iface.unregisterOptionsWidgetFactory(self.options_factory) self.iface.deregisterLocatorFilter(self.locator_filter) self.iface.unregisterCustomDropHandler(self.drop_handler) + QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider) removeMenus() Processing.deinitialize() diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 2addf7a73b5..31d66b7ca32 100755 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -503,23 +503,26 @@ class ModelerDialog(BASE, WIDGET): ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3 *.MODEL3)')) if filename: - alg = QgsProcessingModelAlgorithm() - if alg.fromFile(filename): - self.model = alg - self.model.setProvider(QgsApplication.processingRegistry().providerById('model')) - self.textGroup.setText(alg.group()) - self.textName.setText(alg.name()) - self.repaintModel() + self.loadModel(filename) - self.view.centerOn(0, 0) - self.hasChanged = False - else: - QgsMessageLog.logMessage(self.tr('Could not load model {0}').format(filename), - self.tr('Processing'), - QgsMessageLog.CRITICAL) - QMessageBox.critical(self, self.tr('Could not open model'), - self.tr('The selected model could not be loaded.\n' - 'See the log for more information.')) + def loadModel(self, filename): + alg = QgsProcessingModelAlgorithm() + if alg.fromFile(filename): + self.model = alg + self.model.setProvider(QgsApplication.processingRegistry().providerById('model')) + self.textGroup.setText(alg.group()) + self.textName.setText(alg.name()) + self.repaintModel() + + self.view.centerOn(0, 0) + self.hasChanged = False + else: + QgsMessageLog.logMessage(self.tr('Could not load model {0}').format(filename), + self.tr('Processing'), + QgsMessageLog.CRITICAL) + QMessageBox.critical(self, self.tr('Could not open model'), + self.tr('The selected model could not be loaded.\n' + 'See the log for more information.')) def repaintModel(self, controls=True): self.scene = ModelerScene(self, dialog=self) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 095af405ab6..e2595ea34af 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -6,6 +6,7 @@ SET(QGIS_APP_SRCS qgisappstylesheet.cpp qgsabout.cpp qgsalignrasterdialog.cpp + qgsappbrowserproviders.cpp qgsapplayertreeviewmenuprovider.cpp qgsaddattrdialog.cpp qgsaddtaborgroup.cpp @@ -194,6 +195,7 @@ SET (QGIS_APP_MOC_HDRS qgsabout.h qgsaddattrdialog.h qgsalignrasterdialog.h + qgsappbrowserproviders.h qgsjoindialog.h qgsaddtaborgroup.h qgsannotationwidget.h diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 6a1b85f7034..0dd9a882856 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -124,6 +124,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgisplugin.h" #include "qgsabout.h" #include "qgsalignrasterdialog.h" +#include "qgsappbrowserproviders.h" #include "qgsapplayertreeviewmenuprovider.h" #include "qgsapplication.h" #include "qgsactionmanager.h" @@ -153,6 +154,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgscustomization.h" #include "qgscustomlayerorderwidget.h" #include "qgscustomprojectiondialog.h" +#include "qgsdataitemproviderregistry.h" #include "qgsdatasourceuri.h" #include "qgsdatumtransformdialog.h" #include "qgsdoublespinbox.h" @@ -975,6 +977,9 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh qApp->processEvents(); QgsApplication::initQgis(); + QgsApplication::dataItemProviderRegistry()->addProvider( new QgsQlrDataItemProvider() ); + registerCustomDropHandler( new QgsQlrDropHandler() ); + mSplash->showMessage( tr( "Starting Python" ), Qt::AlignHCenter | Qt::AlignBottom ); qApp->processEvents(); loadPythonSupport(); diff --git a/src/app/qgsappbrowserproviders.cpp b/src/app/qgsappbrowserproviders.cpp new file mode 100644 index 00000000000..cd18bcd4286 --- /dev/null +++ b/src/app/qgsappbrowserproviders.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** + qgsappbrowserproviders.cpp + --------------------------- + begin : September 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsappbrowserproviders.h" +#include "qgisapp.h" + +// +// QgsQlrDataItem +// + +QgsQlrDataItem::QgsQlrDataItem( QgsDataItem *parent, const QString &name, const QString &path ) + : QgsLayerItem( parent, name, path, path, QgsLayerItem::NoType, QStringLiteral( "qlr" ) ) +{ + setState( QgsDataItem::Populated ); // no children + setIconName( QStringLiteral( ":/images/icons/qgis-icon-16x16.png" ) ); + setToolTip( QDir::toNativeSeparators( path ) ); +} + +bool QgsQlrDataItem::hasDragEnabled() const +{ + return true; +} + +QgsMimeDataUtils::Uri QgsQlrDataItem::mimeUri() const +{ + QgsMimeDataUtils::Uri u; + u.layerType = QStringLiteral( "custom" ); + u.providerKey = QStringLiteral( "qlr" ); + u.name = name(); + u.uri = path(); + return u; +} + +// +// QgsQlrDataItemProvider +// + +QString QgsQlrDataItemProvider::name() +{ + return QStringLiteral( "QLR" ); +} + +int QgsQlrDataItemProvider::capabilities() +{ + return QgsDataProvider::File; +} + +QgsDataItem *QgsQlrDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem ) +{ + QFileInfo fileInfo( path ); + + if ( fileInfo.suffix().compare( QStringLiteral( "qlr" ), Qt::CaseInsensitive ) == 0 ) + { + return new QgsQlrDataItem( parentItem, fileInfo.fileName(), path ); + } + return nullptr; +} + +QString QgsQlrDropHandler::customUriProviderKey() const +{ + return QStringLiteral( "qlr" ); +} + +void QgsQlrDropHandler::handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const +{ + QString path = uri.uri; + QgisApp::instance()->openLayerDefinition( path ); +} diff --git a/src/app/qgsappbrowserproviders.h b/src/app/qgsappbrowserproviders.h new file mode 100644 index 00000000000..864f0203d85 --- /dev/null +++ b/src/app/qgsappbrowserproviders.h @@ -0,0 +1,50 @@ +/*************************************************************************** + qgsappbrowserproviders.h + ------------------------- + begin : September 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSAPPBROWSERPROVIDERS_H +#define QGSAPPBROWSERPROVIDERS_H + +#include "qgsdataitemprovider.h" +#include "qgsdataprovider.h" +#include "qgscustomdrophandler.h" + +class QgsQlrDataItem : public QgsLayerItem +{ + Q_OBJECT + + public: + + QgsQlrDataItem( QgsDataItem *parent, const QString &name, const QString &path ); + bool hasDragEnabled() const override; + QgsMimeDataUtils::Uri mimeUri() const override; + +}; + +class QgsQlrDataItemProvider : public QgsDataItemProvider +{ + public: + QString name() override; + int capabilities() override; + QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; +}; + +class QgsQlrDropHandler : public QgsCustomDropHandler +{ + public: + + QString customUriProviderKey() const override; + void handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const override; +}; + +#endif // QGSAPPBROWSERPROVIDERS_H diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 51b488e7d87..6b646ff69dc 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -511,6 +511,11 @@ bool QgsDataItem::equal( const QgsDataItem *other ) mPath == other->path() ); } +bool QgsDataItem::handleDoubleClick() +{ + return false; +} + QgsDataItem::State QgsDataItem::state() const { return mState; diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 14c9ccf22c0..8f8216aea87 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -79,7 +79,8 @@ class CORE_EXPORT QgsDataItem : public QObject Layer, Error, Favorites, //!< Represents a favorite item - Project //!< Represents a QGIS project + Project, //!< Represents a QGIS project + Custom, //!< Custom item type }; Q_ENUM( Type ); @@ -155,6 +156,14 @@ class CORE_EXPORT QgsDataItem : public QObject */ virtual bool handleDrop( const QMimeData * /*data*/, Qt::DropAction /*action*/ ) { return false; } + /** + * Called when a user double clicks on the item. Subclasses should return true + * if they have implemented a double-click handler and do not want the default + * double-click behavior for items. + * \since QGIS 3.0 + */ + virtual bool handleDoubleClick(); + /** Returns true if the item may be dragged. * Default implementation returns false. * A draggable item has to implement mimeUri() that will be used to pass data. diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index b47eaf1d611..db3e5a0bd75 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -97,7 +97,7 @@ QgsBrowserDockWidget::QgsBrowserDockWidget( const QString &name, QgsBrowserModel connect( mLeFilter, &QgsFilterLineEdit::textChanged, this, &QgsBrowserDockWidget::setFilter ); connect( group, &QActionGroup::triggered, this, &QgsBrowserDockWidget::setFilterSyntax ); connect( mBrowserView, &QgsDockBrowserTreeView::customContextMenuRequested, this, &QgsBrowserDockWidget::showContextMenu ); - connect( mBrowserView, &QgsDockBrowserTreeView::doubleClicked, this, &QgsBrowserDockWidget::addLayerAtIndex ); + connect( mBrowserView, &QgsDockBrowserTreeView::doubleClicked, this, &QgsBrowserDockWidget::itemDoubleClicked ); connect( mSplitter, &QSplitter::splitterMoved, this, &QgsBrowserDockWidget::splitterMoved ); } @@ -155,6 +155,18 @@ void QgsBrowserDockWidget::showEvent( QShowEvent *e ) QgsDockWidget::showEvent( e ); } +void QgsBrowserDockWidget::itemDoubleClicked( const QModelIndex &index ) +{ + QgsDataItem *item = mModel->dataItem( mProxyModel->mapToSource( index ) ); + if ( !item ) + return; + + if ( item->handleDoubleClick() ) + return; + else + addLayerAtIndex( index ); // default double-click handler +} + void QgsBrowserDockWidget::showContextMenu( QPoint pt ) { QModelIndex index = mProxyModel->mapToSource( mBrowserView->indexAt( pt ) ); diff --git a/src/gui/qgsbrowserdockwidget.h b/src/gui/qgsbrowserdockwidget.h index 2958e8677e4..5fb95169721 100644 --- a/src/gui/qgsbrowserdockwidget.h +++ b/src/gui/qgsbrowserdockwidget.h @@ -110,6 +110,9 @@ class GUI_EXPORT QgsBrowserDockWidget : public QgsDockWidget, private Ui::QgsBro //! Show event override void showEvent( QShowEvent *event ) override; + private slots: + void itemDoubleClicked( const QModelIndex &index ); + private: //! Refresh the model void refreshModel( const QModelIndex &index );