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