QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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] { extractFileAction->setEnabled( mMode == ModeBase64 ); } );
72
73 QAction *enterUrlAction = new QAction( tr( "From URL…" ), sourceMenu );
74 connect( enterUrlAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectUrl );
75 sourceMenu->addAction( enterUrlAction );
76
77 mFileToolButton->setMenu( sourceMenu );
78 mFileToolButton->setPopupMode( QToolButton::MenuButtonPopup );
79 connect( mFileToolButton, &QToolButton::clicked, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
80
81 connect( mFileLineEdit, &QLineEdit::textEdited, this, &QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited );
82 connect( mFileLineEdit, &QgsFilterLineEdit::cleared, this, [this] {
83 mMode = ModeFile;
84 mFileLineEdit->setPlaceholderText( QString() );
85 mBase64.clear();
86 emit sourceChanged( QString() );
87 } );
88
89 mPropertyOverrideButton->setVisible( mPropertyOverrideButtonVisible );
90}
91
93{
94 switch ( mMode )
95 {
96 case ModeFile:
97 return mFileLineEdit->text();
98
99 case ModeBase64:
100 return mBase64;
101 }
102
103 return QString();
104}
105
107{
108 mLastPathKey = key;
109}
110
112{
113 mPropertyOverrideButtonVisible = visible;
114 mPropertyOverrideButton->setVisible( visible );
115}
116
118{
119 const bool isBase64 = source.startsWith( "base64:"_L1, Qt::CaseInsensitive );
120
121 if ( ( !isBase64 && source == mFileLineEdit->text() && mBase64.isEmpty() ) || ( isBase64 && source == mBase64 ) )
122 return;
123
124 if ( isBase64 )
125 {
126 mMode = ModeBase64;
127 mBase64 = source;
128 mFileLineEdit->clear();
129 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
130 }
131 else
132 {
133 mMode = ModeFile;
134 mBase64.clear();
135 mFileLineEdit->setText( source );
136 mFileLineEdit->setPlaceholderText( QString() );
137 }
138
139 emit sourceChanged( source );
140}
141
142void QgsAbstractFileContentSourceLineEdit::selectFile()
143{
144 QgsSettings s;
145 const QString file = QFileDialog::getOpenFileName( nullptr, selectFileTitle(), defaultPath(), fileFilter() );
146 const QFileInfo fi( file );
147 if ( file.isEmpty() || !fi.exists() || file == source() )
148 {
149 return;
150 }
151 mMode = ModeFile;
152 mBase64.clear();
153 mFileLineEdit->setText( file );
154 mFileLineEdit->setPlaceholderText( QString() );
155 s.setValue( settingsKey(), fi.absolutePath() );
156 emit sourceChanged( mFileLineEdit->text() );
157}
158
159void QgsAbstractFileContentSourceLineEdit::selectUrl()
160{
161 bool ok = false;
162 const QString path = QInputDialog::getText( this, fileFromUrlTitle(), fileFromUrlText(), QLineEdit::Normal, mFileLineEdit->text(), &ok );
163 if ( ok && path != source() )
164 {
165 mMode = ModeFile;
166 mBase64.clear();
167 mFileLineEdit->setText( path );
168 mFileLineEdit->setPlaceholderText( QString() );
169 emit sourceChanged( mFileLineEdit->text() );
170 }
171}
172
173void QgsAbstractFileContentSourceLineEdit::embedFile()
174{
175 QgsSettings s;
176 const QString file = QFileDialog::getOpenFileName( nullptr, embedFileTitle(), defaultPath(), fileFilter() );
177 const QFileInfo fi( file );
178 if ( file.isEmpty() || !fi.exists() )
179 {
180 return;
181 }
182
183 s.setValue( settingsKey(), fi.absolutePath() );
184
185 // encode file as base64
186 QFile fileSource( file );
187 if ( !fileSource.open( QIODevice::ReadOnly ) )
188 {
189 return;
190 }
191
192 const QByteArray blob = fileSource.readAll();
193 const QByteArray encoded = blob.toBase64();
194
195 QString path( encoded );
196 path.prepend( "base64:"_L1 );
197 if ( path == source() )
198 return;
199
200 mBase64 = path;
201 mMode = ModeBase64;
202
203 mFileLineEdit->clear();
204 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
205
206 emit sourceChanged( path );
207}
208
209void QgsAbstractFileContentSourceLineEdit::extractFile()
210{
211 QgsSettings s;
212 const QString file = QFileDialog::getSaveFileName( nullptr, extractFileTitle(), defaultPath(), fileFilter() );
213 // return dialog focus on Mac
214 activateWindow();
215 raise();
216 if ( file.isEmpty() )
217 {
218 return;
219 }
220
221 const QFileInfo fi( file );
222 s.setValue( settingsKey(), fi.absolutePath() );
223
224 // decode current base64 embedded file
225 const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
226 const QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
227
228 QFile fileOut( file );
229 if ( fileOut.open( QIODevice::WriteOnly ) )
230 {
231 fileOut.write( decoded );
232 fileOut.close();
233
234 if ( mMessageBar )
235 {
236 mMessageBar
237 ->pushMessage( extractFileTitle(), tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Success, 0 );
238 }
239 }
240 else if ( mMessageBar )
241 {
242 mMessageBar->pushMessage( extractFileTitle(), tr( "Error opening %1 for write" ).arg( QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Critical );
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// QgsPictureSourceLineEditBase
288//
289
291
292
293QString 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( u"*.%1"_s ).arg( QString( format ) ) );
306 }
307 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Images" ), formatsFilter.join( ' '_L1 ), tr( "All files" ) );
308 }
309
310 case AnimatedImage:
311 {
312 QStringList formatsFilter;
313 const QByteArrayList supportedFormats = QMovie::supportedFormats();
314 for ( const auto &format : supportedFormats )
315 {
316 formatsFilter.append( QString( u"*.%1"_s ).arg( QString( format ) ) );
317 }
318 return QString( "%1 (%2);;%3 (*.*)" ).arg( tr( "Animated Images" ), formatsFilter.join( ' '_L1 ), tr( "All files" ) );
319 }
320 }
322}
323
324QString QgsPictureSourceLineEditBase::selectFileTitle() const
325{
326 switch ( mFormat )
327 {
328 case Svg:
329 return tr( "Select SVG File" );
330 case Image:
331 return tr( "Select Image File" );
332 case AnimatedImage:
333 return tr( "Select Animated Image File" );
334 }
336}
337
338QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
339{
340 switch ( mFormat )
341 {
342 case Svg:
343 return tr( "SVG From URL" );
344 case Image:
345 return tr( "Image From URL" );
346 case AnimatedImage:
347 return tr( "Animated Image From URL" );
348 }
350}
351
352QString QgsPictureSourceLineEditBase::fileFromUrlText() const
353{
354 switch ( mFormat )
355 {
356 case Svg:
357 return tr( "Enter SVG URL" );
358 case Image:
359 return tr( "Enter image URL" );
360 case AnimatedImage:
361 return tr( "Enter animated image URL" );
362 }
364}
365
366QString QgsPictureSourceLineEditBase::embedFileTitle() const
367{
368 switch ( mFormat )
369 {
370 case Svg:
371 return tr( "Embed SVG File" );
372 case Image:
373 return tr( "Embed Image File" );
374 case AnimatedImage:
375 return tr( "Embed Animated Image File" );
376 }
378}
379
380QString QgsPictureSourceLineEditBase::extractFileTitle() const
381{
382 switch ( mFormat )
383 {
384 case Svg:
385 return tr( "Extract SVG File" );
386 case Image:
387 return tr( "Extract Image File" );
388 case AnimatedImage:
389 return tr( "Extract Animated Image File" );
390 }
392}
393
394QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
395{
396 switch ( mFormat )
397 {
398 case Svg:
399 return u"/UI/lastSVGDir"_s;
400 case Image:
401 return u"/UI/lastImageDir"_s;
402 case AnimatedImage:
403 return u"/UI/lastAnimatedImageDir"_s;
404 }
406}
407
@ Critical
Critical/error message.
Definition qgis.h:163
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
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:7540