Skip to content

Commit 8c86aab

Browse files
committedNov 4, 2015
[Expressions] Redesign expression function editor.
Add auto save
1 parent 90a82ba commit 8c86aab

File tree

5 files changed

+275
-177
lines changed

5 files changed

+275
-177
lines changed
 

‎python/gui/qgsexpressionbuilderwidget.sip

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,29 @@ class QgsExpressionBuilderWidget : QWidget
165165
void updateFunctionFileList( const QString& path );
166166

167167
public slots:
168-
void currentChanged( const QModelIndex &index, const QModelIndex & );
169-
void on_btnRun_pressed();
170-
void on_btnNewFile_pressed();
171-
void on_cmbFileNames_currentIndexChanged( int index );
172-
void on_btnSaveFile_pressed();
173-
void on_expressionTree_doubleClicked( const QModelIndex &index );
174-
void on_txtExpressionString_textChanged();
175-
void on_txtSearchEdit_textChanged();
176-
void on_txtSearchEditValues_textChanged();
177-
void on_lblPreview_linkActivated( const QString& link );
178-
void on_mValuesListView_doubleClicked( const QModelIndex &index );
179-
void operatorButtonClicked();
180-
void showContextMenu( const QPoint & );
168+
169+
/**
170+
* Load sample values into the sample value area
171+
*/
181172
void loadSampleValues();
173+
174+
/**
175+
* Load all unique values from the set layer into the sample area
176+
*/
182177
void loadAllValues();
183178

