QGIS API Documentation 4.1.0-Master (376402f9aeb)
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 "qgsfilewidget.h"
19#include "qgsfilterlineedit.h"
20#include "qgsmessagebar.h"
22#include "qgssettings.h"
23
24#include <QFileDialog>
25#include <QHBoxLayout>
26#include <QImageReader>
27#include <QInputDialog>
28#include <QLineEdit>
29#include <QMenu>
30#include <QMovie>
31#include <QString>
32#include <QToolButton>
33#include <QUrl>
34
35#include "moc_qgsfilecontentsourcelineedit.cpp"
36
37using namespace Qt::StringLiterals;
38
39//
40// QgsAbstractFileContentSourceLineEdit
41//
42
44 : QWidget( parent )
45{
46 QHBoxLayout *layout = new QHBoxLayout( this );
47 layout->setContentsMargins( 0, 0, 0, 0 );
48 mFileLineEdit = new QgsFileDropEdit( this );
49 mFileLineEdit->setShowClearButton( true );
51 mFileToolButton = new QToolButton( this );
52 mFileToolButton->setText( QString( QChar( 0x2026 ) ) );
53 mPropertyOverrideButton = new QgsPropertyOverrideButton( this );
54 layout->addWidget( mFileLineEdit, 1 );
55 layout->addWidget( mFileToolButton );
56 layout->addWidget( mPropertyOverrideButton );
57 setLayout( layout );
58
59 QMenu *sourceMenu = new QMenu( mFileToolButton );
60
61 QAction *selectFileAction = new QAction( tr( "Select File…" ), sourceMenu );
62 connect( selectFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::selectFile );
63 sourceMenu->addAction( selectFileAction );
64
65 QAction *embedFileAction = new QAction( tr( "Embed File…" ), sourceMenu );
66 connect( embedFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::embedFile );
67 sourceMenu->addAction( embedFileAction );
68
69 QAction *extractFileAction = new QAction( tr( "Extract Embedded File…" ), sourceMenu );
70 connect( extractFileAction, &QAction::triggered, this, &QgsAbstractFileContentSourceLineEdit::extractFile );
71 sourceMenu->addAction( extractFileAction );
72
73 connect( sourceMenu, &QMenu::aboutToShow, this, [this, extractFileAction] { extractFileAction->setEnabled( mMode == ModeBase64 ); } );
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 connect( mFileLineEdit, &QgsFileDropEdit::fileDropped, this, [this]( const QString &file ) {
92 mMode = ModeFile;
93 mBase64.clear();
94 mFileLineEdit->setText( file );
95 mFileLineEdit->setPlaceholderText( QString() );
96 const QFileInfo fi( file );
97 QgsSettings().setValue( settingsKey(), fi.absolutePath() );
98 emit sourceChanged( mFileLineEdit->text() );
99 } );
100
101 mPropertyOverrideButton->setVisible( mPropertyOverrideButtonVisible );
102}
103
105{
106 switch ( mMode )
107 {
108 case ModeFile:
109 return mFileLineEdit->text();
110
111 case ModeBase64:
112 return mBase64;
113 }
114
115 return QString();
116}
117
119{
120 mLastPathKey = key;
121}
122
124{
125 mPropertyOverrideButtonVisible = visible;
126 mPropertyOverrideButton->setVisible( visible );
127}
128
130{
131 const bool isBase64 = source.startsWith( "base64:"_L1, Qt::CaseInsensitive );
132
133 if ( ( !isBase64 && source == mFileLineEdit->text() && mBase64.isEmpty() ) || ( isBase64 && source == mBase64 ) )
134 return;
135
136 if ( isBase64 )
137 {
138 mMode = ModeBase64;
139 mBase64 = source;
140 mFileLineEdit->clear();
141 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
142 }
143 else
144 {
145 mMode = ModeFile;
146 mBase64.clear();
147 mFileLineEdit->setText( source );
148 mFileLineEdit->setPlaceholderText( QString() );
149 }
150
151 emit sourceChanged( source );
152}
153
154void QgsAbstractFileContentSourceLineEdit::selectFile()
155{
156 QgsSettings s;
157 const QString file = QFileDialog::getOpenFileName( nullptr, selectFileTitle(), defaultPath(), fileFilter( true ) );
158 const QFileInfo fi( file );
159 if ( file.isEmpty() || !fi.exists() || file == source() )
160 {
161 return;
162 }
163 mMode = ModeFile;
164 mBase64.clear();
165 mFileLineEdit->setText( file );
166 mFileLineEdit->setPlaceholderText( QString() );
167 s.setValue( settingsKey(), fi.absolutePath() );
168 emit sourceChanged( mFileLineEdit->text() );
169}
170
171void QgsAbstractFileContentSourceLineEdit::selectUrl()
172{
173 bool ok = false;
174 const QString path = QInputDialog::getText( this, fileFromUrlTitle(), fileFromUrlText(), QLineEdit::Normal, mFileLineEdit->text(), &ok );
175 if ( ok && path != source() )
176 {
177 mMode = ModeFile;
178 mBase64.clear();
179 mFileLineEdit->setText( path );
180 mFileLineEdit->setPlaceholderText( QString() );
181 emit sourceChanged( mFileLineEdit->text() );
182 }
183}
184
185void QgsAbstractFileContentSourceLineEdit::embedFile()
186{
187 QgsSettings s;
188 const QString file = QFileDialog::getOpenFileName( nullptr, embedFileTitle(), defaultPath(), fileFilter( true ) );
189 const QFileInfo fi( file );
190 if ( file.isEmpty() || !fi.exists() )
191 {
192 return;
193 }
194
195 s.setValue( settingsKey(), fi.absolutePath() );
196
197 // encode file as base64
198 QFile fileSource( file );
199 if ( !fileSource.open( QIODevice::ReadOnly ) )
200 {
201 return;
202 }
203
204 const QByteArray blob = fileSource.readAll();
205 const QByteArray encoded = blob.toBase64();
206
207 QString path( encoded );
208 path.prepend( "base64:"_L1 );
209 if ( path == source() )
210 return;
211
212 mBase64 = path;
213 mMode = ModeBase64;
214
215 mFileLineEdit->clear();
216 mFileLineEdit->setPlaceholderText( tr( "Embedded file" ) );
217
218 emit sourceChanged( path );
219}
220
221void QgsAbstractFileContentSourceLineEdit::extractFile()
222{
223 QgsSettings s;
224 const QString file = QFileDialog::getSaveFileName( nullptr, extractFileTitle(), defaultPath(), fileFilter( true ) );
225 // return dialog focus on Mac
226 activateWindow();
227 raise();
228 if ( file.isEmpty() )
229 {
230 return;
231 }
232
233 const QFileInfo fi( file );
234 s.setValue( settingsKey(), fi.absolutePath() );
235
236 // decode current base64 embedded file
237 const QByteArray base64 = mBase64.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
238 const QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
239
240 QFile fileOut( file );
241 if ( fileOut.open( QIODevice::WriteOnly ) )
242 {
243 fileOut.write( decoded );
244 fileOut.close();
245
246 if ( mMessageBar )
247 {
248 mMessageBar
249 ->pushMessage( extractFileTitle(), tr( "Successfully extracted file to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( file ).toString(), QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Success, 0 );
250 }
251 }
252 else if ( mMessageBar )
253 {
254 mMessageBar->pushMessage( extractFileTitle(), tr( "Error opening %1 for write" ).arg( QDir::toNativeSeparators( file ) ), Qgis::MessageLevel::Critical );
255 }
256}
257
258void QgsAbstractFileContentSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
259{
260 mFileLineEdit->setPlaceholderText( QString() );
261 mBase64.clear();
262 mMode = ModeFile;
263 if ( !text.isEmpty() && !QFileInfo::exists( text ) )
264 {
265 const QUrl url( text );
266 if ( !url.isValid() )
267 {
268 return;
269 }
270 }
271 emit sourceChanged( text );
272}
273
274QString QgsAbstractFileContentSourceLineEdit::defaultPath() const
275{
276 if ( QFileInfo::exists( source() ) )
277 return source();
278
279 return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
280}
281
282QString QgsAbstractFileContentSourceLineEdit::settingsKey() const
283{
284 return mLastPathKey.isEmpty() ? defaultSettingsKey() : mLastPathKey;
285}
286
288{
289 mMessageBar = bar;
290}
291
293{
294 return mMessageBar;
295}
296
297
298//
299// QgsPictureSourceLineEditBase
300//
301
303
304
307 , mFormat( format )
308{
309 mFileLineEdit->setFilters( fileFilter( false ) );
310}
311
312QString QgsPictureSourceLineEditBase::fileFilter( bool includeAllFiles ) const
313{
314 switch ( mFormat )
315 {
316 case Svg:
317 return tr( "SVG files" ) + " (*.svg)";
318 case Image:
319 {
320 QStringList formatsFilter;
321 const QByteArrayList supportedFormats = QImageReader::supportedImageFormats();
322 for ( const auto &format : supportedFormats )
323 {
324 formatsFilter.append( QString( u"*.%1"_s ).arg( QString( format ) ) );
325 }
326 return QString( "%1 (%2) " ).arg( tr( "Images" ), formatsFilter.join( ' '_L1 ) ) + ( includeAllFiles ? QString( ";;%1 (*.*)" ).arg( tr( "All files" ) ) : QString() );
327 }
328
329 case AnimatedImage:
330 {
331 QStringList formatsFilter;
332 const QByteArrayList supportedFormats = QMovie::supportedFormats();
333 for ( const auto &format : supportedFormats )
334 {
335 formatsFilter.append( QString( u"*.%1"_s ).arg( QString( format ) ) );
336 }
337 return QString( "%1 (%2)" ).arg( tr( "Animated Images" ), formatsFilter.join( ' '_L1 ) ) + ( includeAllFiles ? QString( ";;%1 (*.*)" ).arg( tr( "All files" ) ) : QString() );
338 ;
339 }
340 }
342}
343
344QString QgsPictureSourceLineEditBase::selectFileTitle() const
345{
346 switch ( mFormat )
347 {
348 case Svg:
349 return tr( "Select SVG File" );
350 case Image:
351 return tr( "Select Image File" );
352 case AnimatedImage:
353 return tr( "Select Animated Image File" );
354 }
356}
357
358QString QgsPictureSourceLineEditBase::fileFromUrlTitle() const
359{
360 switch ( mFormat )
361 {
362 case Svg:
363 return tr( "SVG From URL" );
364 case Image:
365 return tr( "Image From URL" );
366 case AnimatedImage:
367 return tr( "Animated Image From URL" );
368 }
370}
371
372QString QgsPictureSourceLineEditBase::fileFromUrlText() const
373{
374 switch ( mFormat )
375 {
376 case Svg:
377 return tr( "Enter SVG URL" );
378 case Image:
379 return tr( "Enter image URL" );
380 case AnimatedImage:
381 return tr( "Enter animated image URL" );
382 }
384}
385
386QString QgsPictureSourceLineEditBase::embedFileTitle() const
387{
388 switch ( mFormat )
389 {
390 case Svg:
391 return tr( "Embed SVG File" );
392 case Image:
393 return tr( "Embed Image File" );
394 case AnimatedImage:
395 return tr( "Embed Animated Image File" );
396 }
398}
399
400QString QgsPictureSourceLineEditBase::extractFileTitle() const
401{
402 switch ( mFormat )
403 {
404 case Svg:
405 return tr( "Extract SVG File" );
406 case Image:
407 return tr( "Extract Image File" );
408 case AnimatedImage:
409 return tr( "Extract Animated Image File" );
410 }
412}
413
414QString QgsPictureSourceLineEditBase::defaultSettingsKey() const
415{
416 switch ( mFormat )
417 {
418 case Svg:
419 return u"/UI/lastSVGDir"_s;
420 case Image:
421 return u"/UI/lastImageDir"_s;
422 case AnimatedImage:
423 return u"/UI/lastAnimatedImageDir"_s;
424 }
426}
427
@ Critical
Critical/error message.
Definition qgis.h:163
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
Abstract base class for widgets which allow users to select content from a file, embedding a file,...
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.
@ GetFile
Select a single file.
void cleared()
Emitted when the widget is cleared.
A bar for displaying non-blocking messages to the user.
QgsPictureSourceLineEditBase(QWidget *parent=nullptr)
Constructor for QgsImageSourceLineEdit, with the specified parent widget.
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:7714