QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsfilecontentsourcelineedit.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfilecontentsourcelineedit.cpp
3  -----------------------
4  begin : July 2018
5  copyright : (C) 2018 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 
17 #include "qgssettings.h"
18 #include "qgsmessagebar.h"
19 #include "qgsfilterlineedit.h"
21 
22 #include <QFileDialog>
23 #include <QHBoxLayout>
24 #include <QImageReader>
25 #include <QInputDialog>
26 #include <QLineEdit>
27 #include <QMenu>
28 #include <QToolButton>
29 #include <QUrl>
30 
31 //
32 // QgsAbstractFileContentSourceLineEdit
33 //
34 
36  : QWidget( parent )
37 {
38  QHBoxLayout *layout = new QHBoxLayout( this );
39  layout->setContentsMargins( 0, 0, 0, 0 );
40  mFileLineEdit = new QgsFilterLineEdit( this );
41  mFileLineEdit->setShowClearButton( true );
42  mFileToolButton = new QToolButton( this );
43  mFileToolButton->setText( QString( QChar( 0x2026 ) ) );
44  mPropertyOverrideButton = new QgsPropertyOverrideButton( this );
45  layout->addWidget( mFileLineEdit, 1 );
46  layout->addWidget( mFileToolButton );
47  layout->addWidget( mPropertyOverrideButton );
48  setLayout( layout );
49 
50  QMenu *sourceMenu = new QMenu( mFileToolButton );
51 
52  QAction *selectFileAction = new QAction( tr( "Select File…" ), sourceMenu );
53  connect( selectFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
54  sourceMenu->addAction( selectFileAction );
55 
56  QAction *embedFileAction = new QAction( tr( "Embed File…" ), sourceMenu );
57  connect( embedFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::embedFile );
58  sourceMenu->addAction( embedFileAction );
59 
60  QAction *extractFileAction = new QAction( tr( "Extract Embedded File…" ), sourceMenu );
61  connect( extractFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::extractFile );
62  sourceMenu->addAction( extractFileAction );
63 
64  connect( sourceMenu, &QMenu::aboutToShow, this, [this, extractFileAction]
65  {
66  extractFileAction->setEnabled( mMode == ModeBase64 );
67  } );
68 
69  QAction *enterUrlAction = new QAction( tr( "From URL…" ), sourceMenu );
70  connect( enterUrlAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectUrl );
71  sourceMenu->addAction( enterUrlAction );
72 
73  mFileToolButton->setMenu( sourceMenu );
74  mFileToolButton->setPopupMode( QToolButton::MenuButtonPopup );
75  connect( mFileToolButton, &QToolButton::clicked, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
76 
77  connect( mFileLineEdit, &QLineEdit::textEdited, this, &QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited );
78  connect( mFileLineEdit, &QgsFilterLineEdit::cleared, this, [ = ]
79  {
80  mMode = ModeFile;
81  mFileLineEdit->setPlaceholderText( QString() );
82  mBase64.clear();
83  emit sourceChanged( QString() );
84  } );
85 
86  mPropertyOverrideButton->setVisible( mPropertyOverrideButtonVisible );
87 
88 }
89 
91 {
92  switch ( mMode )
93  {
94  case ModeFile:
95  return mFileLineEdit->text();
96 
97  case ModeBase64:
98  return mBase64;
99  }
100 
101  return QString();
102 }
103 
105 {
106  mLastPathKey = key;
107 }
108 
110 {
111  mPropertyOverrideButtonVisible = visible;
112  mPropertyOverrideButton->setVisible( visible );
113 }
114 
116 {
117  const bool isBase64 = source.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive );
118 
119  if ( ( !isBase64 && source == mFileLineEdit->text() && mBase64.isEmpty() ) || ( isBase64 && source == mBase64 ) )
120  return;
121 
122  if ( isBase64 )
123  {
124  mMode = ModeBase64;
125  mBase64 = source;
126  mFileLineEdit->clear();
127  mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
128  }
129  else
130  {
131  mMode = ModeFile;
132  mBase64.clear();
133  mFileLineEdit->setText( source );
134  mFileLineEdit->setPlaceholderText( QString() );
135  }
136 
137  emit sourceChanged( source );
138 }
139 
140 void QgsAbstractFileContentSourceLineEdit::selectFile()
141 {
142  QgsSettings s;
143  QString file = QFileDialog::getOpenFileName( nullptr,
144  selectFileTitle(),
145  defaultPath(),
146  fileFilter() );
147  QFileInfo fi( file );
148  if ( file.isEmpty() || !fi.exists() || file == source() )
149  {
150  return;
151  }
152  mMode = ModeFile;
153  mBase64.clear();
154  mFileLineEdit->setText( file );
155  mFileLineEdit->setPlaceholderText( QString() );
156  s.setValue( settingsKey(), fi.absolutePath() );
157  emit sourceChanged( mFileLineEdit->text() );
158 }
159 
160 void QgsAbstractFileContentSourceLineEdit::selectUrl()
161 {
162  bool ok = false;
163  const QString path = QInputDialog::getText( this, fileFromUrlTitle(), fileFromUrlText(), QLineEdit::Normal, mFileLineEdit->text(), &ok );
164  if ( ok && path != source() )
165  {
166  mMode = ModeFile;
167  mBase64.clear();
168  mFileLineEdit->setText( path );
169  mFileLineEdit->setPlaceholderText( QString() );
170  emit sourceChanged( mFileLineEdit->text() );
171  }
172 }
173 
174 void QgsAbstractFileContentSourceLineEdit::embedFile()
175 {
176  QgsSettings s;
177  QString file = QFileDialog::getOpenFileName( nullptr,
178  embedFileTitle(),
179  defaultPath(),
180  fileFilter() );
181  QFileInfo fi( file );
182  if ( file.isEmpty() || !fi.exists() )
183  {
184  return;
185  }
186 
187  s.setValue( settingsKey(), fi.absolutePath() );
188 
189  // encode file as base64
190  QFile fileSource( file );
191  if ( !fileSource.open( QIODevice::ReadOnly ) )
192  {
193  return;
194  }
195 
196  QByteArray blob = fileSource.readAll();
197  QByteArray encoded = blob.toBase64();
198 
199  QString path( encoded );
200  path.prepend( QLatin1String( "base64:" ) );
201  if ( path == source() )
202  return;
203 
204  mBase64 = path;
205  mMode = ModeBase64;
206 
207  mFileLineEdit->clear();
208  mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
209 
210  emit sourceChanged( path );
211 }
212 
213 void QgsAbstractFileContentSourceLineEdit::extractFile()
214 {
215  QgsSettings s;
216  QString file = QFileDialog::getSaveFileName( nullptr,
217  extractFileTitle(),
218  defaultPath(),
219  fileFilter() );
220  if ( file.isEmpty() )
221  {
222  return;
223  }
224 
225  QFileInfo fi( file );
226  s.setValue( settingsKey(), fi.absolutePath() );
227 
228  // decode current base64 embedded file
229  QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
230  QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
231 
232  QFile fileOut( file );
233  fileOut.open( QIODevice::WriteOnly );
234  fileOut.write( decoded );
235  fileOut.close();
236 
237  if ( mMessageBar )
238  {
239  mMessageBar->pushMessage( extractFileTitle(),
240  tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ),
241  Qgis::MessageLevel::Success, 0 );
242  }
243 }
244 
245 void QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
246 {
247  mFileLineEdit->setPlaceholderText( QString() );
248  mBase64.clear();
249  mMode = ModeFile;
250  if ( !text.isEmpty() && !QFileInfo::exists( text ) )
251  {
252  QUrl url( text );
253  if ( !url.isValid() )
254  {
255  return;
256  }
257  }
258  emit sourceChanged( text );
259 }
260 
261 QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
262 {
263  if ( QFileInfo::exists( source() ) )
264  return source();
265 
266  return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
267 }
268 
269 QString QgsAbstractFileContentSourceLineEdit::settingsKey() const
270 {
271  return mLastPathKey.isEmpty() ? defaultSettingsKey() : mLastPathKey;
272 }
273 
275 {
276  mMessageBar = bar;
277 }
278 
280 {
281  return mMessageBar;
282 }
283 
284 
285 
286 //
287 // QgsPictureSourceLineEditBase
288 //
289 
291 
292 
293 QString QgsPictureSourceLineEditBase::fileFilter() const
294 {
295  switch ( mFormat )
296  {
297  case Svg:
298  return tr( "SVG files" ) + " (*.svg)";
299  case Image:
300  {
301  QStringList formatsFilter;
302  const QByteArrayList supportedFormats = QImageReader::supportedImageFormats();
303  for ( const auto &format : supportedFormats )
304  {
305  formatsFilter.append( QString( QStringLiteral( "*.%1" ) ).arg( QString( format ) ) );
306  }
307  return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Images" ), formatsFilter.join( QLatin1Char( ' ' ) ), tr( "All files" ) );
308  }
310  }
311 }
312 
313 QString QgsPictureSourceLineEditBase::selectFileTitle() const
314 {
315  switch ( mFormat )
316  {
317  case Svg:
318  return tr( "Select SVG File" );
319  case Image:
320  {
321  return tr( "Select Image File" );
322  }
323 
325  }
326 }
327 
328 QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
329 {
330  switch ( mFormat )
331  {
332  case Svg:
333  return tr( "SVG From URL" );
334  case Image:
335  {
336  return tr( "Image From URL" );
337  }
338 
340  }
341 }
342 
343 QString QgsPictureSourceLineEditBase::fileFromUrlText() const
344 {
345  switch ( mFormat )
346  {
347  case Svg:
348  return tr( "Enter SVG URL" );
349  case Image:
350  {
351  return tr( "Enter image URL" );
352  }
353 
355  }
356 }
357 
358 QString QgsPictureSourceLineEditBase::embedFileTitle() const
359 {
360  switch ( mFormat )
361  {
362  case Svg:
363  return tr( "Embed SVG File" );
364  case Image:
365  {
366  return tr( "Embed Image File" );
367  }
368 
370  }
371 }
372 
373 QString QgsPictureSourceLineEditBase::extractFileTitle() const
374 {
375  switch ( mFormat )
376  {
377  case Svg:
378  return tr( "Extract SVG File" );
379  case Image:
380  {
381  return tr( "Extract Image File" );
382  }
383 
385  }
386 }
387 
388 QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
389 {
390  switch ( mFormat )
391  {
392  case Svg:
393  return QStringLiteral( "/UI/lastSVGDir" );
394  case Image:
395  {
396  return QStringLiteral( "/UI/lastImageDir" );
397  }
399  }
400 }
401 
403 
404 
void setLastPathSettingsKey(const QString &key)
Sets a specific settings key to use when storing the last used path for the file source.
void sourceChanged(const QString &source)
Emitted whenever the file source is changed in the widget.
void setMessageBar(QgsMessageBar *bar)
Sets the message bar associated with the widget.
void setPropertyOverrideToolButtonVisible(bool visible)
Sets the visibility of the property override tool button.
QgsMessageBar * messageBar() const
Returns the message bar associated with the widget.
void setSource(const QString &source)
Sets a new source to show in the widget.
QgsAbstractFileContentSourceLineEdit(QWidget *parent=nullptr)
Constructor for QgsAbstractFileContentSourceLineEdit, with the specified parent widget.
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
void setValue(const QString &value)
Sets the current text for the widget with support for handling null values.
void setShowClearButton(bool visible)
Sets whether the widget's clear button is visible.
void cleared()
Emitted when the widget is cleared.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
A button for controlling property overrides which may apply to a widget.
#define DEFAULT_BUILTIN_UNREACHABLE
Definition: qgis.h:1169