QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscolorschemelist.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorschemelist.cpp
3  ----------------------
4  Date : August 2014
5  Copyright : (C) 2014 by 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 "qgscolorschemelist.h"
17 #include "qgsapplication.h"
18 #include "qgslogger.h"
19 #include "qgssymbollayerv2utils.h"
20 #include "qgscolordialog.h"
21 #include <QPainter>
22 #include <QColorDialog>
23 #include <QMimeData>
24 #include <QClipboard>
25 #include <QKeyEvent>
26 
27 #ifdef ENABLE_MODELTEST
28 #include "modeltest.h"
29 #endif
30 
31 QgsColorSchemeList::QgsColorSchemeList( QWidget *parent, QgsColorScheme *scheme, const QString &context, const QColor &baseColor )
32  : QTreeView( parent )
33  , mScheme( scheme )
34 {
35  mModel = new QgsColorSchemeModel( scheme, context, baseColor, this );
36 #ifdef ENABLE_MODELTEST
37  new ModelTest( mModel, this );
38 #endif
39  setModel( mModel );
40 
41  mSwatchDelegate = new QgsColorSwatchDelegate( this );
42  setItemDelegateForColumn( 0, mSwatchDelegate );
43 
44  setRootIsDecorated( false );
45  setSelectionMode( QAbstractItemView::ExtendedSelection );
46  setSelectionBehavior( QAbstractItemView::SelectRows );
47  setDragEnabled( true );
48  setAcceptDrops( true );
49  setDragDropMode( QTreeView::DragDrop );
50  setDropIndicatorShown( true );
51  setDefaultDropAction( Qt::CopyAction );
52 }
53 
55 {
56 
57 }
58 
59 void QgsColorSchemeList::setScheme( QgsColorScheme *scheme, const QString &context, const QColor &baseColor )
60 {
61  mScheme = scheme;
62  mModel->setScheme( scheme, context, baseColor );
63 }
64 
66 {
67  if ( !mScheme || !mScheme->isEditable() )
68  {
69  return false;
70  }
71 
72  mScheme->setColors( mModel->colors(), mModel->context(), mModel->baseColor() );
73  return true;
74 }
75 
77 {
78  QList<int> rows;
79  foreach ( const QModelIndex &index, selectedIndexes() )
80  {
81  rows << index.row();
82  }
83  //remove duplicates
84  QList<int> rowsToRemove = QList<int>::fromSet( rows.toSet() );
85 
86  //remove rows in descending order
87  qSort( rowsToRemove.begin(), rowsToRemove.end(), qGreater<int>() );
88  foreach ( const int row, rowsToRemove )
89  {
90  mModel->removeRow( row );
91  }
92 }
93 
94 void QgsColorSchemeList::addColor( const QColor &color, const QString &label )
95 {
96  mModel->addColor( color, label );
97 }
98 
100 {
101  QgsNamedColorList pastedColors = QgsSymbolLayerV2Utils::colorListFromMimeData( QApplication::clipboard()->mimeData() );
102 
103  if ( pastedColors.length() == 0 )
104  {
105  //no pasted colors
106  return;
107  }
108 
109  //insert pasted colors
110  QgsNamedColorList::const_iterator colorIt = pastedColors.constBegin();
111  for ( ; colorIt != pastedColors.constEnd(); ++colorIt )
112  {
113  mModel->addColor(( *colorIt ).first, !( *colorIt ).second.isEmpty() ? ( *colorIt ).second : QgsSymbolLayerV2Utils::colorToName(( *colorIt ).first ) );
114  }
115 }
116 
118 {
119  QList<int> rows;
120  foreach ( const QModelIndex &index, selectedIndexes() )
121  {
122  rows << index.row();
123  }
124  //remove duplicates
125  QList<int> rowsToCopy = QList<int>::fromSet( rows.toSet() );
126 
127  QgsNamedColorList colorsToCopy;
128  foreach ( const int row, rowsToCopy )
129  {
130  colorsToCopy << mModel->colors().at( row );
131  }
132 
133  //copy colors
134  QMimeData* mimeData = QgsSymbolLayerV2Utils::colorListToMimeData( colorsToCopy );
135  QApplication::clipboard()->setMimeData( mimeData );
136 }
137 
138 void QgsColorSchemeList::keyPressEvent( QKeyEvent *event )
139 {
140  //listen out for delete/backspace presses and remove selected colors
141  if (( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ) )
142  {
143  QList<int> rows;
144  foreach ( const QModelIndex &index, selectedIndexes() )
145  {
146  rows << index.row();
147  }
148  //remove duplicates
149  QList<int> rowsToRemove = QList<int>::fromSet( rows.toSet() );
150 
151  //remove rows in descending order
152  qSort( rowsToRemove.begin(), rowsToRemove.end(), qGreater<int>() );
153  foreach ( const int row, rowsToRemove )
154  {
155  mModel->removeRow( row );
156  }
157  return;
158  }
159 
160  QTreeView::keyPressEvent( event );
161 }
162 
163 void QgsColorSchemeList::mousePressEvent( QMouseEvent *event )
164 {
165  if ( event->button() == Qt::LeftButton )
166  {
167  //record press start position
168  mDragStartPosition = event->pos();
169  }
171 }
172 
173 void QgsColorSchemeList::mouseReleaseEvent( QMouseEvent *event )
174 {
175  if (( event->button() == Qt::LeftButton ) &&
176  ( event->pos() - mDragStartPosition ).manhattanLength() <= QApplication::startDragDistance() )
177  {
178  //just a click, not a drag
179 
180  //if only one item is selected, emit color changed signal
181  //(if multiple are selected, user probably was interacting with color list rather than trying to pick a color)
182  if ( selectedIndexes().length() == mModel->columnCount() )
183  {
184  QModelIndex selectedColor = selectedIndexes().at( 0 );
185  emit colorSelected( mModel->colors().at( selectedColor.row() ).first );
186  }
187  }
188 
190 }
191 
193 {
194  QgsNamedColorList importedColors;
195  bool ok = false;
196  QString name;
197  importedColors = QgsSymbolLayerV2Utils::importColorsFromGpl( file, ok, name );
198  if ( !ok )
199  {
200  return false;
201  }
202 
203  if ( importedColors.length() == 0 )
204  {
205  //no imported colors
206  return false;
207  }
208 
209  //insert imported colors
210  QgsNamedColorList::const_iterator colorIt = importedColors.constBegin();
211  for ( ; colorIt != importedColors.constEnd(); ++colorIt )
212  {
213  mModel->addColor(( *colorIt ).first, !( *colorIt ).second.isEmpty() ? ( *colorIt ).second : QgsSymbolLayerV2Utils::colorToName(( *colorIt ).first ) );
214  }
215 
216  return true;
217 }
218 
220 {
221  return QgsSymbolLayerV2Utils::saveColorsToGpl( file, QString(), mModel->colors() );
222 }
223 
225 {
226  if ( !mModel )
227  {
228  return false;
229  }
230 
231  return mModel->isDirty();
232 }
233 
234 //
235 // QgsColorSchemeModel
236 //
237 
238 QgsColorSchemeModel::QgsColorSchemeModel( QgsColorScheme *scheme, const QString &context, const QColor &baseColor, QObject *parent )
239  : QAbstractItemModel( parent )
240  , mScheme( scheme )
241  , mContext( context )
242  , mBaseColor( baseColor )
243  , mIsDirty( false )
244 {
245  if ( scheme )
246  {
247  mColors = scheme->fetchColors( context, baseColor );
248  }
249 }
250 
252 {
253 
254 }
255 
256 QModelIndex QgsColorSchemeModel::index( int row, int column, const QModelIndex &parent ) const
257 {
258  if ( column < 0 || column >= columnCount() )
259  {
260  //column out of bounds
261  return QModelIndex();
262  }
263 
264  if ( !parent.isValid() && row >= 0 && row < mColors.size() )
265  {
266  //return an index for the composer item at this position
267  return createIndex( row, column );
268  }
269 
270  //only top level supported
271  return QModelIndex();
272 }
273 
274 QModelIndex QgsColorSchemeModel::parent( const QModelIndex &index ) const
275 {
276  Q_UNUSED( index );
277 
278  //all items are top level
279  return QModelIndex();
280 }
281 
282 int QgsColorSchemeModel::rowCount( const QModelIndex &parent ) const
283 {
284  if ( !parent.isValid() )
285  {
286  return mColors.size();
287  }
288  else
289  {
290  //no children
291  return 0;
292  }
293 }
294 
295 int QgsColorSchemeModel::columnCount( const QModelIndex &parent ) const
296 {
297  Q_UNUSED( parent );
298  return 2;
299 }
300 
301 QVariant QgsColorSchemeModel::data( const QModelIndex &index, int role ) const
302 {
303  if ( !index.isValid() )
304  return QVariant();
305 
306  QPair< QColor, QString > namedColor = mColors.at( index.row() );
307  switch ( role )
308  {
309  case Qt::DisplayRole:
310  case Qt::EditRole:
311  switch ( index.column() )
312  {
313  case ColorSwatch:
314  return namedColor.first;
315  case ColorLabel:
316  return namedColor.second;
317  default:
318  return QVariant();
319  }
320 
321  case Qt::TextAlignmentRole:
322  return QVariant( Qt::AlignLeft | Qt::AlignVCenter );
323 
324  default:
325  return QVariant();
326  }
327 }
328 
329 Qt::ItemFlags QgsColorSchemeModel::flags( const QModelIndex &index ) const
330 {
331  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
332 
333  if ( ! index.isValid() )
334  {
335  return flags | Qt::ItemIsDropEnabled;
336  }
337 
338  switch ( index.column() )
339  {
340  case ColorSwatch:
341  case ColorLabel:
342  if ( mScheme && mScheme->isEditable() )
343  {
344  flags = flags | Qt::ItemIsEditable;
345  }
346  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
347  default:
348  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
349  }
350 }
351 
352 bool QgsColorSchemeModel::setData( const QModelIndex &index, const QVariant &value, int role )
353 {
354  Q_UNUSED( role );
355 
356  if ( !mScheme || !mScheme->isEditable() )
357  return false;
358 
359  if ( !index.isValid() )
360  return false;
361 
362  if ( index.row() >= mColors.length() )
363  return false;
364 
365  switch ( index.column() )
366  {
367  case ColorSwatch:
368  mColors[ index.row()].first = value.value<QColor>();
369  emit dataChanged( index, index );
370  mIsDirty = true;
371  return true;
372 
373  case ColorLabel:
374  mColors[ index.row()].second = value.toString();
375  emit dataChanged( index, index );
376  mIsDirty = true;
377  return true;
378 
379  default:
380  return false;
381  }
382 }
383 
384 QVariant QgsColorSchemeModel::headerData( int section, Qt::Orientation orientation, int role ) const
385 {
386  switch ( role )
387  {
388  case Qt::DisplayRole:
389  {
390  switch ( section )
391  {
392  case ColorSwatch:
393  return tr( "Color" );
394  case ColorLabel:
395  return tr( "Label" );
396  default:
397  return QVariant();
398  }
399  break;
400  }
401 
402  case Qt::TextAlignmentRole:
403  switch ( section )
404  {
405  case ColorSwatch:
406  return QVariant( Qt::AlignHCenter | Qt::AlignVCenter );
407  case ColorLabel:
408  return QVariant( Qt::AlignLeft | Qt::AlignVCenter );
409  default:
410  return QVariant();
411  }
412  default:
413  return QAbstractItemModel::headerData( section, orientation, role );
414  }
415 }
416 
418 {
419  if ( mScheme && mScheme->isEditable() )
420  {
421  return Qt::CopyAction | Qt::MoveAction;
422  }
423  else
424  {
425  return Qt::CopyAction;
426  }
427 }
428 
430 {
431  if ( !mScheme || !mScheme->isEditable() )
432  {
433  return QStringList();
434  }
435 
436  QStringList types;
437  types << "text/xml";
438  types << "text/plain";
439  types << "application/x-color";
440  types << "application/x-colorobject-list";
441  return types;
442 }
443 
444 QMimeData* QgsColorSchemeModel::mimeData( const QModelIndexList &indexes ) const
445 {
446  QgsNamedColorList colorList;
447 
448  QModelIndexList::const_iterator indexIt = indexes.constBegin();
449  for ( ; indexIt != indexes.constEnd(); ++indexIt )
450  {
451  if (( *indexIt ).column() > 0 )
452  continue;
453 
454  colorList << qMakePair( mColors[( *indexIt ).row()].first, mColors[( *indexIt ).row()].second );
455  }
456 
457  QMimeData* mimeData = QgsSymbolLayerV2Utils::colorListToMimeData( colorList );
458  return mimeData;
459 }
460 
461 bool QgsColorSchemeModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
462 {
463  Q_UNUSED( column );
464 
465  if ( !mScheme || !mScheme->isEditable() )
466  {
467  return false;
468  }
469 
470  if ( action == Qt::IgnoreAction )
471  {
472  return true;
473  }
474 
475  if ( parent.isValid() )
476  {
477  return false;
478  }
479 
480  int beginRow = row != -1 ? row : rowCount( QModelIndex() );
482 
483  if ( droppedColors.length() == 0 )
484  {
485  //no dropped colors
486  return false;
487  }
488 
489  //any existing colors? if so, remove them first
490  QgsNamedColorList::const_iterator colorIt = droppedColors.constBegin();
491  for ( ; colorIt != droppedColors.constEnd(); ++colorIt )
492  {
493  //dest color
494  QPair< QColor, QString > color = qMakePair(( *colorIt ).first, !( *colorIt ).second.isEmpty() ? ( *colorIt ).second : QgsSymbolLayerV2Utils::colorToName(( *colorIt ).first ) );
495  //if color already exists, remove it
496  int existingIndex = mColors.indexOf( color );
497  if ( existingIndex >= 0 )
498  {
499  if ( existingIndex < beginRow )
500  {
501  //color is before destination row, so decrease destination row to account for removal
502  beginRow--;
503  }
504 
505  beginRemoveRows( parent, existingIndex, existingIndex );
506  mColors.removeAt( existingIndex );
507  endRemoveRows();
508  }
509  }
510 
511  //insert dropped colors
512  insertRows( beginRow, droppedColors.length(), QModelIndex() );
513  colorIt = droppedColors.constBegin();
514  for ( ; colorIt != droppedColors.constEnd(); ++colorIt )
515  {
516  QModelIndex colorIdx = index( beginRow, 0, QModelIndex() );
517  setData( colorIdx, QVariant(( *colorIt ).first ) );
518  QModelIndex labelIdx = index( beginRow, 1, QModelIndex() );
519  setData( labelIdx, !( *colorIt ).second.isEmpty() ? ( *colorIt ).second : QgsSymbolLayerV2Utils::colorToName(( *colorIt ).first ) );
520  beginRow++;
521  }
522  mIsDirty = true;
523 
524  return true;
525 }
526 
527 void QgsColorSchemeModel::setScheme( QgsColorScheme *scheme, const QString &context, const QColor &baseColor )
528 {
529  mScheme = scheme;
530  mContext = context;
531  mBaseColor = baseColor;
532  mIsDirty = false;
533  beginResetModel();
534  mColors = scheme->fetchColors( mContext, mBaseColor );
535  endResetModel();
536 }
537 
538 bool QgsColorSchemeModel::removeRows( int row, int count, const QModelIndex &parent )
539 {
540  if ( !mScheme || !mScheme->isEditable() )
541  {
542  return false;
543  }
544 
545  if ( parent.isValid() )
546  {
547  return false;
548  }
549 
550  if ( row >= mColors.count() )
551  {
552  return false;
553  }
554 
555  for ( int i = row + count - 1; i >= row; --i )
556  {
557  beginRemoveRows( parent, i, i );
558  mColors.removeAt( i );
559  endRemoveRows();
560  }
561 
562  mIsDirty = true;
563  return true;
564 }
565 
566 bool QgsColorSchemeModel::insertRows( int row, int count, const QModelIndex& parent )
567 {
568  Q_UNUSED( parent );
569 
570  if ( !mScheme || !mScheme->isEditable() )
571  {
572  return false;
573  }
574 
575  beginInsertRows( QModelIndex(), row, row + count - 1 );
576  for ( int i = row; i < row + count; ++i )
577  {
578  QPair< QColor, QString > newColor;
579  mColors.insert( i, newColor );
580  }
581  endInsertRows();
582  mIsDirty = true;
583  return true;
584 }
585 
586 void QgsColorSchemeModel::addColor( const QColor &color, const QString &label )
587 {
588  if ( !mScheme || !mScheme->isEditable() )
589  {
590  return;
591  }
592 
593  //matches existing color? if so, remove it first
594  QPair< QColor, QString > newColor = qMakePair( color, !label.isEmpty() ? label : QgsSymbolLayerV2Utils::colorToName( color ) );
595  //if color already exists, remove it
596  int existingIndex = mColors.indexOf( newColor );
597  if ( existingIndex >= 0 )
598  {
599  beginRemoveRows( QModelIndex(), existingIndex, existingIndex );
600  mColors.removeAt( existingIndex );
601  endRemoveRows();
602  }
603 
604  int row = rowCount();
605  insertRow( row );
606  QModelIndex colorIdx = index( row, 0, QModelIndex() );
607  setData( colorIdx, QVariant( color ) );
608  QModelIndex labelIdx = index( row, 1, QModelIndex() );
609  setData( labelIdx, QVariant( label ) );
610  mIsDirty = true;
611 }
612 
613 
614 //
615 // QgsColorSwatchDelegate
616 //
618  : QAbstractItemDelegate( parent )
619  , mParent( parent )
620 {
621 
622 }
623 
624 void QgsColorSwatchDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
625 {
626  if ( option.state & QStyle::State_Selected )
627  {
628  painter->setPen( QPen( Qt::NoPen ) );
629  if ( option.state & QStyle::State_Active )
630  {
631  painter->setBrush( QBrush( QPalette().highlight() ) );
632  }
633  else
634  {
635  painter->setBrush( QBrush( QPalette().color( QPalette::Inactive,
636  QPalette::Highlight ) ) );
637  }
638  painter->drawRect( option.rect );
639  }
640 
641  QColor color = index.model()->data( index, Qt::DisplayRole ).value<QColor>();
642  if ( !color.isValid() )
643  {
644  return;
645  }
646 
647  QRect rect = option.rect;
648  //center it
649  rect.setLeft( option.rect.center().x() - 15 );
650  rect.setSize( QSize( 30, 30 ) );
651  rect.adjust( 0, 1, 0, 1 );
652  //create an icon pixmap
653  painter->save();
654  painter->setRenderHint( QPainter::Antialiasing );
655  painter->setPen( Qt::NoPen );
656  if ( color.alpha() < 255 )
657  {
658  //start with checkboard pattern
659  QBrush checkBrush = QBrush( transparentBackground() );
660  painter->setBrush( checkBrush );
661  painter->drawRoundedRect( rect, 5, 5 );
662  }
663 
664  //draw semi-transparent color on top
665  painter->setBrush( color );
666  painter->drawRoundedRect( rect, 5, 5 );
667  painter->restore();
668 }
669 
670 const QPixmap& QgsColorSwatchDelegate::transparentBackground() const
671 {
672  static QPixmap transpBkgrd;
673 
674  if ( transpBkgrd.isNull() )
675  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
676 
677  return transpBkgrd;
678 }
679 
680 QSize QgsColorSwatchDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
681 {
682  Q_UNUSED( option );
683  Q_UNUSED( index );
684  return QSize( 30, 32 );
685 }
686 
687 bool QgsColorSwatchDelegate::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
688 {
689  Q_UNUSED( option );
690  if ( event->type() == QEvent::MouseButtonDblClick )
691  {
692  if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
693  {
694  //item not editable
695  return false;
696  }
697  QColor color = index.model()->data( index, Qt::DisplayRole ).value<QColor>();
698  QColor newColor = QgsColorDialogV2::getColor( color, mParent, tr( "Select color" ), true );
699  if ( !newColor.isValid() )
700  {
701  return false;
702  }
703 
704  return model->setData( index, newColor, Qt::EditRole );
705  }
706 
707  return false;
708 }