QGIS API Documentation  2.17.0-Master (8784312)
qgssvgselectorwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssvgselectorwidget.cpp - group and preview selector for SVG files
3  built off of work in qgssymbollayerv2widget
4 
5  ---------------------
6  begin : April 2, 2013
7  copyright : (C) 2013 by Larry Shaffer
8  email : larrys at dakcarto dot com
9  ***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include "qgssvgselectorwidget.h"
18 
19 #include "qgsapplication.h"
20 #include "qgslogger.h"
21 #include "qgsproject.h"
22 #include "qgssvgcache.h"
23 #include "qgssymbollayerv2utils.h"
24 
25 #include <QAbstractListModel>
26 #include <QCheckBox>
27 #include <QDir>
28 #include <QFileDialog>
29 #include <QModelIndex>
30 #include <QPixmapCache>
31 #include <QSettings>
32 #include <QStyle>
33 #include <QTime>
34 
35 
36 //--- QgsSvgSelectorListModel
37 
39  : QAbstractListModel( parent )
40 {
42 }
43 
44 // Constructor to create model for icons in a specific path
46  : QAbstractListModel( parent )
47 {
49 }
50 
52 {
53  Q_UNUSED( parent );
54  return mSvgFiles.count();
55 }
56 
58 {
59  QString entry = mSvgFiles.at( index.row() );
60 
61  if ( role == Qt::DecorationRole ) // icon
62  {
63  QPixmap pixmap;
64  if ( !QPixmapCache::find( entry, pixmap ) )
65  {
66  // render SVG file
67  QColor fill, outline;
68  double outlineWidth, fillOpacity, outlineOpacity;
69  bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
70  bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
71  hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
72  QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
73  fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
74  outlineParam, hasDefaultOutlineColor, outline,
75  outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
76  outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
77 
78  //if defaults not set in symbol, use these values
79  if ( !hasDefaultFillColor )
80  fill = QColor( 200, 200, 200 );
81  fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
82  if ( !hasDefaultOutlineColor )
83  outline = Qt::black;
84  outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
85  if ( !hasDefaultOutlineWidth )
86  outlineWidth = 0.2;
87 
88  bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
89  const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
90  pixmap = QPixmap::fromImage( img );
91  QPixmapCache::insert( entry, pixmap );
92  }
93 
94  return pixmap;
95  }
96  else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
97  {
98  return entry;
99  }
100 
101  return QVariant();
102 }
103 
104 
105 //--- QgsSvgSelectorGroupsModel
106 
108  : QStandardItemModel( parent )
109 {
111  QStandardItem *parentItem = invisibleRootItem();
112 
113  for ( int i = 0; i < svgPaths.size(); i++ )
114  {
115  QDir dir( svgPaths[i] );
116  QStandardItem *baseGroup;
117 
118  if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
119  {
120  baseGroup = new QStandardItem( tr( "App Symbols" ) );
121  }
122  else if ( dir.path().contains( QgsApplication::qgisSettingsDirPath() ) )
123  {
124  baseGroup = new QStandardItem( tr( "User Symbols" ) );
125  }
126  else
127  {
128  baseGroup = new QStandardItem( dir.dirName() );
129  }
130  baseGroup->setData( QVariant( svgPaths[i] ) );
131  baseGroup->setEditable( false );
132  baseGroup->setCheckable( false );
133  baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
134  baseGroup->setToolTip( dir.path() );
135  parentItem->appendRow( baseGroup );
136  createTree( baseGroup );
137  QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
138  }
139 }
140 
141 void QgsSvgSelectorGroupsModel::createTree( QStandardItem* &parentGroup )
142 {
143  QDir parentDir( parentGroup->data().toString() );
144  Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
145  {
146  QStandardItem* group = new QStandardItem( item );
147  group->setData( QVariant( parentDir.path() + '/' + item ) );
148  group->setEditable( false );
149  group->setCheckable( false );
150  group->setToolTip( parentDir.path() + '/' + item );
151  group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
152  parentGroup->appendRow( group );
153  createTree( group );
154  }
155 }
156 
157 
158 //-- QgsSvgSelectorWidget
159 
161  : QWidget( parent )
162 {
163  // TODO: in-code gui setup with option to vertically or horizontally stack SVG groups/images widgets
164  setupUi( this );
165 
166  mGroupsTreeView->setHeaderHidden( true );
167  populateList();
168 
169  connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
170  this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
171  connect( mGroupsTreeView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
172  this, SLOT( populateIcons( const QModelIndex& ) ) );
173 
174  QSettings settings;
175  bool useRelativePath = ( QgsProject::instance()->readBoolEntry( "Paths", "/Absolute", false )
176  || settings.value( "/Windows/SvgSelectorWidget/RelativePath" ).toBool() );
177  mRelativePathChkBx->setChecked( useRelativePath );
178 }
179 
181 {
182  QSettings settings;
183  settings.setValue( "/Windows/SvgSelectorWidget/RelativePath", mRelativePathChkBx->isChecked() );
184 }
185 
187 {
188  QString updatedPath( "" );
189 
190  // skip possible urls, excepting those that may locally resolve
191  if ( !svgPath.contains( "://" ) || ( svgPath.contains( "file://", Qt::CaseInsensitive ) ) )
192  {
193  QString resolvedPath = QgsSymbolLayerV2Utils::symbolNameToPath( svgPath.trimmed() );
194  if ( !resolvedPath.isNull() )
195  {
196  updatedPath = resolvedPath;
197  }
198  }
199 
200  mCurrentSvgPath = updatedPath;
201 
202  mFileLineEdit->blockSignals( true );
203  mFileLineEdit->setText( updatedPath );
204  mFileLineEdit->blockSignals( false );
205 
206  mImagesListView->selectionModel()->blockSignals( true );
207  QAbstractItemModel* m = mImagesListView->model();
208  QItemSelectionModel* selModel = mImagesListView->selectionModel();
209  for ( int i = 0; i < m->rowCount(); i++ )
210  {
211  QModelIndex idx( m->index( i, 0 ) );
212  if ( m->data( idx ).toString() == svgPath )
213  {
214  selModel->select( idx, QItemSelectionModel::SelectCurrent );
215  selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
216  mImagesListView->scrollTo( idx );
217  break;
218  }
219  }
220  mImagesListView->selectionModel()->blockSignals( false );
221 }
222 
224 {
225  if ( mRelativePathChkBx->isChecked() )
226  return currentSvgPathToName();
227 
228  return mCurrentSvgPath;
229 }
230 
232 {
233  return QgsSymbolLayerV2Utils::symbolPathToName( mCurrentSvgPath );
234 }
235 
236 void QgsSvgSelectorWidget::updateCurrentSvgPath( const QString& svgPath )
237 {
238  mCurrentSvgPath = svgPath;
239  emit svgSelected( currentSvgPath() );
240 }
241 
242 void QgsSvgSelectorWidget::svgSelectionChanged( const QModelIndex& idx )
243 {
244  QString filePath = idx.data( Qt::UserRole ).toString();
245  mFileLineEdit->setText( filePath );
246  updateCurrentSvgPath( filePath );
247 }
248 
249 void QgsSvgSelectorWidget::populateIcons( const QModelIndex& idx )
250 {
251  QString path = idx.data( Qt::UserRole + 1 ).toString();
252 
253  QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView, path );
254  mImagesListView->setModel( m );
255 
256  connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
257  this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
258 }
259 
260 void QgsSvgSelectorWidget::on_mFilePushButton_clicked()
261 {
262  QSettings settings;
263  QString openDir = settings.value( "/UI/lastSVGMarkerDir", QDir::homePath() ).toString();
264 
265  QString lineEditText = mFileLineEdit->text();
266  if ( !lineEditText.isEmpty() )
267  {
268  QFileInfo openDirFileInfo( lineEditText );
269  openDir = openDirFileInfo.path();
270  }
271 
272  QString file = QFileDialog::getOpenFileName( nullptr,
273  tr( "Select SVG file" ),
274  openDir,
275  tr( "SVG files" ) + " (*.svg *.SVG)" );
276 
277  activateWindow(); // return window focus
278 
279  if ( file.isNull() )
280  return;
281 
282  QFileInfo fi( file );
283  if ( !fi.exists() || !fi.isReadable() )
284  {
285  updateCurrentSvgPath( QString() );
286  updateLineEditFeedback( false );
287  return;
288  }
289  settings.setValue( "/UI/lastSVGMarkerDir", fi.absolutePath() );
290  mFileLineEdit->setText( file );
291  updateCurrentSvgPath( file );
292 }
293 
294 void QgsSvgSelectorWidget::updateLineEditFeedback( bool ok, const QString& tip )
295 {
296  // draw red text for path field if invalid (path can't be resolved)
297  mFileLineEdit->setStyleSheet( QString( !ok ? "QLineEdit{ color: rgb(225, 0, 0); }" : "" ) );
298  mFileLineEdit->setToolTip( !ok ? tr( "File not found" ) : tip );
299 }
300 
301 void QgsSvgSelectorWidget::on_mFileLineEdit_textChanged( const QString& text )
302 {
303  QString resolvedPath = QgsSymbolLayerV2Utils::symbolNameToPath( text );
304  bool validSVG = !resolvedPath.isNull();
305 
306  updateLineEditFeedback( validSVG, resolvedPath );
307  updateCurrentSvgPath( validSVG ? resolvedPath : QString() );
308 }
309 
311 {
312  QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( mGroupsTreeView );
313  mGroupsTreeView->setModel( g );
314  // Set the tree expanded at the first level
315  int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
316  for ( int i = 0; i < rows; i++ )
317  {
318  mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
319  }
320 
321  // Initally load the icons in the List view without any grouping
322  QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView );
323  mImagesListView->setModel( m );
324 }
325 
326 //-- QgsSvgSelectorDialog
327 
329  const QDialogButtonBox::StandardButtons& buttons,
330  Qt::Orientation orientation )
331  : QDialog( parent, fl )
332 {
333  // TODO: pass 'orientation' to QgsSvgSelectorWidget for customizing its layout, once implemented
334  Q_UNUSED( orientation );
335 
336  // create buttonbox
337  mButtonBox = new QDialogButtonBox( buttons, orientation, this );
338  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
339  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
340 
341  setMinimumSize( 480, 320 );
342 
343  // dialog's layout
344  mLayout = new QVBoxLayout();
345  mSvgSelector = new QgsSvgSelectorWidget( this );
347 
349  setLayout( mLayout );
350 
351  QSettings settings;
352  restoreGeometry( settings.value( "/Windows/SvgSelectorDialog/geometry" ).toByteArray() );
353 }
354 
356 {
357  QSettings settings;
358  settings.setValue( "/Windows/SvgSelectorDialog/geometry", saveGeometry() );
359 }
QgsSvgSelectorWidget * mSvgSelector
bool isReadable() const
void setToolTip(const QString &toolTip)
QByteArray toByteArray() const
virtual int rowCount(const QModelIndex &parent) const =0
void setupUi(QWidget *widget)
virtual void reject()
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const =0
QString path() const
void setIcon(const QIcon &icon)
QgsSvgSelectorWidget(QWidget *parent=nullptr)
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user&#39;s home dir.
QStandardItem * invisibleRootItem() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QString currentSvgPath() const
void rejected()
const T & at(int i) const
QgsSvgSelectorGroupsModel(QObject *parent)
void accepted()
QPixmap fromImage(const QImage &image, QFlags< Qt::ImageConversionFlag > flags)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
static QStringList listSvgFilesAt(const QString &directory)
Return a list of svg files at the specified directory.
QString homePath()
QString tr(const char *sourceText, const char *disambiguation, int n)
int size() const
virtual void setData(const QVariant &value, int role)
bool isNull() const
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasOutlineParam, QColor &defaultOutlineColor, bool &hasOutlineWidthParam, double &defaultOutlineWidth) const
Tests if an svg file contains parameters for fill, outline color, outline width.
static QgsSvgCache * instance()
Definition: qgssvgcache.cpp:97
void setValue(const QString &key, const QVariant &value)
int rowCount(const QModelIndex &parent=QModelIndex()) const override
const QImage & svgAsImage(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor, bool &fitsInCache)
Get SVG as QImage.
void setMinimumSize(const QSize &)
static QString symbolPathToName(QString path)
Get symbols&#39;s name from its path.
QString currentSvgPathToName() const
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
int count(const T &value) const
void setLayout(QLayout *layout)
QModelIndex indexFromItem(const QStandardItem *item) const
QDialogButtonBox * mButtonBox
QString path() const
bool restoreGeometry(const QByteArray &geometry)
void svgSelected(const QString &path)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
void appendRow(const QList< QStandardItem * > &items)
static QStringList listSvgFiles()
Return a list of all available svg files.
virtual void select(const QModelIndex &index, QFlags< QItemSelectionModel::SelectionFlag > command)
bool isEmpty() const
QString trimmed() const
int row() const
static QString symbolNameToPath(QString name)
Get symbol&#39;s path from its name.
virtual QVariant data(const QModelIndex &index, int role) const =0
void setSvgPath(const QString &svgPath)
Accepts absolute and relative paths.
virtual void accept()
QgsSvgSelectorDialog(QWidget *parent=nullptr, const Qt::WindowFlags &fl=QgisGui::ModalDialogFlags, const QDialogButtonBox::StandardButtons &buttons=QDialogButtonBox::Close|QDialogButtonBox::Ok, Qt::Orientation orientation=Qt::Horizontal)
bool exists() const
QStandardItem * item(int row, int column) const
static QString pkgDataPath()
Returns the common root path of all application data directories.
bool contains(QChar ch, Qt::CaseSensitivity cs) const
QgsSvgSelectorListModel(QObject *parent)
QVariant value(const QString &key, const QVariant &defaultValue) const
QString dirName() const
typedef StandardButtons
QVariant data(int role) const
void activateWindow()
QByteArray saveGeometry() const
QStyle * style()
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
QPixmap * find(const QString &key)
virtual int rowCount(const QModelIndex &parent) const
static QStringList svgPaths()
Returns the pathes to svg directories.
void setAlphaF(qreal alpha)
typedef WindowFlags
void setCurrentIndex(const QModelIndex &index, QFlags< QItemSelectionModel::SelectionFlag > command)
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=nullptr) const
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
bool insert(const QString &key, const QPixmap &pixmap)
QString absolutePath() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const
QString toString() const
void setCheckable(bool checkable)
virtual QVariant data(int role) const
void setEditable(bool editable)