QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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  const QString file = QFileDialog::getOpenFileName( nullptr,
144  selectFileTitle(),
145  defaultPath(),
146  fileFilter() );
147  const 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  const QString file = QFileDialog::getOpenFileName( nullptr,
178  embedFileTitle(),
179  defaultPath(),
180  fileFilter() );
181  const 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  const QByteArray blob = fileSource.readAll();
197  const 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  const QString file = QFileDialog::getSaveFileName( nullptr,
217  extractFileTitle(),
218  defaultPath(),
219  fileFilter() );
220  if ( file.isEmpty() )
221  {
222  return;
223  }
224 
225  const QFileInfo fi( file );
226  s.setValue( settingsKey(), fi.absolutePath() );
227 
228  // decode current base64 embedded file
229  const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
230  const 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  const 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  }
309  }
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  return tr( "Select Image File" );
321  }
323 }
324 
325 QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
326 {
327  switch ( mFormat )
328  {
329  case Svg:
330  return tr( "SVG From URL" );
331  case Image:
332  return tr( "Image From URL" );
333  }
335 }
336 
337 QString QgsPictureSourceLineEditBase::fileFromUrlText() const
338 {
339  switch ( mFormat )
340  {
341  case Svg:
342  return tr( "Enter SVG URL" );
343  case Image:
344  return tr( "Enter image URL" );
345  }
347 }
348 
349 QString QgsPictureSourceLineEditBase::embedFileTitle() const
350 {
351  switch ( mFormat )
352  {
353  case Svg:
354  return tr( "Embed SVG File" );
355  case Image:
356  return tr( "Embed Image File" );
357  }
359 }
360 
361 QString QgsPictureSourceLineEditBase::extractFileTitle() const
362 {
363  switch ( mFormat )
364  {
365  case Svg:
366  return tr( "Extract SVG File" );
367  case Image:
368  return tr( "Extract Image File" );
369  }
371 }
372 
373 QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
374 {
375  switch ( mFormat )
376  {
377  case Svg:
378  return QStringLiteral( "/UI/lastSVGDir" );
379  case Image:
380  return QStringLiteral( "/UI/lastImageDir" );
381  }
383 }
384 
386 
387 
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 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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
#define BUILTIN_UNREACHABLE
Definition: qgis.h:1829