35 #include "ui_directoryserviceswidget.h"
40 #include <QItemDelegate>
41 #include <QAbstractTableModel>
44 #include <QHeaderView>
48 #include <boost/bind.hpp>
58 using namespace boost;
62 static KUrl defaultX509Service() {
64 url.setScheme( QLatin1String(
"ldap") );
65 url.setHost( i18nc(
"default server name, keep it a valid domain name, ie. no spaces",
"server") );
68 static KUrl defaultOpenPGPService() {
70 url.setScheme( QLatin1String(
"hkp") );
71 url.setHost( QLatin1String(
"keys.gnupg.net") );
75 static bool is_ldap_scheme(
const KUrl & url ) {
76 const QString scheme = url.protocol();
77 return QString::compare( scheme, QLatin1String(
"ldap" ), Qt::CaseInsensitive ) == 0
78 || QString::compare( scheme, QLatin1String(
"ldaps" ), Qt::CaseInsensitive ) == 0;
94 static const unsigned int numProtocols =
sizeof protocols /
sizeof *protocols;
96 static unsigned short default_port(
const QString & scheme ) {
97 for (
unsigned int i = 0 ; i < numProtocols ; ++i )
98 if ( QString::compare( scheme, QLatin1String( protocols[i].
label ), Qt::CaseInsensitive ) == 0 )
99 return protocols[i].port;
103 static QString display_scheme(
const KUrl & url ) {
104 if ( url.scheme().isEmpty() )
105 return QLatin1String(
"hkp" );
110 static QString display_host(
const KUrl & url ) {
112 if ( url.host().isEmpty() )
118 static unsigned short display_port(
const KUrl & url ) {
119 if ( url.port() > 0 )
122 return default_port( display_scheme( url ) );
125 static bool is_default_port(
const KUrl & url ) {
126 return display_port( url ) == default_port( display_scheme( url ) ) ;
129 static QRect calculate_geometry(
const QRect & cell,
const QSize & sizeHint ) {
130 const int height = qMax( cell.height(), sizeHint.height() );
131 return QRect( cell.left(), cell.top() - ( height - cell.height() ) / 2,
132 cell.width(), height );
135 struct KUrl_compare : std::binary_function<KUrl,KUrl,bool> {
136 bool operator()(
const KUrl & lhs,
const KUrl & rhs )
const {
137 return QString::compare( display_scheme( lhs ), display_scheme( rhs ), Qt::CaseInsensitive ) == 0
138 && QString::compare( display_host( lhs ), display_host( rhs ), Qt::CaseInsensitive ) == 0
139 && lhs.port() == rhs.port()
140 && lhs.user() == rhs.user()
142 && ( !is_ldap_scheme( lhs )
143 || KUrl::fromPercentEncoding( lhs.query().mid( 1 ).toLatin1() )
144 == KUrl::fromPercentEncoding( rhs.query().mid( 1 ).toLatin1() ) ) ;
148 class Model :
public QAbstractTableModel {
151 explicit Model(
QObject * parent=0 )
152 : QAbstractTableModel( parent ),
154 m_openPGPReadOnly( false ),
155 m_x509ReadOnly( false ),
161 void setOpenPGPReadOnly(
bool ro ) {
162 if ( ro == m_openPGPReadOnly )
164 m_openPGPReadOnly = ro;
165 for (
unsigned int row = 0, end = rowCount() ; row != end ; ++row )
166 if ( isOpenPGPService( row ) )
167 emit dataChanged( index( row, 0 ), index( row, NumColumns ) );
170 void setX509ReadOnly(
bool ro ) {
171 if ( ro == m_x509ReadOnly )
174 for (
unsigned int row = 0, end = rowCount() ; row != end ; ++row )
175 if ( isX509Service( row ) )
176 emit dataChanged( index( row, 0 ), index( row, NumColumns ) );
179 QModelIndex addOpenPGPService(
const KUrl & url,
bool force=
false ) {
180 return addService( url,
false,
true, force );
182 QModelIndex addX509Service(
const KUrl & url,
bool force=
false ) {
183 return addService( url,
true,
false, force );
185 QModelIndex addService(
const KUrl & url,
bool x509,
bool pgp,
bool force ) {
186 const std::vector<Item>::iterator it = force ? m_items.end() : findExistingUrl( url ) ;
188 if ( it != m_items.end() ) {
192 row = it - m_items.begin() ;
193 emit dataChanged( index( row, std::min( X509, OpenPGP ) ), index( row, std::max( X509, OpenPGP ) ) );
196 const Item item = { url, x509, pgp };
197 row = m_items.size();
198 beginInsertRows( QModelIndex(), row, row );
199 m_items.push_back( item );
202 return index( row, firstEditableColumn( row ) );
205 unsigned int numServices()
const {
return m_items.size(); }
206 bool isOpenPGPService(
unsigned int row )
const {
return row < m_items.size() && m_items[row].pgp; }
207 bool isX509Service(
unsigned int row )
const {
return row < m_items.size() && m_items[row].x509 && isLdapRow( row ) ; }
208 KUrl service(
unsigned int row )
const {
return row < m_items.size() ? m_items[row].url : KUrl() ; }
210 bool isReadOnlyRow(
unsigned int row )
const {
211 return ( isX509Service( row ) && m_x509ReadOnly )
212 || ( isOpenPGPService( row ) && m_openPGPReadOnly );
228 QModelIndex duplicateRow(
unsigned int row ) {
229 if ( row >= m_items.size() )
230 return QModelIndex();
232 beginInsertRows( QModelIndex(), row+1, row+1 );
233 m_items.insert( m_items.begin() + row + 1, m_items[row] );
234 if ( m_items[row].pgp )
235 m_items[row+1].pgp =
false;
237 return index( row+1, 0 );
240 void deleteRow(
unsigned int row ) {
241 if ( row >= m_items.size() )
244 beginRemoveRows( QModelIndex(), row, row );
245 m_items.erase( m_items.begin() + row );
250 if ( m_items.empty() )
252 beginRemoveRows( QModelIndex(), 0, m_items.size()-1 );
257 int columnCount(
const QModelIndex & =QModelIndex() )
const {
return NumColumns; }
258 int rowCount(
const QModelIndex & =QModelIndex() )
const {
return m_items.size(); }
260 QVariant data(
const QModelIndex & idx,
int role )
const;
261 QVariant headerData(
int section, Qt::Orientation o,
int role )
const;
263 Qt::ItemFlags flags(
const QModelIndex & idx )
const;
264 bool setData(
const QModelIndex & idx,
const QVariant & value,
int role );
267 bool doSetData(
unsigned int row,
unsigned int column,
const QVariant & value,
int role );
268 void setExclusivePgpFlag(
unsigned int row );
270 static QString toolTipForColumn(
int column );
271 bool isLdapRow(
unsigned int row )
const;
272 int firstEditableColumn(
unsigned int )
const {
282 std::vector<Item> m_items;
283 bool m_openPGPReadOnly : 1;
284 bool m_x509ReadOnly : 1;
285 DirectoryServicesWidget::Schemes m_schemes;
288 std::vector<Item>::iterator findExistingUrl(
const KUrl & url ) {
290 boost::bind( KUrl_compare(), url, boost::bind( &Item::url, _1 ) ) );
294 class Delegate :
public QItemDelegate {
297 explicit Delegate(
QObject * parent=0 )
298 : QItemDelegate( parent ),
304 void setAllowedSchemes(
const DirectoryServicesWidget::Schemes schemes ) {
307 DirectoryServicesWidget::Schemes allowedSchemes()
const {
return m_schemes; }
310 QWidget * createEditor(
QWidget * parent,
const QStyleOptionViewItem & option,
const QModelIndex & idx )
const {
311 switch ( idx.column() ) {
313 return createSchemeWidget( parent );
315 return createPortWidget( parent );
317 return QItemDelegate::createEditor( parent, option, idx );
321 void setEditorData(
QWidget * editor,
const QModelIndex & idx )
const {
322 switch ( idx.column() ) {
324 setSchemeEditorData( qobject_cast<QComboBox*>( editor ), idx.data( Qt::EditRole ).toString() );
327 setPortEditorData( qobject_cast<QSpinBox*>( editor ), idx.data( Qt::EditRole ).toInt() );
330 QItemDelegate::setEditorData( editor, idx );
336 void setModelData(
QWidget * editor, QAbstractItemModel * model,
const QModelIndex & idx )
const {
337 switch ( idx.column() ) {
339 setSchemeModelData( qobject_cast<QComboBox*>( editor ), model, idx );
342 setPortModelData( qobject_cast<QSpinBox*>( editor ), model, idx );
345 QItemDelegate::setModelData( editor, model, idx );
351 void updateEditorGeometry(
QWidget * editor,
const QStyleOptionViewItem & option,
const QModelIndex & index )
const {
352 if ( index.column() == Model::Scheme || index.column() == Model::Port )
353 editor->setGeometry( calculate_geometry( option.rect, editor->sizeHint() ) );
355 QItemDelegate::updateEditorGeometry( editor, option, index );
362 QComboBox * cb =
new QComboBox( parent );
363 for (
unsigned int i = 0 ; i < numProtocols ; ++i )
364 if ( m_schemes & protocols[i].base )
365 cb->addItem( i18n( protocols[i].
label ), QLatin1String(protocols[i].label) );
366 assert( cb->count() > 0 );
369 void setSchemeEditorData( QComboBox * cb,
const QString & scheme )
const {
371 cb->setCurrentIndex( cb->findData( scheme, Qt::UserRole, Qt::MatchFixedString ) );
373 void setSchemeModelData(
const QComboBox * cb, QAbstractItemModel * model,
const QModelIndex & idx )
const {
376 model->setData( idx, cb->itemData( cb->currentIndex() ) );
380 QSpinBox * sb =
new QSpinBox( parent );
381 sb->setRange( 1, USHRT_MAX );
384 void setPortEditorData( QSpinBox * sb,
unsigned short port )
const {
386 sb->setValue( port );
388 void setPortModelData(
const QSpinBox * sb, QAbstractItemModel * model,
const QModelIndex & idx )
const {
391 model->setData( idx, sb->value() );
395 DirectoryServicesWidget::Schemes m_schemes;
400 class DirectoryServicesWidget::Private {
401 friend class ::Kleo::DirectoryServicesWidget;
406 protocols( AllProtocols ),
407 readOnlyProtocols( NoProtocol ),
412 ui.treeView->setModel( &model );
413 ui.treeView->setItemDelegate( &delegate );
415 connect( &model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
416 q, SIGNAL(changed()) );
417 connect( &model, SIGNAL(rowsInserted(QModelIndex,
int,
int)),
418 q, SIGNAL(changed()) );
419 connect( &model, SIGNAL(rowsRemoved(QModelIndex,
int,
int)),
420 q, SIGNAL(changed()) );
421 connect( ui.treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
422 q, SLOT(slotSelectionChanged()) );
424 slotShowUserAndPasswordToggled(
false );
428 void slotNewClicked() {
429 int row = selectedRow();
432 if ( row < 0 || model.isReadOnlyRow( row ) )
433 if ( protocols & OpenPGPProtocol )
434 slotNewOpenPGPClicked();
435 else if ( protocols & X509Protocol )
436 slotNewX509Clicked();
438 assert( !
"This should not happen.");
440 edit( model.duplicateRow( row ) );
442 void edit(
const QModelIndex & index ) {
443 if ( index.isValid() ) {
444 ui.treeView->clearSelection();
445 ui.treeView->selectionModel()->setCurrentIndex( index, QItemSelectionModel::Select|QItemSelectionModel::Rows );
446 ui.treeView->edit( index );
449 void slotNewX509Clicked() {
450 edit( model.addX509Service( defaultX509Service(),
true ) );
452 void slotNewOpenPGPClicked() {
453 edit( model.addOpenPGPService( defaultOpenPGPService(),
true ) );
455 void slotDeleteClicked() {
456 model.deleteRow( selectedRow() );
458 void slotSelectionChanged() {
459 enableDisableActions();
461 void slotShowUserAndPasswordToggled(
bool on ) {
462 QHeaderView *
const hv = ui.treeView->header();
464 hv->setSectionHidden( Model::UserName, !on );
465 hv->setSectionHidden( Model::Password, !on );
468 int selectedRow()
const {
469 const QModelIndexList mil = ui.treeView->selectionModel()->selectedRows();
470 return mil.empty() ? -1 : mil.front().row();
472 int currentRow()
const {
473 const QModelIndex idx = ui.treeView->selectionModel()->currentIndex();
474 return idx.isValid() ? idx.row() : -1 ;
477 void showHideColumns();
479 void enableDisableActions() {
480 const bool x509 = ( protocols & X509Protocol ) && !( readOnlyProtocols & X509Protocol ) ;
481 const bool pgp = ( protocols & OpenPGPProtocol ) && !( readOnlyProtocols & OpenPGPProtocol ) ;
482 ui.newX509Action.setEnabled( x509 );
483 ui.newOpenPGPAction.setEnabled( pgp );
485 ui.newTB->setMenu( &ui.newMenu );
486 ui.newTB->setPopupMode( QToolButton::MenuButtonPopup );
488 ui.newTB->setMenu( 0 );
489 ui.newTB->setPopupMode( QToolButton::DelayedPopup );
490 ui.newTB->setEnabled( x509 || pgp );
492 const int row = selectedRow();
493 ui.deleteTB->setEnabled( row >= 0 && !model.isReadOnlyRow( row ) );
498 Protocols readOnlyProtocols;
501 struct UI : Ui_DirectoryServicesWidget {
502 QAction newX509Action;
503 QAction newOpenPGPAction;
507 : Ui_DirectoryServicesWidget(),
508 newX509Action( i18nc(
"New X.509 Directory Server",
"X.509"), q ),
509 newOpenPGPAction( i18nc(
"New OpenPGP Directory Server",
"OpenPGP"), q ),
512 newX509Action.setObjectName( QLatin1String(
"newX509Action") );
513 newOpenPGPAction.setObjectName( QLatin1String(
"newOpenPGPAction") );
514 newMenu.setObjectName( QLatin1String(
"newMenu") );
518 connect( &newX509Action, SIGNAL(triggered()), q, SLOT(slotNewX509Clicked()) );
519 connect( &newOpenPGPAction, SIGNAL(triggered()), q, SLOT(slotNewOpenPGPClicked()) );
521 newMenu.addAction( &newX509Action );
522 newMenu.addAction( &newOpenPGPAction );
524 newTB->setMenu( &newMenu );
531 :
QWidget( p, f ), d( new Private( this ) )
542 d->delegate.setAllowedSchemes( schemes );
543 d->showHideColumns();
547 return d->delegate.allowedSchemes();
551 if ( d->protocols == protocols )
553 d->protocols = protocols;
554 d->showHideColumns();
555 d->enableDisableActions();
563 if ( d->readOnlyProtocols == protocols )
565 d->readOnlyProtocols = protocols;
566 d->model.setOpenPGPReadOnly( protocols & OpenPGPProtocol );
567 d->model.setX509ReadOnly( protocols & X509Protocol );
568 d->enableDisableActions();
572 return d->readOnlyProtocols;
576 Q_FOREACH(
const KUrl & url, urls )
577 d->model.addOpenPGPService( url );
582 for (
unsigned int i = 0, end = d->model.numServices() ; i != end ; ++i )
583 if ( d->model.isOpenPGPService( i ) )
584 result.push_back( d->model.service( i ) );
589 Q_FOREACH(
const KUrl & url, urls )
590 d->model.addX509Service( url );
595 for (
unsigned int i = 0, end = d->model.numServices() ; i != end ; ++i )
596 if ( d->model.isX509Service( i ) )
597 result.push_back( d->model.service( i ) );
602 if ( !d->model.numServices() )
608 void DirectoryServicesWidget::Private::showHideColumns() {
609 QHeaderView *
const hv = ui.treeView->header();
612 hv->setSectionHidden( Model::Scheme, protocols == X509Protocol );
614 hv->setSectionHidden( Model::X509, protocols != AllProtocols );
615 hv->setSectionHidden( Model::OpenPGP, protocols != AllProtocols );
622 QVariant Model::headerData(
int section, Qt::Orientation orientation,
int role )
const {
623 if ( orientation == Qt::Horizontal )
624 if ( role == Qt::ToolTipRole )
625 return toolTipForColumn( section );
626 else if ( role == Qt::DisplayRole )
628 case Scheme:
return i18n(
"Scheme");
629 case Host:
return i18n(
"Server Name");
630 case Port:
return i18n(
"Server Port");
631 case BaseDN:
return i18n(
"Base DN");
632 case UserName:
return i18n(
"User Name");
633 case Password:
return i18n(
"Password");
634 case X509:
return i18n(
"X.509");
635 case OpenPGP:
return i18n(
"OpenPGP");
636 default:
return QVariant();
641 return QAbstractTableModel::headerData( section, orientation, role );
644 QVariant Model::data(
const QModelIndex & index,
int role )
const {
645 const unsigned int row = index.row();
646 if ( index.isValid() && row < m_items.size() )
648 case Qt::ToolTipRole: {
649 const QString tt = toolTipForColumn( index.column() );
650 if ( !isReadOnlyRow( index.row() ) )
654 ? i18n(
"(read-only)")
655 : i18nc(
"amended tooltip; %1: original tooltip",
656 "%1 (read-only)", tt );
658 case Qt::DisplayRole:
660 switch ( index.column() ) {
662 return display_scheme( m_items[row].url );
664 return display_host( m_items[row].url );
666 return display_port( m_items[row].url );
668 if ( isLdapRow( row ) )
669 return KUrl::fromPercentEncoding( m_items[row].url.query().mid( 1 ).toLatin1() );
673 return m_items[row].url.user();
675 return m_items[row].url.pass();
681 case Qt::CheckStateRole:
682 switch ( index.column() ) {
684 return m_items[row].x509 && isLdapRow( row ) ? Qt::Checked : Qt::Unchecked ;
686 return m_items[row].pgp ? Qt::Checked : Qt::Unchecked ;
694 bool Model::isLdapRow(
unsigned int row )
const {
695 if ( row >= m_items.size() )
697 return is_ldap_scheme( m_items[row].url );
700 Qt::ItemFlags Model::flags(
const QModelIndex & index )
const {
701 const unsigned int row = index.row();
702 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
703 if ( isReadOnlyRow( row ) )
704 flags &= ~Qt::ItemIsSelectable ;
705 if ( index.isValid() && row < m_items.size() )
706 switch ( index.column() ) {
708 switch ( m_schemes ) {
710 if ( !isReadOnlyRow( row ) )
711 return flags | Qt::ItemIsEditable ;
718 return flags & ~(Qt::ItemIsEditable|Qt::ItemIsEnabled) ;
722 if ( isReadOnlyRow( row ) )
723 return flags & ~(Qt::ItemIsEditable|Qt::ItemIsEnabled) ;
725 return flags | Qt::ItemIsEditable ;
727 if ( isLdapRow( row ) && !isReadOnlyRow( row ) )
728 return flags | Qt::ItemIsEditable ;
730 return flags & ~(Qt::ItemIsEditable|Qt::ItemIsEnabled) ;
733 if ( isReadOnlyRow( row ) )
734 return flags & ~(Qt::ItemIsEditable|Qt::ItemIsEnabled) ;
736 return flags | Qt::ItemIsEditable ;
738 if ( !isLdapRow( row ) )
739 return flags & ~(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled) ;
742 if ( isReadOnlyRow( row ) )
743 return flags & ~(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled) ;
745 return flags | Qt::ItemIsUserCheckable ;
750 bool Model::setData(
const QModelIndex & idx,
const QVariant & value,
int role ) {
751 const unsigned int row = idx.row();
752 if ( !idx.isValid() || row >= m_items.size() )
754 if ( isReadOnlyRow( row ) )
756 if ( !doSetData( row, idx.column(), value, role ) )
758 emit dataChanged( idx, idx );
762 bool Model::doSetData(
unsigned int row,
unsigned int column,
const QVariant & value,
int role ) {
763 if ( role == Qt::EditRole )
766 if ( is_default_port( m_items[row].url ) ) {
768 m_items[row].url.setPort( -1 );
769 const QModelIndex changed = index( row, Port );
770 emit dataChanged( changed, changed );
772 m_items[row].url.setProtocol( value.toString() );
775 if ( display_host( m_items[row].url ) != m_items[row].url.host() ) {
776 m_items[row].url.setProtocol( display_scheme( m_items[row].url ) );
777 m_items[row].url.setPath( QLatin1String(
"/") );
779 m_items[row].url.setHost( value.toString() );
782 if ( value.toUInt() == default_port( display_scheme( m_items[row].url ) ) )
783 m_items[row].url.setPort( -1 );
785 m_items[row].url.setPort( value.toUInt() );
788 if ( value.toString().isEmpty() ) {
789 m_items[row].url.setPath(
QString() );
790 m_items[row].url.setQuery(
QString() );
792 m_items[row].url.setPath( QLatin1String(
"/") );
793 m_items[row].url.setQuery( value.toString() );
797 m_items[row].url.setUserName( value.toString() );
800 m_items[row].url.setPassword( value.toString() );
803 if ( role == Qt::CheckStateRole )
806 m_items[row].x509 = value.toInt() == Qt::Checked ;
810 const bool on = value.toInt() == Qt::Checked ;
812 setExclusivePgpFlag( row );
814 m_items[row].pgp =
false;
821 void Model::setExclusivePgpFlag(
unsigned int row ) {
822 if ( row >= m_items.size() || m_items[row].pgp )
824 m_items[row].pgp =
true;
825 for (
unsigned int i = 0, end = m_items.size() ; i < end ; ++i )
827 if ( m_items[i].pgp ) {
828 m_items[i].pgp =
false;
829 const QModelIndex changed = index( i, OpenPGP );
830 emit dataChanged( changed, changed );
836 QString Model::toolTipForColumn(
int column ) {
838 case Scheme:
return i18n(
"Select the access protocol (scheme) that the "
839 "directory service is available through.");
840 case Host:
return i18n(
"Enter the name or IP address of the server "
841 "hosting the directory service.");
842 case Port:
return i18n(
"<b>(Optional, the default is fine in most cases)</b> "
843 "Pick the port number the directory service is "
845 case BaseDN:
return i18n(
"<b>(Only for LDAP)</b> "
846 "Enter the base DN for this LDAP server to "
847 "limit searches to only that subtree of the directory.");
848 case UserName:
return i18n(
"<b>(Optional)</b> "
849 "Enter your user name here, if needed.");
850 case Password:
return i18n(
"<b>(Optional, not recommended)</b> "
851 "Enter your password here, if needed. "
852 "Note that the password will be saved in the clear "
853 "in a config file in your home directory.");
854 case X509:
return i18n(
"Check this column if this directory service is "
855 "providing S/MIME (X.509) certificates.");
856 case OpenPGP:
return i18n(
"Check this column if this directory service is "
857 "providing OpenPGP certificates.");
863 #include "directoryserviceswidget.moc"
864 #include "moc_directoryserviceswidget.cpp"