QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
141void 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
161void 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
175void 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
214void QgsAbstractFileContentSourceLineEdit::extractFile()
215{
216 QgsSettings s;
217 const QString file = QFileDialog::getSaveFileName( nullptr,
218 extractFileTitle(),
219 defaultPath(),
220 fileFilter() );
221 // return dialog focus on Mac
222 activateWindow();
223 raise();
224 if ( file.isEmpty() )
225 {
226 return;
227 }
228
229 const QFileInfo fi( file );
230 s.setValue( settingsKey(), fi.absolutePath() );
231
232 // decode current base64 embedded file
233 const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
234 const QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
235
236 QFile fileOut( file );
237 fileOut.open( QIODevice::WriteOnly );
238 fileOut.write( decoded );
239 fileOut.close();
240
241 if ( mMessageBar )
242 {
243 mMessageBar->pushMessage( extractFileTitle(),
244 tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ),
245 Qgis::MessageLevel::Success, 0 );
246 }
247}
248
249void QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
250{
251 mFileLineEdit->setPlaceholderText( QString() );
252 mBase64.clear();
253 mMode = ModeFile;
254 if ( !text.isEmpty() && !QFileInfo::exists( text ) )
255 {
256 const QUrl url( text );
257 if ( !url.isValid() )
258 {
259 return;
260 }
261 }
262 emit sourceChanged( text );
263}
264
265QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
266{
267 if ( QFileInfo::exists( source() ) )
268 return source();
269
270 return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
271}
272
273QString QgsAbstractFileContentSourceLineEdit::settingsKey() const
274{
275 return mLastPathKey.isEmpty() ? defaultSettingsKey() : mLastPathKey;
276}
277
279{
280 mMessageBar = bar;
281}
282
284{
285 return mMessageBar;
286}
287
288
289
290//
291// QgsPictureSourceLineEditBase
292//
293
295
296
297QString QgsPictureSourceLineEditBase::fileFilter() const
298{
299 switch ( mFormat )
300 {
301 case Svg:
302 return tr( "SVG files" ) + " (*.svg)";
303 case Image:
304 {
305 QStringList formatsFilter;
306 const QByteArrayList supportedFormats = QImageReader::supportedImageFormats();
307 for ( const auto &format : supportedFormats )
308 {
309 formatsFilter.append( QString( QStringLiteral( "*.%1" ) ).arg( QString( format ) ) );
310 }
311 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Images" ), formatsFilter.join( QLatin1Char( ' ' ) ), tr( "All files" ) );
312 }
313
314 case AnimatedImage:
315 {
316 QStringList formatsFilter;
317 const QByteArrayList supportedFormats = QMovie::supportedFormats();
318 for ( const auto &format : supportedFormats )
319 {
320 formatsFilter.append( QString( QStringLiteral( "*.%1" ) ).arg( QString( format ) ) );
321 }
322 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Animated Images" ), formatsFilter.join( QLatin1Char( ' ' ) ), tr( "All files" ) );
323 }
324 }
326}
327
328QString QgsPictureSourceLineEditBase::selectFileTitle() const
329{
330 switch ( mFormat )
331 {
332 case Svg:
333 return tr( "Select SVG File" );
334 case Image:
335 return tr( "Select Image File" );
336 case AnimatedImage:
337 return tr( "Select Animated Image File" );
338 }
340}
341
342QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
343{
344 switch ( mFormat )
345 {
346 case Svg:
347 return tr( "SVG From URL" );
348 case Image:
349 return tr( "Image From URL" );
350 case AnimatedImage:
351 return tr( "Animated Image From URL" );
352 }
354}
355
356QString QgsPictureSourceLineEditBase::fileFromUrlText() const
357{
358 switch ( mFormat )
359 {
360 case Svg:
361 return tr( "Enter SVG URL" );
362 case Image:
363 return tr( "Enter image URL" );
364 case AnimatedImage:
365 return tr( "Enter animated image URL" );
366 }
368}
369
370QString QgsPictureSourceLineEditBase::embedFileTitle() const
371{
372 switch ( mFormat )
373 {
374 case Svg:
375 return tr( "Embed SVG File" );
376 case Image:
377 return tr( "Embed Image File" );
378 case AnimatedImage:
379 return tr( "Embed Animated Image File" );
380 }
382}
383
384QString QgsPictureSourceLineEditBase::extractFileTitle() const
385{
386 switch ( mFormat )
387 {
388 case Svg:
389 return tr( "Extract SVG File" );
390 case Image:
391 return tr( "Extract Image File" );
392 case AnimatedImage:
393 return tr( "Extract Animated Image File" );
394 }
396}
397
398QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
399{
400 switch ( mFormat )
401 {
402 case Svg:
403 return QStringLiteral( "/UI/lastSVGDir" );
404 case Image:
405 return QStringLiteral( "/UI/lastImageDir" );
406 case AnimatedImage:
407 return QStringLiteral( "/UI/lastAnimatedImageDir" );
408 }
410}
411
413
414
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:64
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:5853