QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 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
246void 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
262QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
263{
264 if ( QFileInfo::exists( source() ) )
265 return source();
266
267 return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
268}
269
270QString 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
294QString 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
325QString 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
339QString 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
353QString 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
367QString 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
381QString 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
395QString 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:63
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:4180