179+
/**
180+
* Auto save the current Python function code.
181+
*/
182+
void autosave();
183+
184+
/**
185+
* Enabled or disable auto saving. When enabled Python scripts will be auto saved
186+
* when text changes.
187+
* @param enabled True to enable auto saving.
188+
*/
189+
void setAutoSave( bool enabled );
190+
184191
signals:
185192
/** Emitted when the user changes the expression in the widget.
186193
* Users of this widget should connect to this signal to decide if to let the user

‎src/gui/qgscodeeditorpython.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ bool QgsCodeEditorPython::loadScript( const QString &script )
127127

128128
QTextStream in( &file );
129129

130-
setText( in.readAll() );
130+
setText( in.readAll().trimmed() );
131131
file.close();
132132

133133
setSciLexerPython();

‎src/gui/qgsexpressionbuilderwidget.cpp

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@
2727
#include <QFile>
2828
#include <QTextStream>
2929
#include <QDir>
30+
#include <QInputDialog>
3031
#include <QComboBox>
32+
#include <QGraphicsOpacityEffect>
33+
#include <QPropertyAnimation>
3134

3235

3336

3437
QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
3538
: QWidget( parent )
39+
, mAutoSave( true )
3640
, mLayer( NULL )
3741
, highlighter( NULL )
3842
, mExpressionValid( false )
@@ -75,6 +79,7 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
7579

7680
QSettings settings;
7781
splitter->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter" ).toByteArray() );
82+
editorSplit->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/editorsplitter" ).toByteArray() );
7883
functionsplit->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/functionsplitter" ).toByteArray() );
7984

8085
txtExpressionString->setFoldingVisible( false );
@@ -84,27 +89,27 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
8489
if ( QgsPythonRunner::isValid() )
8590
{
8691
QgsPythonRunner::eval( "qgis.user.expressionspath", mFunctionsPath );
87-
newFunctionFile();
88-
// The scratch file gets written each time the widget opens.
89-
saveFunctionFile( "scratch" );
9092
updateFunctionFileList( mFunctionsPath );
9193
}
9294
else
9395
{
94-
tab_2->setEnabled( false );
96+
tab_2->hide();
9597
}
9698

9799
// select the first item in the function list
98100
// in order to avoid a blank help widget
99101
QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
100102
expressionTree->setCurrentIndex( firstItem );
103+
104+
lblAutoSave->setText("");
101105
}
102106

103107

104108
QgsExpressionBuilderWidget::~QgsExpressionBuilderWidget()
105109
{
106110
QSettings settings;
107111
settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter", splitter->saveState() );
112+
settings.setValue( "/windows/QgsExpressionBuilderWidget/editorsplitter", editorSplit->saveState() );
108113
settings.setValue( "/windows/QgsExpressionBuilderWidget/functionsplitter", functionsplit->saveState() );
109114

110115
delete mModel;
@@ -149,7 +154,11 @@ void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const
149154

150155
void QgsExpressionBuilderWidget::on_btnRun_pressed()
151156
{
152-
saveFunctionFile( cmbFileNames->currentText() );
157+
if ( !cmbFileNames->currentItem() )
158+
return;
159+
160+
QString file = cmbFileNames->currentItem()->text();
161+
saveFunctionFile( file );
153162
runPythonCode( txtPython->text() );
154163
}
155164

@@ -180,7 +189,7 @@ void QgsExpressionBuilderWidget::saveFunctionFile( QString fileName )
180189

181190
fileName = mFunctionsPath + QDir::separator() + fileName;
182191
QFile myFile( fileName );
183-
if ( myFile.open( QIODevice::WriteOnly ) )
192+
if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
184193
{
185194
QTextStream myFileStream( &myFile );
186195
myFileStream << txtPython->text() << endl;
@@ -199,33 +208,47 @@ void QgsExpressionBuilderWidget::updateFunctionFileList( const QString& path )
199208
{
200209
QFileInfo info( mFunctionsPath + QDir::separator() + name );
201210
if ( info.baseName() == "__init__" ) continue;
202-
cmbFileNames->addItem( info.baseName() );
211+
QListWidgetItem* item = new QListWidgetItem( QgsApplication::getThemeIcon("console/iconTabEditorConsole.png"), info.baseName() );
212+
cmbFileNames->addItem( item );
203213
}
214+
if ( !cmbFileNames->currentItem() )
215+
cmbFileNames->setCurrentRow( 0 );
204216
}
205217

206218
void QgsExpressionBuilderWidget::newFunctionFile( const QString& fileName )
207219
{
220+
QList<QListWidgetItem*> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
221+
if ( items.count() > 0 )
222+
return;
223+
208224
QString templatetxt;
209225
QgsPythonRunner::eval( "qgis.user.expressions.template", templatetxt );
210226
txtPython->setText( templatetxt );
211-
int index = cmbFileNames->findText( fileName );
212-
if ( index == -1 )
213-
cmbFileNames->setEditText( fileName );
214-
else
215-
cmbFileNames->setCurrentIndex( index );
227+
cmbFileNames->insertItem( 0, fileName );
228+
cmbFileNames->setCurrentRow( 0 );
229+
saveFunctionFile( fileName );
216230
}
217231

218232
void QgsExpressionBuilderWidget::on_btnNewFile_pressed()
219233
{
220-
newFunctionFile();
234+
bool ok;
235+
QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
236+
tr( "File name:" ), QLineEdit::Normal,
237+
"", &ok );
238+
if ( ok && !text.isEmpty() )
239+
{
240+
newFunctionFile( text );
241+
}
221242
}
222243

223-
void QgsExpressionBuilderWidget::on_cmbFileNames_currentIndexChanged( int index )
244+
void QgsExpressionBuilderWidget::on_cmbFileNames_currentItemChanged( QListWidgetItem* item, QListWidgetItem* lastitem )
224245
{
225-
if ( index == -1 )
226-
return;
227-
228-
QString path = mFunctionsPath + QDir::separator() + cmbFileNames->currentText();
246+
if ( lastitem )
247+
{
248+
QString filename = lastitem->text();
249+
saveFunctionFile( filename );
250+
}
251+
QString path = mFunctionsPath + QDir::separator() + item->text();
229252
loadCodeFromFile( path );
230253
}
231254

@@ -242,18 +265,6 @@ void QgsExpressionBuilderWidget::loadFunctionCode( const QString& code )
242265
txtPython->setText( code );
243266
}
244267

245-
void QgsExpressionBuilderWidget::on_btnSaveFile_pressed()
246-
{
247-
QString name = cmbFileNames->currentText();
248-
saveFunctionFile( name );
249-
int index = cmbFileNames->findText( name );
250-
if ( index == -1 )
251-
{
252-
cmbFileNames->addItem( name );
253-
cmbFileNames->setCurrentIndex( cmbFileNames->count() - 1 );
254-
}
255-
}
256-
257268
void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
258269
{
259270
QModelIndex idx = mProxyModel->mapToSource( index );
@@ -733,6 +744,38 @@ void QgsExpressionBuilderWidget::loadAllValues()
733744
fillFieldValues( item->text(), -1 );
734745
}
735746

747+
void QgsExpressionBuilderWidget::on_txtPython_textChanged()
748+
{
749+
lblAutoSave->setText( "Saving..." );
750+
if ( mAutoSave )
751+
{
752+
autosave();
753+
}
754+
}
755+
756+
void QgsExpressionBuilderWidget::autosave()
757+
{
758+
// Don't auto save if not on function editor that would be silly.
759+
if ( tabWidget->currentIndex() != 1 )
760+
return;
761+
762+
QListWidgetItem* item = cmbFileNames->currentItem();
763+
if ( !item )
764+
return;
765+
766+
QString file = item->text();
767+
saveFunctionFile( file );
768+
lblAutoSave->setText( "Saved" );
769+
QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
770+
lblAutoSave->setGraphicsEffect( effect );
771+
QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
772+
anim->setDuration( 2000 );
773+
anim->setStartValue( 1.0 );
774+
anim->setEndValue( 0.0 );
775+
anim->setEasingCurve( QEasingCurve::OutQuad );
776+
anim->start( QAbstractAnimation::DeleteWhenStopped );
777+
}
778+
736779
void QgsExpressionBuilderWidget::setExpressionState( bool state )
737780
{
738781
mExpressionValid = state;

‎src/gui/qgsexpressionbuilderwidget.h

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,24 +232,43 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
232232
void updateFunctionFileList( const QString& path );
233233

234234
public slots:
235+
236+
/**
237+
* Load sample values into the sample value area
238+
*/
239+
void loadSampleValues();
240+
241+
/**
242+
* Load all unique values from the set layer into the sample area
243+
*/
244+
void loadAllValues();
245+
246+
/**
247+
* Auto save the current Python function code.
248+
*/
249+
void autosave();
250+
/**
251+
* Enabled or disable auto saving. When enabled Python scripts will be auto saved
252+
* when text changes.
253+
* @param enabled True to enable auto saving.
254+
*/
255+
void setAutoSave( bool enabled ) { mAutoSave = enabled; }
256+
257+
private slots:
258+
void showContextMenu( const QPoint & );
259+
void setExpressionState( bool state );
235260
void currentChanged( const QModelIndex &index, const QModelIndex & );
261+
void operatorButtonClicked();
236262
void on_btnRun_pressed();
237263
void on_btnNewFile_pressed();
238-
void on_cmbFileNames_currentIndexChanged( int index );
239-
void on_btnSaveFile_pressed();
264+
void on_cmbFileNames_currentItemChanged( QListWidgetItem* item, QListWidgetItem* lastitem );
240265
void on_expressionTree_doubleClicked( const QModelIndex &index );
241266
void on_txtExpressionString_textChanged();
242267
void on_txtSearchEdit_textChanged();
243268
void on_txtSearchEditValues_textChanged();
244269
void on_lblPreview_linkActivated( const QString& link );
245270
void on_mValuesListView_doubleClicked( const QModelIndex &index );
246-
void operatorButtonClicked();
247-
void showContextMenu( const QPoint & );
248-
void loadSampleValues();
249-
void loadAllValues();
250-
251-
private slots:
252-
void setExpressionState( bool state );
271+
void on_txtPython_textChanged();
253272

