QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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
18#include "qgsfilterlineedit.h"
19#include "qgsmessagebar.h"
21#include "qgssettings.h"
22
23#include <QFileDialog>
24#include <QHBoxLayout>
25#include <QImageReader>
26#include <QInputDialog>
27#include <QLineEdit>
28#include <QMenu>
29#include <QMovie>
30#include <QString>
31#include <QToolButton>
32#include <QUrl>
33
34#include "moc_qgsfilecontentsourcelineedit.cpp"
35
36using namespace Qt::StringLiterals;
37
38//
39// QgsAbstractFileContentSourceLineEdit
40//
41
43 : QWidget( parent )
44{
45 QHBoxLayout *layout = new QHBoxLayout( this );
46 layout->setContentsMargins( 0, 0, 0, 0 );
47 mFileLineEdit = new QgsFilterLineEdit( this );
48 mFileLineEdit->setShowClearButton( true );
49 mFileToolButton = new QToolButton( this );
50 mFileToolButton->setText( QString( QChar( 0x2026 ) ) );
51 mPropertyOverrideButton = new QgsPropertyOverrideButton( this );
52 layout->addWidget( mFileLineEdit, 1 );
53 layout->addWidget( mFileToolButton );
54 layout->addWidget( mPropertyOverrideButton );
55 setLayout( layout );
56
57 QMenu *sourceMenu = new QMenu( mFileToolButton );
58
59 QAction *selectFileAction = new QAction( tr( "Select File…" ), sourceMenu );
60 connect( selectFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
61 sourceMenu->addAction( selectFileAction );
62
63 QAction *embedFileAction = new QAction( tr( "Embed File…" ), sourceMenu );
64 connect( embedFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::embedFile );
65 sourceMenu->addAction( embedFileAction );
66
67 QAction *extractFileAction = new QAction( tr( "Extract Embedded File…" ), sourceMenu );
68 connect( extractFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::extractFile );
69 sourceMenu->addAction( extractFileAction );
70
71 connect( sourceMenu, &QMenu::aboutToShow, this, [this, extractFileAction] {
72 extractFileAction->setEnabled( mMode == ModeBase64 );
73 } );
74
75 QAction *enterUrlAction = new QAction( tr( "From URL…" ), sourceMenu );
76 connect( enterUrlAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectUrl );
77 sourceMenu->addAction( enterUrlAction );
78
79 mFileToolButton->setMenu( sourceMenu );
80 mFileToolButton->setPopupMode( QToolButton::MenuButtonPopup );
81 connect( mFileToolButton, &QToolButton::clicked, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
82
83 connect( mFileLineEdit, &QLineEdit::textEdited, this, &QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited );
84 connect( mFileLineEdit, &QgsFilterLineEdit::cleared, this, [this] {
85 mMode = ModeFile;
86 mFileLineEdit->setPlaceholderText( QString() );
87 mBase64.clear();
88 emit sourceChanged( QString() );
89 } );
90
91 mPropertyOverrideButton->setVisible( mPropertyOverrideButtonVisible );
92}
93
95{
96 switch ( mMode )
97 {
98 case ModeFile:
99 return mFileLineEdit->text();
100
101 case ModeBase64:
102 return mBase64;
103 }
104
105 return QString();
106}
107
109{
110 mLastPathKey = key;
111}
112
114{
115 mPropertyOverrideButtonVisible = visible;
116 mPropertyOverrideButton->setVisible( visible );
117}
118
120{
121 const bool isBase64 = source.startsWith( "base64:"_L1, Qt::CaseInsensitive );
122
123 if ( ( !isBase64 && source == mFileLineEdit->text() && mBase64.isEmpty() ) || ( isBase64 && source == mBase64 ) )
124 return;
125
126 if ( isBase64 )
127 {
128 mMode = ModeBase64;
129 mBase64 = source;
130 mFileLineEdit->clear();
131 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
132 }
133 else
134 {
135 mMode = ModeFile;
136 mBase64.clear();
137 mFileLineEdit->setText( source );
138 mFileLineEdit->setPlaceholderText( QString() );
139 }
140
141 emit sourceChanged( source );
142}
143
144void QgsAbstractFileContentSourceLineEdit::selectFile()
145{
146 QgsSettings s;
147 const QString file = QFileDialog::getOpenFileName( nullptr, selectFileTitle(), defaultPath(), 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, embedFileTitle(), defaultPath(), fileFilter() );
179 const QFileInfo fi( file );
180 if ( file.isEmpty() || !fi.exists() )
181 {
182 return;
183 }
184
185 s.setValue( settingsKey(), fi.absolutePath() );
186
187 // encode file as base64
188 QFile fileSource( file );
189 if ( !fileSource.open( QIODevice::ReadOnly ) )
190 {
191 return;
192 }
193
194 const QByteArray blob = fileSource.readAll();
195 const QByteArray encoded = blob.toBase64();
196
197 QString path( encoded );
198 path.prepend( "base64:"_L1 );
199 if ( path == source() )
200 return;
201
202 mBase64 = path;
203 mMode = ModeBase64;
204
205 mFileLineEdit->clear();
206 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
207
208 emit sourceChanged( path );
209}
210
211void QgsAbstractFileContentSourceLineEdit::extractFile()
212{
213 QgsSettings s;
214 const QString file = QFileDialog::getSaveFileName( nullptr, extractFileTitle(), defaultPath(), fileFilter() );
215 // return dialog focus on Mac
216 activateWindow();
217 raise();
218 if ( file.isEmpty() )
219 {
220 return;
221 }
222
223 const QFileInfo fi( file );
224 s.setValue( settingsKey(), fi.absolutePath() );
225
226 // decode current base64 embedded file
227 const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
228 const QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
229
230 QFile fileOut( file );
231 if ( fileOut.open( QIODevice::WriteOnly ) )
232 {
233 fileOut.write( decoded );
234 fileOut.close();
235
236 if ( mMessageBar )
237 {
238 mMessageBar->pushMessage( extractFileTitle(), tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Success, 0 );
239 }
240 }
241 else if ( mMessageBar )
242 {
243 mMessageBar->pushMessage( extractFileTitle(), tr( "Error opening %1 for write" ).arg( QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Critical );
244 }
245}
246
247void QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
248{
249 mFileLineEdit->setPlaceholderText( QString() );
250 mBase64.clear();
251 mMode = ModeFile;
252 if ( !text.isEmpty() && !QFileInfo::exists( text ) )
253 {
254 const QUrl url( text );
255 if ( !url.isValid() )
256 {
257 return;
258 }
259 }
260 emit sourceChanged( text );
261}
262
263QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
264{
265 if ( QFileInfo::exists( source() ) )
266 return source();
267
268 return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
269}
270
271QString QgsAbstractFileContentSourceLineEdit::settingsKey() const
272{
273 return mLastPathKey.isEmpty() ? defaultSettingsKey() : mLastPathKey;
274}
275
277{
278 mMessageBar = bar;
279}
280
282{
283 return mMessageBar;
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( u"*.%1"_s ).arg( QString( format ) ) );
307 }
308 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Images" ), formatsFilter.join( ' '_L1 ), 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( u"*.%1"_s ).arg( QString( format ) ) );
318 }
319 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Animated Images" ), formatsFilter.join( ' '_L1 ), 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 u"/UI/lastSVGDir"_s;
401 case Image:
402 return u"/UI/lastImageDir"_s;
403 case AnimatedImage:
404 return u"/UI/lastAnimatedImageDir"_s;
405 }
407}
408
@ Critical
Critical/error message.
Definition qgis.h:162
@ Success
Used for reporting a successful operation.
Definition qgis.h:163
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 cleared()
Emitted when the widget is cleared.
A bar for displaying non-blocking messages to the user.
A button for controlling property overrides which may apply to a widget.
Stores settings for use within QGIS.
Definition qgssettings.h:68
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:7489