QGIS API Documentation  3.25.0-Master (6b426f5f8a)
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 #include <QMovie>
31 
32 //
33 // QgsAbstractFileContentSourceLineEdit
34 //
35 
37  : QWidget( parent )
38 {
39  QHBoxLayout *layout = new QHBoxLayout( this );
40  layout->setContentsMargins( 0, 0, 0, 0 );
41  mFileLineEdit = new QgsFilterLineEdit( this );
42  mFileLineEdit->setShowClearButton( true );
43  mFileToolButton = new QToolButton( this );
44  mFileToolButton->setText( QString( QChar( 0x2026 ) ) );
45  mPropertyOverrideButton = new QgsPropertyOverrideButton( this );
46  layout->addWidget( mFileLineEdit, 1 );
47  layout->addWidget( mFileToolButton );
48  layout->addWidget( mPropertyOverrideButton );
49  setLayout( layout );
50 
51  QMenu *sourceMenu = new QMenu( mFileToolButton );
52 
53  QAction *selectFileAction = new QAction( tr( "Select File…" ), sourceMenu );
54  connect( selectFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
55  sourceMenu->addAction( selectFileAction );
56 
57  QAction *embedFileAction = new QAction( tr( "Embed File…" ), sourceMenu );
58  connect( embedFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::embedFile );
59  sourceMenu->addAction( embedFileAction );
60 
61  QAction *extractFileAction = new QAction( tr( "Extract Embedded File…" ), sourceMenu );
62  connect( extractFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::extractFile );
63  sourceMenu->addAction( extractFileAction );
64 
65  connect( sourceMenu, &QMenu::aboutToShow, this, [this, extractFileAction]
66  {
67  extractFileAction->setEnabled( mMode == ModeBase64 );
68  } );
69 
70  QAction *enterUrlAction = new QAction( tr( "From URL…" ), sourceMenu );
71  connect( enterUrlAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectUrl );
72  sourceMenu->addAction( enterUrlAction );
73 
74  mFileToolButton->setMenu( sourceMenu );
75  mFileToolButton->setPopupMode( QToolButton::MenuButtonPopup );
76  connect( mFileToolButton, &QToolButton::clicked, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
77 
78  connect( mFileLineEdit, &QLineEdit::textEdited, this, &QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited );
79  connect( mFileLineEdit, &QgsFilterLineEdit::cleared, this, [ = ]
80  {
81  mMode = ModeFile;
82  mFileLineEdit->setPlaceholderText( QString() );
83  mBase64.clear();
84  emit sourceChanged( QString() );
85  } );
86 
87  mPropertyOverrideButton->setVisible( mPropertyOverrideButtonVisible );
88 
89 }
90 
92 {
93  switch ( mMode )
94  {
95  case ModeFile:
96  return mFileLineEdit->text();
97 
98  case ModeBase64:
99  return mBase64;
100  }
101 
102  return QString();
103 }
104 
106 {
107  mLastPathKey = key;
108 }
109 
111 {
112  mPropertyOverrideButtonVisible = visible;
113  mPropertyOverrideButton->setVisible( visible );
114 }
115 
117 {
118  const bool isBase64 = source.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive );
119 
120  if ( ( !isBase64 && source == mFileLineEdit->text() && mBase64.isEmpty() ) || ( isBase64 && source == mBase64 ) )
121  return;
122 
123  if ( isBase64 )
124  {
125  mMode = ModeBase64;
126  mBase64 = source;
127  mFileLineEdit->clear();
128  mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
129  }
130  else
131  {
132  mMode = ModeFile;
133  mBase64.clear();
134  mFileLineEdit->setText( source );
135  mFileLineEdit->setPlaceholderText( QString() );
136  }
137 
138  emit sourceChanged( source );
139 }
140 
141 void QgsAbstractFileContentSourceLineEdit::selectFile()
142 {
143  QgsSettings s;
144  const QString file = QFileDialog::getOpenFileName( nullptr,
145  selectFileTitle(),
146  defaultPath(),
147  fileFilter() );
148  const QFileInfo fi( file );
149  if ( file.isEmpty() || !fi.exists() || file == source() )
150  {
151  return;
152  }
153  mMode = ModeFile;
154  mBase64.clear();
155  mFileLineEdit->setText( file );
156  mFileLineEdit->setPlaceholderText( QString() );
157  s.setValue( settingsKey(), fi.absolutePath() );
158  emit sourceChanged( mFileLineEdit->text() );
159 }
160 
161 void QgsAbstractFileContentSourceLineEdit::selectUrl()
162 {
163  bool ok = false;
164  const QString path = QInputDialog::getText( this, fileFromUrlTitle(), fileFromUrlText(), QLineEdit::Normal, mFileLineEdit->text(), &ok );
165  if ( ok && path != source() )
166  {
167  mMode = ModeFile;
168  mBase64.clear();
169  mFileLineEdit->setText( path );
170  mFileLineEdit->setPlaceholderText( QString() );
171  emit sourceChanged( mFileLineEdit->text() );
172  }
173 }
174 
175 void QgsAbstractFileContentSourceLineEdit::embedFile()
176 {
177  QgsSettings s;
178  const QString file = QFileDialog::getOpenFileName( nullptr,
179  embedFileTitle(),
180  defaultPath(),
181  fileFilter() );
182  const QFileInfo fi( file );
183  if ( file.isEmpty() || !fi.exists() )
184  {
185  return;
186  }
187 
188  s.setValue( settingsKey(), fi.absolutePath() );
189 
190  // encode file as base64
191  QFile fileSource( file );
192  if ( !fileSource.open( QIODevice::ReadOnly ) )
193  {
194  return;
195  }
196 
197  const QByteArray blob = fileSource.readAll();
198  const QByteArray encoded = blob.toBase64();
199 
200  QString path( encoded );
201  path.prepend( QLatin1String( "base64:" ) );
202  if ( path == source() )
203  return;
204 
205  mBase64 = path;
206  mMode = ModeBase64;
207 
208  mFileLineEdit->clear();
209  mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
210 
211  emit sourceChanged( path );
212 }
213 
214 void QgsAbstractFileContentSourceLineEdit::extractFile()
215 {
216  QgsSettings s;
217  const QString file = QFileDialog::getSaveFileName( nullptr,
218  extractFileTitle(),
219  defaultPath(),
220  fileFilter() );
221  if ( file.isEmpty() )
222  {
223  return;
224  }
225 
226  const QFileInfo fi( file );
227  s.setValue( settingsKey(), fi.absolutePath() );
228 
229  // decode current base64 embedded file
230  const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
231  const QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
232 
233  QFile fileOut( file );
234  fileOut.open( QIODevice::WriteOnly );
235  fileOut.write( decoded );
236  fileOut.close();
237 
238  if ( mMessageBar )
239  {
240  mMessageBar->pushMessage( extractFileTitle(),
241  tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ),
242  Qgis::MessageLevel::Success, 0 );
243  }
244 }
245 
246 void QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
247 {
248  mFileLineEdit->setPlaceholderText( QString() );
249  mBase64.clear();
250  mMode = ModeFile;
251  if ( !text.isEmpty() && !QFileInfo::exists( text ) )
252  {
253  const QUrl url( text );
254  if ( !url.isValid() )
255  {
256  return;
257  }
258  }
259  emit sourceChanged( text );
260 }
261 
262 QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
263 {
264  if ( QFileInfo::exists( source() ) )
265  return source();
266 
267  return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
268 }
269 
270 QString QgsAbstractFileContentSourceLineEdit::settingsKey() const
271 {
272  return mLastPathKey.isEmpty() ? defaultSettingsKey() : mLastPathKey;
273 }
274 
276 {
277  mMessageBar = bar;
278 }
279 
281 {
282  return mMessageBar;
283 }
284 
285 
286 
287 //
288 // QgsPictureSourceLineEditBase
289 //
290 
292 
293 
294 QString QgsPictureSourceLineEditBase::fileFilter() const
295 {
296  switch ( mFormat )
297  {
298  case Svg:
299  return tr( "SVG files" ) + " (*.svg)";
300  case Image:
301  {
302  QStringList formatsFilter;
303  const QByteArrayList supportedFormats = QImageReader::supportedImageFormats();
304  for ( const auto &format : supportedFormats )
305  {
306  formatsFilter.append( QString( QStringLiteral( "*.%1" ) ).arg( QString( format ) ) );
307  }
308  return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Images" ), formatsFilter.join( QLatin1Char( ' ' ) ), tr( "All files" ) );
309  }
310 
311  case AnimatedImage:
312  {
313  QStringList formatsFilter;
314  const QByteArrayList supportedFormats = QMovie::supportedFormats();
315  for ( const auto &format : supportedFormats )
316  {
317  formatsFilter.append( QString( QStringLiteral( "*.%1" ) ).arg( QString( format ) ) );
318  }
319  return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Animated Images" ), formatsFilter.join( QLatin1Char( ' ' ) ), tr( "All files" ) );
320  }
321  }
323 }
324 
325 QString QgsPictureSourceLineEditBase::selectFileTitle() const
326 {
327  switch ( mFormat )
328  {
329  case Svg:
330  return tr( "Select SVG File" );
331  case Image:
332  return tr( "Select Image File" );
333  case AnimatedImage:
334  return tr( "Select Animated Image File" );
335  }
337 }
338 
339 QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
340 {
341  switch ( mFormat )
342  {
343  case Svg:
344  return tr( "SVG From URL" );
345  case Image:
346  return tr( "Image From URL" );
347  case AnimatedImage:
348  return tr( "Animated Image From URL" );
349  }
351 }
352 
353 QString QgsPictureSourceLineEditBase::fileFromUrlText() const
354 {
355  switch ( mFormat )
356  {
357  case Svg:
358  return tr( "Enter SVG URL" );
359  case Image:
360  return tr( "Enter image URL" );
361  case AnimatedImage:
362  return tr( "Enter animated image URL" );
363  }
365 }
366 
367 QString QgsPictureSourceLineEditBase::embedFileTitle() const
368 {
369  switch ( mFormat )
370  {
371  case Svg:
372  return tr( "Embed SVG File" );
373  case Image:
374  return tr( "Embed Image File" );
375  case AnimatedImage:
376  return tr( "Embed Animated Image File" );
377  }
379 }
380 
381 QString QgsPictureSourceLineEditBase::extractFileTitle() const
382 {
383  switch ( mFormat )
384  {
385  case Svg:
386  return tr( "Extract SVG File" );
387  case Image:
388  return tr( "Extract Image File" );
389  case AnimatedImage:
390  return tr( "Extract Animated Image File" );
391  }
393 }
394 
395 QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
396 {
397  switch ( mFormat )
398  {
399  case Svg:
400  return QStringLiteral( "/UI/lastSVGDir" );
401  case Image:
402  return QStringLiteral( "/UI/lastImageDir" );
403  case AnimatedImage:
404  return QStringLiteral( "/UI/lastAnimatedImageDir" );
405  }
407 }
408 
410 
411 
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.
@ AnimatedImage
Animated image (since QGIS 3.26)
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:2620