Skip to content

Commit

Permalink
Merge pull request #37760 from elpaso/browser-expose-fields
Browse files Browse the repository at this point in the history
Browser expose fields
  • Loading branch information
elpaso committed Jul 14, 2020
2 parents b4c21fa + 156e938 commit 56613de
Show file tree
Hide file tree
Showing 24 changed files with 783 additions and 28 deletions.
Expand Up @@ -235,7 +235,8 @@ This information is calculated from the geometry columns types.
SpatialIndexExists,
DeleteSpatialIndex,
DeleteField,
CreateField,
DeleteFieldCascade,
AddField,
};

typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;
Expand Down Expand Up @@ -359,6 +360,44 @@ Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End

virtual void deleteField( const QString &fieldName, const QString &schema, const QString &tableName, bool force = false ) const throw( QgsProviderConnectionException );
%Docstring
Deletes the field with the specified name.
Raises a QgsProviderConnectionException if any errors are encountered.

:param fieldName: name of the field to be deleted
:param schema: name of the schema (schema is ignored if not supported by the backend).
:param tableName: name of the table
:param force: if ``True``, a DROP CASCADE will drop all related objects

.. note::

it is responsibility of the caller to handle open layers and registry entries.

:raises :: py:class:`QgsProviderConnectionException`

.. versionadded:: 3.16
%End

virtual void addField( const QgsField &field, const QString &schema, const QString &tableName ) const throw( QgsProviderConnectionException );
%Docstring
Adds a field
Raises a QgsProviderConnectionException if any errors are encountered.

:param field: specification of the new field
:param schema: name of the schema (schema is ignored if not supported by the backend).
:param tableName: name of the table

.. note::

it is responsibility of the caller to handle open layers and registry entries.

:raises :: py:class:`QgsProviderConnectionException`

.. versionadded:: 3.16
%End


virtual void renameSchema( const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
Renames a schema with the specified ``name``.
Expand Down
100 changes: 100 additions & 0 deletions python/core/auto_generated/qgsdataitem.sip.in
Expand Up @@ -49,6 +49,8 @@ Parent/children hierarchy is not based on QObject.
Favorites,
Project,
Custom,
Fields,
Field,
};


Expand Down Expand Up @@ -900,6 +902,104 @@ Creates a new data item from the specified path.
};


class QgsFieldsItem : QgsDataItem
{
%Docstring
A collection of field items with some internal logic to retrieve
the fields and a the vector layer instance from a connection URI,
the schema and the table name.

.. versionadded:: 3.16
%End

%TypeHeaderCode
#include "qgsdataitem.h"
%End
public:

QgsFieldsItem( QgsDataItem *parent /TransferThis/,
const QString &path,
const QString &connectionUri,
const QString &providerKey,
const QString &schema,
const QString &tableName );
%Docstring
Constructor for QgsFieldsItem, with the specified ``parent`` item.

The ``path`` argument gives the item path in the browser tree. The ``path`` string can take any form,
but QgsDataItem items pointing to different logical locations should always use a different item ``path``.
The \connectionUri argument is the connection part of the layer URI that it is used internally to create
a connection and retrieve fields information.
The ``providerKey`` string can be used to specify the key for the QgsDataItemProvider that created this item.
The ``schema`` and ``tableName`` are used to retrieve the layer and field information from the ``connectionUri``.
%End

~QgsFieldsItem();

virtual QVector<QgsDataItem *> createChildren();


virtual QIcon icon();


QString schema() const;
%Docstring
Returns the schema name
%End

QString tableName() const;
%Docstring
Returns the table name
%End

QString connectionUri() const;
%Docstring
Returns the connection URI
%End

QgsVectorLayer *layer() /Factory/;
%Docstring
Creates and returns a (possibly NULL) layer from the connection URI and schema/table information
%End


};


class QgsFieldItem : QgsDataItem
{
%Docstring
A layer field item, information about the connection URI, the schema and the
table as well as the layer instance the field belongs to can be retrieved
from the parent QgsFieldsItem object.

.. versionadded:: 3.16
%End

%TypeHeaderCode
#include "qgsdataitem.h"
%End
public:

QgsFieldItem( QgsDataItem *parent /TransferThis/,
const QgsField &field );
%Docstring
Constructor for QgsFieldItem, with the specified ``parent`` item and ``field``.

.. note::

parent item must be a :py:class:`QgsFieldsItem`
%End

~QgsFieldItem();

virtual QIcon icon();


};






Expand Down
151 changes: 151 additions & 0 deletions src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -37,6 +37,9 @@
#include "processing/qgsprojectstylealgorithms.h"
#include "qgsstylemanagerdialog.h"
#include "qgsproviderregistry.h"
#include "qgsaddattrdialog.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include "qgsprovidermetadata.h"

#include <QFileInfo>
#include <QMenu>
Expand Down Expand Up @@ -688,3 +691,151 @@ bool QgsProjectItemGuiProvider::handleDoubleClick( QgsDataItem *item, QgsDataIte
return false;
}
}

QString QgsFieldsItemGuiProvider::name()
{
return QStringLiteral( "fields_item" );
}

void QgsFieldsItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
{
Q_UNUSED( selectedItems )

if ( !item || item->type() != QgsDataItem::Type::Fields )
return;


if ( QgsFieldsItem *fieldsItem = qobject_cast<QgsFieldsItem *>( item ) )
{
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( fieldsItem->providerKey() ) };
if ( md )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
// Check if it is supported
if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::AddField ) )
{
QAction *addColumnAction = new QAction( tr( "Add New Field…" ), menu );
QPointer<QgsDataItem>itemPtr { item };
const QString itemName { item->name() };

connect( addColumnAction, &QAction::triggered, fieldsItem, [ md, fieldsItem, context, itemPtr, menu ]
{
std::unique_ptr<QgsVectorLayer> layer { fieldsItem->layer() };
if ( layer )
{
QgsAddAttrDialog dialog( layer.get(), menu );
if ( dialog.exec() == QDialog::Accepted )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
try
{
conn2->addField( dialog.field(), fieldsItem->schema(), fieldsItem->tableName() );
if ( itemPtr )
itemPtr->refresh();
}
catch ( const QgsProviderConnectionException &ex )
{
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "New Field" ), tr( "Failed to add the new field to '%1': %2" ).arg( fieldsItem->tableName(), ex.what() ) );
}
else
{
QMessageBox::critical( menu, tr( "New Field" ), tr( "Failed to a add new field to '%1': %2" ).arg( fieldsItem->tableName(), ex.what() ) );
}
}
}
}
else
{
const QString message { tr( "Failed to create layer '%1'. Check application logs and user permissions." ).arg( fieldsItem->tableName() ) };
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "Add Field" ), message );
}
else
{
QMessageBox::critical( menu, tr( "Add Field" ), message );
}
}
} );
menu->addAction( addColumnAction );
}
}
}
}

QString QgsFieldItemGuiProvider::name()
{
return QStringLiteral( "field_item" );
}

void QgsFieldItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
{
Q_UNUSED( selectedItems )

if ( !item || item->type() != QgsDataItem::Type::Field )
return;

if ( QgsFieldItem *fieldItem = qobject_cast<QgsFieldItem *>( item ) )
{
// Retrieve the connection from the parent
QgsFieldsItem *fieldsItem { static_cast<QgsFieldsItem *>( fieldItem->parent() ) };
if ( fieldsItem )
{
// Check if it is supported
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( fieldsItem->providerKey() ) };
if ( md )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::DeleteField ) )
{
QAction *deleteFieldAction = new QAction( tr( "Delete Field…" ), menu );
const bool supportsCascade { conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::DeleteFieldCascade ) };
const QString itemName { item->name() };

connect( deleteFieldAction, &QAction::triggered, fieldsItem, [ md, fieldsItem, itemName, context, supportsCascade, menu ]
{
// Confirmation dialog
QMessageBox msgbox{QMessageBox::Icon::Question, tr( "Delete Field" ), tr( "Delete '%1' permanently?" ).arg( itemName ), QMessageBox::Ok | QMessageBox::Cancel };
QCheckBox *cb = new QCheckBox( tr( "Delete all related objects (CASCADE)?" ) );
msgbox.setCheckBox( cb );
msgbox.setDefaultButton( QMessageBox::Cancel );

if ( ! supportsCascade )
{
cb->hide();
}

if ( msgbox.exec() == QMessageBox::Ok )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
try
{
conn2->deleteField( itemName, fieldsItem->schema(), fieldsItem->tableName(), supportsCascade && cb->isChecked() );
fieldsItem->refresh();
}
catch ( const QgsProviderConnectionException &ex )
{
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "Delete Field" ), tr( "Failed to delete field '%1': %2" ).arg( itemName, ex.what() ) );
}
else
{
QMessageBox::critical( menu, tr( "Delete Field" ), tr( "Failed to delete field '%1': %2" ).arg( itemName, ex.what() ) );
}
}
}
} );
menu->addAction( deleteFieldAction );
}
}
}
else
{
// This should never happen!
QgsDebugMsg( QStringLiteral( "Error getting parent fields for %1" ).arg( item->name() ) );
}
}
}
35 changes: 35 additions & 0 deletions src/app/browser/qgsinbuiltdataitemproviders.h
Expand Up @@ -25,6 +25,8 @@
class QgsDirectoryItem;
class QgsFavoriteItem;
class QgsLayerItem;
class QgsFieldsItem;
class QgsFieldItem;

class QgsAppDirectoryItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Expand Down Expand Up @@ -105,6 +107,39 @@ class QgsLayerItemGuiProvider : public QObject, public QgsDataItemGuiProvider
};


class QgsFieldsItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT

public:

QgsFieldsItemGuiProvider() = default;

QString name() override;

void populateContextMenu( QgsDataItem *item, QMenu *menu,
const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context ) override;

};


class QgsFieldItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT

public:

QgsFieldItemGuiProvider() = default;

QString name() override;

void populateContextMenu( QgsDataItem *item, QMenu *menu,
const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context ) override;

};



class QgsProjectItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -1212,6 +1212,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFavoritesItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsLayerItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsBookmarksItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldsItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldItemGuiProvider() );

QShortcut *showBrowserDock = new QShortcut( QKeySequence( tr( "Ctrl+2" ) ), this );
connect( showBrowserDock, &QShortcut::activated, mBrowserWidget, &QgsDockWidget::toggleUserVisible );
Expand Down

0 comments on commit 56613de

Please sign in to comment.