QGIS API Documentation  2.12.0-Lyon
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;
69  bool fillParam, outlineParam, outlineWidthParam;
70  bool hasDefaultFillColor = false, hasDefaultOutlineColor = false, hasDefaultOutlineWidth = false;
71  QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
72  outlineParam, hasDefaultOutlineColor, outline,
73  outlineWidthParam, hasDefaultOutlineWidth, outlineWidth );
74 
75  //if defaults not set in symbol, use these values
76  if ( !hasDefaultFillColor )
77  fill = QColor( 200, 200, 200 );
78  if ( !hasDefaultOutlineColor )
79  outline = Qt::black;
80  if ( !hasDefaultOutlineWidth )
81  outlineWidth = 0.2;
82 
83  bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
84  const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
85  pixmap = QPixmap::fromImage( img );
86  QPixmapCache::insert( entry, pixmap );
87  }
88 
89  return pixmap;
90  }
91  else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
92  {
93  return entry;
94  }
95 
96  return QVariant();
97 }
98 
99 
100 //--- QgsSvgSelectorGroupsModel
101 
103  : QStandardItemModel( parent )
104 {
106  QStandardItem *parentItem = invisibleRootItem();
107 
108  for ( int i = 0; i < svgPaths.size(); i++ )
109  {
110  QDir dir( svgPaths[i] );
111  QStandardItem *baseGroup;
112 
113  if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
114  {
115  baseGroup = new QStandardItem( tr( "App Symbols" ) );
116  }
117  else if ( dir.path().contains( QgsApplication::qgisSettingsDirPath() ) )
118  {
119  baseGroup = new QStandardItem( tr( "User Symbols" ) );
120  }
121  else
122  {
123  baseGroup = new QStandardItem( dir.dirName() );
124  }
125  baseGroup->setData( QVariant( svgPaths[i] ) );
126  baseGroup->setEditable( false );
127  baseGroup->setCheckable( false );
128  baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
129  baseGroup->setToolTip( dir.path() );
130  parentItem->appendRow( baseGroup );
131  createTree( baseGroup );
132  QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
133  }
134 }
135 
136 void QgsSvgSelectorGroupsModel::createTree( QStandardItem* &parentGroup )
137 {
138  QDir parentDir( parentGroup->data().toString() );
139  Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
140  {
141  QStandardItem* group = new QStandardItem( item );
142  group->setData( QVariant( parentDir.path() + "/" + item ) );
143  group->setEditable( false );
144  group->setCheckable( false );
145  group->setToolTip( parentDir.path() + "/" + item );
146  group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
147  parentGroup->appendRow( group );
148  createTree( group );
149  }
150 }
151 
152 
153 //-- QgsSvgSelectorWidget
154 
156  : QWidget( parent )
157 {
158  // TODO: in-code gui setup with option to vertically or horizontally stack SVG groups/images widgets
159  setupUi( this );
160 
161  mGroupsTreeView->setHeaderHidden( true );
162  populateList();
163 
164  connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
165  this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
166  connect( mGroupsTreeView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
167  this, SLOT( populateIcons( const QModelIndex& ) ) );
168 
169  QSettings settings;
170  bool useRelativePath = ( QgsProject::instance()->readBoolEntry( "Paths", "/Absolute", false )
171  || settings.value( "/Windows/SvgSelectorWidget/RelativePath" ).toBool() );
172  mRelativePathChkBx->setChecked( useRelativePath );
173 }
174 
176 {
177  QSettings settings;
178  settings.setValue( "/Windows/SvgSelectorWidget/RelativePath", mRelativePathChkBx->isChecked() );
179 }
180 
182 {
183  QString updatedPath( "" );
184 
185  // skip possible urls, excepting those that may locally resolve
186  if ( !svgPath.contains( "://" ) || ( svgPath.contains( "file://", Qt::CaseInsensitive ) ) )
187  {
188  QString resolvedPath = QgsSymbolLayerV2Utils::symbolNameToPath( svgPath.trimmed() );
189  if ( !resolvedPath.isNull() )
190  {
191  updatedPath = resolvedPath;
192  }
193  }
194 
195  mCurrentSvgPath = updatedPath;
196 
197  mFileLineEdit->blockSignals( true );
198  mFileLineEdit->setText( updatedPath );
199  mFileLineEdit->blockSignals( false );
200 
201  mImagesListView->selectionModel()->blockSignals( true );
202  QAbstractItemModel* m = mImagesListView->model();
203  QItemSelectionModel* selModel = mImagesListView->selectionModel();
204  for ( int i = 0; i < m->rowCount(); i++ )
205  {
206  QModelIndex idx( m->index( i, 0 ) );
207  if ( m->data( idx ).toString() == svgPath )
208  {
209  selModel->select( idx, QItemSelectionModel::SelectCurrent );
210  selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
211  mImagesListView->scrollTo( idx );
212  break;
213  }
214  }
215  mImagesListView->selectionModel()->blockSignals( false );
216 }
217 
219 {
220  if ( mRelativePathChkBx->isChecked() )
221  return currentSvgPathToName();
222 
223  return mCurrentSvgPath;
224 }
225 
227 {
228  return QgsSymbolLayerV2Utils::symbolPathToName( mCurrentSvgPath );
229 }
230 
231 void QgsSvgSelectorWidget::updateCurrentSvgPath( const QString& svgPath )
232 {
233  mCurrentSvgPath = svgPath;
234  emit svgSelected( currentSvgPath() );
235 }
236 
237 void QgsSvgSelectorWidget::svgSelectionChanged( const QModelIndex& idx )
238 {
239  QString filePath = idx.data( Qt::UserRole ).toString();
240  mFileLineEdit->setText( filePath );
241  updateCurrentSvgPath( filePath );
242 }
243 
244 void QgsSvgSelectorWidget::populateIcons( const QModelIndex& idx )
245 {
246  QString path = idx.data( Qt::UserRole + 1 ).toString();
247 
248  QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView, path );
249  mImagesListView->setModel( m );
250 
251  connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
252  this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
253 }
254 
255 void QgsSvgSelectorWidget::on_mFilePushButton_clicked()
256 {
257  QSettings settings;
258  QString openDir = settings.value( "/UI/lastSVGMarkerDir", "." ).toString();
259 
260  QString lineEditText = mFileLineEdit->text();
261  if ( !lineEditText.isEmpty() )
262  {
263  QFileInfo openDirFileInfo( lineEditText );
264  openDir = openDirFileInfo.path();
265  }
266 
268  tr( "Select SVG file" ),
269  openDir,
270  tr( "SVG files" ) + " (*.svg *.SVG)" );
271 
272  activateWindow(); // return window focus
273 
274  if ( file.isNull() )
275  return;
276 
277  QFileInfo fi( file );
278  if ( !fi.exists() || !fi.isReadable() )
279  {
280  updateCurrentSvgPath( QString() );
281  updateLineEditFeedback( false );
282  return;
283  }
284  settings.setValue( "/UI/lastSVGMarkerDir", fi.absolutePath() );
285  mFileLineEdit->setText( file );
286  updateCurrentSvgPath( file );
287 }
288 
289 void QgsSvgSelectorWidget::updateLineEditFeedback( bool ok, const QString& tip )
290 {
291  // draw red text for path field if invalid (path can't be resolved)
292  mFileLineEdit->setStyleSheet( QString( !ok ? "QLineEdit{ color: rgb(225, 0, 0); }" : "" ) );
293  mFileLineEdit->setToolTip( !ok ? tr( "File not found" ) : tip );
294 }
295 
296 void QgsSvgSelectorWidget::on_mFileLineEdit_textChanged( const QString& text )
297 {
298  QString resolvedPath = QgsSymbolLayerV2Utils::symbolNameToPath( text );
299  bool validSVG = !resolvedPath.isNull();
300 
301  updateLineEditFeedback( validSVG, resolvedPath );
302  updateCurrentSvgPath( validSVG ? resolvedPath : QString() );
303 }
304 
306 {
307  QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( mGroupsTreeView );
308  mGroupsTreeView->setModel( g );
309  // Set the tree expanded at the first level
310  int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
311  for ( int i = 0; i < rows; i++ )
312  {
313  mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
314  }
315 
316  // Initally load the icons in the List view without any grouping
317  QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView );
318  mImagesListView->setModel( m );
319 }
320 
321 //-- QgsSvgSelectorDialog
322 
324  const QDialogButtonBox::StandardButtons& buttons,
325  Qt::Orientation orientation )
326  : QDialog( parent, fl )
327 {
328  // TODO: pass 'orientation' to QgsSvgSelectorWidget for customizing its layout, once implemented
329  Q_UNUSED( orientation );
330 
331  // create buttonbox
332  mButtonBox = new QDialogButtonBox( buttons, orientation, this );
333  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
334  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
335 
336  setMinimumSize( 480, 320 );
337 
338  // dialog's layout
339  mLayout = new QVBoxLayout();
340  mSvgSelector = new QgsSvgSelectorWidget( this );
342 
344  setLayout( mLayout );
345 
346  QSettings settings;
347  restoreGeometry( settings.value( "/Windows/SvgSelectorDialog/geometry" ).toByteArray() );
348 }
349 
351 {
352  QSettings settings;
353  settings.setValue( "/Windows/SvgSelectorDialog/geometry", saveGeometry() );
354 }
QgsSvgSelectorWidget * mSvgSelector
void setToolTip(const QString &toolTip)
QByteArray toByteArray() const
static unsigned index
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
void setIcon(const QIcon &icon)
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user'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.
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=0) const
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'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)
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'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()
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()
access to canonical QgsProject instance
Definition: qgsproject.cpp:353
QPixmap * find(const QString &key)
virtual int rowCount(const QModelIndex &parent) const
QgsSvgSelectorWidget(QWidget *parent=0)
static QStringList svgPaths()
Returns the pathes to svg directories.
typedef WindowFlags
void setCurrentIndex(const QModelIndex &index, QFlags< QItemSelectionModel::SelectionFlag > command)
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)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString toString() const
void setCheckable(bool checkable)
QgsSvgSelectorDialog(QWidget *parent=0, const Qt::WindowFlags &fl=QgisGui::ModalDialogFlags, const QDialogButtonBox::StandardButtons &buttons=QDialogButtonBox::Close|QDialogButtonBox::Ok, Qt::Orientation orientation=Qt::Horizontal)
virtual QVariant data(int role) const
void setEditable(bool editable)