254273
signals:
255274
/** Emitted when the user changes the expression in the widget.
@@ -277,6 +296,7 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
277296

278297
void loadExpressionContext();
279298

299+
bool mAutoSave;
280300
QString mFunctionsPath;
281301
QgsVectorLayer *mLayer;
282302
QStandardItemModel *mModel;

‎src/ui/qgsexpressionbuilder.ui

Lines changed: 152 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@
355355
</item>
356356
</layout>
357357
</widget>
358-
<widget class="QWidget" name="layoutWidget_2">
358+
<widget class="QWidget" name="layoutWidget">
359359
<layout class="QVBoxLayout" name="verticalLayout_2">
360360
<item>
361361
<widget class="QTextEdit" name="txtHelpText">
@@ -451,135 +451,163 @@
451451
<attribute name="title">
452452
<string>Function Editor</string>
453453
</attribute>
454-
<layout class="QGridLayout" name="gridLayout_4">
455-
<property name="margin">
456-
<number>0</number>
457-
</property>
458-
<item row="1" column="0">
459-
<widget class="QgsCodeEditorPython" name="txtPython" native="true"/>
460-
</item>
461-
<item row="0" column="0">
462-
<layout class="QHBoxLayout" name="horizontalLayout_5">
463-
<property name="sizeConstraint">
464-
<enum>QLayout::SetDefaultConstraint</enum>
454+
<layout class="QHBoxLayout" name="horizontalLayout_3">
455+
<item>
456+
<widget class="QSplitter" name="editorSplit">
457+
<property name="sizePolicy">
458+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
459+
<horstretch>0</horstretch>
460+
<verstretch>0</verstretch>
461+
</sizepolicy>
462+
</property>
463+
<property name="lineWidth">
464+
<number>0</number>
465+
</property>
466+
<property name="midLineWidth">
467+
<number>0</number>
468+
</property>
469+
<property name="orientation">
470+
<enum>Qt::Horizontal</enum>
471+
</property>
472+
<property name="opaqueResize">
473+
<bool>true</bool>
465474
</property>
466-
<property name="topMargin">
467-
<number>3</number>
475+
<property name="handleWidth">
476+
<number>4</number>
468477
</property>
469-
<item>
470-
<widget class="QToolButton" name="btnRun">
471-
<property name="toolTip">
472-
<string>Run the current editor text in QGIS (also saves current script).
478+
<property name="childrenCollapsible">
479+
<bool>false</bool>
480+
</property>
481+
<widget class="QWidget" name="layoutWidget">
482+
<layout class="QVBoxLayout" name="verticalLayout_5">
483+
<item>
484+
<layout class="QHBoxLayout" name="horizontalLayout_6">
485+
<property name="topMargin">
486+
<number>0</number>
487+
</property>
488+
<item>
489+
<widget class="QToolButton" name="btnNewFile">
490+
<property name="toolTip">
491+
<string>Create a new function file based on the template file.
492+
493+
Change the name of the script and save to allow QGIS to auto load on startup.</string>
494+
</property>
495+
<property name="text">
496+
<string>New file</string>
497+
</property>
498+
<property name="icon">
499+
<iconset resource="../../images/images.qrc">
500+
<normaloff>:/images/themes/default/console/iconTabEditorConsole.png</normaloff>:/images/themes/default/console/iconTabEditorConsole.png</iconset>
501+
</property>
502+
<property name="toolButtonStyle">
503+
<enum>Qt::ToolButtonTextBesideIcon</enum>
504+
</property>
505+
<property name="autoRaise">
506+
<bool>true</bool>
507+
</property>
508+
</widget>
509+
</item>
510+
<item>
511+
<spacer name="horizontalSpacer_4">
512+
<property name="orientation">
513+
<enum>Qt::Horizontal</enum>
514+
</property>
515+
<property name="sizeHint" stdset="0">
516+
<size>
517+
<width>40</width>
518+
<height>20</height>
519+
</size>
520+
</property>
521+
</spacer>
522+
</item>
523+
</layout>
524+
</item>
525+
<item>
526+
<widget class="QListWidget" name="cmbFileNames">
527+
<property name="sizePolicy">
528+
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
529+
<horstretch>0</horstretch>
530+
<verstretch>0</verstretch>
531+
</sizepolicy>
532+
</property>
533+
<property name="minimumSize">
534+
<size>
535+
<width>0</width>
536+
<height>0</height>
537+
</size>
538+
</property>
539+
<property name="editTriggers">
540+
<set>QAbstractItemView::NoEditTriggers</set>
541+
</property>
542+
</widget>
543+
</item>
544+
</layout>
545+
</widget>
546+
<widget class="QWidget" name="layoutWidget">
547+
<layout class="QVBoxLayout" name="verticalLayout_3">
548+
<item>
549+
<layout class="QHBoxLayout" name="horizontalLayout_5">
550+
<property name="sizeConstraint">
551+
<enum>QLayout::SetDefaultConstraint</enum>
552+
</property>
553+
<property name="topMargin">
554+
<number>0</number>
555+
</property>
556+
<item>
557+
<widget class="QToolButton" name="btnRun">
558+
<property name="toolTip">
559+
<string>Run the current editor text in QGIS (also saves current script).
473560

474561
Use this when testing your functions.
475562

476563
Saved scripts are auto loaded on QGIS startup.</string>
477-
</property>
478-
<property name="text">
479-
<string>Run Script</string>
480-
</property>
481-
<property name="icon">
482-
<iconset resource="../../images/images.qrc">
483-
<normaloff>:/images/themes/default/console/iconRunScriptConsole.png</normaloff>:/images/themes/default/console/iconRunScriptConsole.png</iconset>
484-
</property>
485-
<property name="toolButtonStyle">
486-
<enum>Qt::ToolButtonTextUnderIcon</enum>
487-
</property>
488-
<property name="autoRaise">
489-
<bool>true</bool>
490-
</property>
491-
</widget>
492-
</item>
493-
<item>
494-
<spacer name="horizontalSpacer_3">
495-
<property name="orientation">
496-
<enum>Qt::Horizontal</enum>
497-
</property>
498-
<property name="sizeType">
499-
<enum>QSizePolicy::Minimum</enum>
500-
</property>
501-
<property name="sizeHint" stdset="0">
502-
<size>
503-
<width>20</width>
504-
<height>20</height>
505-
</size>
506-
</property>
507-
</spacer>
508-
</item>
509-
<item>
510-
<widget class="QToolButton" name="btnNewFile">
511-
<property name="toolTip">
512-
<string>Create a new function file based on the template file.
513-
514-
Change the name of the script and save to allow QGIS to auto load on startup.</string>
515-
</property>
516-
<property name="text">
517-
<string>New file</string>
518-
</property>
519-
<property name="icon">
520-
<iconset resource="../../images/images.qrc">
521-
<normaloff>:/images/themes/default/console/iconTabEditorConsole.png</normaloff>:/images/themes/default/console/iconTabEditorConsole.png</iconset>
522-
</property>
523-
<property name="toolButtonStyle">
524-
<enum>Qt::ToolButtonTextUnderIcon</enum>
525-
</property>
526-
<property name="autoRaise">
527-
<bool>true</bool>
528-
</property>
529-
</widget>
530-
</item>
531-
<item>
532-
<widget class="QComboBox" name="cmbFileNames">
533-
<property name="sizePolicy">
534-
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
535-
<horstretch>0</horstretch>
536-
<verstretch>0</verstretch>
537-
</sizepolicy>
538-
</property>
539-
<property name="toolTip">
540-
<string>Name of the file to save.
541-
542-
Existing files are listed here and loaded when selected.</string>
543-
</property>
544-
<property name="editable">
545-
<bool>true</bool>
546-
</property>
547-
</widget>
548-
</item>
549-
<item>
550-
<widget class="QToolButton" name="btnSaveFile">
551-
<property name="toolTip">
552-
<string>Save the current script into the users function file folder.</string>
553-
</property>
554-
<property name="text">
555-
<string>Save file</string>
556-
</property>
557-
<property name="icon">
558-
<iconset resource="../../images/images.qrc">
559-
<normaloff>:/images/themes/default/console/iconSaveConsole.png</normaloff>:/images/themes/default/console/iconSaveConsole.png</iconset>
560-
</property>
561-
<property name="toolButtonStyle">
562-
<enum>Qt::ToolButtonTextUnderIcon</enum>
563-
</property>
564-
<property name="autoRaise">
565-
<bool>true</bool>
566-
</property>
567-
</widget>
568-
</item>
569-
<item>
570-
<spacer name="horizontalSpacer_2">
571-
<property name="orientation">
572-
<enum>Qt::Horizontal</enum>
573-
</property>
574-
<property name="sizeHint" stdset="0">
575-
<size>
576-
<width>40</width>
577-
<height>20</height>
578-
</size>
579-
</property>
580-
</spacer>
581-
</item>
582-
</layout>
564+
</property>
565+
<property name="text">
566+
<string>Load</string>
567+
</property>
568+
<property name="icon">
569+
<iconset resource="../../images/images.qrc">
570+
<normaloff>:/images/themes/default/console/iconRunScriptConsole.png</normaloff>:/images/themes/default/console/iconRunScriptConsole.png</iconset>
571+
</property>
572+
<property name="toolButtonStyle">
573+
<enum>Qt::ToolButtonTextBesideIcon</enum>
574+
</property>
575+
<property name="autoRaise">
576+
<bool>true</bool>
577+
</property>
578+
</widget>
579+
</item>
580+
<item>
581+
<spacer name="horizontalSpacer_2">
582+
<property name="orientation">
583+
<enum>Qt::Horizontal</enum>
584+
</property>
585+
<property name="sizeType">
586+
<enum>QSizePolicy::Expanding</enum>
587+
</property>
588+
<property name="sizeHint" stdset="0">
589+
<size>
590+
<width>40</width>
591+
<height>20</height>
592+
</size>
593+
</property>
594+
</spacer>
595+
</item>
596+
<item>
597+
<widget class="QLabel" name="lblAutoSave">
598+
<property name="text">
599+
<string/>
600+
</property>
601+
</widget>
602+
</item>
603+
</layout>
604+
</item>
605+
<item>
606+
<widget class="QgsCodeEditorPython" name="txtPython" native="true"/>
607+
</item>
608+
</layout>
609+
</widget>
610+
</widget>
583611
</item>
584612
</layout>
585613
</widget>

0 commit comments

Comments
 (0)
Please sign in to comment.