1 /***************************************************************************
2    qgsdatabasetablecombobox.cpp
3     --------------------------------
4    Date                 : March 2020
5    Copyright            : (C) 2020 Nyall Dawson
6    Email                : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 *                                                                         *
9 *   This program is free software; you can redistribute it and/or modify  *
10 *   it under the terms of the GNU General Public License as published by  *
11 *   the Free Software Foundation; either version 2 of the License, or     *
12 *   (at your option) any later version.                                   *
13 *                                                                         *
14 ***************************************************************************/
15 
16 #include "qgsdatabasetablecombobox.h"
17 #include "qgsdatabasetablemodel.h"
18 #include "qgsapplication.h"
19 #include "qgsabstractdatabaseproviderconnection.h"
20 #include <QHBoxLayout>
21 #include <QToolButton>
22 
QgsDatabaseTableComboBox(const QString & provider,const QString & connection,const QString & schema,QWidget * parent)23 QgsDatabaseTableComboBox::QgsDatabaseTableComboBox( const QString &provider, const QString &connection, const QString &schema, QWidget *parent )
24   : QWidget( parent )
25   , mProvider( provider )
26   , mConnection( connection )
27   , mSchema( schema )
28 {
29   if ( !provider.isEmpty() && !connection.isEmpty() )
30     mModel = new QgsDatabaseTableModel( provider, connection, schema, this );
31   init();
32 }
33 
QgsDatabaseTableComboBox(QgsAbstractDatabaseProviderConnection * connection,const QString & schema,QWidget * parent)34 QgsDatabaseTableComboBox::QgsDatabaseTableComboBox( QgsAbstractDatabaseProviderConnection *connection, const QString &schema, QWidget *parent )
35   : QWidget( parent )
36   , mSchema( schema )
37 {
38   mModel = new QgsDatabaseTableModel( connection, schema, this );
39   init();
40 }
41 
setAllowEmptyTable(bool allowEmpty)42 void QgsDatabaseTableComboBox::setAllowEmptyTable( bool allowEmpty )
43 {
44   mAllowEmpty = allowEmpty;
45   if ( mModel )
46     mModel->setAllowEmptyTable( allowEmpty );
47 }
48 
allowEmptyTable() const49 bool QgsDatabaseTableComboBox::allowEmptyTable() const
50 {
51   return mAllowEmpty;
52 }
53 
init()54 void QgsDatabaseTableComboBox::init()
55 {
56   mComboBox = new QComboBox();
57 
58   mSortModel = new QgsDatabaseTableComboBoxSortModel( this );
59   if ( mModel )
60     mSortModel->setSourceModel( mModel );
61   mSortModel->setSortRole( Qt::DisplayRole );
62   mSortModel->setSortLocaleAware( true );
63   mSortModel->setSortCaseSensitivity( Qt::CaseInsensitive );
64   mSortModel->setDynamicSortFilter( true );
65   mSortModel->sort( 0 );
66 
67   mComboBox->setModel( mSortModel );
68 
69   QHBoxLayout *l = new QHBoxLayout();
70   l->setContentsMargins( 0, 0, 0, 0 );
71   l->addWidget( mComboBox );
72 
73   QToolButton *refreshButton = new QToolButton();
74   refreshButton->setAutoRaise( true );
75   refreshButton->setToolTip( tr( "Refresh tables" ) );
76   refreshButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionRefresh.svg" ) ) );
77   l->addWidget( refreshButton );
78   setLayout( l );
79 
80   connect( refreshButton, &QToolButton::clicked, this, &QgsDatabaseTableComboBox::refreshTables );
81 
82   connect( mComboBox, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsDatabaseTableComboBox::indexChanged );
83   connect( mSortModel, &QAbstractItemModel::rowsInserted, this, &QgsDatabaseTableComboBox::rowsChanged );
84   connect( mSortModel, &QAbstractItemModel::rowsRemoved, this, &QgsDatabaseTableComboBox::rowsChanged );
85 }
86 
setTable(const QString & table,const QString & schema)87 void QgsDatabaseTableComboBox::setTable( const QString &table, const QString &schema )
88 {
89   if ( schema == currentSchema() && table == currentTable() )
90     return;
91 
92   if ( table.isEmpty() )
93   {
94     if ( mAllowEmpty )
95       mComboBox->setCurrentIndex( 0 );
96     else
97       mComboBox->setCurrentIndex( -1 );
98 
99     emit tableChanged( QString() );
100     return;
101   }
102 
103   const QModelIndexList idxs = mSortModel->match( mSortModel->index( 0, 0 ), QgsDatabaseTableModel::RoleTableName, table, -1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
104   for ( const QModelIndex &proxyIdx : idxs )
105   {
106     if ( proxyIdx.isValid()  && proxyIdx.data( QgsDatabaseTableModel::RoleTableName ).toString() == table
107          && ( schema.isEmpty() || proxyIdx.data( QgsDatabaseTableModel::RoleSchema ).toString() == schema ) )
108     {
109       mComboBox->setCurrentIndex( proxyIdx.row() );
110       emit tableChanged( currentTable(), currentSchema() );
111       return;
112     }
113   }
114   mComboBox->setCurrentIndex( -1 );
115   emit tableChanged( QString() );
116 }
117 
setConnectionName(const QString & connection,const QString & provider)118 void QgsDatabaseTableComboBox::setConnectionName( const QString &connection, const QString &provider )
119 {
120   if ( provider.isEmpty() && mConnection == connection )
121     return;
122 
123   if ( !provider.isEmpty() )
124     mProvider = provider;
125 
126   mConnection = connection;
127 
128   const QString oldTable = currentTable();
129   const QString oldSchema = currentSchema();
130   QgsDatabaseTableModel *oldModel = mModel;
131   if ( !mConnection.isEmpty() )
132   {
133     mModel = new QgsDatabaseTableModel( mProvider, mConnection, mSchema, this );
134     mModel->setAllowEmptyTable( mAllowEmpty );
135   }
136   else
137     mModel = nullptr;
138 
139   mSortModel->setSourceModel( mModel );
140   if ( oldModel )
141     oldModel->deleteLater();
142   if ( currentTable() != oldTable || currentSchema() != oldSchema )
143     setTable( oldTable, oldSchema );
144 }
145 
setSchema(const QString & schema)146 void QgsDatabaseTableComboBox::setSchema( const QString &schema )
147 {
148   if ( schema == mSchema )
149     return;
150   mSchema = schema;
151 
152   if ( !mProvider.isEmpty() && !mConnection.isEmpty() )
153   {
154     const QString oldTable = currentTable();
155     QgsDatabaseTableModel *oldModel = mModel;
156     mModel = new QgsDatabaseTableModel( mProvider, mConnection, mSchema, this );
157     mSortModel->setSourceModel( mModel );
158     oldModel->deleteLater();
159     setTable( oldTable );
160   }
161 }
162 
refreshTables()163 void QgsDatabaseTableComboBox::refreshTables()
164 {
165   const QString oldSchema = currentSchema();
166   const QString oldTable = currentTable();
167   if ( mModel )
168     mModel->refresh();
169   setTable( oldTable, oldSchema );
170 }
171 
currentSchema() const172 QString QgsDatabaseTableComboBox::currentSchema() const
173 {
174   const QModelIndex proxyIndex = mSortModel->index( mComboBox->currentIndex(), 0 );
175   if ( !proxyIndex.isValid() )
176   {
177     return QString();
178   }
179 
180   return mSortModel->data( proxyIndex, QgsDatabaseTableModel::RoleSchema ).toString();
181 }
182 
currentTable() const183 QString QgsDatabaseTableComboBox::currentTable() const
184 {
185   const QModelIndex proxyIndex = mSortModel->index( mComboBox->currentIndex(), 0 );
186   if ( !proxyIndex.isValid() )
187   {
188     return QString();
189   }
190 
191   return mSortModel->data( proxyIndex, QgsDatabaseTableModel::RoleTableName ).toString();
192 }
193 
indexChanged(int i)194 void QgsDatabaseTableComboBox::indexChanged( int i )
195 {
196   Q_UNUSED( i )
197   emit tableChanged( currentTable() );
198 }
199 
rowsChanged()200 void QgsDatabaseTableComboBox::rowsChanged()
201 {
202   if ( mComboBox->count() == 1 || ( mAllowEmpty && mComboBox->count() == 2 && mComboBox->currentIndex() == 1 ) )
203   {
204     //currently selected connection item has changed
205     emit tableChanged( currentTable(), currentSchema() );
206   }
207   else if ( mComboBox->count() == 0 )
208   {
209     emit tableChanged( QString() );
210   }
211 }
212 
213 ///@cond PRIVATE
QgsDatabaseTableComboBoxSortModel(QObject * parent)214 QgsDatabaseTableComboBoxSortModel::QgsDatabaseTableComboBoxSortModel( QObject *parent )
215   : QSortFilterProxyModel( parent )
216 {
217 
218 }
219 
lessThan(const QModelIndex & left,const QModelIndex & right) const220 bool QgsDatabaseTableComboBoxSortModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
221 {
222   // empty row is always first
223   if ( sourceModel()->data( left, QgsDatabaseTableModel::RoleEmpty ).toBool() )
224     return true;
225   else if ( sourceModel()->data( right, QgsDatabaseTableModel::RoleEmpty ).toBool() )
226     return false;
227 
228   // default mode is alphabetical order
229   QString leftStr = sourceModel()->data( left ).toString();
230   QString rightStr = sourceModel()->data( right ).toString();
231   return QString::localeAwareCompare( leftStr, rightStr ) < 0;
232 }
233 
234 ///@endcond
235