aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/actionentry.cpp42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/actionentry.h30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp39
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h34
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp36
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h46
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp138
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h54
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extaction.h20
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp260
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h254
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp19
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h44
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp118
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h45
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp98
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/fileedit.h52
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp68
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/fontedit.h43
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui35
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp38
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h25
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp152
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h57
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp49
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h23
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp33
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h31
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp20
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h22
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp209
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp412
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h99
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp133
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h31
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp170
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h50
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp70
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completerview.h30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp229
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h64
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configmapper.cpp652
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configmapper.h121
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configuiplugin.h24
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp63
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h35
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp67
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h22
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp54
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h20
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp56
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h27
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp107
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h40
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui69
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp204
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h41
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui56
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp266
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h48
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui147
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp13
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h17
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp127
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h36
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui105
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp99
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp13
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h22
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp175
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h49
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp96
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h79
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp56
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h27
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp418
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h57
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui215
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp299
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h63
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui228
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp83
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h23
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui102
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp21
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h21
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp460
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h96
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp70
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h23
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp1519
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h444
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp465
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h177
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp63
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h27
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp454
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h130
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp332
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h50
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dataview.cpp836
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dataview.h199
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp184
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dblistmodel.h66
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp309
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h67
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp120
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h59
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp1557
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h205
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui90
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp332
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h112
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp164
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp74
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h26
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp1222
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h135
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp255
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h57
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp79
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/debugconsole.h44
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/debugconsole.ui73
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp220
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui208
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h40
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui132
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp616
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h113
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui348
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp335
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h58
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp1529
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h141
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui1923
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp213
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h79
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui113
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp219
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h52
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui144
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp590
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui236
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp58
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h35
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui106
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui85
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp737
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h105
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui438
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp376
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h69
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui230
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp468
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui195
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp97
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui84
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp275
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp68
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h34
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui144
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp119
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui99
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp332
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h76
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui158
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui83
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h39
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui153
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp248
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h60
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui112
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp52
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h33
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui117
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp413
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h62
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui215
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp31
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui91
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/formmanager.cpp185
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/formmanager.h43
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui46
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/formview.cpp274
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/formview.h114
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro364
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h12
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/icon.cpp385
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/icon.h104
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp164
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/iconmanager.h275
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/icons.qrc194
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/abort24.pngbin0 -> 1081 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_abort.pngbin0 -> 544 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_clear.pngbin0 -> 784 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_copy.pngbin0 -> 606 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_cut.pngbin0 -> 690 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_del_line.pngbin0 -> 529 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_delete.pngbin0 -> 663 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_paste.pngbin0 -> 685 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_redo.pngbin0 -> 613 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_search.pngbin0 -> 781 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_select_all.pngbin0 -> 327 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/act_undo.pngbin0 -> 632 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/apply_filter.pngbin0 -> 591 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.pngbin0 -> 589 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.pngbin0 -> 601 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.pngbin0 -> 566 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/bug.pngbin0 -> 704 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/bug_list.pngbin0 -> 793 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/check.pngbin0 -> 650 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/clear_history.pngbin0 -> 658 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.pngbin0 -> 493 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/close.pngbin0 -> 544 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/collation.pngbin0 -> 599 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/column.pngbin0 -> 580 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/column_constraint.pngbin0 -> 814 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/columns.pngbin0 -> 707 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/commit.pngbin0 -> 605 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/complete.pngbin0 -> 671 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_blob.pngbin0 -> 668 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.pngbin0 -> 404 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_number.pngbin0 -> 276 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_operator.pngbin0 -> 308 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_other.pngbin0 -> 415 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.pngbin0 -> 845 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/completer_string.pngbin0 -> 524 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_colors.pngbin0 -> 865 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.pngbin0 -> 359 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_font.pngbin0 -> 459 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_general.pngbin0 -> 435 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.pngbin0 -> 538 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/config_style.pngbin0 -> 927 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/configure.pngbin0 -> 699 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.pngbin0 -> 297 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/convert_db.pngbin0 -> 744 bytes
-rwxr-xr-xSQLiteStudio3/guiSQLiteStudio/img/database.pngbin0 -> 569 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_connect.pngbin0 -> 1370 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_connected.pngbin0 -> 725 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.pngbin0 -> 1520 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_export.pngbin0 -> 941 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg284
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_file.pngbin0 -> 701 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg284
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_invalid.pngbin0 -> 692 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_network.pngbin0 -> 619 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_offline.pngbin0 -> 495 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_online.pngbin0 -> 569 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/database_reload.pngbin0 -> 697 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/ddl_history.pngbin0 -> 845 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/default.pngbin0 -> 591 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/delete_row.pngbin0 -> 479 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/delete_selected.pngbin0 -> 512 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/delete_small.pngbin0 -> 348 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/denied_small.pngbin0 -> 338 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/directory.pngbin0 -> 401 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/directory_open.pngbin0 -> 511 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.pngbin0 -> 746 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.pngbin0 -> 663 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/edit_small.pngbin0 -> 314 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/erase.pngbin0 -> 680 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/error_small.pngbin0 -> 337 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/exec_query.pngbin0 -> 470 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/explain_query.pngbin0 -> 524 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/export.pngbin0 -> 813 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.pngbin0 -> 507 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/feature_request.pngbin0 -> 818 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/fk.pngbin0 -> 673 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/font_browse.pngbin0 -> 340 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/format_sql.pngbin0 -> 445 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/function.pngbin0 -> 372 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/get_update.pngbin0 -> 1153 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/go_back.pngbin0 -> 631 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/help.pngbin0 -> 621 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/homepage.pngbin0 -> 961 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/import.pngbin0 -> 771 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/index.pngbin0 -> 749 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/indexes.pngbin0 -> 759 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/indicator_error.pngbin0 -> 346 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.pngbin0 -> 453 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/indicator_info.pngbin0 -> 370 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.pngbin0 -> 429 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/info_balloon.pngbin0 -> 745 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/info_small.pngbin0 -> 357 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/insert_row.pngbin0 -> 583 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/insert_rows.pngbin0 -> 565 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/integrity_check.pngbin0 -> 845 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/keyboard.pngbin0 -> 1485 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/keyword.pngbin0 -> 603 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/licenses.pngbin0 -> 707 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/loading.gifbin0 -> 1849 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/minus_small.pngbin0 -> 351 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/move_down.pngbin0 -> 659 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/move_up.pngbin0 -> 651 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/not_null.pngbin0 -> 753 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/open_forum.pngbin0 -> 1510 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.pngbin0 -> 648 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.pngbin0 -> 511 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.pngbin0 -> 514 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/page_first.pngbin0 -> 684 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/page_last.pngbin0 -> 659 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/page_next.pngbin0 -> 658 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/page_prev.pngbin0 -> 658 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/pk.pngbin0 -> 689 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/plugin.pngbin0 -> 768 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/plus_small.pngbin0 -> 358 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/question_small.pngbin0 -> 365 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/reload.pngbin0 -> 659 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.pngbin0 -> 639 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/results_below.pngbin0 -> 361 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.pngbin0 -> 343 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/rollback.pngbin0 -> 588 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.pngbin0 -> 507 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/set_null.pngbin0 -> 667 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.pngbin0 -> 429 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.pngbin0 -> 443 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.pngbin0 -> 446 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.pngbin0 -> 446 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.pngbin0 -> 443 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.pngbin0 -> 444 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.pngbin0 -> 432 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.pngbin0 -> 442 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.pngbin0 -> 443 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.pngbin0 -> 456 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.pngbin0 -> 453 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.pngbin0 -> 474 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.pngbin0 -> 473 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.pngbin0 -> 467 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.pngbin0 -> 463 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.pngbin0 -> 468 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.pngbin0 -> 465 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.pngbin0 -> 472 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.pngbin0 -> 466 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.pngbin0 -> 472 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.pngbin0 -> 497 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_columns.pngbin0 -> 1489 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.pngbin0 -> 376 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.pngbin0 -> 383 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sort_reset.pngbin0 -> 681 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sql.pngbin0 -> 376 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.pngbin0 -> 670 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icnsbin0 -> 287952 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icobin0 -> 297966 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg384
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.pngbin0 -> 1381 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/status_error.pngbin0 -> 696 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/status_info.pngbin0 -> 744 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/status_warn.pngbin0 -> 722 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table.pngbin0 -> 563 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_column_add.pngbin0 -> 636 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.pngbin0 -> 660 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.pngbin0 -> 783 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_constraint.pngbin0 -> 802 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.pngbin0 -> 574 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_export.pngbin0 -> 930 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_import.pngbin0 -> 975 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/table_populate.pngbin0 -> 718 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/tables.pngbin0 -> 654 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.pngbin0 -> 364 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.pngbin0 -> 352 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.pngbin0 -> 874 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.pngbin0 -> 905 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/tip.pngbin0 -> 743 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/trigger.pngbin0 -> 736 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.pngbin0 -> 300 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/triggers.pngbin0 -> 680 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/unique.pngbin0 -> 630 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/user.pngbin0 -> 744 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/user_manual.pngbin0 -> 628 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/user_unknown.pngbin0 -> 614 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.pngbin0 -> 933 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/view.pngbin0 -> 813 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/views.pngbin0 -> 1283 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/virtual_table.pngbin0 -> 671 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/warn_small.pngbin0 -> 424 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/win_cascade.pngbin0 -> 452 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/win_tile.pngbin0 -> 408 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.pngbin0 -> 434 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.pngbin0 -> 324 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/window_close.pngbin0 -> 386 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/window_close_all.pngbin0 -> 551 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/window_close_other.pngbin0 -> 698 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/window_rename.pngbin0 -> 586 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/img/window_restore.pngbin0 -> 479 bytes
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/license.txt502
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp909
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mainwindow.h234
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mainwindow.ui142
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp264
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdiarea.h73
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdichild.cpp77
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdichild.h47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp201
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/mdiwindow.h43
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp366
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h96
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp215
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h68
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp87
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp275
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h84
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp49
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h29
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h50
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp184
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h87
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp90
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h38
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp23
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h33
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h17
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp115
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h70
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp180
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h230
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp883
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h129
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp167
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h67
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp359
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp204
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h36
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp244
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h44
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp143
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h34
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp1521
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqleditor.h280
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp447
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h185
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlview.cpp43
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/sqlview.h25
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/statusfield.cpp218
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/statusfield.h58
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/statusfield.ui97
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h17
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/taskbar.cpp299
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/taskbar.h73
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp68
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiconfig.h90
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp30
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uicustomicon.h16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uidebug.cpp116
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uidebug.h36
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiloader.cpp95
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiloader.h33
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp26
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp27
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h16
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiutils.cpp123
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/uiutils.h23
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp137
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/widgetresizer.h47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp155
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui55
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp389
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui210
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp287
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h77
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp395
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h70
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp150
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h54
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui126
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp652
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h155
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui137
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp632
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui346
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp348
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h98
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp482
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp604
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp1508
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h241
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui307
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp760
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h146
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui137
524 files changed, 57566 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/actionentry.cpp b/SQLiteStudio3/guiSQLiteStudio/actionentry.cpp
new file mode 100644
index 0000000..5a81425
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/actionentry.cpp
@@ -0,0 +1,42 @@
+#include "actionentry.h"
+#include "iconmanager.h"
+
+ActionEntry::ActionEntry(const DbTree::Action& action)
+{
+ this->action = action;
+ type = Type::SINGLE;
+}
+
+ActionEntry::ActionEntry(const QString &subMenuLabel)
+{
+ this->subMenuLabel = subMenuLabel;
+ type = Type::SUB_MENU;
+}
+
+ActionEntry::ActionEntry(const Icon& icon, const QString &subMenuLabel)
+{
+ this->subMenuLabel = subMenuLabel;
+ this->subMenuIcon = icon;
+ type = Type::SUB_MENU;
+}
+
+ActionEntry::ActionEntry(const QIcon &icon, const QString &subMenuLabel)
+{
+ this->subMenuLabel = subMenuLabel;
+ this->subMenuIcon = icon;
+ type = Type::SUB_MENU;
+}
+
+ActionEntry &ActionEntry::operator =(const DbTree::Action& action)
+{
+ this->action = action;
+ type = Type::SINGLE;
+ return *this;
+}
+
+ActionEntry &ActionEntry::operator +=(const DbTree::Action& action)
+{
+ this->actions += action;
+ type = Type::SUB_MENU;
+ return *this;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/actionentry.h b/SQLiteStudio3/guiSQLiteStudio/actionentry.h
new file mode 100644
index 0000000..861cffb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/actionentry.h
@@ -0,0 +1,30 @@
+#ifndef ACTIONENTRY_H
+#define ACTIONENTRY_H
+
+#include "dbtree/dbtree.h"
+#include <QString>
+#include <QIcon>
+
+struct GUI_API_EXPORT ActionEntry
+{
+ enum class Type
+ {
+ SINGLE,
+ SUB_MENU
+ };
+
+ QString subMenuLabel;
+ QIcon subMenuIcon;
+ QList<DbTree::Action> actions;
+ DbTree::Action action;
+ Type type;
+
+ ActionEntry(const DbTree::Action &action);
+ ActionEntry(const QString& subMenuLabel);
+ ActionEntry(const Icon& icon, const QString& subMenuLabel);
+ ActionEntry(const QIcon& icon, const QString& subMenuLabel);
+ ActionEntry& operator=(const DbTree::Action& action);
+ ActionEntry& operator+=(const DbTree::Action& action);
+};
+
+#endif // ACTIONENTRY_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp
new file mode 100644
index 0000000..005de4c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp
@@ -0,0 +1,39 @@
+#include "colorbutton.h"
+#include <QResizeEvent>
+#include <QColorDialog>
+
+ColorButton::ColorButton(QWidget *parent) :
+ QPushButton(parent)
+{
+ setFixedWidth(height()*2);
+ setColor(Qt::black);
+ connect(this, SIGNAL(clicked()), this, SLOT(pickColor()));
+}
+
+QColor ColorButton::getColor() const
+{
+ return color;
+}
+
+void ColorButton::setColor(const QColor& value)
+{
+ color = value;
+ QPixmap pix(iconSize());
+ pix.fill(color);
+ setIcon(pix);
+ emit colorChanged(color);
+}
+
+void ColorButton::pickColor()
+{
+ QColor newColor = QColorDialog::getColor(color, parentWidget(), tr("Pick a color"));
+ if (!newColor.isValid())
+ return;
+
+ setColor(newColor);
+}
+
+void ColorButton::resizeEvent(QResizeEvent* e)
+{
+ setFixedWidth(e->size().height()*2);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h
new file mode 100644
index 0000000..237a4ee
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h
@@ -0,0 +1,30 @@
+#ifndef COLORBUTTON_H
+#define COLORBUTTON_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QPushButton>
+#include <QColor>
+
+class GUI_API_EXPORT ColorButton : public QPushButton
+{
+ Q_OBJECT
+ public:
+ explicit ColorButton(QWidget *parent = 0);
+
+ QColor getColor() const;
+ void setColor(const QColor& value);
+
+ protected:
+ void resizeEvent(QResizeEvent* e);
+
+ private:
+ QColor color;
+
+ private slots:
+ void pickColor();
+
+ signals:
+ void colorChanged(const QColor& color);
+};
+
+#endif // COLORBUTTON_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp
new file mode 100644
index 0000000..aa2f115
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp
@@ -0,0 +1,16 @@
+#include "configcombobox.h"
+
+ConfigComboBox::ConfigComboBox(QWidget *parent) :
+ QComboBox(parent)
+{
+}
+
+QVariant ConfigComboBox::getModelName() const
+{
+ return modelName;
+}
+
+void ConfigComboBox::setModelName(QVariant arg)
+{
+ modelName = arg;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h
new file mode 100644
index 0000000..dd4c71c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h
@@ -0,0 +1,34 @@
+#ifndef CONFIGCOMBOBOX_H
+#define CONFIGCOMBOBOX_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QComboBox>
+
+/**
+ * @brief Config-oriented combo box.
+ *
+ * It's just like a regular QComboBox, except it honors additional Qt dynamic property
+ * called "modelName". The "modelName" property should name a CfgEntry key (together with its category,
+ * just like "cfg" properties for CfgEntry linked widgets), that is of QStringList type.
+ * The QStringList is used as a data model for QComboBox. Every time that the CfgEntry
+ * with QStringList changes, the combo box data entries are updated.
+ */
+class GUI_API_EXPORT ConfigComboBox : public QComboBox
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVariant modelName READ getModelName WRITE setModelName)
+
+ public:
+ explicit ConfigComboBox(QWidget* parent = 0);
+
+ QVariant getModelName() const;
+
+ public slots:
+ void setModelName(QVariant arg);
+
+ private:
+ QVariant modelName;
+};
+
+#endif // CONFIGCOMBOBOX_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp
new file mode 100644
index 0000000..62ed4f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp
@@ -0,0 +1,36 @@
+#include "configradiobutton.h"
+
+ConfigRadioButton::ConfigRadioButton(QWidget* parent) :
+ QRadioButton(parent)
+{
+ connect(this, SIGNAL(toggled(bool)), this, SLOT(handleToggled(bool)));
+}
+
+QVariant ConfigRadioButton::getAssignedValue() const
+{
+ return assignedValue;
+}
+
+void ConfigRadioButton::setAssignedValue(const QVariant& value)
+{
+ assignedValue = value;
+}
+
+void ConfigRadioButton::handleToggled(bool checked)
+{
+ if (handlingSlot)
+ return;
+
+ if (checked)
+ emit toggledOn(assignedValue);
+ else
+ emit toggledOff(assignedValue);
+}
+
+void ConfigRadioButton::alignToValue(const QVariant& value)
+{
+ handlingSlot = true;
+ setChecked(value == assignedValue);
+ handlingSlot = false;
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h
new file mode 100644
index 0000000..b972c26
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h
@@ -0,0 +1,46 @@
+#ifndef CONFIGRADIOBUTTON_H
+#define CONFIGRADIOBUTTON_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QRadioButton>
+#include <QVariant>
+
+/**
+ * @brief Config-oriented radio button.
+ *
+ * It's just like a usual QRadioButton, except it has a value assigned to it
+ * and when the radio is toggled on, the signal is emitted to inform about it.
+ * To inform about the button being toggled off a different signal is emitted.
+ * It also has a slot to be called when the associated property in the application
+ * has changed and needs to be reflected in the button - the button checks
+ * if the value of the property reflects the button's assigned value
+ * and toggles on or off approprietly. In that case the signals are not emitted.
+ */
+class GUI_API_EXPORT ConfigRadioButton : public QRadioButton
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVariant assignedValue READ getAssignedValue WRITE setAssignedValue)
+
+ public:
+ explicit ConfigRadioButton(QWidget *parent = 0);
+
+ QVariant getAssignedValue() const;
+ void setAssignedValue(const QVariant& value);
+
+ private:
+ QVariant assignedValue;
+ bool handlingSlot = false;
+
+ signals:
+ void toggledOn(const QVariant& assignedBalue);
+ void toggledOff(const QVariant& assignedBalue);
+
+ private slots:
+ void handleToggled(bool checked);
+
+ public slots:
+ void alignToValue(const QVariant& value);
+};
+
+#endif // CONFIGRADIOBUTTON_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp
new file mode 100644
index 0000000..655a9aa
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp
@@ -0,0 +1,138 @@
+#include "datawidgetmapper.h"
+#include <QAbstractItemModel>
+#include <QWidget>
+
+DataWidgetMapper::DataWidgetMapper(QObject *parent) :
+ QObject(parent)
+{
+}
+QAbstractItemModel* DataWidgetMapper::getModel() const
+{
+ return model;
+}
+
+void DataWidgetMapper::setModel(QAbstractItemModel* value)
+{
+ model = value;
+}
+
+void DataWidgetMapper::addMapping(QWidget* widget, int modelColumn, const QString& propertyName)
+{
+ MappingEntry* entry = new MappingEntry;
+ entry->columnIndex = modelColumn;
+ entry->widget = widget;
+ entry->propertyName = propertyName;
+ mappings[widget] = entry;
+}
+
+void DataWidgetMapper::clearMapping()
+{
+ for (MappingEntry* entry : mappings.values())
+ delete entry;
+
+ mappings.clear();
+}
+
+int DataWidgetMapper::getCurrentIndex() const
+{
+ return currentIndex;
+}
+
+int DataWidgetMapper::mappedSection(QWidget* widget) const
+{
+ if (mappings.contains(widget))
+ return mappings[widget]->columnIndex;
+
+ return -1;
+}
+
+void DataWidgetMapper::loadFromModel()
+{
+ QModelIndex idx;
+ QVariant data;
+ for (MappingEntry* entry : mappings.values())
+ {
+ idx = model->index(currentIndex, entry->columnIndex);
+ data = model->data(idx, Qt::EditRole);
+ entry->widget->setProperty(entry->propertyName.toLatin1().constData(), data);
+ }
+}
+
+DataWidgetMapper::SubmitFilter DataWidgetMapper::getSubmitFilter() const
+{
+ return submitFilter;
+}
+
+void DataWidgetMapper::setSubmitFilter(const SubmitFilter& value)
+{
+ submitFilter = value;
+}
+
+void DataWidgetMapper::setCurrentIndex(int rowIndex)
+{
+ if (!model)
+ return;
+
+ if (rowIndex < 0)
+ return;
+
+ if (rowIndex >= model->rowCount())
+ return;
+
+ if (model->rowCount() == 0)
+ return;
+
+ currentIndex = rowIndex;
+ loadFromModel();
+ emit currentIndexChanged(rowIndex);
+}
+
+void DataWidgetMapper::toFirst()
+{
+ setCurrentIndex(0);
+}
+
+void DataWidgetMapper::toLast()
+{
+ if (!model)
+ return;
+
+ setCurrentIndex(model->rowCount() - 1);
+}
+
+void DataWidgetMapper::toNext()
+{
+ setCurrentIndex(currentIndex + 1);
+}
+
+void DataWidgetMapper::toPrevious()
+{
+ setCurrentIndex(currentIndex - 1);
+}
+
+void DataWidgetMapper::submit()
+{
+ QModelIndex idx;
+ QVariant value;
+ for (MappingEntry* entry : mappings.values())
+ {
+ if (submitFilter && !submitFilter(entry->widget))
+ continue;
+
+ idx = model->index(currentIndex, entry->columnIndex);
+ value = entry->widget->property(entry->propertyName.toLatin1().constData());
+ model->setData(idx, value, Qt::EditRole);
+ }
+}
+
+void DataWidgetMapper::revert()
+{
+ if (!model)
+ return;
+
+ if (currentIndex < 0)
+ return;
+
+ loadFromModel();
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h
new file mode 100644
index 0000000..4df7d5e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h
@@ -0,0 +1,54 @@
+#ifndef DATAWIDGETMAPPER_H
+#define DATAWIDGETMAPPER_H
+
+#include <QObject>
+#include <QHash>
+
+class QAbstractItemModel;
+
+class DataWidgetMapper : public QObject
+{
+ Q_OBJECT
+ public:
+ typedef std::function<bool(QWidget*)> SubmitFilter;
+
+ explicit DataWidgetMapper(QObject *parent = 0);
+
+ QAbstractItemModel* getModel() const;
+ void setModel(QAbstractItemModel* value);
+ void addMapping(QWidget* widget, int modelColumn, const QString& propertyName);
+ void clearMapping();
+ int getCurrentIndex() const;
+ int mappedSection(QWidget* widget) const;
+ SubmitFilter getSubmitFilter() const;
+ void setSubmitFilter(const SubmitFilter& value);
+
+ private:
+ struct MappingEntry
+ {
+ QWidget* widget = nullptr;
+ int columnIndex = 0;
+ QString propertyName;
+ };
+
+ void loadFromModel();
+
+ QAbstractItemModel* model = nullptr;
+ int currentIndex = -1;
+ QHash<QWidget*,MappingEntry*> mappings;
+ SubmitFilter submitFilter = nullptr;
+
+ public slots:
+ void setCurrentIndex(int rowIndex);
+ void toFirst();
+ void toLast();
+ void toNext();
+ void toPrevious();
+ void submit();
+ void revert();
+
+ signals:
+ void currentIndexChanged(int newRowIndex);
+};
+
+#endif // DATAWIDGETMAPPER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp
new file mode 100644
index 0000000..3b09c79
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp
@@ -0,0 +1,30 @@
+#include "extaction.h"
+#include <QDebug>
+#include <QShortcutEvent>
+
+ExtAction::ExtAction(QObject *parent) :
+ QAction(parent)
+{
+}
+
+ExtAction::ExtAction(const QString& text, QObject* parent) :
+ QAction(text, parent)
+{
+}
+
+ExtAction::ExtAction(const QIcon& icon, const QString& text, QObject* parent) :
+ QAction(icon, text, parent)
+{
+}
+
+bool ExtAction::event(QEvent* e)
+{
+ // This implementation code comes mostly from Qt 5.1.0,
+ // but it was modified to handle ambiguous shortcuts.
+ if (e->type() == QEvent::Shortcut)
+ {
+ activate(Trigger);
+ return true;
+ }
+ return QObject::event(e);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extaction.h b/SQLiteStudio3/guiSQLiteStudio/common/extaction.h
new file mode 100644
index 0000000..5530271
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extaction.h
@@ -0,0 +1,20 @@
+#ifndef extaction_H
+#define extaction_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QAction>
+
+class GUI_API_EXPORT ExtAction : public QAction
+{
+ Q_OBJECT
+
+ public:
+ explicit ExtAction(QObject *parent = 0);
+ ExtAction(const QString& text, QObject* parent = 0);
+ ExtAction(const QIcon& icon, const QString& text, QObject* parent = 0);
+
+ protected:
+ bool event(QEvent* e);
+};
+
+#endif // extaction_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp
new file mode 100644
index 0000000..15bf926
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp
@@ -0,0 +1,260 @@
+#include "extactioncontainer.h"
+#include "iconmanager.h"
+#include "common/extaction.h"
+#include "common/global.h"
+#include <QSignalMapper>
+#include <QToolButton>
+#include <QToolBar>
+#include <QMenu>
+#include <QDebug>
+
+ExtActionContainer::ClassNameToToolBarAndAction ExtActionContainer::extraActions;
+QList<ExtActionContainer*> ExtActionContainer::instances;
+
+ExtActionContainer::ExtActionContainer()
+{
+ actionIdMapper = new QSignalMapper();
+
+ // We need to explicitly cast QSignalMapper::mapped to tell which overloaded version of function we want
+ QObject::connect(actionIdMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped),
+ [=](int action) {refreshShortcut(action);});
+ instances << this;
+}
+
+ExtActionContainer::~ExtActionContainer()
+{
+ deleteActions();
+ safe_delete(actionIdMapper);
+ instances.removeOne(this);
+}
+
+void ExtActionContainer::initActions()
+{
+ createActions();
+ setupDefShortcuts();
+ refreshShortcuts();
+ handleExtraActions();
+}
+
+void ExtActionContainer::createAction(int action, const Icon& icon, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner)
+{
+ QAction* qAction = new ExtAction(icon, text);
+ createAction(action, qAction, receiver, slot, container, owner);
+}
+
+void ExtActionContainer::createAction(int action, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner)
+{
+ QAction* qAction = new ExtAction(text);
+ createAction(action, qAction, receiver, slot, container, owner);
+}
+
+void ExtActionContainer::bindShortcutsToEnum(CfgCategory &cfgCategory, const QMetaEnum &actionsEnum)
+{
+ QHash<QString, CfgEntry *>& cfgEntries = cfgCategory.getEntries();
+ QString enumName;
+ CfgStringEntry* stringEntry = nullptr;
+ for (int i = 0, total = actionsEnum.keyCount(); i < total; ++i)
+ {
+ enumName = QString::fromLatin1(actionsEnum.key(i));
+ if (!cfgEntries.contains(enumName))
+ continue;
+
+ stringEntry = dynamic_cast<CfgStringEntry*>(cfgEntries[enumName]);
+ if (!stringEntry)
+ {
+ qDebug() << "Tried to bind key sequence config entry, but its type was not QString. Ignoring entry:" << cfgEntries[enumName]->getFullKey();
+ continue;
+ }
+
+ defShortcut(actionsEnum.value(i), stringEntry);
+ }
+}
+
+void ExtActionContainer::defShortcut(int action, CfgStringEntry *cfgEntry)
+{
+ shortcuts[action] = cfgEntry;
+
+ actionIdMapper->setMapping(cfgEntry, action);
+ QObject::connect(cfgEntry, SIGNAL(changed(QVariant)), actionIdMapper, SLOT(map()));
+}
+
+void ExtActionContainer::setShortcutContext(const QList<qint32> actions, Qt::ShortcutContext context)
+{
+ foreach (qint32 act, actions)
+ actionMap[act]->setShortcutContext(context);
+}
+
+void ExtActionContainer::attachActionInMenu(int parentAction, int childAction, QToolBar* toolbar)
+{
+ attachActionInMenu(parentAction, actionMap[childAction], toolbar);
+}
+
+void ExtActionContainer::attachActionInMenu(int parentAction, QAction* childAction, QToolBar* toolbar)
+{
+ attachActionInMenu(actionMap[parentAction], childAction, toolbar);
+}
+
+void ExtActionContainer::attachActionInMenu(QAction* parentAction, QAction* childAction, QToolBar* toolbar)
+{
+ QToolButton* button = dynamic_cast<QToolButton*>(toolbar->widgetForAction(parentAction));
+ QMenu* menu = button->menu();
+
+ if (!menu)
+ {
+ menu = new QMenu(button);
+ button->setMenu(menu);
+ button->setPopupMode(QToolButton::MenuButtonPopup);
+ }
+
+ menu->addAction(childAction);
+}
+
+void ExtActionContainer::updateShortcutTips()
+{
+}
+
+void ExtActionContainer::createAction(int action, QAction* qAction, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner)
+{
+ if (!owner)
+ owner = container;
+ else
+ owner->addAction(qAction);
+
+ qAction->setParent(owner);
+ actionMap[action] = qAction;
+ QObject::connect(qAction, SIGNAL(triggered()), receiver, slot);
+ container->addAction(qAction);
+}
+
+void ExtActionContainer::deleteActions()
+{
+ foreach (QAction* action, actionMap.values())
+ delete action;
+
+ actionMap.clear();
+}
+
+void ExtActionContainer::refreshShortcuts()
+{
+ foreach (int action, actionMap.keys())
+ {
+ if (!shortcuts.contains(action))
+ continue;
+
+ if (noConfigShortcutActions.contains(action))
+ continue;
+
+ refreshShortcut(action);
+ }
+}
+
+void ExtActionContainer::refreshShortcut(int action)
+{
+ QKeySequence seq(shortcuts[action]->get());
+ QString txt = seq.toString(QKeySequence::NativeText);
+ actionMap[action]->setShortcut(seq);
+ actionMap[action]->setToolTip(actionMap[action]->text() + QString(" (%1)").arg(txt));
+}
+
+QAction* ExtActionContainer::getAction(int action)
+{
+ if (!actionMap.contains(action))
+ return nullptr;
+
+ return actionMap.value(action);
+}
+
+void ExtActionContainer::handleActionInsert(int toolbar, ActionDetails* details)
+{
+ if (details->position > -1 && !actionMap.contains(details->position))
+ {
+ qWarning() << "Tried to insert action" << details->action->text() << "before action" << details->position
+ << "which is not present in action container:" << metaObject()->className();
+ return;
+ }
+
+ QToolBar* toolBar = getToolBar(toolbar);
+ if (!toolBar)
+ {
+ qWarning() << "Tried to insert action" << details->action->text() << ", but toolbar was incorrect: " << toolbar
+ << "or there is no toolbar in action container:" << metaObject()->className();
+ return;
+ }
+
+ QAction* beforeQAction = actionMap[details->position];
+ if (details->after)
+ {
+ QList<QAction*> acts = toolBar->actions();
+ int idx = acts.indexOf(beforeQAction);
+ idx++;
+ if (idx > 0 && idx < acts.size())
+ beforeQAction = acts[idx];
+ else
+ beforeQAction = nullptr;
+ }
+
+ QAction* action = details->action->create();
+ toolBar->insertAction(beforeQAction, action);
+
+ ToolbarAndProto toolbarAndProto(toolbar, details);
+ extraActionToToolbarAndProto[action] = toolbarAndProto;
+ toolbarAndProtoToAction[toolbarAndProto] = action;
+
+ QObject::connect(action, &QAction::triggered, [this, details, toolbar]()
+ {
+ details->action->emitTriggered(this, toolbar);
+ });
+
+ details->action->emitInsertedTo(this, toolbar, action);
+}
+
+void ExtActionContainer::handleActionRemoval(int toolbar, ActionDetails* details)
+{
+ QToolBar* toolBar = getToolBar(toolbar);
+ if (!toolBar)
+ {
+ qWarning() << "Tried to remove action" << details->action->text() << ", but toolbar was incorrect: " << toolbar << "or there is no toolbar in action container:"
+ << metaObject()->className();
+ return;
+ }
+
+
+ ToolbarAndProto toolbarAndProto(toolbar, details);
+ QAction* action = toolbarAndProtoToAction[toolbarAndProto];
+
+ details->action->emitAboutToRemoveFrom(this, toolbar, action);
+
+ toolBar->removeAction(action);
+ extraActionToToolbarAndProto.remove(action);
+ toolbarAndProtoToAction.remove(toolbarAndProto);
+
+ details->action->emitRemovedFrom(this, toolbar, action);
+ delete action;
+}
+
+void ExtActionContainer::handleExtraActions()
+{
+ QString clsName = metaObject()->className();
+ if (!extraActions.contains(clsName))
+ return;
+
+ // For each toolbar
+ for (int toolbarId : extraActions[clsName].keys())
+ {
+ // For each action for this toolbar
+ for (ActionDetails* actionDetails : extraActions[clsName][toolbarId])
+ {
+ // Insert action into toolbar, before action's assigned "before" action
+ handleActionInsert(toolbarId, actionDetails);
+ }
+ }
+}
+
+ExtActionContainer::ActionDetails::ActionDetails()
+{
+}
+
+ExtActionContainer::ActionDetails::ActionDetails(ExtActionPrototype* action, int position, bool after) :
+ action(action), position(position), after(after)
+{
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h
new file mode 100644
index 0000000..808af3e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h
@@ -0,0 +1,254 @@
+#ifndef extactionCONTAINER_H
+#define extactionCONTAINER_H
+
+#include "iconmanager.h"
+#include "config_builder.h"
+#include "extactionprototype.h"
+#include <QString>
+#include <QHash>
+#include <QSet>
+#include <QKeySequence>
+#include <QShortcut>
+#include <QMetaEnum>
+
+class QAction;
+class QObject;
+class QWidget;
+class QActionGroup;
+class QToolBar;
+class QSignalMapper;
+
+#define CFG_SHORTCUTS_METANAME "Shortcuts"
+
+#define CFG_KEY_LIST(Type, Title, Entries) \
+ _CFG_CATEGORIES_WITH_METANAME(Shortcuts##Type, \
+ _CFG_CATEGORY_WITH_TITLE(ShortcutsCategory##Type, Entries, Title), \
+ CFG_SHORTCUTS_METANAME\
+ )
+
+#define CFG_KEY_ENTRY(Name, KeyStr, Title) CFG_ENTRY(QString, Name, QKeySequence(KeyStr).toString(), Title)
+
+#define CFG_KEYS_DEFINE(Type) CFG_DEFINE_LAZY(Shortcuts##Type)
+
+/**
+ * @def Declares access object for defined shortuts.
+ *
+ * This is the same as CFG_INSTANCE for regular config values.
+ * It's optional. It doesn't need to be declared, but if you want to refer
+ * to keys as to configuration values, then you will need this.
+ */
+#define CFG_KEYS_INSTANCE(Type) (*Cfg::getShortcuts##Type##Instance())
+
+/**
+ * @def Binds shortcuts configuration with actions enumerator.
+ * @param Type Shortcuts category type that was passed to CFG_KEY_LIST.
+ * @param EnumName Enumerator type which lists actions that you want bind shortcuts to.
+ *
+ * Names of shortcut entries have to match names of enumerator literals in order to bind shortcuts
+ * to proper actions.
+ */
+#define BIND_SHORTCUTS(Type, EnumName) \
+ for (int _enumCounter = 0, _totalEnums = staticMetaObject.enumeratorCount(); _enumCounter < _totalEnums; _enumCounter++) \
+ { \
+ if (QString::fromLatin1(staticMetaObject.enumerator(_enumCounter).name()) == #EnumName) \
+ { \
+ bindShortcutsToEnum(Cfg::getShortcuts##Type##Instance()->ShortcutsCategory##Type, staticMetaObject.enumerator(_enumCounter)); \
+ break; \
+ } \
+ }
+
+#define GET_SHORTCUTS(Type) ExtActionContainer::getAllShortcutSequences(Cfg::getShortcuts##Type##Instance()->ShortcutsCategory##Type)
+
+class GUI_API_EXPORT ExtActionContainer
+{
+ private:
+ struct GUI_API_EXPORT ActionDetails
+ {
+ ActionDetails();
+ ActionDetails(ExtActionPrototype* action, int position, bool after);
+
+ ExtActionPrototype* action = nullptr;
+ int position = -1;
+ bool after = false;
+ };
+
+ typedef QList<ActionDetails*> ExtraActions;
+ typedef QHash<int,ExtraActions> ToolBarToAction;
+ typedef QHash<QString,ToolBarToAction> ClassNameToToolBarAndAction;
+
+ public:
+ ExtActionContainer();
+ virtual ~ExtActionContainer();
+
+ QAction* getAction(int action);
+ virtual const QMetaObject* metaObject() const = 0;
+
+ template <class T>
+ static void insertAction(ExtActionPrototype* action, int toolbar = -1);
+
+ template <class T>
+ static void insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar = -1);
+
+ template <class T>
+ static void insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar = -1);
+
+ template <class T>
+ static void removeAction(ExtActionPrototype* action, int toolbar = -1);
+
+ protected:
+ QHash<int,QAction*> actionMap;
+ QHash<int,CfgStringEntry*> shortcuts;
+ QSet<int> noConfigShortcutActions;
+
+ virtual void createActions() = 0;
+ virtual void setupDefShortcuts() = 0;
+
+ void initActions();
+ void createAction(int action, const Icon& icon, const QString& text, const QObject* receiver, const char* slot, QWidget* container,
+ QWidget* owner = 0);
+ void createAction(int action, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner = 0);
+
+ /**
+ * @brief Binds config shortcut entries with action enumerator.
+ * @param cfgCategory Config category with QString entries that have shortcut definitions.
+ * @param actionsEnum Enumerator with actions.
+ *
+ * Binds shortcuts defined in given config category to actions listed by the enumerator.
+ * Binding is done by name, that is name of the config entry (in the category) is matched against enumeration name,
+ *
+ * You don't normally use this method, but instead use BIND_SHORTCUTS.
+ */
+ void bindShortcutsToEnum(CfgCategory &cfgCategory, const QMetaEnum& actionsEnum);
+ void defShortcut(int action, CfgStringEntry* cfgEntry);
+ void setShortcutContext(const QList<qint32> actions, Qt::ShortcutContext context);
+
+ /**
+ * @brief attachActionInMenu
+ * @param parentAction Action that will have a submenu. Must already exist.
+ * @param childAction Action to add to the submenu. Must already exist.
+ * @param toolbar Toolbar that parentAction is already added to.
+ * Puts childAction into submenu of parentAction.
+ */
+ void attachActionInMenu(int parentAction, int childAction, QToolBar* toolbar);
+ void attachActionInMenu(int parentAction, QAction* childAction, QToolBar* toolbar);
+ void attachActionInMenu(QAction* parentAction, QAction* childAction, QToolBar* toolbar);
+ void updateShortcutTips();
+
+ /**
+ * @brief Tells the toolbar object for given toolbar enum value.
+ * @param toolbar Toolbar enum value for specific implementation of MdiChild.
+ * @return Toolbar object or null of there's no toolbar for given value, or no toolbar at all.
+ *
+ * The \p toolbar argument should be enum value from the specific implementation of MdiChild,
+ * for example for TableWindow it could be TOOLBAR_GRID_DATA, which refers to grid data tab toolbar.
+ *
+ * For classes with no toolbar this function will always return null;
+ *
+ * For classes with only one toolbar this method will always return that toolbar, no matter
+ * if the \p toolbar argument was correct.
+ *
+ * For classes with more than one toolbar this method will return proper toolbar objects only
+ * when the \p toolbar argument was correct, otherwise it returns null (assuming correct implementation
+ * of this method).
+ */
+ virtual QToolBar* getToolBar(int toolbar) const = 0;
+
+ void handleActionInsert(int toolbar, ActionDetails* details);
+ void handleActionRemoval(int toolbar, ActionDetails* details);
+
+ private:
+ typedef QPair<int,ActionDetails*> ToolbarAndProto;
+
+ void refreshShortcuts();
+ void refreshShortcut(int action);
+ void deleteActions();
+ void createAction(int action, QAction* qAction, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner);
+ void handleExtraActions();
+
+ template <class T>
+ static QList<T*> getInstances();
+
+ template <class T>
+ static void insertAction(ExtActionPrototype* action, int pos, bool after, int toolbar);
+
+ static ClassNameToToolBarAndAction extraActions;
+ static QList<ExtActionContainer*> instances;
+
+ QSignalMapper* actionIdMapper = nullptr;
+ QHash<QAction*,ToolbarAndProto> extraActionToToolbarAndProto;
+ QHash<ToolbarAndProto,QAction*> toolbarAndProtoToAction;
+};
+
+template <class T>
+void ExtActionContainer::insertAction(ExtActionPrototype* action, int pos, bool after, int toolbar)
+{
+ ActionDetails* dets = new ActionDetails(action, pos, after);
+ QString clsName = T::staticMetaObject.className();
+ extraActions[clsName][toolbar] << dets;
+ for (T* instance : getInstances<T>())
+ instance->handleActionInsert(toolbar, dets);
+}
+
+template <class T>
+void ExtActionContainer::insertAction(ExtActionPrototype* action, int toolbar)
+{
+ insertAction<T>(action, -1, false, toolbar);
+}
+
+template <class T>
+void ExtActionContainer::insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar)
+{
+ insertAction<T>(action, afterAction, true, toolbar);
+}
+
+template <class T>
+void ExtActionContainer::insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar)
+{
+ insertAction<T>(action, beforeAction, false, toolbar);
+}
+
+template <class T>
+void ExtActionContainer::removeAction(ExtActionPrototype* action, int toolbar)
+{
+ QString clsName = T::staticMetaObject.className();
+ if (!extraActions.contains(clsName))
+ return;
+
+ if (!extraActions[clsName].contains(toolbar))
+ return;
+
+ ActionDetails* dets = nullptr;
+ for (ActionDetails* d : extraActions[clsName][toolbar])
+ {
+ if (d->action == action)
+ {
+ dets = d;
+ break;
+ }
+ }
+
+ if (!dets)
+ return;
+
+ for (T* instance : getInstances<T>())
+ instance->handleActionRemoval(toolbar, dets);
+
+ extraActions[clsName][toolbar].removeOne(dets);
+ delete dets;
+}
+
+template <class T>
+QList<T*> ExtActionContainer::getInstances()
+{
+ QList<T*> typedInstances;
+ T* typedInstance = nullptr;
+ for (ExtActionContainer* instance : instances)
+ {
+ typedInstance = dynamic_cast<T*>(instance);
+ if (typedInstance)
+ typedInstances << typedInstance;
+ }
+ return typedInstances;
+}
+
+#endif // extactionCONTAINER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp
new file mode 100644
index 0000000..a6756b0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp
@@ -0,0 +1,19 @@
+#include "extactionmanagementnotifier.h"
+#include "extactioncontainer.h"
+#include <QDebug>
+
+ExtActionManagementNotifier::ExtActionManagementNotifier(QAction* action) :
+ QObject(nullptr), action(action)
+{
+ qDebug() << "create notifier" << this;
+}
+
+void ExtActionManagementNotifier::inserted(ExtActionContainer* object, QToolBar* toolbar)
+{
+ emit actionInserted(object, toolbar, action);
+}
+
+void ExtActionManagementNotifier::removed(ExtActionContainer* object, QToolBar* toolbar)
+{
+ emit actionRemoved(object, toolbar, action);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h
new file mode 100644
index 0000000..5ecb4e0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h
@@ -0,0 +1,30 @@
+#ifndef EXTACTIONMANAGEMENTNOTIFIER_H
+#define EXTACTIONMANAGEMENTNOTIFIER_H
+
+#include <QObject>
+#include <QSharedPointer>
+
+class QToolBar;
+class QAction;
+class ExtActionContainer;
+
+class ExtActionManagementNotifier : public QObject
+{
+ Q_OBJECT
+ public:
+ explicit ExtActionManagementNotifier(QAction* action);
+
+ void inserted(ExtActionContainer* object, QToolBar* toolbar);
+ void removed(ExtActionContainer* object, QToolBar* toolbar);
+
+ private:
+ QAction* action = nullptr;
+
+ signals:
+ void actionInserted(ExtActionContainer* object, QToolBar* toolbar, QAction* action);
+ void actionRemoved(ExtActionContainer* object, QToolBar* toolbar, QAction* action);
+};
+
+typedef QSharedPointer<ExtActionManagementNotifier> ExtActionManagementNotifierPtr;
+
+#endif // EXTACTIONMANAGEMENTNOTIFIER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp
new file mode 100644
index 0000000..0dfafba
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp
@@ -0,0 +1,65 @@
+#include "extactionprototype.h"
+#include "extactioncontainer.h"
+#include <QAction>
+#include <QDebug>
+
+ExtActionPrototype::ExtActionPrototype(QObject* parent) :
+ QObject(parent)
+{
+ separator = true;
+}
+
+ExtActionPrototype::ExtActionPrototype(const QString& text, QObject* parent) :
+ QObject(parent), actionText(text)
+{
+}
+
+ExtActionPrototype::ExtActionPrototype(const QIcon& icon, const QString& text, QObject* parent) :
+ QObject(parent), icon(icon), actionText(text)
+{
+}
+
+ExtActionPrototype::~ExtActionPrototype()
+{
+
+}
+
+QString ExtActionPrototype::text() const
+{
+ return actionText;
+}
+
+QAction* ExtActionPrototype::create(QObject* parent)
+{
+ if (!parent)
+ parent = this;
+
+ if (separator)
+ {
+ QAction* act = new QAction(parent);
+ act->setSeparator(true);
+ return act;
+ }
+
+ return new QAction(icon, actionText, parent);
+}
+
+void ExtActionPrototype::emitInsertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action)
+{
+ emit insertedTo(actionContainer, toolbar, action);
+}
+
+void ExtActionPrototype::emitAboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action)
+{
+ emit aboutToRemoveFrom(actionContainer, toolbar, action);
+}
+
+void ExtActionPrototype::emitRemovedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action)
+{
+ emit removedFrom(actionContainer, toolbar, action);
+}
+
+void ExtActionPrototype::emitTriggered(ExtActionContainer* actionContainer, int toolbar)
+{
+ emit triggered(actionContainer, toolbar);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h
new file mode 100644
index 0000000..7fd20d1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h
@@ -0,0 +1,44 @@
+#ifndef EXTACTIONPROTOTYPE_H
+#define EXTACTIONPROTOTYPE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QString>
+#include <QIcon>
+#include <QObject>
+
+class QAction;
+class ExtActionContainer;
+
+class GUI_API_EXPORT ExtActionPrototype : public QObject
+{
+ Q_OBJECT
+
+ friend class ExtActionContainer;
+
+ public:
+ explicit ExtActionPrototype(QObject* parent);
+ ExtActionPrototype(const QString& text, QObject* parent = 0);
+ ExtActionPrototype(const QIcon& icon, const QString& text, QObject* parent = 0);
+ ~ExtActionPrototype();
+
+ QString text() const;
+ QAction* create(QObject* parent = 0);
+
+ private:
+ void emitInsertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void emitAboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void emitRemovedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void emitTriggered(ExtActionContainer* actionContainer, int toolbar);
+
+ QIcon icon;
+ QString actionText;
+ bool separator = false;
+
+ signals:
+ void insertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void aboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void removedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action);
+ void triggered(ExtActionContainer* actionContainer, int toolbar);
+};
+
+#endif // EXTACTIONPROTOTYPE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp
new file mode 100644
index 0000000..bd0bffa
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp
@@ -0,0 +1,118 @@
+#include "extlineedit.h"
+#include "iconmanager.h"
+#include <QStyle>
+#include <QAction>
+#include <QDebug>
+
+ExtLineEdit::ExtLineEdit(QWidget* parent)
+ : QLineEdit(parent)
+{
+ init();
+}
+
+ExtLineEdit::ExtLineEdit(const QString& text, QWidget *parent)
+ : QLineEdit(text, parent)
+{
+ init();
+}
+
+void ExtLineEdit::init()
+{
+ connect(this, &QLineEdit::textChanged, this, &ExtLineEdit::handleTextChanged);
+}
+
+void ExtLineEdit::updateMinSize()
+{
+ setMinimumSize(expandingMinWidth, 0);
+}
+
+int ExtLineEdit::getExpandingMaxWidth() const
+{
+ return expandingMaxWidth;
+}
+
+void ExtLineEdit::setExpandingMaxWidth(int value)
+{
+ expandingMaxWidth = value;
+ setMaximumWidth(value);
+}
+
+void ExtLineEdit::setClearButtonEnabled(bool enable)
+{
+ QLineEdit::setClearButtonEnabled(enable);
+ if (enable)
+ {
+ // This is a hack to get to know when QLineEdit's clear button is pressed.
+ // Unfortunately Qt 5.2 API doesn't provide such information,
+ // but we can find QAction responsible for it by its object name
+ // and handle its triggered() signal.
+ // This is not part of an official Qt's API and may be modified in any Qt version.
+ // Ugly, but works.
+ static const char* qtClearBtnActionName = "_q_qlineeditclearaction";
+ QAction *clearAction = findChild<QAction*>(qtClearBtnActionName);
+ if (!clearAction)
+ {
+ qWarning() << "Could not find 'clear action' in QLineEdit, so 'valueErased()' signal won't be emitted from ExtLineEdit.";
+ return;
+ }
+ connect(clearAction, SIGNAL(triggered()), this, SIGNAL(valueErased()));
+ }
+}
+
+
+bool ExtLineEdit::getExpanding() const
+{
+ return expanding;
+}
+
+void ExtLineEdit::setExpanding(bool value)
+{
+ expanding = value;
+ if (!expanding)
+ setFixedWidth(-1);
+ else
+ setFixedWidth(expandingMinWidth);
+}
+
+int ExtLineEdit::getExpandingMinWidth() const
+{
+ return expandingMinWidth;
+}
+
+void ExtLineEdit::setExpandingMinWidth(int value)
+{
+ expandingMinWidth = value;
+ updateMinSize();
+}
+
+void ExtLineEdit::handleTextChanged()
+{
+ QString txt = text();
+ if (!expanding)
+ return;
+
+ // Text width
+ int newWidth = fontMetrics().width(txt);
+
+ // Text margins
+ QMargins margins = textMargins();
+ newWidth += margins.left() + margins.right();
+
+ // Content margins
+ QMargins localContentsMargins = contentsMargins();
+ newWidth += localContentsMargins.left() + localContentsMargins.right();
+
+ // Frame
+ int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+ newWidth += frameWidth * 2;
+
+ // Extra space
+ newWidth += expandingExtraSpace;
+
+ if (newWidth < expandingMinWidth)
+ newWidth = expandingMinWidth;
+ else if (expandingMaxWidth > 0 && newWidth > expandingMaxWidth)
+ newWidth = expandingMaxWidth;
+
+ setFixedWidth(newWidth);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h
new file mode 100644
index 0000000..b3bffbb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h
@@ -0,0 +1,45 @@
+#ifndef EXTLINEEDIT_H
+#define EXTLINEEDIT_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QLineEdit>
+
+class QToolButton;
+
+class GUI_API_EXPORT ExtLineEdit : public QLineEdit
+{
+ Q_OBJECT
+
+ public:
+ explicit ExtLineEdit(QWidget *parent = 0);
+ explicit ExtLineEdit(const QString& text, QWidget *parent = 0);
+
+ bool getExpanding() const;
+ void setExpanding(bool value);
+
+ int getExpandingMinWidth() const;
+ void setExpandingMinWidth(int value);
+
+ int getExpandingMaxWidth() const;
+ void setExpandingMaxWidth(int value);
+
+ void setClearButtonEnabled(bool enable);
+
+ private:
+ void init();
+ void updateMinSize();
+
+ static const int expandingExtraSpace = 4; // QLineEdit has hardcoded horizontal margin of 2 for both sides
+
+ bool expanding = false;
+ int expandingMinWidth = 0;
+ int expandingMaxWidth = -1;
+
+ private slots:
+ void handleTextChanged();
+
+ signals:
+ void valueErased();
+};
+
+#endif // EXTLINEEDIT_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp
new file mode 100644
index 0000000..9f628ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp
@@ -0,0 +1,98 @@
+#include "fileedit.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QToolButton>
+#include <QFileDialog>
+
+FileEdit::FileEdit(QWidget *parent) :
+ QWidget(parent)
+{
+ setLayout(new QHBoxLayout());
+ layout()->setMargin(0);
+
+ lineEdit = new QLineEdit();
+ button = new QToolButton();
+ button->setIcon(ICONS.OPEN_FILE);
+ layout()->addWidget(lineEdit);
+ layout()->addWidget(button);
+
+ connect(button, SIGNAL(clicked()), this, SLOT(browse()));
+ connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(lineTextChanged()));
+}
+
+QString FileEdit::getFile() const
+{
+ return file;
+}
+
+bool FileEdit::getSave() const
+{
+ return save;
+}
+
+QString FileEdit::getDialogTitle() const
+{
+ return dialogTitle;
+}
+
+QString FileEdit::getFilters() const
+{
+ return filters;
+}
+
+void FileEdit::browse()
+{
+ QString path;
+ QString dir = getFileDialogInitPath();
+ if (save)
+ path = QFileDialog::getSaveFileName(this, dialogTitle, dir, filters);
+ else
+ path = QFileDialog::getOpenFileName(this, dialogTitle, dir, filters);
+
+ if (path.isNull())
+ return;
+
+ setFile(path);
+ setFileDialogInitPathByFile(path);
+}
+
+void FileEdit::lineTextChanged()
+{
+ file = lineEdit->text();
+ emit fileChanged(file);
+}
+
+void FileEdit::setFile(QString arg)
+{
+ if (file != arg) {
+ file = arg;
+ lineEdit->setText(file);
+ emit fileChanged(arg);
+ }
+}
+
+void FileEdit::setSave(bool arg)
+{
+ if (save != arg) {
+ save = arg;
+ emit saveChanged(arg);
+ }
+}
+
+void FileEdit::setDialogTitle(QString arg)
+{
+ if (dialogTitle != arg) {
+ dialogTitle = arg;
+ emit dialogTitleChanged(arg);
+ }
+}
+
+void FileEdit::setFilters(QString arg)
+{
+ if (filters != arg) {
+ filters = arg;
+ emit filtersChanged(arg);
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h
new file mode 100644
index 0000000..8cb62a6
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h
@@ -0,0 +1,52 @@
+#ifndef FILEEDIT_H
+#define FILEEDIT_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+class QLineEdit;
+class QToolButton;
+
+class GUI_API_EXPORT FileEdit : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString file READ getFile WRITE setFile NOTIFY fileChanged)
+ Q_PROPERTY(bool save READ getSave WRITE setSave NOTIFY saveChanged)
+ Q_PROPERTY(QString dialogTitle READ getDialogTitle WRITE setDialogTitle NOTIFY dialogTitleChanged)
+ Q_PROPERTY(QString filters READ getFilters WRITE setFilters NOTIFY filtersChanged)
+
+ public:
+ explicit FileEdit(QWidget *parent = 0);
+
+ QString getFile() const;
+ bool getSave() const;
+ QString getDialogTitle() const;
+ QString getFilters() const;
+
+ private:
+ QString file;
+ bool save = false;
+ QString dialogTitle;
+ QString filters;
+ QLineEdit* lineEdit = nullptr;
+ QToolButton* button = nullptr;
+
+ signals:
+ void fileChanged(QString arg);
+ void saveChanged(bool arg);
+ void dialogTitleChanged(QString arg);
+ void filtersChanged(QString arg);
+
+ private slots:
+ void browse();
+ void lineTextChanged();
+
+ public slots:
+ void setFile(QString arg);
+ void setSave(bool arg);
+ void setDialogTitle(QString arg);
+ void setFilters(QString arg);
+};
+
+#endif // FILEEDIT_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp
new file mode 100644
index 0000000..a70122b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp
@@ -0,0 +1,68 @@
+#include "fontedit.h"
+#include "ui_fontedit.h"
+#include "iconmanager.h"
+#include <QDebug>
+#include <QFontDialog>
+
+FontEdit::FontEdit(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::FontEdit)
+{
+ init();
+}
+
+FontEdit::~FontEdit()
+{
+ delete ui;
+}
+
+QFont FontEdit::getFont() const
+{
+ return font;
+}
+
+void FontEdit::setFont(QFont arg)
+{
+ font = arg;
+ updateFont();
+}
+
+void FontEdit::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void FontEdit::init()
+{
+ ui->setupUi(this);
+ ui->button->setIcon(ICONS.FONT_BROWSE);
+ connect(ui->button, SIGNAL(clicked()), this, SLOT(browse()));
+ updateFont();
+}
+
+void FontEdit::updateFont()
+{
+ static const QString text = "%1, %2";
+ ui->label->setFont(font);
+ int size = font.pointSize() > -1 ? font.pointSize() : font.pixelSize();
+ ui->label->setText(text.arg(font.family()).arg(size));
+}
+
+void FontEdit::browse()
+{
+ bool ok;
+ QFont newFont = QFontDialog::getFont(&ok, ui->label->font(), this, tr("Choose font", "font configuration"));
+ if (!ok)
+ return;
+
+ font = newFont;
+ updateFont();
+ emit fontChanged(newFont);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h
new file mode 100644
index 0000000..e68463b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h
@@ -0,0 +1,43 @@
+#ifndef FONTEDIT_H
+#define FONTEDIT_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class FontEdit;
+}
+
+class GUI_API_EXPORT FontEdit : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QFont font READ getFont WRITE setFont NOTIFY fontChanged)
+
+ public:
+ explicit FontEdit(QWidget *parent = 0);
+ ~FontEdit();
+
+ QFont getFont() const;
+
+ public slots:
+ void setFont(QFont arg);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void updateFont();
+
+ Ui::FontEdit *ui = nullptr;
+ QFont font;
+
+ private slots:
+ void browse();
+
+ signals:
+ void fontChanged(const QFont& font);
+};
+
+#endif // FONTEDIT_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui
new file mode 100644
index 0000000..d8daa9f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FontEdit</class>
+ <widget class="QWidget" name="FontEdit">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>451</width>
+ <height>35</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp
new file mode 100644
index 0000000..bfcf7b0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp
@@ -0,0 +1,38 @@
+#include "intvalidator.h"
+
+IntValidator::IntValidator(QObject *parent) :
+ QIntValidator(parent)
+{
+}
+
+IntValidator::IntValidator(int min, int max, QObject* parent)
+ : QIntValidator(min, max, parent)
+{
+}
+
+void IntValidator::fixup(QString& input) const
+{
+ QIntValidator::fixup(input);
+ if (input.trimmed().isEmpty())
+ input = QString::number(defaultValue);
+
+ bool ok;
+ int val = input.toInt(&ok);
+ if (!ok)
+ return;
+
+ if (val < bottom())
+ input = QString::number(bottom());
+ else if (val > top())
+ input = QString::number(top());
+}
+
+int IntValidator::getDefaultValue() const
+{
+ return defaultValue;
+}
+
+void IntValidator::setDefaultValue(int value)
+{
+ defaultValue = value;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h
new file mode 100644
index 0000000..5d2edaa
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h
@@ -0,0 +1,25 @@
+#ifndef INTVALIDATOR_H
+#define INTVALIDATOR_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QIntValidator>
+#include <QString>
+
+class GUI_API_EXPORT IntValidator : public QIntValidator
+{
+ Q_OBJECT
+
+ public:
+ explicit IntValidator(QObject *parent = 0);
+ explicit IntValidator(int min, int max, QObject *parent = 0);
+
+ void fixup(QString& input) const;
+
+ int getDefaultValue() const;
+ void setDefaultValue(int value);
+
+ private:
+ int defaultValue = 0;
+};
+
+#endif // INTVALIDATOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp
new file mode 100644
index 0000000..5a48033
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp
@@ -0,0 +1,152 @@
+#include "numericspinbox.h"
+#include "common/unused.h"
+#include <QLineEdit>
+#include <QVariant>
+#include <QDebug>
+
+NumericSpinBox::NumericSpinBox(QWidget *parent) :
+ QAbstractSpinBox(parent)
+{
+ connect(lineEdit(), &QLineEdit::textChanged, this, &NumericSpinBox::valueEdited);
+}
+
+void NumericSpinBox::stepBy(int steps)
+{
+ if (isReadOnly())
+ return;
+
+ switch (value.userType())
+ {
+ case QVariant::Double:
+ stepDoubleBy(steps);
+ break;
+ case QVariant::Int:
+ case QVariant::LongLong:
+ stepIntBy(steps);
+ break;
+ default:
+ break;
+ }
+ updateText();
+}
+
+QValidator::State NumericSpinBox::validate(QString& input, int& pos) const
+{
+ UNUSED(input);
+ UNUSED(pos);
+ emit modified();
+
+ if (strict)
+ return validateStrict(input, pos);
+
+ return QValidator::Acceptable;
+}
+
+void NumericSpinBox::stepIntBy(int steps)
+{
+ qint64 intVal = value.toLongLong();
+ intVal += steps;
+ value = intVal;
+ emit modified();
+}
+
+void NumericSpinBox::stepDoubleBy(int steps)
+{
+ double doubleVal = value.toDouble();
+ doubleVal += steps;
+ value = doubleVal;
+ emit modified();
+}
+
+void NumericSpinBox::updateText()
+{
+ lineEdit()->setText(value.toString());
+}
+
+QValidator::State NumericSpinBox::validateStrict(QString& input, int& pos) const
+{
+ if (input.trimmed().isEmpty())
+ return allowEmpty ? QValidator::Acceptable : QValidator::Invalid;
+
+ QIntValidator vint;
+ if (vint.validate(input, pos) != QValidator::Invalid)
+ return QValidator::Acceptable;
+
+ QDoubleValidator dint;
+ if (dint.validate(input, pos) != QValidator::Invalid)
+ return QValidator::Acceptable;
+
+ return QValidator::Invalid;
+}
+bool NumericSpinBox::getAllowEmpty() const
+{
+ return allowEmpty;
+}
+
+void NumericSpinBox::setAllowEmpty(bool value)
+{
+ allowEmpty = value;
+}
+
+
+bool NumericSpinBox::isStrict() const
+{
+ return strict;
+}
+
+void NumericSpinBox::setStrict(bool value, bool allowEmpty)
+{
+ strict = value;
+ this->allowEmpty = allowEmpty;
+}
+
+void NumericSpinBox::valueEdited(const QString& value)
+{
+ setValueInternal(value);
+}
+
+QAbstractSpinBox::StepEnabled NumericSpinBox::stepEnabled() const
+{
+ return StepDownEnabled|StepUpEnabled;
+}
+
+QVariant NumericSpinBox::getFixedVariant(const QVariant& value)
+{
+ bool ok;
+ qint64 longVal = value.toLongLong(&ok);
+ if (ok)
+ return longVal;
+
+ return value.toDouble();
+}
+
+void NumericSpinBox::setValueInternal(const QVariant& newValue)
+{
+ switch (newValue.userType())
+ {
+ case QVariant::String:
+ value = getFixedVariant(newValue);
+ break;
+ case QVariant::Double:
+ case QVariant::Int:
+ case QVariant::LongLong:
+ value = newValue;
+ break;
+ default:
+ value = 0;
+ }
+}
+
+QVariant NumericSpinBox::getValue() const
+{
+ return value;
+}
+
+void NumericSpinBox::setValue(const QVariant& newValue, bool nullAsZero)
+{
+ setValueInternal(newValue);
+ if (!nullAsZero && newValue.isNull())
+ value = newValue;
+
+ updateText();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h
new file mode 100644
index 0000000..79cd56c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h
@@ -0,0 +1,57 @@
+#ifndef QNUMERICSPINBOX_H
+#define QNUMERICSPINBOX_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractSpinBox>
+
+/**
+ * @brief The NumericSpinBox class
+ * This class implements a spinbox for numeric SQLite types.
+ * This includes integers, as well as decimals.
+ * User is also allowed to type in any text value (unless "strict" property is set),
+ * but once he uses "step up" or "step down", the text value
+ * gets replaced with 0.
+ * If strict propery is set, also the allowEmpty property starts to matter.
+ * Otherwise allowEmpty is ignored.
+ */
+class GUI_API_EXPORT NumericSpinBox : public QAbstractSpinBox
+{
+ Q_OBJECT
+ public:
+ explicit NumericSpinBox(QWidget *parent = 0);
+
+ void stepBy(int steps);
+ QValidator::State validate(QString& input, int& pos) const;
+
+ QVariant getValue() const;
+ void setValue(const QVariant& newValue, bool nullAsZero = true);
+
+ bool isStrict() const;
+ void setStrict(bool value, bool allowEmpty = true);
+
+ bool getAllowEmpty() const;
+ void setAllowEmpty(bool value);
+
+ protected:
+ StepEnabled stepEnabled() const;
+
+ private:
+ QVariant getFixedVariant(const QVariant& value);
+ void setValueInternal(const QVariant& newValue);
+ void stepIntBy(int steps);
+ void stepDoubleBy(int steps);
+ void updateText();
+ QValidator::State validateStrict(QString &input, int &pos) const;
+
+ QVariant value;
+ bool strict = false;
+ bool allowEmpty = true;
+
+ private slots:
+ void valueEdited(const QString& value);
+
+ signals:
+ void modified() const;
+};
+
+#endif // QNUMERICSPINBOX_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp
new file mode 100644
index 0000000..c9e1446
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp
@@ -0,0 +1,49 @@
+#include "tablewidget.h"
+#include <QKeyEvent>
+#include <QApplication>
+#include <QClipboard>
+#include <QLabel>
+
+TableWidget::TableWidget(QWidget *parent) :
+ QTableWidget(parent)
+{
+}
+
+void TableWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (event->matches(QKeySequence::Copy))
+ {
+ copy();
+ return;
+ }
+
+ QTableWidget::keyPressEvent(event);
+}
+
+void TableWidget::copy()
+{
+ QStringList strings;
+ QStringList cols;
+ for (int i = 0, total = rowCount(); i < total; ++i)
+ {
+ if (!item(i, 0)->isSelected())
+ continue;
+
+ for (int c = 1; c <= 2; c++)
+ {
+ if (cellWidget(i, c))
+ {
+ QLabel* l = dynamic_cast<QLabel*>(cellWidget(i, c));
+ if (l)
+ cols << l->text();
+ }
+ else
+ {
+ cols << item(i, c)->text();
+ }
+ }
+ strings << cols.join(" ");
+ }
+
+ QApplication::clipboard()->setText(strings.join("\n"));
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h
new file mode 100644
index 0000000..26b424c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h
@@ -0,0 +1,23 @@
+#ifndef TABLEWIDGET_H
+#define TABLEWIDGET_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QTableWidget>
+
+class QKeyEvent;
+
+class GUI_API_EXPORT TableWidget : public QTableWidget
+{
+ Q_OBJECT
+ public:
+ explicit TableWidget(QWidget *parent = 0);
+
+ protected:
+ void keyPressEvent(QKeyEvent *event);
+
+
+ public slots:
+ void copy();
+};
+
+#endif // TABLEWIDGET_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp
new file mode 100644
index 0000000..48ea46e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp
@@ -0,0 +1,33 @@
+#include "userinputfilter.h"
+#include "common/unused.h"
+#include <QTimer>
+#include <QLineEdit>
+
+UserInputFilter::UserInputFilter(QLineEdit* lineEdit, QObject* filterHandler, const char* handlerSlot) :
+ QObject(lineEdit),
+ lineEdit(lineEdit)
+{
+ timer = new QTimer(this);
+ timer->setSingleShot(false);
+ timer->setInterval(200);
+ connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterModified(QString)));
+ connect(timer, SIGNAL(timeout()), this, SLOT(applyFilter()));
+ connect(this, SIGNAL(applyFilter(QString)), filterHandler, handlerSlot);
+}
+
+void UserInputFilter::setDelay(int msecs)
+{
+ timer->setInterval(msecs);
+}
+
+void UserInputFilter::filterModified(const QString& newValue)
+{
+ UNUSED(newValue);
+ timer->start();
+}
+
+void UserInputFilter::applyFilter()
+{
+ timer->stop();
+ emit applyFilter(lineEdit->text());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h
new file mode 100644
index 0000000..1b6f7ee
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h
@@ -0,0 +1,31 @@
+#ifndef USERINPUTFILTER_H
+#define USERINPUTFILTER_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QObject>
+
+class QTimer;
+class QLineEdit;
+
+class GUI_API_EXPORT UserInputFilter : public QObject
+{
+ Q_OBJECT
+
+ public:
+ UserInputFilter(QLineEdit* lineEdit, QObject* filterHandler, const char* handlerSlot);
+
+ void setDelay(int msecs);
+
+ private:
+ QTimer* timer = nullptr;
+ QLineEdit* lineEdit = nullptr;
+
+ private slots:
+ void filterModified(const QString& newValue);
+ void applyFilter();
+
+ signals:
+ void applyFilter(const QString& value);
+};
+
+#endif // USERINPUTFILTER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp
new file mode 100644
index 0000000..596b000
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp
@@ -0,0 +1,20 @@
+#include "verifiablewizardpage.h"
+
+VerifiableWizardPage::VerifiableWizardPage(QWidget *parent) :
+ QWizardPage(parent)
+{
+}
+
+bool VerifiableWizardPage::isComplete() const
+{
+ if (!validator)
+ return false;
+
+ return validator();
+}
+
+void VerifiableWizardPage::setValidator(const Validator& value)
+{
+ validator = value;
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h
new file mode 100644
index 0000000..8912258
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h
@@ -0,0 +1,22 @@
+#ifndef VERIFIABLEWIZARDPAGE_H
+#define VERIFIABLEWIZARDPAGE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWizardPage>
+
+class GUI_API_EXPORT VerifiableWizardPage : public QWizardPage
+{
+ Q_OBJECT
+ public:
+ typedef std::function<bool()> Validator;
+
+ explicit VerifiableWizardPage(QWidget *parent = 0);
+
+ bool isComplete() const;
+ void setValidator(const Validator& value);
+
+ private:
+ Validator validator;
+};
+
+#endif // VERIFIABLEWIZARDPAGE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp
new file mode 100644
index 0000000..7cc6a4e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp
@@ -0,0 +1,209 @@
+#include "widgetcover.h"
+#include "common/unused.h"
+#include <QVariantAnimation>
+#include <QDebug>
+#include <QGraphicsBlurEffect>
+#include <QPushButton>
+#include <QGridLayout>
+#include <QEvent>
+#include <QPushButton>
+#include <QProgressBar>
+
+WidgetCover::WidgetCover(QWidget *parent) :
+ QWidget(parent)
+{
+ init();
+}
+
+WidgetCover::WidgetCover(const QEasingCurve& easingCurve, QWidget* parent)
+ : QWidget(parent), easingCurve(easingCurve)
+{
+ init();
+}
+
+WidgetCover::~WidgetCover()
+{
+ interruptAction();
+}
+
+void WidgetCover::init()
+{
+ parentWidget()->installEventFilter(this);
+
+ setLayout(new QGridLayout(this));
+ layout()->setAlignment(Qt::AlignCenter);
+
+ container = new QWidget(this);
+ container->setVisible(false);
+ layout()->addWidget(container);
+
+ containerLayout = new QGridLayout(container);
+ containerLayout->setSizeConstraint(QLayout::SetFixedSize);
+
+ animation = new QVariantAnimation(this);
+ animation->setEasingCurve(easingCurve);
+ animation->setDuration(duration);
+ connect(animation, SIGNAL(valueChanged(QVariant)), this, SLOT(animationUpdate(QVariant)));
+ connect(animation, SIGNAL(finished()), this, SLOT(animationFinished()));
+
+ setAutoFillBackground(true);
+ resetBackground();
+ move(0, 0);
+ widgetResized();
+ hide();
+}
+
+void WidgetCover::interruptAction()
+{
+ setVisible(false);
+ animation->stop();
+}
+
+void WidgetCover::resetBackground()
+{
+ QPalette pal = palette();
+ pal.setBrush(QPalette::Window, QColor(0, 0, 0, 0));
+ setPalette(pal);
+}
+
+void WidgetCover::animationUpdate(const QVariant& value)
+{
+ QPalette pal = palette();
+ pal.setBrush(QPalette::Window, value.value<QColor>());
+ setPalette(pal);
+}
+
+void WidgetCover::animationFinished()
+{
+ switch (actionInProgres)
+ {
+ case Action::HIDING:
+ {
+ setVisible(false);
+ resetBackground();
+ break;
+ }
+ case Action::SHOWING:
+ {
+ container->setVisible(true);
+ break;
+ }
+ default:
+ break;
+ }
+
+ actionInProgres = Action::NONE;
+}
+
+void WidgetCover::widgetResized()
+{
+// qDebug() << parentWidget()->size();
+ setFixedSize(parentWidget()->size());
+}
+
+void WidgetCover::show()
+{
+ if (actionInProgres == Action::SHOWING)
+ return;
+
+ if (actionInProgres == Action::HIDING)
+ animation->stop();
+
+ actionInProgres = Action::SHOWING;
+
+ if (cancelButton)
+ cancelButton->setEnabled(true);
+
+ QPalette pal = palette();
+ animation->setStartValue(QVariant(pal.brush(QPalette::Window).color()));
+ animation->setEndValue(QVariant(QColor(0, 0, 0, transparency)));
+ setVisible(true);
+ container->setVisible(true);
+ animation->start();
+}
+
+void WidgetCover::hide()
+{
+ if (actionInProgres == Action::HIDING)
+ return;
+
+ if (actionInProgres == Action::SHOWING)
+ animation->stop();
+
+ actionInProgres = Action::HIDING;
+
+ container->setVisible(false);
+
+ QPalette pal = palette();
+ animation->setStartValue(QVariant(pal.brush(QPalette::Window).color()));
+ animation->setEndValue(QVariant(QColor(0, 0, 0, 0)));
+ animation->start();
+}
+
+QEasingCurve WidgetCover::getEasingCurve() const
+{
+ return easingCurve;
+}
+
+void WidgetCover::setEasingCurve(const QEasingCurve& value)
+{
+ easingCurve = value;
+ animation->setEasingCurve(easingCurve);
+}
+
+int WidgetCover::getDuration() const
+{
+ return duration;
+}
+
+void WidgetCover::setDuration(int value)
+{
+ duration = value;
+ animation->setDuration(duration);
+}
+
+int WidgetCover::getTransparency() const
+{
+ return transparency;
+}
+
+void WidgetCover::setTransparency(int value)
+{
+ if (value < 0)
+ value = 0;
+
+ if (value > 255)
+ value = 255;
+
+ transparency = value;
+}
+
+QGridLayout* WidgetCover::getContainerLayout()
+{
+ return containerLayout;
+}
+
+bool WidgetCover::eventFilter(QObject* obj, QEvent* e)
+{
+ UNUSED(obj);
+ if (e->type() == QEvent::Resize)
+ widgetResized();
+
+ return false;
+}
+
+void WidgetCover::initWithInterruptContainer(const QString& interruptButtonText)
+{
+ cancelButton = new QPushButton();
+ cancelButton->setText(interruptButtonText.isNull() ? tr("Interrupt") : interruptButtonText);
+
+ busyBar = new QProgressBar();
+ busyBar->setRange(0, 0);
+ busyBar->setTextVisible(false);
+
+ containerLayout->addWidget(busyBar, 0, 0);
+ containerLayout->addWidget(cancelButton, 1, 0);
+
+ connect(cancelButton, &QPushButton::clicked, [=]() {cancelButton->setEnabled(false);});
+ connect(cancelButton, SIGNAL(clicked()), this, SIGNAL(cancelClicked()));
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h
new file mode 100644
index 0000000..d0ccef7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h
@@ -0,0 +1,72 @@
+#ifndef WIDGETCOVER_H
+#define WIDGETCOVER_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+#include <QEasingCurve>
+#include <QVariant>
+
+class QVariantAnimation;
+class QGridLayout;
+class QPushButton;
+class QProgressBar;
+
+class GUI_API_EXPORT WidgetCover : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit WidgetCover(QWidget *parent);
+ explicit WidgetCover(const QEasingCurve& easingCurve, QWidget *parent);
+ virtual ~WidgetCover();
+
+ QEasingCurve getEasingCurve() const;
+ void setEasingCurve(const QEasingCurve& value);
+
+ int getDuration() const;
+ void setDuration(int value);
+
+ int getTransparency() const;
+ void setTransparency(int value);
+
+ QGridLayout* getContainerLayout();
+ bool eventFilter(QObject* obj, QEvent* e);
+
+ void initWithInterruptContainer(const QString& interruptButtonText = QString());
+
+ private:
+ enum class Action
+ {
+ SHOWING,
+ HIDING,
+ NONE
+ };
+
+ void init();
+ void interruptAction();
+ void resetBackground();
+ void widgetResized();
+
+ Action actionInProgres = Action::NONE;
+ QVariantAnimation* animation = nullptr;
+ QEasingCurve easingCurve = QEasingCurve::OutCubic;
+ int duration = 150;
+ int transparency = 128;
+ QWidget* container = nullptr;
+ QGridLayout* containerLayout = nullptr;
+ QPushButton* cancelButton = nullptr;
+ QProgressBar* busyBar = nullptr;
+
+ signals:
+ void cancelClicked();
+
+ private slots:
+ void animationUpdate(const QVariant& value);
+ void animationFinished();
+
+ public slots:
+ void show();
+ void hide();
+};
+
+#endif // WIDGETCOVER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp
new file mode 100644
index 0000000..c6823e4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp
@@ -0,0 +1,412 @@
+#include "widgetstateindicator.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include "uiutils.h"
+#include "mdichild.h"
+#include <QLabel>
+#include <QCheckBox>
+#include <QGroupBox>
+#include <QEvent>
+#include <QHBoxLayout>
+#include <QGraphicsDropShadowEffect>
+#include <QSequentialAnimationGroup>
+#include <QPropertyAnimation>
+#include <QDebug>
+#include <QScrollArea>
+
+QHash<QWidget*,WidgetStateIndicator*> WidgetStateIndicator::instances;
+
+WidgetStateIndicator::WidgetStateIndicator(QWidget *widget) :
+ QObject(widget), widget(widget)
+{
+ widget->installEventFilter(this);
+ detectWindowParent();
+ initPositionMode();
+ initEffects();
+ initLabel();
+ updateMode();
+ updatePosition();
+ finalInit();
+}
+
+WidgetStateIndicator::~WidgetStateIndicator()
+{
+ instances.remove(widget);
+ widget->removeEventFilter(this);
+ windowParent->removeEventFilter(this);
+}
+
+void WidgetStateIndicator::initLabel()
+{
+ label = new QLabel();
+ label->setMargin(0);
+ label->installEventFilter(this);
+ label->setGraphicsEffect(highlightingEffect);
+
+ labelParent = new QWidget(windowParent);
+ labelParent->setLayout(new QHBoxLayout());
+ labelParent->layout()->setMargin(0);
+ labelParent->layout()->addWidget(label);
+ labelParent->setGraphicsEffect(glowEffect);
+}
+
+void WidgetStateIndicator::initEffects()
+{
+ initGlowEffects();
+ initHighlightingEffects();
+ initAnimations();
+}
+
+void WidgetStateIndicator::initGlowEffects()
+{
+ glowEffect = new QGraphicsDropShadowEffect();
+ glowEffect->setBlurRadius(10.0);
+ glowEffect->setOffset(0.0);
+ glowEffect->setEnabled(true);
+}
+
+void WidgetStateIndicator::initHighlightingEffects()
+{
+ highlightingEffect = new QGraphicsColorizeEffect();
+ highlightingEffect->setColor(Qt::white);
+ highlightingEffect->setStrength(0.3);
+ highlightingEffect->setEnabled(false);
+}
+
+void WidgetStateIndicator::initAnimations()
+{
+ animation = new QSequentialAnimationGroup(this);
+ animation->setLoopCount(-1);
+
+ // Animation of glow efect
+ QPropertyAnimation* varAnim = new QPropertyAnimation(glowEffect, "blurRadius");
+ varAnim->setStartValue(3.0);
+ varAnim->setEndValue(14.0);
+ varAnim->setEasingCurve(QEasingCurve::InOutCubic);
+ varAnim->setDuration(300);
+ animation->addAnimation(varAnim);
+
+ varAnim = new QPropertyAnimation(glowEffect, "blurRadius");
+ varAnim->setStartValue(14.0);
+ varAnim->setEndValue(3.0);
+ varAnim->setEasingCurve(QEasingCurve::InOutCubic);
+ varAnim->setDuration(300);
+ animation->addAnimation(varAnim);
+}
+
+void WidgetStateIndicator::initPositionMode()
+{
+ if (dynamic_cast<QGroupBox*>(widget))
+ positionMode = PositionMode::GROUP_BOX;
+ else if (dynamic_cast<QLabel*>(widget))
+ positionMode = PositionMode::LABEL;
+ else if (dynamic_cast<QCheckBox*>(widget))
+ positionMode = PositionMode::CHECK_BOX;
+}
+
+void WidgetStateIndicator::finalInit()
+{
+ label->setFixedSize(label->pixmap()->size());
+ labelParent->setFixedSize(label->pixmap()->size());
+ widgetVisible = widget->isVisible();
+ labelParent->setVisible(false);
+}
+
+void WidgetStateIndicator::setMessage(const QString& msg)
+{
+ static const QString paraTpl = QStringLiteral("<p>%1</p>");
+ if (msg.startsWith("<p>") && msg.endsWith("</p>"))
+ message = msg;
+ else
+ message = paraTpl.arg(msg);
+
+ label->setToolTip(message);
+ if (!msg.isNull())
+ label->setCursor(Qt::WhatsThisCursor);
+ else
+ label->unsetCursor();
+}
+
+void WidgetStateIndicator::clearMessage()
+{
+ message = QString::null;
+ label->setToolTip(message);
+ label->unsetCursor();
+}
+
+void WidgetStateIndicator::detectWindowParent()
+{
+ if (windowParent)
+ windowParent->removeEventFilter(this);
+
+ windowParent = findParentWindow(widget);
+ windowParent->installEventFilter(this);
+ if (labelParent)
+ labelParent->setParent(windowParent);
+}
+
+void WidgetStateIndicator::setMode(WidgetStateIndicator::Mode mode)
+{
+ this->mode = mode;
+ updateMode();
+}
+
+void WidgetStateIndicator::show(const QString& msg, bool animated)
+{
+ visibilityRequested = true;
+ setMessage(msg);
+ if (animated && animation->state() != QAbstractAnimation::Running)
+ animation->start();
+
+ updateVisibility();
+}
+
+void WidgetStateIndicator::hide()
+{
+ visibilityRequested = false;
+ clearMessage();
+ if (animation->state() == QAbstractAnimation::Running)
+ animation->stop();
+
+ updateVisibility();
+}
+
+void WidgetStateIndicator::setVisible(bool visible, const QString& msg)
+{
+ if (visible)
+ show(msg);
+ else
+ hide();
+}
+
+void WidgetStateIndicator::release()
+{
+ setVisible(false);
+ instances.remove(widget);
+ deleteLater();
+}
+
+void WidgetStateIndicator::info(const QString& msg, bool animated)
+{
+ setMode(Mode::INFO);
+ show(msg, animated);
+}
+
+void WidgetStateIndicator::warn(const QString& msg, bool animated)
+{
+ setMode(Mode::WARNING);
+ show(msg, animated);
+}
+
+void WidgetStateIndicator::error(const QString& msg, bool animated)
+{
+ setMode(Mode::ERROR);
+ show(msg, animated);
+}
+
+void WidgetStateIndicator::hint(const QString& msg, bool animated)
+{
+ setMode(Mode::HINT);
+ show(msg, animated);
+}
+
+bool WidgetStateIndicator::exists(QWidget* widget)
+{
+ return instances.contains(widget);
+}
+
+WidgetStateIndicator* WidgetStateIndicator::getInstance(QWidget* widget)
+{
+ if (!instances.contains(widget))
+ instances[widget] = new WidgetStateIndicator(widget);
+
+ return instances[widget];
+}
+
+bool WidgetStateIndicator::eventFilter(QObject* obj, QEvent* ev)
+{
+ if (obj == widget)
+ {
+ switch (ev->type())
+ {
+ case QEvent::Move:
+ case QEvent::Resize:
+ case QEvent::Scroll:
+ updatePosition();
+ break;
+ case QEvent::Show:
+ widgetVisible = true;
+ updateVisibility();
+ break;
+ case QEvent::Hide:
+ widgetVisible = false;
+ updateVisibility();
+ break;
+ case QEvent::EnabledChange:
+ updateVisibility();
+ break;
+ default:
+ break;
+ }
+ }
+ else if (obj == windowParent)
+ {
+ switch (ev->type())
+ {
+ case QEvent::ParentChange:
+ detectWindowParent();
+ break;
+ default:
+ break;
+ }
+ }
+ else if (obj == label)
+ {
+ if (ev->type() == QEvent::Enter)
+ highlightingEffect->setEnabled(true);
+ else if (ev->type() == QEvent::Leave)
+ highlightingEffect->setEnabled(false);
+ }
+
+ return false;
+}
+
+void WidgetStateIndicator::updateMode()
+{
+ switch (mode)
+ {
+ case Mode::ERROR:
+ label->setPixmap(ICONS.INDICATOR_ERROR);
+ glowEffect->setColor(Qt::red);
+ break;
+ case Mode::WARNING:
+ label->setPixmap(ICONS.INDICATOR_WARN);
+ glowEffect->setColor(Qt::darkYellow);
+ break;
+ case Mode::INFO:
+ label->setPixmap(ICONS.INDICATOR_INFO);
+ glowEffect->setColor(Qt::blue);
+ break;
+ case Mode::HINT:
+ label->setPixmap(ICONS.INDICATOR_HINT);
+ glowEffect->setColor(Qt::darkCyan);
+ break;
+ }
+}
+
+void WidgetStateIndicator::updatePosition()
+{
+ switch (positionMode)
+ {
+ case PositionMode::DEFAULT:
+ updatePositionDefault();
+ break;
+ case PositionMode::GROUP_BOX:
+ updatePositionGroupBox();
+ break;
+ case PositionMode::LABEL:
+ updatePositionLabel();
+ break;
+ case PositionMode::CHECK_BOX:
+ updatePositionCheckBox();
+ break;
+ }
+}
+
+void WidgetStateIndicator::updatePositionDefault()
+{
+ QPoint xy = widget->mapTo(windowParent, QPoint(0,0));
+ labelParent->move(xy + QPoint(-4, -4));
+}
+
+void WidgetStateIndicator::updatePositionGroupBox()
+{
+ QPoint xy = widget->mapTo(windowParent, QPoint(0,0));
+
+ QGroupBox* gb = dynamic_cast<QGroupBox*>(widget);
+
+ QFont font = gb->font();
+ QFontMetrics fm(font);
+ QString txt = gb->title();
+ QPoint diff(fm.width(txt), 2);
+
+ labelParent->move(xy + diff);
+}
+
+void WidgetStateIndicator::updatePositionLabel()
+{
+ updatePositionCheckBox(); // currently they're equal
+}
+
+void WidgetStateIndicator::updatePositionCheckBox()
+{
+ QPoint xy = widget->mapTo(windowParent, QPoint(0,0));
+ labelParent->move(xy + QPoint(-6, -2));
+}
+
+void WidgetStateIndicator::updateVisibility()
+{
+ if (shouldHide())
+ {
+ labelParent->setVisible(false);
+ }
+ else if (shouldShow())
+ {
+ updatePosition();
+ labelParent->setVisible(true);
+ }
+}
+
+bool WidgetStateIndicator::shouldHide()
+{
+ if (!labelParent->isVisible())
+ return false;
+
+ if (!widgetVisible)
+ return true;
+
+ if (!visibilityRequested)
+ return true;
+
+ if (!widget->isEnabled())
+ return true;
+
+ return false;
+}
+
+bool WidgetStateIndicator::shouldShow()
+{
+ if (labelParent->isVisible())
+ return false;
+
+ if (!widget->isEnabled())
+ return false;
+
+ if (!widgetVisible)
+ return false;
+
+ if (!visibilityRequested)
+ return false;
+
+ return true;
+}
+WidgetStateIndicator::PositionMode WidgetStateIndicator::getPositionMode() const
+{
+ return positionMode;
+}
+
+void WidgetStateIndicator::setPositionMode(const PositionMode& value)
+{
+ positionMode = value;
+}
+
+QWidget* WidgetStateIndicator::findParentWindow(QWidget* w)
+{
+ while (w && !w->windowFlags().testFlag(Qt::Window) && !dynamic_cast<QScrollArea*>(w) && !dynamic_cast<MdiChild*>(w))
+ w = w->parentWidget();
+
+ if (dynamic_cast<QScrollArea*>(w))
+ return dynamic_cast<QScrollArea*>(w)->widget();
+
+ return w;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h
new file mode 100644
index 0000000..eee49d5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h
@@ -0,0 +1,99 @@
+#ifndef WIDGETSTATEINDICATOR_H
+#define WIDGETSTATEINDICATOR_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QObject>
+
+class QLabel;
+class QGraphicsDropShadowEffect;
+class QGraphicsColorizeEffect;
+class QSequentialAnimationGroup;
+
+class GUI_API_EXPORT WidgetStateIndicator : public QObject
+{
+ Q_OBJECT
+ public:
+ enum class Mode
+ {
+ INFO,
+ ERROR,
+ WARNING,
+ HINT
+ };
+
+ enum class PositionMode
+ {
+ DEFAULT,
+ GROUP_BOX,
+ LABEL,
+ CHECK_BOX,
+ };
+
+ ~WidgetStateIndicator();
+
+ void setMode(Mode mode);
+ void show(const QString& msg = QString(), bool animated = true);
+ void hide();
+ void setVisible(bool visible, const QString& msg = QString());
+ void release();
+ void info(const QString& msg, bool animated = true);
+ void warn(const QString& msg, bool animated = true);
+ void error(const QString& msg, bool animated = true);
+ void hint(const QString& msg, bool animated = true);
+
+ static bool exists(QWidget* widget);
+ static WidgetStateIndicator* getInstance(QWidget* widget);
+
+ PositionMode getPositionMode() const;
+ void setPositionMode(const PositionMode& value);
+
+ protected:
+ bool eventFilter(QObject *obj, QEvent *ev);
+
+ private:
+ explicit WidgetStateIndicator(QWidget *widget);
+
+ void initLabel();
+ void initEffects();
+ void initGlowEffects();
+ void initHighlightingEffects();
+ void initAnimations();
+ void initPositionMode();
+ void finalInit();
+ void setMessage(const QString& msg);
+ void clearMessage();
+ void detectWindowParent();
+ QWidget* findParentWindow(QWidget* w);
+ bool shouldHide();
+ bool shouldShow();
+
+ QWidget* labelParent = nullptr;
+ QLabel* label = nullptr;
+ Mode mode = Mode::ERROR;
+ QWidget* widget = nullptr;
+ QString message;
+ QGraphicsColorizeEffect* highlightingEffect = nullptr;
+ QGraphicsDropShadowEffect* glowEffect = nullptr;
+ QSequentialAnimationGroup* animation = nullptr;
+ bool widgetVisible = false;
+ bool visibilityRequested = false;
+ QWidget* windowParent = nullptr;
+ PositionMode positionMode = PositionMode::DEFAULT;
+
+ static QHash<QWidget*,WidgetStateIndicator*> instances;
+
+ private slots:
+ void updateMode();
+ void updatePosition();
+ void updatePositionDefault();
+ void updatePositionGroupBox();
+ void updatePositionLabel();
+ void updatePositionCheckBox();
+ void updateVisibility();
+
+};
+
+#define INDICATOR(w) WidgetStateIndicator::getInstance(w)
+#define EXISTS_INDICATOR(w) WidgetStateIndicator::exists(w)
+
+#endif // WIDGETSTATEINDICATOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp
new file mode 100644
index 0000000..bda3040
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp
@@ -0,0 +1,133 @@
+#include "completeritemdelegate.h"
+#include "completermodel.h"
+#include <QPainter>
+#include <QIcon>
+#include <QApplication>
+#include <QVariant>
+#include <QDebug>
+
+/*
+ * Some of the code in this file comes from Qt5.1, from qcommonstyle.cpp.
+ * It uses similar (but different) routines as Qt does for drawing CE_ItemViewItem.
+ */
+
+CompleterItemDelegate::CompleterItemDelegate(QObject *parent) :
+ QStyledItemDelegate(parent)
+{
+}
+
+void CompleterItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
+
+ paintIcon(painter, opt, index);
+ paintText(painter, opt, index);
+}
+
+QSize CompleterItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ QSize size = QStyledItemDelegate::sizeHint(option, index);
+ if (size.height() < 18)
+ size.setHeight(18); // at least 1 pixel larger than icons
+
+ return size;
+}
+
+void CompleterItemDelegate::paintIcon(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
+ QSize iconSize = icon.availableSizes()[0];
+
+ QIcon::Mode mode = QIcon::Normal;
+ if (!(option.state & QStyle::State_Enabled))
+ mode = QIcon::Disabled;
+ else if (option.state & QStyle::State_Selected)
+ mode = QIcon::Selected;
+
+ QRect iconRect = option.rect;
+ iconRect.setSize(iconSize + QSize(spacer*2, spacer*2));
+ iconRect.setTopLeft(iconRect.topLeft() + QPoint(spacer, spacer));
+
+ QIcon::State state = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
+ icon.paint(painter, iconRect, option.decorationAlignment, mode, state);
+}
+
+void CompleterItemDelegate::paintText(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ painter->save();
+
+ // Colors
+ QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
+ if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+
+ QColor prefixColor = option.palette.color(cg, QPalette::Dark);
+ QColor valueColor = option.palette.color(cg, QPalette::Text);
+ QColor labelColor = option.palette.color(cg, QPalette::Link);
+ if (option.state & QStyle::State_Selected)
+ {
+#ifdef Q_OS_WIN32
+ prefixColor = option.palette.color(cg, QPalette::Text);
+ valueColor = option.palette.color(cg, QPalette::Text);
+ labelColor = option.palette.color(cg, QPalette::Text);
+#else
+ prefixColor = option.palette.color(cg, QPalette::HighlightedText);
+ valueColor = option.palette.color(cg, QPalette::HighlightedText);
+ labelColor = option.palette.color(cg, QPalette::HighlightedText);
+#endif
+ }
+
+ // Using ascent() to measure usual height of the font, excluding anything below baseline.
+ int x = option.rect.x() + 20;
+ int y = option.rect.y() + option.rect.height() / 2 + option.fontMetrics.ascent() / 2 - spacer;
+
+ painter->setFont(option.font);
+
+ // Getting all data to be painted
+ QString prefixValue = index.data(CompleterModel::PREFIX).toString();
+ QString value = index.data(CompleterModel::VALUE).toString();
+ QString label = index.data(CompleterModel::LABEL).toString();
+
+ // Drawing prefix, value and label
+ painter->setPen(prefixColor);
+ paintPrefix(painter, option.fontMetrics, x, y, prefixValue);
+
+ painter->setPen(valueColor);
+ paintValue(painter, option.fontMetrics, x, y, value);
+
+ painter->setPen(labelColor);
+ paintLabel(painter, x, y, label, value.isEmpty());
+
+ painter->restore();
+}
+
+void CompleterItemDelegate::paintPrefix(QPainter* painter, const QFontMetrics& metrics, int& x, int y, const QString& text) const
+{
+ if (text.isNull())
+ return;
+
+ QString value = text + ".";
+ painter->drawText(QPoint(x, y), value);
+ x += metrics.width(value);
+}
+
+void CompleterItemDelegate::paintValue(QPainter* painter, const QFontMetrics& metrics, int& x, int y, const QString& text) const
+{
+ painter->drawText(QPoint(x, y), text);
+ x += metrics.width(text);
+}
+
+void CompleterItemDelegate::paintLabel(QPainter* painter, int& x, int y, const QString& text, bool emptyValue) const
+{
+ if (text.isNull())
+ return;
+
+ if (!emptyValue) //if the value was empty, there's no reason to move te label right
+ x += 10;
+
+ QString label = "(" + text + ")";
+ painter->drawText(QPoint(x, y), label);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h
new file mode 100644
index 0000000..5f7aa7c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h
@@ -0,0 +1,31 @@
+#ifndef COMPLETERITEMDELEGATE_H
+#define COMPLETERITEMDELEGATE_H
+
+#include "guiSQLiteStudio_global.h"
+#include "expectedtoken.h"
+#include <QStyledItemDelegate>
+
+class QIcon;
+
+class GUI_API_EXPORT CompleterItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+ public:
+ explicit CompleterItemDelegate(QObject *parent = 0);
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
+ private:
+ QIcon* getIcon(ExpectedToken::Type type) const;
+ void paintBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paintIcon(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paintText(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paintPrefix(QPainter* painter, const QFontMetrics& metrics, int& x, int y, const QString& text) const;
+ void paintValue(QPainter* painter, const QFontMetrics& metrics, int& x, int y, const QString& text) const;
+ void paintLabel(QPainter* painter, int& x, int y, const QString& text, bool emptyValue) const;
+
+ const static int spacer = 1;
+};
+
+#endif // COMPLETERITEMDELEGATE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp b/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp
new file mode 100644
index 0000000..aeb481e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp
@@ -0,0 +1,170 @@
+#include "completermodel.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include "completerview.h"
+#include <QIcon>
+#include <QVariant>
+
+CompleterModel::CompleterModel(QObject *parent) :
+ QAbstractItemModel(parent)
+{
+}
+
+
+QModelIndex CompleterModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return QModelIndex(); // no childrens
+
+ return createIndex(row, column);
+}
+
+QModelIndex CompleterModel::parent(const QModelIndex& child) const
+{
+ UNUSED(child);
+ return QModelIndex();
+}
+
+int CompleterModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return tokens.size();
+}
+
+int CompleterModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 1;
+}
+
+QVariant CompleterModel::data(const QModelIndex& index, int role) const
+{
+ int row = index.row();
+ if (row < 0 || row >= tokens.size())
+ return QVariant();
+
+ ExpectedTokenPtr token = tokens[row];
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ case VALUE:
+ return token->value;
+ case CONTEXT:
+ return token->contextInfo;
+ case LABEL:
+ return token->label;
+ case PREFIX:
+ return token->prefix;
+ case TYPE:
+ return (int)token->type;
+ case Qt::DecorationRole:
+ return getIcon(token->type);
+ }
+
+ return QVariant();
+}
+
+void CompleterModel::setCompleterView(CompleterView* view)
+{
+ completerView = view;
+}
+
+void CompleterModel::setData(const QList<ExpectedTokenPtr>& data)
+{
+ clear();
+ beginInsertRows(QModelIndex(), 0, data.size()-1);
+ tokens = data;
+ endInsertRows();
+}
+
+void CompleterModel::setFilter(const QString& filter)
+{
+ this->filter = filter;
+ applyFilter();
+}
+
+QString CompleterModel::getFilter() const
+{
+ return filter;
+}
+
+void CompleterModel::applyFilter()
+{
+ bool empty = filter.isEmpty();
+ QModelIndex idx;
+ QString value;
+ QString prefix;
+ bool matched = empty;
+ for (int i = 0; i < rowCount(QModelIndex()); i++)
+ {
+ if (!empty)
+ {
+ idx = index(i, 0, QModelIndex());
+ value = idx.data(VALUE).toString();
+ prefix = idx.data(PREFIX).toString();
+ if (!prefix.isEmpty())
+ value.prepend(prefix+".");
+
+ matched = value.startsWith(filter, Qt::CaseInsensitive);
+ }
+
+ completerView->setRowHidden(i, !matched);
+ }
+}
+
+
+void CompleterModel::clear()
+{
+ beginResetModel();
+ tokens.clear();
+ endResetModel();
+}
+
+ExpectedTokenPtr CompleterModel::getToken(int index) const
+{
+ if (index < 0 || index >= tokens.size())
+ return ExpectedTokenPtr();
+
+ return tokens[index];
+}
+
+QIcon CompleterModel::getIcon(ExpectedToken::Type type) const
+{
+ switch (type)
+ {
+ case ExpectedToken::COLUMN:
+ return ICONS.COLUMN;
+ case ExpectedToken::TABLE:
+ return ICONS.TABLE;
+ case ExpectedToken::INDEX:
+ return ICONS.INDEX;
+ case ExpectedToken::TRIGGER:
+ return ICONS.TRIGGER;
+ case ExpectedToken::VIEW:
+ return ICONS.VIEW;
+ case ExpectedToken::DATABASE:
+ return ICONS.DATABASE;
+ case ExpectedToken::OTHER:
+ return ICONS.COMPLETER_OTHER;
+ case ExpectedToken::KEYWORD:
+ return ICONS.KEYWORD;
+ case ExpectedToken::FUNCTION:
+ return ICONS.FUNCTION;
+ case ExpectedToken::OPERATOR:
+ return ICONS.COMPLETER_OPERATOR;
+ case ExpectedToken::STRING:
+ return ICONS.COMPLETER_STRING;
+ case ExpectedToken::NUMBER:
+ return ICONS.COMPLETER_NUMBER;
+ case ExpectedToken::BLOB:
+ return ICONS.COMPLETER_BLOB;
+ case ExpectedToken::COLLATION:
+ return ICONS.CONSTRAINT_COLLATION;
+ case ExpectedToken::PRAGMA:
+ return ICONS.COMPLETER_PRAGMA;
+ case ExpectedToken::NO_VALUE:
+ return ICONS.COMPLETER_NO_VALUE;
+ }
+
+ return ICONS.COMPLETER_NO_VALUE;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h b/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h
new file mode 100644
index 0000000..5edd46f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h
@@ -0,0 +1,50 @@
+#ifndef COMPLETERMODEL_H
+#define COMPLETERMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include "expectedtoken.h"
+#include <QAbstractItemModel>
+#include <QModelIndex>
+
+class CompleterView;
+class Icon;
+
+class GUI_API_EXPORT CompleterModel : public QAbstractItemModel
+{
+ public:
+ using QAbstractItemModel::setData;
+
+ enum UserRole
+ {
+ VALUE = 1000,
+ CONTEXT = 1001,
+ PREFIX = 1002,
+ LABEL = 1003,
+ TYPE = 1004
+ };
+
+ explicit CompleterModel(QObject *parent = 0);
+
+ QModelIndex index(int row, int column, const QModelIndex& parent) const;
+ QModelIndex parent(const QModelIndex& child) const;
+ int rowCount(const QModelIndex& parent) const;
+ int columnCount(const QModelIndex& parent) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ void setCompleterView(CompleterView* view);
+ void setData(const QList<ExpectedTokenPtr>& data);
+ void setFilter(const QString& filter);
+ QString getFilter() const;
+ void clear();
+ ExpectedTokenPtr getToken(int index) const;
+
+ private:
+ QIcon getIcon(ExpectedToken::Type type) const;
+ void applyFilter();
+
+ QList<ExpectedTokenPtr> tokens;
+ QString filter;
+ CompleterView* completerView = nullptr;
+};
+
+#endif // COMPLETERMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp b/SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp
new file mode 100644
index 0000000..c154b14
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp
@@ -0,0 +1,70 @@
+#include "completerview.h"
+#include "completeritemdelegate.h"
+#include <QMouseEvent>
+
+CompleterView::CompleterView(QWidget *parent) :
+ QListView(parent)
+{
+ setItemDelegate(new CompleterItemDelegate(this));
+}
+
+void CompleterView::selectFirstVisible()
+{
+ QModelIndex idx;
+ for (int i = 0; i < model()->rowCount(); i++)
+ {
+ if (isRowHidden(i))
+ continue;
+
+ idx = model()->index(i, 0, QModelIndex());
+ selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
+ break;
+ }
+}
+
+bool CompleterView::hasVisibleItem() const
+{
+ return countVisibleItem() > 0;
+}
+
+int CompleterView::countVisibleItem() const
+{
+ int cnt = 0;
+ for (int i = 0; i < model()->rowCount(); i++)
+ {
+ if (!isRowHidden(i))
+ cnt++;
+ }
+ return cnt;
+}
+
+void CompleterView::focusOutEvent(QFocusEvent* e)
+{
+ emit focusOut();
+ QListView::focusOutEvent(e);
+}
+
+void CompleterView::keyPressEvent(QKeyEvent* e)
+{
+ QString txt = e->text();
+ if (!txt.isEmpty() && txt[0].isPrint())
+ {
+ emit textTyped(txt);
+ return;
+ }
+
+ switch (e->key())
+ {
+ case Qt::Key_Backspace:
+ emit backspace();
+ return;
+ case Qt::Key_Left:
+ emit left();
+ return;
+ case Qt::Key_Right:
+ emit right();
+ return;
+ }
+
+ QListView::keyPressEvent(e);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerview.h b/SQLiteStudio3/guiSQLiteStudio/completer/completerview.h
new file mode 100644
index 0000000..ef654af
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerview.h
@@ -0,0 +1,30 @@
+#ifndef COMPLETERLIST_H
+#define COMPLETERLIST_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QListWidget>
+
+class CompleterView : public QListView
+{
+ Q_OBJECT
+
+ public:
+ explicit CompleterView(QWidget *parent = 0);
+
+ void selectFirstVisible();
+ bool hasVisibleItem() const;
+ int countVisibleItem() const;
+
+ protected:
+ void focusOutEvent(QFocusEvent* e);
+ void keyPressEvent(QKeyEvent* e);
+
+ signals:
+ void focusOut();
+ void textTyped(const QString& text);
+ void backspace();
+ void left();
+ void right();
+};
+
+#endif // COMPLETERLIST_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp
new file mode 100644
index 0000000..34ae30b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp
@@ -0,0 +1,229 @@
+#include "completerwindow.h"
+#include "ui_completerwindow.h"
+#include "completermodel.h"
+#include "common/unused.h"
+#include "sqleditor.h"
+#include "common/utils_sql.h"
+#include <QKeyEvent>
+#include <QListView>
+#include <QDebug>
+
+CompleterWindow::CompleterWindow(SqlEditor *parent) :
+ QDialog(parent, Qt::FramelessWindowHint),
+ ui(new Ui::CompleterWindow),
+ sqlEditor(parent)
+{
+ ui->setupUi(this);
+ init();
+}
+
+CompleterWindow::~CompleterWindow()
+{
+ delete ui;
+}
+
+void CompleterWindow::init()
+{
+ model = new CompleterModel(this);
+ ui->list->setModel(model);
+ model->setCompleterView(ui->list);
+
+ setFocusProxy(ui->list);
+ connect(ui->list, SIGNAL(focusOut()), this, SLOT(focusOut()));
+ connect(ui->list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClicked(QModelIndex)));
+ connect(ui->list->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(currentRowChanged(QModelIndex,QModelIndex)));
+ connect(ui->list, SIGNAL(textTyped(QString)), this, SIGNAL(textTyped(QString)));
+ connect(ui->list, SIGNAL(backspace()), this, SIGNAL(backspacePressed()));
+ connect(ui->list, SIGNAL(left()), this, SIGNAL(leftPressed()));
+ connect(ui->list, SIGNAL(right()), this, SIGNAL(rightPressed()));
+ reset();
+}
+
+void CompleterWindow::reset()
+{
+ model->clear();
+ ui->status->showMessage(QString::null);
+}
+
+void CompleterWindow::setData(const CompletionHelper::Results& completionResults)
+{
+ ui->status->showMessage(QString::null);
+ model->setData(completionResults.expectedTokens);
+ filter = completionResults.partialToken;
+ wrappedFilter = completionResults.wrappedToken;
+ updateFilter();
+}
+
+void CompleterWindow::setDb(Db* db)
+{
+ this->db = db;
+}
+
+void CompleterWindow::updateFilter()
+{
+ model->setFilter(filter);
+ ui->list->selectFirstVisible();
+
+ if (!ui->list->hasVisibleItem())
+ reject();
+}
+
+void CompleterWindow::shringFilterBy(int chars)
+{
+ if (filter.size() < chars)
+ {
+ if (wrappedFilter && chars == 1)
+ {
+ wrappedFilter = false;
+ updateFilter();
+ return;
+ }
+
+ reject();
+ return;
+ }
+
+ filter.truncate(filter.length() - chars);
+ updateFilter();
+}
+
+void CompleterWindow::extendFilterBy(const QString& text)
+{
+ if (filter.isEmpty() && text.size() == 1 && isWrapperChar(text[0], db->getDialect()))
+ {
+ wrappedFilter = true;
+ updateFilter();
+ return;
+ }
+
+ filter.append(text);
+ updateFilter();
+}
+
+bool CompleterWindow::immediateResolution()
+{
+ if (ui->list->countVisibleItem() == 1)
+ {
+ accept();
+ return true;
+ }
+ return false;
+}
+
+ExpectedTokenPtr CompleterWindow::getSelected()
+{
+ QModelIndex current = ui->list->currentIndex();
+ if (!current.isValid())
+ return ExpectedTokenPtr();
+
+ return model->getToken(current.row());
+}
+
+int CompleterWindow::getNumberOfCharsToRemove()
+{
+ return filter.size() + (wrappedFilter ? 1 : 0);
+}
+
+void CompleterWindow::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void CompleterWindow::keyPressEvent(QKeyEvent* e)
+{
+ if (e->key() == Qt::Key_Return)
+ {
+ accept();
+ return;
+ }
+
+ QDialog::keyPressEvent(e);
+}
+
+QString CompleterWindow::getStatusMsg(const QModelIndex& index)
+{
+ ExpectedToken::Type type = (ExpectedToken::Type)index.data(CompleterModel::TYPE).toInt();
+ QString value = index.data(CompleterModel::VALUE).toString();
+ QString label = index.data(CompleterModel::LABEL).toString();
+
+ switch (type)
+ {
+ case ExpectedToken::COLUMN:
+ return tr("Column: %1", "completer statusbar").arg(value);
+ case ExpectedToken::TABLE:
+ return tr("Table: %1", "completer statusbar").arg(value);
+ case ExpectedToken::INDEX:
+ return tr("Index: %1", "completer statusbar").arg(value);
+ case ExpectedToken::TRIGGER:
+ return tr("Trigger: %1", "completer statusbar").arg(value);
+ case ExpectedToken::VIEW:
+ return tr("View: %1", "completer statusbar").arg(value);
+ case ExpectedToken::DATABASE:
+ return tr("Database: %1", "completer statusbar").arg(value);
+ case ExpectedToken::OTHER:
+ {
+ if (!label.isEmpty())
+ return label;
+
+ if (!value.isEmpty())
+ return value;
+
+ return "";
+ }
+ case ExpectedToken::KEYWORD:
+ return tr("Keyword: %1", "completer statusbar").arg(value);
+ case ExpectedToken::FUNCTION:
+ return tr("Function: %1", "completer statusbar").arg(value);
+ case ExpectedToken::OPERATOR:
+ return tr("Operator: %1", "completer statusbar").arg(value);
+ case ExpectedToken::STRING:
+ return tr("String", "completer statusbar");
+ case ExpectedToken::NUMBER:
+ return tr("Number", "completer statusbar").arg(value);
+ case ExpectedToken::BLOB:
+ return tr("Binary data", "completer statusbar").arg(value);
+ case ExpectedToken::COLLATION:
+ return tr("Collation: %1", "completer statusbar").arg(value);
+ case ExpectedToken::PRAGMA:
+ return tr("Pragma function: %1", "completer statusbar").arg(value);
+ case ExpectedToken::NO_VALUE:
+ {
+ if (!label.isEmpty())
+ return label;
+
+ if (!value.isEmpty())
+ return value;
+
+ return "";
+ }
+ }
+ return "";
+}
+
+void CompleterWindow::focusOut()
+{
+ QWidget* focused = QApplication::focusWidget();
+ if (!focused || focused == this || isAncestorOf(focused))
+ return;
+
+ reject();
+}
+
+void CompleterWindow::doubleClicked(const QModelIndex& index)
+{
+ UNUSED(index);
+ accept();
+}
+
+void CompleterWindow::currentRowChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ UNUSED(previous);
+ ui->status->showMessage(getStatusMsg(current));
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h
new file mode 100644
index 0000000..7c933c8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h
@@ -0,0 +1,64 @@
+#ifndef COMPLETERWINDOW_H
+#define COMPLETERWINDOW_H
+
+#include "expectedtoken.h"
+#include "completionhelper.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QModelIndex>
+
+namespace Ui {
+ class CompleterWindow;
+}
+
+class CompleterModel;
+class QSizeGrip;
+class SqlEditor;
+
+class GUI_API_EXPORT CompleterWindow : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit CompleterWindow(SqlEditor* parent = 0);
+ ~CompleterWindow();
+
+ void reset();
+ void setData(const CompletionHelper::Results& completionResults);
+ void setDb(Db* db);
+ ExpectedTokenPtr getSelected();
+ int getNumberOfCharsToRemove();
+ void shringFilterBy(int chars);
+ void extendFilterBy(const QString& text);
+ bool immediateResolution();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void keyPressEvent(QKeyEvent* e);
+
+ private:
+ void updateCurrent();
+ QString getStatusMsg(const QModelIndex& index);
+ void updateFilter();
+ void init();
+
+ Ui::CompleterWindow *ui = nullptr;
+ CompleterModel* model = nullptr;
+ SqlEditor* sqlEditor = nullptr;
+ QString filter;
+ Db* db = nullptr;
+ bool wrappedFilter = false;
+
+ private slots:
+ void focusOut();
+ void doubleClicked(const QModelIndex& index);
+ void currentRowChanged(const QModelIndex& current, const QModelIndex& previous);
+
+ signals:
+ void textTyped(const QString& text);
+ void backspacePressed();
+ void leftPressed();
+ void rightPressed();
+};
+
+#endif // COMPLETERWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui
new file mode 100644
index 0000000..d5ce987
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CompleterWindow</class>
+ <widget class="QDialog" name="CompleterWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>309</width>
+ <height>184</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string notr="true">SQLiteStudio completer</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="CompleterView" name="list">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QStatusBar" name="status"/>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>CompleterView</class>
+ <extends>QListView</extends>
+ <header>completer/completerview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp b/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp
new file mode 100644
index 0000000..255b5cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp
@@ -0,0 +1,652 @@
+#include "configmapper.h"
+#include "config_builder.h"
+#include "services/config.h"
+#include "services/pluginmanager.h"
+#include "customconfigwidgetplugin.h"
+#include "common/colorbutton.h"
+#include "common/fontedit.h"
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QComboBox>
+#include <QPlainTextEdit>
+#include <QListWidget>
+#include <QTableWidget>
+#include <QCheckBox>
+#include <QGroupBox>
+#include <QDebug>
+#include <QStringListModel>
+#include <QFontComboBox>
+#include <QKeySequenceEdit>
+#include <common/configcombobox.h>
+#include <common/configradiobutton.h>
+#include <common/fileedit.h>
+
+#define APPLY_CFG(Widget, Value, WidgetType, Method, DataType) \
+ APPLY_CFG_VARIANT(Widget, Value.value<DataType>(), WidgetType, Method)
+
+#define APPLY_CFG_COND(Widget, Value, WidgetType, Method, DataType, ExtraConditionMethod) \
+ if (qobject_cast<WidgetType*>(Widget) && qobject_cast<WidgetType*>(Widget)->ExtraConditionMethod())\
+ {\
+ qobject_cast<WidgetType*>(Widget)->Method(Value.value<DataType>());\
+ return;\
+ }
+
+#define APPLY_CFG_VARIANT(Widget, Value, WidgetType, Method) \
+ if (qobject_cast<WidgetType*>(Widget))\
+ {\
+ qobject_cast<WidgetType*>(Widget)->Method(Value);\
+ return;\
+ }
+
+#define APPLY_NOTIFIER(Widget, Key, WidgetType, Notifier) \
+ if (qobject_cast<WidgetType*>(Widget))\
+ {\
+ connect(Widget, Notifier, this, SLOT(handleModified()));\
+ if (Widget->property(CFG_NOTIFY_PROPERTY).isValid() && Widget->property(CFG_NOTIFY_PROPERTY).toBool())\
+ connect(Widget, Notifier, this, SLOT(uiConfigEntryChanged()));\
+ \
+ if (Widget->property(CFG_PREVIEW_PROPERTY).isValid() && Widget->property(CFG_PREVIEW_PROPERTY).toBool())\
+ connect(Key, SIGNAL(changed(QVariant)), this, SLOT(notifiableConfigKeyChanged()));\
+ \
+ return;\
+ }
+
+#define APPLY_NOTIFIER_COND(Widget, Key, WidgetType, Notifier, ExtraConditionMethod) \
+ if (qobject_cast<WidgetType*>(Widget) && qobject_cast<WidgetType*>(Widget)->ExtraConditionMethod())\
+ {\
+ connect(Widget, Notifier, this, SLOT(handleModified()));\
+ if (Widget->property(CFG_NOTIFY_PROPERTY).isValid() && Widget->property(CFG_NOTIFY_PROPERTY).toBool())\
+ connect(Widget, Notifier, this, SLOT(uiConfigEntryChanged()));\
+ \
+ if (Widget->property(CFG_PREVIEW_PROPERTY).isValid() && Widget->property(CFG_PREVIEW_PROPERTY).toBool())\
+ connect(Key, SIGNAL(changed(QVariant)), this, SLOT(notifiableConfigKeyChanged()));\
+ \
+ return;\
+ }
+
+#define GET_CFG_VALUE(Widget, Key, WidgetType, Method) \
+ if (qobject_cast<WidgetType*>(Widget))\
+ return qobject_cast<WidgetType*>(Widget)->Method();
+
+#define GET_CFG_VALUE_COND(Widget, Key, WidgetType, Method, ExtraConditionMethod) \
+ if (qobject_cast<WidgetType*>(Widget) && qobject_cast<WidgetType*>(Widget)->ExtraConditionMethod())\
+ return qobject_cast<WidgetType*>(Widget)->Method();
+
+ConfigMapper::ConfigMapper(CfgMain* cfgMain)
+{
+ this->cfgMainList << cfgMain;
+}
+
+ConfigMapper::ConfigMapper(const QList<CfgMain*> cfgMain) :
+ cfgMainList(cfgMain)
+{
+}
+
+
+void ConfigMapper::applyCommonConfigToWidget(QWidget *widget, const QVariant &value, CfgEntry* cfgEntry)
+{
+ APPLY_CFG(widget, value, QCheckBox, setChecked, bool);
+ APPLY_CFG(widget, value, QLineEdit, setText, QString);
+ APPLY_CFG(widget, value, QTextEdit, setPlainText, QString);
+ APPLY_CFG(widget, value, QPlainTextEdit, setPlainText, QString);
+ APPLY_CFG(widget, value, QSpinBox, setValue, int);
+ APPLY_CFG(widget, value, QFontComboBox, setCurrentFont, QFont);
+ APPLY_CFG(widget, value, FontEdit, setFont, QFont);
+ APPLY_CFG(widget, value, ColorButton, setColor, QColor);
+ APPLY_CFG(widget, value, FileEdit, setFile, QString);
+ APPLY_CFG_VARIANT(widget, QKeySequence::fromString(value.toString()), QKeySequenceEdit, setKeySequence);
+ APPLY_CFG_VARIANT(widget, value, ConfigRadioButton, alignToValue);
+ APPLY_CFG_COND(widget, value, QGroupBox, setChecked, bool, isCheckable);
+
+ // ComboBox needs special treatment, cause setting its value might not be successful (value not in valid values)
+ QComboBox* cb = dynamic_cast<QComboBox*>(widget);
+ if (cb)
+ {
+ if (cfgEntry->get().type() == QVariant::Int)
+ {
+ cb->setCurrentIndex(value.toInt());
+ if (cb->currentIndex() != value.toInt())
+ cfgEntry->set(cb->currentIndex());
+ }
+ else
+ {
+ cb->setCurrentText(value.toString());
+ if (cb->currentText() != value.toString())
+ cfgEntry->set(cb->currentText());
+ }
+ return;
+ }
+
+ qWarning() << "Unhandled config widget type (for APPLY_CFG):" << widget->metaObject()->className()
+ << "with value:" << value;
+}
+
+void ConfigMapper::connectCommonNotifierToWidget(QWidget* widget, CfgEntry* key)
+{
+ APPLY_NOTIFIER(widget, key, QCheckBox, SIGNAL(stateChanged(int)));
+ APPLY_NOTIFIER(widget, key, QLineEdit, SIGNAL(textChanged(QString)));
+ APPLY_NOTIFIER(widget, key, QTextEdit, SIGNAL(textChanged()));
+ APPLY_NOTIFIER(widget, key, QPlainTextEdit, SIGNAL(textChanged()));
+ APPLY_NOTIFIER(widget, key, QSpinBox, SIGNAL(valueChanged(QString)));
+ APPLY_NOTIFIER(widget, key, QFontComboBox, SIGNAL(currentFontChanged(QFont)));
+ APPLY_NOTIFIER(widget, key, FontEdit, SIGNAL(fontChanged(QFont)));
+ APPLY_NOTIFIER(widget, key, FileEdit, SIGNAL(fileChanged(QString)));
+ APPLY_NOTIFIER(widget, key, QKeySequenceEdit, SIGNAL(editingFinished()));
+ APPLY_NOTIFIER(widget, key, ColorButton, SIGNAL(colorChanged(QColor)));
+ APPLY_NOTIFIER(widget, key, ConfigRadioButton, SIGNAL(toggledOn(QVariant)));
+ APPLY_NOTIFIER_COND(widget, key, QGroupBox, SIGNAL(clicked(bool)), isCheckable);
+ if (key->get().type() == QVariant::Int)
+ {
+ APPLY_NOTIFIER(widget, key, QComboBox, SIGNAL(currentIndexChanged(int)));
+ }
+ else
+ {
+ APPLY_NOTIFIER(widget, key, QComboBox, SIGNAL(currentTextChanged(QString)));
+ }
+
+ qWarning() << "Unhandled config widget type (for APPLY_NOTIFIER):" << widget->metaObject()->className();
+}
+
+void ConfigMapper::saveCommonConfigFromWidget(QWidget* widget, CfgEntry* key)
+{
+ bool ok = false;
+ QVariant value = getCommonConfigValueFromWidget(widget, key, ok);
+ if (ok)
+ key->set(value);
+}
+
+QVariant ConfigMapper::getCommonConfigValueFromWidget(QWidget* widget, CfgEntry* key, bool& ok)
+{
+ ok = true;
+ GET_CFG_VALUE(widget, key, QCheckBox, isChecked);
+ GET_CFG_VALUE(widget, key, QLineEdit, text);
+ GET_CFG_VALUE(widget, key, QTextEdit, toPlainText);
+ GET_CFG_VALUE(widget, key, QPlainTextEdit, toPlainText);
+ GET_CFG_VALUE(widget, key, QSpinBox, value);
+ GET_CFG_VALUE(widget, key, QFontComboBox, currentFont);
+ GET_CFG_VALUE(widget, key, FontEdit, getFont);
+ GET_CFG_VALUE(widget, key, FileEdit, getFile);
+ GET_CFG_VALUE(widget, key, QKeySequenceEdit, keySequence().toString);
+ GET_CFG_VALUE(widget, key, ColorButton, getColor);
+ GET_CFG_VALUE_COND(widget, key, ConfigRadioButton, getAssignedValue, isChecked);
+ GET_CFG_VALUE_COND(widget, key, QGroupBox, isChecked, isCheckable);
+ if (key->get().type() == QVariant::Int)
+ {
+ GET_CFG_VALUE(widget, key, QComboBox, currentIndex);
+ }
+ else
+ {
+ GET_CFG_VALUE(widget, key, QComboBox, currentText);
+ }
+
+ qWarning() << "Unhandled config widget type (for GET_CFG_VALUE):" << widget->metaObject()->className();
+ ok = false;
+ return QVariant();
+}
+
+QVariant ConfigMapper::getCustomConfigValueFromWidget(QWidget* widget, CfgEntry* key, bool& ok)
+{
+ CustomConfigWidgetPlugin* plugin = nullptr;
+ QList<CustomConfigWidgetPlugin*> handlers;
+ handlers += internalCustomConfigWidgets;
+ handlers += PLUGINS->getLoadedPlugins<CustomConfigWidgetPlugin>();
+
+ foreach (plugin, handlers)
+ {
+ if (plugin->isConfigForWidget(key, widget))
+ return plugin->getWidgetConfigValue(widget, ok);
+ }
+
+ ok = false;
+ return QVariant();
+}
+
+QVariant ConfigMapper::getConfigValueFromWidget(QWidget* widget, CfgEntry* key)
+{
+ bool ok;
+ QVariant value = getCustomConfigValueFromWidget(widget, key, ok);
+ if (!ok)
+ value = getCommonConfigValueFromWidget(widget, key, ok);
+
+ return value;
+}
+
+void ConfigMapper::loadToWidget(QWidget *topLevelWidget)
+{
+ QHash<QString, CfgEntry *> allConfigEntries = getAllConfigEntries();
+ QList<QWidget*> allConfigWidgets = getAllConfigWidgets(topLevelWidget) + extraWidgets;
+ QHash<QString,QVariant> config;
+
+ if (isPersistant())
+ config = CFG->getAll();
+
+ updatingEntry = true;
+ foreach (QWidget* widget, allConfigWidgets)
+ applyConfigToWidget(widget, allConfigEntries, config);
+
+ updatingEntry = false;
+}
+
+void ConfigMapper::loadToWidget(CfgEntry* config, QWidget* widget)
+{
+ QVariant configValue = config->get();
+
+ updatingEntry = true;
+ if (applyCustomConfigToWidget(config, widget, configValue))
+ return;
+
+ applyCommonConfigToWidget(widget, configValue, config);
+ updatingEntry = false;
+}
+
+void ConfigMapper::saveFromWidget(QWidget *widget, bool noTransaction)
+{
+ QHash<QString, CfgEntry *> allConfigEntries = getAllConfigEntries();
+ QList<QWidget*> allConfigWidgets = getAllConfigWidgets(widget);
+
+ if (!noTransaction && isPersistant())
+ CFG->beginMassSave();
+
+ foreach (QWidget* w, allConfigWidgets)
+ saveWidget(w, allConfigEntries);
+
+ if (!noTransaction && isPersistant())
+ CFG->commitMassSave();
+}
+
+
+void ConfigMapper::applyConfigToWidget(QWidget* widget, const QHash<QString, CfgEntry *> &allConfigEntries, const QHash<QString,QVariant>& config)
+{
+ CfgEntry* cfgEntry = getConfigEntry(widget, allConfigEntries);
+ if (!cfgEntry)
+ return;
+
+ QVariant configValue;
+ if (config.contains(cfgEntry->getFullKey()))
+ {
+ configValue = config[cfgEntry->getFullKey()];
+ if (!configValue.isValid())
+ configValue = cfgEntry->getDefultValue();
+ }
+ else if (cfgEntry->isPersistable())
+ {
+ // In case this is a persistable config, we should have everything in the config hash, which is just one, initial database query.
+ // If we don't, than we don't want to call get(), because it will cause one more query to the database for it.
+ // We go with the default value.
+ configValue = cfgEntry->getDefultValue();
+ }
+ else
+ {
+ // Non persistable entries will return whatever they have, without querying database.
+ configValue = cfgEntry->get();
+ }
+
+ widgetToConfigEntry.insert(widget, cfgEntry);
+ configEntryToWidgets.insertMulti(cfgEntry, widget);
+
+ handleSpecialWidgets(widget, allConfigEntries);
+
+ if (!connectCustomNotifierToWidget(widget, cfgEntry))
+ connectCommonNotifierToWidget(widget, cfgEntry);
+
+ applyConfigToWidget(widget, cfgEntry, configValue);
+}
+
+void ConfigMapper::applyConfigToWidget(QWidget* widget, CfgEntry* cfgEntry, const QVariant& configValue)
+{
+ if (applyCustomConfigToWidget(cfgEntry, widget, configValue))
+ return;
+
+ applyCommonConfigToWidget(widget, configValue, cfgEntry);
+}
+
+void ConfigMapper::handleSpecialWidgets(QWidget* widget, const QHash<QString, CfgEntry*>& allConfigEntries)
+{
+ handleConfigComboBox(widget, allConfigEntries);
+}
+
+void ConfigMapper::handleConfigComboBox(QWidget* widget, const QHash<QString, CfgEntry*>& allConfigEntries)
+{
+ ConfigComboBox* ccb = dynamic_cast<ConfigComboBox*>(widget);
+ if (!ccb)
+ return;
+
+ CfgEntry* key = getEntryForProperty(widget, "modelName", allConfigEntries);
+ if (!key)
+ return;
+
+ QStringList list = key->get().toStringList();
+ ccb->setModel(new QStringListModel(list));
+
+ if (realTimeUpdates)
+ {
+ specialConfigEntryToWidgets.insertMulti(key, widget);
+ connect(key, SIGNAL(changed(QVariant)), this, SLOT(updateConfigComboModel(QVariant)));
+ }
+}
+
+bool ConfigMapper::applyCustomConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value)
+{
+ CustomConfigWidgetPlugin* handler = nullptr;
+ QList<CustomConfigWidgetPlugin*> handlers;
+ handlers += internalCustomConfigWidgets;
+ handlers += PLUGINS->getLoadedPlugins<CustomConfigWidgetPlugin>();
+
+ foreach (handler, handlers)
+ {
+ if (handler->isConfigForWidget(key, widget))
+ {
+ handler->applyConfigToWidget(key, widget, value);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ConfigMapper::connectCustomNotifierToWidget(QWidget* widget, CfgEntry* cfgEntry)
+{
+ CustomConfigWidgetPlugin* handler = nullptr;
+ QList<CustomConfigWidgetPlugin*> handlers;
+ handlers += internalCustomConfigWidgets;
+ handlers += PLUGINS->getLoadedPlugins<CustomConfigWidgetPlugin>();
+
+ foreach (handler, handlers)
+ {
+ if (handler->isConfigForWidget(cfgEntry, widget))
+ {
+ connect(widget, handler->getModifiedNotifier(), this, SIGNAL(modified()));
+ if (widget->property(CFG_NOTIFY_PROPERTY).isValid() && widget->property(CFG_NOTIFY_PROPERTY).toBool())
+ connect(widget, handler->getModifiedNotifier(), this, SLOT(uiConfigEntryChanged()));
+
+ if (widget->property(CFG_PREVIEW_PROPERTY).isValid() && widget->property(CFG_PREVIEW_PROPERTY).toBool())
+ connect(cfgEntry, SIGNAL(changed(QVariant)), this, SLOT(notifiableConfigKeyChanged()));
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void ConfigMapper::saveWidget(QWidget* widget, const QHash<QString, CfgEntry *> &allConfigEntries)
+{
+ CfgEntry* cfgEntry = getConfigEntry(widget, allConfigEntries);
+ if (!cfgEntry)
+ return;
+
+ saveFromWidget(widget, cfgEntry);
+}
+
+void ConfigMapper::saveFromWidget(QWidget* widget, CfgEntry* cfgEntry)
+{
+ if (saveCustomConfigFromWidget(widget, cfgEntry))
+ return;
+
+ saveCommonConfigFromWidget(widget, cfgEntry);
+}
+
+
+bool ConfigMapper::saveCustomConfigFromWidget(QWidget* widget, CfgEntry* key)
+{
+ CustomConfigWidgetPlugin* plugin = nullptr;
+ QList<CustomConfigWidgetPlugin*> handlers;
+ handlers += internalCustomConfigWidgets;
+ handlers += PLUGINS->getLoadedPlugins<CustomConfigWidgetPlugin>();
+
+ foreach (plugin, handlers)
+ {
+ if (plugin->isConfigForWidget(key, widget))
+ {
+ bool ok = false;
+ QVariant value = plugin->getWidgetConfigValue(widget, ok);
+ if (!ok)
+ return false;
+
+ key->set(value);
+ return true;
+ }
+ }
+ return false;
+}
+
+CfgEntry* ConfigMapper::getConfigEntry(QWidget* widget, const QHash<QString, CfgEntry*>& allConfigEntries)
+{
+ return getEntryForProperty(widget, CFG_MODEL_PROPERTY, allConfigEntries);
+}
+
+CfgEntry* ConfigMapper::getEntryForProperty(QWidget* widget, const char* propertyName, const QHash<QString, CfgEntry*>& allConfigEntries)
+{
+ QString key = widget->property(propertyName).toString();
+ if (!allConfigEntries.contains(key))
+ {
+ qCritical() << "Config entries don't contain key" << key
+ << "but it was requested by ConfigMapper::getEntryForProperty() for widget"
+ << widget->metaObject()->className() << "::" << widget->objectName();
+ return nullptr;
+ }
+
+ return allConfigEntries[key];
+}
+
+QHash<QString, CfgEntry *> ConfigMapper::getAllConfigEntries()
+{
+ if (allEntries.isEmpty())
+ {
+ QString key;
+ for (CfgMain* cfgMain : cfgMainList)
+ {
+ QHashIterator<QString,CfgCategory*> catIt(cfgMain->getCategories());
+ while (catIt.hasNext())
+ {
+ catIt.next();
+ QHashIterator<QString,CfgEntry*> entryIt( catIt.value()->getEntries());
+ while (entryIt.hasNext())
+ {
+ entryIt.next();
+ key = catIt.key()+"."+entryIt.key();
+ if (allEntries.contains(key))
+ {
+ qCritical() << "Duplicate config entry key:" << key;
+ continue;
+ }
+ allEntries[key] = entryIt.value();
+ }
+ }
+ }
+ }
+
+ return allEntries;
+}
+
+QList<QWidget*> ConfigMapper::getAllConfigWidgets(QWidget *parent)
+{
+ QList<QWidget*> results;
+ QWidget* widget = nullptr;
+ foreach (QObject* obj, parent->children())
+ {
+ widget = qobject_cast<QWidget*>(obj);
+ if (!widget)
+ continue;
+
+ results += getAllConfigWidgets(widget);
+ if (!widget->property(CFG_MODEL_PROPERTY).isValid())
+ continue;
+
+ results << widget;
+ }
+ return results;
+}
+
+bool ConfigMapper::isPersistant() const
+{
+ for (CfgMain* cfgMain : cfgMainList)
+ {
+ if (cfgMain->isPersistable())
+ return true;
+ }
+ return false;
+}
+QList<QWidget *> ConfigMapper::getExtraWidgets() const
+{
+ return extraWidgets;
+}
+
+void ConfigMapper::setExtraWidgets(const QList<QWidget *> &value)
+{
+ extraWidgets = value;
+}
+
+void ConfigMapper::addExtraWidget(QWidget *w)
+{
+ extraWidgets << w;
+}
+
+void ConfigMapper::addExtraWidgets(const QList<QWidget *> &list)
+{
+ extraWidgets += list;
+}
+
+void ConfigMapper::clearExtraWidgets()
+{
+ extraWidgets.clear();
+}
+
+void ConfigMapper::handleModified()
+{
+ if (realTimeUpdates && !updatingEntry)
+ {
+ QWidget* widget = dynamic_cast<QWidget*>(sender());
+ if (widget && widgetToConfigEntry.contains(widget))
+ {
+ updatingEntry = true;
+ saveFromWidget(widget, widgetToConfigEntry.value(widget));
+ updatingEntry = false;
+ }
+ }
+ emit modified();
+}
+
+void ConfigMapper::entryChanged(const QVariant& newValue)
+{
+ // This is called only when bindToConfig() was used.
+ if (updatingEntry)
+ return;
+
+ CfgEntry* cfgEntry = dynamic_cast<CfgEntry*>(sender());
+ if (!cfgEntry)
+ {
+ qCritical() << "entryChanged() invoked by object that is not CfgEntry:" << sender();
+ return;
+ }
+
+ if (!configEntryToWidgets.contains(cfgEntry))
+ return;
+
+ updatingEntry = true;
+ for (QWidget* w : configEntryToWidgets.values(cfgEntry))
+ applyConfigToWidget(w, cfgEntry, newValue);
+
+ updatingEntry = false;
+}
+
+void ConfigMapper::uiConfigEntryChanged()
+{
+ if (updatingEntry)
+ return;
+
+ QWidget* w = dynamic_cast<QWidget*>(sender());
+ if (!w)
+ {
+ qWarning() << "ConfigMapper::uiConfigEntryChanged() called not from widget:" << sender();
+ return;
+ }
+
+ if (!widgetToConfigEntry.contains(w))
+ {
+ qWarning() << "ConfigMapper::uiConfigEntryChanged() called with widget that has no key assigned:" << w;
+ return;
+ }
+
+ CfgEntry* key = widgetToConfigEntry[w];
+ QVariant value = getConfigValueFromWidget(w, key);
+ emit notifyEnabledWidgetModified(w, key, value);
+}
+
+void ConfigMapper::updateConfigComboModel(const QVariant& value)
+{
+ CfgEntry* key = dynamic_cast<CfgEntry*>(sender());
+ if (!specialConfigEntryToWidgets.contains(key))
+ return;
+
+ QWidget* w = specialConfigEntryToWidgets[key];
+ ConfigComboBox* ccb = dynamic_cast<ConfigComboBox*>(w);
+ if (!w)
+ return;
+
+ QString cText = ccb->currentText();
+ QStringList newList = value.toStringList();
+ ccb->setModel(new QStringListModel(newList));
+ if (newList.contains(cText))
+ ccb->setCurrentText(cText);
+}
+
+void ConfigMapper::notifiableConfigKeyChanged()
+{
+ CfgEntry* key = dynamic_cast<CfgEntry*>(sender());
+ if (!key)
+ {
+ qCritical() << "ConfigMapper::notifiableConfigKeyChanged() called not from CfgEntry";
+ return;
+ }
+
+ if (!configEntryToWidgets.contains(key))
+ {
+ qCritical() << "No entry in configEntryToWidgets for key:" << key->getFullKey();
+ return;
+ }
+
+ loadToWidget(key, configEntryToWidgets[key]);
+}
+
+void ConfigMapper::bindToConfig(QWidget* topLevelWidget)
+{
+ realTimeUpdates = true;
+ loadToWidget(topLevelWidget);
+ for (CfgEntry* cfgEntry : configEntryToWidgets.keys())
+ connect(cfgEntry, SIGNAL(changed(QVariant)), this, SLOT(entryChanged(QVariant)));
+}
+
+void ConfigMapper::unbindFromConfig()
+{
+ for (CfgEntry* cfgEntry : configEntryToWidgets.keys())
+ disconnect(cfgEntry, SIGNAL(changed(QVariant)), this, SLOT(entryChanged(QVariant)));
+
+ for (CfgEntry* cfgEntry : specialConfigEntryToWidgets.keys())
+ disconnect(cfgEntry, SIGNAL(changed(QVariant)), this, SLOT(entryChanged(QVariant)));
+
+ configEntryToWidgets.clear();
+ widgetToConfigEntry.clear();
+ specialConfigEntryToWidgets.clear();
+ realTimeUpdates = false;
+}
+
+QWidget* ConfigMapper::getBindWidgetForConfig(CfgEntry* key) const
+{
+ if (configEntryToWidgets.contains(key))
+ return configEntryToWidgets[key];
+
+ return nullptr;
+}
+
+CfgEntry* ConfigMapper::getBindConfigForWidget(QWidget* widget) const
+{
+ if (widgetToConfigEntry.contains(widget))
+ return widgetToConfigEntry.value(widget);
+
+ return nullptr;
+}
+
+void ConfigMapper::setInternalCustomConfigWidgets(const QList<CustomConfigWidgetPlugin*>& value)
+{
+ internalCustomConfigWidgets = value;
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/configmapper.h b/SQLiteStudio3/guiSQLiteStudio/configmapper.h
new file mode 100644
index 0000000..049d25a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configmapper.h
@@ -0,0 +1,121 @@
+#ifndef CONFIGMAPPER_H
+#define CONFIGMAPPER_H
+
+#include "common/bihash.h"
+#include "guiSQLiteStudio_global.h"
+#include <QObject>
+
+class CfgMain;
+class CfgEntry;
+class CustomConfigWidgetPlugin;
+class ConfigComboBox;
+
+class GUI_API_EXPORT ConfigMapper : public QObject
+{
+ Q_OBJECT
+
+ public:
+ explicit ConfigMapper(CfgMain* cfgMainList);
+ ConfigMapper(const QList<CfgMain*> cfgMainList);
+
+ void loadToWidget(QWidget* topLevelWidget);
+ void loadToWidget(CfgEntry* config, QWidget* widget);
+ void saveFromWidget(QWidget* widget, bool noTransaction = false);
+ void setInternalCustomConfigWidgets(const QList<CustomConfigWidgetPlugin*>& value);
+
+ /**
+ * @brief Scans widget and binds widgets to proper config objects.
+ * @param topLevelWidget Toplevel widget that contains all required configuration widgets.
+ *
+ * It goes through all widgets in given top level widget and looks for statusText property
+ * that matches any of config entries in CfgMain instance(s) passed in constructor.
+ *
+ * For each matched pair (QWidget::statusText() to CfgEntry::getFullKey()) it remembers
+ * pair of QWidget and CfgEntry and from now on when CfgEntry is modified, the QWidget
+ * gets updated with the value and vice versa - when QWidget gets new input, then CfgEntry
+ * is updated with that value. All these updates are done in real time.
+ *
+ * @note Binding mechanism can be used only against CfgMain that is not persistable.
+ * If you try to bind CfgMain that is persistable, it just won't work and it is intended
+ * that way, because having a QWidget bind to a persistable CfgEntry would cause lots
+ * of database updates to store changing value (like from QLineEdit). For persistable please
+ * use loadToWidget() and saveFromWidget() methods, as you can call them when necessary.
+ */
+ void bindToConfig(QWidget* topLevelWidget);
+
+ /**
+ * @brief Releases any bingins against widgets.
+ *
+ * This simply revokes what was done with bindToConfig().
+ */
+ void unbindFromConfig();
+
+ QWidget* getBindWidgetForConfig(CfgEntry* key) const;
+ CfgEntry* getBindConfigForWidget(QWidget* widget) const;
+
+ QList<QWidget *> getExtraWidgets() const;
+
+ /**
+ * @brief Sets list of extra widgets to load/save values to/from.
+ * @param value List of widgets.
+ *
+ * Extra widgets list can be provided to the mapper if it will be impossible to find those widgets
+ * just by looking for childrens of top widget recurrently. This is for example the case
+ * when widgets are embedded in QAbstractItemView::setIndexWidget(). Such widgets have to be
+ * provided as list of extra widgets to the mapper, so the mapper handles them when loading/saving
+ * values.
+ */
+ void setExtraWidgets(const QList<QWidget *> &value);
+ void addExtraWidget(QWidget* w);
+ void addExtraWidgets(const QList<QWidget*>& list);
+ void clearExtraWidgets();
+
+ private:
+ void applyConfigToWidget(QWidget *widget, const QHash<QString, CfgEntry*>& allConfigEntries, const QHash<QString, QVariant> &config);
+ void applyConfigToWidget(QWidget *widget, CfgEntry* cfgEntry, const QVariant& configValue);
+ void handleSpecialWidgets(QWidget *widget, const QHash<QString, CfgEntry*>& allConfigEntries);
+ void handleConfigComboBox(QWidget *widget, const QHash<QString, CfgEntry*>& allConfigEntries);
+ void connectCommonNotifierToWidget(QWidget *widget, CfgEntry* key);
+ bool connectCustomNotifierToWidget(QWidget *widget, CfgEntry* cfgEntry);
+ void applyCommonConfigToWidget(QWidget *widget, const QVariant& value, CfgEntry* cfgEntry);
+ bool applyCustomConfigToWidget(CfgEntry* key, QWidget *widget, const QVariant& value);
+ void saveWidget(QWidget* widget, const QHash<QString, CfgEntry*>& allConfigEntries);
+ void saveFromWidget(QWidget* widget, CfgEntry* cfgEntry);
+ void saveCommonConfigFromWidget(QWidget *widget, CfgEntry* key);
+ bool saveCustomConfigFromWidget(QWidget *widget, CfgEntry* key);
+ QVariant getCommonConfigValueFromWidget(QWidget *widget, CfgEntry* key, bool& ok);
+ QVariant getCustomConfigValueFromWidget(QWidget *widget, CfgEntry* key, bool& ok);
+ QVariant getConfigValueFromWidget(QWidget *widget, CfgEntry* key);
+ CfgEntry* getConfigEntry(QWidget* widget, const QHash<QString, CfgEntry*>& allConfigEntries);
+ CfgEntry* getEntryForProperty(QWidget* widget, const char* propertyName, const QHash<QString, CfgEntry*>& allConfigEntries);
+ QHash<QString,CfgEntry*> getAllConfigEntries();
+ QList<QWidget*> getAllConfigWidgets(QWidget* parent);
+ bool isPersistant() const;
+
+ QList<CfgMain*> cfgMainList;
+ QList<CustomConfigWidgetPlugin*> internalCustomConfigWidgets;
+ bool realTimeUpdates = false;
+ QHash<QWidget*,CfgEntry*> widgetToConfigEntry;
+ QHash<CfgEntry*,QWidget*> configEntryToWidgets;
+ QHash<CfgEntry*,QWidget*> specialConfigEntryToWidgets;
+ bool updatingEntry = false;
+ QList<QWidget*> extraWidgets;
+ QHash<QString, CfgEntry*> allEntries;
+
+ static constexpr const char* CFG_MODEL_PROPERTY = "cfg";
+ static constexpr const char* CFG_NOTIFY_PROPERTY = "notify";
+ static constexpr const char* CFG_PREVIEW_PROPERTY = "preview";
+
+ private slots:
+ void handleModified();
+ void entryChanged(const QVariant& newValue);
+ void uiConfigEntryChanged();
+ void updateConfigComboModel(const QVariant& value);
+ void notifiableConfigKeyChanged();
+
+ signals:
+ void modified();
+ void notifyEnabledWidgetModified(QWidget* widget, CfgEntry* key, const QVariant& value);
+};
+
+#endif // CONFIGMAPPER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/configuiplugin.h b/SQLiteStudio3/guiSQLiteStudio/configuiplugin.h
new file mode 100644
index 0000000..3fa3860
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configuiplugin.h
@@ -0,0 +1,24 @@
+#ifndef CONFIGUIPLUGIN_H
+#define CONFIGUIPLUGIN_H
+
+#include <QtPlugin>
+
+class CfgEntry;
+
+class ConfigUiPlugin
+{
+ public:
+ virtual ~ConfigUiPlugin() {}
+
+ virtual bool isEligible(CfgEntry* key, QWidget *widget, const QVariant& value) const = 0;
+ virtual void loadConfigToWidget(CfgEntry* key, QWidget *widget, const QVariant& value) = 0;
+ virtual QVariant saveConfigFromWidget(CfgEntry* key, QWidget *widget) = 0;
+ virtual bool init() = 0;
+ virtual void deinit() = 0;
+};
+
+#define ConfigUiPluginInterface "pl.sqlitestudio.ConfigUiPlugin/1.0"
+Q_DECLARE_INTERFACE(ConfigUiPlugin, ConfigUiPluginInterface)
+
+
+#endif // CONFIGUIPLUGIN_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp b/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp
new file mode 100644
index 0000000..93433a0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp
@@ -0,0 +1,63 @@
+#include "combodatawidget.h"
+#include "common/unused.h"
+#include "config_builder.h"
+#include "dialogs/configdialog.h"
+#include <QComboBox>
+#include <QDebug>
+
+ComboDataWidget::ComboDataWidget(CfgEntry* key) :
+ assignedKey(key)
+{
+}
+
+bool ComboDataWidget::isConfigForWidget(CfgEntry* key, QWidget* widget)
+{
+ UNUSED(widget);
+ return (assignedKey == key);
+}
+
+void ComboDataWidget::applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value)
+{
+ QComboBox* cb = dynamic_cast<QComboBox*>(widget);
+ if (!cb)
+ {
+ qWarning() << "ComboDataWidget assigned to widget which is not combobox, but:" << widget->metaObject()->className()
+ << ", config key:" << key->getFullKey();
+ return;
+ }
+
+ QVariant data;
+ for (int i = 0; i < cb->count(); i++)
+ {
+ data = cb->itemData(i);
+ if (data == value)
+ {
+ cb->setCurrentIndex(i);
+ break;
+ }
+ }
+}
+
+QVariant ComboDataWidget::getWidgetConfigValue(QWidget* widget, bool& ok)
+{
+ QComboBox* cb = dynamic_cast<QComboBox*>(widget);
+ if (!cb)
+ {
+ ok = false;
+ qWarning() << "ComboDataWidget assigned to widget which is not combobox, but:" << widget->metaObject()->className();
+ return QVariant();
+ }
+
+ ok = true;
+ return cb->itemData(cb->currentIndex());
+}
+
+const char* ComboDataWidget::getModifiedNotifier() const
+{
+ return SIGNAL(currentTextChanged(QString));
+}
+
+QString ComboDataWidget::getFilterString(QWidget *widget) const
+{
+ return ConfigDialog::getFilterString(widget);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h b/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h
new file mode 100644
index 0000000..302186e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h
@@ -0,0 +1,35 @@
+#ifndef COMBODATAWIDGET_H
+#define COMBODATAWIDGET_H
+
+#include "customconfigwidgetplugin.h"
+#include "plugins/genericplugin.h"
+#include "guiSQLiteStudio_global.h"
+
+/**
+ * @brief Config entry handler for combo box items with dynamic data set
+ *
+ * This config entry handler runs only for specified "assigned key", so even it's implements CustomConfigWidgetPlugin,
+ * it's created explicitly for each combo.
+ *
+ * It is used to convert CfgEntry value to one of combo's entries and set that value in the combo.
+ * It also works the other way, of course (from combo value to CfgEntry value).
+ *
+ * Currently it is used only by ConfigDialog because of its specific case with custom formatter combo,
+ * which has dynamic contents based on what's added/removed from the combo.
+ */
+class GUI_API_EXPORT ComboDataWidget : public GenericPlugin, public CustomConfigWidgetPlugin
+{
+ public:
+ explicit ComboDataWidget(CfgEntry* key);
+
+ bool isConfigForWidget(CfgEntry* key, QWidget* widget);
+ void applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value);
+ QVariant getWidgetConfigValue(QWidget* widget, bool& ok);
+ const char*getModifiedNotifier() const;
+ QString getFilterString(QWidget* widget) const;
+
+ private:
+ CfgEntry* assignedKey = nullptr;
+};
+
+#endif // COMBODATAWIDGET_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp b/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp
new file mode 100644
index 0000000..48b46cc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp
@@ -0,0 +1,67 @@
+#include "listtostringlisthash.h"
+#include "config_builder.h"
+#include "common/unused.h"
+#include <QListWidget>
+
+ListToStringListHash::ListToStringListHash(CfgEntry* key) :
+ assignedKey(key)
+{
+}
+
+bool ListToStringListHash::isConfigForWidget(CfgEntry* key, QWidget* widget)
+{
+ UNUSED(widget);
+ return (assignedKey == key);
+}
+
+void ListToStringListHash::applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value)
+{
+ UNUSED(key);
+
+ QListWidget* list = dynamic_cast<QListWidget*>(widget);
+ QHash<QString,QListWidgetItem*> itemsByName;
+ for (int i = 0; i < list->count(); i++)
+ itemsByName[list->item(i)->text()] = list->item(i);
+
+ QHash<QString,QVariant> orderHash = value.toHash();
+
+ for (const QString& typeName : itemsByName.keys())
+ {
+ if (!orderHash.contains(typeName))
+ continue;
+
+ itemsByName[typeName]->setData(QListWidgetItem::UserType, orderHash[typeName]);
+ }
+}
+
+QVariant ListToStringListHash::getWidgetConfigValue(QWidget* widget, bool& ok)
+{
+ QListWidget* list = dynamic_cast<QListWidget*>(widget);
+ if (!list)
+ {
+ ok = false;
+ return QVariant();
+ }
+
+ QHash<QString,QVariant> orderHash;
+ for (int i = 0; i < list->count(); i++)
+ orderHash[list->item(i)->text()] = list->item(i)->data(QListWidgetItem::UserType);
+
+ ok = true;
+ return orderHash;
+}
+
+const char*ListToStringListHash::getModifiedNotifier() const
+{
+ return SIGNAL(itemChanged(QListWidgetItem*));
+}
+
+QString ListToStringListHash::getFilterString(QWidget* widget) const
+{
+ QListWidget* list = dynamic_cast<QListWidget*>(widget);
+ QStringList strList;
+ for (int i = 0; i < list->count(); i++)
+ strList += list->item(i)->text() + " " + list->item(i)->data(QListWidgetItem::UserType).toStringList().join(" ");
+
+ return strList.join(" ");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h b/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h
new file mode 100644
index 0000000..1f5d6fc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h
@@ -0,0 +1,22 @@
+#ifndef LISTTOSTRINGLISTHASH_H
+#define LISTTOSTRINGLISTHASH_H
+
+#include "customconfigwidgetplugin.h"
+#include "plugins/genericplugin.h"
+#include "guiSQLiteStudio_global.h"
+
+class GUI_API_EXPORT ListToStringListHash: public GenericPlugin, public CustomConfigWidgetPlugin
+{
+ public:
+ ListToStringListHash(CfgEntry* key);
+ bool isConfigForWidget(CfgEntry* key, QWidget* widget);
+ void applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value);
+ QVariant getWidgetConfigValue(QWidget* widget, bool& ok);
+ const char*getModifiedNotifier() const;
+ QString getFilterString(QWidget* widget) const;
+
+ private:
+ CfgEntry* assignedKey = nullptr;
+};
+
+#endif // LISTTOSTRINGLISTHASH_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp b/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp
new file mode 100644
index 0000000..a8d8b46
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp
@@ -0,0 +1,54 @@
+#include "styleconfigwidget.h"
+#include "uiconfig.h"
+#include "common/unused.h"
+#include "dialogs/configdialog.h"
+#include <QDebug>
+#include <QComboBox>
+
+StyleConfigWidget::StyleConfigWidget()
+{
+}
+
+bool StyleConfigWidget::isConfigForWidget(CfgEntry* key, QWidget* widget)
+{
+ UNUSED(widget);
+ return (key == &CFG_UI.General.Style);
+}
+
+void StyleConfigWidget::applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value)
+{
+ UNUSED(key);
+
+ QComboBox* combo = qobject_cast<QComboBox*>(widget);
+
+ // QComboBox fails to findIndex() in case insensitive manner, so we will do it manually:
+ QStringList items;
+ for (int i = 0; i < combo->count(); i++)
+ items << combo->itemText(i).toLower();
+
+ int idx = items.indexOf(value.toString().toLower());
+ combo->setCurrentIndex(idx);
+}
+
+QVariant StyleConfigWidget::getWidgetConfigValue(QWidget* widget, bool& ok)
+{
+ QComboBox* combo = qobject_cast<QComboBox*>(widget);
+ if (!combo)
+ {
+ ok = false;
+ return QVariant();
+ }
+
+ ok = true;
+ return combo->currentText();
+}
+
+const char* StyleConfigWidget::getModifiedNotifier() const
+{
+ return SIGNAL(currentTextChanged(QString));
+}
+
+QString StyleConfigWidget::getFilterString(QWidget *widget) const
+{
+ return ConfigDialog::getFilterString(widget);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h b/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h
new file mode 100644
index 0000000..d3548af
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h
@@ -0,0 +1,20 @@
+#ifndef STYLECONFIGWIDGET_H
+#define STYLECONFIGWIDGET_H
+
+#include "plugins/genericplugin.h"
+#include "customconfigwidgetplugin.h"
+#include "guiSQLiteStudio_global.h"
+
+class GUI_API_EXPORT StyleConfigWidget : public GenericPlugin, public CustomConfigWidgetPlugin
+{
+ public:
+ StyleConfigWidget();
+
+ bool isConfigForWidget(CfgEntry* key, QWidget* widget);
+ void applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant& value);
+ QVariant getWidgetConfigValue(QWidget* widget, bool& ok);
+ const char* getModifiedNotifier() const;
+ QString getFilterString(QWidget* widget) const;
+};
+
+#endif // STYLECONFIGWIDGET_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp
new file mode 100644
index 0000000..8402c21
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp
@@ -0,0 +1,56 @@
+#include "columncheckpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/parser.h"
+#include <QDebug>
+
+ColumnCheckPanel::ColumnCheckPanel(QWidget *parent) :
+ ConstraintCheckPanel(parent)
+{
+}
+
+SqliteExpr* ColumnCheckPanel::readExpr()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ return constr->expr;
+}
+
+QString ColumnCheckPanel::readName()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ return constr->name;
+}
+
+void ColumnCheckPanel::storeType()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::CHECK;
+}
+
+SqliteConflictAlgo ColumnCheckPanel::readConflictAlgo()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ return constr->onConflict;
+}
+
+void ColumnCheckPanel::storeExpr(SqliteExpr* expr)
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->expr = expr;
+}
+
+void ColumnCheckPanel::storeName(const QString& name)
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->name = name;
+}
+
+void ColumnCheckPanel::storeConflictAlgo(SqliteConflictAlgo algo)
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->onConflict = algo;
+}
+
+SqliteCreateTable* ColumnCheckPanel::getCreateTable()
+{
+ return dynamic_cast<SqliteCreateTable*>(constraint->parentStatement()->parentStatement());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h
new file mode 100644
index 0000000..8c23770
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h
@@ -0,0 +1,27 @@
+#ifndef COLUMNCHECKPANEL_H
+#define COLUMNCHECKPANEL_H
+
+#include "constraintcheckpanel.h"
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+class GUI_API_EXPORT ColumnCheckPanel : public ConstraintCheckPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnCheckPanel(QWidget *parent = 0);
+
+ protected:
+ SqliteExpr* readExpr();
+ QString readName();
+ void storeType();
+ SqliteConflictAlgo readConflictAlgo();
+ void storeExpr(SqliteExpr* expr);
+ void storeName(const QString& name);
+ void storeConflictAlgo(SqliteConflictAlgo algo);
+ SqliteCreateTable* getCreateTable();
+};
+
+#endif // COLUMNCHECKPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp
new file mode 100644
index 0000000..de78b2b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp
@@ -0,0 +1,107 @@
+#include "columncollatepanel.h"
+#include "ui_columncollatepanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "schemaresolver.h"
+#include "uiutils.h"
+#include <QStringListModel>
+
+ColumnCollatePanel::ColumnCollatePanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ColumnCollatePanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ColumnCollatePanel::~ColumnCollatePanel()
+{
+ delete ui;
+}
+
+void ColumnCollatePanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ColumnCollatePanel::init()
+{
+ collationModel = new QStringListModel(this);
+ ui->collationCombo->setModel(collationModel);
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->collationCombo->lineEdit(), SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void ColumnCollatePanel::readConstraint()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ ui->collationCombo->setCurrentText(constr->collationName);
+
+ if (!constr->name.isNull())
+ {
+ ui->namedCheck->setChecked(true);
+ ui->namedEdit->setText(constr->name);
+ }
+}
+
+void ColumnCollatePanel::readCollations()
+{
+ SchemaResolver resolver(db);
+ QStringList collList = resolver.getCollations();
+
+ if (collList.size() > 0)
+ collList.prepend("");
+
+ collationModel->setStringList(collList);
+}
+
+void ColumnCollatePanel::updateState()
+{
+ ui->namedEdit->setEnabled(ui->namedCheck->isChecked());
+}
+
+bool ColumnCollatePanel::validate()
+{
+ bool nameOk = true;
+ if (ui->namedCheck->isChecked() && ui->namedEdit->text().isEmpty())
+ nameOk = false;
+
+ bool collationOk = !ui->collationCombo->currentText().isEmpty();
+
+ setValidState(ui->namedEdit, nameOk, tr("Enter a name of the constraint."));
+ setValidState(ui->collationCombo, collationOk, tr("Enter a collation name."));
+
+ return nameOk && collationOk;
+}
+
+void ColumnCollatePanel::constraintAvailable()
+{
+ if (constraint.isNull())
+ return;
+
+ readCollations();
+ readConstraint();
+}
+
+void ColumnCollatePanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::COLLATE;
+
+ if (ui->namedCheck->isChecked())
+ constr->name = ui->namedEdit->text();
+
+ constr->collationName = ui->collationCombo->currentText();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h
new file mode 100644
index 0000000..a7afb54
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h
@@ -0,0 +1,40 @@
+#ifndef COLUMNCOLLATEPANEL_H
+#define COLUMNCOLLATEPANEL_H
+
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+
+namespace Ui {
+ class ColumnCollatePanel;
+}
+
+class QStringListModel;
+
+class GUI_API_EXPORT ColumnCollatePanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnCollatePanel(QWidget *parent = 0);
+ ~ColumnCollatePanel();
+
+ bool validate();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+
+ private:
+ void init();
+ void readConstraint();
+ void readCollations();
+
+ QStringListModel* collationModel = nullptr;
+ Ui::ColumnCollatePanel *ui = nullptr;
+
+ private slots:
+ void updateState();
+};
+
+#endif // COLUMNCOLLATEPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui
new file mode 100644
index 0000000..a481875
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnCollatePanel</class>
+ <widget class="QWidget" name="ColumnCollatePanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>79</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="collationWidget" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>30</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="collationLabel">
+ <property name="text">
+ <string>Collation name:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="collationCombo">
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="namedCheck">
+ <property name="text">
+ <string>Named constraint:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp
new file mode 100644
index 0000000..3d7090a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp
@@ -0,0 +1,204 @@
+#include "columndefaultpanel.h"
+#include "ui_columndefaultpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/parser.h"
+#include "parser/keywords.h"
+#include "uiutils.h"
+#include <QDebug>
+
+ColumnDefaultPanel::ColumnDefaultPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ColumnDefaultPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ColumnDefaultPanel::~ColumnDefaultPanel()
+{
+ delete ui;
+}
+
+void ColumnDefaultPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+
+bool ColumnDefaultPanel::validate()
+{
+ bool nameOk = true;
+ if (ui->namedCheck->isChecked() && ui->namedEdit->text().isEmpty())
+ nameOk = false;
+
+ bool exprOk = !ui->exprEdit->toPlainText().trimmed().isEmpty() &&
+ !ui->exprEdit->haveErrors();
+
+ bool exprCheckedOk = exprOk && ui->exprEdit->isSyntaxChecked();
+
+ setValidState(ui->exprEdit, exprOk, tr("Enter a default value expression."));
+ setValidState(ui->namedEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return exprCheckedOk && nameOk;
+}
+
+bool ColumnDefaultPanel::validateOnly()
+{
+ ui->exprEdit->checkSyntaxNow();
+ return validate();
+}
+
+void ColumnDefaultPanel::constraintAvailable()
+{
+ if (constraint.isNull())
+ return;
+
+ readConstraint();
+ updateVirtualSql();
+}
+
+void ColumnDefaultPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::DEFAULT;
+
+ SqliteExprPtr expr = parseExpression(ui->exprEdit->toPlainText());
+ SqliteExpr* newExpr = new SqliteExpr(*expr.data());
+ newExpr->setParent(constraint.data());
+ constr->expr = newExpr;
+
+ if (ui->namedCheck->isChecked())
+ constr->name = ui->namedEdit->text();
+}
+
+void ColumnDefaultPanel::init()
+{
+ setFocusProxy(ui->exprEdit);
+ ui->exprEdit->setShowLineNumbers(false);
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->exprEdit, SIGNAL(textChanged()), this, SIGNAL(updateValidation()));
+ connect(ui->exprEdit, SIGNAL(errorsChecked(bool)), this, SIGNAL(updateValidation()));
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+
+ updateState();
+}
+
+void ColumnDefaultPanel::readConstraint()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+
+ if (constr->expr)
+ ui->exprEdit->setPlainText(constr->expr->detokenize());
+ else if (!constr->literalValue.isNull())
+ ui->exprEdit->setPlainText(constr->literalValue.toString());
+
+ if (!constr->name.isNull())
+ {
+ ui->namedCheck->setChecked(true);
+ ui->namedEdit->setText(constr->name);
+ }
+}
+
+void ColumnDefaultPanel::updateVirtualSql()
+{
+ ui->exprEdit->setDb(db);
+
+ SqliteCreateTable::Column* column = dynamic_cast<SqliteCreateTable::Column*>(constraint->parentStatement());
+ SqliteCreateTable* createTable = dynamic_cast<SqliteCreateTable*>(column->parentStatement());
+
+ createTable->rebuildTokens();
+ TokenList tokens = createTable->tokens;
+ TokenList colTokens = column->tokens;
+ if (createTable->columns.indexOf(column) == -1)
+ {
+ if (createTable->columns.size() == 0)
+ {
+ // No columns. Cannot get any context info.
+ return;
+ }
+
+ colTokens = createTable->columns.last()->tokens;
+ }
+
+ if (colTokens.size() == 0)
+ {
+ qWarning() << "CREATE TABLE tokens are invalid (0) while call to ColumnDefaultPanel::updateVirtualSql().";
+ return;
+ }
+
+ int idx = tokens.lastIndexOf(colTokens.last());
+ if (idx == -1)
+ {
+ qWarning() << "CREATE TABLE tokens are invalid while call to ColumnDefaultPanel::updateVirtualSql().";
+ return;
+ }
+ idx++;
+
+ TokenList newTokens;
+ newTokens << TokenPtr::create(Token::SPACE, " ")
+ << TokenPtr::create(Token::KEYWORD, "DEFAULT")
+ << TokenPtr::create(Token::SPACE, " ");
+
+ if (constraint->dialect == Dialect::Sqlite3)
+ {
+ newTokens << TokenPtr::create(Token::PAR_LEFT, "(")
+ << TokenPtr::create(Token::OTHER, "%1")
+ << TokenPtr::create(Token::PAR_RIGHT, ")");
+ }
+ else
+ {
+ newTokens << TokenPtr::create(Token::OTHER, "%1");
+ }
+
+ tokens.insert(idx, newTokens);
+ QString sql = tokens.detokenize();
+
+ ui->exprEdit->setVirtualSqlExpression(sql);
+}
+
+SqliteExprPtr ColumnDefaultPanel::parseExpression(const QString& sql)
+{
+ Parser parser(db->getDialect());
+ if (!parser.parse("SELECT "+sql))
+ return SqliteExprPtr();
+
+ QList<SqliteQueryPtr> queries = parser.getQueries();
+ if (queries.size() == 0)
+ return SqliteExprPtr();
+
+ SqliteQueryPtr first = queries.first();
+ if (first->queryType != SqliteQueryType::Select)
+ return SqliteExprPtr();
+
+ SqliteSelectPtr select = first.dynamicCast<SqliteSelect>();
+ if (select->coreSelects.size() < 1)
+ return SqliteExprPtr();
+
+ SqliteSelect::Core* core = select->coreSelects.first();
+ if (core->resultColumns.size() < 1)
+ return SqliteExprPtr();
+
+ SqliteSelect::Core::ResultColumn* resCol = core->resultColumns.first();
+ if (!resCol->expr)
+ return SqliteExprPtr();
+
+ return resCol->expr->detach().dynamicCast<SqliteExpr>();
+}
+
+void ColumnDefaultPanel::updateState()
+{
+ ui->namedEdit->setEnabled(ui->namedCheck->isChecked());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h
new file mode 100644
index 0000000..933c2dd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h
@@ -0,0 +1,41 @@
+#ifndef COLUMNDEFAULTPANEL_H
+#define COLUMNDEFAULTPANEL_H
+
+#include "constraintpanel.h"
+#include "parser/ast/sqliteconflictalgo.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class ColumnDefaultPanel;
+}
+
+class GUI_API_EXPORT ColumnDefaultPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnDefaultPanel(QWidget *parent = 0);
+ ~ColumnDefaultPanel();
+
+ bool validate();
+ bool validateOnly();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+
+ private:
+ void init();
+ void readConstraint();
+ void updateVirtualSql();
+ SqliteExprPtr parseExpression(const QString& sql);
+
+ Ui::ColumnDefaultPanel *ui = nullptr;
+
+ private slots:
+ void updateState();
+};
+
+#endif // COLUMNDEFAULTPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui
new file mode 100644
index 0000000..fdf55b0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnDefaultPanel</class>
+ <widget class="QWidget" name="ColumnDefaultPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>169</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="exprGroup">
+ <property name="title">
+ <string>Default value:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlEditor" name="exprEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="namedCheck">
+ <property name="text">
+ <string>Named constraint:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp
new file mode 100644
index 0000000..cf234f7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp
@@ -0,0 +1,266 @@
+#include "columnforeignkeypanel.h"
+#include "ui_columnforeignkeypanel.h"
+#include "schemaresolver.h"
+#include "uiutils.h"
+#include <QDebug>
+#include <QSignalMapper>
+
+ColumnForeignKeyPanel::ColumnForeignKeyPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ColumnForeignKeyPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ColumnForeignKeyPanel::~ColumnForeignKeyPanel()
+{
+ delete ui;
+}
+
+void ColumnForeignKeyPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+
+bool ColumnForeignKeyPanel::validate()
+{
+ bool tableOk = (ui->fkTableCombo->currentIndex() > -1);
+ bool columnOk = (ui->fkColumnCombo->currentIndex() > -1);
+ bool nameOk = !ui->namedCheckBox->isChecked() || !ui->nameEdit->text().isEmpty();
+
+ setValidState(ui->fkTableCombo, tableOk, tr("Pick the foreign table."));
+ setValidState(ui->fkColumnCombo, columnOk, tr("Pick the foreign column."));
+ setValidState(ui->nameEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return tableOk && columnOk && nameOk;
+}
+
+void ColumnForeignKeyPanel::constraintAvailable()
+{
+ readTables();
+ readConstraint();
+}
+
+void ColumnForeignKeyPanel::init()
+{
+ setFocusProxy(ui->fkTableCombo);
+
+ ui->fkColumnCombo->setModel(&fkColumnsModel);
+ connect(ui->fkColumnCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(updateValidation()));
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(updateValidation()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFkColumns()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+ connect(ui->onDeleteCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->onUpdateCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->matchCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+
+ ui->deferrableCombo->addItems({
+ "",
+ sqliteDeferrable(SqliteDeferrable::DEFERRABLE),
+ sqliteDeferrable(SqliteDeferrable::NOT_DEFERRABLE)
+ });
+ ui->initiallyCombo->addItems({
+ "",
+ sqliteInitially(SqliteInitially::DEFERRED),
+ sqliteInitially(SqliteInitially::IMMEDIATE),
+ });
+
+ QStringList reactions = {
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::NO_ACTION),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::SET_NULL),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::SET_DEFAULT),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::CASCADE),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::RESTRICT)
+ };
+ ui->onUpdateCombo->addItems(reactions);
+ ui->onDeleteCombo->addItems(reactions);
+ ui->matchCombo->addItems({"SIMPLE", "FULL", "PARTIAL"});
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void ColumnForeignKeyPanel::updateState()
+{
+ bool tableSelected = (ui->fkTableCombo->currentIndex() > -1);
+ ui->fkColumnCombo->setEnabled(tableSelected);
+ ui->deferrableCombo->setEnabled(tableSelected);
+ ui->initiallyCombo->setEnabled(tableSelected);
+ ui->namedCheckBox->setEnabled(tableSelected);
+ ui->nameEdit->setEnabled(tableSelected && ui->namedCheckBox->isChecked());
+ ui->onDeleteCheckBox->setEnabled(tableSelected);
+ ui->onUpdateCheckBox->setEnabled(tableSelected);
+ ui->matchCheckBox->setEnabled(tableSelected);
+ ui->onDeleteCombo->setEnabled(tableSelected && ui->onDeleteCheckBox->isChecked());
+ ui->onUpdateCombo->setEnabled(tableSelected && ui->onUpdateCheckBox->isChecked());
+ ui->matchCombo->setEnabled(tableSelected && ui->matchCheckBox->isChecked());
+}
+
+void ColumnForeignKeyPanel::updateFkColumns()
+{
+ QStringList columns;
+ if (ui->fkTableCombo->currentIndex() == -1)
+ {
+ fkColumnsModel.setStringList(columns);
+ updateState();
+ return;
+ }
+
+ SchemaResolver resolver(db);
+ columns = resolver.getTableColumns(ui->fkTableCombo->currentText()); // TODO named db attach not supported
+ fkColumnsModel.setStringList(columns);
+}
+
+void ColumnForeignKeyPanel::readConstraint()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Column* column = dynamic_cast<SqliteCreateTable::Column*>(constraint->parent());
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ if (!constr->foreignKey)
+ return;
+
+ // Table
+ if (!constr->foreignKey->foreignTable.isNull())
+ ui->fkTableCombo->setCurrentText(constr->foreignKey->foreignTable);
+
+ // Conditions
+ foreach (SqliteForeignKey::Condition* condition, constr->foreignKey->conditions)
+ readCondition(condition);
+
+ // Initially, Deferrable
+ ui->deferrableCombo->setCurrentText(sqliteDeferrable(constr->foreignKey->deferrable));
+ ui->initiallyCombo->setCurrentText(sqliteInitially(constr->foreignKey->initially));
+
+ // Name
+ if (!constr->name.isNull())
+ {
+ ui->namedCheckBox->setChecked(true);
+ ui->nameEdit->setText(constr->name);
+ }
+
+ // Column
+ if (constr->foreignKey->indexedColumns.size() > 1)
+ {
+ qWarning() << "More than one referenced column in the column foreign key:" << constr->detokenize();
+ return;
+ }
+
+ QString fkColumn = column->name;
+ if (constr->foreignKey->indexedColumns.size() == 1)
+ fkColumn = constr->foreignKey->indexedColumns.first()->name;
+
+ ui->fkColumnCombo->setCurrentText(fkColumn);
+}
+
+void ColumnForeignKeyPanel::readCondition(SqliteForeignKey::Condition* condition)
+{
+ switch (condition->action)
+ {
+ case SqliteForeignKey::Condition::UPDATE:
+ ui->onUpdateCheckBox->setChecked(true);
+ ui->onUpdateCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ case SqliteForeignKey::Condition::INSERT:
+ // INSERT is not officially supported.
+ break;
+ case SqliteForeignKey::Condition::DELETE:
+ ui->onDeleteCheckBox->setChecked(true);
+ ui->onDeleteCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ case SqliteForeignKey::Condition::MATCH:
+ ui->matchCheckBox->setChecked(true);
+ ui->matchCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ }
+}
+
+void ColumnForeignKeyPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ // Type
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY;
+
+ // Cleanup & initial setup
+ if (constr->foreignKey)
+ delete constr->foreignKey;
+
+ constr->foreignKey = new SqliteForeignKey();
+ constr->foreignKey->setParent(constr);
+
+ // Foreign table
+ constr->foreignKey->foreignTable = ui->fkTableCombo->currentText();
+
+ // Column
+ SqliteIndexedColumn* idxCol = new SqliteIndexedColumn(ui->fkColumnCombo->currentText());
+ idxCol->setParent(constr->foreignKey);
+ constr->foreignKey->indexedColumns << idxCol;
+
+ // Actions/reactions
+ if (ui->onDeleteCheckBox->isChecked())
+ storeCondition(SqliteForeignKey::Condition::DELETE, ui->onDeleteCombo->currentText());
+
+ if (ui->onUpdateCheckBox->isChecked())
+ storeCondition(SqliteForeignKey::Condition::UPDATE, ui->onDeleteCombo->currentText());
+
+ if (ui->matchCheckBox->isChecked())
+ storeMatchCondition(ui->matchCombo->currentText());
+
+ // Deferred/initially
+ constr->foreignKey->deferrable = sqliteDeferrable(ui->deferrableCombo->currentText());
+ constr->foreignKey->initially = sqliteInitially(ui->initiallyCombo->currentText());
+
+ // Name
+ constr->name = QString::null;
+ if (ui->namedCheckBox->isChecked())
+ constr->name = ui->nameEdit->text();
+}
+
+void ColumnForeignKeyPanel::storeCondition(SqliteForeignKey::Condition::Action action, const QString& reaction)
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+
+ SqliteForeignKey::Condition* condition = new SqliteForeignKey::Condition(
+ action,
+ SqliteForeignKey::Condition::toEnum(reaction)
+ );
+ condition->setParent(constr->foreignKey);
+ constr->foreignKey->conditions << condition;
+}
+
+void ColumnForeignKeyPanel::storeMatchCondition(const QString& reaction)
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+
+ SqliteForeignKey::Condition* condition = new SqliteForeignKey::Condition(reaction);
+ condition->setParent(constr->foreignKey);
+ constr->foreignKey->conditions << condition;
+}
+
+void ColumnForeignKeyPanel::readTables()
+{
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(true);
+ QStringList tables = resolver.getTables(); // TODO named db attach not supported
+
+ tables.sort(Qt::CaseInsensitive);
+
+ ui->fkTableCombo->addItems(tables);
+ ui->fkTableCombo->setCurrentIndex(-1);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h
new file mode 100644
index 0000000..3fe3077
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h
@@ -0,0 +1,48 @@
+#ifndef COLUMNFOREIGNKEYPANEL_H
+#define COLUMNFOREIGNKEYPANEL_H
+
+#include "constraintpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStringListModel>
+#include <QWidget>
+
+namespace Ui {
+ class ColumnForeignKeyPanel;
+}
+
+class QGridLayout;
+class QSignalMapper;
+
+class GUI_API_EXPORT ColumnForeignKeyPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnForeignKeyPanel(QWidget *parent = 0);
+ ~ColumnForeignKeyPanel();
+
+ bool validate();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+
+ private:
+ void init();
+ void readConstraint();
+ void readTables();
+ void readCondition(SqliteForeignKey::Condition* condition);
+ void storeCondition(SqliteForeignKey::Condition::Action action, const QString& reaction);
+ void storeMatchCondition(const QString& reaction);
+
+ Ui::ColumnForeignKeyPanel *ui = nullptr;
+ QStringListModel fkColumnsModel;
+
+ private slots:
+ void updateState();
+ void updateFkColumns();
+};
+
+#endif // COLUMNFOREIGNKEYPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui
new file mode 100644
index 0000000..c442967
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnForeignKeyPanel</class>
+ <widget class="QWidget" name="ColumnForeignKeyPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>320</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>320</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="fkTableWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="fkTableLabel">
+ <property name="text">
+ <string>Foreign table:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="fkTableCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="fkColumnWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="fkColumnLabel">
+ <property name="text">
+ <string>Foreign column:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="fkColumnCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="reactionsGroup">
+ <property name="title">
+ <string>Reactions</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="onUpdateCheckBox">
+ <property name="text">
+ <string>ON UPDATE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="onUpdateCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="onDeleteCheckBox">
+ <property name="text">
+ <string>ON DELETE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="matchCheckBox">
+ <property name="text">
+ <string>MATCH</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="onDeleteCombo"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="matchCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="deferredGroup">
+ <property name="title">
+ <string>Deferred foreign key</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QComboBox" name="deferrableCombo"/>
+ </item>
+ <item>
+ <widget class="QComboBox" name="initiallyCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QCheckBox" name="namedCheckBox">
+ <property name="text">
+ <string>Named constraint</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nameEdit">
+ <property name="placeholderText">
+ <string>Constraint name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>fkTableCombo</tabstop>
+ <tabstop>fkColumnCombo</tabstop>
+ <tabstop>onUpdateCheckBox</tabstop>
+ <tabstop>onUpdateCombo</tabstop>
+ <tabstop>onDeleteCheckBox</tabstop>
+ <tabstop>onDeleteCombo</tabstop>
+ <tabstop>matchCheckBox</tabstop>
+ <tabstop>matchCombo</tabstop>
+ <tabstop>deferrableCombo</tabstop>
+ <tabstop>initiallyCombo</tabstop>
+ <tabstop>namedCheckBox</tabstop>
+ <tabstop>nameEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp
new file mode 100644
index 0000000..9da20db
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp
@@ -0,0 +1,13 @@
+#include "columnnotnullpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+
+ColumnNotNullPanel::ColumnNotNullPanel(QWidget *parent) :
+ ColumnUniqueAndNotNullPanel(parent)
+{
+}
+
+void ColumnNotNullPanel::storeType()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::UNIQUE;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h
new file mode 100644
index 0000000..1cde833
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h
@@ -0,0 +1,17 @@
+#ifndef COLUMNNOTNULLPANEL_H
+#define COLUMNNOTNULLPANEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include "columnuniqueandnotnullpanel.h"
+
+class GUI_API_EXPORT ColumnNotNullPanel : public ColumnUniqueAndNotNullPanel
+{
+ Q_OBJECT
+ public:
+ explicit ColumnNotNullPanel(QWidget *parent = 0);
+
+ protected:
+ void storeType();
+};
+
+#endif // COLUMNNOTNULLPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp
new file mode 100644
index 0000000..d10b223
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp
@@ -0,0 +1,127 @@
+#include "columnprimarykeypanel.h"
+#include "ui_columnprimarykeypanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/keywords.h"
+#include "datatype.h"
+#include "uiutils.h"
+
+ColumnPrimaryKeyPanel::ColumnPrimaryKeyPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ColumnPrimaryKeyPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ColumnPrimaryKeyPanel::~ColumnPrimaryKeyPanel()
+{
+ delete ui;
+}
+
+void ColumnPrimaryKeyPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ColumnPrimaryKeyPanel::init()
+{
+ QStringList sortOrders = {sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)};
+ ui->sortOrderCombo->addItems(sortOrders);
+
+ ui->conflictCombo->addItems(getConflictAlgorithms());
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->sortOrderCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->conflictCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void ColumnPrimaryKeyPanel::readConstraint()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ if (constraint->dialect == Dialect::Sqlite3)
+ ui->autoIncrCheck->setChecked(constr->autoincrKw);
+
+ if (constr->sortOrder != SqliteSortOrder::null)
+ {
+ ui->sortOrderCheck->setChecked(true);
+ ui->sortOrderCombo->setCurrentText(sqliteSortOrder(constr->sortOrder));
+ }
+
+ if (!constr->name.isNull())
+ {
+ ui->namedCheck->setEnabled(true);
+ ui->namedEdit->setText(constr->name);
+ }
+
+ if (constr->onConflict != SqliteConflictAlgo::null)
+ {
+ ui->conflictCheck->setChecked(true);
+ ui->conflictCombo->setCurrentText(sqliteConflictAlgo(constr->onConflict));
+ }
+}
+
+void ColumnPrimaryKeyPanel::updateState()
+{
+ ui->sortOrderCombo->setEnabled(ui->sortOrderCheck->isChecked());
+ ui->namedEdit->setEnabled(ui->namedCheck->isChecked());
+ ui->conflictCombo->setEnabled(ui->conflictCheck->isChecked());
+}
+
+
+bool ColumnPrimaryKeyPanel::validate()
+{
+ bool nameOk = true;
+ if (ui->namedCheck->isChecked() && ui->namedEdit->text().isEmpty())
+ nameOk = false;
+
+ setValidState(ui->namedEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return nameOk;
+}
+
+void ColumnPrimaryKeyPanel::constraintAvailable()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Column* column = dynamic_cast<SqliteCreateTable::Column*>(constraint->parent());
+ ui->autoIncrCheck->setVisible(constraint->dialect == Dialect::Sqlite3);
+ ui->autoIncrCheck->setEnabled(column->type &&
+ DataType::fromString(column->type->detokenize().trimmed(), Qt::CaseInsensitive) == DataType::INTEGER);
+
+ if (!ui->autoIncrCheck->isEnabled())
+ ui->autoIncrCheck->setText(tr("Autoincrement (only for %1 type columns)", "column primary key").arg("INTEGER"));
+
+ readConstraint();
+}
+
+void ColumnPrimaryKeyPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY;
+
+ if (constraint->dialect == Dialect::Sqlite3)
+ constr->autoincrKw = ui->autoIncrCheck->isChecked();
+
+ if (ui->sortOrderCheck->isChecked() && ui->sortOrderCombo->currentIndex() > -1)
+ constr->sortOrder = sqliteSortOrder(ui->sortOrderCombo->currentText());
+
+ if (ui->namedCheck->isChecked())
+ constr->name = ui->namedEdit->text();
+
+ if (ui->conflictCheck->isChecked() && ui->conflictCombo->currentIndex() > -1)
+ constr->onConflict = sqliteConflictAlgo(ui->conflictCombo->currentText());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h
new file mode 100644
index 0000000..ac97363
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h
@@ -0,0 +1,36 @@
+#ifndef COLUMNPRIMARYKEYPANEL_H
+#define COLUMNPRIMARYKEYPANEL_H
+
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+
+namespace Ui {
+ class ColumnPrimaryKeyPanel;
+}
+
+class GUI_API_EXPORT ColumnPrimaryKeyPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnPrimaryKeyPanel(QWidget *parent = 0);
+ ~ColumnPrimaryKeyPanel();
+
+ bool validate();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+
+ private:
+ void init();
+ void readConstraint();
+
+ Ui::ColumnPrimaryKeyPanel *ui = nullptr;
+
+ private slots:
+ void updateState();
+};
+
+#endif // COLUMNPRIMARYKEYPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui
new file mode 100644
index 0000000..bedabca
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnPrimaryKeyPanel</class>
+ <widget class="QWidget" name="ColumnPrimaryKeyPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>379</width>
+ <height>110</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>110</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="autoIncrCheck">
+ <property name="text">
+ <string>Autoincrement</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="sortOrderWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="sortOrderCheck">
+ <property name="text">
+ <string>Sort order:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="sortOrderCombo">
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="namedCheck">
+ <property name="text">
+ <string>Named constraint:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="conflictWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="conflictCheck">
+ <property name="text">
+ <string>On conflict:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="conflictCombo">
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp
new file mode 100644
index 0000000..7c0f5a8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp
@@ -0,0 +1,99 @@
+#include "columnuniqueandnotnullpanel.h"
+#include "ui_columnuniqueandnotnullpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/keywords.h"
+#include "uiutils.h"
+
+ColumnUniqueAndNotNullPanel::ColumnUniqueAndNotNullPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ColumnUniqueAndNotNullPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ColumnUniqueAndNotNullPanel::~ColumnUniqueAndNotNullPanel()
+{
+ delete ui;
+}
+
+void ColumnUniqueAndNotNullPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ColumnUniqueAndNotNullPanel::init()
+{
+ ui->conflictCombo->addItems(getConflictAlgorithms());
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->conflictCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void ColumnUniqueAndNotNullPanel::readConstraint()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+
+ if (!constr->name.isNull())
+ {
+ ui->namedCheck->setChecked(true);
+ ui->namedEdit->setText(constr->name);
+ }
+
+ if (constr->onConflict != SqliteConflictAlgo::null)
+ {
+ ui->conflictCheck->setChecked(true);
+ ui->conflictCombo->setCurrentText(sqliteConflictAlgo(constr->onConflict));
+ }
+}
+
+void ColumnUniqueAndNotNullPanel::updateState()
+{
+ ui->namedEdit->setEnabled(ui->namedCheck->isChecked());
+ ui->conflictCombo->setEnabled(ui->conflictCheck->isChecked());
+}
+
+
+bool ColumnUniqueAndNotNullPanel::validate()
+{
+ bool nameOk = true;
+ if (ui->namedCheck->isChecked() && ui->namedEdit->text().isEmpty())
+ nameOk = false;
+
+ setValidState(ui->namedEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return nameOk;
+}
+
+void ColumnUniqueAndNotNullPanel::constraintAvailable()
+{
+ if (constraint.isNull())
+ return;
+
+ readConstraint();
+}
+
+void ColumnUniqueAndNotNullPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ storeType();
+
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ if (ui->namedCheck->isChecked())
+ constr->name = ui->namedEdit->text();
+
+ if (ui->conflictCheck->isChecked() && ui->conflictCombo->currentIndex() > -1)
+ constr->onConflict = sqliteConflictAlgo(ui->conflictCombo->currentText());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h
new file mode 100644
index 0000000..95cd5fb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h
@@ -0,0 +1,37 @@
+#ifndef COLUMNUNIQUEANDNOTNULLPANEL_H
+#define COLUMNUNIQUEANDNOTNULLPANEL_H
+
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+
+namespace Ui {
+ class ColumnUniqueAndNotNullPanel;
+}
+
+class GUI_API_EXPORT ColumnUniqueAndNotNullPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnUniqueAndNotNullPanel(QWidget *parent = 0);
+ ~ColumnUniqueAndNotNullPanel();
+
+ bool validate();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+ virtual void storeType() = 0;
+
+ private:
+ void init();
+ void readConstraint();
+
+ Ui::ColumnUniqueAndNotNullPanel *ui = nullptr;
+
+ private slots:
+ void updateState();
+};
+
+#endif // COLUMNUNIQUEANDNOTNULLPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui
new file mode 100644
index 0000000..16efc89
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnUniqueAndNotNullPanel</class>
+ <widget class="QWidget" name="ColumnUniqueAndNotNullPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>82</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="namedCheck">
+ <property name="text">
+ <string>Named constraint:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="conflictWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="conflictCheck">
+ <property name="text">
+ <string>On conflict:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="conflictCombo">
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp
new file mode 100644
index 0000000..da5e00b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp
@@ -0,0 +1,13 @@
+#include "columnuniquepanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+
+ColumnUniquePanel::ColumnUniquePanel(QWidget *parent) :
+ ColumnUniqueAndNotNullPanel(parent)
+{
+}
+
+void ColumnUniquePanel::storeType()
+{
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Column::Constraint::UNIQUE;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h
new file mode 100644
index 0000000..3abf779
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h
@@ -0,0 +1,22 @@
+#ifndef COLUMNUNIQUEPANEL_H
+#define COLUMNUNIQUEPANEL_H
+
+#include "columnuniqueandnotnullpanel.h"
+#include "guiSQLiteStudio_global.h"
+
+namespace Ui {
+ class ColumnUniquePanel;
+}
+
+class GUI_API_EXPORT ColumnUniquePanel : public ColumnUniqueAndNotNullPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ColumnUniquePanel(QWidget *parent = 0);
+
+ protected:
+ void storeType();
+};
+
+#endif // COLUMNUNIQUEPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp
new file mode 100644
index 0000000..adb5e2b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp
@@ -0,0 +1,175 @@
+#include "constraintcheckpanel.h"
+#include "ui_constraintcheckpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/parser.h"
+#include "parser/keywords.h"
+#include "uiutils.h"
+#include <QDebug>
+
+ConstraintCheckPanel::ConstraintCheckPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::ConstraintCheckPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+ConstraintCheckPanel::~ConstraintCheckPanel()
+{
+ delete ui;
+}
+
+void ConstraintCheckPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+
+bool ConstraintCheckPanel::validate()
+{
+ bool nameOk = true;
+ if (ui->namedCheck->isChecked() && ui->namedEdit->text().isEmpty())
+ nameOk = false;
+
+ bool exprOk = !ui->exprEdit->toPlainText().trimmed().isEmpty() &&
+ !ui->exprEdit->haveErrors();
+
+ bool exprCheckedOk = exprOk && ui->exprEdit->isSyntaxChecked();
+
+ setValidState(ui->exprEdit, exprOk, tr("Enter a valid condition."));
+ setValidState(ui->namedEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return exprCheckedOk && nameOk;
+}
+
+void ConstraintCheckPanel::constraintAvailable()
+{
+ if (constraint.isNull())
+ return;
+
+ if (constraint->dialect == Dialect::Sqlite3)
+ {
+ ui->onConflictCheck->setVisible(false);
+ ui->onConflictCombo->setVisible(false);
+ }
+
+ readConstraint();
+ updateVirtualSql();
+}
+
+void ConstraintCheckPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ storeType();
+
+ SqliteExprPtr expr = parseExpression(ui->exprEdit->toPlainText());
+ SqliteExpr* newExpr = new SqliteExpr(*expr.data());
+ newExpr->setParent(constraint.data());
+ storeExpr(newExpr);
+
+ QString name = QString::null;
+ if (ui->namedCheck->isChecked())
+ name = ui->namedEdit->text();
+
+ storeName(name);
+
+ if (constraint->dialect == Dialect::Sqlite2 && ui->onConflictCheck->isChecked())
+ storeConflictAlgo(sqliteConflictAlgo(ui->onConflictCombo->currentText()));
+}
+
+void ConstraintCheckPanel::init()
+{
+ setFocusProxy(ui->exprEdit);
+ ui->exprEdit->setShowLineNumbers(false);
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->exprEdit, SIGNAL(textChanged()), this, SIGNAL(updateValidation()));
+ connect(ui->exprEdit, SIGNAL(errorsChecked(bool)), this, SIGNAL(updateValidation()));
+
+ connect(ui->namedCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->onConflictCheck, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+
+ ui->onConflictCombo->addItems(getConflictAlgorithms());
+
+ updateState();
+}
+
+void ConstraintCheckPanel::readConstraint()
+{
+ SqliteExpr* expr = readExpr();
+ if (expr)
+ ui->exprEdit->setPlainText(expr->detokenize());
+
+ QString name = readName();
+ if (!name.isNull())
+ {
+ ui->namedCheck->setChecked(true);
+ ui->namedEdit->setText(name);
+ }
+
+ SqliteConflictAlgo onConflict = readConflictAlgo();
+ if (constraint->dialect == Dialect::Sqlite2 && onConflict != SqliteConflictAlgo::null)
+ {
+ ui->onConflictCheck->setChecked(true);
+ ui->onConflictCombo->setCurrentText(sqliteConflictAlgo(onConflict));
+ }
+}
+
+void ConstraintCheckPanel::updateVirtualSql()
+{
+ ui->exprEdit->setDb(db);
+
+ SqliteCreateTable* createTable = getCreateTable();
+
+ createTable->rebuildTokens();
+ TokenList tokens = createTable->tokens;
+ int idx = tokens.lastIndexOf(Token::PAR_RIGHT);
+ if (idx == -1)
+ {
+ qWarning() << "CREATE TABLE tokens are invalid while call to ConstraintCheckPanel::updateVirtualSql().";
+ return;
+ }
+
+ TokenList newTokens;
+ newTokens << TokenPtr::create(Token::OPERATOR, ",")
+ << TokenPtr::create(Token::SPACE, " ")
+ << TokenPtr::create(Token::KEYWORD, "CHECK")
+ << TokenPtr::create(Token::SPACE, " ")
+ << TokenPtr::create(Token::PAR_LEFT, "(")
+ << TokenPtr::create(Token::OTHER, "%1")
+ << TokenPtr::create(Token::PAR_RIGHT, ")");
+
+ tokens.insert(idx, newTokens);
+ QString sql = tokens.detokenize();
+
+ ui->exprEdit->setVirtualSqlExpression(sql);
+}
+
+SqliteExprPtr ConstraintCheckPanel::parseExpression(const QString& sql)
+{
+ Parser parser(db->getDialect());
+ SqliteExpr *expr = parser.parseExpr(sql);
+ return SqliteExprPtr(expr);
+}
+
+void ConstraintCheckPanel::updateState()
+{
+ ui->namedEdit->setEnabled(ui->namedCheck->isChecked());
+ ui->onConflictCombo->setEnabled(ui->onConflictCheck->isChecked());
+}
+
+bool ConstraintCheckPanel::validateOnly()
+{
+ ui->exprEdit->checkSyntaxNow();
+ return validate();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h
new file mode 100644
index 0000000..5098880
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h
@@ -0,0 +1,49 @@
+#ifndef CONSTRAINTCHECKPANEL_H
+#define CONSTRAINTCHECKPANEL_H
+
+#include "constraintpanel.h"
+#include "parser/ast/sqliteconflictalgo.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class ConstraintCheckPanel;
+}
+
+class GUI_API_EXPORT ConstraintCheckPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit ConstraintCheckPanel(QWidget *parent = 0);
+ ~ConstraintCheckPanel();
+
+ bool validate();
+ bool validateOnly();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+ virtual SqliteExpr* readExpr() = 0;
+ virtual QString readName() = 0;
+ virtual void storeType() = 0;
+ virtual SqliteConflictAlgo readConflictAlgo() = 0;
+ virtual void storeExpr(SqliteExpr* expr) = 0;
+ virtual void storeName(const QString& name) = 0;
+ virtual void storeConflictAlgo(SqliteConflictAlgo algo) = 0;
+ virtual SqliteCreateTable* getCreateTable() = 0;
+
+ private:
+ void init();
+ void readConstraint();
+ void updateVirtualSql();
+ SqliteExprPtr parseExpression(const QString& sql);
+
+ Ui::ConstraintCheckPanel *ui = nullptr;
+
+ private slots:
+ void updateState();
+};
+
+#endif // CONSTRAINTCHECKPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui
new file mode 100644
index 0000000..58f58e6
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConstraintCheckPanel</class>
+ <widget class="QWidget" name="ConstraintCheckPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>197</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="exprGroup">
+ <property name="title">
+ <string>The expression</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlEditor" name="exprEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="namedCheck">
+ <property name="text">
+ <string>Named constraint:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="onConflictWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="onConflictCheck">
+ <property name="text">
+ <string>On conflict</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="onConflictCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp
new file mode 100644
index 0000000..031fbfe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp
@@ -0,0 +1,96 @@
+#include "constraintpanel.h"
+#include "common/unused.h"
+#include "constraints/tableprimarykeypanel.h"
+#include "constraints/tableforeignkeypanel.h"
+#include "constraints/tableuniquepanel.h"
+#include "constraints/tablecheckpanel.h"
+#include "constraints/columncheckpanel.h"
+#include "constraints/columncollatepanel.h"
+#include "constraints/columndefaultpanel.h"
+#include "constraints/columnforeignkeypanel.h"
+#include "constraints/columnnotnullpanel.h"
+#include "constraints/columnprimarykeypanel.h"
+#include "constraints/columnuniquepanel.h"
+#include <QDebug>
+
+ConstraintPanel::ConstraintPanel(QWidget *parent) :
+ QWidget(parent)
+{
+}
+
+ConstraintPanel::~ConstraintPanel()
+{
+}
+
+void ConstraintPanel::setConstraint(SqliteStatement* stmt)
+{
+ constraint = stmt;
+ constraintAvailable();
+}
+
+void ConstraintPanel::storeDefinition()
+{
+ storeConfiguration();
+ constraint->rebuildTokens();
+}
+
+void ConstraintPanel::setDb(Db* value)
+{
+ db = value;
+}
+
+bool ConstraintPanel::validateOnly()
+{
+ return validate();
+}
+
+ConstraintPanel* ConstraintPanel::produce(SqliteCreateTable::Constraint* constr)
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return new TablePrimaryKeyPanel();
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return new TableUniquePanel();
+ case SqliteCreateTable::Constraint::CHECK:
+ return new TableCheckPanel();
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return new TableForeignKeyPanel();
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ break;
+ }
+
+ qCritical() << "No panel defined in ConstraintPanel::createConstraintPanel()!";
+ Q_ASSERT_X(true, "ConstraintPanel::produce()", "No panel defined!");
+ return nullptr;
+}
+
+ConstraintPanel* ConstraintPanel::produce(SqliteCreateTable::Column::Constraint* constr)
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return new ColumnPrimaryKeyPanel();
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return new ColumnNotNullPanel();
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return new ColumnUniquePanel();
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return new ColumnCheckPanel();
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return new ColumnDefaultPanel();
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return new ColumnCollatePanel();
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return new ColumnForeignKeyPanel();
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+
+ qCritical() << "No panel defined in ConstraintPanel::createConstraintPanel()!";
+ Q_ASSERT_X(true, "ConstraintPanel::produce()", "No panel defined");
+ return nullptr;
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h
new file mode 100644
index 0000000..9f875a9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h
@@ -0,0 +1,79 @@
+#ifndef CONSTRAINTPANEL_H
+#define CONSTRAINTPANEL_H
+
+#include "db/db.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+#include <QPointer>
+
+class GUI_API_EXPORT ConstraintPanel : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit ConstraintPanel(QWidget *parent = 0);
+ virtual ~ConstraintPanel();
+
+ void setConstraint(SqliteStatement* stmt);
+ void storeDefinition();
+ virtual void setDb(Db* value);
+
+ /**
+ * @brief validate Validates panel for correct data filled in.
+ * @return true if the data is valid, or false otherwise.
+ * Apart from returning boolean result it also marks
+ * invalid fields with red color. See validateOnly() description
+ * for details on differences between those two methods.
+ */
+ virtual bool validate() = 0;
+
+ /**
+ * @brief validateOnly Validates panel for correct data filled in.
+ * @return true if the data is valid, or false otherwise.
+ * The difference between validateOnly() and validate() is that validateOnly()
+ * will run all validations immediately (ie. SQL syntax checking
+ * in DEFAULT constraint, etc), while the validate() will wait for
+ * SqlEditor to do the validation in its scheduled time and return
+ * false until the validation isn't done yet.
+ * The validate() should be used when user actually edits the panel,
+ * while validateOnly() is to be used when using ConstraintPanel for validation
+ * of a Constraint object, but not displayed - in that case the validation
+ * result is needed immediately and that's where validateOnly() does its job.
+ * Not every constraint panel has to reimplement this. Most of the constraints
+ * don't work asynchronously and return proper result just from a validate() call.
+ * In that case the default implementation of validateOnly() will do the job.
+ */
+ virtual bool validateOnly();
+
+ static ConstraintPanel* produce(SqliteCreateTable::Constraint* constr);
+ static ConstraintPanel* produce(SqliteCreateTable::Column::Constraint* constr);
+
+ protected:
+ /**
+ * @brief constraintAvailable
+ * This method is called once the constraint object (the member variable)
+ * is available to the panel as well, as the database (the db member).
+ * The implementation should read values from constraint object and put them
+ * to panel's fields, but also initialise any database related data,
+ * like existing collations, etc.
+ */
+ virtual void constraintAvailable() = 0;
+
+ /**
+ * @brief storeConfiguration
+ * The implementation should store all field valies into the constraint object.
+ */
+ virtual void storeConfiguration() = 0;
+
+ Db* db = nullptr;
+ QPointer<SqliteStatement> constraint;
+
+ public slots:
+
+ signals:
+ void updateValidation();
+
+};
+
+#endif // CONSTRAINTPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp
new file mode 100644
index 0000000..72c9d35
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp
@@ -0,0 +1,56 @@
+#include "tablecheckpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/parser.h"
+#include <QDebug>
+
+TableCheckPanel::TableCheckPanel(QWidget *parent) :
+ ConstraintCheckPanel(parent)
+{
+}
+
+SqliteExpr* TableCheckPanel::readExpr()
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ return constr->expr;
+}
+
+QString TableCheckPanel::readName()
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ return constr->name;
+}
+
+void TableCheckPanel::storeType()
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Constraint::CHECK;
+}
+
+SqliteConflictAlgo TableCheckPanel::readConflictAlgo()
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ return constr->onConflict;
+}
+
+void TableCheckPanel::storeExpr(SqliteExpr* expr)
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->expr = expr;
+}
+
+void TableCheckPanel::storeName(const QString& name)
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->name = name;
+}
+
+void TableCheckPanel::storeConflictAlgo(SqliteConflictAlgo algo)
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->onConflict = algo;
+}
+
+SqliteCreateTable* TableCheckPanel::getCreateTable()
+{
+ return dynamic_cast<SqliteCreateTable*>(constraint->parentStatement());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h
new file mode 100644
index 0000000..af9039e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h
@@ -0,0 +1,27 @@
+#ifndef TABLECHECKPANEL_H
+#define TABLECHECKPANEL_H
+
+#include "constraintcheckpanel.h"
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+class GUI_API_EXPORT TableCheckPanel : public ConstraintCheckPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit TableCheckPanel(QWidget *parent = 0);
+
+ protected:
+ SqliteExpr* readExpr();
+ QString readName();
+ void storeType();
+ SqliteConflictAlgo readConflictAlgo();
+ void storeExpr(SqliteExpr* expr);
+ void storeName(const QString& name);
+ void storeConflictAlgo(SqliteConflictAlgo algo);
+ SqliteCreateTable* getCreateTable();
+};
+
+#endif // TABLECHECKPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp
new file mode 100644
index 0000000..60da220
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp
@@ -0,0 +1,418 @@
+#include "tableforeignkeypanel.h"
+#include "ui_tableforeignkeypanel.h"
+#include "schemaresolver.h"
+#include "uiutils.h"
+#include "common/widgetstateindicator.h"
+#include <QDebug>
+#include <QSignalMapper>
+
+TableForeignKeyPanel::TableForeignKeyPanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::TableForeignKeyPanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+TableForeignKeyPanel::~TableForeignKeyPanel()
+{
+ delete ui;
+}
+
+void TableForeignKeyPanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+
+bool TableForeignKeyPanel::validate()
+{
+ bool tableOk = (ui->fkTableCombo->currentIndex() > -1);
+
+ bool columnsOk = false;
+ bool columnsSelected = true;
+ bool idxOk = true;
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ check = qobject_cast<QCheckBox*>(columnsLayout->itemAtPosition(i, 0)->widget());
+ combo = qobject_cast<QComboBox*>(columnsLayout->itemAtPosition(i, 1)->widget());
+ if (check->isChecked())
+ {
+ columnsOk = true;
+
+ idxOk = (combo->currentIndex() > -1);
+ setValidState(combo, idxOk, tr("Pick the foreign column."));
+ if (!idxOk)
+ columnsSelected = false;
+
+ break;
+ }
+ }
+
+ bool nameOk = true;
+ if (ui->namedCheckBox->isChecked() && ui->nameEdit->text().isEmpty())
+ nameOk = false;
+
+ setValidState(ui->fkTableCombo, tableOk, tr("Pick the foreign table."));
+ setValidState(ui->columnsGroup, columnsOk, tr("Select at least one foreign column."));
+ setValidState(ui->nameEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return tableOk && columnsOk && nameOk && columnsSelected;
+}
+
+void TableForeignKeyPanel::setDb(Db* value)
+{
+ ConstraintPanel::setDb(value);
+ ui->sqlite2Warn->setVisible(value->getDialect() == Dialect::Sqlite2);
+}
+
+void TableForeignKeyPanel::constraintAvailable()
+{
+ readTables();
+ buildColumns();
+ readConstraint();
+}
+
+void TableForeignKeyPanel::init()
+{
+ setFocusProxy(ui->fkTableCombo);
+ columnsLayout = new QGridLayout();
+ ui->columnsScrollContents->setLayout(columnsLayout);
+
+ columnSignalMapping = new QSignalMapper(this);
+ connect(columnSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(updateValidation()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFkColumns()));
+ connect(ui->fkTableCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+ connect(ui->onDeleteCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->onUpdateCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->matchCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+
+ ui->deferrableCombo->addItems({
+ "",
+ sqliteDeferrable(SqliteDeferrable::DEFERRABLE),
+ sqliteDeferrable(SqliteDeferrable::NOT_DEFERRABLE)
+ });
+ ui->initiallyCombo->addItems({
+ "",
+ sqliteInitially(SqliteInitially::DEFERRED),
+ sqliteInitially(SqliteInitially::IMMEDIATE),
+ });
+
+ QStringList reactions = {
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::NO_ACTION),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::SET_NULL),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::SET_DEFAULT),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::CASCADE),
+ SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::RESTRICT)
+ };
+ ui->onUpdateCombo->addItems(reactions);
+ ui->onDeleteCombo->addItems(reactions);
+ ui->matchCombo->addItems({"SIMPLE", "FULL", "PARTIAL"});
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void TableForeignKeyPanel::updateState()
+{
+ bool tableSelected = (ui->fkTableCombo->currentIndex() > -1);
+ for (int i = 0; i < totalColumns; i++)
+ updateColumnState(i, tableSelected);
+
+ ui->deferrableCombo->setEnabled(tableSelected);
+ ui->initiallyCombo->setEnabled(tableSelected);
+ ui->namedCheckBox->setEnabled(tableSelected);
+ ui->nameEdit->setEnabled(tableSelected && ui->namedCheckBox->isChecked());
+ ui->onDeleteCheckBox->setEnabled(tableSelected);
+ ui->onUpdateCheckBox->setEnabled(tableSelected);
+ ui->matchCheckBox->setEnabled(tableSelected);
+ ui->onDeleteCombo->setEnabled(tableSelected && ui->onDeleteCheckBox->isChecked());
+ ui->onUpdateCombo->setEnabled(tableSelected && ui->onUpdateCheckBox->isChecked());
+ ui->matchCombo->setEnabled(tableSelected && ui->matchCheckBox->isChecked());
+}
+
+void TableForeignKeyPanel::updateColumnState(int rowIdx, bool tableSelected)
+{
+ QCheckBox* check = qobject_cast<QCheckBox*>(columnsLayout->itemAtPosition(rowIdx, 0)->widget());
+ bool wasEnabled = check->isEnabled();
+ check->setEnabled(tableSelected);
+
+ QComboBox* combo = qobject_cast<QComboBox*>(columnsLayout->itemAtPosition(rowIdx, 1)->widget());
+ combo->setEnabled(tableSelected && check->isChecked());
+
+ if (!wasEnabled && check->isEnabled())
+ {
+ // Automatically set matching column
+ int idx = fkColumnsModel.stringList().indexOf(check->text());
+ if (idx > -1)
+ combo->setCurrentIndex(idx);
+ }
+}
+
+void TableForeignKeyPanel::updateColumnState(int rowIdx)
+{
+ bool tableSelected = (ui->fkTableCombo->currentIndex() > -1);
+ updateColumnState(rowIdx, tableSelected);
+}
+
+void TableForeignKeyPanel::updateFkColumns()
+{
+ QStringList columns;
+ if (ui->fkTableCombo->currentIndex() == -1)
+ {
+ fkColumnsModel.setStringList(columns);
+ updateState();
+ return;
+ }
+
+ SchemaResolver resolver(db);
+ columns = resolver.getTableColumns(ui->fkTableCombo->currentText()); // TODO named db attach not supported
+ fkColumnsModel.setStringList(columns);
+}
+
+void TableForeignKeyPanel::buildColumns()
+{
+ totalColumns = 0;
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable* createTable = dynamic_cast<SqliteCreateTable*>(constraint->parentStatement());
+ int row = 0;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ buildColumn(column, row++);
+}
+
+void TableForeignKeyPanel::buildColumn(SqliteCreateTable::Column* column, int row)
+{
+ int col = 0;
+
+ QCheckBox* check = new QCheckBox(column->name);
+ columnsLayout->addWidget(check, row, col++);
+ columnSignalMapping->setMapping(check, row);
+ connect(check, SIGNAL(toggled(bool)), columnSignalMapping, SLOT(map()));
+ connect(check, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+
+ QComboBox* fkCol = new QComboBox();
+ fkCol->setToolTip(tr("Foreign column", "table constraints"));
+ fkCol->setModel(&fkColumnsModel);
+ columnsLayout->addWidget(fkCol, row, col++);
+ connect(fkCol, SIGNAL(currentIndexChanged(int)), this, SIGNAL(updateValidation()));
+
+ totalColumns++;
+
+ updateColumnState(row);
+}
+
+void TableForeignKeyPanel::readConstraint()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ if (!constr->foreignKey)
+ return;
+
+ if (!constr->foreignKey->foreignTable.isNull())
+ ui->fkTableCombo->setCurrentText(constr->foreignKey->foreignTable);
+
+ foreach (SqliteForeignKey::Condition* condition, constr->foreignKey->conditions)
+ readCondition(condition);
+
+ ui->deferrableCombo->setCurrentText(sqliteDeferrable(constr->foreignKey->deferrable));
+ ui->initiallyCombo->setCurrentText(sqliteInitially(constr->foreignKey->initially));
+
+ // Name
+ if (!constr->name.isNull())
+ {
+ ui->namedCheckBox->setChecked(true);
+ ui->nameEdit->setText(constr->name);
+ }
+
+ // Columns
+ int idx;
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ SqliteIndexedColumn* localCol = nullptr;
+ SqliteIndexedColumn* foreignCol = nullptr;
+ int i = 0;
+ foreach (localCol, constr->indexedColumns)
+ {
+ // Foreign col
+ if (i < constr->foreignKey->indexedColumns.size())
+ foreignCol = constr->foreignKey->indexedColumns[i];
+ else
+ foreignCol = nullptr;
+
+ i++;
+
+ // Column index
+ idx = getColumnIndex(localCol->name);
+ if (idx < 0)
+ continue;
+
+ // Column states
+ check = dynamic_cast<QCheckBox*>(columnsLayout->itemAtPosition(idx, 0)->widget());
+ check->setChecked(true);
+
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(idx, 1)->widget());
+ if (foreignCol)
+ combo->setCurrentText(foreignCol->name);
+ else if (fkColumnsModel.stringList().contains(localCol->name))
+ combo->setCurrentText(localCol->name);
+ else
+ combo->setCurrentIndex(-1);
+ }
+}
+
+void TableForeignKeyPanel::readCondition(SqliteForeignKey::Condition* condition)
+{
+ switch (condition->action)
+ {
+ case SqliteForeignKey::Condition::UPDATE:
+ ui->onUpdateCheckBox->setChecked(true);
+ ui->onUpdateCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ case SqliteForeignKey::Condition::INSERT:
+ // INSERT is not officially supported.
+ break;
+ case SqliteForeignKey::Condition::DELETE:
+ ui->onDeleteCheckBox->setChecked(true);
+ ui->onDeleteCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ case SqliteForeignKey::Condition::MATCH:
+ ui->matchCheckBox->setChecked(true);
+ ui->matchCombo->setCurrentText(SqliteForeignKey::Condition::toString(condition->reaction));
+ break;
+ }
+}
+
+void TableForeignKeyPanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ // Type
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Constraint::FOREIGN_KEY;
+
+ // Cleanup & initial setup
+ if (constr->foreignKey)
+ delete constr->foreignKey;
+
+ foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ delete idxCol;
+
+ constr->indexedColumns.clear();
+
+ constr->foreignKey = new SqliteForeignKey();
+ constr->foreignKey->setParent(constr);
+
+ // Foreign table
+ constr->foreignKey->foreignTable = ui->fkTableCombo->currentText();
+
+ // Columns
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ SqliteIndexedColumn* idxCol = nullptr;
+ QString name;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ // Local column
+ check = dynamic_cast<QCheckBox*>(columnsLayout->itemAtPosition(i, 0)->widget());
+ if (!check->isChecked())
+ continue;
+
+ idxCol = new SqliteIndexedColumn(check->text());
+ idxCol->setParent(constr);
+ constr->indexedColumns << idxCol;
+
+ // Foreign column
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(i, 1)->widget());
+
+ idxCol = new SqliteIndexedColumn(combo->currentText());
+ idxCol->setParent(constr->foreignKey);
+ constr->foreignKey->indexedColumns << idxCol;
+ }
+
+ // Actions/reactions
+ if (ui->onDeleteCheckBox->isChecked())
+ storeCondition(SqliteForeignKey::Condition::DELETE, ui->onDeleteCombo->currentText());
+
+ if (ui->onUpdateCheckBox->isChecked())
+ storeCondition(SqliteForeignKey::Condition::UPDATE, ui->onDeleteCombo->currentText());
+
+ if (ui->matchCheckBox->isChecked())
+ storeMatchCondition(ui->matchCombo->currentText());
+
+ // Deferred/initially
+ constr->foreignKey->deferrable = sqliteDeferrable(ui->deferrableCombo->currentText());
+ constr->foreignKey->initially = sqliteInitially(ui->initiallyCombo->currentText());
+
+ // Name
+ constr->name = QString::null;
+ if (ui->namedCheckBox->isChecked())
+ constr->name = ui->nameEdit->text();
+}
+
+void TableForeignKeyPanel::storeCondition(SqliteForeignKey::Condition::Action action, const QString& reaction)
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+
+ SqliteForeignKey::Condition* condition = new SqliteForeignKey::Condition(
+ action,
+ SqliteForeignKey::Condition::toEnum(reaction)
+ );
+ condition->setParent(constr->foreignKey);
+ constr->foreignKey->conditions << condition;
+}
+
+void TableForeignKeyPanel::storeMatchCondition(const QString& reaction)
+{
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+
+ SqliteForeignKey::Condition* condition = new SqliteForeignKey::Condition(reaction);
+ condition->setParent(constr->foreignKey);
+ constr->foreignKey->conditions << condition;
+}
+
+void TableForeignKeyPanel::readTables()
+{
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(true);
+ QStringList tables = resolver.getTables(); // TODO named db attach not supported
+
+ SqliteCreateTable* createTable = dynamic_cast<SqliteCreateTable*>(constraint->parentStatement());
+ tables.removeOne(createTable->table);
+
+ tables.sort(Qt::CaseInsensitive);
+
+ ui->fkTableCombo->addItems(tables);
+ ui->fkTableCombo->setCurrentIndex(-1);
+}
+
+int TableForeignKeyPanel::getColumnIndex(const QString& colName)
+{
+ QWidget* item = nullptr;
+ QCheckBox* cb = nullptr;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ item = columnsLayout->itemAtPosition(i, 0)->widget();
+ cb = qobject_cast<QCheckBox*>(item);
+ if (cb->text().compare(colName, Qt::CaseInsensitive) == 0)
+ return i;
+ }
+ return -1;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h
new file mode 100644
index 0000000..cecc04a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h
@@ -0,0 +1,57 @@
+#ifndef TABLEFOREIGNKEYPANEL_H
+#define TABLEFOREIGNKEYPANEL_H
+
+#include "constraintpanel.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStringListModel>
+#include <QWidget>
+
+namespace Ui {
+ class TableForeignKeyPanel;
+}
+
+class QGridLayout;
+class QSignalMapper;
+
+class GUI_API_EXPORT TableForeignKeyPanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit TableForeignKeyPanel(QWidget *parent = 0);
+ ~TableForeignKeyPanel();
+
+ bool validate();
+ void setDb(Db* value);
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ void storeConfiguration();
+
+ private:
+ void init();
+ void buildColumns();
+ void buildColumn(SqliteCreateTable::Column* column, int row);
+ void readConstraint();
+ void readTables();
+ void readCondition(SqliteForeignKey::Condition* condition);
+ int getColumnIndex(const QString& colName);
+ void storeCondition(SqliteForeignKey::Condition::Action action, const QString& reaction);
+ void storeMatchCondition(const QString& reaction);
+
+ Ui::TableForeignKeyPanel *ui = nullptr;
+ QGridLayout* columnsLayout = nullptr;
+ int totalColumns = 0;
+ QStringListModel fkColumnsModel;
+ QSignalMapper* columnSignalMapping = nullptr;
+
+ private slots:
+ void updateState();
+ void updateColumnState(int rowIdx, bool tableSelected);
+ void updateColumnState(int rowIdx);
+ void updateFkColumns();
+};
+
+#endif // TABLEFOREIGNKEYPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui
new file mode 100644
index 0000000..8917381
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TableForeignKeyPanel</class>
+ <widget class="QWidget" name="TableForeignKeyPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>400</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="fkTableWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="fkTableLabel">
+ <property name="text">
+ <string>Foreign table:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="fkTableCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="sqlite2Warn">
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>SQLite 2 does not support foreign keys officially,
+but it's okay to use them anyway.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="margin">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="columnsGroup">
+ <property name="title">
+ <string>Columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="columnsHeader" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="colHdrLocal">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Local column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="colHdrForeign">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Foreign column</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="columnsArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="columnsScrollContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>362</width>
+ <height>25</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="reactionsGroup">
+ <property name="title">
+ <string>Reactions</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="onUpdateCheckBox">
+ <property name="text">
+ <string>ON UPDATE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="onUpdateCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="onDeleteCheckBox">
+ <property name="text">
+ <string>ON DELETE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="matchCheckBox">
+ <property name="text">
+ <string>MATCH</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="onDeleteCombo"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="matchCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="deferredGroup">
+ <property name="title">
+ <string>Deferred foreign key</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QComboBox" name="deferrableCombo"/>
+ </item>
+ <item>
+ <widget class="QComboBox" name="initiallyCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QCheckBox" name="namedCheckBox">
+ <property name="text">
+ <string>Named constraint</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nameEdit">
+ <property name="placeholderText">
+ <string>Constraint name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>fkTableCombo</tabstop>
+ <tabstop>columnsArea</tabstop>
+ <tabstop>onUpdateCheckBox</tabstop>
+ <tabstop>onUpdateCombo</tabstop>
+ <tabstop>onDeleteCheckBox</tabstop>
+ <tabstop>onDeleteCombo</tabstop>
+ <tabstop>matchCheckBox</tabstop>
+ <tabstop>matchCombo</tabstop>
+ <tabstop>deferrableCombo</tabstop>
+ <tabstop>initiallyCombo</tabstop>
+ <tabstop>namedCheckBox</tabstop>
+ <tabstop>nameEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp
new file mode 100644
index 0000000..f2a0ada
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp
@@ -0,0 +1,299 @@
+#include "tablepkanduniquepanel.h"
+#include "ui_tablepkanduniquepanel.h"
+#include "parser/keywords.h"
+#include "schemaresolver.h"
+#include "uiutils.h"
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QLabel>
+#include <QSignalMapper>
+#include <QDebug>
+#include <QScrollBar>
+
+TablePrimaryKeyAndUniquePanel::TablePrimaryKeyAndUniquePanel(QWidget *parent) :
+ ConstraintPanel(parent),
+ ui(new Ui::TablePrimaryKeyAndUniquePanel)
+{
+ ui->setupUi(this);
+ init();
+}
+
+TablePrimaryKeyAndUniquePanel::~TablePrimaryKeyAndUniquePanel()
+{
+ delete ui;
+}
+
+void TablePrimaryKeyAndUniquePanel::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void TablePrimaryKeyAndUniquePanel::init()
+{
+ columnsLayout = new QGridLayout();
+ ui->scrollAreaWidgetContents->setLayout(columnsLayout);
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+ connect(ui->namedLineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(updateValidation()));
+
+ ui->conflictComboBox->addItems(getConflictAlgorithms());
+
+ columnSignalMapping = new QSignalMapper(this);
+ connect(columnSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ connect(ui->namedCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->conflictCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ updateState();
+}
+
+void TablePrimaryKeyAndUniquePanel::readCollations()
+{
+ SchemaResolver resolver(db);
+ QStringList collList = resolver.getCollations();
+
+ if (collList.size() > 0)
+ collList.prepend("");
+
+ collations.setStringList(collList);
+}
+
+void TablePrimaryKeyAndUniquePanel::buildColumn(SqliteCreateTable::Column* column, int row)
+{
+ int col = 0;
+
+ QCheckBox* check = new QCheckBox(column->name);
+ columnsLayout->addWidget(check, row, col++);
+ columnSignalMapping->setMapping(check, row);
+ connect(check, SIGNAL(toggled(bool)), columnSignalMapping, SLOT(map()));
+ connect(check, SIGNAL(toggled(bool)), this, SIGNAL(updateValidation()));
+
+ QComboBox* collation = nullptr;
+ if (!constraint.isNull() && constraint->dialect == Dialect::Sqlite3)
+ {
+ collation = new QComboBox();
+ collation->setMaximumWidth(ui->colHdrCollation->width());
+ collation->setMinimumWidth(ui->colHdrCollation->width() - ui->scrollArea->verticalScrollBar()->width());
+ collation->setEditable(true);
+ collation->lineEdit()->setPlaceholderText(tr("Collate", "table constraints"));
+ collation->setModel(&collations);
+ columnsLayout->addWidget(collation, row, col++);
+ }
+
+ QComboBox* sortOrder = new QComboBox();
+ sortOrder->setFixedWidth(ui->colHdrSort->width());
+ sortOrder->setToolTip(tr("Sort order", "table constraints"));
+ columnsLayout->addWidget(sortOrder, row, col++);
+
+ QStringList sortList = {"", sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)};
+ sortOrder->addItems(sortList);
+
+ totalColumns++;
+
+ updateColumnState(row);
+}
+
+int TablePrimaryKeyAndUniquePanel::getColumnIndex(const QString& colName)
+{
+ QWidget* item = nullptr;
+ QCheckBox* cb = nullptr;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ item = columnsLayout->itemAtPosition(i, 0)->widget();
+ cb = qobject_cast<QCheckBox*>(item);
+ if (cb->text().compare(colName, Qt::CaseInsensitive) == 0)
+ return i;
+ }
+ return -1;
+}
+
+void TablePrimaryKeyAndUniquePanel::updateColumnState(int colIdx)
+{
+ QWidget* item = columnsLayout->itemAtPosition(colIdx, 0)->widget();
+ QCheckBox* cb = qobject_cast<QCheckBox*>(item);
+ bool enable = cb->isChecked();
+
+ item = columnsLayout->itemAtPosition(colIdx, 1)->widget();
+ qobject_cast<QComboBox*>(item)->setEnabled(enable);
+
+ if (!constraint.isNull() && constraint->dialect == Dialect::Sqlite3)
+ {
+ item = columnsLayout->itemAtPosition(colIdx, 2)->widget();
+ qobject_cast<QComboBox*>(item)->setEnabled(enable);
+ }
+
+ updateState();
+}
+
+void TablePrimaryKeyAndUniquePanel::updateState()
+{
+ ui->namedLineEdit->setEnabled(ui->namedCheckBox->isChecked());
+ ui->conflictComboBox->setEnabled(ui->conflictCheckBox->isChecked());
+}
+
+void TablePrimaryKeyAndUniquePanel::constraintAvailable()
+{
+ readCollations();
+ buildColumns();
+ readConstraint();
+}
+
+bool TablePrimaryKeyAndUniquePanel::validate()
+{
+ bool countOk = false;
+ QWidget* item = nullptr;
+ QCheckBox* cb = nullptr;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ item = columnsLayout->itemAtPosition(i, 0)->widget();
+ cb = qobject_cast<QCheckBox*>(item);
+ if (cb->isChecked())
+ {
+ countOk = true;
+ break;
+ }
+ }
+
+ bool nameOk = true;
+ if (ui->namedCheckBox->isChecked() && ui->namedLineEdit->text().isEmpty())
+ nameOk = false;
+
+ setValidState(ui->groupBox, countOk, tr("Select at least one column."));
+ setValidState(ui->namedLineEdit, nameOk, tr("Enter a name of the constraint."));
+
+ return countOk && nameOk;
+}
+
+
+void TablePrimaryKeyAndUniquePanel::storeConfiguration()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+
+ // Name
+ constr->name = QString::null;
+ if (ui->namedCheckBox->isChecked())
+ constr->name = ui->namedLineEdit->text();
+
+ // On conflict
+ if (ui->conflictCheckBox->isChecked())
+ constr->onConflict = sqliteConflictAlgo(ui->conflictComboBox->currentText());
+
+ // Columns
+ foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ delete idxCol;
+
+ constr->indexedColumns.clear();
+
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ SqliteIndexedColumn* idxCol = nullptr;
+ QString name;
+ QString collate;
+ SqliteSortOrder sortOrder;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ check = dynamic_cast<QCheckBox*>(columnsLayout->itemAtPosition(i, 0)->widget());
+ if (!check->isChecked())
+ continue;
+
+ name = check->text();
+
+ if (constr->dialect == Dialect::Sqlite3)
+ {
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(i, 1)->widget());
+ collate = combo->currentText();
+ if (collate.isEmpty())
+ collate = QString::null;
+
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(i, 2)->widget());
+ sortOrder = sqliteSortOrder(combo->currentText());
+ }
+ else
+ {
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(i, 1)->widget());
+ sortOrder = sqliteSortOrder(combo->currentText());
+ }
+
+ idxCol = new SqliteIndexedColumn(name, collate, sortOrder);
+ idxCol->setParent(constr);
+ constr->indexedColumns << idxCol;
+ }
+}
+
+void TablePrimaryKeyAndUniquePanel::readConstraint()
+{
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+
+ // Name
+ if (!constr->name.isNull())
+ {
+ ui->namedCheckBox->setChecked(true);
+ ui->namedLineEdit->setText(constr->name);
+ }
+
+ // On conflict
+ if (constr->onConflict != SqliteConflictAlgo::null)
+ {
+ ui->conflictCheckBox->setChecked(true);
+ ui->conflictComboBox->setCurrentText(sqliteConflictAlgo(constr->onConflict));
+ }
+
+ // Columns
+ int idx;
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ {
+ idx = getColumnIndex(idxCol->name);
+ if (idx < 0)
+ continue;
+
+ check = dynamic_cast<QCheckBox*>(columnsLayout->itemAtPosition(idx, 0)->widget());
+ check->setChecked(true);
+
+ if (constr->dialect == Dialect::Sqlite3)
+ {
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(idx, 1)->widget());
+ combo->setCurrentText(idxCol->collate);
+
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(idx, 2)->widget());
+ combo->setCurrentText(sqliteSortOrder(idxCol->sortOrder));
+ }
+ else
+ {
+ combo = dynamic_cast<QComboBox*>(columnsLayout->itemAtPosition(idx, 1)->widget());
+ combo->setCurrentText(sqliteSortOrder(idxCol->sortOrder));
+ }
+ }
+
+ if (constr->dialect == Dialect::Sqlite2)
+ {
+ ui->colHdrCollation->setVisible(false);
+ }
+}
+
+void TablePrimaryKeyAndUniquePanel::buildColumns()
+{
+ totalColumns = 0;
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable* createTable = dynamic_cast<SqliteCreateTable*>(constraint->parentStatement());
+ int row = 0;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ buildColumn(column, row++);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h
new file mode 100644
index 0000000..1092115
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h
@@ -0,0 +1,63 @@
+#ifndef PRIMARYKEYANDUNIQUEPANEL_H
+#define PRIMARYKEYANDUNIQUEPANEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "constraintpanel.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStringListModel>
+
+namespace Ui {
+ class TablePrimaryKeyAndUniquePanel;
+}
+
+class QGridLayout;
+class QSignalMapper;
+
+class GUI_API_EXPORT TablePrimaryKeyAndUniquePanel : public ConstraintPanel
+{
+ Q_OBJECT
+
+ public:
+ explicit TablePrimaryKeyAndUniquePanel(QWidget *parent = 0);
+ ~TablePrimaryKeyAndUniquePanel();
+
+ bool validate();
+ void storeConfiguration();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void constraintAvailable();
+ virtual void readConstraint();
+
+ Ui::TablePrimaryKeyAndUniquePanel *ui = nullptr;
+ QGridLayout* columnsLayout = nullptr;
+
+ /**
+ * @brief totalColumns
+ * Used to count columns created by buildColumns().
+ * It's a workaround for QGridLayout::rowCount(), which doesn't return
+ * number of visible rows, but instead a number of allocated rows,
+ * which isn't useful in any way.
+ * This variable lets us to avoid asking for widgets from rows
+ * that does not exist.
+ */
+ int totalColumns = 0;
+
+ private:
+ void init();
+ void readCollations();
+ void buildColumns();
+ void buildColumn(SqliteCreateTable::Column* column, int row);
+ int getColumnIndex(const QString& colName);
+
+ QStringListModel collations;
+ QSignalMapper* columnSignalMapping = nullptr;
+
+ private slots:
+ void updateColumnState(int colIdx);
+
+ protected slots:
+ virtual void updateState();
+};
+
+#endif // PRIMARYKEYANDUNIQUEPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui
new file mode 100644
index 0000000..9453d3c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TablePrimaryKeyAndUniquePanel</class>
+ <widget class="QWidget" name="TablePrimaryKeyAndUniquePanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>386</width>
+ <height>269</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>PrimaryKeyOrUniquePanel</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="columnsHeader" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="colHdrColumn">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="colHdrCollation">
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Collation</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="colHdrSort">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>364</width>
+ <height>132</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="autoIncrCheckBox">
+ <property name="toolTip">
+ <string>Valid only for a single column with INTEGER data type</string>
+ </property>
+ <property name="text">
+ <string>Autoincrement</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="namedCheckBox">
+ <property name="text">
+ <string>Named constraint</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedLineEdit">
+ <property name="placeholderText">
+ <string>Constraint name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="conflictWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="conflictCheckBox">
+ <property name="text">
+ <string>On conflict</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="conflictComboBox">
+ <property name="maximumSize">
+ <size>
+ <width>140</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>scrollArea</tabstop>
+ <tabstop>autoIncrCheckBox</tabstop>
+ <tabstop>namedCheckBox</tabstop>
+ <tabstop>namedLineEdit</tabstop>
+ <tabstop>conflictCheckBox</tabstop>
+ <tabstop>conflictComboBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp
new file mode 100644
index 0000000..c538990
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp
@@ -0,0 +1,83 @@
+#include "tableprimarykeypanel.h"
+#include "ui_tablepkanduniquepanel.h"
+#include <QDebug>
+
+TablePrimaryKeyPanel::TablePrimaryKeyPanel(QWidget *parent) :
+ TablePrimaryKeyAndUniquePanel(parent)
+{
+}
+
+void TablePrimaryKeyPanel::storeConfiguration()
+{
+ TablePrimaryKeyAndUniquePanel::storeConfiguration();
+
+ if (constraint.isNull())
+ return;
+
+ // Type
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Constraint::PRIMARY_KEY;
+
+ // Autoincr
+ constr->autoincrKw = ui->autoIncrCheckBox->isChecked();
+}
+
+
+void TablePrimaryKeyPanel::readConstraint()
+{
+ TablePrimaryKeyAndUniquePanel::readConstraint();
+
+ if (constraint.isNull())
+ return;
+
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+
+ // Autoincr
+ if (constr->autoincrKw)
+ ui->autoIncrCheckBox->setChecked(true);
+}
+
+void TablePrimaryKeyPanel::updateState()
+{
+ TablePrimaryKeyAndUniquePanel::updateState();
+
+ // Autoincr
+ QStringList columns;
+ QWidget* item = nullptr;
+ QCheckBox* cb = nullptr;
+ for (int i = 0; i < totalColumns; i++)
+ {
+ item = columnsLayout->itemAtPosition(i, 0)->widget();
+ cb = qobject_cast<QCheckBox*>(item);
+ if (cb->isChecked())
+ columns << cb->text();
+ }
+
+ if (columns.size() != 1)
+ {
+ ui->autoIncrCheckBox->setChecked(false);
+ ui->autoIncrCheckBox->setEnabled(false);
+ return;
+ }
+
+ SqliteCreateTable* createTable = dynamic_cast<SqliteCreateTable*>(constraint->parentStatement());
+ QString colName = columns.first();
+ SqliteCreateTable::Column* column = createTable->getColumn(colName);
+ if (!column)
+ {
+ qCritical() << "Could not find column when checking for AUTOINCREMENT checkbox state:" << colName
+ << "\nCreateTable statement:" << createTable->detokenize();
+ ui->autoIncrCheckBox->setChecked(false);
+ ui->autoIncrCheckBox->setEnabled(false);
+ return;
+ }
+
+ if (!column->type || column->type->detokenize().trimmed().compare("INTEGER", Qt::CaseInsensitive) != 0)
+ {
+ ui->autoIncrCheckBox->setChecked(false);
+ ui->autoIncrCheckBox->setEnabled(false);
+ return;
+ }
+
+ ui->autoIncrCheckBox->setEnabled(true);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h
new file mode 100644
index 0000000..d5e2e59
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h
@@ -0,0 +1,23 @@
+#ifndef PRIMARYKEYPANEL_H
+#define PRIMARYKEYPANEL_H
+
+#include "tablepkanduniquepanel.h"
+#include "guiSQLiteStudio_global.h"
+
+class GUI_API_EXPORT TablePrimaryKeyPanel : public TablePrimaryKeyAndUniquePanel
+{
+ Q_OBJECT
+
+ public:
+ explicit TablePrimaryKeyPanel(QWidget *parent = 0);
+
+ void storeConfiguration();
+
+ private:
+ void readConstraint();
+
+ private slots:
+ void updateState();
+};
+
+#endif // PRIMARYKEYPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui
new file mode 100644
index 0000000..76febc9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PrimaryKeyPanel</class>
+ <widget class="QWidget" name="PrimaryKeyPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>386</width>
+ <height>269</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Column</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>364</width>
+ <height>147</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="autoIncrCheckBox">
+ <property name="text">
+ <string>Autoincrement</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="namedWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="namedCheckBox">
+ <property name="text">
+ <string>Named constraint</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="namedLineEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="conflictWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="conflictCheckBox">
+ <property name="text">
+ <string>On conflict</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <property name="maximumSize">
+ <size>
+ <width>140</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp
new file mode 100644
index 0000000..14b298e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp
@@ -0,0 +1,21 @@
+#include "tableuniquepanel.h"
+#include "ui_tablepkanduniquepanel.h"
+
+TableUniquePanel::TableUniquePanel(QWidget *parent) :
+ TablePrimaryKeyAndUniquePanel(parent)
+{
+ ui->autoIncrCheckBox->setVisible(false);
+}
+
+
+void TableUniquePanel::storeConfiguration()
+{
+ TablePrimaryKeyAndUniquePanel::storeConfiguration();
+
+ if (constraint.isNull())
+ return;
+
+ // Type
+ SqliteCreateTable::Constraint* constr = dynamic_cast<SqliteCreateTable::Constraint*>(constraint.data());
+ constr->type = SqliteCreateTable::Constraint::UNIQUE;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h
new file mode 100644
index 0000000..bd77910
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h
@@ -0,0 +1,16 @@
+#ifndef TABLEUNIQUEPANEL_H
+#define TABLEUNIQUEPANEL_H
+
+#include "tablepkanduniquepanel.h"
+#include "guiSQLiteStudio_global.h"
+
+class GUI_API_EXPORT TableUniquePanel : public TablePrimaryKeyAndUniquePanel
+{
+ Q_OBJECT
+ public:
+ explicit TableUniquePanel(QWidget *parent = 0);
+
+ void storeConfiguration();
+};
+
+#endif // TABLEUNIQUEPANEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h b/SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h
new file mode 100644
index 0000000..0ff734e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h
@@ -0,0 +1,21 @@
+#ifndef CUSTOMCONFIGWIDGETPLUGIN_H
+#define CUSTOMCONFIGWIDGETPLUGIN_H
+
+#include "plugins/plugin.h"
+#include "guiSQLiteStudio_global.h"
+#include <QVariant>
+
+class CfgEntry;
+class QWidget;
+
+class GUI_API_EXPORT CustomConfigWidgetPlugin : public virtual Plugin
+{
+ public:
+ virtual bool isConfigForWidget(CfgEntry* key, QWidget* widget) = 0;
+ virtual void applyConfigToWidget(CfgEntry* key, QWidget* widget, const QVariant &value) = 0;
+ virtual QVariant getWidgetConfigValue(QWidget* widget, bool& ok) = 0;
+ virtual const char* getModifiedNotifier() const = 0;
+ virtual QString getFilterString(QWidget* widget) const = 0;
+};
+
+#endif // CUSTOMCONFIGWIDGETPLUGIN_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp
new file mode 100644
index 0000000..c732b6d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp
@@ -0,0 +1,460 @@
+#include "sqlqueryitem.h"
+#include "common/utils_sql.h"
+#include "sqlquerymodel.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include <QDate>
+#include <QDebug>
+
+SqlQueryItem::SqlQueryItem(QObject *parent) :
+ QObject(parent)
+{
+ setUncommited(false);
+ setCommitingError(false);
+ setRowId(RowId());
+ setColumn(nullptr);
+}
+
+SqlQueryItem::SqlQueryItem(const SqlQueryItem &item)
+ : QObject(item.QObject::parent()), QStandardItem(item)
+{
+}
+
+QStandardItem *SqlQueryItem::clone() const
+{
+ return new SqlQueryItem(*this);
+}
+
+RowId SqlQueryItem::getRowId() const
+{
+ return QStandardItem::data(DataRole::ROWID).toHash();
+}
+
+void SqlQueryItem::setRowId(const RowId& rowId)
+{
+ QStandardItem::setData(rowId, DataRole::ROWID);
+}
+
+bool SqlQueryItem::isUncommited() const
+{
+ return QStandardItem::data(DataRole::UNCOMMITED).toBool();
+}
+
+void SqlQueryItem::setUncommited(bool uncommited)
+{
+ QStandardItem::setData(QVariant(uncommited), DataRole::UNCOMMITED);
+ if (!uncommited)
+ {
+ setOldValue(QVariant());
+ setCommitingError(false);
+ }
+}
+
+void SqlQueryItem::rollback()
+{
+ setValue(getOldValue(), true, true);
+ setUncommited(false);
+ setDeletedRow(false);
+}
+
+bool SqlQueryItem::isCommitingError() const
+{
+ return QStandardItem::data(DataRole::COMMITING_ERROR).toBool();
+}
+
+void SqlQueryItem::setCommitingError(bool isError)
+{
+ QStandardItem::setData(QVariant(isError), DataRole::COMMITING_ERROR);
+}
+
+bool SqlQueryItem::isNewRow() const
+{
+ return QStandardItem::data(DataRole::NEW_ROW).toBool();
+}
+
+void SqlQueryItem::setNewRow(bool isNew)
+{
+ QStandardItem::setData(QVariant(isNew), DataRole::NEW_ROW);
+}
+
+bool SqlQueryItem::isJustInsertedWithOutRowId() const
+{
+ return QStandardItem::data(DataRole::JUST_INSERTED_WITHOUT_ROWID).toBool();
+}
+
+void SqlQueryItem::setJustInsertedWithOutRowId(bool justInsertedWithOutRowId)
+{
+ QStandardItem::setData(QVariant(justInsertedWithOutRowId), DataRole::JUST_INSERTED_WITHOUT_ROWID);
+}
+
+bool SqlQueryItem::isDeletedRow() const
+{
+ return QStandardItem::data(DataRole::DELETED).toBool();
+}
+
+void SqlQueryItem::setDeletedRow(bool isDeleted)
+{
+ if (isDeleted)
+ setOldValue(getValue());
+
+ QStandardItem::setData(QVariant(isDeleted), DataRole::DELETED);
+}
+
+QVariant SqlQueryItem::getValue() const
+{
+ return QStandardItem::data(DataRole::VALUE);
+}
+
+void SqlQueryItem::setValue(const QVariant &value, bool limited, bool loadedFromDb)
+{
+ QVariant newValue = adjustVariantType(value);
+ QVariant origValue = getValue();
+
+ // It's modified when:
+ // - original and new value is different (value or NULL status), while it's not loading from DB
+ // - this item was already marked as uncommited
+ bool modified = (
+ (
+ newValue != origValue ||
+ origValue.isNull() != newValue.isNull()
+ ) &&
+ !loadedFromDb
+ ) ||
+ isUncommited();
+
+ if (modified && !getOldValue().isValid())
+ setOldValue(origValue);
+
+ // This is a workaround for an issue in Qt, that uses operator== to compare values in QStandardItem::setData().
+ // If the old value is null and the new value is empty, then operator == returns true, which is a lie.
+ // We need to trick QStandardItem to force updating the value. We feed it with any non-empty value,
+ // then we can set whatever value we want and it should be updated (or not, if it's truly the same value).
+ QStandardItem::setData("x", DataRole::VALUE);
+
+ QStandardItem::setData(newValue, DataRole::VALUE);
+ setLimitedValue(limited);
+ setUncommited(modified);
+
+ // Value for display (in a cell) will always be limited, for performance reasons
+ QStandardItem::setData("x", DataRole::VALUE_FOR_DISPLAY); // the same trick as with the DataRole::VALUE
+ if (!limited)
+ {
+ int theLimit = SqlQueryModel::getCellDataLengthLimit();
+ switch (value.type())
+ {
+ case QVariant::ByteArray:
+ {
+ QByteArray newBytes = newValue.toByteArray();
+ if (newBytes.size() > theLimit)
+ {
+ newBytes.resize(theLimit);
+ setValueForDisplay(newBytes);
+ }
+ else
+ setValueForDisplay(newValue);
+
+ break;
+ }
+ case QVariant::String:
+ {
+ QString newString = newValue.toString();
+ if (newString.size() > theLimit)
+ {
+ newString.resize(theLimit);
+ setValueForDisplay(newString);
+ }
+ else
+ setValueForDisplay(newValue);
+
+ break;
+ }
+ default:
+ setValueForDisplay(newValue);
+ }
+ }
+ else
+ setValueForDisplay(newValue);
+
+ if (modified && getModel())
+ getModel()->itemValueEdited(this);
+}
+
+bool SqlQueryItem::isLimitedValue() const
+{
+ return QStandardItem::data(DataRole::LIMITED_VALUE).toBool();
+}
+
+QVariant SqlQueryItem::getOldValue() const
+{
+ return QStandardItem::data(DataRole::OLD_VALUE);
+}
+
+void SqlQueryItem::setOldValue(const QVariant& value)
+{
+ QStandardItem::setData(value, DataRole::OLD_VALUE);
+}
+
+QVariant SqlQueryItem::getValueForDisplay() const
+{
+ return QStandardItem::data(DataRole::VALUE_FOR_DISPLAY);
+}
+
+void SqlQueryItem::setValueForDisplay(const QVariant &value)
+{
+ QStandardItem::setData(value, DataRole::VALUE_FOR_DISPLAY);
+}
+
+void SqlQueryItem::setLimitedValue(bool limited)
+{
+ QStandardItem::setData(QVariant(limited), DataRole::LIMITED_VALUE);
+}
+
+QVariant SqlQueryItem::adjustVariantType(const QVariant& value)
+{
+ QVariant newValue;
+ bool ok;
+ newValue = value.toInt(&ok);
+ if (ok)
+ {
+ ok = (value.toString() == newValue.toString());
+ if (ok)
+ return newValue;
+ }
+
+ newValue = value.toLongLong(&ok);
+ if (ok)
+ {
+ ok = (value.toString() == newValue.toString());
+ if (ok)
+ return newValue;
+ }
+
+ newValue = value.toDouble(&ok);
+ if (ok)
+ {
+ ok = (value.toString() == newValue.toString());
+ if (ok)
+ return newValue;
+ }
+
+ return value;
+}
+
+QString SqlQueryItem::getToolTip() const
+{
+ static const QString tableTmp = "<table>%1</table>";
+ static const QString rowTmp = "<tr><td colspan=2 style=\"white-space: pre\">%1</td><td style=\"align: right\"><b>%2</b></td></tr>";
+ static const QString hdrRowTmp = "<tr><td width=16><img src=\"%1\"/></td><th colspan=2 style=\"align: center\">%2 %3</th></tr>";
+ static const QString constrRowTmp = "<tr><td width=16><img src=\"%1\"/></td><td style=\"white-space: pre\"><b>%2</b></td><td>%3</td></tr>";
+ static const QString emptyRow = "<tr><td colspan=3></td></tr>";
+
+ if (!index().isValid())
+ return QString::null;
+
+ SqlQueryModelColumn* col = getColumn();
+ if (!col)
+ return QString::null; // happens when simple execution method was performed
+
+ QStringList rows;
+ rows << hdrRowTmp.arg(ICONS.COLUMN.getPath()).arg(tr("Column:", "data view tooltip")).arg(col->column);
+ rows << rowTmp.arg(tr("Data type:", "data view")).arg(col->dataType.toString());
+ if (!col->table.isNull())
+ {
+ rows << rowTmp.arg(tr("Table:", "data view tooltip")).arg(col->table);
+
+ RowId rowId = getRowId();
+ QString rowIdStr;
+ if (rowId.size() == 1)
+ {
+ rowIdStr = rowId.values().first().toString();
+ }
+ else
+ {
+ QStringList values;
+ QString rowIdValue;
+ QHashIterator<QString,QVariant> it(rowId);
+ while (it.hasNext())
+ {
+ it.next();
+ rowIdValue = it.value().toString();
+ if (rowIdValue.length() > 30)
+ rowIdValue = rowIdValue.left(27) + "...";
+
+ values << it.key() + "=" + rowIdValue;
+ }
+ rowIdStr = "[" + values.join(", ") + "]";
+ }
+ rows << rowTmp.arg("ROWID:").arg(rowIdStr);
+ }
+
+ if (col->constraints.size() > 0)
+ {
+ rows << emptyRow;
+ rows << hdrRowTmp.arg(ICONS.COLUMN_CONSTRAINT.getPath()).arg(tr("Constraints:", "data view tooltip")).arg("");
+ foreach (SqlQueryModelColumn::Constraint* constr, col->constraints)
+ rows << constrRowTmp.arg(constr->getIcon()->toUrl()).arg(constr->getTypeString()).arg(constr->getDetails());
+ }
+
+ return tableTmp.arg(rows.join(""));
+}
+
+SqlQueryModelColumn* SqlQueryItem::getColumn() const
+{
+ return QStandardItem::data(DataRole::COLUMN).value<SqlQueryModelColumn*>();
+}
+
+void SqlQueryItem::setColumn(SqlQueryModelColumn* column)
+{
+ QStandardItem::setData(QVariant::fromValue(column), DataRole::COLUMN);
+}
+
+SqlQueryModel *SqlQueryItem::getModel() const
+{
+ if (!model())
+ return nullptr;
+
+ return dynamic_cast<SqlQueryModel*>(model());
+}
+
+void SqlQueryItem::setData(const QVariant &value, int role)
+{
+ switch (role)
+ {
+ case Qt::EditRole:
+ {
+ // -1 column is used by Qt for header items (ie. when setHeaderData() is called)
+ // and we want this to mean that the value was loaded from db, because it forces
+ // the value to be interpreted as not modified.
+ setValue(value, false, (column() == -1));
+ return;
+ }
+ }
+
+ QStandardItem::setData(value, role);
+}
+
+QVariant SqlQueryItem::data(int role) const
+{
+ switch (role)
+ {
+ case Qt::EditRole:
+ {
+ if (isDeletedRow())
+ return QVariant();
+
+ return getValue();
+ }
+ case Qt::DisplayRole:
+ {
+ if (isDeletedRow())
+ return "";
+
+ QVariant value = getValueForDisplay();
+ if (value.isNull())
+ return "NULL";
+
+ return value;
+ }
+ case Qt::ForegroundRole:
+ {
+ QVariant value = getValue();
+ if (value.isNull())
+ return QBrush(CFG_UI.Colors.DataNullFg.get());
+
+ break;
+ }
+ case Qt::BackgroundRole:
+ {
+ if (isDeletedRow())
+ return QBrush(CFG_UI.Colors.DataDeletedBg.get());
+
+ break;
+ }
+ case Qt::TextAlignmentRole:
+ {
+ QVariant value = getValue();
+ if (value.isNull() || isDeletedRow())
+ return Qt::AlignCenter;
+
+ break;
+ }
+ case Qt::FontRole:
+ {
+ QFont font = CFG_UI.Fonts.DataView.get();
+
+ QVariant value = getValue();
+ if (value.isNull() || isDeletedRow())
+ font.setItalic(true);
+
+ return font;
+ }
+ case Qt::ToolTipRole:
+ {
+ return getToolTip();
+ }
+ }
+
+ return QStandardItem::data(role);
+}
+
+QString SqlQueryItem::loadFullData()
+{
+ SqlQueryModelColumn* col = getColumn();
+ if (col->editionForbiddenReason.size() > 0)
+ {
+ qWarning() << "Tried to load full cell which is not editable. This should be already handled in Editor class when invoking edition action.";
+ return tr("This cell is not editable, because: %1").arg(SqlQueryModelColumn::resolveMessage(col->editionForbiddenReason.values().first()));
+ }
+
+ if (isJustInsertedWithOutRowId())
+ {
+ QString msg = tr("When inserted new row to the WITHOUT ROWID table, using DEFAULT value for PRIMARY KEY, "
+ "the table has to be reloaded in order to edit the new row.");
+ return tr("This cell is not editable, because: %1").arg(msg);
+ }
+
+ SqlQueryModel *model = getModel();
+ Db* db = model->getDb();
+ if (!db->isOpen())
+ {
+ qWarning() << "Tried to load the data for a cell that refers to the already closed database.";
+ return tr("Cannot load the data for a cell that refers to the already closed database.");
+ }
+
+ Dialect dialect = db->getDialect();
+
+ // Query
+ RowIdConditionBuilder rowIdBuilder;
+ rowIdBuilder.setRowId(getRowId());
+ QString query = "SELECT %1 FROM %2 WHERE " + rowIdBuilder.build();
+
+ // Column
+ query = query.arg(wrapObjIfNeeded(col->column, dialect));
+
+ // Database and table
+ QString source = wrapObjIfNeeded(col->table, dialect);
+ if (!col->database.isNull())
+ source.prepend(wrapObjIfNeeded(col->database, dialect)+".");
+
+ query = query.arg(source);
+
+ // Get the data
+ SqlQueryPtr results = db->exec(query, rowIdBuilder.getQueryArgs());
+ if (results->isError())
+ return results->getErrorText();
+
+ setValue(results->getSingleCell(), false, true);
+ return QString::null;
+}
+
+QVariant SqlQueryItem::getFullValue()
+{
+ if (!isLimitedValue())
+ return getValue();
+
+ QVariant originalValue = getValue();
+ loadFullData();
+ QVariant result = getValue();
+ setValue(originalValue, true, !isUncommited());
+ return result;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h
new file mode 100644
index 0000000..b2552cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h
@@ -0,0 +1,96 @@
+#ifndef SQLQUERYITEM_H
+#define SQLQUERYITEM_H
+
+#include "sqlquerymodelcolumn.h"
+#include "db/sqlquery.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStandardItem>
+
+class SqlQueryModel;
+
+class GUI_API_EXPORT SqlQueryItem : public QObject, public QStandardItem
+{
+ Q_OBJECT
+
+ public:
+ struct GUI_API_EXPORT DataRole // not 'enum class' because we need autocasting to int for this one
+ {
+ enum Enum
+ {
+ ROWID = 1001,
+ VALUE = 1002,
+ LIMITED_VALUE = 1003,
+ COLUMN = 1004,
+ UNCOMMITED = 1005,
+ COMMITING_ERROR = 1006,
+ NEW_ROW = 1007,
+ DELETED = 1008,
+ OLD_VALUE = 1009,
+ JUST_INSERTED_WITHOUT_ROWID = 1010,
+ VALUE_FOR_DISPLAY = 1011
+ };
+ };
+
+ explicit SqlQueryItem(QObject *parent = 0);
+ SqlQueryItem(const SqlQueryItem& item);
+
+ QStandardItem* clone() const;
+
+ RowId getRowId() const;
+ void setRowId(const RowId& rowId);
+
+ bool isUncommited() const;
+ void setUncommited(bool uncommited);
+ void rollback();
+
+ bool isCommitingError() const;
+ void setCommitingError(bool isError);
+
+ bool isNewRow() const;
+ void setNewRow(bool isNew);
+
+ bool isJustInsertedWithOutRowId() const;
+ void setJustInsertedWithOutRowId(bool justInsertedWithOutRowId);
+
+ bool isDeletedRow() const;
+ void setDeletedRow(bool isDeleted);
+
+ QVariant getValue() const;
+ void setValue(const QVariant& value, bool limited = false, bool loadedFromDb = false);
+ bool isLimitedValue() const;
+
+ QVariant getOldValue() const;
+ void setOldValue(const QVariant& value);
+
+ QVariant getValueForDisplay() const;
+ void setValueForDisplay(const QVariant& value);
+
+ /**
+ * @brief loadFullData Reloads entire value of the cell from database.
+ * @return QString::null on sucess, or error string on failure.
+ */
+ QString loadFullData();
+
+ /**
+ * @brief getFullValue Loads and returns full value from database, but keeps the original value.
+ * @return Full value, reloaded from database.
+ * Calls loadFullData(), then getValue() for the result,
+ * but just before returning - restores initial, limited value.
+ */
+ QVariant getFullValue();
+
+ SqlQueryModelColumn* getColumn() const;
+ void setColumn(SqlQueryModelColumn* column);
+
+ SqlQueryModel* getModel() const;
+
+ void setData(const QVariant& value, int role = Qt::UserRole + 1);
+ QVariant data(int role = Qt::UserRole + 1) const;
+
+ private:
+ void setLimitedValue(bool limited);
+ QVariant adjustVariantType(const QVariant& value);
+ QString getToolTip() const;
+};
+
+#endif // SQLQUERYITEM_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp
new file mode 100644
index 0000000..ab8f7f2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp
@@ -0,0 +1,70 @@
+#include "sqlqueryitemdelegate.h"
+#include "sqlquerymodel.h"
+#include "sqlqueryitem.h"
+#include "common/unused.h"
+#include "services/notifymanager.h"
+#include "uiconfig.h"
+#include <QPainter>
+#include <QEvent>
+#include <QLineEdit>
+
+SqlQueryItemDelegate::SqlQueryItemDelegate(QObject *parent) :
+ QStyledItemDelegate(parent)
+{
+}
+
+void SqlQueryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyledItemDelegate::paint(painter, option, index);
+
+ SqlQueryItem* item = getItem(index);
+
+ if (item->isUncommited())
+ {
+ painter->setPen(item->isCommitingError() ? CFG_UI.Colors.DataUncommitedError.get() : CFG_UI.Colors.DataUncommited.get());
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRect(option.rect.x(), option.rect.y(), option.rect.width()-1, option.rect.height()-1);
+ }
+}
+
+QWidget* SqlQueryItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ UNUSED(option);
+ if (!index.isValid())
+ return nullptr;
+
+ const SqlQueryModel* model = dynamic_cast<const SqlQueryModel*>(index.model());
+ SqlQueryItem* item = model->itemFromIndex(index);
+
+ if (item->isDeletedRow())
+ {
+ notifyWarn(tr("Cannot edit this cell. Details: %2").arg(tr("The row is marked for deletion.")));
+ return nullptr;
+ }
+
+ if (!item->getColumn()->canEdit())
+ {
+ notifyWarn(tr("Cannot edit this cell. Details: %2").arg(item->getColumn()->getEditionForbiddenReason()));
+ return nullptr;
+ }
+
+ if (item->isLimitedValue())
+ item->loadFullData();
+
+ return getEditor(item->getValue().userType(), parent);
+}
+
+SqlQueryItem* SqlQueryItemDelegate::getItem(const QModelIndex &index) const
+{
+ const SqlQueryModel* queryModel = dynamic_cast<const SqlQueryModel*>(index.model());
+ return queryModel->itemFromIndex(index);
+}
+
+QWidget* SqlQueryItemDelegate::getEditor(int type, QWidget* parent) const
+{
+ UNUSED(type);
+ QLineEdit *editor = new QLineEdit(parent);
+ editor->setFrame(editor->style()->styleHint(QStyle::SH_ItemView_DrawDelegateFrame, 0, editor));
+ return editor;
+
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h
new file mode 100644
index 0000000..a190202
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h
@@ -0,0 +1,23 @@
+#ifndef SQLQUERYITEMDELEGATE_H
+#define SQLQUERYITEMDELEGATE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QStyledItemDelegate>
+
+class SqlQueryItem;
+
+class GUI_API_EXPORT SqlQueryItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+ public:
+ explicit SqlQueryItemDelegate(QObject *parent = 0);
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
+ private:
+ SqlQueryItem* getItem(const QModelIndex &index) const;
+ QWidget* getEditor(int type, QWidget* parent) const;
+};
+
+#endif // SQLQUERYITEMDELEGATE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp
new file mode 100644
index 0000000..56bf78d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp
@@ -0,0 +1,1519 @@
+#include "sqlquerymodel.h"
+#include "parser/keywords.h"
+#include "sqlqueryitem.h"
+#include "services/notifymanager.h"
+#include "common/utils_sql.h"
+#include "schemaresolver.h"
+#include "common/unused.h"
+#include "db/sqlerrorcodes.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "uiconfig.h"
+#include "datagrid/sqlqueryview.h"
+#include "datagrid/sqlqueryrownummodel.h"
+#include <QHeaderView>
+#include <QDebug>
+#include <QApplication>
+#include <QMutableListIterator>
+#include <QInputDialog>
+#include <QTime>
+#include <QMessageBox>
+
+SqlQueryModel::SqlQueryModel(QObject *parent) :
+ QStandardItemModel(parent)
+{
+ queryExecutor = new QueryExecutor();
+ queryExecutor->setDataLengthLimit(cellDataLengthLimit);
+ connect(queryExecutor, SIGNAL(executionFinished(SqlQueryPtr)), this, SLOT(handleExecFinished(SqlQueryPtr)));
+ connect(queryExecutor, SIGNAL(executionFailed(int,QString)), this, SLOT(handleExecFailed(int,QString)));
+ connect(queryExecutor, SIGNAL(resultsCountingFinished(quint64,quint64,int)), this, SLOT(resultsCountingFinished(quint64,quint64,int)));
+ setItemPrototype(new SqlQueryItem());
+}
+
+SqlQueryModel::~SqlQueryModel()
+{
+ delete queryExecutor;
+ queryExecutor = nullptr;
+}
+
+void SqlQueryModel::staticInit()
+{
+}
+
+QString SqlQueryModel::getQuery() const
+{
+ return query;
+}
+
+void SqlQueryModel::setQuery(const QString &value)
+{
+ query = value;
+}
+
+void SqlQueryModel::setExplainMode(bool explain)
+{
+ this->explain = explain;
+}
+
+void SqlQueryModel::executeQuery()
+{
+ if (queryExecutor->isExecutionInProgress())
+ {
+ notifyWarn(tr("Only one query can be executed simultaneously."));
+ return;
+ }
+
+ sortOrder.clear();
+ queryExecutor->setSkipRowCounting(false);
+ queryExecutor->setSortOrder(sortOrder);
+ queryExecutor->setPage(0);
+ reloading = false;
+
+ executeQueryInternal();
+}
+
+void SqlQueryModel::executeQueryInternal()
+{
+ if (!db || !db->isValid())
+ {
+ notifyWarn("Cannot execute query on undefined or invalid database.");
+ internalExecutionStopped();
+ return;
+ }
+
+ if (query.isEmpty())
+ {
+ notifyWarn("Cannot execute empty query.");
+ internalExecutionStopped();
+ return;
+ }
+
+ QList<SqlQueryItem*> uncommitedItems = getUncommitedItems();
+ if (uncommitedItems.size() > 0)
+ {
+ QMessageBox::StandardButton result = QMessageBox::question(nullptr, tr("Uncommited data"),
+ tr("There are uncommited data changes. Do you want to proceed anyway? "
+ "All uncommited changes will be lost."));
+
+ if (result != QMessageBox::Yes)
+ {
+ internalExecutionStopped();
+ return;
+ }
+
+ rollback(uncommitedItems);
+ }
+
+ emit executionStarted();
+
+ queryExecutor->setQuery(query);
+ queryExecutor->setResultsPerPage(CFG_UI.General.NumberOfRowsPerPage.get());
+ queryExecutor->setExplainMode(explain);
+ queryExecutor->setPreloadResults(true);
+ queryExecutor->exec();
+}
+
+void SqlQueryModel::internalExecutionStopped()
+{
+ reloading = false;
+ emit loadingEnded(false);
+}
+
+void SqlQueryModel::interrupt()
+{
+ queryExecutor->interrupt();
+}
+
+qint64 SqlQueryModel::getExecutionTime()
+{
+ return lastExecutionTime;
+}
+
+qint64 SqlQueryModel::getTotalRowsReturned()
+{
+ return totalRowsReturned;
+}
+
+qint64 SqlQueryModel::getTotalRowsAffected()
+{
+ return rowsAffected;
+}
+
+qint64 SqlQueryModel::getTotalPages()
+{
+ return totalPages;
+}
+
+QList<SqlQueryModelColumnPtr> SqlQueryModel::getColumns()
+{
+ return columns;
+}
+
+SqlQueryItem *SqlQueryModel::itemFromIndex(const QModelIndex &index) const
+{
+ return dynamic_cast<SqlQueryItem*>(QStandardItemModel::itemFromIndex(index));
+}
+
+SqlQueryItem*SqlQueryModel::itemFromIndex(int row, int column) const
+{
+ return dynamic_cast<SqlQueryItem*>(item(row, column));
+}
+
+int SqlQueryModel::getCellDataLengthLimit()
+{
+ return cellDataLengthLimit;
+}
+
+QModelIndexList SqlQueryModel::findIndexes(int role, const QVariant& value, int hits) const
+{
+ QModelIndex startIdx = index(0, 0);
+ QModelIndex endIdx = index(rowCount() - 1, columnCount() - 1);
+ return findIndexes(startIdx, endIdx, role, value, hits);
+}
+
+QModelIndexList SqlQueryModel::findIndexes(const QModelIndex& start, const QModelIndex& end, int role, const QVariant& value, int hits) const
+{
+ QModelIndexList results;
+ bool allHits = hits < 0;
+ QModelIndex parentIdx = parent(start);
+ int fromRow = start.row();
+ int toRow = end.row();
+ int fromCol = start.column();
+ int toCol = end.column();
+
+ for (int row = fromRow; row <= toRow && (allHits || results.count() < hits); row++)
+ {
+ for (int col = fromCol; col <= toCol && (allHits || results.count() < hits); col++)
+ {
+ QModelIndex idx = index(row, col, parentIdx);
+ if (!idx.isValid())
+ continue;
+
+ QVariant cellVal = data(idx, role);
+ if (value != cellVal)
+ continue;
+
+ results.append(idx);
+ }
+ }
+
+ return results;
+}
+
+QList<SqlQueryItem*> SqlQueryModel::findItems(int role, const QVariant& value, int hits) const
+{
+ return toItemList(findIndexes(role, value, hits));
+}
+
+QList<SqlQueryItem*> SqlQueryModel::findItems(const QModelIndex& start, const QModelIndex& end, int role, const QVariant& value, int hits) const
+{
+ return toItemList(findIndexes(start, end, role, value, hits));
+}
+
+QList<SqlQueryItem*> SqlQueryModel::getUncommitedItems() const
+{
+ return findItems(SqlQueryItem::DataRole::UNCOMMITED, true);
+}
+
+QList<QList<SqlQueryItem*> > SqlQueryModel::groupItemsByRows(const QList<SqlQueryItem*>& items)
+{
+ QMap<int,QList<SqlQueryItem*>> itemsByRow;
+ for (SqlQueryItem* item : items)
+ itemsByRow[item->row()] << item;
+
+ return itemsByRow.values();
+}
+
+QHash<Table,QList<SqlQueryItem*>> SqlQueryModel::groupItemsByTable(const QList<SqlQueryItem*>& items)
+{
+ QHash<Table,QList<SqlQueryItem*>> itemsByTable;
+ Table table;
+ foreach (SqlQueryItem* item, items)
+ {
+ if (item->getColumn())
+ {
+ table.setDatabase(item->getColumn()->database.toLower());
+ table.setTable(item->getColumn()->table.toLower());
+ itemsByTable[table] << item;
+ }
+ else
+ itemsByTable[Table()] << item;
+ }
+
+ return itemsByTable;
+}
+
+QList<SqlQueryItem*> SqlQueryModel::filterOutCommitedItems(const QList<SqlQueryItem*>& items)
+{
+ // This method doesn't make use of QMutableListIterator to remove items from passed list,
+ // because it would require list in argument to drop 'const' keyword and it's already
+ // there in calling methods, so it's easier to copy list and filter on the fly.
+ QList<SqlQueryItem*> newList;
+ foreach (SqlQueryItem* item, items)
+ if (item->isUncommited())
+ newList << item;
+
+ return newList;
+}
+
+QList<SqlQueryItem*> SqlQueryModel::getRow(int row)
+{
+ QList<SqlQueryItem*> items;
+ for (int i = 0; i < columnCount(); i++)
+ items << itemFromIndex(row, i);
+
+ return items;
+}
+
+SqlQueryModel::Features SqlQueryModel::features() const
+{
+ return Features();
+}
+
+QList<SqlQueryItem*> SqlQueryModel::toItemList(const QModelIndexList& indexes) const
+{
+ QList<SqlQueryItem*> list;
+ foreach (const QModelIndex& idx, indexes)
+ list << itemFromIndex(idx);
+
+ return list;
+}
+
+void SqlQueryModel::commit()
+{
+ QList<SqlQueryItem*> items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true);
+ commitInternal(items);
+}
+
+void SqlQueryModel::commit(const QList<SqlQueryItem*>& items)
+{
+ commitInternal(filterOutCommitedItems(items));
+}
+
+bool SqlQueryModel::commitRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ const SqlQueryItem* item = itemsInRow.at(0);
+ if (!item)
+ {
+ qWarning() << "null item while call to commitRow() method. It shouldn't happen.";
+ return true;
+ }
+ if (item->isNewRow())
+ return commitAddedRow(getRow(item->row())); // we need to get all items again, in case of selective commit
+ else if (item->isDeletedRow())
+ return commitDeletedRow(getRow(item->row())); // we need to get all items again, in case of selective commit
+ else
+ return commitEditedRow(itemsInRow);
+}
+
+void SqlQueryModel::rollbackRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ const SqlQueryItem* item = itemsInRow.at(0);
+ if (!item)
+ {
+ qWarning() << "null item while call to rollbackRow() method. It shouldn't happen.";
+ return;
+ }
+ if (item->isNewRow())
+ rollbackAddedRow(getRow(item->row())); // we need to get all items again, in case of selective commit
+ else if (item->isDeletedRow())
+ rollbackDeletedRow(getRow(item->row())); // we need to get all items again, in case of selective commit
+ else
+ rollbackEditedRow(itemsInRow);
+}
+
+void SqlQueryModel::rollback()
+{
+ QList<SqlQueryItem*> items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true);
+ rollbackInternal(items);
+}
+
+void SqlQueryModel::rollback(const QList<SqlQueryItem*>& items)
+{
+ rollbackInternal(filterOutCommitedItems(items));
+}
+
+void SqlQueryModel::commitInternal(const QList<SqlQueryItem*>& items)
+{
+ Db* db = getDb();
+ if (!db->isOpen())
+ {
+ notifyError(tr("Cannot commit the data for a cell that refers to the already closed database."));
+ return;
+ }
+
+ if (!db->begin())
+ {
+ notifyError(tr("Could not begin transaction on the database. Details: %1").arg(db->getErrorText()));
+ return;
+ }
+
+ // Removing "commit error" mark from items that are going to be commited now
+ for (SqlQueryItem* item : items)
+ item->setCommitingError(false);
+
+ // Grouping by row and commiting
+ QList<QList<SqlQueryItem*> > groupedItems = groupItemsByRows(items);
+ bool ok = true;
+ foreach (const QList<SqlQueryItem*>& itemsInRow, groupedItems)
+ {
+ if (!commitRow(itemsInRow))
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ // Getting current uncommited list (after rows deletion it may be different)
+ QList<SqlQueryItem*> itemsLeft = findItems(SqlQueryItem::DataRole::UNCOMMITED, true);
+
+ // Getting common elements of initial and current item list, because of a possibility of the selective commit
+ QMutableListIterator<SqlQueryItem*> it(itemsLeft);
+ while (it.hasNext())
+ {
+ if (!items.contains(it.next()))
+ it.remove();
+ }
+
+ // Commiting to the database
+ if (ok)
+ {
+ if (!db->commit())
+ {
+ ok = false;
+ notifyError(tr("An error occurred while commiting the transaction: %1").arg(db->getErrorText()));
+ }
+ else
+ {
+ // Commited successfully
+ foreach (SqlQueryItem* item, itemsLeft)
+ {
+ item->setUncommited(false);
+ item->setNewRow(false);
+ }
+
+ emit commitStatusChanged(getUncommitedItems().size() > 0);
+ }
+ }
+
+ if (!ok)
+ {
+ if (!db->rollback())
+ {
+ notifyError(tr("An error occurred while rolling back the transaction: %1").arg(db->getErrorText()));
+ // Nothing else we can do about it, but it should not happen.
+ }
+ }
+}
+
+void SqlQueryModel::rollbackInternal(const QList<SqlQueryItem*>& items)
+{
+ QList<QList<SqlQueryItem*> > groupedItems = groupItemsByRows(items);
+ foreach (const QList<SqlQueryItem*>& itemsInRow, groupedItems)
+ rollbackRow(itemsInRow);
+
+ emit commitStatusChanged(getUncommitedItems().size() > 0);
+}
+
+void SqlQueryModel::reload()
+{
+ queryExecutor->setSkipRowCounting(false);
+ reloadInternal();
+}
+
+void SqlQueryModel::reloadInternal()
+{
+ if (!reloadAvailable)
+ return;
+
+ if (queryExecutor->isExecutionInProgress())
+ {
+ notifyWarn(tr("Only one query can be executed simultaneously."));
+ return;
+ }
+ reloading = true;
+ executeQueryInternal();
+}
+
+SqlQueryView* SqlQueryModel::getView() const
+{
+ return view;
+}
+
+void SqlQueryModel::setView(SqlQueryView* value)
+{
+ view = value;
+ view->setModel(this);
+}
+
+int SqlQueryModel::getCurrentPage(bool includeOneBeingLoaded) const
+{
+ int result = includeOneBeingLoaded ? queryExecutor->getPage() : page;
+ return result < 0 ? 0 : result;
+}
+
+bool SqlQueryModel::commitAddedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ UNUSED(itemsInRow);
+ return false;
+}
+
+bool SqlQueryModel::commitEditedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ if (itemsInRow.size() == 0)
+ {
+ qWarning() << "SqlQueryModel::commitEditedRow() called with no items in the list.";
+ return true;
+ }
+
+ Dialect dialect = db->getDialect();
+
+ QHash<Table,QList<SqlQueryItem*>> itemsByTable = groupItemsByTable(itemsInRow);
+
+ // Values
+ QString query;
+ SqlQueryModelColumn* col = nullptr;
+ QHash<QString,QVariant> queryArgs;
+ QStringList assignmentArgs;
+ RowId rowId;
+ RowId newRowId;
+ CommitUpdateQueryBuilder queryBuilder;
+ QHashIterator<Table,QList<SqlQueryItem*>> it(itemsByTable);
+ QList<SqlQueryItem*> items;
+ Table table;
+ while (it.hasNext())
+ {
+ it.next();
+ table = it.key();
+ if (table.getTable().isNull())
+ {
+ qCritical() << "Tried to commit null table in SqlQueryModel::commitEditedRow().";
+ continue;
+ }
+
+ items = it.value();
+ if (items.size() == 0)
+ continue;
+
+ // RowId
+ queryBuilder.clear();
+ rowId = items.first()->getRowId();
+ queryBuilder.setRowId(rowId);
+ newRowId = getNewRowId(rowId, items); // if any of item updates any of rowid columns, then this will be different than initial rowid
+
+ // Database and table
+ queryBuilder.setTable(wrapObjIfNeeded(table.getTable(), dialect));
+ if (!table.getDatabase().isNull())
+ queryBuilder.setDatabase(wrapObjIfNeeded(table.getDatabase(), dialect));
+
+ for (SqlQueryItem* item : items)
+ {
+ col = item->getColumn();
+ if (col->editionForbiddenReason.size() > 0 || item->isJustInsertedWithOutRowId())
+ {
+ notifyError(tr("Tried to commit a cell which is not editable (yet modified and waiting for commit)! This is a bug. Please report it."));
+ return false;
+ }
+
+ // Column
+ queryBuilder.addColumn(wrapObjIfNeeded(col->column, dialect));
+ }
+
+ // Completing query
+ query = queryBuilder.build();
+
+ // RowId condition arguments
+ queryArgs = queryBuilder.getQueryArgs();
+
+ // Per-column arguments
+ assignmentArgs = queryBuilder.getAssignmentArgs();
+ for (int i = 0, total = items.size(); i < total; ++i)
+ queryArgs[assignmentArgs[i]] = items[i]->getValue();
+
+ // Get the data
+ SqlQueryPtr results = db->exec(query, queryArgs);
+ if (results->isError())
+ {
+ for (SqlQueryItem* item : items)
+ item->setCommitingError(true);
+
+ notifyError(tr("An error occurred while commiting the data: %1").arg(results->getErrorText()));
+ return false;
+ }
+
+ // After successful commit, check if RowId was modified and upadate it accordingly
+ if (rowId != newRowId)
+ updateRowIdForAllItems(table, rowId, newRowId);
+ }
+
+ return true;
+}
+
+bool SqlQueryModel::commitDeletedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ if (itemsInRow.size() == 0)
+ {
+ qCritical() << "No items passed to SqlQueryModel::commitDeletedRow().";
+ return false;
+ }
+
+ int row = itemsInRow[0]->index().row();
+ return removeRow(row);
+}
+
+void SqlQueryModel::rollbackAddedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ if (itemsInRow.size() == 0)
+ {
+ qCritical() << "No items passed to SqlQueryModel::rollbackAddedRow().";
+ return;
+ }
+
+ int row = itemsInRow[0]->index().row();
+ removeRow(row);
+}
+
+void SqlQueryModel::rollbackEditedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ foreach (SqlQueryItem* item, itemsInRow)
+ item->rollback();
+}
+
+void SqlQueryModel::rollbackDeletedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ foreach (SqlQueryItem* item, itemsInRow)
+ item->rollback();
+}
+
+SqlQueryModelColumnPtr SqlQueryModel::getColumnModel(const QString& database, const QString& table, const QString& column)
+{
+ Column colObj(database, table, column);
+ if (columnMap.contains(colObj))
+ return columnMap.value(colObj);
+
+ return SqlQueryModelColumnPtr();
+}
+
+SqlQueryModelColumnPtr SqlQueryModel::getColumnModel(const QString& table, const QString& column)
+{
+ return getColumnModel("main", table, column);
+}
+
+QList<SqlQueryModelColumnPtr> SqlQueryModel::getTableColumnModels(const QString& database, const QString& table)
+{
+ QList<SqlQueryModelColumnPtr> results;
+ foreach (SqlQueryModelColumnPtr modelColumn, columns)
+ {
+ if (modelColumn->database.compare(database, Qt::CaseInsensitive) != 0)
+ continue;
+
+ if (modelColumn->table.compare(table, Qt::CaseInsensitive) != 0)
+ continue;
+
+ results << modelColumn;
+ }
+ return results;
+}
+
+QList<SqlQueryModelColumnPtr> SqlQueryModel::getTableColumnModels(const QString& table)
+{
+ return getTableColumnModels("main", table);
+}
+
+void SqlQueryModel::loadData(SqlQueryPtr results)
+{
+ if (rowCount() > 0)
+ clear();
+
+ view->horizontalHeader()->show();
+
+ // Read columns first. It will be needed later.
+ readColumns();
+
+ // Load data
+ SqlResultsRowPtr row;
+ int rowIdx = 0;
+ int rowsPerPage = CFG_UI.General.NumberOfRowsPerPage.get();
+ rowNumBase = getCurrentPage() * rowsPerPage + 1;
+
+ updateColumnHeaderLabels();
+ QList<QStandardItem*> itemList;
+ while (results->hasNext() && rowIdx < rowsPerPage)
+ {
+ row = results->next();
+ if (!row)
+ break;
+
+ itemList = loadRow(row);
+ insertRow(rowIdx, itemList);
+
+ if ((rowIdx % 100) == 0)
+ qApp->processEvents();
+
+ rowIdx++;
+ }
+}
+
+QList<QStandardItem*> SqlQueryModel::loadRow(SqlResultsRowPtr row)
+{
+ QList<QStandardItem*> itemList;
+ SqlQueryItem* item = nullptr;
+ RowId rowId;
+ int colIdx = 0;
+ foreach (const QVariant& value, row->valueList().mid(rowIdColumns))
+ {
+ item = new SqlQueryItem();
+ rowId = getRowIdValue(row, colIdx);
+ updateItem(item, value, colIdx, rowId);
+ itemList << item;
+ colIdx++;
+ }
+
+ return itemList;
+}
+
+RowId SqlQueryModel::getRowIdValue(SqlResultsRowPtr row, int columnIdx)
+{
+ RowId rowId;
+ Table table = tablesForColumns[columnIdx];
+ QHash<QString,QString> rowIdColumns = tableToRowIdColumn[table];
+ QHashIterator<QString,QString> it(rowIdColumns);
+ QString col;
+ while (it.hasNext())
+ {
+ // Check if the result row contains QueryExecutor's column alias for this RowId column
+ col = it.next().key();
+ if (row->contains(col))
+ {
+ // It does, do let's put the actual column name into the RowId and assign the RowId value to it.
+ // Using the actucal column name as a key will let create a proper query for updates, etc, later on.
+ rowId[it.value()] = row->value(col);
+ }
+ else if (columnEditionStatus[columnIdx])
+ {
+ qCritical() << "No row ID column for cell that is editable. Asked for row ID column named:" << col
+ << "in table" << tablesForColumns[columnIdx].getTable();
+ return RowId();
+ }
+ }
+ return rowId;
+}
+
+void SqlQueryModel::updateItem(SqlQueryItem* item, const QVariant& value, int columnIndex, const RowId& rowId)
+{
+ SqlQueryModelColumnPtr column = columns[columnIndex];
+ Qt::Alignment alignment;
+
+ if (column->isNumeric() && isNumeric(value))
+ alignment = Qt::AlignRight|Qt::AlignVCenter;
+ else
+ alignment = Qt::AlignLeft|Qt::AlignVCenter;
+
+ // This should be equal at most, unless we have UTF-8 string, than there might be more bytes.
+ // If less, than it's not limited.
+ bool limited = value.toByteArray().size() >= cellDataLengthLimit;
+
+ item->setJustInsertedWithOutRowId(false);
+ item->setValue(value, limited, true);
+ item->setColumn(column.data());
+ item->setTextAlignment(alignment);
+ item->setRowId(rowId);
+}
+
+RowId SqlQueryModel::getNewRowId(const RowId& currentRowId, const QList<SqlQueryItem*> items)
+{
+ if (currentRowId.size() > 1)
+ {
+ // For WITHOUT ROWID tables we need to look up all columns
+ QStringList rowIdColumns = currentRowId.keys();
+ RowId newRowIdCandidate = currentRowId;
+ int idx;
+ for (SqlQueryItem* item : items)
+ {
+ if (rowIdColumns.contains(item->getColumn()->column, Qt::CaseInsensitive))
+ {
+ idx = indexOf(rowIdColumns, item->getColumn()->column, Qt::CaseInsensitive);
+ newRowIdCandidate[rowIdColumns[idx]] = item->getValue();
+ }
+ }
+ return newRowIdCandidate;
+ }
+ else
+ {
+ // Check for an update on the standard ROWID
+ SqlQueryModelColumn* col = nullptr;
+ for (SqlQueryItem* item : items)
+ {
+ col = item->getColumn();
+ if (isRowIdKeyword(col->column) || col->isRowIdPk())
+ {
+ RowId newRowId;
+ newRowId["ROWID"] = item->getValue();
+ return newRowId;
+ }
+ }
+ }
+
+ return currentRowId;
+}
+
+void SqlQueryModel::updateRowIdForAllItems(const Table& table, const RowId& rowId, const RowId& newRowId)
+{
+ SqlQueryItem* item = nullptr;
+ for (int row = 0; row < rowCount(); row++)
+ {
+ for (int col = 0; col < columnCount(); col++)
+ {
+ item = itemFromIndex(row, col);
+ if (item->getColumn()->database.compare(table.getDatabase(), Qt::CaseInsensitive) != 0)
+ continue;
+
+ if (item->getColumn()->table.compare(table.getTable(), Qt::CaseInsensitive) != 0)
+ continue;
+
+ if (item->getRowId() != rowId)
+ continue;
+
+ item->setRowId(newRowId);
+ }
+ }
+}
+
+void SqlQueryModel::readColumns()
+{
+ columns.clear();
+ tableToRowIdColumn.clear();
+
+ // Reading column mapping for ROWID columns
+ int totalRowIdCols = 0;
+ Table table;
+ foreach (const QueryExecutor::ResultRowIdColumnPtr& resCol, queryExecutor->getRowIdResultColumns())
+ {
+ table.setDatabase(resCol->database);
+ table.setTable(resCol->table);
+ tableToRowIdColumn[table] = resCol->queryExecutorAliasToColumn;
+ totalRowIdCols += resCol->queryExecutorAliasToColumn.size();
+ }
+
+ // Reading column details (datatype, constraints)
+ readColumnDetails();
+
+ // Preparing other usful information about columns
+ rowIdColumns = totalRowIdCols;
+ tablesForColumns = getTablesForColumns();
+ columnEditionStatus = getColumnEditionEnabledList();
+}
+
+void SqlQueryModel::readColumnDetails()
+{
+ // Preparing global (table oriented) edition forbidden reasons
+ QSet<SqlQueryModelColumn::EditionForbiddenReason> editionForbiddenGlobalReasons;
+ foreach (QueryExecutor::EditionForbiddenReason reason, queryExecutor->getEditionForbiddenGlobalReasons())
+ editionForbiddenGlobalReasons << SqlQueryModelColumn::convert(reason);
+
+ // Reading all the details from query executor source tables
+ QHash<Table, TableDetails> tableDetails = readTableDetails();
+
+ // Preparing for processing
+ Table table;
+ Column column;
+ TableDetails details;
+ TableDetails::ColumnDetails colDetails;
+
+ SqlQueryModelColumnPtr modelColumn;
+ SqliteColumnTypePtr modelColumnType;
+ SqlQueryModelColumn::Constraint* modelConstraint = nullptr;
+
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, queryExecutor->getResultColumns())
+ {
+ // Creating new column for the model (this includes column oriented forbidden reasons)
+ modelColumn = SqlQueryModelColumnPtr::create(resCol);
+
+ // Adding global edition forbidden reasons
+ modelColumn->editionForbiddenReason += editionForbiddenGlobalReasons;
+
+ // Getting details of given table and column
+ table = Table(modelColumn->database, modelColumn->table);
+ column = Column(modelColumn->database, modelColumn->table, modelColumn->column);
+
+ details = tableDetails[table];
+ colDetails = details.columns[modelColumn->column];
+
+ // Column type
+ modelColumnType = colDetails.type;
+ if (modelColumnType)
+ modelColumn->dataType = DataType(modelColumnType->name, modelColumnType->precision, modelColumnType->scale);
+
+ // Column constraints
+ foreach (SqliteCreateTable::Column::ConstraintPtr constrPtr, colDetails.constraints)
+ {
+ modelConstraint = SqlQueryModelColumn::Constraint::create(constrPtr);
+ if (modelConstraint)
+ modelColumn->constraints << modelConstraint;
+ }
+
+ // Table constraints
+ foreach (SqliteCreateTable::ConstraintPtr constrPtr, details.constraints)
+ {
+ modelConstraint = SqlQueryModelColumn::Constraint::create(modelColumn->column, constrPtr);
+ if (modelConstraint)
+ modelColumn->constraints << modelConstraint;
+ }
+
+ // Adding to list for ordered access
+ columns << modelColumn;
+
+ // Adding to hash for fast, key based access
+ columnMap[column] = modelColumn;
+ }
+}
+
+QHash<Table, SqlQueryModel::TableDetails> SqlQueryModel::readTableDetails()
+{
+ QHash<Table, TableDetails> results;
+ SqliteQueryPtr query;
+ SqliteCreateTablePtr createTable;
+ Dialect dialect = db->getDialect();
+ SchemaResolver resolver(getDb());
+ QString database;
+ Table table;
+ QString columnName;
+
+ foreach (const QueryExecutor::SourceTablePtr& srcTable, queryExecutor->getSourceTables())
+ {
+ database = srcTable->database.isEmpty() ? "main" : srcTable->database;
+
+ query = resolver.getParsedObject(database, srcTable->table, SchemaResolver::TABLE);
+ if (!query || !query.dynamicCast<SqliteCreateTable>())
+ {
+ qWarning() << "Could not get parsed table while reading table details in SqlQueryModel. Queried table was:"
+ << database + "." + srcTable->table;
+ continue;
+ }
+ createTable = query.dynamicCast<SqliteCreateTable>();
+
+ // Table details
+ TableDetails tableDetails;
+ table = {database, srcTable->table};
+
+ // Table constraints
+ foreach (SqliteCreateTable::Constraint* tableConstr, createTable->constraints)
+ tableDetails.constraints << tableConstr->detach<SqliteCreateTable::Constraint>();
+
+ // Table columns
+ foreach (SqliteCreateTable::Column* columnStmt, createTable->columns)
+ {
+ // Column details
+ TableDetails::ColumnDetails columnDetails;
+ columnName = stripObjName(columnStmt->name, dialect);
+
+ // Column type
+ if (columnStmt->type)
+ columnDetails.type = columnStmt->type->detach<SqliteColumnType>();
+ else
+ columnDetails.type = SqliteColumnTypePtr();
+
+ // Column constraints
+ foreach (SqliteCreateTable::Column::Constraint* columnConstr, columnStmt->constraints)
+ columnDetails.constraints << columnConstr->detach<SqliteCreateTable::Column::Constraint>();
+
+ tableDetails.columns[columnName] = columnDetails;
+ }
+
+ results[table] = tableDetails;
+ }
+
+ return results;
+
+}
+
+QList<Table> SqlQueryModel::getTablesForColumns()
+{
+ QList<Table> columnTables;
+ Table table;
+ foreach (SqlQueryModelColumnPtr column, columns)
+ {
+ if (column->editionForbiddenReason.size() > 0)
+ {
+ columnTables << Table();
+ continue;
+ }
+ table = Table(column->database, column->table);
+ columnTables << table;
+ }
+ return columnTables;
+}
+
+QList<bool> SqlQueryModel::getColumnEditionEnabledList()
+{
+ QList<bool> columnEditionEnabled;
+ foreach (SqlQueryModelColumnPtr column, columns)
+ columnEditionEnabled << (column->editionForbiddenReason.size() == 0);
+
+ return columnEditionEnabled;
+}
+
+void SqlQueryModel::updateColumnsHeader()
+{
+ QueryExecutor::SortList executorSortOrder = queryExecutor->getSortOrder();
+ if (executorSortOrder.size() > 0)
+ emit sortingUpdated(executorSortOrder);
+}
+
+void SqlQueryModel::updateColumnHeaderLabels()
+{
+ headerColumns.clear();
+ foreach (SqlQueryModelColumnPtr column, columns)
+ {
+ headerColumns << column->displayName;
+ }
+
+ setColumnCount(headerColumns.size());
+}
+
+void SqlQueryModel::handleExecFinished(SqlQueryPtr results)
+{
+ if (results->isError())
+ {
+ emit executionFailed(tr("Error while executing SQL query: %1").arg(results->getErrorText()));
+ return;
+ }
+
+ storeStep1NumbersFromExecution();
+ loadData(results);
+ storeStep2NumbersFromExecution();
+
+ reloadAvailable = true;
+
+ emit loadingEnded(true);
+ restoreNumbersToQueryExecutor();
+ if (!reloading)
+ emit executionSuccessful();
+
+ reloading = false;
+
+ if (queryExecutor->isRowCountingRequired() || rowCount() < CFG_UI.General.NumberOfRowsPerPage.get())
+ emit totalRowsAndPagesAvailable(); // rows were counted manually
+ else
+ queryExecutor->countResults();
+
+}
+
+void SqlQueryModel::handleExecFailed(int code, QString errorMessage)
+{
+ UNUSED(code);
+
+ if (rowCount() > 0)
+ {
+ clear();
+ columns.clear();
+ updateColumnHeaderLabels();
+ view->horizontalHeader()->hide();
+ }
+
+ emit loadingEnded(false);
+ if (reloading)
+ {
+ // If we were reloading, but it was interrupted, we don't want message about it.
+ if (!SqlErrorCode::isInterrupted(code))
+ emit executionFailed(tr("Error while loading query results: %1").arg(errorMessage));
+ }
+ else
+ emit executionFailed(tr("Error while executing SQL query: %1").arg(errorMessage));
+
+ restoreNumbersToQueryExecutor();
+ resultsCountingFinished(0, 0, 0);
+
+ reloading = false;
+}
+
+void SqlQueryModel::resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages)
+{
+ this->rowsAffected = rowsAffected;
+ this->totalRowsReturned = rowsReturned;
+ this->totalPages = totalPages;
+ emit totalRowsAndPagesAvailable();
+}
+
+void SqlQueryModel::itemValueEdited(SqlQueryItem* item)
+{
+ UNUSED(item);
+ emit commitStatusChanged(getUncommitedItems().size() > 0);
+}
+
+void SqlQueryModel::changeSorting(int logicalIndex, Qt::SortOrder order)
+{
+ if (!reloadAvailable)
+ return;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setSortOrder({QueryExecutor::Sort(order, logicalIndex)});
+ reloadInternal();
+}
+
+void SqlQueryModel::changeSorting(int logicalIndex)
+{
+ Qt::SortOrder newOrder = Qt::AscendingOrder;
+ if (sortOrder.size() == 1)
+ {
+ switch (sortOrder.first().order)
+ {
+ case QueryExecutor::Sort::ASC:
+ newOrder = Qt::DescendingOrder;
+ break;
+ case QueryExecutor::Sort::DESC:
+ newOrder = Qt::AscendingOrder;
+ break;
+ case QueryExecutor::Sort::NONE:
+ newOrder = Qt::AscendingOrder;
+ break;
+ }
+ }
+ changeSorting(logicalIndex, newOrder);
+}
+
+void SqlQueryModel::firstPage()
+{
+ if (!reloadAvailable)
+ return;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setPage(0);
+ reloadInternal();
+}
+
+void SqlQueryModel::prevPage()
+{
+ if (!reloadAvailable)
+ return;
+
+ int newPage = page - 1;
+ if (newPage < 0)
+ newPage = 0;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setPage(newPage);
+ reloadInternal();
+}
+
+void SqlQueryModel::nextPage()
+{
+ if (!reloadAvailable)
+ return;
+
+ int newPage = this->page + 1;
+ if ((newPage + 1) > totalPages)
+ newPage = totalPages - 1;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setPage(newPage);
+ reloadInternal();
+}
+
+void SqlQueryModel::lastPage()
+{
+ if (!reloadAvailable)
+ return;
+
+ int page = totalPages - 1;
+ if (page < 0) // this should never happen, but let's have it just in case
+ {
+ qWarning() << "Page < 0 while calling SqlQueryModel::lastPage()";
+ page = 0;
+ }
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setPage(page);
+ reloadInternal();
+}
+
+void SqlQueryModel::gotoPage(int newPage)
+{
+ if (!reloadAvailable)
+ return;
+
+ if (newPage < 0 || (newPage + 1) > totalPages)
+ newPage = 0;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setPage(newPage);
+ reloadInternal();
+}
+
+bool SqlQueryModel::canReload()
+{
+ return reloadAvailable;
+}
+
+void SqlQueryModel::storeStep1NumbersFromExecution()
+{
+ lastExecutionTime = queryExecutor->getLastExecutionTime();
+ page = queryExecutor->getPage();
+ sortOrder = queryExecutor->getSortOrder();
+
+ if (!queryExecutor->getSkipRowCounting())
+ {
+ rowsAffected = queryExecutor->getRowsAffected();
+ totalPages = queryExecutor->getTotalPages();
+ if (!queryExecutor->isRowCountingRequired())
+ totalRowsReturned = queryExecutor->getTotalRowsReturned();
+ }
+}
+
+void SqlQueryModel::storeStep2NumbersFromExecution()
+{
+ if (!queryExecutor->getSkipRowCounting())
+ {
+ if (queryExecutor->isRowCountingRequired() || rowCount() < CFG_UI.General.NumberOfRowsPerPage.get())
+ totalRowsReturned = rowCount();
+ }
+}
+
+void SqlQueryModel::restoreNumbersToQueryExecutor()
+{
+ /*
+ * Currently only page and sort order have to be restored after failed execution,
+ * so reloading current data works on the old page and order, not the ones that were
+ * requested but never loaded successfully.
+ */
+ queryExecutor->setPage(page);
+ queryExecutor->setSortOrder(sortOrder);
+ emit sortingUpdated(sortOrder);
+}
+
+Db* SqlQueryModel::getDb() const
+{
+ return db;
+}
+
+void SqlQueryModel::setDb(Db* value)
+{
+ db = value;
+ queryExecutor->setDb(db);
+}
+
+QueryExecutor::SortList SqlQueryModel::getSortOrder() const
+{
+ return sortOrder;
+}
+
+void SqlQueryModel::setSortOrder(const QueryExecutor::SortList& newSortOrder)
+{
+ sortOrder = newSortOrder;
+
+ if (!reloadAvailable)
+ return;
+
+ queryExecutor->setSkipRowCounting(true);
+ queryExecutor->setSortOrder(newSortOrder);
+ reloadInternal();
+}
+
+bool SqlQueryModel::wasSchemaModified() const
+{
+ return queryExecutor->wasSchemaModified();
+}
+
+void SqlQueryModel::updateSelectiveCommitRollbackActions(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ UNUSED(selected);
+ UNUSED(deselected);
+ QList<SqlQueryItem*> selectedItems = view->getSelectedItems();
+ bool result = false;
+ if (selectedItems.size() > 0)
+ {
+ foreach (SqlQueryItem* item, selectedItems)
+ {
+ if (item->isUncommited())
+ {
+ result = true;
+ break;
+ }
+ }
+ }
+
+ emit selectiveCommitStatusChanged(result);
+}
+
+void SqlQueryModel::addNewRowInternal(int rowIdx)
+{
+ QList<QStandardItem*> items;
+ int colCnt = columnCount();
+ SqlQueryItem* item = nullptr;
+ SqlQueryModelColumn* columnModel = nullptr;
+ for (int i = 0; i < colCnt; i++)
+ {
+ columnModel = columns[i].data();
+
+ item = new SqlQueryItem();
+ item->setNewRow(true);
+ item->setUncommited(true);
+ item->setColumn(columnModel);
+
+ items << item;
+ }
+ insertRow(rowIdx, items);
+
+ if (rowIdx == 0) // when adding first row, we need to update header
+ updateColumnHeaderLabels();
+
+ view->selectionModel()->clear();;
+ view->setCurrentRow(rowIdx);
+ view->setFocus();
+}
+
+Icon& SqlQueryModel::getIconForIdx(int idx) const
+{
+ switch (idx)
+ {
+ case 0:
+ return ICONS.SORT_COUNT_01;
+ case 1:
+ return ICONS.SORT_COUNT_02;
+ case 2:
+ return ICONS.SORT_COUNT_03;
+ case 3:
+ return ICONS.SORT_COUNT_04;
+ case 4:
+ return ICONS.SORT_COUNT_05;
+ case 5:
+ return ICONS.SORT_COUNT_06;
+ case 6:
+ return ICONS.SORT_COUNT_07;
+ case 7:
+ return ICONS.SORT_COUNT_08;
+ case 8:
+ return ICONS.SORT_COUNT_09;
+ case 9:
+ return ICONS.SORT_COUNT_10;
+ case 10:
+ return ICONS.SORT_COUNT_11;
+ case 11:
+ return ICONS.SORT_COUNT_12;
+ case 12:
+ return ICONS.SORT_COUNT_13;
+ case 13:
+ return ICONS.SORT_COUNT_14;
+ case 14:
+ return ICONS.SORT_COUNT_15;
+ case 15:
+ return ICONS.SORT_COUNT_16;
+ case 16:
+ return ICONS.SORT_COUNT_17;
+ case 17:
+ return ICONS.SORT_COUNT_18;
+ case 18:
+ return ICONS.SORT_COUNT_19;
+ case 19:
+ return ICONS.SORT_COUNT_20;
+ }
+ return ICONS.SORT_COUNT_20_PLUS;
+}
+
+void SqlQueryModel::addNewRow()
+{
+ int row = rowCount();
+ SqlQueryItem* currentItem = view->getCurrentItem();
+ if (currentItem)
+ row = currentItem->index().row();
+
+ addNewRowInternal(row);
+
+ emit commitStatusChanged(true);
+}
+
+void SqlQueryModel::addMultipleRows()
+{
+ bool ok;
+ int rows = QInputDialog::getInt(view, tr("Insert multiple rows"), tr("Number of rows to insert:"), 1, 1, 10000, 1, &ok);
+ if (!ok)
+ return;
+
+ int row = rowCount();
+ SqlQueryItem* currentItem = view->getCurrentItem();
+ if (currentItem)
+ row = currentItem->index().row();
+
+ for (int i = 0; i < rows; i++)
+ addNewRowInternal(row);
+
+ emit commitStatusChanged(true);
+}
+
+void SqlQueryModel::deleteSelectedRows()
+{
+ QList<SqlQueryItem*> selectedItems = view->getSelectedItems();
+ QSet<int> rows;
+ foreach (SqlQueryItem* item, selectedItems)
+ rows << item->index().row();
+
+ QList<int> rowList = rows.toList();
+ qSort(rowList);
+
+ QList<SqlQueryItem*> newItemsToDelete;
+ int cols = columnCount();
+ foreach (int row, rowList)
+ {
+ for (int colIdx = 0; colIdx < cols; colIdx++)
+ {
+ SqlQueryItem* item = itemFromIndex(row, colIdx);
+ if (item->isNewRow())
+ {
+ newItemsToDelete << item;
+ break;
+ }
+
+ item->setDeletedRow(true);
+ item->setUncommited(true);
+ }
+ }
+
+ foreach (SqlQueryItem* item, newItemsToDelete)
+ removeRow(item->index().row());
+
+ emit commitStatusChanged(getUncommitedItems().size() > 0);
+}
+
+void SqlQueryModel::applySqlFilter(const QString& value)
+{
+ UNUSED(value);
+ // For custom query this is not supported.
+}
+
+void SqlQueryModel::applyStringFilter(const QString& value)
+{
+ UNUSED(value);
+ // For custom query this is not supported.
+}
+
+void SqlQueryModel::applyRegExpFilter(const QString& value)
+{
+ UNUSED(value);
+ // For custom query this is not supported.
+}
+
+void SqlQueryModel::resetFilter()
+{
+ // For custom query this is not supported.
+}
+
+int SqlQueryModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return headerColumns.size();
+}
+
+QVariant SqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role == Qt::DisplayRole)
+ {
+ if (orientation == Qt::Horizontal)
+ {
+ if (section < 0 || section >= headerColumns.size())
+ return QVariant();
+
+ return headerColumns[section];
+ }
+ else
+ return rowNumBase + section;
+ }
+
+ if (role == Qt::DecorationRole && orientation == Qt::Horizontal)
+ {
+ int idx = 0;
+ for (const QueryExecutor::Sort& sort : sortOrder)
+ {
+ if (sort.column == section)
+ {
+ bool desc = sort.order == QueryExecutor::Sort::DESC;
+ return *(getIconForIdx(idx).with(desc ? Icon::SORT_DESC : Icon::SORT_ASC));
+ }
+ idx++;
+ }
+ return QVariant();
+ }
+
+ if (role == Qt::FontRole)
+ return CFG_UI.Fonts.DataView.get();
+
+ return QAbstractItemModel::headerData(section, orientation, role);
+}
+
+bool SqlQueryModel::isExecutionInProgress() const
+{
+ return queryExecutor->isExecutionInProgress();
+}
+
+void SqlQueryModel::loadFullDataForEntireRow(int row)
+{
+ int colCnt = columns.size();
+ SqlQueryItem *item = nullptr;
+ for (int col = 0; col < colCnt; col++)
+ {
+ item = itemFromIndex(row, col);
+ if (!item)
+ continue;
+
+ if (!item->isLimitedValue())
+ continue;
+
+ item->loadFullData();
+ }
+}
+
+void SqlQueryModel::CommitUpdateQueryBuilder::clear()
+{
+ database.clear();
+ table.clear();
+ columns.clear();
+ queryArgs.clear();
+ conditions.clear();
+ assignmentArgs.clear();
+}
+
+void SqlQueryModel::CommitUpdateQueryBuilder::setDatabase(const QString& database)
+{
+ this->database = database;
+}
+
+void SqlQueryModel::CommitUpdateQueryBuilder::setTable(const QString& table)
+{
+ this->table = table;
+}
+
+void SqlQueryModel::CommitUpdateQueryBuilder::setColumn(const QString& column)
+{
+ this->columns = {column};
+}
+
+void SqlQueryModel::CommitUpdateQueryBuilder::addColumn(const QString& column)
+{
+ columns << column;
+}
+
+QString SqlQueryModel::CommitUpdateQueryBuilder::build()
+{
+ QString conditionsString = RowIdConditionBuilder::build();
+
+ QString dbAndTable;
+ if (!database.isNull())
+ dbAndTable += database + ".";
+
+ dbAndTable += table;
+
+ int argIndex = 0;
+ QString arg;
+ QStringList assignments;
+ for (const QString& col : columns)
+ {
+ arg = ":value_" + QString::number(argIndex++);
+ assignmentArgs << arg;
+ assignments << col + " = " + arg;
+ }
+
+ return "UPDATE " + dbAndTable + " SET "+ assignments.join(", ") +" WHERE " + conditionsString + ";";
+}
+
+QStringList SqlQueryModel::CommitUpdateQueryBuilder::getAssignmentArgs() const
+{
+ return assignmentArgs;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h
new file mode 100644
index 0000000..cb626ae
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h
@@ -0,0 +1,444 @@
+#ifndef SQLQUERYMODEL_H
+#define SQLQUERYMODEL_H
+
+#include "db/db.h"
+#include "db/sqlquery.h"
+#include "db/queryexecutor.h"
+#include "sqlquerymodelcolumn.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "common/column.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStandardItemModel>
+#include <QItemSelection>
+
+class SqlQueryItem;
+class FormView;
+class SqlQueryView;
+class SqlQueryRowNumModel;
+
+class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+ public:
+ enum Feature
+ {
+ INSERT_ROW = 0x01,
+ DELETE_ROW = 0x02,
+ FILTERING = 0x04
+ };
+ Q_DECLARE_FLAGS(Features, Feature)
+
+ explicit SqlQueryModel(QObject *parent = 0);
+ virtual ~SqlQueryModel();
+
+ static void staticInit();
+ static int getCellDataLengthLimit();
+
+ QString getQuery() const;
+ void setQuery(const QString &value);
+ void setExplainMode(bool explain);
+ Db* getDb() const;
+ void setDb(Db* value);
+ qint64 getExecutionTime();
+ qint64 getTotalRowsReturned();
+ qint64 getTotalRowsAffected();
+ qint64 getTotalPages();
+ QList<SqlQueryModelColumnPtr> getColumns();
+ SqlQueryItem* itemFromIndex(const QModelIndex& index) const;
+ SqlQueryItem* itemFromIndex(int row, int column) const;
+ QModelIndexList findIndexes(int role, const QVariant &value, int hits = -1) const;
+ QModelIndexList findIndexes(const QModelIndex &start, const QModelIndex& end, int role, const QVariant &value, int hits = -1) const;
+ QList<SqlQueryItem*> findItems(int role, const QVariant &value, int hits = -1) const;
+ QList<SqlQueryItem*> findItems(const QModelIndex &start, const QModelIndex& end, int role, const QVariant &value, int hits = -1) const;
+ QList<SqlQueryItem*> getUncommitedItems() const;
+ QList<SqlQueryItem*> getRow(int row);
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ bool isExecutionInProgress() const;
+ void loadFullDataForEntireRow(int row);
+
+ virtual Features features() const;
+
+ /**
+ * @brief applySqlFilter
+ * @param value Filter expression.
+ * Default implementation does nothing. Working implementation (i.e. for a table)
+ * should set the query to temporary value which respects given filter and reload the data.
+ * Filter passed to this method is meant to be treated as SQL expression to be placed after WHERE clause.
+ */
+ virtual void applySqlFilter(const QString& value);
+
+ /**
+ * @brief applyStringFilter
+ * @param value Filter expression.
+ * Default implementation does nothing. Working implementation (i.e. for a table)
+ * should set the query to temporary value which respects given filter and reload the data.
+ * Filter passed to this method is meant to be treated as plain text to be matched in any column.
+ */
+ virtual void applyStringFilter(const QString& value);
+
+ /**
+ * @brief applyStringFilter
+ * @param value Filter expression.
+ * Default implementation does nothing. Working implementation (i.e. for a table)
+ * should set the query to temporary value which respects given filter and reload the data.
+ * Filter passed to this method is meant to be treated as regular expression to be matched in any column.
+ */
+ virtual void applyRegExpFilter(const QString& value);
+
+ /**
+ * @brief resetFilter
+ * Default implementation does nothing. Working implementation (i.e. for a table)
+ * should resets filter, so the data is no longer filtered.
+ */
+ virtual void resetFilter();
+
+ /**
+ * @brief getCurrentPage Gets number of current results page
+ * @param includeOneBeingLoaded If true, then also the page that is currently being loaded (but not yet done) will returned over the currently presented page.
+ * @return Current page as 0-based index. If current page is not yet defined or paging is disabled, then this method returns 0.
+ * This method returns always the page that is currently presented in results, not the one that might be currently being queried.
+ * If you need to include the one being loaded (if any), then use getLoadingPage().
+ */
+ int getCurrentPage(bool includeOneBeingLoaded = false) const;
+ void gotoPage(int newPage);
+ bool canReload();
+
+ QueryExecutor::SortList getSortOrder() const;
+ void setSortOrder(const QueryExecutor::SortList& newSortOrder);
+
+ /**
+ * @brief Tells if database schema was modified by last query executed.
+ * @return true if schema was modified, or false if not.
+ */
+ bool wasSchemaModified() const;
+
+ SqlQueryView* getView() const;
+ void setView(SqlQueryView* value);
+
+ static QList<QList<SqlQueryItem*>> groupItemsByRows(const QList<SqlQueryItem*>& items);
+ static QHash<Table, QList<SqlQueryItem*> > groupItemsByTable(const QList<SqlQueryItem*>& items);
+
+ protected:
+ class CommitUpdateQueryBuilder : public RowIdConditionBuilder
+ {
+ public:
+ void clear();
+
+ void setDatabase(const QString& database);
+ void setTable(const QString& table);
+ void setColumn(const QString& column);
+ void addColumn(const QString& column);
+
+ QString build();
+ QStringList getAssignmentArgs() const;
+
+ protected:
+ QString database;
+ QString table;
+ QStringList columns;
+ QStringList assignmentArgs;
+ };
+
+ /**
+ * @brief commitAddedRow Inserts new row to a table.
+ * @param itemsInRow All cells for the new row.
+ * @return true on success, false on failure.
+ * Default implementation does nothing and returns false, because inserting for custom query results is not possible.
+ * Inheriting class can reimplement this, so for example model specialized for single table can add rows.
+ * The method implementation should take items that are in model (and are passed to this method)
+ * and insert them into the actual database table. It also has to update items in the model,
+ * so they are no longer "new" and have the same data as inserted into the database.
+ */
+ virtual bool commitAddedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ /**
+ * @brief commitEditedRow Updates table row with new values.
+ * @param itemsInRow Modified cell values.
+ * @return true on success, false on failure.
+ * Default implementation should be okay for most cases. It takes all modified cells and updates their
+ * values in table basing on the ROWID, database, table and column names - which are all available,
+ * unless the cell doesn't referr to the table, but in that case the cell should not be editable for user anyway.
+ * <b>Important</b> thing to pay attention to is that the item list passed in arguments contains <b>only modified items</b>.
+ */
+ virtual bool commitEditedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ /**
+ * @brief commitDeletedRow Deletes row from the table.
+ * @param itemsInRow All cells for the deleted row.
+ * @return true on success, false on failure.
+ * Default implementation gets rid of row items from the model and that's all.
+ * Inheriting class can reimplement this, so for example model specialized for single table can delete rows.
+ * The method implementation should delete the row from the database.
+ */
+ virtual bool commitDeletedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ /**
+ * @brief rollbackAddedRow
+ * @param itemsInRow All cells for the new row.
+ * Default implementation gets rid of row items from the model and that's all.
+ */
+ virtual void rollbackAddedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ /**
+ * @brief rollbackEditedRow
+ * @param itemsInRow All cells for the deleted row.
+ * Restores original values in items.
+ */
+ virtual void rollbackEditedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ /**
+ * @brief rollbackDeletedRow
+ * @param itemsInRow Modified cell values.
+ * The implementation should restore original values to items in the model.
+ * The default implementation is pretty much complete. It restores original state of row items.
+ */
+ virtual void rollbackDeletedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ SqlQueryModelColumnPtr getColumnModel(const QString& database, const QString& table, const QString& column);
+ SqlQueryModelColumnPtr getColumnModel(const QString& table, const QString& column);
+ QList<SqlQueryModelColumnPtr> getTableColumnModels(const QString& database, const QString& table);
+ QList<SqlQueryModelColumnPtr> getTableColumnModels(const QString& table);
+ void updateItem(SqlQueryItem* item, const QVariant& value, int columnIndex, const RowId& rowId);
+ RowId getNewRowId(const RowId& currentRowId, const QList<SqlQueryItem*> items);
+ void updateRowIdForAllItems(const Table& table, const RowId& rowId, const RowId& newRowId);
+
+ QueryExecutor* queryExecutor = nullptr;
+ Db* db = nullptr;
+ QList<SqlQueryModelColumnPtr> columns;
+
+ /**
+ * @brief Limit of data length in loaded cells.
+ *
+ * Bytes or utf-8 characters.
+ * Having this set to 10000 gives about 290 MB of memory consumption
+ * while having 30 columns and 1000 result rows loaded, all with 10000 bytes.
+ */
+ static const int cellDataLengthLimit = 10000;
+
+ private:
+ struct TableDetails
+ {
+ struct ColumnDetails
+ {
+ SqliteColumnTypePtr type;
+ QList<SqliteCreateTable::Column::ConstraintPtr> constraints;
+ };
+
+ QHash<QString,ColumnDetails> columns;
+ QList<SqliteCreateTable::ConstraintPtr> constraints;
+ };
+
+ void loadData(SqlQueryPtr results);
+ QList<QStandardItem*> loadRow(SqlResultsRowPtr row);
+ RowId getRowIdValue(SqlResultsRowPtr row, int columnIdx);
+ void readColumns();
+ void readColumnDetails();
+ void updateColumnsHeader();
+ void updateColumnHeaderLabels();
+ void executeQueryInternal();
+ void internalExecutionStopped();
+ QHash<Table,TableDetails> readTableDetails();
+ QList<Table> getTablesForColumns();
+ QList<bool> getColumnEditionEnabledList();
+ QList<SqlQueryItem*> toItemList(const QModelIndexList& indexes) const;
+ bool commitRow(const QList<SqlQueryItem*>& itemsInRow);
+ void rollbackRow(const QList<SqlQueryItem*>& itemsInRow);
+ void storeStep1NumbersFromExecution();
+ void storeStep2NumbersFromExecution();
+ void restoreNumbersToQueryExecutor();
+ QList<SqlQueryItem*> filterOutCommitedItems(const QList<SqlQueryItem*>& items);
+ void commitInternal(const QList<SqlQueryItem*>& items);
+ void rollbackInternal(const QList<SqlQueryItem*>& items);
+ void reloadInternal();
+ void addNewRowInternal(int rowIdx);
+ Icon& getIconForIdx(int idx) const;
+
+ QString query;
+ bool explain = false;
+
+ /**
+ * @brief reloadAvailable
+ * This value is false by default and is changed only once - after first successful
+ * query execution. It's designed to report proper status by canReload().
+ * Data reloading is available to user practically after any query was executed.
+ */
+ bool reloadAvailable = false;
+
+ /**
+ * @brief reloading
+ * This switch tells you if model is in the process of data reloading (true value)
+ * or initial query execution (then it's false). Data reloading takes place in any case
+ * when page is changed, order is changed, or simply user calls the data reloading.
+ * The initial query execution takes place when user calls "Execute query",
+ * which is translated to call to executeQuery().
+ */
+ bool reloading = false;
+
+ /**
+ * @brief lastExecutionTime
+ * Keeps number of milliseconds that recently successfully executed query took to complete.
+ * If there was no such query executed, this will be 0.
+ */
+ quint64 lastExecutionTime = 0;
+
+ /**
+ * @brief totalRowsReturned
+ * Keeps number of rows returned from recently successfully executed query.
+ * If there was no such query executed, this will be 0.
+ */
+ quint64 totalRowsReturned = 0;
+
+ /**
+ * @brief rowsAffected
+ * Keeps number of rows affected by recently successfully executed query.
+ * If there was no such query executed, this will be 0.
+ */
+ quint64 rowsAffected = 0;
+
+ /**
+ * @brief totalPages
+ * Keeps number of pages available in recently successfully executed query.
+ * If there was no such query executed, this will be -1.
+ */
+ int totalPages = -1;
+
+ /**
+ * @brief page
+ * The page variable keeps page of recently sucessfly loaded data.
+ * If there was no successful data load, or when paging is disabled, then this will be -1.
+ */
+ int page = -1;
+
+ /**
+ * @brief sortOrder
+ * The sortOrder variable keeps sorting order of recently sucessfly loaded data.
+ * If column member of the sort object is -1, then no sorting is being aplied.
+ */
+ QueryExecutor::SortList sortOrder;
+
+ QHash<Column,SqlQueryModelColumnPtr> columnMap;
+ QHash<Table,QHash<QString,QString>> tableToRowIdColumn;
+ QStringList headerColumns;
+ int rowNumBase = 0;
+ SqlQueryView* view = nullptr;
+ quint32 resultsCountingAsyncId = 0;
+
+ /**
+ * @brief rowIdColumns
+ * We skip first this number of columns from the results of the SQL query, because those are ROWID columns.
+ * The query returns ROWID columns, because this is how QueryExecutor provides this information.
+ */
+ int rowIdColumns = 0;
+
+ /**
+ * @brief tablesForColumns
+ * List of tables associated to \link #columns by order index.
+ */
+ QList<Table> tablesForColumns;
+
+ /**
+ * @brief columnEditionStatus
+ * List of column edition capabilities, in the same order as \link #columns.
+ */
+ QList<bool> columnEditionStatus;
+
+ private slots:
+ void handleExecFinished(SqlQueryPtr results);
+ void handleExecFailed(int code, QString errorMessage);
+ void resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages);
+
+ public slots:
+ void itemValueEdited(SqlQueryItem* item);
+ void changeSorting(int logicalIndex, Qt::SortOrder order);
+ void changeSorting(int logicalIndex);
+ void firstPage();
+ void prevPage();
+ void nextPage();
+ void lastPage();
+ void executeQuery();
+ void interrupt();
+ void commit();
+ void rollback();
+ void commit(const QList<SqlQueryItem*>& items);
+ void rollback(const QList<SqlQueryItem*>& items);
+ void reload();
+ void updateSelectiveCommitRollbackActions(const QItemSelection& selected, const QItemSelection& deselected);
+ void addNewRow();
+ void addMultipleRows();
+ void deleteSelectedRows();
+
+ signals:
+ /**
+ * @brief executionStarted
+ *
+ * Emitted just after query started executing.
+ */
+ void executionStarted();
+
+ /**
+ * @brief executionSuccessful
+ *
+ * Emitted after initial query execution was successful. It's not emitted after data reloading of page changing.
+ */
+ void executionSuccessful();
+
+ /**
+ * @brief executionFailed
+ * @param errorText
+ *
+ * Emitted after failed query execution, or data reloading failed or page changing failed.
+ */
+ void executionFailed(const QString& errorText);
+
+ /**
+ * @brief loadingEnded
+ * @param executionSuccessful
+ *
+ * Emitted every query execution, every data reloading and every page change.
+ */
+ void loadingEnded(bool executionSuccessful);
+
+ /**
+ * @brief totalRowsAndPagesAvailable
+ *
+ * Emitted when model finished querying total number of rows (and pages).
+ * This is asynchronously emitted after execution has finished, so counting doesn't block the model.
+ * It might not get emitted in some cases, like when there was an error when counting (it will be logged with qWarning()),
+ * or when counting was interrupted by executing query (the same, or modified).
+ *
+ * When the main query execution failed, this signal will be emitted to inform about total rows and pages being 0.
+ */
+ void totalRowsAndPagesAvailable();
+
+ /**
+ * @brief commitStatusChanged
+ * @param commitAvailable Tells if there's anything to commit/rollback or not.
+ *
+ * Emitted after any results cell has been modified and can now be commited or rolled back.
+ * Also emitted after commit and rollback.
+ */
+ void commitStatusChanged(bool commitAvailable);
+
+ /**
+ * @brief selectiveCommitStatusChanged
+ * @param commitAvailable Tells if there's anything to commit/rollback or not.
+ *
+ * Emitted when user changes selection in the view, so if the selection includes any uncommited cells,
+ * then this signal will be emitted with parameter true, or if there is no uncommited cells,
+ * then it will be emitted with parameter false.
+ */
+ void selectiveCommitStatusChanged(bool commitAvailable);
+
+ /**
+ * @brief sortIndicatorUpdated
+ *
+ * Emitted after columns header sorting has been changed.
+ */
+ void sortingUpdated(const QueryExecutor::SortList& sortOrder);
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(SqlQueryModel::Features)
+
+#endif // SQLQUERYMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp
new file mode 100644
index 0000000..62d0b45
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp
@@ -0,0 +1,465 @@
+#include "sqlquerymodelcolumn.h"
+#include "iconmanager.h"
+#include <QDebug>
+
+SqlQueryModelColumn::SqlQueryModelColumn(const QueryExecutor::ResultColumnPtr& resultColumn)
+{
+ displayName = resultColumn->displayName;
+ column = resultColumn->column;
+ table = resultColumn->table;
+ database = resultColumn->database.isEmpty() ? "main": resultColumn->database;
+ foreach (QueryExecutor::ColumnEditionForbiddenReason reason, resultColumn->editionForbiddenReasons)
+ editionForbiddenReason << SqlQueryModelColumn::convert(reason);
+}
+
+SqlQueryModelColumn::~SqlQueryModelColumn()
+{
+ foreach (Constraint* constr, constraints)
+ delete constr;
+
+ constraints.clear();
+}
+
+void SqlQueryModelColumn::initMeta()
+{
+ qRegisterMetaType<SqlQueryModelColumn*>("SqlQueryModelColumn*");
+ qRegisterMetaTypeStreamOperators<SqlQueryModelColumn*>("SqlQueryModelColumn*");
+}
+
+SqlQueryModelColumn::EditionForbiddenReason SqlQueryModelColumn::convert(QueryExecutor::EditionForbiddenReason reason)
+{
+ switch (reason)
+ {
+ case QueryExecutor::EditionForbiddenReason::NOT_A_SELECT:
+ return EditionForbiddenReason::NOT_A_SELECT;
+ case QueryExecutor::EditionForbiddenReason::SMART_EXECUTION_FAILED:
+ return EditionForbiddenReason::SMART_EXECUTION_FAILED;
+ }
+ return static_cast<EditionForbiddenReason>(-1);
+}
+
+SqlQueryModelColumn::EditionForbiddenReason SqlQueryModelColumn::convert(QueryExecutor::ColumnEditionForbiddenReason reason)
+{
+ switch (reason)
+ {
+ case QueryExecutor::ColumnEditionForbiddenReason::EXPRESSION:
+ return EditionForbiddenReason::EXPRESSION;
+ case QueryExecutor::ColumnEditionForbiddenReason::SYSTEM_TABLE:
+ return EditionForbiddenReason::SYSTEM_TABLE;
+ case QueryExecutor::ColumnEditionForbiddenReason::COMPOUND_SELECT:
+ return EditionForbiddenReason::COMPOUND_SELECT;
+ case QueryExecutor::ColumnEditionForbiddenReason::GROUPED_RESULTS:
+ return EditionForbiddenReason::GROUPED_RESULTS;
+ case QueryExecutor::ColumnEditionForbiddenReason::DISTINCT_RESULTS:
+ return EditionForbiddenReason::DISTINCT_RESULTS;
+ case QueryExecutor::ColumnEditionForbiddenReason::COMM_TAB_EXPR:
+ return EditionForbiddenReason::COMMON_TABLE_EXPRESSION;
+ }
+ return static_cast<EditionForbiddenReason>(-1);
+}
+
+QString SqlQueryModelColumn::resolveMessage(SqlQueryModelColumn::EditionForbiddenReason reason)
+{
+ switch (reason)
+ {
+ case EditionForbiddenReason::COMPOUND_SELECT:
+ return QObject::tr("Cannot edit columns that are result of compound SELECT statements (one that includes UNION, INTERSECT or EXCEPT keywords).");
+ case EditionForbiddenReason::SMART_EXECUTION_FAILED:
+ return QObject::tr("The query execution mechanism had problems with extracting ROWID's properly. This might be a bug in the application. You may want to report this.");
+ case EditionForbiddenReason::EXPRESSION:
+ return QObject::tr("Requested column is a result of SQL expression, instead of a simple column selection. Such columns cannot be edited.");
+ case EditionForbiddenReason::SYSTEM_TABLE:
+ return QObject::tr("Requested column belongs to restricted SQLite table. Those tables cannot be edited directly.");
+ case EditionForbiddenReason::NOT_A_SELECT:
+ return QObject::tr("Cannot edit results of query other than SELECT.");
+ case EditionForbiddenReason::GROUPED_RESULTS:
+ return QObject::tr("Cannot edit columns that are result of aggregated SELECT statements.");
+ case EditionForbiddenReason::DISTINCT_RESULTS:
+ return QObject::tr("Cannot edit columns that are result of SELECT DISTINCT statement.");
+ case EditionForbiddenReason::COMMON_TABLE_EXPRESSION:
+ return QObject::tr("Cannot edit columns that are result of common table expression statement (%1).").arg("WITH ... SELECT ...");
+ }
+ qCritical() << "Reached null text message for SqlQueryModel::EditionForbiddenReason. This should not happen!";
+ return QString::null;
+}
+
+bool SqlQueryModelColumn::isNumeric()
+{
+ return dataType.isNumeric();
+}
+
+bool SqlQueryModelColumn::canEdit()
+{
+ return editionForbiddenReason.size() == 0;
+}
+
+QString SqlQueryModelColumn::getEditionForbiddenReason()
+{
+ if (canEdit())
+ return QString::null;
+
+ // We sort reasons to get most significant reason at first position.
+ QList<EditionForbiddenReason> list = editionForbiddenReason.toList();
+ qSort(list);
+ return resolveMessage(list[0]);
+}
+
+bool SqlQueryModelColumn::isPk() const
+{
+ return getConstraints<ConstraintPk*>().size() > 0;
+}
+
+bool SqlQueryModelColumn::isRowIdPk() const
+{
+ if (dataType.getType() != DataType::INTEGER)
+ return false;
+
+ foreach (ConstraintPk* pk, getConstraints<ConstraintPk*>())
+ if (pk->scope == Constraint::Scope::COLUMN)
+ return true;
+
+ return false;
+}
+
+bool SqlQueryModelColumn::isAutoIncr() const
+{
+ foreach (ConstraintPk* pk, getConstraints<ConstraintPk*>())
+ if (pk->autoIncrement)
+ return true;
+
+ return false;
+}
+
+bool SqlQueryModelColumn::isNotNull() const
+{
+ return getConstraints<ConstraintNotNull*>().size() > 0;
+}
+
+bool SqlQueryModelColumn::isUnique() const
+{
+ return getConstraints<ConstraintUnique*>().size() > 0;
+}
+
+bool SqlQueryModelColumn::isFk() const
+{
+ return getConstraints<ConstraintFk*>().size() > 0;
+}
+
+bool SqlQueryModelColumn::isDefault() const
+{
+ return getConstraints<ConstraintDefault*>().size() > 0;
+}
+
+bool SqlQueryModelColumn::isCollate() const
+{
+ return getConstraints<ConstraintCollate*>().size() > 0;
+}
+
+QList<SqlQueryModelColumn::ConstraintFk*> SqlQueryModelColumn::getFkConstraints() const
+{
+ return getConstraints<ConstraintFk*>();
+}
+
+SqlQueryModelColumn::ConstraintDefault* SqlQueryModelColumn::getDefaultConstraint() const
+{
+ QList<ConstraintDefault*> list = getConstraints<ConstraintDefault*>();
+ if (list.size() == 0)
+ return nullptr;
+
+ return list[0];
+}
+
+int qHash(SqlQueryModelColumn::EditionForbiddenReason reason)
+{
+ return static_cast<int>(reason);
+}
+
+QDataStream&operator <<(QDataStream& out, const SqlQueryModelColumn* col)
+{
+ out << reinterpret_cast<quint64>(col);
+ return out;
+}
+
+QDataStream&operator >>(QDataStream& in, SqlQueryModelColumn*& col)
+{
+ quint64 ptr;
+ in >> ptr;
+ col = reinterpret_cast<SqlQueryModelColumn*>(ptr);
+ return in;
+}
+
+
+SqlQueryModelColumn::Constraint* SqlQueryModelColumn::Constraint::create(const QString& column, SqliteCreateTable::ConstraintPtr tableConstraint)
+{
+ Constraint* constr = nullptr;
+ switch (tableConstraint->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ {
+ if (!tableConstraint->doesAffectColumn(column))
+ return nullptr;
+
+ constr = new ConstraintPk();
+ constr->type = Type::PRIMARY_KEY;
+ break;
+ }
+ case SqliteCreateTable::Constraint::UNIQUE:
+ {
+ constr = new ConstraintUnique();
+ constr->type = Type::UNIQUE;
+ break;
+ }
+ case SqliteCreateTable::Constraint::CHECK:
+ {
+ ConstraintCheck* check = new ConstraintCheck();
+ check->condition = tableConstraint->expr->detokenize();
+ constr = check;
+ constr->type = Type::CHECK;
+ break;
+ }
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ {
+ int idx = tableConstraint->getAffectedColumnIdx(column);
+ if (idx < 0 || tableConstraint->foreignKey->indexedColumns.size() <= idx)
+ {
+ qWarning() << "Could not find FK column for definition:" << tableConstraint->detokenize();
+ return nullptr;
+ }
+
+ ConstraintFk* fk = new ConstraintFk();
+ fk->foreignTable = tableConstraint->foreignKey->foreignTable;
+ fk->foreignColumn = tableConstraint->foreignKey->indexedColumns[idx]->name;
+
+ constr = fk;
+ constr->type = Type::FOREIGN_KEY;
+ break;
+ }
+ default:
+ return nullptr;
+ }
+
+ constr->scope = Scope::TABLE;
+ constr->definition = tableConstraint->detokenize();
+ return constr;
+}
+
+SqlQueryModelColumn::Constraint* SqlQueryModelColumn::Constraint::create(SqliteCreateTable::Column::ConstraintPtr columnConstraint)
+{
+ Constraint* constr = nullptr;
+ switch (columnConstraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ {
+ ConstraintPk* pk = new ConstraintPk();
+ pk->autoIncrement = columnConstraint->autoincrKw;
+ constr = pk;
+ constr->type = Type::PRIMARY_KEY;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ {
+ constr = new ConstraintNotNull();
+ constr->type = Type::NOT_NULL;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ {
+ constr = new ConstraintUnique();
+ constr->type = Type::UNIQUE;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ {
+ ConstraintCheck* check = new ConstraintCheck();
+ check->condition = columnConstraint->expr->detokenize();
+ constr = check;
+ constr->type = Type::CHECK;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ {
+ ConstraintDefault* def = new ConstraintDefault();
+ if (!columnConstraint->id.isNull())
+ def->defaultValue = columnConstraint->id;
+ else if (!columnConstraint->ctime.isNull())
+ def->defaultValue = columnConstraint->ctime;
+ else if (columnConstraint->expr)
+ def->defaultValue = columnConstraint->expr->detokenize();
+ else
+ def->defaultValue = columnConstraint->literalValue.toString();
+
+ constr = def;
+ constr->type = Type::DEFAULT;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ {
+ ConstraintCollate* collate = new ConstraintCollate();
+ collate->collationName = columnConstraint->collationName;
+ constr = collate;
+ constr->type = Type::COLLATE;
+ break;
+ }
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ {
+ if (columnConstraint->foreignKey->indexedColumns.size() == 0)
+ {
+ qWarning() << "No foreign column defined for FK column constraint while creating SqlQueryModelColumn::Constraint.";
+ return nullptr;
+ }
+
+ ConstraintFk* fk = new ConstraintFk();
+ fk->foreignTable = columnConstraint->foreignKey->foreignTable;
+ fk->foreignColumn = columnConstraint->foreignKey->indexedColumns.first()->name;
+
+ constr = fk;
+ constr->type = Type::FOREIGN_KEY;
+ break;
+ }
+ default:
+ return nullptr;
+ }
+
+ constr->scope = Scope::COLUMN;
+ constr->definition = columnConstraint->detokenize();
+ return constr;
+}
+
+template <class T>
+QList<T> SqlQueryModelColumn::getConstraints() const
+{
+ QList<T> results;
+ foreach (Constraint* constr, constraints)
+ if (dynamic_cast<T>(constr))
+ results << dynamic_cast<T>(constr);
+
+ return results;
+}
+
+
+QString SqlQueryModelColumn::ConstraintPk::getTypeString() const
+{
+ return "PRIMARY KEY";
+}
+
+QString SqlQueryModelColumn::ConstraintPk::getDetails() const
+{
+ QStringList detailList;
+ if (autoIncrement)
+ detailList << "AUTOINCREMENT";
+
+ if (onConflict != SqliteConflictAlgo::null)
+ detailList << QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict));
+
+ if (detailList.size() > 0)
+ return "("+detailList.join(", ")+")";
+
+ return "";
+}
+
+Icon* SqlQueryModelColumn::ConstraintPk::getIcon() const
+{
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+}
+
+QString SqlQueryModelColumn::ConstraintFk::getTypeString() const
+{
+ return "FOREIGN KEY";
+}
+
+QString SqlQueryModelColumn::ConstraintFk::getDetails() const
+{
+ return "("+QObject::tr("references table %1, column %2", "data view tooltip").arg(foreignTable).arg(foreignColumn)+")";
+}
+
+Icon* SqlQueryModelColumn::ConstraintFk::getIcon() const
+{
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+}
+
+QString SqlQueryModelColumn::ConstraintUnique::getTypeString() const
+{
+ return "UNIQUE";
+}
+
+QString SqlQueryModelColumn::ConstraintUnique::getDetails() const
+{
+ if (onConflict != SqliteConflictAlgo::null)
+ return "("+QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict))+")";
+
+ return QString::null;
+}
+
+Icon* SqlQueryModelColumn::ConstraintUnique::getIcon() const
+{
+ return ICONS.CONSTRAINT_UNIQUE;
+}
+
+QString SqlQueryModelColumn::ConstraintNotNull::getTypeString() const
+{
+ return "NOT NULL";
+}
+
+QString SqlQueryModelColumn::ConstraintNotNull::getDetails() const
+{
+ if (onConflict != SqliteConflictAlgo::null)
+ return "("+QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict))+")";
+
+ return QString::null;
+}
+
+Icon* SqlQueryModelColumn::ConstraintNotNull::getIcon() const
+{
+ return ICONS.CONSTRAINT_NOT_NULL;
+}
+
+QString SqlQueryModelColumn::ConstraintDefault::getTypeString() const
+{
+ return "DEFAULT";
+}
+
+QString SqlQueryModelColumn::ConstraintDefault::getDetails() const
+{
+ return "("+defaultValue+")";
+}
+
+Icon* SqlQueryModelColumn::ConstraintDefault::getIcon() const
+{
+ return ICONS.CONSTRAINT_DEFAULT;
+}
+
+QString SqlQueryModelColumn::ConstraintCheck::getTypeString() const
+{
+ return "CHECK";
+}
+
+QString SqlQueryModelColumn::ConstraintCheck::getDetails() const
+{
+ QStringList detailList;
+ detailList << QObject::tr("condition: %1", "data view tooltip").arg(condition);
+
+ if (onConflict != SqliteConflictAlgo::null)
+ detailList << QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict));
+
+ return "("+detailList.join(", ")+")";
+}
+
+Icon* SqlQueryModelColumn::ConstraintCheck::getIcon() const
+{
+ return ICONS.CONSTRAINT_CHECK;
+}
+
+QString SqlQueryModelColumn::ConstraintCollate::getTypeString() const
+{
+ return "COLLATE";
+}
+
+QString SqlQueryModelColumn::ConstraintCollate::getDetails() const
+{
+ return "("+QObject::tr("collation name: %1", "data view tooltip").arg(collationName)+")";
+}
+
+Icon* SqlQueryModelColumn::ConstraintCollate::getIcon() const
+{
+ return ICONS.CONSTRAINT_COLLATION;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h
new file mode 100644
index 0000000..fb55fe5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h
@@ -0,0 +1,177 @@
+#ifndef SQLQUERYMODELCOLUMN_H
+#define SQLQUERYMODELCOLUMN_H
+
+#include "db/queryexecutor.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "datatype.h"
+#include "common/global.h"
+#include "guiSQLiteStudio_global.h"
+
+class Icon;
+
+class GUI_API_EXPORT SqlQueryModelColumn
+{
+ public:
+ /**
+ * @brief The EditionForbiddenReason enum
+ * Order of this enum is important, because when user requests item edition,
+ * then reason for edition forbidden (if any) is taken as specified order.
+ * The earlier item is in the enum, the more significant it is and user
+ * will be notified with the more significant reason before any other.
+ */
+ enum class EditionForbiddenReason
+ {
+ SYSTEM_TABLE,
+ NOT_A_SELECT,
+ COMPOUND_SELECT,
+ GROUPED_RESULTS,
+ EXPRESSION,
+ SMART_EXECUTION_FAILED,
+ DISTINCT_RESULTS,
+ COMMON_TABLE_EXPRESSION
+ };
+
+ struct Constraint
+ {
+ enum class Type
+ {
+ PRIMARY_KEY,
+ NOT_NULL,
+ UNIQUE,
+ CHECK,
+ DEFAULT,
+ COLLATE,
+ FOREIGN_KEY,
+ null
+ };
+
+ enum class Scope
+ {
+ TABLE,
+ COLUMN
+ };
+
+ virtual ~Constraint() {}
+
+ static Constraint* create(const QString& column, SqliteCreateTable::ConstraintPtr tableConstraint);
+ static Constraint* create(SqliteCreateTable::Column::ConstraintPtr columnConstraint);
+
+ virtual QString getTypeString() const = 0;
+ virtual QString getDetails() const = 0;
+ virtual Icon* getIcon() const = 0;
+
+ Type type;
+ Scope scope;
+ QString definition;
+ };
+
+ struct ConstraintPk : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ bool autoIncrement;
+ SqliteConflictAlgo onConflict = SqliteConflictAlgo::null;
+ };
+
+ struct ConstraintFk : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ QString foreignTable;
+ QString foreignColumn;
+ };
+
+ struct ConstraintUnique : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ SqliteConflictAlgo onConflict = SqliteConflictAlgo::null;
+ };
+
+ struct ConstraintNotNull : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ SqliteConflictAlgo onConflict = SqliteConflictAlgo::null;
+ };
+
+ struct ConstraintDefault : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ QString defaultValue;
+ };
+
+ struct ConstraintCheck : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ QString condition;
+ SqliteConflictAlgo onConflict = SqliteConflictAlgo::null;
+ };
+
+ struct ConstraintCollate : public Constraint
+ {
+ QString getTypeString() const;
+ QString getDetails() const;
+ Icon* getIcon() const;
+
+ QString collationName;
+ };
+
+ SqlQueryModelColumn(const QueryExecutor::ResultColumnPtr& resultColumn);
+ virtual ~SqlQueryModelColumn();
+
+ static void initMeta();
+ static EditionForbiddenReason convert(QueryExecutor::EditionForbiddenReason reason);
+ static EditionForbiddenReason convert(QueryExecutor::ColumnEditionForbiddenReason reason);
+ static QString resolveMessage(EditionForbiddenReason reason);
+ bool isNumeric();
+ bool canEdit();
+ QString getEditionForbiddenReason();
+ bool isPk() const;
+ bool isRowIdPk() const;
+ bool isAutoIncr() const;
+ bool isNotNull() const;
+ bool isUnique() const;
+ bool isFk() const;
+ bool isDefault() const;
+ bool isCollate() const;
+ QList<ConstraintFk*> getFkConstraints() const;
+ ConstraintDefault* getDefaultConstraint() const;
+
+ QString displayName;
+ QString column;
+ QString table;
+ QString database;
+ DataType dataType;
+ QSet<EditionForbiddenReason> editionForbiddenReason;
+ QList<Constraint*> constraints;
+
+ private:
+ template <class T>
+ QList<T> getConstraints() const;
+};
+
+typedef QSharedPointer<SqlQueryModelColumn> SqlQueryModelColumnPtr;
+
+int qHash(SqlQueryModelColumn::EditionForbiddenReason reason);
+
+QDataStream &operator<<(QDataStream &out, const SqlQueryModelColumn* col);
+QDataStream &operator>>(QDataStream &in, SqlQueryModelColumn*& col);
+
+Q_DECLARE_METATYPE(SqlQueryModelColumn*)
+
+#endif // SQLQUERYMODELCOLUMN_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp
new file mode 100644
index 0000000..f494bd9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp
@@ -0,0 +1,63 @@
+#include "sqlqueryrownummodel.h"
+#include "common/unused.h"
+
+SqlQueryRowNumModel::SqlQueryRowNumModel(QAbstractItemModel *value, QObject *parent) :
+ QAbstractItemModel(parent)
+{
+ mainModel = value;
+}
+
+QModelIndex SqlQueryRowNumModel::index(int row, int column, const QModelIndex &parent) const
+{
+ UNUSED(row);
+ UNUSED(column);
+ UNUSED(parent);
+ return QModelIndex();
+}
+
+QModelIndex SqlQueryRowNumModel::parent(const QModelIndex &child) const
+{
+ UNUSED(child);
+ return QModelIndex();
+}
+
+int SqlQueryRowNumModel::rowCount(const QModelIndex &parent) const
+{
+ UNUSED(parent);
+
+ if (!mainModel)
+ return 0;
+
+ return mainModel->rowCount();
+}
+
+int SqlQueryRowNumModel::columnCount(const QModelIndex &parent) const
+{
+ UNUSED(parent);
+
+ if (!mainModel)
+ return 0;
+
+ return mainModel->columnCount();
+}
+
+QVariant SqlQueryRowNumModel::data(const QModelIndex &index, int role) const
+{
+ UNUSED(index);
+ UNUSED(role);
+ return QVariant();
+}
+
+QVariant SqlQueryRowNumModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ UNUSED(role);
+ if (orientation == Qt::Horizontal)
+ return QVariant();
+
+ return rowNumBase + section;
+}
+
+void SqlQueryRowNumModel::setRowNumBase(int value)
+{
+ rowNumBase = value;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h
new file mode 100644
index 0000000..962e1d3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h
@@ -0,0 +1,27 @@
+#ifndef SQLQUERYROWNUMMODEL_H
+#define SQLQUERYROWNUMMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractItemModel>
+
+class GUI_API_EXPORT SqlQueryRowNumModel : public QAbstractItemModel
+{
+ Q_OBJECT
+ public:
+ SqlQueryRowNumModel(QAbstractItemModel *value, QObject *parent = 0);
+
+ QModelIndex index(int row, int column, const QModelIndex &parent) const;
+ QModelIndex parent(const QModelIndex &child) const;
+ int rowCount(const QModelIndex &parent) const;
+ int columnCount(const QModelIndex &parent) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+ void setRowNumBase(int value);
+
+ private:
+ int rowNumBase = 1;
+ QAbstractItemModel* mainModel = nullptr;
+};
+
+#endif // SQLQUERYROWNUMMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp
new file mode 100644
index 0000000..e4a0656
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp
@@ -0,0 +1,454 @@
+#include "sqlqueryview.h"
+#include "sqlqueryitemdelegate.h"
+#include "sqlquerymodel.h"
+#include "sqlqueryitem.h"
+#include "common/widgetcover.h"
+#include "tsvserializer.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include "common/extaction.h"
+#include "multieditor/multieditor.h"
+#include "multieditor/multieditordialog.h"
+#include "uiconfig.h"
+#include "dialogs/sortdialog.h"
+#include <QHeaderView>
+#include <QPushButton>
+#include <QProgressBar>
+#include <QGridLayout>
+#include <QDebug>
+#include <QFocusEvent>
+#include <QApplication>
+#include <QClipboard>
+#include <QAction>
+#include <QMenu>
+
+CFG_KEYS_DEFINE(SqlQueryView)
+
+SqlQueryView::SqlQueryView(QWidget *parent) :
+ QTableView(parent)
+{
+ init();
+}
+
+SqlQueryView::~SqlQueryView()
+{
+ delete itemDelegate;
+}
+
+QList<SqlQueryItem*> SqlQueryView::getSelectedItems()
+{
+ QList<SqlQueryItem*> items;
+ QModelIndexList idxList = selectionModel()->selectedIndexes();
+ QModelIndex currIdx = getCurrentIndex();
+ if (!idxList.contains(currIdx) && currIdx.isValid())
+ idxList << currIdx;
+
+ if (idxList.size() == 0)
+ return items;
+
+ qSort(idxList);
+ const SqlQueryModel* model = dynamic_cast<const SqlQueryModel*>(idxList.first().model());
+ foreach (const QModelIndex& idx, idxList)
+ items << model->itemFromIndex(idx);
+
+ return items;
+}
+
+SqlQueryItem* SqlQueryView::getCurrentItem()
+{
+ QModelIndex idx = getCurrentIndex();
+ if (!idx.isValid())
+ return nullptr;
+
+ return getModel()->itemFromIndex(idx);
+}
+
+SqlQueryModel* SqlQueryView::getModel()
+{
+ return dynamic_cast<SqlQueryModel*>(model());
+}
+
+void SqlQueryView::setModel(QAbstractItemModel* model)
+{
+ QTableView::setModel(model);
+ connect(widgetCover, SIGNAL(cancelClicked()), getModel(), SLOT(interrupt()));
+ connect(getModel(), &SqlQueryModel::commitStatusChanged, this, &SqlQueryView::updateCommitRollbackActions);
+ connect(getModel(), &SqlQueryModel::sortingUpdated, this, &SqlQueryView::sortingUpdated);
+}
+
+SqlQueryItem* SqlQueryView::itemAt(const QPoint& pos)
+{
+ return dynamic_cast<SqlQueryItem*>(getModel()->itemFromIndex(indexAt(pos)));
+}
+
+QToolBar* SqlQueryView::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void SqlQueryView::addAdditionalAction(QAction* action)
+{
+ additionalActions << action;
+}
+
+QModelIndex SqlQueryView::getCurrentIndex() const
+{
+ return currentIndex();
+}
+
+void SqlQueryView::mouseDoubleClickEvent(QMouseEvent* event)
+{
+ SqlQueryItem* item = itemAt(event->pos());
+ if (item && !handleDoubleClick(item))
+ return;
+
+ QTableView::mouseDoubleClickEvent(event);
+}
+
+void SqlQueryView::init()
+{
+ itemDelegate = new SqlQueryItemDelegate();
+ setItemDelegate(itemDelegate);
+ setMouseTracking(true);
+
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ contextMenu = new QMenu(this);
+
+ connect(this, &QWidget::customContextMenuRequested, this, &SqlQueryView::customContextMenuRequested);
+ connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont()));
+
+ horizontalHeader()->setSortIndicatorShown(false);
+ horizontalHeader()->setSectionsClickable(true);
+ updateFont();
+
+ setupWidgetCover();
+ initActions();
+ setupHeaderMenu();
+}
+
+void SqlQueryView::setupWidgetCover()
+{
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer();
+}
+
+void SqlQueryView::createActions()
+{
+ createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this);
+ createAction(COPY_AS, ICONS.ACT_COPY, tr("Copy as..."), this, SLOT(copyAs()), this);
+ createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this);
+ createAction(PASTE_AS, ICONS.ACT_PASTE, tr("Paste as..."), this, SLOT(pasteAs()), this);
+ createAction(SET_NULL, ICONS.SET_NULL, tr("Set NULL values"), this, SLOT(setNull()), this);
+ createAction(ERASE, ICONS.ERASE, tr("Erase values"), this, SLOT(erase()), this);
+ createAction(OPEN_VALUE_EDITOR, ICONS.OPEN_VALUE_EDITOR, tr("Edit value in editor"), this, SLOT(openValueEditor()), this);
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit"), this, SLOT(commit()), this);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback"), this, SLOT(rollback()), this);
+ createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit selected cells"), this, SLOT(selectiveCommit()), this);
+ createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback selected cells"), this, SLOT(selectiveRollback()), this);
+ createAction(SORT_DIALOG, ICONS.SORT_COLUMNS, tr("Define columns to sort by"), this, SLOT(openSortDialog()), this);
+ createAction(RESET_SORTING, ICONS.SORT_RESET, tr("Remove custom sorting"), this, SLOT(resetSorting()), this);
+ createAction(INSERT_ROW, ICONS.INSERT_ROW, tr("Insert row"), this, SIGNAL(requestForRowInsert()), this);
+ createAction(INSERT_MULTIPLE_ROWS, ICONS.INSERT_ROWS, tr("Insert multiple rows"), this, SIGNAL(requestForMultipleRowInsert()), this);
+ createAction(DELETE_ROW, ICONS.DELETE_ROW, tr("Delete selected row"), this, SIGNAL(requestForRowDelete()), this);
+
+ actionMap[RESET_SORTING]->setEnabled(false);
+}
+
+void SqlQueryView::setupDefShortcuts()
+{
+ setShortcutContext({ROLLBACK, SET_NULL, ERASE, OPEN_VALUE_EDITOR, COMMIT, COPY, COPY_AS,
+ PASTE, PASTE_AS}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(SqlQueryView, Action);
+}
+
+void SqlQueryView::setupActionsForMenu(SqlQueryItem* currentItem, const QList<SqlQueryItem*>& selectedItems)
+{
+ UNUSED(currentItem);
+
+ // Selected items count
+ int selCount = selectedItems.size();
+
+ // Uncommited items count
+ QList<SqlQueryItem*> uncommitedItems = getModel()->getUncommitedItems();
+ int uncommitedCount = uncommitedItems.size();
+
+ // Uncommited & selected items count
+ int uncommitedSelCount = 0;
+ foreach (SqlQueryItem* item, uncommitedItems)
+ if (selectedItems.contains(item))
+ uncommitedSelCount++;
+
+ if (uncommitedCount > 0)
+ contextMenu->addAction(actionMap[COMMIT]);
+
+ if (uncommitedSelCount > 0)
+ contextMenu->addAction(actionMap[SELECTIVE_COMMIT]);
+
+ if (uncommitedCount > 0)
+ contextMenu->addAction(actionMap[ROLLBACK]);
+
+ if (uncommitedSelCount > 0)
+ contextMenu->addAction(actionMap[SELECTIVE_ROLLBACK]);
+
+ if (uncommitedCount > 0 && selCount > 0)
+ contextMenu->addSeparator();
+
+ if (selCount > 0)
+ {
+ contextMenu->addAction(actionMap[ERASE]);
+ contextMenu->addAction(actionMap[SET_NULL]);
+ contextMenu->addAction(actionMap[OPEN_VALUE_EDITOR]);
+ contextMenu->addSeparator();
+ }
+
+ if (selCount > 0)
+ {
+ contextMenu->addAction(actionMap[COPY]);
+ //contextMenu->addAction(actionMap[COPY_AS]); // TODO uncomment when implemented
+ contextMenu->addAction(actionMap[PASTE]);
+ //contextMenu->addAction(actionMap[PASTE_AS]); // TODO uncomment when implemented
+ }
+ if (additionalActions.size() > 0)
+ {
+ contextMenu->addSeparator();
+ foreach (QAction* action, additionalActions)
+ contextMenu->addAction(action);
+ }
+}
+
+void SqlQueryView::setupHeaderMenu()
+{
+ horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(horizontalHeader(), &QWidget::customContextMenuRequested, this, &SqlQueryView::headerContextMenuRequested);
+ headerContextMenu = new QMenu(horizontalHeader());
+ headerContextMenu->addAction(actionMap[SORT_DIALOG]);
+ headerContextMenu->addAction(actionMap[RESET_SORTING]);
+}
+
+bool SqlQueryView::handleDoubleClick(SqlQueryItem* item)
+{
+ if (item->getColumn()->dataType.getType() == DataType::BLOB)
+ {
+ openValueEditor(item);
+ return false;
+ }
+ return true;
+}
+
+void SqlQueryView::updateCommitRollbackActions(bool enabled)
+{
+ actionMap[COMMIT]->setEnabled(enabled);
+ actionMap[ROLLBACK]->setEnabled(enabled);
+}
+
+void SqlQueryView::customContextMenuRequested(const QPoint& pos)
+{
+ SqlQueryItem* currentItem = getCurrentItem();
+ QList<SqlQueryItem*> selectedItems = getSelectedItems();
+
+ contextMenu->clear();
+
+ setupActionsForMenu(currentItem, selectedItems);
+ emit contextMenuRequested(currentItem, selectedItems);
+
+ if (contextMenu->actions().size() == 0)
+ return;
+
+ contextMenu->popup(viewport()->mapToGlobal(pos));
+}
+
+void SqlQueryView::headerContextMenuRequested(const QPoint& pos)
+{
+ headerContextMenu->popup(horizontalHeader()->mapToGlobal(pos));
+}
+
+void SqlQueryView::openSortDialog()
+{
+ QStringList columns;
+ for (SqlQueryModelColumnPtr col : getModel()->getColumns())
+ columns << col->displayName;
+
+ SortDialog dialog(this);
+ dialog.setColumns(columns);
+ dialog.setSortOrder(getModel()->getSortOrder());
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ getModel()->setSortOrder(dialog.getSortOrder());
+}
+
+void SqlQueryView::resetSorting()
+{
+ getModel()->setSortOrder(QueryExecutor::SortList());
+}
+
+void SqlQueryView::sortingUpdated(const QueryExecutor::SortList& sortOrder)
+{
+ actionMap[RESET_SORTING]->setEnabled(sortOrder.size() > 0);
+}
+
+void SqlQueryView::updateFont()
+{
+ QFont f = CFG_UI.Fonts.DataView.get();
+ QFontMetrics fm(f);
+ verticalHeader()->setDefaultSectionSize(fm.height() + 4);
+}
+
+void SqlQueryView::executionStarted()
+{
+ widgetCover->show();
+}
+
+void SqlQueryView::executionEnded()
+{
+ widgetCover->hide();
+}
+
+void SqlQueryView::setCurrentRow(int row)
+{
+ setCurrentIndex(model()->index(row, 0));
+}
+
+void SqlQueryView::copy()
+{
+ QList<SqlQueryItem*> selectedItems = getSelectedItems();
+ QList<QList<SqlQueryItem*> > groupedItems = SqlQueryModel::groupItemsByRows(selectedItems);
+
+ QStringList cells;
+ QList<QStringList> rows;
+
+ foreach (const QList<SqlQueryItem*>& itemsInRows, groupedItems)
+ {
+ foreach (SqlQueryItem* item, itemsInRows)
+ cells << item->getFullValue().toString();
+
+ rows << cells;
+ cells.clear();
+ }
+
+ QString tsv = TsvSerializer::serialize(rows);
+ qApp->clipboard()->setText(tsv);
+}
+
+void SqlQueryView::paste()
+{
+ QList<QStringList> deserializedRows = TsvSerializer::deserialize(qApp->clipboard()->text());
+
+ QList<SqlQueryItem*> selectedItems = getSelectedItems();
+ qSort(selectedItems);
+ SqlQueryItem* topLeft = selectedItems.first();
+
+ int columnCount = getModel()->columnCount();
+ int rowCount = getModel()->rowCount();
+ int rowIdx = topLeft->row();
+ int colIdx = topLeft->column();
+
+ SqlQueryItem* item = nullptr;
+
+ foreach (const QStringList& cells, deserializedRows)
+ {
+ // Check if we're out of rows range
+ if (rowIdx >= rowCount)
+ {
+ // No more rows available.
+ qDebug() << "Tried to paste more rows than available in the grid.";
+ break;
+ }
+
+ foreach (const QString& cell, cells)
+ {
+ // Get current cell
+ if (colIdx >= columnCount)
+ {
+ // No more columns available.
+ qDebug() << "Tried to paste more columns than available in the grid.";
+ break;
+ }
+ item = getModel()->itemFromIndex(rowIdx, colIdx);
+
+ // Set value to the cell
+ item->setValue(cell, false, false);
+
+ // Go to next cell
+ colIdx++;
+ }
+
+ // Go to next row, first cell
+ rowIdx++;
+ colIdx = topLeft->column();
+ }
+}
+
+void SqlQueryView::copyAs()
+{
+ // TODO copyAs()
+}
+
+void SqlQueryView::pasteAs()
+{
+ // TODO pasteAs()
+}
+
+void SqlQueryView::setNull()
+{
+ foreach (SqlQueryItem* selItem, getSelectedItems())
+ selItem->setValue(QVariant(QString::null), false, false);
+}
+
+void SqlQueryView::erase()
+{
+ foreach (SqlQueryItem* selItem, getSelectedItems())
+ selItem->setValue("", false, false);
+}
+
+void SqlQueryView::commit()
+{
+ getModel()->commit();
+}
+
+void SqlQueryView::rollback()
+{
+ getModel()->rollback();
+}
+
+void SqlQueryView::selectiveCommit()
+{
+ getModel()->commit(getSelectedItems());
+}
+
+void SqlQueryView::selectiveRollback()
+{
+ getModel()->rollback(getSelectedItems());
+}
+
+void SqlQueryView::openValueEditor(SqlQueryItem* item)
+{
+ if (!item)
+ {
+ qWarning() << "Tried to open value editor while there's no current item. It should not be called in that case.";
+ return;
+ }
+
+ MultiEditorDialog editor(this);
+ editor.setWindowTitle(tr("Edit value"));
+ editor.setDataType(item->getColumn()->dataType);
+ editor.setValue(item->getFullValue());
+ editor.setReadOnly(!item->getColumn()->canEdit());
+ if (editor.exec() == QDialog::Rejected)
+ return;
+
+ item->setValue(editor.getValue());
+}
+
+void SqlQueryView::openValueEditor()
+{
+ SqlQueryItem* currentItem = getCurrentItem();
+ openValueEditor(currentItem);
+}
+
+int qHash(SqlQueryView::Action action)
+{
+ return static_cast<int>(action);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h
new file mode 100644
index 0000000..65486fe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h
@@ -0,0 +1,130 @@
+#ifndef SQLQUERYVIEW_H
+#define SQLQUERYVIEW_H
+
+#include "csvformat.h"
+#include "common/extactioncontainer.h"
+#include "db/queryexecutor.h"
+#include "guiSQLiteStudio_global.h"
+#include <QTableView>
+
+class SqlQueryItemDelegate;
+class SqlQueryItem;
+class WidgetCover;
+class SqlQueryModel;
+class SqlQueryModelColumn;
+class QPushButton;
+class QProgressBar;
+class QMenu;
+
+CFG_KEY_LIST(SqlQueryView, QObject::tr("Data grid view"),
+ CFG_KEY_ENTRY(COPY, Qt::CTRL + Qt::Key_C, QObject::tr("Copy cell(s) contents to clipboard"))
+// CFG_KEY_ENTRY(COPY_AS, Qt::CTRL + Qt::SHIFT + Qt::Key_C, QObject::tr(""))
+ CFG_KEY_ENTRY(PASTE, Qt::CTRL + Qt::Key_V, QObject::tr("Paste cell(s) contents from clipboard"))
+// CFG_KEY_ENTRY(PASTE_AS, Qt::CTRL + Qt::SHIFT + Qt::Key_V, QObject::tr(""))
+ CFG_KEY_ENTRY(ERASE, Qt::ALT + Qt::Key_Backspace, QObject::tr("Set empty value to selected cell(s)"))
+ CFG_KEY_ENTRY(SET_NULL, Qt::Key_Backspace, QObject::tr("Set NULL value to selected cell(s)"))
+ CFG_KEY_ENTRY(COMMIT, Qt::CTRL + Qt::Key_Return, QObject::tr("Commit changes to cell(s) contents"))
+ CFG_KEY_ENTRY(ROLLBACK, Qt::CTRL + Qt::Key_Backspace, QObject::tr("Rollback changes to cell(s) contents"))
+ CFG_KEY_ENTRY(DELETE_ROW, Qt::Key_Delete, QObject::tr("Delete selected data row"))
+ CFG_KEY_ENTRY(INSERT_ROW, Qt::Key_Insert, QObject::tr("Insert new data row"))
+ CFG_KEY_ENTRY(OPEN_VALUE_EDITOR, Qt::ALT + Qt::Key_Return, QObject::tr("Open contents of selected cell in a separate editor"))
+)
+
+class GUI_API_EXPORT SqlQueryView : public QTableView, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ COPY,
+ COPY_AS,
+ PASTE,
+ PASTE_AS,
+ SET_NULL,
+ ERASE,
+ ROLLBACK,
+ COMMIT,
+ INSERT_ROW,
+ INSERT_MULTIPLE_ROWS,
+ DELETE_ROW,
+ SELECTIVE_COMMIT,
+ SELECTIVE_ROLLBACK,
+ OPEN_VALUE_EDITOR,
+ SORT_DIALOG,
+ RESET_SORTING
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit SqlQueryView(QWidget* parent = 0);
+ virtual ~SqlQueryView();
+ QList<SqlQueryItem*> getSelectedItems();
+ SqlQueryItem* getCurrentItem();
+ SqlQueryModel* getModel();
+ void setModel(QAbstractItemModel *model);
+ SqlQueryItem *itemAt(const QPoint& pos);
+ QToolBar* getToolBar(int toolbar) const;
+ void addAdditionalAction(QAction* action);
+ QModelIndex getCurrentIndex() const;
+
+ protected:
+ void mouseDoubleClickEvent(QMouseEvent* event);
+
+ private:
+ void init();
+ void setupWidgetCover();
+ void createActions();
+ void setupDefShortcuts();
+ void refreshShortcuts();
+ void setupActionsForMenu(SqlQueryItem* currentItem, const QList<SqlQueryItem*>& selectedItems);
+ void setupHeaderMenu();
+ bool handleDoubleClick(SqlQueryItem* item);
+
+ SqlQueryItemDelegate* itemDelegate = nullptr;
+ QMenu* contextMenu = nullptr;
+ QMenu* headerContextMenu = nullptr;
+ WidgetCover* widgetCover = nullptr;
+ QPushButton* cancelButton = nullptr;
+ QProgressBar* busyBar = nullptr;
+ QList<QAction*> additionalActions;
+
+ private slots:
+ void updateCommitRollbackActions(bool enabled);
+ void customContextMenuRequested(const QPoint& pos);
+ void headerContextMenuRequested(const QPoint& pos);
+ void openSortDialog();
+ void resetSorting();
+ void sortingUpdated(const QueryExecutor::SortList& sortOrder);
+ void updateFont();
+
+ public slots:
+ void executionStarted();
+ void executionEnded();
+ void setCurrentRow(int row);
+ void copy();
+ void paste();
+ void copyAs();
+ void pasteAs();
+ void setNull();
+ void erase();
+ void commit();
+ void rollback();
+ void selectiveCommit();
+ void selectiveRollback();
+ void openValueEditor(SqlQueryItem* item);
+ void openValueEditor();
+
+ signals:
+ void contextMenuRequested(SqlQueryItem* currentItem, const QList<SqlQueryItem*>& selectedItems);
+ void requestForRowInsert();
+ void requestForMultipleRowInsert();
+ void requestForRowDelete();
+};
+
+GUI_API_EXPORT int qHash(SqlQueryView::Action action);
+
+#endif // SQLQUERYVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp
new file mode 100644
index 0000000..c713737
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp
@@ -0,0 +1,332 @@
+#include "sqltablemodel.h"
+#include "common/utils_sql.h"
+#include "sqlqueryitem.h"
+#include "services/notifymanager.h"
+#include <QDebug>
+#include <QApplication>
+#include <schemaresolver.h>
+
+SqlTableModel::SqlTableModel(QObject *parent) :
+ SqlQueryModel(parent)
+{
+}
+
+QString SqlTableModel::getDatabase() const
+{
+ return database;
+}
+
+QString SqlTableModel::getTable() const
+{
+ return table;
+}
+
+void SqlTableModel::setDatabaseAndTable(const QString& database, const QString& table)
+{
+ this->database = database;
+ this->table = table;
+ setQuery("SELECT * FROM "+getDataSource());
+
+ SchemaResolver resolver(db);
+ isWithOutRowIdTable = resolver.isWithoutRowIdTable(database, table);
+}
+
+SqlQueryModel::Features SqlTableModel::features() const
+{
+ return INSERT_ROW|DELETE_ROW|FILTERING;
+}
+
+
+bool SqlTableModel::commitAddedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ QList<SqlQueryModelColumnPtr> modelColumns = getTableColumnModels(table);
+ if (modelColumns.size() != itemsInRow.size())
+ {
+ qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of columns in argument different than model resolved for the table.";
+ return false;
+ }
+
+ // Check that just in case:
+ if (modelColumns.size() == 0)
+ {
+ qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of resolved columns in the table equal to 0!";
+ return false;
+ }
+
+ // Prepare column placeholders and their values
+ QStringList colNameList;
+ QStringList sqlValues;
+ QList<QVariant> args;
+ updateColumnsAndValues(itemsInRow, modelColumns, colNameList, sqlValues, args);
+
+ // Prepare SQL query
+ QString sql = getInsertSql(modelColumns, colNameList, sqlValues, args);
+
+ // Execute query
+ SqlQueryPtr result = db->exec(sql, args);
+
+ // Handle error
+ if (result->isError())
+ {
+ foreach (SqlQueryItem* item, itemsInRow)
+ item->setCommitingError(true);
+
+ notifyError(tr("Error while commiting new row: %1").arg(result->getErrorText()));
+ return false;
+ }
+
+ // Reloading row with actual values (because of DEFAULT, AUTOINCR)
+ RowId rowId = result->getInsertRowId();
+ updateRowAfterInsert(itemsInRow, modelColumns, rowId);
+ return true;
+}
+
+bool SqlTableModel::commitDeletedRow(const QList<SqlQueryItem*>& itemsInRow)
+{
+ if (itemsInRow.size() == 0)
+ {
+ qCritical() << "Tried to SqlTableModel::commitDeletedRow() with number of items equal to 0!";
+ return false;
+ }
+
+ if (itemsInRow[0]->isJustInsertedWithOutRowId())
+ {
+ QString msg = tr("When inserted new row to the WITHOUT ROWID table, using DEFAULT value for PRIMARY KEY, "
+ "the table has to be reloaded in order to delete the new row.");
+ notifyError(tr("Error while deleting row from table %1: %2").arg(table).arg(msg));
+ return false;
+ }
+
+ RowId rowId = itemsInRow[0]->getRowId();
+ if (rowId.isEmpty())
+ return false;
+
+ Dialect dialect = db->getDialect();
+
+ CommitDeleteQueryBuilder queryBuilder;
+ queryBuilder.setTable(wrapObjIfNeeded(table, dialect));
+ queryBuilder.setRowId(rowId);
+
+ QString sql = queryBuilder.build();
+ QHash<QString, QVariant> args = queryBuilder.getQueryArgs();
+
+ SqlQueryPtr result = db->exec(sql, args);
+ if (result->isError())
+ {
+ notifyError(tr("Error while deleting row from table %1: %2").arg(table).arg(result->getErrorText()));
+ return false;
+ }
+
+ if (!SqlQueryModel::commitDeletedRow(itemsInRow))
+ qCritical() << "Could not delete row from SqlQueryView while commiting row deletion.";
+
+ return true;
+}
+
+void SqlTableModel::applySqlFilter(const QString& value)
+{
+ if (value.isEmpty())
+ {
+ resetFilter();
+ return;
+ }
+
+ setQuery("SELECT * FROM "+getDataSource()+" WHERE "+value);
+ executeQuery();
+}
+
+void SqlTableModel::applyStringFilter(const QString& value)
+{
+ if (value.isEmpty())
+ {
+ resetFilter();
+ return;
+ }
+
+ Dialect dialect = db->getDialect();
+ QStringList conditions;
+ foreach (SqlQueryModelColumnPtr column, columns)
+ conditions << wrapObjIfNeeded(column->column, dialect)+" LIKE '%"+value+"%'";
+
+ setQuery("SELECT * FROM "+getDataSource()+" WHERE "+conditions.join(" OR "));
+ executeQuery();
+}
+
+void SqlTableModel::applyRegExpFilter(const QString& value)
+{
+ if (value.isEmpty())
+ {
+ resetFilter();
+ return;
+ }
+
+ Dialect dialect = db->getDialect();
+ QStringList conditions;
+ foreach (SqlQueryModelColumnPtr column, columns)
+ conditions << wrapObjIfNeeded(column->column, dialect)+" REGEXP '"+value+"'";
+
+ setQuery("SELECT * FROM "+getDataSource()+" WHERE "+conditions.join(" OR "));
+ executeQuery();
+}
+
+void SqlTableModel::resetFilter()
+{
+ setQuery("SELECT * FROM "+getDataSource());
+ //reload();
+ executeQuery();
+}
+
+void SqlTableModel::updateRowAfterInsert(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns, RowId rowId)
+{
+ // Update cells with data just like it was entered. Only DEFAULT and PRIMARY KEY AUTOINCREMENT will have special values.
+ QList<QVariant> values;
+ SqlQueryItem* item = nullptr;
+ int i = 0;
+ for (const SqlQueryModelColumnPtr& modelColumn : modelColumns)
+ {
+ item = itemsInRow[i++];
+// qDebug() << "Item is for column" << item->getColumn()->column << ", column iterated:" << modelColumn->column;
+ if (item->getValue().isNull())
+ {
+ if (modelColumn->isDefault())
+ {
+ values << modelColumn->getDefaultConstraint()->defaultValue;
+ continue;
+ }
+
+ // If this is the PK AUTOINCR column we use RowId as value, because it was skipped when setting values to items
+ if (modelColumn->isPk() && modelColumn->isAutoIncr())
+ {
+ values << rowId["ROWID"];
+ continue;
+ }
+ }
+
+ values << item->getValue();
+ }
+
+ // Update cell data with results
+ int colIdx = 0;
+ for (SqlQueryItem* itemToUpdate : itemsInRow)
+ {
+ updateItem(itemToUpdate, values[colIdx], colIdx, rowId);
+
+ if (isWithOutRowIdTable && rowId.isEmpty())
+ itemToUpdate->setJustInsertedWithOutRowId(true);
+
+ colIdx++;
+ }
+}
+
+QString SqlTableModel::getDatabasePrefix()
+{
+ if (database.isNull())
+ return "main.";
+
+ return wrapObjIfNeeded(database, db->getDialect()) + ".";
+}
+
+QString SqlTableModel::getDataSource()
+{
+ return getDatabasePrefix() + wrapObjIfNeeded(table, db->getDialect());
+}
+
+QString SqlTableModel::getInsertSql(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList,
+ QStringList& sqlValues, QList<QVariant>& args)
+{
+ Dialect dialect = db->getDialect();
+
+ QString sql = "INSERT INTO "+wrapObjIfNeeded(table, dialect);
+ if (colNameList.size() == 0)
+ {
+ // There are all null values passed to the query. We need to use Sqlite3 special syntax, or find at least one default value
+ if (dialect == Dialect::Sqlite2)
+ updateColumnsAndValuesWithDefaultValues(modelColumns, colNameList, sqlValues, args);
+ else // Sqlite3 has default values syntax for that case
+ sql += " DEFAULT VALUES";
+ }
+ else
+ sql += " ("+colNameList.join(", ")+") VALUES ("+sqlValues.join(", ")+")";
+
+ return sql;
+}
+
+void SqlTableModel::updateColumnsAndValues(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns,
+ QStringList& colNameList, QStringList& sqlValues, QList<QVariant>& args)
+{
+ Dialect dialect = db->getDialect();
+
+ SqlQueryItem* item = nullptr;
+ int i = 0;
+ foreach (SqlQueryModelColumnPtr modelColumn, modelColumns)
+ {
+ item = itemsInRow[i++];
+ if (item->getValue().isNull())
+ {
+ if (modelColumn->isDefault())
+ continue;
+
+ if (modelColumn->isPk() && modelColumn->isAutoIncr())
+ continue;
+ }
+
+ colNameList << wrapObjIfNeeded(modelColumn->column, dialect);
+ sqlValues << ":arg" + QString::number(i);
+ args << item->getFullValue();
+ }
+}
+
+void SqlTableModel::updateColumnsAndValuesWithDefaultValues(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList,
+ QStringList& sqlValues, QList<QVariant>& args)
+{
+ Dialect dialect = db->getDialect();
+
+ // First try to find the one with DEFAULT value
+ foreach (SqlQueryModelColumnPtr modelColumn, modelColumns)
+ {
+ if (modelColumn->isDefault())
+ {
+ colNameList << wrapObjIfNeeded(modelColumn->column, dialect);
+ sqlValues << ":defValue";
+ args << modelColumn->getDefaultConstraint()->defaultValue;
+ return;
+ }
+ }
+
+ // No DEFAULT, try with AUTOINCR
+ foreach (SqlQueryModelColumnPtr modelColumn, modelColumns)
+ {
+ if (modelColumn->isPk() && modelColumn->isAutoIncr())
+ {
+ QString colName = wrapObjIfNeeded(modelColumn->column, dialect);
+ QString tableName = wrapObjIfNeeded(table, dialect);
+ SqlQueryPtr results = db->exec("SELECT max("+colName+") FROM "+tableName);
+ int rowid = 0;
+ QVariant cellValue = results->getSingleCell();
+ if (!cellValue.isNull())
+ rowid = cellValue.toInt();
+
+ colNameList << wrapObjIfNeeded(modelColumn->column, dialect);
+ sqlValues << ":defValue";
+ args << rowid;
+ return;
+ }
+ }
+
+ // No luck with AUTOINCR either, put NULL and if there's a NOT NULL in any column,
+ // user will get the proper error message from Sqlite.
+ colNameList << wrapObjIfNeeded(modelColumns[0]->column, dialect);
+ sqlValues << ":defValue";
+ args << QVariant();
+}
+
+QString SqlTableModel::CommitDeleteQueryBuilder::build()
+{
+ QString dbAndTable;
+ if (!database.isNull())
+ dbAndTable += database+".";
+
+ dbAndTable += table;
+ QString conditions = RowIdConditionBuilder::build();
+ return "DELETE FROM "+dbAndTable+" WHERE "+conditions+";";
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h
new file mode 100644
index 0000000..6adb5b6
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h
@@ -0,0 +1,50 @@
+#ifndef SQLTABLEMODEL_H
+#define SQLTABLEMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include "sqlquerymodel.h"
+
+class GUI_API_EXPORT SqlTableModel : public SqlQueryModel
+{
+ Q_OBJECT
+ public:
+ explicit SqlTableModel(QObject *parent = 0);
+
+ QString getDatabase() const;
+ QString getTable() const;
+ void setDatabaseAndTable(const QString& database, const QString& table);
+
+ Features features() const;
+ void applySqlFilter(const QString& value);
+ void applyStringFilter(const QString& value);
+ void applyRegExpFilter(const QString& value);
+ void resetFilter();
+
+ protected:
+ bool commitAddedRow(const QList<SqlQueryItem*>& itemsInRow);
+ bool commitDeletedRow(const QList<SqlQueryItem*>& itemsInRow);
+
+ private:
+ class CommitDeleteQueryBuilder : public CommitUpdateQueryBuilder
+ {
+ public:
+ QString build();
+ };
+
+
+ void updateColumnsAndValuesWithDefaultValues(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList,
+ QStringList& sqlValues, QList<QVariant>& args);
+ void updateColumnsAndValues(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns,
+ QStringList& colNameList, QStringList& sqlValues, QList<QVariant>& args);
+ QString getInsertSql(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList, QStringList& sqlValues,
+ QList<QVariant>& args);
+ void updateRowAfterInsert(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns, RowId rowId);
+ QString getDatabasePrefix();
+ QString getDataSource();
+
+ QString table;
+ QString database;
+ bool isWithOutRowIdTable = false;
+};
+
+#endif // SQLTABLEMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp
new file mode 100644
index 0000000..c7f615a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp
@@ -0,0 +1,836 @@
+#include "dataview.h"
+#include "datagrid/sqltablemodel.h"
+#include "datagrid/sqlquerymodel.h"
+#include "datagrid/sqlqueryview.h"
+#include "formview.h"
+#include "common/extlineedit.h"
+#include "mainwindow.h"
+#include "statusfield.h"
+#include "common/intvalidator.h"
+#include "common/extaction.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include "datagrid/sqlqueryitem.h"
+#include <QDebug>
+#include <QHeaderView>
+#include <QVBoxLayout>
+#include <QToolBar>
+#include <QLabel>
+#include <QAction>
+#include <QTime>
+#include <QStyleFactory>
+
+CFG_KEYS_DEFINE(DataView)
+DataView::FilterMode DataView::filterMode;
+DataView::TabsPosition DataView::tabsPosition;
+QHash<DataView::Action,QAction*> DataView::staticActions;
+QHash<DataView::ActionGroup,QActionGroup*> DataView::staticActionGroups;
+
+DataView::DataView(QWidget *parent) :
+ QTabWidget(parent)
+{
+}
+
+void DataView::init(SqlQueryModel* model)
+{
+ createContents();
+
+ this->model = model;
+ this->model->setView(gridView);
+
+ rowCountLabel = new QLabel();
+ formViewRowCountLabel = new QLabel();
+ formViewCurrentRowLabel = new QLabel();
+
+ initFormView();
+ initPageEdit();
+ initFilter();
+ initActions();
+ initUpdates();
+ initSlots();
+ updateTabsMode();
+}
+
+void DataView::initUpdates()
+{
+ updatePageEdit();
+ updateFormNavigationState();
+ updateGridNavigationState();
+ updateCommitRollbackActions(false);
+}
+
+void DataView::initSlots()
+{
+ connect(model, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(model, SIGNAL(loadingEnded(bool)), this, SLOT(dataLoadingEnded(bool)));
+ connect(model, SIGNAL(commitStatusChanged(bool)), this, SLOT(updateCommitRollbackActions(bool)));
+ connect(gridView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+ model, SLOT(updateSelectiveCommitRollbackActions(QItemSelection,QItemSelection)));
+ connect(model, SIGNAL(selectiveCommitStatusChanged(bool)), this, SLOT(updateSelectiveCommitRollbackActions(bool)));
+ connect(model, SIGNAL(executionStarted()), gridView, SLOT(executionStarted()));
+ connect(model, SIGNAL(loadingEnded(bool)), gridView, SLOT(executionEnded()));
+ connect(model, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable()));
+ connect(gridView->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(columnsHeaderClicked(int)));
+ connect(this, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+}
+
+void DataView::initFormView()
+{
+ formView = new FormView();
+ formWidget->layout()->addWidget(formView);
+ formView->setModel(model);
+ formView->setGridView(gridView);
+ connect(formView, SIGNAL(commitStatusChanged()), this, SLOT(updateFormCommitRollbackActions()));
+ connect(formView, SIGNAL(currentRowChanged()), this, SLOT(updateFormNavigationState()));
+ updateCurrentFormViewRow();
+}
+
+void DataView::initFilter()
+{
+ filterEdit = new ExtLineEdit();
+ filterEdit->setExpandingMinWidth(100);
+ filterEdit->setExpandingMaxWidth(200);
+ filterEdit->setExpanding(true);
+ filterEdit->setClearButtonEnabled(true);
+ filterEdit->setPlaceholderText(tr("Filter data", "data view"));
+ connect(filterEdit, SIGNAL(valueErased()), this, SLOT(resetFilter()));
+ connect(filterEdit, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
+}
+
+void DataView::createContents()
+{
+ gridWidget = new QWidget();
+ formWidget = new QWidget();
+ addTab(gridWidget, tr("Grid view"));
+ addTab(formWidget, tr("Form view"));
+
+ QVBoxLayout* vbox = new QVBoxLayout();
+ gridWidget->setLayout(vbox);
+
+ vbox = new QVBoxLayout();
+ formWidget->setLayout(vbox);
+
+ gridToolBar = new QToolBar();
+ formToolBar = new QToolBar();
+ gridWidget->layout()->addWidget(gridToolBar);
+ formWidget->layout()->addWidget(formToolBar);
+
+#ifdef Q_OS_MACX
+ QStyle *fusion = QStyleFactory::create("Fusion");
+ gridToolBar->setStyle(fusion);
+ formToolBar->setStyle(fusion);
+#endif
+
+ gridView = new SqlQueryView();
+ gridView->setCornerButtonEnabled(true);
+ gridView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+ gridWidget->layout()->addWidget(gridView);
+}
+
+void DataView::initPageEdit()
+{
+ pageEdit = new ExtLineEdit();
+ pageValidator = new IntValidator(1, 1, pageEdit);
+ pageValidator->setDefaultValue(1);
+ pageEdit->setAlignment(Qt::AlignCenter);
+ pageEdit->setValidator(pageValidator);
+ pageEdit->setExpanding(true);
+ pageEdit->setExpandingMinWidth(20);
+ connect(pageEdit, SIGNAL(editingFinished()), this, SLOT(pageEntered()));
+}
+
+void DataView::createActions()
+{
+ bool rowInserting = model->features().testFlag(SqlQueryModel::INSERT_ROW);
+ bool rowDeleting = model->features().testFlag(SqlQueryModel::DELETE_ROW);
+
+ // Grid actions
+ createAction(REFRESH_DATA, ICONS.RELOAD, tr("Refresh table data", "data view"), this, SLOT(refreshData()), gridToolBar, gridView);
+ gridToolBar->addSeparator();
+ if (rowInserting)
+ {
+ gridToolBar->addAction(gridView->getAction(SqlQueryView::INSERT_ROW));
+ attachActionInMenu(gridView->getAction(SqlQueryView::INSERT_ROW), gridView->getAction(SqlQueryView::INSERT_MULTIPLE_ROWS), gridToolBar);
+ }
+
+ if (rowDeleting)
+ gridToolBar->addAction(gridView->getAction(SqlQueryView::DELETE_ROW));
+
+ gridToolBar->addAction(gridView->getAction(SqlQueryView::COMMIT));
+ gridToolBar->addAction(gridView->getAction(SqlQueryView::ROLLBACK));
+ gridToolBar->addSeparator();
+ createAction(FIRST_PAGE, ICONS.PAGE_FIRST, tr("First page", "data view"), this, SLOT(firstPage()), gridToolBar);
+ createAction(PREV_PAGE, ICONS.PAGE_PREV, tr("Previous page", "data view"), this, SLOT(prevPage()), gridToolBar);
+ actionMap[PAGE_EDIT] = gridToolBar->addWidget(pageEdit);
+ createAction(NEXT_PAGE, ICONS.PAGE_NEXT, tr("Next page", "data view"), this, SLOT(nextPage()), gridToolBar);
+ createAction(LAST_PAGE, ICONS.PAGE_LAST, tr("Last page", "data view"), this, SLOT(lastPage()), gridToolBar);
+ gridToolBar->addSeparator();
+ if (model->features().testFlag(SqlQueryModel::FILTERING))
+ {
+ actionMap[FILTER_VALUE] = gridToolBar->addWidget(filterEdit);
+ createAction(FILTER, tr("Apply filter", "data view"), this, SLOT(applyFilter()), gridToolBar);
+ attachActionInMenu(FILTER, staticActions[FILTER_STRING], gridToolBar);
+ attachActionInMenu(FILTER, staticActions[FILTER_REGEXP], gridToolBar);
+ attachActionInMenu(FILTER, staticActions[FILTER_SQL], gridToolBar);
+ gridToolBar->addSeparator();
+ updateFilterIcon();
+
+ connect(staticActions[FILTER_STRING], SIGNAL(triggered()), this, SLOT(filterModeSelected()));
+ connect(staticActions[FILTER_REGEXP], SIGNAL(triggered()), this, SLOT(filterModeSelected()));
+ connect(staticActions[FILTER_SQL], SIGNAL(triggered()), this, SLOT(filterModeSelected()));
+ }
+ actionMap[GRID_TOTAL_ROWS] = gridToolBar->addWidget(rowCountLabel);
+
+ noConfigShortcutActions << GRID_TOTAL_ROWS << FILTER_VALUE;
+
+ createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit changes for selected cells", "data view"), this, SLOT(selectiveCommitGrid()), this);
+ createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback changes for selected cells", "data view"), this, SLOT(selectiveRollbackGrid()), this);
+ createAction(SHOW_GRID_VIEW, tr("Show grid view of results", "sql editor"), this, SLOT(showGridView()), this);
+ createAction(SHOW_FORM_VIEW, tr("Show form view of results", "sql editor"), this, SLOT(showFormView()), this);
+
+ connect(gridView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow()));
+ connect(gridView, SIGNAL(requestForMultipleRowInsert()), this, SLOT(insertMultipleRows()));
+ connect(gridView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow()));
+
+
+ // Form view actions
+ if (rowInserting)
+ formToolBar->addAction(formView->getAction(FormView::INSERT_ROW));
+
+ if (rowDeleting)
+ formToolBar->addAction(formView->getAction(FormView::DELETE_ROW));
+
+ if (rowInserting || rowDeleting)
+ formToolBar->addSeparator();
+
+ formToolBar->addAction(formView->getAction(FormView::COMMIT));
+ formToolBar->addAction(formView->getAction(FormView::ROLLBACK));
+ formToolBar->addSeparator();
+ formToolBar->addAction(formView->getAction(FormView::FIRST_ROW));
+ formToolBar->addAction(formView->getAction(FormView::PREV_ROW));
+ formToolBar->addAction(formView->getAction(FormView::NEXT_ROW));
+ formToolBar->addAction(formView->getAction(FormView::LAST_ROW));
+ formToolBar->addSeparator();
+ actionMap[FORM_TOTAL_ROWS] = formToolBar->addWidget(formViewRowCountLabel);
+ formToolBar->addSeparator();
+ actionMap[FORM_CURRENT_ROW] = formToolBar->addWidget(formViewCurrentRowLabel);
+
+ noConfigShortcutActions << FORM_TOTAL_ROWS;
+
+ connect(formView, SIGNAL(requestForCommit()), this, SLOT(commitForm()));
+ connect(formView, SIGNAL(requestForRollback()), this, SLOT(rollbackForm()));
+ connect(formView, SIGNAL(requestForFirstRow()), this, SLOT(firstRow()));
+ connect(formView, SIGNAL(requestForPrevRow()), this, SLOT(prevRow()));
+ connect(formView, SIGNAL(requestForNextRow()), this, SLOT(nextRow()));
+ connect(formView, SIGNAL(requestForLastRow()), this, SLOT(lastRow()));
+ connect(formView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow()));
+ connect(formView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow()));
+
+ // Actions for grid menu only
+ gridView->addAdditionalAction(staticActions[TABS_ON_TOP]);
+ gridView->addAdditionalAction(staticActions[TABS_AT_BOTTOM]);
+ connect(staticActions[TABS_ON_TOP], SIGNAL(triggered()), this, SLOT(updateTabsMode()));
+ connect(staticActions[TABS_AT_BOTTOM], SIGNAL(triggered()), this, SLOT(updateTabsMode()));
+}
+
+void DataView::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({REFRESH_DATA, SHOW_GRID_VIEW, SHOW_FORM_VIEW}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(DataView, Action);
+}
+
+void DataView::createStaticActions()
+{
+ // Filtering actions
+ staticActions[FILTER_STRING] = new ExtAction(ICONS.APPLY_FILTER_TXT, tr("Filter by text", "data view"), MainWindow::getInstance());
+ staticActions[FILTER_REGEXP] = new ExtAction(ICONS.APPLY_FILTER_RE, tr("Filter by the Regular Expression", "data view"), MainWindow::getInstance());
+ staticActions[FILTER_SQL] = new ExtAction(ICONS.APPLY_FILTER_SQL, tr("Filter by SQL expression", "data view"), MainWindow::getInstance());
+
+ staticActionGroups[ActionGroup::FILTER_MODE] = new QActionGroup(MainWindow::getInstance());
+ staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_STRING]);
+ staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_SQL]);
+ staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_REGEXP]);
+
+ connect(staticActions[FILTER_STRING], &QAction::triggered, [=]()
+ {
+ filterMode = FilterMode::STRING;
+ });
+ connect(staticActions[FILTER_SQL], &QAction::triggered, [=]()
+ {
+ filterMode = FilterMode::SQL;
+ });
+ connect(staticActions[FILTER_REGEXP], &QAction::triggered, [=]()
+ {
+ filterMode = FilterMode::REGEXP;
+ });
+
+ staticActions[FILTER_STRING]->setCheckable(true);
+ staticActions[FILTER_REGEXP]->setCheckable(true);
+ staticActions[FILTER_SQL]->setCheckable(true);
+ if (filterMode == FilterMode::STRING)
+ staticActions[FILTER_STRING]->setChecked(true);
+ else if (filterMode == FilterMode::REGEXP)
+ staticActions[FILTER_REGEXP]->setChecked(true);
+ else
+ staticActions[FILTER_SQL]->setChecked(true);
+
+ // Tabs position actions
+ staticActions[TABS_ON_TOP] = new ExtAction(ICONS.TABS_ON_TOP, tr("Tabs on top", "data view"), MainWindow::getInstance());
+ staticActions[TABS_AT_BOTTOM] = new ExtAction(ICONS.TABS_AT_BOTTOM, tr("Tabs at bottom", "data view"), MainWindow::getInstance());
+
+ staticActionGroups[ActionGroup::TABS_POSITION] = new QActionGroup(MainWindow::getInstance());
+ staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_ON_TOP]);
+ staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_AT_BOTTOM]);
+
+ connect(staticActions[TABS_ON_TOP], &QAction::triggered, [=]()
+ {
+ tabsPosition = TabsPosition::TOP;
+ CFG_UI.General.DataViewTabs.set("TOP");
+ });
+ connect(staticActions[TABS_AT_BOTTOM], &QAction::triggered, [=]()
+ {
+ tabsPosition = TabsPosition::BOTTOM;
+ CFG_UI.General.DataViewTabs.set("BOTTOM");
+ });
+
+ staticActions[TABS_ON_TOP]->setCheckable(true);
+ staticActions[TABS_AT_BOTTOM]->setCheckable(true);
+ if (tabsPosition == TabsPosition::TOP)
+ staticActions[TABS_ON_TOP]->setChecked(true);
+ else
+ staticActions[TABS_AT_BOTTOM]->setChecked(true);
+
+}
+
+void DataView::loadTabsMode()
+{
+ QString valString = CFG_UI.General.DataViewTabs.get();
+ if (valString == "TOP")
+ tabsPosition = TabsPosition::TOP;
+ else if (valString == "BOTTOM")
+ tabsPosition = TabsPosition::BOTTOM;
+}
+
+void DataView::goToFormRow(IndexModifier idxMod)
+{
+ if (formView->isModified())
+ formView->copyDataToGrid();
+
+ int row = gridView->getCurrentIndex().row();
+
+ switch (idxMod)
+ {
+ case IndexModifier::FIRST:
+ row = 0;
+ break;
+ case IndexModifier::PREV:
+ row--;
+ break;
+ case IndexModifier::NEXT:
+ row++;
+ break;
+ case IndexModifier::LAST:
+ row = model->rowCount() - 1;
+ break;
+ }
+
+ QModelIndex newRowIdx = model->index(row, 0);
+ if (!newRowIdx.isValid())
+ return;
+
+ gridView->setCurrentIndex(newRowIdx);
+ model->loadFullDataForEntireRow(row);
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+}
+
+void DataView::setNavigationState(bool enabled)
+{
+ navigationState = enabled;
+ updateNavigationState();
+ setFormViewEnabled(enabled);
+}
+
+void DataView::updateNavigationState()
+{
+ updateGridNavigationState();
+ updateFormNavigationState();
+}
+
+void DataView::updateGridNavigationState()
+{
+ int page = model->getCurrentPage();
+ bool prevResultsAvailable = page > 0;
+ bool nextResultsAvailable = (page + 1) < model->getTotalPages();
+ bool reloadResultsAvailable = model->canReload();
+ bool pageNumEditAvailable = (prevResultsAvailable || nextResultsAvailable);
+
+ actionMap[PAGE_EDIT]->setEnabled(navigationState && totalPagesAvailable && pageNumEditAvailable);
+ actionMap[REFRESH_DATA]->setEnabled(navigationState && reloadResultsAvailable);
+ actionMap[NEXT_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable);
+ actionMap[LAST_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable);
+ actionMap[PREV_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable);
+ actionMap[FIRST_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable);
+}
+
+void DataView::updateFormNavigationState()
+{
+ int row = gridView->getCurrentIndex().row();
+ int lastRow = model->rowCount() - 1;
+ bool nextRowAvailable = row < lastRow;
+ bool prevRowAvailable = row > 0;
+
+ formView->getAction(FormView::NEXT_ROW)->setEnabled(navigationState && nextRowAvailable);
+ formView->getAction(FormView::PREV_ROW)->setEnabled(navigationState && prevRowAvailable);
+
+ // We changed row in form view, this one might be already modified and be capable for commit/rollback
+ updateFormCommitRollbackActions();
+}
+
+void DataView::updateFormCommitRollbackActions()
+{
+ bool enabled = formView->isModified();
+ formView->getAction(FormView::COMMIT)->setEnabled(enabled);
+ formView->getAction(FormView::ROLLBACK)->setEnabled(enabled);
+ uncommittedForm = enabled;
+}
+
+void DataView::showGridView()
+{
+ setCurrentIndex(0);
+}
+
+void DataView::showFormView()
+{
+ setCurrentIndex(1);
+ updateCurrentFormViewRow();
+}
+
+void DataView::updateTabsMode()
+{
+ switch (tabsPosition)
+ {
+ case DataView::TabsPosition::TOP:
+ setTabPosition(TabPosition::North);
+ break;
+ case DataView::TabsPosition::BOTTOM:
+ setTabPosition(TabPosition::South);
+ break;
+ }
+}
+
+void DataView::filterModeSelected()
+{
+ QAction* modeAction = dynamic_cast<QAction*>(sender());
+ actionMap[FILTER]->setIcon(modeAction->icon());
+}
+
+void DataView::updateCommitRollbackActions(bool enabled)
+{
+ gridView->getAction(SqlQueryView::COMMIT)->setEnabled(enabled);
+ gridView->getAction(SqlQueryView::ROLLBACK)->setEnabled(enabled);
+ uncommittedGrid = enabled;
+}
+
+void DataView::updateSelectiveCommitRollbackActions(bool enabled)
+{
+ actionMap[SELECTIVE_COMMIT]->setEnabled(enabled);
+ actionMap[SELECTIVE_ROLLBACK]->setEnabled(enabled);
+}
+
+void DataView::goToPage(const QString& pageStr)
+{
+ bool ok;
+ int page = pageStr.toInt(&ok);
+ if (!ok)
+ return;
+
+ page--; // Converting from visual page representation to logical
+
+ // We need to get this synchronized against event loop, cause changeing action status (probably) calls event loop update,
+ // so this method was sometimes called twice at the time (until setResultsNavigationState() call below),
+ // but the page in results model wasn't updated yet. We cannot simply move setResultsNavigationState() below gotoPage(),
+ // because we need to disable actions, before model returns from execution, so it can re-enable those actions.
+ // This method was called twice, because QLineEdit::editionFinished (the filter) signal is emitted twice:
+ // - because enter was pressed
+ // - because edit lost its focus (which happens after enter was hit), at least it looks like it
+ if (!manualPageChangeMutex.tryLock())
+ return;
+
+ if (page == model->getCurrentPage(true))
+ return;
+
+ setNavigationState(false);
+ model->gotoPage(page);
+ manualPageChangeMutex.unlock();
+}
+
+void DataView::updatePageEdit()
+{
+ int page = model->getCurrentPage()+1;
+ QString text = QString::number(page);
+ int totalPages = model->getTotalPages();
+ pageEdit->setText(text);
+ pageEdit->setToolTip(QObject::tr("Total pages available: %1").arg(totalPages));
+ pageValidator->setTop(totalPages);
+ pageValidator->setDefaultValue(page);
+ updateCurrentFormViewRow();
+}
+
+void DataView::updateResultsCount(int resultsCount)
+{
+ if (resultsCount >= 0)
+ {
+ QString msg = QObject::tr("Total rows loaded: %1").arg(resultsCount);
+ rowCountLabel->setText(msg);
+ formViewRowCountLabel->setText(msg);
+ rowCountLabel->setToolTip(QString::null);
+ formViewRowCountLabel->setToolTip(QString::null);
+ }
+ else
+ {
+ rowCountLabel->setText(" "); // this might seem weird, but if it's not a wide, whitespace string, then icon is truncated from right side
+ formViewRowCountLabel->setText(" ");
+ rowCountLabel->setMovie(ICONS.LOADING);
+ formViewRowCountLabel->setMovie(ICONS.LOADING);
+
+ static QString loadingMsg = tr("Total number of rows is being counted.\nBrowsing other pages will be possible after the row counting is done.");
+ rowCountLabel->setToolTip(loadingMsg);
+ formViewRowCountLabel->setToolTip(loadingMsg);
+ }
+}
+
+void DataView::updateCurrentFormViewRow()
+{
+ int rowsPerPage = CFG_UI.General.NumberOfRowsPerPage.get();
+ int page = gridView->getModel()->getCurrentPage();
+ int row = rowsPerPage * page + 1 + gridView->getCurrentIndex().row();
+ formViewCurrentRowLabel->setText(tr("Row: %1").arg(row));
+}
+
+void DataView::setFormViewEnabled(bool enabled)
+{
+ if (!enabled)
+ setCurrentIndex(0);
+
+ setTabEnabled(1, enabled);
+}
+
+void DataView::readData()
+{
+ setNavigationState(false);
+ model->executeQuery();
+}
+
+void DataView::updateFilterIcon()
+{
+ for (Action act : {FILTER_STRING, FILTER_SQL, FILTER_REGEXP})
+ {
+ if (staticActions[act]->isChecked())
+ {
+ actionMap[FILTER]->setIcon(staticActions[act]->icon());
+ break;
+ }
+ }
+}
+
+bool DataView::isUncommited() const
+{
+ return uncommittedGrid || uncommittedForm;
+}
+
+void DataView::dataLoadingEnded(bool successful)
+{
+ if (successful)
+ updatePageEdit();
+
+ setNavigationState(true);
+}
+
+void DataView::executionSuccessful()
+{
+ updateResultsCount(-1);
+}
+
+void DataView::totalRowsAndPagesAvailable()
+{
+ updateResultsCount(model->getTotalRowsReturned());
+ totalPagesAvailable = true;
+ updatePageEdit();
+ updateNavigationState();
+}
+
+void DataView::refreshData()
+{
+ totalPagesAvailable = false;
+ readData();
+}
+
+void DataView::insertRow()
+{
+ if (!model->features().testFlag(SqlQueryModel::INSERT_ROW))
+ return;
+
+ model->addNewRow();
+ initFormViewForNewRow();
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+ formViewFocusFirstEditor();
+}
+
+void DataView::insertMultipleRows()
+{
+ if (!model->features().testFlag(SqlQueryModel::INSERT_ROW))
+ return;
+
+ model->addMultipleRows();
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+ formViewFocusFirstEditor();
+}
+
+void DataView::deleteRow()
+{
+ if (!model->features().testFlag(SqlQueryModel::DELETE_ROW))
+ return;
+
+ model->deleteSelectedRows();
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+ formViewFocusFirstEditor();
+}
+
+void DataView::commitGrid()
+{
+ model->commit();
+}
+
+void DataView::rollbackGrid()
+{
+ model->rollback();
+}
+
+void DataView::selectiveCommitGrid()
+{
+ QList<SqlQueryItem*> selectedItems = gridView->getSelectedItems();
+ model->commit(selectedItems);
+}
+
+void DataView::selectiveRollbackGrid()
+{
+ QList<SqlQueryItem*> selectedItems = gridView->getSelectedItems();
+ model->rollback(selectedItems);
+}
+
+void DataView::firstPage()
+{
+ setNavigationState(false);
+ model->firstPage();
+}
+
+void DataView::prevPage()
+{
+ setNavigationState(false);
+ model->prevPage();
+}
+
+void DataView::nextPage()
+{
+ setNavigationState(false);
+ model->nextPage();
+}
+
+void DataView::lastPage()
+{
+ setNavigationState(false);
+ model->lastPage();
+}
+
+void DataView::pageEntered()
+{
+ goToPage(pageEdit->text());
+}
+
+void DataView::applyFilter()
+{
+ if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING))
+ {
+ qWarning() << "Tried to apply filter on model that doesn't support it.";
+ return;
+ }
+
+ QString value = filterEdit->text();
+ switch (filterMode)
+ {
+ case DataView::FilterMode::STRING:
+ model->applyStringFilter(value);
+ break;
+ case DataView::FilterMode::SQL:
+ model->applySqlFilter(value);
+ break;
+ case DataView::FilterMode::REGEXP:
+ model->applyRegExpFilter(value);
+ break;
+ }
+}
+
+void DataView::resetFilter()
+{
+ if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING))
+ {
+ qWarning() << "Tried to reset filter on model that doesn't support it.";
+ return;
+ }
+
+ model->resetFilter();
+}
+
+void DataView::commitForm()
+{
+ formView->copyDataToGrid();
+ gridView->selectRow(formView->getCurrentRow());
+ selectiveCommitGrid();
+ formView->updateFromGrid();
+ formViewFocusFirstEditor();
+}
+
+void DataView::rollbackForm()
+{
+ formView->copyDataToGrid();
+ gridView->selectRow(formView->getCurrentRow());
+ selectiveRollbackGrid();
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+ formViewFocusFirstEditor();
+}
+
+void DataView::firstRow()
+{
+ goToFormRow(IndexModifier::FIRST);
+ formViewFocusFirstEditor();
+}
+
+void DataView::prevRow()
+{
+ goToFormRow(IndexModifier::PREV);
+ formViewFocusFirstEditor();
+}
+
+void DataView::nextRow()
+{
+ goToFormRow(IndexModifier::NEXT);
+ formViewFocusFirstEditor();
+}
+
+void DataView::lastRow()
+{
+ goToFormRow(IndexModifier::LAST);
+ formViewFocusFirstEditor();
+}
+
+void DataView::initFormViewForNewRow()
+{
+ if (currentWidget() != formWidget)
+ return;
+
+ int row = gridView->getCurrentIndex().row();
+ for (SqlQueryItem* item : getModel()->getRow(row))
+ item->setValue("");
+}
+
+void DataView::formViewFocusFirstEditor()
+{
+ if (currentWidget() == formWidget)
+ formView->focusFirstEditor();
+}
+
+void DataView::columnsHeaderClicked(int columnIdx)
+{
+ model->changeSorting(columnIdx);
+}
+
+void DataView::tabChanged(int newIndex)
+{
+ switch (newIndex)
+ {
+ case 0:
+ {
+ formView->copyDataToGrid();
+ gridView->setFocus();
+ break;
+ }
+ case 1:
+ {
+ if (!gridView->getCurrentIndex().isValid() && model->rowCount() > 0)
+ gridView->setCurrentRow(0);
+
+ int row = gridView->getCurrentIndex().row();
+ model->loadFullDataForEntireRow(row);
+ formView->updateFromGrid();
+ updateCurrentFormViewRow();
+ break;
+ }
+ }
+}
+
+FormView* DataView::getFormView() const
+{
+ return formView;
+}
+
+SqlQueryModel* DataView::getModel() const
+{
+ return model;
+}
+
+QToolBar* DataView::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_GRID:
+ return gridToolBar;
+ case TOOLBAR_FORM:
+ return formToolBar;
+ }
+ return nullptr;
+}
+
+void DataView::staticInit()
+{
+ filterMode = FilterMode::STRING;
+ tabsPosition = TabsPosition::TOP;
+ loadTabsMode();
+ createStaticActions();
+}
+
+void DataView::insertAction(ExtActionPrototype* action, DataView::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<DataView>(action, toolbar);
+}
+
+void DataView::insertActionBefore(ExtActionPrototype* action, DataView::Action beforeAction, DataView::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<DataView>(action, beforeAction, toolbar);
+}
+
+void DataView::insertActionAfter(ExtActionPrototype* action, DataView::Action afterAction, DataView::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<DataView>(action, afterAction, toolbar);
+}
+
+void DataView::removeAction(ExtActionPrototype* action, DataView::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<DataView>(action, toolbar);
+}
+
+SqlQueryView* DataView::getGridView() const
+{
+ return gridView;
+}
+
+int qHash(DataView::ActionGroup action)
+{
+ return static_cast<int>(action);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.h b/SQLiteStudio3/guiSQLiteStudio/dataview.h
new file mode 100644
index 0000000..19e56c0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dataview.h
@@ -0,0 +1,199 @@
+#ifndef DATAVIEW_H
+#define DATAVIEW_H
+
+#include "common/extactioncontainer.h"
+#include "guiSQLiteStudio_global.h"
+#include <QTabWidget>
+#include <QMutex>
+
+class QToolBar;
+class SqlQueryView;
+class SqlQueryModel;
+class FormView;
+class ExtLineEdit;
+class QLabel;
+class IntValidator;
+
+CFG_KEY_LIST(DataView, QObject::tr("Data view (both grid and form)"),
+ CFG_KEY_ENTRY(REFRESH_DATA, Qt::Key_F5, QObject::tr("Refresh data"))
+ CFG_KEY_ENTRY(SHOW_GRID_VIEW, Qt::CTRL + Qt::Key_Comma, QObject::tr("Switch to grid view of the data"))
+ CFG_KEY_ENTRY(SHOW_FORM_VIEW, Qt::CTRL + Qt::Key_Period, QObject::tr("Switch to form view of the data"))
+)
+
+class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ SHOW_GRID_VIEW,
+ SHOW_FORM_VIEW,
+ TABS_ON_TOP,
+ TABS_AT_BOTTOM,
+ // Grid view
+ REFRESH_DATA,
+ FIRST_PAGE,
+ PREV_PAGE,
+ NEXT_PAGE,
+ LAST_PAGE,
+ PAGE_EDIT,
+ FILTER_VALUE,
+ FILTER,
+ FILTER_STRING,
+ FILTER_SQL,
+ FILTER_REGEXP,
+ GRID_TOTAL_ROWS,
+ SELECTIVE_COMMIT,
+ SELECTIVE_ROLLBACK,
+ // Form view
+ FORM_TOTAL_ROWS,
+ FORM_CURRENT_ROW
+ };
+
+ enum class ActionGroup
+ {
+ FILTER_MODE,
+ TABS_POSITION
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_GRID,
+ TOOLBAR_FORM
+ };
+
+ explicit DataView(QWidget *parent = 0);
+
+ void init(SqlQueryModel* model);
+
+ FormView* getFormView() const;
+ SqlQueryView* getGridView() const;
+ SqlQueryModel* getModel() const;
+ QToolBar* getToolBar(int toolbar) const;
+ bool isUncommited() const;
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_GRID);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_GRID);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_GRID);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_GRID);
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ enum class TabsPosition
+ {
+ TOP,
+ BOTTOM
+ };
+
+ enum class IndexModifier
+ {
+ FIRST,
+ PREV,
+ NEXT,
+ LAST
+ };
+
+ enum class FilterMode
+ {
+ STRING,
+ SQL,
+ REGEXP
+ };
+
+ static void createStaticActions();
+ static void loadTabsMode();
+
+ void initFormView();
+ void initFilter();
+ void initUpdates();
+ void initSlots();
+ void initPageEdit();
+ void createContents();
+ void goToFormRow(IndexModifier idxMod);
+ void setNavigationState(bool enabled);
+ void updateNavigationState();
+ void updateGridNavigationState();
+ void goToPage(const QString& pageStr);
+ void updatePageEdit();
+ void updateResultsCount(int resultsCount);
+ void updateCurrentFormViewRow();
+ void setFormViewEnabled(bool enabled);
+ void readData();
+ void updateFilterIcon();
+ void initFormViewForNewRow();
+ void formViewFocusFirstEditor();
+
+ static FilterMode filterMode;
+ static TabsPosition tabsPosition;
+ static QHash<Action,QAction*> staticActions;
+ static QHash<ActionGroup,QActionGroup*> staticActionGroups;
+
+ QToolBar* gridToolBar = nullptr;
+ QToolBar* formToolBar = nullptr;
+ SqlQueryView* gridView = nullptr;
+ SqlQueryModel* model = nullptr;
+ FormView* formView = nullptr;
+ QWidget* gridWidget = nullptr;
+ QWidget* formWidget = nullptr;
+ ExtLineEdit* filterEdit = nullptr;
+ QLabel* rowCountLabel = nullptr;
+ QLabel* formViewRowCountLabel = nullptr;
+ QLabel* formViewCurrentRowLabel = nullptr;
+ ExtLineEdit* pageEdit = nullptr;
+ IntValidator* pageValidator = nullptr;
+ bool navigationState = false;
+ bool totalPagesAvailable = false;
+ QMutex manualPageChangeMutex;
+ bool uncommittedGrid = false;
+ bool uncommittedForm = false;
+
+ signals:
+
+ public slots:
+ void refreshData();
+
+ private slots:
+ void dataLoadingEnded(bool successful);
+ void executionSuccessful();
+ void totalRowsAndPagesAvailable();
+ void insertRow();
+ void insertMultipleRows();
+ void deleteRow();
+ void commitGrid();
+ void rollbackGrid();
+ void selectiveCommitGrid();
+ void selectiveRollbackGrid();
+ void firstPage();
+ void prevPage();
+ void nextPage();
+ void lastPage();
+ void pageEntered();
+ void applyFilter();
+ void resetFilter();
+ void commitForm();
+ void rollbackForm();
+ void firstRow();
+ void prevRow();
+ void nextRow();
+ void lastRow();
+ void columnsHeaderClicked(int columnIdx);
+ void tabChanged(int newIndex);
+ void updateFormNavigationState();
+ void updateFormCommitRollbackActions();
+ void updateCommitRollbackActions(bool enabled);
+ void updateSelectiveCommitRollbackActions(bool enabled);
+ void showGridView();
+ void showFormView();
+ void updateTabsMode();
+ void filterModeSelected();
+};
+
+int qHash(DataView::ActionGroup action);
+
+#endif // DATAVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp
new file mode 100644
index 0000000..baa0f76
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp
@@ -0,0 +1,184 @@
+#include "dblistmodel.h"
+#include "services/dbmanager.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "uiconfig.h"
+#include <QComboBox>
+
+DbListModel::DbListModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+ unsortedList = DBLIST->getConnectedDbList();
+
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+
+ setSortMode(CFG_UI.General.SqlEditorDbListOrder.get());
+}
+
+DbListModel::~DbListModel()
+{
+}
+
+QVariant DbListModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() < 0 || index.row() >= dbList.size())
+ return QVariant();
+
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ return dbList[index.row()]->getName();
+
+ return QVariant();
+}
+
+int DbListModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return dbList.count();
+}
+
+QModelIndex DbListModel::sibling(int row, int column, const QModelIndex &idx) const
+{
+ if (!idx.isValid() || column != 0 || row >= dbList.count())
+ return QModelIndex();
+
+ return createIndex(row, 0);
+}
+
+Db* DbListModel::getDb(int index)
+{
+ if (index < 0 || index >= dbList.size())
+ return nullptr;
+
+ return dbList[index];
+}
+
+void DbListModel::setSortMode(DbListModel::SortMode sortMode)
+{
+ this->sortMode = sortMode;
+ sort();
+}
+
+DbListModel::SortMode DbListModel::getSortMode() const
+{
+ return sortMode;
+}
+
+void DbListModel::setSortMode(const QString& sortMode)
+{
+ if (sortMode == "LikeDbTree")
+ this->sortMode = SortMode::LikeDbTree;
+ else if (sortMode == "Alphabetical")
+ this->sortMode = SortMode::Alphabetical;
+ else
+ this->sortMode = SortMode::ConnectionOrder;
+
+ sort();
+}
+
+QString DbListModel::getSortModeString() const
+{
+ switch (sortMode)
+ {
+ case DbListModel::SortMode::LikeDbTree:
+ return "LikeDbTree";
+ case DbListModel::SortMode::Alphabetical:
+ return "Alphabetical";
+ case DbListModel::SortMode::ConnectionOrder:
+ break;
+ }
+ return "ConnectionOrder";
+}
+
+void DbListModel::setCombo(QComboBox* combo)
+{
+ comboBox = combo;
+}
+
+void DbListModel::sort()
+{
+ dbList = unsortedList;
+ switch (sortMode)
+ {
+ case DbListModel::SortMode::LikeDbTree:
+ {
+ DbTreeComparer comparer;
+ qSort(dbList.begin(), dbList.end(), comparer);
+ break;
+ }
+ case DbListModel::SortMode::Alphabetical:
+ {
+ AlphaComparer comparer;
+ qSort(dbList.begin(), dbList.end(), comparer);
+ break;
+ }
+ case DbListModel::SortMode::ConnectionOrder:
+ break;
+ }
+}
+
+void DbListModel::dbConnected(Db* db)
+{
+ QString current;
+ if (comboBox)
+ current = comboBox->currentText();
+
+ beginResetModel();
+ unsortedList += db;
+ sort();
+ endResetModel();
+
+ if (!current.isNull())
+ comboBox->setCurrentText(current);
+ else
+ comboBox->setCurrentText(dbList.first()->getName());
+}
+
+void DbListModel::dbDisconnected(Db* db)
+{
+ QString current;
+ int newIdx = -1;
+ if (comboBox)
+ {
+ if (db->getName() == comboBox->currentText())
+ newIdx = 0;
+ else
+ current = comboBox->currentText();
+ }
+
+ beginResetModel();
+ dbList.removeAll(db);
+ unsortedList.removeAll(db);
+ endResetModel();
+
+ if (!current.isNull())
+ comboBox->setCurrentText(current);
+ else if (newIdx > -1)
+ comboBox->setCurrentIndex(newIdx);
+}
+
+DbListModel::DbTreeComparer::DbTreeComparer()
+{
+ // TODO when sorting or D&D databases in the tree, this should be updated
+ QList<DbTreeItem*> allItems = DBTREE->getModel()->getAllItemsAsFlatList();
+ dbTreeOrder.clear();
+ foreach (DbTreeItem* item, allItems)
+ {
+ if (item->getType() != DbTreeItem::Type::DB)
+ continue;
+
+ dbTreeOrder << item->text();
+ }
+}
+
+bool DbListModel::DbTreeComparer::operator()(Db* db1, Db* db2)
+{
+ return dbTreeOrder.indexOf(db1->getName()) < dbTreeOrder.indexOf(db2->getName());
+}
+
+bool DbListModel::AlphaComparer::operator()(Db* db1, Db* db2)
+{
+ return db1->getName().compare(db2->getName()) < 0;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h
new file mode 100644
index 0000000..121db4d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h
@@ -0,0 +1,66 @@
+#ifndef DBLISTMODEL_H
+#define DBLISTMODEL_H
+
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractListModel>
+
+class QComboBox;
+
+class GUI_API_EXPORT DbListModel : public QAbstractListModel
+{
+ Q_OBJECT
+ public:
+ enum class SortMode
+ {
+ LikeDbTree,
+ Alphabetical,
+ ConnectionOrder
+ };
+
+ explicit DbListModel(QObject *parent = 0);
+ ~DbListModel();
+
+ QVariant data(const QModelIndex & index, int role) const;
+ int rowCount(const QModelIndex & parent = QModelIndex()) const;
+ QModelIndex sibling(int row, int column, const QModelIndex & idx) const;
+
+ Db* getDb(int index);
+ void setSortMode(SortMode sortMode);
+ SortMode getSortMode() const;
+ void setSortMode(const QString& sortMode);
+ QString getSortModeString() const;
+ void setCombo(QComboBox* combo);
+
+ private:
+ using QAbstractItemModel::sort;
+
+ class DbTreeComparer
+ {
+ public:
+ DbTreeComparer();
+ bool operator()(Db* db1, Db* db2);
+
+ private:
+ QStringList dbTreeOrder;
+ };
+
+ class AlphaComparer
+ {
+ public:
+ bool operator()(Db* db1, Db* db2);
+ };
+
+ void sort();
+
+ QList<Db*> unsortedList;
+ QList<Db*> dbList;
+ SortMode sortMode = SortMode::ConnectionOrder;
+ QComboBox* comboBox = nullptr;
+
+ private slots:
+ void dbConnected(Db* db);
+ void dbDisconnected(Db* db);
+};
+
+#endif // DBLISTMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp
new file mode 100644
index 0000000..25686b5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp
@@ -0,0 +1,309 @@
+#include "dbobjectdialogs.h"
+#include "mainwindow.h"
+#include "dialogs/indexdialog.h"
+#include "dialogs/triggerdialog.h"
+#include "common/utils_sql.h"
+#include "dbtree/dbtree.h"
+#include "services/notifymanager.h"
+#include "mdiarea.h"
+#include "mdiwindow.h"
+#include "windows/tablewindow.h"
+#include "windows/viewwindow.h"
+#include "db/sqlquery.h"
+#include "services/config.h"
+#include <QMessageBox>
+#include <QDebug>
+
+DbObjectDialogs::DbObjectDialogs(Db* db) :
+ db(db)
+{
+ mainWindow = MainWindow::getInstance();
+ mdiArea = mainWindow->getMdiArea();
+ parentWidget = mainWindow;
+}
+
+DbObjectDialogs::DbObjectDialogs(Db* db, QWidget* parentWidget) :
+ db(db), parentWidget(parentWidget)
+{
+ mainWindow = MainWindow::getInstance();
+ mdiArea = mainWindow->getMdiArea();
+}
+
+void DbObjectDialogs::addIndex(const QString& table)
+{
+ IndexDialog dialog(db, parentWidget);
+ if (!table.isNull())
+ dialog.setTable(table);
+
+ dialog.exec();
+}
+
+void DbObjectDialogs::editIndex(const QString& index)
+{
+ if (index.isNull())
+ {
+ qWarning() << "Tried to edit null index.";
+ return;
+ }
+
+ IndexDialog dialog(db, index, parentWidget);
+ dialog.exec();
+}
+
+void DbObjectDialogs::addTriggerOnTable(const QString& table)
+{
+ addTrigger(table, QString::null);
+}
+
+void DbObjectDialogs::addTriggerOnView(const QString& view)
+{
+ addTrigger(QString::null, view);
+}
+
+void DbObjectDialogs::addTrigger(const QString& table, const QString& view)
+{
+ TriggerDialog dialog(db, parentWidget);
+ if (!table.isNull())
+ dialog.setParentTable(table);
+ else if (!view.isNull())
+ dialog.setParentView(view);
+ else
+ return;
+
+ dialog.exec();
+}
+
+void DbObjectDialogs::editTrigger(const QString& trigger)
+{
+ if (trigger.isNull())
+ {
+ qWarning() << "Tried to edit null trigger.";
+ return;
+ }
+
+ TriggerDialog dialog(db, parentWidget);
+ dialog.setTrigger(trigger);
+ dialog.exec();
+}
+
+ViewWindow* DbObjectDialogs::addView(const QString &initialSelect)
+{
+ ViewWindow* win = new ViewWindow(db, mdiArea);
+ win->setSelect(initialSelect);
+ mdiArea->addSubWindow(win);
+ return win;
+}
+
+ViewWindow* DbObjectDialogs::editView(const QString& database, const QString& view)
+{
+ ViewWindow* win = nullptr;
+ foreach (MdiWindow* mdiWin, mdiArea->getWindows())
+ {
+ win = dynamic_cast<ViewWindow*>(mdiWin->getMdiChild());
+ if (!win)
+ continue;
+
+ if (win->getDb() == db && win->getView() == view)
+ {
+ mdiArea->setActiveSubWindow(mdiWin);
+ return win;
+ }
+ }
+
+ win = new ViewWindow(mdiArea, db, database, view);
+ if (win->isInvalid())
+ {
+ delete win;
+ return nullptr;
+ }
+
+ mdiArea->addSubWindow(win);
+ return win;
+}
+
+void DbObjectDialogs::editObject(const QString& name)
+{
+ editObject("main", name);
+}
+
+void DbObjectDialogs::editObject(const QString& database, const QString& name)
+{
+ Type type = getObjectType(database, name);
+ switch (type)
+ {
+ case Type::TABLE:
+ editTable(database, name);
+ break;
+ case Type::INDEX:
+ editIndex(name);
+ break;
+ case Type::TRIGGER:
+ editTrigger(name);
+ break;
+ case Type::VIEW:
+ editView(database, name);
+ break;
+ default:
+ {
+ qCritical() << "Unknown object type while trying to edit object. Object name:" << database << "." << name;
+ return;
+ }
+ }
+}
+
+bool DbObjectDialogs::dropObject(const QString& name)
+{
+ return dropObject("main", name);
+}
+
+bool DbObjectDialogs::dropObject(const QString& database, const QString& name)
+{
+ static const QString dropSql2 = "DROP %1 %2;";
+ static const QString dropSql3 = "DROP %1 %2.%3;";
+
+ Dialect dialect = db->getDialect();
+ QString dbName = wrapObjIfNeeded(database, dialect);
+
+ Type type = getObjectType(database, name);
+ QString title;
+ QString message;
+ QString typeForSql;
+ switch (type)
+ {
+ case Type::TABLE:
+ title = tr("Delete table");
+ message = tr("Are you sure you want to delete table %1?");
+ typeForSql = "TABLE";
+ break;
+ case Type::INDEX:
+ title = tr("Delete index");
+ message = tr("Are you sure you want to delete index %1?");
+ typeForSql = "INDEX";
+ break;
+ case Type::TRIGGER:
+ title = tr("Delete trigger");
+ message = tr("Are you sure you want to delete trigger %1?");
+ typeForSql = "TRIGGER";
+ break;
+ case Type::VIEW:
+ title = tr("Delete view");
+ message = tr("Are you sure you want to delete view %1?");
+ typeForSql = "VIEW";
+ break;
+ default:
+ {
+ qCritical() << "Unknown object type while trying to drop object. Object name:" << database << "." << name;
+ return false;
+ }
+ }
+
+ if (!noConfirmation)
+ {
+ QMessageBox::StandardButton resp = QMessageBox::question(parentWidget, title, message.arg(name));
+ if (resp != QMessageBox::Yes)
+ return false;
+ }
+
+ SqlQueryPtr results;
+
+ QString finalSql;
+ if (dialect == Dialect::Sqlite3)
+ finalSql = dropSql3.arg(typeForSql, dbName, wrapObjIfNeeded(name, dialect));
+ else
+ finalSql = dropSql2.arg(typeForSql, wrapObjIfNeeded(name, dialect));
+
+ results = db->exec(finalSql);
+ if (results->isError())
+ {
+ notifyError(tr("Error while dropping %1: %2").arg(name).arg(results->getErrorText()));
+ qCritical() << "Error while dropping object " << database << "." << name << ":" << results->getErrorText();
+ return false;
+ }
+
+ CFG->addDdlHistory(finalSql, db->getName(), db->getPath());
+ if (!noSchemaRefreshing)
+ DBTREE->refreshSchema(db);
+
+ return true;
+}
+
+DbObjectDialogs::Type DbObjectDialogs::getObjectType(const QString& database, const QString& name)
+{
+ static const QString typeSql = "SELECT type FROM %1.sqlite_master WHERE name = ?;";
+ static const QStringList types = {"table", "index", "trigger", "view"};
+
+ Dialect dialect = db->getDialect();
+ QString dbName = wrapObjIfNeeded(database, dialect);
+ SqlQueryPtr results = db->exec(typeSql.arg(dbName), {name});
+ if (results->isError())
+ {
+ qCritical() << "Could not get object type. Object name:" << database << "." << name << ", error:"
+ << results->getErrorText();
+ return Type::UNKNOWN;
+ }
+
+ QString typeStr = results->getSingleCell().toString();
+ return static_cast<Type>(types.indexOf(typeStr));
+}
+bool DbObjectDialogs::getNoSchemaRefreshing() const
+{
+ return noSchemaRefreshing;
+}
+
+void DbObjectDialogs::setNoSchemaRefreshing(bool value)
+{
+ noSchemaRefreshing = value;
+}
+
+bool DbObjectDialogs::getNoConfirmation() const
+{
+ return noConfirmation;
+}
+
+void DbObjectDialogs::setNoConfirmation(bool value)
+{
+ noConfirmation = value;
+}
+
+
+TableWindow* DbObjectDialogs::editTable(const QString& database, const QString& table)
+{
+ TableWindow* win = nullptr;
+ foreach (MdiWindow* mdiWin, mdiArea->getWindows())
+ {
+ win = dynamic_cast<TableWindow*>(mdiWin->getMdiChild());
+ if (!win)
+ continue;
+
+ if (win->getDb() == db && win->getTable() == table)
+ {
+ mdiArea->setActiveSubWindow(mdiWin);
+ return win;
+ }
+ }
+
+ win = new TableWindow(mdiArea, db, database, table);
+ if (win->isInvalid())
+ {
+ delete win;
+ return nullptr;
+ }
+
+ mdiArea->addSubWindow(win);
+ return win;
+}
+
+TableWindow *DbObjectDialogs::addTableSimilarTo(const QString &database, const QString &table)
+{
+ TableWindow* win = new TableWindow(mdiArea, db, database, table);
+ mdiArea->addSubWindow(win);
+ win->useCurrentTableAsBaseForNew();
+ return win;
+}
+
+TableWindow* DbObjectDialogs::addTable()
+{
+ TableWindow* win = new TableWindow(db, mdiArea);
+ mdiArea->addSubWindow(win);
+ return win;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h
new file mode 100644
index 0000000..bedbab8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h
@@ -0,0 +1,67 @@
+#ifndef DBOBJECTDIALOGS_H
+#define DBOBJECTDIALOGS_H
+
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include <QString>
+#include <QStringList>
+
+class QWidget;
+class MainWindow;
+class MdiArea;
+class TableWindow;
+class ViewWindow;
+
+class GUI_API_EXPORT DbObjectDialogs : public QObject
+{
+ public:
+ explicit DbObjectDialogs(Db* db);
+ DbObjectDialogs(Db* db, QWidget* parentWidget);
+
+ TableWindow* addTable();
+ TableWindow* editTable(const QString& database, const QString& table);
+ TableWindow* addTableSimilarTo(const QString& database, const QString& table);
+
+ void addIndex(const QString& table);
+ void editIndex(const QString& index);
+
+ void addTriggerOnTable(const QString& table);
+ void addTriggerOnView(const QString& view);
+ void addTrigger(const QString& table, const QString& view);
+ void editTrigger(const QString& trigger);
+
+ ViewWindow* addView(const QString& initialSelect = QString());
+ ViewWindow* editView(const QString& database, const QString& view);
+
+ void editObject(const QString& name);
+ void editObject(const QString& database, const QString& name);
+ bool dropObject(const QString& name);
+ bool dropObject(const QString& database, const QString& name);
+
+ bool getNoConfirmation() const;
+ void setNoConfirmation(bool value);
+
+ bool getNoSchemaRefreshing() const;
+ void setNoSchemaRefreshing(bool value);
+
+ private:
+ enum class Type
+ {
+ TABLE = 0,
+ INDEX = 1,
+ TRIGGER = 2,
+ VIEW = 3,
+ UNKNOWN = -1
+ };
+
+ Type getObjectType(const QString& database, const QString& name);
+
+ Db* db = nullptr;
+ QWidget* parentWidget = nullptr;
+ MainWindow* mainWindow = nullptr;
+ MdiArea* mdiArea = nullptr;
+ bool noConfirmation = false;
+ bool noSchemaRefreshing = false;
+};
+
+#endif // DBOBJECTDIALOGS_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp
new file mode 100644
index 0000000..99d3f27
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp
@@ -0,0 +1,120 @@
+#include "dbobjlistmodel.h"
+#include "db/db.h"
+#include <QDebug>
+#include <schemaresolver.h>
+
+DbObjListModel::DbObjListModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+}
+
+QVariant DbObjListModel::data(const QModelIndex& index, int role) const
+{
+ if (index.row() < 0 || index.row() >= objectList.size())
+ return QVariant();
+
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ if (sortMode == SortMode::Alphabetical)
+ return objectList[index.row()];
+ else
+ return unsortedObjectList[index.row()];
+ }
+
+ return QVariant();
+}
+
+int DbObjListModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return objectList.count();
+}
+
+QModelIndex DbObjListModel::sibling(int row, int column, const QModelIndex& idx) const
+{
+ if (!idx.isValid() || column != 0 || row >= objectList.count())
+ return QModelIndex();
+
+ return createIndex(row, 0);
+}
+
+Db* DbObjListModel::getDb() const
+{
+ return db;
+}
+
+void DbObjListModel::setDb(Db* value)
+{
+ db = value;
+ updateList();
+}
+
+DbObjListModel::SortMode DbObjListModel::getSortMode() const
+{
+ return sortMode;
+}
+
+void DbObjListModel::setSortMode(const SortMode& value)
+{
+ sortMode = value;
+ beginResetModel();
+ endResetModel();
+}
+
+DbObjListModel::ObjectType DbObjListModel::getType() const
+{
+ return type;
+}
+
+void DbObjListModel::setType(const ObjectType& value)
+{
+ type = value;
+ updateList();
+}
+
+void DbObjListModel::updateList()
+{
+ if (!db || type == ObjectType::null)
+ return;
+
+ beginResetModel();
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!includeSystemObjects);
+ objectList = resolver.getObjects(typeString().toLower());
+ unsortedObjectList = objectList;
+ qSort(objectList);
+ endResetModel();
+}
+
+QString DbObjListModel::typeString() const
+{
+ switch (type)
+ {
+ case ObjectType::TABLE:
+ return "TABLE";
+ case ObjectType::INDEX:
+ return "INDEX";
+ case ObjectType::TRIGGER:
+ return "TRIGGER";
+ case ObjectType::VIEW:
+ return "VIEW";
+ case ObjectType::null:
+ break;
+ }
+ return QString::null;
+}
+bool DbObjListModel::getIncludeSystemObjects() const
+{
+ return includeSystemObjects;
+}
+
+void DbObjListModel::setIncludeSystemObjects(bool value)
+{
+ includeSystemObjects = value;
+}
+
+
+
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h
new file mode 100644
index 0000000..cac1202
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h
@@ -0,0 +1,59 @@
+#ifndef DBOBJLISTMODEL_H
+#define DBOBJLISTMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractListModel>
+
+class Db;
+
+class GUI_API_EXPORT DbObjListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ public:
+ enum class SortMode
+ {
+ LikeInDb,
+ Alphabetical
+ };
+
+ enum class ObjectType
+ {
+ TABLE,
+ INDEX,
+ TRIGGER,
+ VIEW,
+ null
+ };
+
+ explicit DbObjListModel(QObject *parent = 0);
+
+ QVariant data(const QModelIndex & index, int role) const;
+ int rowCount(const QModelIndex & parent = QModelIndex()) const;
+ QModelIndex sibling(int row, int column, const QModelIndex & idx) const;
+
+ Db* getDb() const;
+ void setDb(Db* value);
+
+ SortMode getSortMode() const;
+ void setSortMode(const SortMode& value);
+
+ ObjectType getType() const;
+ void setType(const ObjectType& value);
+
+ bool getIncludeSystemObjects() const;
+ void setIncludeSystemObjects(bool value);
+
+ private:
+ void updateList();
+ QString typeString() const;
+
+ ObjectType type = ObjectType::null;
+ Db* db = nullptr;
+ SortMode sortMode = SortMode::LikeInDb;
+ QStringList objectList;
+ QStringList unsortedObjectList;
+ bool includeSystemObjects = true;
+};
+
+#endif // DBOBJLISTMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp
new file mode 100644
index 0000000..98baaa9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp
@@ -0,0 +1,1557 @@
+#include "dbtree.h"
+#include "dbtreeitem.h"
+#include "ui_dbtree.h"
+#include "actionentry.h"
+#include "common/utils_sql.h"
+#include "dbtreemodel.h"
+#include "dialogs/dbdialog.h"
+#include "services/dbmanager.h"
+#include "iconmanager.h"
+#include "common/global.h"
+#include "services/notifymanager.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "common/unused.h"
+#include "dbobjectdialogs.h"
+#include "common/userinputfilter.h"
+#include "common/widgetcover.h"
+#include "windows/tablewindow.h"
+#include "dialogs/indexdialog.h"
+#include "dialogs/triggerdialog.h"
+#include "dialogs/exportdialog.h"
+#include "dialogs/importdialog.h"
+#include "dialogs/populatedialog.h"
+#include "services/importmanager.h"
+#include "windows/editorwindow.h"
+#include "uiconfig.h"
+#include <QApplication>
+#include <QClipboard>
+#include <QAction>
+#include <QMenu>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QTimer>
+#include <QDebug>
+#include <QKeyEvent>
+#include <QMimeData>
+#include <dialogs/dbconverterdialog.h>
+
+CFG_KEYS_DEFINE(DbTree)
+QHash<DbTreeItem::Type,QList<DbTreeItem::Type>> DbTree::allowedTypesInside;
+QSet<DbTreeItem::Type> DbTree::draggableTypes;
+
+DbTree::DbTree(QWidget *parent) :
+ QDockWidget(parent),
+ ui(new Ui::DbTree)
+{
+ init();
+}
+
+DbTree::~DbTree()
+{
+ delete ui;
+ delete treeModel;
+}
+
+void DbTree::staticInit()
+{
+ initDndTypes();
+}
+
+void DbTree::init()
+{
+ ui->setupUi(this);
+ initDndTypes();
+
+ ui->nameFilter->setClearButtonEnabled(true);
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer();
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(interrupt()));
+
+ treeModel = new DbTreeModel();
+ treeModel->setTreeView(ui->treeView);
+
+ new UserInputFilter(ui->nameFilter, treeModel, SLOT(applyFilter(QString)));
+
+ ui->treeView->setDbTree(this);
+ ui->treeView->setModel(treeModel);
+
+ initActions();
+
+ if (DBLIST->getDbList().size() > 0)
+ treeModel->loadDbList();
+
+ connect(DBLIST, SIGNAL(dbListLoaded()), treeModel, SLOT(loadDbList()));
+ connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &DbTree::currentChanged);
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+ connect(IMPORT_MANAGER, SIGNAL(schemaModified(Db*)), this, SLOT(refreshSchema(Db*)));
+
+ connect(CFG_UI.Fonts.DbTree, SIGNAL(changed(QVariant)), this, SLOT(refreshFont()));
+
+ updateActionsForCurrent();
+}
+
+void DbTree::createActions()
+{
+ createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this);
+ createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this);
+ createAction(SELECT_ALL, ICONS.ACT_SELECT_ALL, tr("Select all"), this, SLOT(selectAll()), this);
+ createAction(CREATE_GROUP, ICONS.DIRECTORY_ADD, tr("Create a group"), this, SLOT(createGroup()), this);
+ createAction(DELETE_GROUP, ICONS.DIRECTORY_DEL, tr("Delete the group"), this, SLOT(deleteGroup()), this);
+ createAction(RENAME_GROUP, ICONS.DIRECTORY_EDIT, tr("Rename the group"), this, SLOT(renameGroup()), this);
+ createAction(ADD_DB, ICONS.DATABASE_ADD, tr("Add a database"), this, SLOT(addDb()), this);
+ createAction(EDIT_DB, ICONS.DATABASE_EDIT, tr("Edit the database"), this, SLOT(editDb()), this);
+ createAction(DELETE_DB, ICONS.DATABASE_DEL, tr("Remove the database"), this, SLOT(removeDb()), this);
+ createAction(CONNECT_TO_DB, ICONS.DATABASE_CONNECT, tr("Connect to the database"), this, SLOT(connectToDb()), this);
+ createAction(DISCONNECT_FROM_DB, ICONS.DATABASE_DISCONNECT, tr("Disconnect from the database"), this, SLOT(disconnectFromDb()), this);
+ createAction(IMPORT_INTO_DB, ICONS.IMPORT, tr("Import"), this, SLOT(import()), this);
+ createAction(EXPORT_DB, ICONS.DATABASE_EXPORT, tr("Export the database"), this, SLOT(exportDb()), this);
+ createAction(CONVERT_DB, ICONS.CONVERT_DB, tr("Convert database type"), this, SLOT(convertDb()), this);
+ createAction(VACUUM_DB, ICONS.VACUUM_DB, tr("Vacuum"), this, SLOT(vacuumDb()), this);
+ createAction(INTEGRITY_CHECK, ICONS.INTEGRITY_CHECK, tr("Integrity check"), this, SLOT(integrityCheck()), this);
+ createAction(ADD_TABLE, ICONS.TABLE_ADD, tr("Create a table"), this, SLOT(addTable()), this);
+ createAction(EDIT_TABLE, ICONS.TABLE_EDIT, tr("Edit the table"), this, SLOT(editTable()), this);
+ createAction(DEL_TABLE, ICONS.TABLE_DEL, tr("Drop the table"), this, SLOT(delTable()), this);
+ createAction(EXPORT_TABLE, ICONS.TABLE_EXPORT, tr("Export the table"), this, SLOT(exportTable()), this);
+ createAction(IMPORT_TABLE, ICONS.TABLE_IMPORT, tr("Import into the table"), this, SLOT(importTable()), this);
+ createAction(POPULATE_TABLE, ICONS.TABLE_POPULATE, tr("Populate table"), this, SLOT(populateTable()), this);
+ createAction(CREATE_SIMILAR_TABLE, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table"), this, SLOT(createSimilarTable()), this);
+ createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create an index"), this, SLOT(addIndex()), this);
+ createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit the index"), this, SLOT(editIndex()), this);
+ createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Drop the index"), this, SLOT(delIndex()), this);
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create a trigger"), this, SLOT(addTrigger()), this);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit the trigger"), this, SLOT(editTrigger()), this);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Drop the trigger"), this, SLOT(delTrigger()), this);
+ createAction(ADD_VIEW, ICONS.VIEW_ADD, tr("Create a view"), this, SLOT(addView()), this);
+ createAction(EDIT_VIEW, ICONS.VIEW_EDIT, tr("Edit the view"), this, SLOT(editView()), this);
+ createAction(DEL_VIEW, ICONS.VIEW_DEL, tr("Drop the view"), this, SLOT(delView()), this);
+ createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add a column"), this, SLOT(addColumn()), this);
+ createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit the column"), this, SLOT(editColumn()), this);
+ createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete the column"), this, SLOT(delColumn()), this);
+ createAction(DEL_SELECTED, ICONS.DELETE_SELECTED, tr("Delete selected items"), this, SLOT(deleteSelected()), this);
+ createAction(CLEAR_FILTER, tr("Clear filter"), ui->nameFilter, SLOT(clear()), this);
+ createAction(REFRESH_SCHEMAS, ICONS.DATABASE_RELOAD, tr("Refresh all database schemas"), this, SLOT(refreshSchemas()), this);
+ createAction(REFRESH_SCHEMA, ICONS.DATABASE_RELOAD, tr("Refresh selected database schema"), this, SLOT(refreshSchema()), this);
+}
+
+void DbTree::updateActionStates(const QStandardItem *item)
+{
+ QList<int> enabled;
+ const DbTreeItem* dbTreeItem = dynamic_cast<const DbTreeItem*>(item);
+ if (item != nullptr)
+ {
+ bool isDbOpen = false;
+ DbTreeItem* parentItem = dbTreeItem->parentDbTreeItem();
+ DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr;
+
+ // Add database should always be available, as well as a copy of an item
+ enabled << ADD_DB << COPY;
+
+ if (isMimeDataValidForItem(QApplication::clipboard()->mimeData(), dbTreeItem))
+ enabled << PASTE;
+
+ enabled << CLEAR_FILTER;
+
+ // Group actions
+ if (dbTreeItem->getType() == DbTreeItem::Type::DIR)
+ enabled << CREATE_GROUP << RENAME_GROUP << DELETE_GROUP << ADD_DB;
+
+ if (dbTreeItem->getDb())
+ {
+ enabled << DELETE_DB << EDIT_DB;
+ if (dbTreeItem->getDb()->isOpen())
+ {
+ enabled << DISCONNECT_FROM_DB << ADD_TABLE << ADD_VIEW << IMPORT_INTO_DB << EXPORT_DB << REFRESH_SCHEMA << CONVERT_DB
+ << VACUUM_DB << INTEGRITY_CHECK;
+ isDbOpen = true;
+ }
+ else
+ enabled << CONNECT_TO_DB;
+ }
+
+ if (isDbOpen)
+ {
+ switch (dbTreeItem->getType())
+ {
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ case DbTreeItem::Type::DIR:
+ // It's handled outside of "item with db", above
+ break;
+ case DbTreeItem::Type::DB:
+ enabled << CREATE_GROUP << DELETE_DB << EDIT_DB;
+ break;
+ case DbTreeItem::Type::TABLES:
+ break;
+ case DbTreeItem::Type::TABLE:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << CREATE_SIMILAR_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ // TODO change below when virtual tables can be edited
+// enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << DEL_TABLE;
+ break;
+ case DbTreeItem::Type::INDEXES:
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::INDEX:
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << EDIT_INDEX << DEL_INDEX;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::TRIGGERS:
+ {
+ if (parentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ }
+ else
+ {
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ }
+
+ enabled << ADD_TRIGGER;
+ break;
+ }
+ case DbTreeItem::Type::TRIGGER:
+ {
+ if (grandParentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ }
+ else
+ {
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ }
+
+ enabled << EDIT_TRIGGER << DEL_TRIGGER;
+ break;
+ }
+ case DbTreeItem::Type::VIEWS:
+ break;
+ case DbTreeItem::Type::VIEW:
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << DEL_COLUMN;
+ enabled << EDIT_COLUMN;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ }
+ }
+
+ // Do we have any deletable object selected? If yes, enable "Del" action.
+ bool enableDel = false;
+ for (DbTreeItem* selItem : getModel()->getItemsForIndexes(getView()->getSelectedIndexes()))
+ {
+ switch (selItem->getType())
+ {
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ enableDel = true;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ break;
+ }
+
+ if (enableDel)
+ {
+ enabled << DEL_SELECTED;
+ break;
+ }
+ }
+ }
+ else
+ {
+ enabled << CREATE_GROUP << ADD_DB;
+ }
+
+ if (treeModel->rowCount() > 0)
+ enabled << SELECT_ALL; // if there's at least 1 item, enable this
+
+ enabled << REFRESH_SCHEMAS;
+
+ foreach (int action, actionMap.keys())
+ setActionEnabled(action, enabled.contains(action));
+}
+
+void DbTree::setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu)
+{
+ QList<ActionEntry> actions;
+
+ ActionEntry dbEntry(ICONS.DATABASE, tr("Datatabase"));
+ dbEntry += ADD_DB;
+ dbEntry += EDIT_DB;
+ dbEntry += DELETE_DB;
+
+ ActionEntry dbEntryExt(ICONS.DATABASE, tr("Datatabase"));
+ dbEntryExt += CONNECT_TO_DB;
+ dbEntryExt += DISCONNECT_FROM_DB;
+ dbEntryExt += _separator;
+ dbEntryExt += REFRESH_SCHEMA;
+ dbEntryExt += _separator;
+ dbEntryExt += ADD_DB;
+ dbEntryExt += EDIT_DB;
+ dbEntryExt += DELETE_DB;
+
+ ActionEntry groupEntry(ICONS.DIRECTORY, tr("Grouping"));
+ groupEntry += CREATE_GROUP;
+ groupEntry += RENAME_GROUP;
+ groupEntry += DELETE_GROUP;
+
+ if (currItem)
+ {
+ DbTreeItem* parentItem = currItem->parentDbTreeItem();
+ DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr;
+ DbTreeItem::Type itemType = currItem->getType();
+ switch (itemType)
+ {
+ case DbTreeItem::Type::DIR:
+ {
+ actions += ActionEntry(CREATE_GROUP);
+ actions += ActionEntry(RENAME_GROUP);
+ actions += ActionEntry(DELETE_GROUP);
+ actions += ActionEntry(_separator);
+ actions += dbEntry;
+ break;
+ }
+ case DbTreeItem::Type::DB:
+ {
+ if (currItem->getDb()->isValid())
+ {
+ actions += ActionEntry(CONNECT_TO_DB);
+ actions += ActionEntry(DISCONNECT_FROM_DB);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_DB);
+ actions += ActionEntry(EDIT_DB);
+ actions += ActionEntry(DELETE_DB);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(REFRESH_SCHEMA);
+ actions += ActionEntry(IMPORT_INTO_DB);
+ actions += ActionEntry(EXPORT_DB);
+ actions += ActionEntry(CONVERT_DB);
+ actions += ActionEntry(VACUUM_DB);
+ actions += ActionEntry(INTEGRITY_CHECK);
+ actions += ActionEntry(_separator);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_DB);
+ actions += ActionEntry(EDIT_DB);
+ actions += ActionEntry(DELETE_DB);
+ actions += ActionEntry(_separator);
+ }
+ break;
+ }
+ case DbTreeItem::Type::TABLES:
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::TABLE:
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(CREATE_SIMILAR_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ actions += ActionEntry(ADD_TABLE);
+ //actions += ActionEntry(EDIT_TABLE); // TODO uncomment when virtual tables have their own edition window
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::INDEXES:
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::INDEX:
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(EDIT_INDEX);
+ actions += ActionEntry(DEL_INDEX);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::TRIGGERS:
+ {
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ if (parentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ }
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ }
+ case DbTreeItem::Type::TRIGGER:
+ {
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(EDIT_TRIGGER);
+ actions += ActionEntry(DEL_TRIGGER);
+ actions += ActionEntry(_separator);
+ if (grandParentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ }
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ }
+ case DbTreeItem::Type::VIEWS:
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::VIEW:
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(EDIT_TRIGGER);
+ actions += ActionEntry(DEL_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(EDIT_COLUMN);
+ actions += ActionEntry(DEL_COLUMN);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ actions += ActionEntry(_separator);
+
+ if (itemType == DbTreeItem::Type::DB)
+ actions += groupEntry;
+ }
+ else
+ {
+ actions += dbEntry;
+ actions += ActionEntry(_separator);
+ actions += groupEntry;
+ }
+
+ actions += COPY;
+ actions += PASTE;
+ actions += _separator;
+ actions += DEL_SELECTED;
+ actions += _separator;
+ actions += SELECT_ALL;
+ actions += ActionEntry(REFRESH_SCHEMAS);
+
+ QMenu* subMenu = nullptr;
+ foreach (ActionEntry actionEntry, actions)
+ {
+ switch (actionEntry.type)
+ {
+ case ActionEntry::Type::SINGLE:
+ {
+ if (actionEntry.action == DbTree::_separator)
+ {
+ contextMenu->addSeparator();
+ break;
+ }
+ contextMenu->addAction(actionMap[actionEntry.action]);
+ break;
+ }
+ case ActionEntry::Type::SUB_MENU:
+ {
+ subMenu = contextMenu->addMenu(actionEntry.subMenuIcon, actionEntry.subMenuLabel);
+ foreach (Action action, actionEntry.actions)
+ {
+ if (action == DbTree::_separator)
+ {
+ subMenu->addSeparator();
+ continue;
+ }
+ subMenu->addAction(actionMap[action]);
+ }
+ break;
+ }
+ }
+ }
+}
+
+void DbTree::initDndTypes()
+{
+ draggableTypes << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW << DbTreeItem::Type::DIR << DbTreeItem::Type::DB;
+
+ allowedTypesInside[DbTreeItem::Type::DIR] << DbTreeItem::Type::DB << DbTreeItem::Type::DIR;
+ allowedTypesInside[DbTreeItem::Type::DB] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::TABLES] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::TABLE] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::VIEWS] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::VIEW] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+}
+
+QVariant DbTree::saveSession()
+{
+ treeModel->storeGroups();
+ return QVariant();
+}
+
+void DbTree::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+}
+
+DbTreeModel* DbTree::getModel() const
+{
+ return treeModel;
+}
+
+DbTreeView*DbTree::getView() const
+{
+ return ui->treeView;
+}
+
+bool DbTree::isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item)
+{
+ if (mimeData->formats().contains(DbTreeModel::MIMETYPE))
+ return areDbTreeItemsValidForItem(getModel()->getDragItems(mimeData), item);
+ else if (mimeData->hasUrls())
+ return areUrlsValidForItem(mimeData->urls(), item);
+
+ return false;
+}
+
+bool DbTree::isItemDraggable(const DbTreeItem* item)
+{
+ return item && draggableTypes.contains(item->getType());
+}
+
+bool DbTree::areDbTreeItemsValidForItem(QList<DbTreeItem*> srcItems, const DbTreeItem* dstItem)
+{
+ QSet<Db*> srcDbs;
+ QList<DbTreeItem::Type> srcTypes;
+ DbTreeItem::Type dstType = DbTreeItem::Type::DIR; // the empty space is treated as group
+ if (dstItem)
+ dstType = dstItem->getType();
+
+ for (DbTreeItem* srcItem : srcItems)
+ {
+ if (srcItem)
+ srcTypes << srcItem->getType();
+ else
+ srcTypes << DbTreeItem::Type::ITEM_PROTOTYPE;
+
+ if (srcItem->getDb())
+ srcDbs << srcItem->getDb();
+ }
+
+ for (DbTreeItem::Type srcType : srcTypes)
+ {
+ if (!allowedTypesInside[dstType].contains(srcType))
+ return false;
+
+ if (dstType == DbTreeItem::Type::DB && !dstItem->getDb()->isOpen())
+ return false;
+ }
+
+ if (dstItem && dstItem->getDb() && srcDbs.contains(dstItem->getDb()))
+ return false;
+
+ return true;
+}
+
+bool DbTree::areUrlsValidForItem(const QList<QUrl>& srcUrls, const DbTreeItem* dstItem)
+{
+ UNUSED(dstItem);
+ for (const QUrl& srcUrl : srcUrls)
+ {
+ if (!srcUrl.isLocalFile())
+ return false;
+ }
+ return true;
+}
+
+void DbTree::showWidgetCover()
+{
+ widgetCover->show();
+}
+
+void DbTree::hideWidgetCover()
+{
+ widgetCover->hide();
+}
+
+void DbTree::setSelectedItem(DbTreeItem *item)
+{
+ ui->treeView->setCurrentIndex(item->index());
+ ui->treeView->selectionModel()->select(item->index(), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+QToolBar* DbTree::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void DbTree::setActionEnabled(int action, bool enabled)
+{
+ actionMap[action]->setEnabled(enabled);
+}
+
+Db* DbTree::getSelectedDb()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return nullptr;
+
+ return item->getDb();
+}
+
+Db* DbTree::getSelectedOpenDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isOpen())
+ return nullptr;
+
+ return db;
+}
+
+TableWindow* DbTree::openTable(DbTreeItem* item)
+{
+ QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+ return openTable(db, database, item->text());
+}
+
+TableWindow* DbTree::openTable(Db* db, const QString& database, const QString& table)
+{
+ DbObjectDialogs dialogs(db);
+ return dialogs.editTable(database, table);
+}
+
+void DbTree::editIndex(DbTreeItem* item)
+{
+ //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editIndex(item->text());
+}
+
+ViewWindow* DbTree::openView(DbTreeItem* item)
+{
+ QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+ return openView(db, database, item->text());
+}
+
+ViewWindow* DbTree::openView(Db* db, const QString& database, const QString& view)
+{
+ DbObjectDialogs dialogs(db);
+ return dialogs.editView(database, view);
+}
+
+TableWindow* DbTree::newTable(DbTreeItem* item)
+{
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ return dialogs.addTable();
+}
+
+ViewWindow* DbTree::newView(DbTreeItem* item)
+{
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ return dialogs.addView();
+}
+
+void DbTree::editTrigger(DbTreeItem* item)
+{
+ //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editTrigger(item->text());
+}
+
+void DbTree::delSelectedObject()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db)
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ DbObjectDialogs dialogs(db);
+ dialogs.dropObject(item->text()); // TODO add database prefix when supported
+}
+
+void DbTree::filterUndeletableItems(QList<DbTreeItem*>& items)
+{
+ QMutableListIterator<DbTreeItem*> it(items);
+ DbTreeItem::Type type;
+ while (it.hasNext())
+ {
+ type = it.next()->getType();
+ switch (type)
+ {
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ it.remove();
+ break;
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::COLUMN:
+ break;
+ }
+ }
+}
+
+void DbTree::filterItemsWithParentInList(QList<DbTreeItem*>& items)
+{
+ QMutableListIterator<DbTreeItem*> it(items);
+ DbTreeItem* item = nullptr;
+ DbTreeItem* pathItem = nullptr;
+ while (it.hasNext())
+ {
+ item = it.next();
+ foreach (pathItem, item->getPathToRoot().mid(1))
+ {
+ if (items.contains(pathItem) && pathItem->getType() != DbTreeItem::Type::DIR)
+ {
+ it.remove();
+ break;
+ }
+ }
+ }
+}
+
+void DbTree::deleteItem(DbTreeItem* item)
+{
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ treeModel->deleteGroup(item);
+ break;
+ case DbTreeItem::Type::DB:
+ DBLIST->removeDb(item->getDb());
+ break;
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ {
+ Db* db = item->getDb();
+ DbObjectDialogs dialogs(db);
+ dialogs.setNoConfirmation(true); // confirmation is done in deleteSelected()
+ dialogs.setNoSchemaRefreshing(true); // we will refresh after all items are deleted
+ dialogs.dropObject(item->text()); // TODO database name when supported
+ break;
+ }
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+}
+
+
+void DbTree::refreshSchema(Db* db)
+{
+ if (!db)
+ return;
+
+ if (!db->isOpen())
+ return;
+
+ treeModel->refreshSchema(db);
+}
+
+void DbTree::copy()
+{
+ QMimeData* mimeData = treeModel->mimeData(ui->treeView->getSelectedIndexes());
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void DbTree::paste()
+{
+ DbTreeItem* currItem = ui->treeView->currentItem();
+ QModelIndex idx;
+ if (currItem)
+ idx = currItem->index();
+
+ treeModel->pasteData(QApplication::clipboard()->mimeData(), -1, -1, idx, Qt::CopyAction);
+}
+
+void DbTree::selectAll()
+{
+ ui->treeView->selectAll();
+}
+
+void DbTree::createGroup()
+{
+ QStringList existingItems;
+ QStandardItem* currItem = ui->treeView->getItemForAction(true);
+ DbTreeItem* itemToMove = nullptr;
+ if (currItem)
+ {
+ // Look for any directory in the path to the root, starting with the current item
+ do
+ {
+ if (dynamic_cast<DbTreeItem*>(currItem)->getType() == DbTreeItem::Type::DIR)
+ {
+ existingItems = dynamic_cast<DbTreeItem*>(currItem)->childNames();
+ break;
+ }
+ else
+ {
+ itemToMove = dynamic_cast<DbTreeItem*>(currItem);
+ }
+ }
+ while ((currItem = currItem->parent()) != nullptr);
+ }
+
+ // No luck? Use root.
+ if (!currItem)
+ currItem = treeModel->root();
+
+ QString name = "";
+ while (existingItems.contains(name = QInputDialog::getText(this, tr("Create group"), tr("Group name"))) ||
+ (name.isEmpty() && !name.isNull()))
+ {
+ QMessageBox::information(this, tr("Create directory"), tr("Entry with name %1 already exists in directory %2.")
+ .arg(name).arg(currItem->text()), QMessageBox::Ok);
+ }
+
+ if (name.isNull())
+ return;
+
+ DbTreeItem* newDir = treeModel->createGroup(name, currItem);
+ if (itemToMove)
+ treeModel->move(itemToMove, newDir);
+}
+
+void DbTree::deleteGroup()
+{
+ DbTreeItem* item = ui->treeView->getItemForAction();
+ if (!item)
+ return;
+
+ QMessageBox::StandardButton resp = QMessageBox::question(this, tr("Delete group"),
+ tr("Are you sure you want to delete group %1?\nAll objects from this group will be moved to parent group.").arg(item->text().left(ITEM_TEXT_LIMIT)));
+
+ if (resp != QMessageBox::Yes)
+ return;
+
+ treeModel->deleteGroup(item);
+}
+
+void DbTree::renameGroup()
+{
+ DbTreeItem* item = ui->treeView->getItemForAction();
+ if (!item)
+ return;
+
+ ui->treeView->edit(item->index());
+}
+
+void DbTree::addDb()
+{
+ DbTreeItem* currItem = ui->treeView->getItemForAction();
+
+ DbDialog dialog(DbDialog::ADD, this);
+ if (!dialog.exec())
+ return;
+
+ QString name = dialog.getName();
+
+ // If we created db in some group, move it there
+ if (currItem && currItem->getType() == DbTreeItem::Type::DIR)
+ {
+ DbTreeItem* dbItem = dynamic_cast<DbTreeItem*>(treeModel->findItem(DbTreeItem::Type::DB, name));
+ if (!dbItem)
+ {
+ qWarning() << "Created and added db to tree, but could not find it while trying to move it to target group" << currItem->text();
+ return;
+ }
+ treeModel->move(dbItem, currItem);
+ }
+}
+
+void DbTree::editDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ bool perm = CFG->isDbInConfig(db->getName());
+
+ DbDialog dialog(DbDialog::EDIT, this);
+ dialog.setDb(db);
+ dialog.setPermanent(perm);
+ dialog.exec();
+}
+
+void DbTree::removeDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete database"), tr("Are you sure you want to delete database '%1'?").arg(db->getName().left(ITEM_TEXT_LIMIT)));
+ if (result != QMessageBox::Yes)
+ return;
+
+ DBLIST->removeDb(db);
+}
+
+void DbTree::connectToDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ if (db->isOpen())
+ return;
+
+ db->open();
+}
+
+void DbTree::disconnectFromDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ if (!db->isOpen())
+ return;
+
+ db->close();
+}
+
+
+void DbTree::import()
+{
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ Db* db = getSelectedDb();
+ if (db)
+ dialog.setDb(db);
+
+ dialog.exec();
+}
+
+void DbTree::exportDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setDatabaseMode(db);
+ dialog.exec();
+}
+
+void DbTree::addTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ newTable(item);
+}
+
+void DbTree::editTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to edit table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ openTable(db, QString::null, table); // TODO put database name when supported
+}
+
+void DbTree::delTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to drop table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ DbObjectDialogs dialogs(db);
+ dialogs.dropObject(table); // TODO add database prefix when supported
+}
+
+void DbTree::addIndex()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.addIndex(table);
+}
+
+void DbTree::editIndex()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString index = item->getIndex();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editIndex(index);
+}
+
+void DbTree::delIndex()
+{
+ delSelectedObject();
+}
+
+void DbTree::addTrigger()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db)
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ QString view = item->getView();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.addTrigger(table, view);
+}
+
+void DbTree::editTrigger()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString trigger = item->getTrigger();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editTrigger(trigger);
+}
+
+void DbTree::delTrigger()
+{
+ delSelectedObject();
+}
+
+void DbTree::addView()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ newView(item);
+}
+
+void DbTree::editView()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString view = item->getView();
+ if (view.isNull())
+ {
+ qWarning() << "Tried to edit view, while view wasn't selected in DbTree.";
+ return;
+ }
+
+ openView(db, QString(), view); // TODO handle named database when supported
+}
+
+void DbTree::delView()
+{
+ delSelectedObject();
+}
+
+void DbTree::exportTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to export table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setTableMode(db, table);
+ dialog.exec();
+}
+
+void DbTree::importTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to import into table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ dialog.exec();
+}
+
+void DbTree::populateTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to populate table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ PopulateDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ dialog.exec();
+}
+
+void DbTree::addColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ addColumn(item);
+}
+
+void DbTree::editColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ editColumn(item);
+}
+
+void DbTree::delColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ delColumn(item);
+}
+
+void DbTree::convertDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbConverterDialog dialog(this);
+ dialog.setDb(db);
+ dialog.exec();
+}
+
+void DbTree::vacuumDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ SqlQueryPtr res = db->exec("VACUUM;");
+ if (res->isError())
+ notifyError(tr("Error while executing VACUUM on the database %1: %2").arg(db->getName(), res->getErrorText()));
+ else
+ notifyInfo(tr("VACUUM execution finished successfully."));
+}
+
+void DbTree::integrityCheck()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ EditorWindow* win = MAINWINDOW->openSqlEditor();
+ if (!win->setCurrentDb(db))
+ {
+ qCritical() << "Created EditorWindow had not got requested database:" << db->getName();
+ win->close();
+ return;
+ }
+
+ win->getMdiWindow()->rename(tr("Integrity check (%1)").arg(db->getName()));
+ win->setContents("PRAGMA integrity_check;");
+ win->execute();
+}
+
+void DbTree::createSimilarTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to clone table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ DbObjectDialogs dialog(db);
+ dialog.addTableSimilarTo(QString(), table);
+}
+
+void DbTree::addColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* tableItem = nullptr;
+
+ if (item->getType() == DbTreeItem::Type::TABLE)
+ tableItem = item;
+ else
+ tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->addColumn();
+}
+
+void DbTree::editColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (item->getType() != DbTreeItem::Type::COLUMN)
+ return;
+
+ DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->editColumn(item->text());
+}
+
+void DbTree::delColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (item->getType() != DbTreeItem::Type::COLUMN)
+ return;
+
+ DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->delColumn(item->text());
+}
+
+void DbTree::currentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+ UNUSED(previous);
+ updateActionStates(treeModel->itemFromIndex(current));
+}
+
+void DbTree::deleteSelected()
+{
+ QModelIndexList idxList = ui->treeView->getSelectedIndexes();
+ QList<DbTreeItem*> items;
+ foreach (const QModelIndex& idx, idxList)
+ items << dynamic_cast<DbTreeItem*>(treeModel->itemFromIndex(idx));
+
+ deleteItems(items);
+}
+
+void DbTree::deleteItems(const QList<DbTreeItem*>& itemsToDelete)
+{
+ QList<DbTreeItem*> items = itemsToDelete;
+
+ filterUndeletableItems(items);
+ filterItemsWithParentInList(items);
+
+ // Warning user about items to be deleted
+ static const QString itemTmp = "<img src=\"%1\"/> %2";
+
+ QStringList toDelete;
+ QStringList databasesToRemove;
+ QString itemStr;
+ int groupItems = 0;
+ foreach (DbTreeItem* item, items)
+ {
+ itemStr = itemTmp.arg(item->getIcon()->toUrl()).arg(item->text().left(ITEM_TEXT_LIMIT));
+
+ if (item->getType() == DbTreeItem::Type::DB)
+ databasesToRemove << itemStr;
+ else
+ toDelete << itemStr;
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ groupItems++;
+ }
+
+ QStringList actions;
+ if (toDelete.size() > 0)
+ actions << tr("Following objects will be deleted: %1.").arg(toDelete.join(", "));
+
+ if (databasesToRemove.size() > 0)
+ actions << tr("Following databases will be removed from list: %1.").arg(databasesToRemove.join(", "));
+
+ if (groupItems > 0)
+ actions << tr("Remainig objects from deleted group will be moved in place where the group used to be.");
+
+ QString msg = tr("%1<br><br>Are you sure you want to continue?").arg(actions.join("<br><br>"));
+
+ QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete objects"), msg);
+ if (result != QMessageBox::Yes)
+ return;
+
+ // Deleting items
+ QSet<Db*> databasesToRefresh;
+ for (DbTreeItem* item : items)
+ {
+ databasesToRefresh << item->getDb();
+ deleteItem(item);
+ }
+
+ for (Db* dbToRefresh : databasesToRefresh)
+ DBTREE->refreshSchema(dbToRefresh);
+}
+
+void DbTree::refreshSchemas()
+{
+ foreach (Db* db, DBLIST->getDbList())
+ treeModel->refreshSchema(db);
+}
+
+void DbTree::interrupt()
+{
+ treeModel->interrupt();
+}
+
+void DbTree::refreshSchema()
+{
+ Db* db = getSelectedDb();
+ refreshSchema(db);
+}
+
+void DbTree::updateActionsForCurrent()
+{
+ updateActionStates(ui->treeView->currentItem());
+}
+
+void DbTree::dbConnected(Db* db)
+{
+ updateActionsForCurrent();
+ updateDbIcon(db);
+}
+
+void DbTree::dbDisconnected(Db* db)
+{
+ updateActionsForCurrent();
+ updateDbIcon(db);
+}
+
+void DbTree::updateDbIcon(Db* db)
+{
+ DbTreeItem* item = treeModel->findItem(DbTreeItem::Type::DB, db);
+ if (item)
+ item->updateDbIcon();
+}
+
+void DbTree::refreshFont()
+{
+ ui->treeView->doItemsLayout();
+}
+
+void DbTree::setupDefShortcuts()
+{
+ setShortcutContext({
+ CLEAR_FILTER, DEL_SELECTED, REFRESH_SCHEMA, REFRESH_SCHEMAS,
+ ADD_DB, SELECT_ALL, COPY, PASTE
+ }, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(DbTree, Action);
+}
+
+int qHash(DbTree::Action action)
+{
+ return static_cast<int>(action);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h
new file mode 100644
index 0000000..b368c08
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h
@@ -0,0 +1,205 @@
+#ifndef DBTREE_H
+#define DBTREE_H
+
+#include "db/db.h"
+#include "common/extactioncontainer.h"
+#include "mainwindow.h"
+#include "dbtree/dbtreeitem.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDockWidget>
+
+class WidgetCover;
+class QAction;
+class QMenu;
+class DbTreeModel;
+class QStandardItem;
+class QTimer;
+class TableWindow;
+class ViewWindow;
+class UserInputFilter;
+class DbTreeView;
+
+namespace Ui {
+ class DbTree;
+}
+
+CFG_KEY_LIST(DbTree, QObject::tr("Database list"),
+ CFG_KEY_ENTRY(DEL_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected item"))
+ CFG_KEY_ENTRY(CLEAR_FILTER, Qt::Key_Escape, QObject::tr("Clear filter contents"))
+ CFG_KEY_ENTRY(REFRESH_SCHEMA, Qt::Key_F5, QObject::tr("Refresh schema"))
+ CFG_KEY_ENTRY(REFRESH_SCHEMAS, Qt::SHIFT + Qt::Key_F5, QObject::tr("Refresh all schemas"))
+ CFG_KEY_ENTRY(ADD_DB, Qt::CTRL + Qt::Key_O, QObject::tr("Add database"))
+ CFG_KEY_ENTRY(SELECT_ALL, Qt::CTRL + Qt::Key_A, QObject::tr("Select all items"))
+ CFG_KEY_ENTRY(COPY, Qt::CTRL + Qt::Key_C, QObject::tr("Copy selected item(s)"))
+ CFG_KEY_ENTRY(PASTE, Qt::CTRL + Qt::Key_V, QObject::tr("Paste from clipboard"))
+)
+
+class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ friend class DbTreeView;
+
+ enum Action
+ {
+ COPY,
+ PASTE,
+ SELECT_ALL,
+ DEL_SELECTED,
+ CREATE_GROUP,
+ DELETE_GROUP,
+ RENAME_GROUP,
+ ADD_DB,
+ EDIT_DB,
+ DELETE_DB,
+ CONNECT_TO_DB,
+ DISCONNECT_FROM_DB,
+ IMPORT_INTO_DB,
+ EXPORT_DB,
+ CONVERT_DB,
+ VACUUM_DB,
+ INTEGRITY_CHECK,
+ ADD_TABLE,
+ EDIT_TABLE,
+ DEL_TABLE,
+ EXPORT_TABLE,
+ IMPORT_TABLE,
+ POPULATE_TABLE,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ ADD_VIEW,
+ EDIT_VIEW,
+ DEL_VIEW,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ CLEAR_FILTER,
+ REFRESH_SCHEMAS,
+ REFRESH_SCHEMA,
+ CREATE_SIMILAR_TABLE,
+ _separator // Never use it directly, it's just for menu setup
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit DbTree(QWidget *parent = 0);
+ ~DbTree();
+
+ static void staticInit();
+
+ void init();
+ void updateActionStates(const QStandardItem* item);
+ void setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu);
+ QVariant saveSession();
+ void restoreSession(const QVariant& sessionValue);
+ DbTreeModel* getModel() const;
+ DbTreeView* getView() const;
+ void showWidgetCover();
+ void hideWidgetCover();
+ void setSelectedItem(DbTreeItem* item);
+ bool isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item);
+ QToolBar* getToolBar(int toolbar) const;
+
+ static bool isItemDraggable(const DbTreeItem* item);
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void setActionEnabled(int action, bool enabled);
+ Db* getSelectedDb();
+ Db* getSelectedOpenDb();
+ TableWindow* openTable(DbTreeItem* item);
+ TableWindow* openTable(Db* db, const QString& database, const QString& table);
+ TableWindow* newTable(DbTreeItem* item);
+ ViewWindow* openView(DbTreeItem* item);
+ ViewWindow* openView(Db* db, const QString& database, const QString& view);
+ ViewWindow* newView(DbTreeItem* item);
+ void editIndex(DbTreeItem* item);
+ void editTrigger(DbTreeItem* item);
+ void delSelectedObject();
+ void filterUndeletableItems(QList<DbTreeItem*>& items);
+ void filterItemsWithParentInList(QList<DbTreeItem*>& items);
+ void deleteItem(DbTreeItem* item);
+ static bool areDbTreeItemsValidForItem(QList<DbTreeItem*> srcItems, const DbTreeItem* dstItem);
+ static bool areUrlsValidForItem(const QList<QUrl>& srcUrls, const DbTreeItem* dstItem);
+
+ static void initDndTypes();
+
+ Ui::DbTree *ui = nullptr;
+ DbTreeModel* treeModel = nullptr;
+ WidgetCover* widgetCover = nullptr;
+
+ static QHash<DbTreeItem::Type,QList<DbTreeItem::Type>> allowedTypesInside;
+ static QSet<DbTreeItem::Type> draggableTypes;
+ static const constexpr int ITEM_TEXT_LIMIT = 300;
+
+ public slots:
+ void refreshSchema(Db* db);
+ void refreshSchemas();
+ void interrupt();
+
+ private slots:
+ void copy();
+ void paste();
+ void selectAll();
+ void createGroup();
+ void deleteGroup();
+ void renameGroup();
+ void addDb();
+ void editDb();
+ void removeDb();
+ void connectToDb();
+ void disconnectFromDb();
+ void import();
+ void exportDb();
+ void addTable();
+ void editTable();
+ void delTable();
+ void addIndex();
+ void editIndex();
+ void delIndex();
+ void addTrigger();
+ void editTrigger();
+ void delTrigger();
+ void addView();
+ void editView();
+ void delView();
+ void exportTable();
+ void importTable();
+ void populateTable();
+ void addColumn();
+ void editColumn();
+ void delColumn();
+ void convertDb();
+ void vacuumDb();
+ void integrityCheck();
+ void createSimilarTable();
+ void addColumn(DbTreeItem* item);
+ void editColumn(DbTreeItem* item);
+ void delColumn(DbTreeItem* item);
+ void currentChanged(const QModelIndex & current, const QModelIndex & previous);
+ void deleteSelected();
+ void deleteItems(const QList<DbTreeItem*>& itemsToDelete);
+ void refreshSchema();
+ void updateActionsForCurrent();
+ void dbConnected(Db* db);
+ void dbDisconnected(Db* db);
+ void updateDbIcon(Db* db);
+ void refreshFont();
+};
+
+int qHash(DbTree::Action action);
+
+#define DBTREE MainWindow::getInstance()->getDbTree()
+
+#endif // DBTREE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui
new file mode 100644
index 0000000..52b0c7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbTree</class>
+ <widget class="QDockWidget" name="DbTree">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>200</width>
+ <height>618</height>
+ </rect>
+ </property>
+ <property name="floating">
+ <bool>false</bool>
+ </property>
+ <property name="features">
+ <set>QDockWidget::AllDockWidgetFeatures</set>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>
+ </property>
+ <property name="windowTitle">
+ <string>Databases</string>
+ </property>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="DbTreeView" name="treeView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Ignored" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::CopyAction</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QWidget" name="top" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="nameFilter">
+ <property name="placeholderText">
+ <string>Filter by name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DbTreeView</class>
+ <extends>QTreeView</extends>
+ <header>dbtree/dbtreeview.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>nameFilter</tabstop>
+ <tabstop>treeView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp
new file mode 100644
index 0000000..ead5e3d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp
@@ -0,0 +1,332 @@
+#include "dbtreeitem.h"
+#include "iconmanager.h"
+#include "dbtreemodel.h"
+#include "services/dbmanager.h"
+#include "dbtree.h"
+#include <QDebug>
+#include <db/invaliddb.h>
+
+DbTreeItem::DbTreeItem(DbTreeItem::Type type, const Icon& icon, const QString& nodeName, QObject* parent)
+ : DbTreeItem(type, nodeName, parent)
+{
+ setIcon(icon);
+}
+
+DbTreeItem::DbTreeItem(DbTreeItem::Type type, const QString& nodeName, QObject *parent)
+ : QObject(parent)
+{
+ setText(nodeName);
+ setType(type);
+ init();
+}
+
+DbTreeItem::DbTreeItem(const DbTreeItem& item)
+ : QObject(item.QObject::parent()), QStandardItem(item)
+{
+ init();
+}
+
+DbTreeItem::DbTreeItem()
+{
+ setType(Type::ITEM_PROTOTYPE);
+ init();
+}
+
+void DbTreeItem::initMeta()
+{
+ qRegisterMetaType<DbTreeItem*>("DbTreeItem*");
+ qRegisterMetaTypeStreamOperators<DbTreeItem*>("DbTreeItem*");
+}
+
+DbTreeItem::Type DbTreeItem::getType() const
+{
+ return static_cast<Type>(type());
+}
+
+void DbTreeItem::setType(Type type)
+{
+ setData(static_cast<int>(type), DataRole::TYPE);
+}
+
+int DbTreeItem::type() const
+{
+ return data(DataRole::TYPE).toInt();
+}
+
+DbTreeItem* DbTreeItem::findItem(DbTreeItem::Type type, const QString& name)
+{
+ return DbTreeModel::findItem(this, type, name);
+}
+
+QStandardItem* DbTreeItem::clone() const
+{
+ return new DbTreeItem(*this);
+}
+
+QList<QStandardItem *> DbTreeItem::childs() const
+{
+ QList<QStandardItem *> results;
+ for (int i = 0; i < rowCount(); i++)
+ results += child(i);
+
+ return results;
+}
+
+QStringList DbTreeItem::childNames() const
+{
+ QStringList results;
+ for (int i = 0; i < rowCount(); i++)
+ results += child(i)->text();
+
+ return results;
+}
+
+QString DbTreeItem::getTable() const
+{
+ const DbTreeItem* item = getParentItem(Type::TABLE);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getColumn() const
+{
+ if (getType() != Type::COLUMN)
+ return QString::null;
+
+ return text();
+}
+
+QString DbTreeItem::getIndex() const
+{
+ const DbTreeItem* item = getParentItem(Type::INDEX);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getTrigger() const
+{
+ const DbTreeItem* item = getParentItem(Type::TRIGGER);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getView() const
+{
+ const DbTreeItem* item = getParentItem(Type::VIEW);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QStandardItem *DbTreeItem::parentItem() const
+{
+ if (!QStandardItem::parent())
+ return model()->invisibleRootItem();
+
+ return QStandardItem::parent();
+}
+
+DbTreeItem *DbTreeItem::parentDbTreeItem() const
+{
+ QStandardItem* parentItem = QStandardItem::parent();
+ if (!parentItem)
+ return nullptr;
+
+ return dynamic_cast<DbTreeItem*>(parentItem);
+}
+
+QList<DbTreeItem *> DbTreeItem::getPathToRoot()
+{
+ QList<DbTreeItem *> path;
+ getPathToRoot(path);
+ return path;
+}
+
+QList<DbTreeItem*> DbTreeItem::getPathToParentItem(DbTreeItem::Type type)
+{
+ QList<DbTreeItem*> path;
+ getPathToParentItem(path, type);
+ return path;
+}
+
+QList<DbTreeItem*> DbTreeItem::getPathToParentItem(DbTreeItem::Type type, const QString& name)
+{
+ QList<DbTreeItem*> path;
+ getPathToParentItem(path, type, name);
+ return path;
+}
+
+DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type)
+{
+ DbTreeItem* parent = parentDbTreeItem();
+ if (!parent)
+ return nullptr;
+
+ if (parent->getType() == type)
+ return parent;
+
+ return parent->findParentItem(type);
+}
+
+DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type, const QString& name)
+{
+ DbTreeItem* parent = parentDbTreeItem();
+ if (!parent)
+ return nullptr;
+
+ if (parent->getType() == type && name == parent->text())
+ return parent;
+
+ return parent->findParentItem(type);
+}
+
+void DbTreeItem::getPathToRoot(QList<DbTreeItem *> &path)
+{
+ path << this;
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToRoot(path);
+}
+
+QString DbTreeItem::signature() const
+{
+ QString sig;
+ if (parentDbTreeItem())
+ sig += parentDbTreeItem()->signature() + "_";
+
+ sig += QString::number(type()) + "." + QString::fromLatin1(text().toUtf8().toBase64());
+ return sig;
+}
+
+void DbTreeItem::getPathToParentItem(QList<DbTreeItem*>& path, DbTreeItem::Type type)
+{
+ path << this;
+ if (getType() == type)
+ return;
+
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToParentItem(path, type);
+}
+
+void DbTreeItem::getPathToParentItem(QList<DbTreeItem*>& path, DbTreeItem::Type type, const QString& name)
+{
+ path << this;
+ if (getType() == type && name == text())
+ return;
+
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToParentItem(path, type, name);
+}
+
+const DbTreeItem* DbTreeItem::getParentItem(DbTreeItem::Type type) const
+{
+ if (getType() == type)
+ return this;
+
+ DbTreeItem* parent = parentDbTreeItem();
+ if (parent)
+ return parent->getParentItem(type);
+
+ return nullptr;
+}
+
+Db* DbTreeItem::getDb() const
+{
+ QString dbName = data(DataRole::DB).toString();
+ return DBLIST->getByName(dbName);
+}
+
+void DbTreeItem::setDb(Db* value)
+{
+ setDb(value->getName());
+}
+
+void DbTreeItem::setDb(const QString& dbName)
+{
+ setData(dbName, DataRole::DB);
+ updateDbIcon();
+}
+
+void DbTreeItem::updateDbIcon()
+{
+ if (getType() != DbTreeItem::Type::DB)
+ return;
+
+ Db* db = getDb();
+ if (db->isValid())
+ {
+ if (db->isOpen())
+ setIcon(ICONS.DATABASE_ONLINE);
+ else
+ setIcon(ICONS.DATABASE_OFFLINE);
+ }
+ else
+ setIcon(ICONS.DATABASE_INVALID);
+}
+
+const Icon* DbTreeItem::getIcon() const
+{
+ return data(DataRole::ICON_PTR).value<const Icon*>();
+}
+
+void DbTreeItem::setHidden(bool hidden)
+{
+ setData(hidden, DataRole::HIDDEN);
+ dynamic_cast<DbTreeModel*>(model())->itemChangedVisibility(this);
+}
+
+bool DbTreeItem::isHidden() const
+{
+ return data(DataRole::HIDDEN).toBool();
+}
+
+void DbTreeItem::setIcon(const Icon& icon)
+{
+ setData(QVariant::fromValue(&icon), DataRole::ICON_PTR);
+ if (!icon.isNull())
+ QStandardItem::setIcon(icon);
+}
+
+void DbTreeItem::init()
+{
+ Type type = getType();
+ if (type == Type::DIR)
+ setEditable(true);
+ else
+ setEditable(false);
+
+ setData(false, DataRole::HIDDEN);
+
+ Qt::ItemFlags f = flags();
+ if (DbTree::isItemDraggable(this))
+ f |= Qt::ItemIsDragEnabled;
+ else
+ f ^= Qt::ItemIsDragEnabled;
+
+ setFlags(f);
+}
+
+QDataStream &operator <<(QDataStream &out, const DbTreeItem *item)
+{
+ out << item->signature();
+ return out;
+}
+
+QDataStream &operator >>(QDataStream &in, DbTreeItem *&item)
+{
+ QString signature;
+ in >> signature;
+ item = DBTREE->getModel()->findItemBySignature(signature);
+ return in;
+}
+
+int qHash(DbTreeItem::Type type)
+{
+ return static_cast<int>(type);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h
new file mode 100644
index 0000000..ba230f2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h
@@ -0,0 +1,112 @@
+#ifndef DBTREEITEM_H
+#define DBTREEITEM_H
+
+#include "db/db.h"
+#include "iconmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStandardItem>
+#include <QObject>
+
+class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem
+{
+ Q_OBJECT
+
+ public:
+ enum class Type
+ {
+ DIR = 1000,
+ DB = 1001,
+ TABLES = 1002,
+ TABLE = 1003,
+ INDEXES = 1004,
+ INDEX = 1005,
+ TRIGGERS = 1006,
+ TRIGGER = 1007,
+ VIEWS = 1008,
+ VIEW = 1009,
+ COLUMNS = 1010,
+ COLUMN = 1011,
+ VIRTUAL_TABLE = 1012,
+ ITEM_PROTOTYPE = 9999
+ };
+
+ DbTreeItem(Type type, const Icon& icon, const QString& nodeName, QObject* parent = 0);
+ DbTreeItem(const DbTreeItem& item);
+ DbTreeItem();
+
+ static void initMeta();
+
+ int type() const;
+ DbTreeItem* findItem(Type type, const QString& name);
+ QStandardItem* clone() const;
+ QList<QStandardItem*> childs() const;
+ QStringList childNames() const;
+ QString getTable() const;
+ QString getColumn() const;
+ QString getIndex() const;
+ QString getTrigger() const;
+ QString getView() const;
+
+ /**
+ * @brief parentItem
+ * @return Parent item for this item. Might be the "invisible root item" if this is the top level item. It will never be null.
+ */
+ QStandardItem* parentItem() const;
+
+ /**
+ * @brief parentDbTreeItem
+ * @return Parent item that is always DbTreeItem. If there is no parent item (i.e. this is the top item), then null is returned.
+ */
+ DbTreeItem* parentDbTreeItem() const;
+ QList<DbTreeItem*> getPathToRoot();
+ QList<DbTreeItem*> getPathToParentItem(Type type);
+ QList<DbTreeItem*> getPathToParentItem(Type type, const QString& name);
+ DbTreeItem* findParentItem(Type type);
+ DbTreeItem* findParentItem(Type type, const QString& name);
+ QString signature() const;
+
+ Type getType() const;
+ void setType(Type type);
+ Db* getDb() const;
+ void setDb(Db* value);
+ void setDb(const QString& dbName);
+ void updateDbIcon();
+ const Icon* getIcon() const;
+ void setHidden(bool hidden);
+ bool isHidden() const;
+ void setIcon(const Icon& icon);
+
+ private:
+ struct DataRole // not 'enum class' because we need autocasting to int for this one
+ {
+ enum Enum
+ {
+ TYPE = 1001,
+ DB = 1002,
+ ICON_PTR = 1003,
+ HIDDEN = 1004
+ };
+ };
+
+ DbTreeItem(Type type, const QString& nodeName, QObject* parent = 0);
+
+ void init();
+ void getPathToRoot(QList<DbTreeItem*>& path);
+ void getPathToParentItem(QList<DbTreeItem*>& path, Type type);
+ void getPathToParentItem(QList<DbTreeItem*>& path, Type type, const QString& name);
+ const DbTreeItem* getParentItem(Type type) const;
+
+ signals:
+
+ public slots:
+
+};
+
+GUI_API_EXPORT QDataStream &operator<<(QDataStream &out, const DbTreeItem* item);
+GUI_API_EXPORT QDataStream &operator>>(QDataStream &in, DbTreeItem*& item);
+
+GUI_API_EXPORT int qHash(DbTreeItem::Type type);
+
+Q_DECLARE_METATYPE(DbTreeItem*)
+
+#endif // DBTREEITEM_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp
new file mode 100644
index 0000000..ef691d2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp
@@ -0,0 +1,164 @@
+#include "dbtreeitemdelegate.h"
+#include "dbtreeitem.h"
+#include "dbtreemodel.h"
+#include "common/utils_sql.h"
+#include "uiconfig.h"
+#include "dbtree.h"
+#include "dbtreeview.h"
+#include <QPainter>
+#include <QDebug>
+
+DbTreeItemDelegate::DbTreeItemDelegate(QObject *parent) :
+ QStyledItemDelegate(parent)
+{
+}
+
+QSize DbTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QSize size = QStyledItemDelegate::sizeHint(option, index);
+
+ QFont f = CFG_UI.Fonts.DbTree.get();
+ QFontMetrics fm(f);
+ size.setHeight(qMax(18, fm.height()));
+ return size;
+}
+
+void DbTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ const DbTreeModel* model = dynamic_cast<const DbTreeModel*>(index.model());
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(model->itemFromIndex(index));
+
+ opt.font = CFG_UI.Fonts.DbTree.get();
+ opt.fontMetrics = QFontMetrics(opt.font);
+
+ QModelIndex currIndex = DBTREE->getView()->selectionModel()->currentIndex();
+ if (currIndex.isValid() && item->index() == currIndex)
+ opt.state |= QStyle::State_HasFocus;
+
+ QStyledItemDelegate::paint(painter, opt, index);
+
+ if (!CFG_UI.General.ShowDbTreeLabels.get())
+ return;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ break;
+ case DbTreeItem::Type::DB:
+ paintDb(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ paintChildCount(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TABLE:
+ paintTableLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ paintVirtualTableLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::INDEX:
+ paintSystemIndexLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+}
+
+void DbTreeItemDelegate::paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const
+{
+ static const QString versionStringTemplate = QStringLiteral("(%1)");
+ QString versionString = versionStringTemplate.arg("?");
+ Db* db = item->getDb();
+ if (!db)
+ return;
+
+ if (db->isValid())
+ {
+ QString t = db->getTypeLabel();
+ versionString = versionStringTemplate.arg(t);
+ }
+ else
+ {
+ versionString = versionStringTemplate.arg(tr("error", "dbtree labels"));
+ }
+
+ paintLabel(painter, option, index, item, versionString);
+}
+
+void DbTreeItemDelegate::paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const
+{
+ int cnt = item->rowCount();
+ if (cnt > 0)
+ paintLabel(painter, option, index, item, QString("(%1)").arg(cnt));
+}
+
+void DbTreeItemDelegate::paintTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ if (isSystemTable(item->text()))
+ {
+ paintLabel(painter, option, index, item, tr("(system table)", "database tree label"));
+ return;
+ }
+
+ if (!CFG_UI.General.ShowRegularTableLabels.get())
+ return;
+
+ int columnsCount = item->child(0)->rowCount();
+ int indexesCount = item->child(1)->rowCount();
+ int triggersCount = item->child(2)->rowCount();
+ paintLabel(painter, option, index, item, QString("(%1, %2, %3)").arg(columnsCount).arg(indexesCount).arg(triggersCount));
+}
+
+void DbTreeItemDelegate::paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ if (!CFG_UI.General.ShowVirtualTableLabels.get())
+ return;
+
+ paintLabel(painter, option, index, item, tr("(virtual)", "virtual table label"));
+}
+
+void DbTreeItemDelegate::paintSystemIndexLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ Db* db = item->getDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (!isSystemIndex(item->text(), db->getDialect()))
+ return;
+
+ paintLabel(painter, option, index, item, tr("(system index)", "database tree label"));
+}
+
+void DbTreeItemDelegate::paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item, const QString &label) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ painter->save();
+
+ // Colors
+ painter->setPen(CFG_UI.Colors.DbTreeLabelsFg.get());
+
+ // Font
+ opt.font = CFG_UI.Fonts.DbTreeLabel.get();
+ opt.fontMetrics = QFontMetrics(opt.font);
+ painter->setFont(opt.font);
+
+ // Coords
+ int x = option.rect.x() + option.fontMetrics.width(item->text()) + 15 + option.decorationSize.width();
+ int y = opt.rect.y() + (opt.rect.height() - opt.fontMetrics.descent() - opt.fontMetrics.ascent()) / 2 + opt.fontMetrics.ascent();
+
+ // Paint
+ painter->drawText(QPoint(x, y), label);
+ painter->restore();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h
new file mode 100644
index 0000000..43eeac2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h
@@ -0,0 +1,28 @@
+#ifndef DBTREEITEMDELEGATE_H
+#define DBTREEITEMDELEGATE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QStyledItemDelegate>
+
+class DbTreeItem;
+
+class GUI_API_EXPORT DbTreeItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+ public:
+ explicit DbTreeItemDelegate(QObject *parent = 0);
+
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+ private:
+ void paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintTableLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const;
+ void paintSystemIndexLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item, const QString& label) const;
+};
+
+#endif // DBTREEITEMDELEGATE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp
new file mode 100644
index 0000000..b112713
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp
@@ -0,0 +1,74 @@
+#include "dbtreeitemfactory.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+
+DbTreeItem *DbTreeItemFactory::createDir(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::DIR, ICONS.DIRECTORY, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createDb(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::DB, ICONS.DATABASE_OFFLINE, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTable(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TABLE, ICONS.TABLE, name, parent);
+}
+
+DbTreeItem* DbTreeItemFactory::createVirtualTable(const QString& name, QObject* parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIRTUAL_TABLE, ICONS.VIRTUAL_TABLE, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createIndex(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::INDEX, ICONS.INDEX, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTrigger(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TRIGGER, ICONS.TRIGGER, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createView(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIEW, ICONS.VIEW, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createColumn(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::COLUMN, ICONS.COLUMN, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTables(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TABLES, ICONS.TABLES, QObject::tr("Tables"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createIndexes(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::INDEXES, ICONS.INDEXES, QObject::tr("Indexes"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTriggers(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TRIGGERS, ICONS.TRIGGERS, QObject::tr("Triggers"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createViews(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIEWS, ICONS.VIEWS, QObject::tr("Views"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createColumns(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::COLUMNS, ICONS.COLUMNS, QObject::tr("Columns"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createPrototype(QObject *parent)
+{
+ UNUSED(parent);
+ return new DbTreeItem();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h
new file mode 100644
index 0000000..acb3aeb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h
@@ -0,0 +1,26 @@
+#ifndef DBTREEITEMFACTORY_H
+#define DBTREEITEMFACTORY_H
+
+#include "guiSQLiteStudio_global.h"
+#include "dbtree/dbtreeitem.h"
+
+class GUI_API_EXPORT DbTreeItemFactory
+{
+ public:
+ static DbTreeItem* createDir(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createDb(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTable(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createVirtualTable(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createIndex(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTrigger(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createView(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createColumn(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTables(QObject *parent = nullptr);
+ static DbTreeItem* createIndexes(QObject *parent = nullptr);
+ static DbTreeItem* createTriggers(QObject *parent = nullptr);
+ static DbTreeItem* createViews(QObject *parent = nullptr);
+ static DbTreeItem* createColumns(QObject *parent = nullptr);
+ static DbTreeItem* createPrototype(QObject *parent = nullptr);
+};
+
+#endif // DBTREEITEMFACTORY_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
new file mode 100644
index 0000000..8a71a10
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
@@ -0,0 +1,1222 @@
+#include "dbtreemodel.h"
+#include "services/dbmanager.h"
+#include "dbtreeview.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include "schemaresolver.h"
+#include "dbtreeitemfactory.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "dbobjectorganizer.h"
+#include "dialogs/dbdialog.h"
+#include "dialogs/errorsconfirmdialog.h"
+#include "dialogs/versionconvertsummarydialog.h"
+#include "db/invaliddb.h"
+#include <QMimeData>
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QClipboard>
+
+const QString DbTreeModel::toolTipTableTmp = "<table>%1</table>";
+const QString DbTreeModel::toolTipHdrRowTmp = "<tr><th><img src=\"%1\"/></th><th colspan=2>%2</th></tr>";
+const QString DbTreeModel::toolTipRowTmp = "<tr><td></td><td>%1</td><td align=\"right\">%2</td></tr>";
+const QString DbTreeModel::toolTipIconRowTmp = "<tr><td><img src=\"%1\"/></td><td>%2</td><td align=\"right\">%3</td></tr>";
+
+DbTreeModel::DbTreeModel()
+{
+ setItemPrototype(DbTreeItemFactory::createPrototype());
+ connectDbManagerSignals();
+
+ connect(CFG, SIGNAL(massSaveBegins()), this, SLOT(massSaveBegins()));
+ connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(massSaveCommited()));
+ connect(CFG_UI.General.ShowSystemObjects, SIGNAL(changed(QVariant)), this, SLOT(markSchemaReloadingRequired()));
+
+ dbOrganizer = new DbObjectOrganizer(confirmReferencedTables, resolveNameConflict, confirmConversion, confirmConversionErrors);
+ dbOrganizer->setAutoDelete(false);
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsCopy(bool,Db*,Db*)), this, SLOT(dbObjectsCopyFinished(bool,Db*,Db*)));
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsMove(bool,Db*,Db*)), this, SLOT(dbObjectsMoveFinished(bool,Db*,Db*)));
+}
+
+DbTreeModel::~DbTreeModel()
+{
+}
+
+void DbTreeModel::connectDbManagerSignals()
+{
+ connect(DBLIST, SIGNAL(dbAdded(Db*)), this, SLOT(dbAdded(Db*)));
+ connect(DBLIST, SIGNAL(dbUpdated(QString,Db*)), this, SLOT(dbUpdated(QString,Db*)));
+ connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(dbRemoved(Db*)));
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+ connect(DBLIST, SIGNAL(dbLoaded(Db*)), this, SLOT(dbLoaded(Db*)));
+ connect(DBLIST, SIGNAL(dbUnloaded(Db*)), this, SLOT(dbUnloaded(Db*)));
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, QStandardItem *newParentItem, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ if (!newParentItem)
+ newParentItem = root();
+
+ if (newParentItem == currParent)
+ {
+ move(itemToMove, newRow);
+ return;
+ }
+
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+
+ if (newRow > currParent->rowCount() || newRow < 0)
+ newParentItem->appendRow(itemToMove);
+ else
+ newParentItem->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+ if (newRow > currParent->rowCount() || newRow < 0)
+ currParent->appendRow(itemToMove);
+ else if (oldRow < newRow)
+ currParent->insertRow(newRow - 1, itemToMove);
+ else
+ currParent->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::deleteGroup(QStandardItem *groupItem)
+{
+ QStandardItem* parentItem = dynamic_cast<DbTreeItem*>(groupItem)->parentItem();
+ if (!parentItem)
+ parentItem = root();
+
+ foreach (QStandardItem* child, dynamic_cast<DbTreeItem*>(groupItem)->childs())
+ move(child, parentItem);
+
+ parentItem->removeRow(groupItem->row());
+}
+
+DbTreeItem* DbTreeModel::createGroup(const QString& name, QStandardItem* parent)
+{
+ if (!parent)
+ parent = root();
+
+ DbTreeItem* item = DbTreeItemFactory::createDir(name, this);
+ parent->appendRow(item);
+ return item;
+}
+
+QStringList DbTreeModel::getGroupFor(QStandardItem *item)
+{
+ QStringList group;
+ while ((item = item->parent()) != nullptr)
+ {
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ group.prepend(item->text());
+ }
+ return group;
+}
+
+void DbTreeModel::applyFilter(const QString &filter)
+{
+ applyFilter(root(), filter);
+ currentFilter = filter;
+}
+
+bool DbTreeModel::applyFilter(QStandardItem *parentItem, const QString &filter)
+{
+ bool empty = filter.isEmpty();
+ bool visibilityForParent = false;
+ DbTreeItem* item = nullptr;
+ QModelIndex index;
+ bool subFilterResult;
+ bool matched;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+ index = item->index();
+ subFilterResult = applyFilter(item, filter);
+ matched = empty || subFilterResult || item->text().contains(filter, Qt::CaseInsensitive);
+ treeView->setRowHidden(index.row(), index.parent(), !matched);
+
+ if (matched)
+ visibilityForParent = true;
+ }
+ return visibilityForParent;
+}
+
+void DbTreeModel::storeGroups()
+{
+ QList<Config::DbGroupPtr> groups = childsToConfig(invisibleRootItem());
+ CFG->storeGroups(groups);
+}
+
+void DbTreeModel::readGroups(QList<Db*> dbList)
+{
+ QList<Config::DbGroupPtr> groups = CFG->getGroups();
+ foreach (const Config::DbGroupPtr& group, groups)
+ restoreGroup(group, &dbList);
+
+ // Add rest of databases, not mentioned in groups
+ Config::DbGroupPtr group;
+ foreach (Db* db, dbList)
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = db->getName();
+ restoreGroup(group);
+ }
+}
+
+QList<Config::DbGroupPtr> DbTreeModel::childsToConfig(QStandardItem *item)
+{
+ QList<Config::DbGroupPtr> groups;
+ Config::DbGroupPtr group;
+ DbTreeItem* dbTreeItem = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ dbTreeItem = dynamic_cast<DbTreeItem*>(item->child(i));
+ switch (dbTreeItem->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ {
+ group = Config::DbGroupPtr::create();
+ group->name = dbTreeItem->text();
+ group->order = i;
+ group->open = treeView->isExpanded(dbTreeItem->index());
+ group->childs = childsToConfig(dbTreeItem);
+ groups += group;
+ break;
+ }
+ case DbTreeItem::Type::DB:
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = dbTreeItem->text();
+ group->order = i;
+ group->open = dbTreeItem->getDb()->isOpen();
+ groups += group;
+ break;
+ }
+ default:
+ // no-op
+ break;
+ }
+ }
+ return groups;
+}
+
+void DbTreeModel::restoreGroup(const Config::DbGroupPtr& group, QList<Db*>* dbList, QStandardItem* parent)
+{
+ Db* db = nullptr;
+ DbTreeItem* item = nullptr;
+ if (group->referencedDbName.isNull())
+ {
+ item = DbTreeItemFactory::createDir(group->name, this);
+ }
+ else
+ {
+ // If db is managed by manager, it means it was successfully loaded.
+ // Otherwise there was a problem with the file, or with plugin for that database
+ // and we still want to have dbtree item for that database, we will just hide it.
+ // Later, when plugin is loaded, item might become visible.
+ item = DbTreeItemFactory::createDb(group->referencedDbName, this);
+ item->setDb(group->referencedDbName);
+
+ db = DBLIST->getByName(group->referencedDbName);
+ if (db && dbList)
+ dbList->removeOne(db);
+ }
+
+ if (!parent)
+ parent = invisibleRootItem();
+
+ parent->appendRow(item);
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ {
+ foreach (const Config::DbGroupPtr& childGroup, group->childs)
+ restoreGroup(childGroup, dbList, item);
+ }
+
+ if (group->open)
+ {
+ if (db)
+ {
+ if (db->open())
+ treeView->expand(item->index());
+ }
+ else
+ {
+ treeView->expand(item->index());
+ }
+ }
+}
+
+void DbTreeModel::expanded(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (!item->hasChildren())
+ {
+ treeView->collapse(index);
+ return;
+ }
+
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::collapsed(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ item->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::dbAdded(Db* db)
+{
+ DbTreeItem* item = DbTreeItemFactory::createDb(db->getName(), this);
+ item->setDb(db);
+ root()->appendRow(item);
+}
+
+void DbTreeModel::dbUpdated(const QString& oldName, Db* db)
+{
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(findItem(DbTreeItem::Type::DB, oldName));
+ if (!item)
+ {
+ qWarning() << "Updated database in db model that couldn't be found in the model:" << oldName;
+ return;
+ }
+
+ item->setText(db->getName());
+ item->setDb(db->getName());
+}
+
+void DbTreeModel::dbRemoved(Db* db)
+{
+ dbRemoved(db->getName());
+}
+
+void DbTreeModel::dbRemoved(const QString& name)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, name);
+ if (!item)
+ {
+ qWarning() << "Removed database from db model that couldn't be found in the model:" << name;
+ return;
+ }
+ dbRemoved(item);
+}
+
+void DbTreeModel::dbRemoved(QStandardItem* item)
+{
+ QStandardItem* parent = item->parent();
+ if (!parent)
+ parent = root();
+
+ parent->removeRow(item->index().row());
+ if (!parent->hasChildren())
+ treeView->collapse(parent->index());
+}
+
+void DbTreeModel::interrupt()
+{
+ dbOrganizer->interrupt();
+}
+
+void DbTreeModel::refreshSchema(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Refreshing schema of db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ applyFilter(item, currentFilter);
+}
+
+QList<DbTreeItem*> DbTreeModel::getAllItemsAsFlatList() const
+{
+ return getChildsAsFlatList(root());
+}
+
+QList<DbTreeItem*> DbTreeModel::getChildsAsFlatList(QStandardItem* item) const
+{
+ QList<DbTreeItem*> items;
+ QStandardItem* child = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ child = item->child(i);
+ items << dynamic_cast<DbTreeItem*>(child);
+ items += getChildsAsFlatList(child);
+ }
+ return items;
+}
+
+QVariant DbTreeModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QStandardItemModel::data(index, role);;
+
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(itemFromIndex(index));
+ switch (role)
+ {
+ case Qt::ToolTipRole:
+ {
+ return getToolTip(item);
+ }
+ }
+ return QStandardItemModel::data(index, role);
+}
+
+QString DbTreeModel::getToolTip(DbTreeItem* item) const
+{
+ if (!item)
+ return QString::null;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DB:
+ return getDbToolTip(item);
+ case DbTreeItem::Type::TABLE:
+ return getTableToolTip(item);
+ default:
+ break;
+ }
+ return QString::null;
+}
+
+QString DbTreeModel::getDbToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ Db* db = item->getDb();
+ QFile dbFile(db->getPath());
+ QString iconPath = db->isValid() ? ICONS.DATABASE.toImgSrc() : ICONS.DATABASE_INVALID.toImgSrc();
+
+ rows << toolTipHdrRowTmp.arg(iconPath).arg(tr("Database: %1", "dbtree tooltip").arg(db->getName()));
+ rows << toolTipRowTmp.arg("URI:").arg(db->getPath());
+
+ if (db->isValid())
+ {
+ rows << toolTipRowTmp.arg(tr("Version:", "dbtree tooltip")).arg(QString("SQLite %1").arg(db->getVersion()));
+ rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(dbFile.size()));
+ rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding());
+ }
+ else
+ {
+ InvalidDb* idb = dynamic_cast<InvalidDb*>(db);
+ rows << toolTipRowTmp.arg(tr("Error details:", "dbtree tooltip")).arg(idb->getError());
+ }
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+QString DbTreeModel::getTableToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ rows << toolTipHdrRowTmp.arg(ICONS.TABLE.getPath()).arg(tr("Table : %1", "dbtree tooltip").arg(item->text()));
+
+ QStandardItem* columnsItem = item->child(0);
+ QStandardItem* indexesItem = item->child(1);
+ QStandardItem* triggersItem = item->child(2);
+
+ int columnCnt = columnsItem->rowCount();
+ int indexesCount = indexesItem->rowCount();
+ int triggersCount = triggersItem->rowCount();
+
+ QStringList columns;
+ for (int i = 0; i < columnCnt; i++)
+ columns << columnsItem->child(i)->text();
+
+ QStringList indexes;
+ for (int i = 0; i < indexesCount; i++)
+ indexes << indexesItem->child(i)->text();
+
+ QStringList triggers;
+ for (int i = 0; i < triggersCount; i++)
+ triggers << triggersItem->child(i)->text();
+
+ rows << toolTipIconRowTmp.arg(ICONS.COLUMN.getPath())
+ .arg(tr("Columns (%1):", "dbtree tooltip").arg(columnCnt))
+ .arg(columns.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.INDEX.getPath())
+ .arg(tr("Indexes (%1):", "dbtree tooltip").arg(indexesCount))
+ .arg(indexes.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.TRIGGER.getPath())
+ .arg(tr("Triggers (%1):", "dbtree tooltip").arg(triggersCount))
+ .arg(triggers.join(", "));
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+void DbTreeModel::refreshSchema(Db* db, QStandardItem *item)
+{
+ if (!db->isOpen())
+ return;
+
+ // Remember expanded state of this branch
+ QHash<QString, bool> expandedState;
+ collectExpandedState(expandedState, item);
+
+ // Delete child nodes
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ // Now prepare to create new branch
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get());
+
+ // Collect all db objects and build the db branch
+ bool sort = CFG_UI.General.SortObjects.get();
+ QStringList tables = resolver.getTables();
+ QStringList virtualTables;
+ for (const QString& table : tables)
+ {
+ if (resolver.isVirtualTable(table))
+ virtualTables << table;
+ }
+
+ QList<QStandardItem*> tableItems = refreshSchemaTables(tables, virtualTables, sort);
+ StrHash<QList<QStandardItem*>> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns());
+ StrHash<QList<QStandardItem*>> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort);
+ StrHash<QList<QStandardItem*>> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort);
+ QList<QStandardItem*> viewItems = refreshSchemaViews(resolver.getViews(), sort);
+ refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns);
+ populateChildItemsWithDb(item, db);
+ restoreExpandedState(expandedState, item);
+}
+
+void DbTreeModel::collectExpandedState(QHash<QString, bool> &state, QStandardItem *parentItem)
+{
+ if (!parentItem)
+ parentItem = root();
+
+ DbTreeItem* dbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ if (dbTreeItem)
+ state[dbTreeItem->signature()] = treeView->isExpanded(dbTreeItem->index());
+
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ collectExpandedState(state, parentItem->child(i));
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort)
+{
+ QStringList sortedTables = tables;
+ if (sort)
+ qSort(sortedTables);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& table, sortedTables)
+ {
+ if (virtualTables.contains(table))
+ items += DbTreeItemFactory::createVirtualTable(table, this);
+ else
+ items += DbTreeItemFactory::createTable(table, this);
+ }
+
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTableColumns(const StrHash<QStringList> &columns)
+{
+ QStringList sortedColumns;
+ bool sort = CFG_UI.General.SortColumns.get();
+ StrHash<QList<QStandardItem*>> items;
+ for (const QString& key : columns.keys())
+ {
+ sortedColumns = columns[key];
+ if (sort)
+ qSort(sortedColumns);
+
+ for (const QString& column : sortedColumns)
+ items[key] += DbTreeItemFactory::createColumn(column, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem *> > DbTreeModel::refreshSchemaIndexes(const StrHash<QStringList> &indexes, bool sort)
+{
+ StrHash<QList<QStandardItem *> > items;
+ QStringList sortedIndexes;
+ for (const QString& key : indexes.keys())
+ {
+ sortedIndexes = indexes[key];
+ if (sort)
+ qSort(sortedIndexes);
+
+ for (const QString& index : sortedIndexes)
+ items[key] += DbTreeItemFactory::createIndex(index, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTriggers(const StrHash<QStringList> &triggers, bool sort)
+{
+ StrHash<QList<QStandardItem*>> items;
+ QStringList sortedTriggers;
+ for (const QString& key : triggers.keys())
+ {
+ sortedTriggers = triggers[key];
+ if (sort)
+ qSort(sortedTriggers);
+
+ for (const QString& trigger : sortedTriggers)
+ items[key] += DbTreeItemFactory::createTrigger(trigger, this);
+ }
+ return items;
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaViews(const QStringList &views, bool sort)
+{
+ QStringList sortedViews = views;
+ if (sort)
+ qSort(sortedViews);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& view, views)
+ items += DbTreeItemFactory::createView(view, this);
+
+ return items;
+}
+
+void DbTreeModel::populateChildItemsWithDb(QStandardItem *parentItem, Db* db)
+{
+ QStandardItem* childItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ childItem = parentItem->child(i);
+ dynamic_cast<DbTreeItem*>(childItem)->setDb(db);
+ populateChildItemsWithDb(childItem, db);
+ }
+}
+
+void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem,
+ QList<QStandardItem*> tables,
+ StrHash<QList<QStandardItem*> > indexes,
+ StrHash<QList<QStandardItem*> > triggers,
+ QList<QStandardItem*> views,
+ StrHash<QList<QStandardItem*> > allTableColumns)
+{
+ DbTreeItem* tablesItem = DbTreeItemFactory::createTables(this);
+ DbTreeItem* viewsItem = DbTreeItemFactory::createViews(this);
+
+ dbItem->appendRow(tablesItem);
+ dbItem->appendRow(viewsItem);
+
+ DbTreeItem* columnsItem = nullptr;
+ DbTreeItem* indexesItem = nullptr;
+ DbTreeItem* triggersItem = nullptr;
+ foreach (QStandardItem* tableItem, tables)
+ {
+ tablesItem->appendRow(tableItem);
+
+ columnsItem = DbTreeItemFactory::createColumns(this);
+ indexesItem = DbTreeItemFactory::createIndexes(this);
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+
+ tableItem->appendRow(columnsItem);
+ tableItem->appendRow(indexesItem);
+ tableItem->appendRow(triggersItem);
+
+ foreach (QStandardItem* columnItem, allTableColumns[tableItem->text()])
+ columnsItem->appendRow(columnItem);
+
+ foreach (QStandardItem* indexItem, indexes[tableItem->text()])
+ indexesItem->appendRow(indexItem);
+
+ foreach (QStandardItem* triggerItem, triggers[tableItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+ foreach (QStandardItem* viewItem, views)
+ {
+ viewsItem->appendRow(viewItem);
+
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+ viewItem->appendRow(triggersItem);
+ foreach (QStandardItem* triggerItem, triggers[viewItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+}
+
+void DbTreeModel::restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem)
+{
+ DbTreeItem* parentDbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ QString sig = parentDbTreeItem->signature();
+ if (expandedState.contains(sig) && expandedState[sig])
+ treeView->expand(parentItem->index());
+
+ foreach (QStandardItem* child, parentDbTreeItem->childs())
+ restoreExpandedState(expandedState, child);
+}
+
+void DbTreeModel::dbConnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Connected to db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ treeView->expand(item->index());
+ if (CFG_UI.General.ExpandTables.get())
+ treeView->expand(item->index().child(0, 0)); // also expand tables
+
+ if (CFG_UI.General.ExpandViews.get())
+ treeView->expand(item->index().child(1, 0)); // also expand views
+}
+
+void DbTreeModel::dbDisconnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Disconnected from db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ treeView->collapse(item->index());
+}
+
+void DbTreeModel::dbUnloaded(Db* db)
+{
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::dbLoaded(Db* db)
+{
+ if (ignoreDbLoadedSignal)
+ return;
+
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::massSaveBegins()
+{
+ requireSchemaReloading = false;
+}
+
+void DbTreeModel::massSaveCommited()
+{
+ if (requireSchemaReloading)
+ {
+ for (Db* db : DBLIST->getDbList())
+ {
+ if (db->isOpen())
+ refreshSchema(db);
+ }
+ }
+}
+
+void DbTreeModel::markSchemaReloadingRequired()
+{
+ requireSchemaReloading = true;
+}
+
+DbTreeItem* DbTreeModel::findItem(DbTreeItem::Type type, const QString &name)
+{
+ return findItem(root(), type, name);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, const QString& name)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, name);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != name)
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+DbTreeItem *DbTreeModel::findItem(DbTreeItem::Type type, Db* db)
+{
+ return findItem(root(), type, db);
+}
+
+DbTreeItem *DbTreeModel::findItemBySignature(const QString &signature)
+{
+ QStringList parts = signature.split("_");
+ QStringList pair;
+ DbTreeItem* currItem = nullptr;
+ DbTreeItem::Type type;
+ QString name;
+ for (const QString& part : parts)
+ {
+ pair = part.split(".");
+ type = static_cast<DbTreeItem::Type>(pair.first().toInt());
+ name = QString::fromUtf8(QByteArray::fromBase64(pair.last().toLatin1()));
+ currItem = findItem((currItem ? currItem : root()), type, name);
+ if (!currItem)
+ return nullptr; // not found the target item
+ }
+ return currItem;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(DbTreeItem::Type type)
+{
+ return findItems(root(), type);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, db);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != db->getName())
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem::Type type)
+{
+ QList<DbTreeItem*> items;
+ DbTreeItem* item = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->getType() == DbTreeItem::Type::DIR)
+ items += findItems(item, type);
+
+ if (item->getType() != type)
+ continue;
+
+ items += item;
+ }
+
+ return items;
+}
+
+QStandardItem* DbTreeModel::root() const
+{
+ return invisibleRootItem();
+}
+
+void DbTreeModel::loadDbList()
+{
+ clear();
+ readGroups(DBLIST->getDbList());
+}
+
+void DbTreeModel::itemChangedVisibility(DbTreeItem* item)
+{
+ emit updateItemHidden(item);
+}
+
+void DbTreeModel::setTreeView(DbTreeView *value)
+{
+ treeView = value;
+ connect(treeView, &QTreeView::expanded, this, &DbTreeModel::expanded);
+ connect(treeView, &QTreeView::collapsed, this, &DbTreeModel::collapsed);
+ connect(this, SIGNAL(updateItemHidden(DbTreeItem*)), treeView, SLOT(updateItemHidden(DbTreeItem*)));
+}
+
+QStringList DbTreeModel::mimeTypes() const
+{
+ QStringList types = QStandardItemModel::mimeTypes();
+ types << MIMETYPE;
+ return types;
+}
+
+QMimeData *DbTreeModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *data = QStandardItemModel::mimeData(indexes);
+ if (!data)
+ return nullptr;
+
+ if (indexes.size() == 0)
+ return nullptr;
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ QList<QUrl> urlList;
+ QStringList textList;
+
+ DbTreeItem* item = nullptr;
+ stream << reinterpret_cast<qint32>(indexes.size());
+ for (const QModelIndex& idx : indexes)
+ {
+ item = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ stream << item->signature();
+
+ textList << item->text();
+ if (item->getType() == DbTreeItem::Type::DB)
+ urlList << QUrl("file://"+item->getDb()->getPath());
+ }
+ data->setData(MIMETYPE, output);
+ data->setText(textList.join("\n"));
+ data->setUrls(urlList);
+
+ return data;
+}
+
+bool DbTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(action);
+ // The result means: do we want the old item to be removed from the tree?
+ bool invokeStdAction = false;
+ bool res = pasteData(data, row, column, parent, Qt::IgnoreAction, &invokeStdAction);
+ if (!invokeStdAction)
+ return res;
+
+ return QStandardItemModel::dropMimeData(data, action, row, column, parent);
+}
+
+bool DbTreeModel::pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction, bool* invokeStdAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ DbTreeItem* dstItem = nullptr;
+ if (parent.isValid())
+ {
+ QModelIndex idx = parent.child(row, column);
+ if (idx.isValid())
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ else // drop on top of the parent
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(parent));
+ }
+ else
+ {
+ dstItem = dynamic_cast<DbTreeItem*>(item(row, column));
+ }
+
+ if (data->formats().contains(MIMETYPE))
+ return dropDbTreeItem(getDragItems(data), dstItem, defaultAction, *invokeStdAction);
+ else if (data->hasUrls())
+ return dropUrls(data->urls());
+ else
+ return false;
+}
+
+void DbTreeModel::interruptableStarted(Interruptable* obj)
+{
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->showWidgetCover();
+
+ interruptables << obj;
+}
+
+void DbTreeModel::interruptableFinished(Interruptable* obj)
+{
+ interruptables.removeOne(obj);
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->hideWidgetCover();
+}
+
+QList<DbTreeItem*> DbTreeModel::getDragItems(const QMimeData* data)
+{
+ QList<DbTreeItem*> items;
+ QByteArray byteData = data->data(MIMETYPE);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+
+ qint32 itemCount;
+ stream >> itemCount;
+
+ DbTreeItem* item = nullptr;
+ QString signature;
+ for (qint32 i = 0; i < itemCount; i++)
+ {
+ stream >> signature;
+ item = findItemBySignature(signature);
+ if (item)
+ items << item;
+ }
+
+ return items;
+}
+
+QList<DbTreeItem*> DbTreeModel::getItemsForIndexes(const QModelIndexList& indexes) const
+{
+ QList<DbTreeItem*> items;
+ for (const QModelIndex& idx : indexes)
+ {
+ if (idx.isValid())
+ items << dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ }
+
+ return items;
+}
+
+void DbTreeModel::staticInit()
+{
+}
+
+bool DbTreeModel::dropDbTreeItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool& invokeStdDropAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ if (srcItems.size() == 0)
+ return false;
+
+ DbTreeItem* srcItem = srcItems.first();
+ switch (srcItem->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIEW:
+ {
+ if (!dstItem)
+ return false;
+
+ if (srcItem->getDb() == dstItem->getDb())
+ return true;
+
+ return dropDbObjectItem(srcItems, dstItem, defaultAction);
+ }
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::DIR:
+ invokeStdDropAction = true;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return false;
+}
+
+bool DbTreeModel::dropDbObjectItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction)
+{
+ bool copy = false;
+ bool move = false;
+ bool includeData = false;
+ bool includeIndexes = false;
+ bool includeTriggers = false;
+
+ if (defaultAction == Qt::CopyAction)
+ {
+ copy = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else if (defaultAction == Qt::MoveAction)
+ {
+ move = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else
+ {
+ QMenu menu;
+ QAction* copyAction = menu.addAction(ICONS.ACT_COPY, tr("Copy"));
+ QAction* moveAction = menu.addAction(ICONS.ACT_CUT, tr("Move"));
+ menu.addSeparator();
+ QCheckBox *includeDataCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include data"));
+ QCheckBox *includeIndexesCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include indexes"));
+ QCheckBox *includeTriggersCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include triggers"));
+ menu.addSeparator();
+ menu.addAction(ICONS.ACT_ABORT, tr("Abort"));
+
+ connect(moveAction, &QAction::triggered, [&move]() {move = true;});
+ connect(copyAction, &QAction::triggered, [&copy]() {copy = true;});
+
+ menu.exec(treeView->mapToGlobal(treeView->getLastDropPosition()));
+
+ includeData = includeDataCheck->isChecked();
+ includeIndexes = includeIndexesCheck->isChecked();
+ includeTriggers = includeTriggersCheck->isChecked();
+ }
+
+ // The result means: do we want the old item to be removed from the tree?
+ if (!copy && !move)
+ return false;
+
+ moveOrCopyDbObjects(srcItems, dstItem, move, includeData, includeIndexes, includeTriggers);
+ return move;
+}
+
+QCheckBox* DbTreeModel::createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label)
+{
+ QWidget* parentWidget = new QWidget(menu);
+ parentWidget->setLayout(new QVBoxLayout());
+ QMargins margins = parentWidget->layout()->contentsMargins();
+ parentWidget->layout()->setContentsMargins(margins.left(), 0, margins.right(), 0);
+
+ QCheckBox *cb = new QCheckBox(label);
+ cb->setChecked(true);
+ parentWidget->layout()->addWidget(cb);
+
+ QWidgetAction *action = new QWidgetAction(menu);
+ action->setDefaultWidget(parentWidget);
+ menu->addAction(action);
+ return cb;
+}
+
+bool DbTreeModel::dropUrls(const QList<QUrl>& urls)
+{
+ for (const QUrl& url : urls)
+ {
+ if (!url.isLocalFile())
+ {
+ qDebug() << url.toString() + "skipped, not a local file.";
+ continue;
+ }
+
+ DbDialog dialog(DbDialog::ADD, MAINWINDOW);
+ dialog.setPath(url.toLocalFile());
+ dialog.exec();
+ }
+ return false;
+}
+
+void DbTreeModel::moveOrCopyDbObjects(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers)
+{
+ if (srcItems.size() == 0)
+ return;
+
+ DbTreeItem* srcItem = srcItems.first();
+ Db* srcDb = srcItem->getDb();
+ Db* dstDb = dstItem->getDb();
+
+ QStringList srcNames;
+ for (DbTreeItem* item : srcItems)
+ srcNames << item->text();
+
+ interruptableStarted(dbOrganizer);
+ if (move)
+ dbOrganizer->moveObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+ else
+ dbOrganizer->copyObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+}
+
+bool DbTreeModel::confirmReferencedTables(const QStringList& tables)
+{
+ QMessageBox::StandardButton result = QMessageBox::question(MAINWINDOW, tr("Referenced tables"),
+ tr("Do you want to include following referenced tables as well:\n%1").arg(tables.join(", ")));
+
+ return result == QMessageBox::Yes;
+}
+
+bool DbTreeModel::resolveNameConflict(QString& nameInConflict)
+{
+ bool ok = false;
+ QInputDialog tmpDialog; // just for a cancel button text
+ QString result = QInputDialog::getText(MAINWINDOW, tr("Name conflict"),
+ tr("Following object already exists in the target database.\nPlease enter new, unique name, or "
+ "press '%1' to abort the operation:").arg(tmpDialog.cancelButtonText()),
+ QLineEdit::Normal, nameInConflict, &ok);
+
+ if (ok)
+ nameInConflict = result;
+
+ return ok;
+}
+
+bool DbTreeModel::confirmConversion(const QList<QPair<QString, QString> >& diffs)
+{
+ VersionConvertSummaryDialog dialog(MAINWINDOW);
+ dialog.setWindowTitle(tr("SQL statements conversion"));
+ dialog.setSides(diffs);
+ return dialog.exec() == QDialog::Accepted;
+}
+
+bool DbTreeModel::confirmConversionErrors(const QHash<QString,QSet<QString>>& errors)
+{
+ ErrorsConfirmDialog dialog(MAINWINDOW);
+ dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:"));
+ dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?"));
+ dialog.setErrors(errors);
+ return dialog.exec() == QDialog::Accepted;
+}
+bool DbTreeModel::getIgnoreDbLoadedSignal() const
+{
+ return ignoreDbLoadedSignal;
+}
+
+void DbTreeModel::setIgnoreDbLoadedSignal(bool value)
+{
+ ignoreDbLoadedSignal = value;
+}
+
+bool DbTreeModel::hasDbTreeItem(const QMimeData *data)
+{
+ return data->formats().contains(MIMETYPE);
+}
+
+void DbTreeModel::dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ if (!success)
+ {
+ interruptableFinished(dbOrganizer);
+ return;
+ }
+
+ DBTREE->refreshSchema(srcDb);
+ DBTREE->refreshSchema(dstDb);
+ interruptableFinished(dbOrganizer);
+}
+
+void DbTreeModel::dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ dbObjectsMoveFinished(success, srcDb, dstDb);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h
new file mode 100644
index 0000000..c92fa2c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h
@@ -0,0 +1,135 @@
+#ifndef DBTREEMODEL_H
+#define DBTREEMODEL_H
+
+#include "db/db.h"
+#include "dbtreeitem.h"
+#include "services/config.h"
+#include "guiSQLiteStudio_global.h"
+#include "common/strhash.h"
+#include <QStandardItemModel>
+#include <QObject>
+
+class DbManager;
+class DbTreeView;
+class DbPlugin;
+class DbObjectOrganizer;
+class QMenu;
+class QCheckBox;
+
+class GUI_API_EXPORT DbTreeModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+ public:
+ DbTreeModel();
+ ~DbTreeModel();
+
+ void connectDbManagerSignals();
+ DbTreeItem* findItem(DbTreeItem::Type type, const QString &name);
+ DbTreeItem* findItem(DbTreeItem::Type type, Db* db);
+ DbTreeItem* findItemBySignature(const QString& signature);
+ QList<DbTreeItem*> findItems(DbTreeItem::Type type);
+ void move(QStandardItem* itemToMove, QStandardItem* newParentItem, int newRow = -1);
+ void move(QStandardItem* itemToMove, int newRow);
+ DbTreeItem *createGroup(const QString& name, QStandardItem *parent = nullptr);
+ void deleteGroup(QStandardItem* groupItem);
+ QStandardItem *root() const;
+ QStringList getGroupFor(QStandardItem* item);
+ void storeGroups();
+ void refreshSchema(Db* db);
+ QList<DbTreeItem*> getAllItemsAsFlatList() const;
+ void setTreeView(DbTreeView *value);
+ QVariant data(const QModelIndex &index, int role) const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList &indexes) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ bool pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction = Qt::IgnoreAction,
+ bool *invokeStdAction = nullptr);
+ void interruptableStarted(Interruptable* obj);
+ void interruptableFinished(Interruptable* obj);
+ bool getIgnoreDbLoadedSignal() const;
+ void setIgnoreDbLoadedSignal(bool value);
+ bool hasDbTreeItem(const QMimeData* data);
+ QList<DbTreeItem*> getDragItems(const QMimeData* data);
+ QList<DbTreeItem*> getItemsForIndexes(const QModelIndexList& indexes) const;
+
+ static DbTreeItem* findItem(QStandardItem *parentItem, DbTreeItem::Type type, const QString &name);
+ static DbTreeItem* findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db);
+ static QList<DbTreeItem*> findItems(QStandardItem* parentItem, DbTreeItem::Type type);
+ static void staticInit();
+
+ static const constexpr char* MIMETYPE = "application/x-sqlitestudio-dbtreeitem";
+
+ private:
+ void readGroups(QList<Db*> dbList);
+ QList<Config::DbGroupPtr> childsToConfig(QStandardItem* item);
+ void restoreGroup(const Config::DbGroupPtr& group, QList<Db*>* dbList = nullptr, QStandardItem *parent = nullptr);
+ bool applyFilter(QStandardItem* parentItem, const QString& filter);
+ void refreshSchema(Db* db, QStandardItem* item);
+ void collectExpandedState(QHash<QString, bool>& state, QStandardItem* parentItem = nullptr);
+ QStandardItem* refreshSchemaDb(Db* db);
+ QList<QStandardItem*> refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort);
+ StrHash<QList<QStandardItem*> > refreshSchemaTableColumns(const StrHash<QStringList>& columns);
+ StrHash<QList<QStandardItem*> > refreshSchemaIndexes(const StrHash<QStringList>& indexes, bool sort);
+ StrHash<QList<QStandardItem*> > refreshSchemaTriggers(const StrHash<QStringList>& triggers, bool sort);
+ QList<QStandardItem*> refreshSchemaViews(const QStringList &views, bool sort);
+ void populateChildItemsWithDb(QStandardItem* parentItem, Db* db);
+ void refreshSchemaBuild(QStandardItem* dbItem, QList<QStandardItem*> tables, StrHash<QList<QStandardItem*> > indexes,
+ StrHash<QList<QStandardItem*> > triggers, QList<QStandardItem*> views, StrHash<QList<QStandardItem*> > allTableColumns);
+ void restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem);
+ QString getToolTip(DbTreeItem *item) const;
+ QString getDbToolTip(DbTreeItem *item) const;
+ QString getTableToolTip(DbTreeItem *item) const;
+ QList<DbTreeItem*> getChildsAsFlatList(QStandardItem* item) const;
+ bool dropDbTreeItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool &invokeStdDropAction);
+ bool dropDbObjectItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction);
+ QCheckBox* createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label);
+ bool dropUrls(const QList<QUrl>& urls);
+ void moveOrCopyDbObjects(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers);
+
+ static bool confirmReferencedTables(const QStringList& tables);
+ static bool resolveNameConflict(QString& nameInConflict);
+ static bool confirmConversion(const QList<QPair<QString,QString>>& diffs);
+ static bool confirmConversionErrors(const QHash<QString, QSet<QString> >& errors);
+
+ static const QString toolTipTableTmp;
+ static const QString toolTipHdrRowTmp;
+ static const QString toolTipRowTmp;
+ static const QString toolTipIconRowTmp;
+
+ DbTreeView* treeView = nullptr;
+ bool requireSchemaReloading = false;
+ DbObjectOrganizer* dbOrganizer = nullptr;
+ QList<Interruptable*> interruptables;
+ bool ignoreDbLoadedSignal = false;
+ QString currentFilter;
+
+ private slots:
+ void expanded(const QModelIndex &index);
+ void collapsed(const QModelIndex &index);
+ void dbAdded(Db* db);
+ void dbUpdated(const QString &oldName, Db* db);
+ void dbRemoved(Db* db);
+ void dbConnected(Db* db);
+ void dbDisconnected(Db* db);
+ void dbUnloaded(Db* db);
+ void dbLoaded(Db* db);
+ void massSaveBegins();
+ void massSaveCommited();
+ void markSchemaReloadingRequired();
+ void dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb);
+ void dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb);
+
+ public slots:
+ void loadDbList();
+ void itemChangedVisibility(DbTreeItem* item);
+ void applyFilter(const QString& filter);
+ void dbRemoved(const QString& name);
+ void dbRemoved(QStandardItem* item);
+ void interrupt();
+
+ signals:
+ void updateItemHidden(DbTreeItem* item);
+};
+
+#endif // DBTREEMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp
new file mode 100644
index 0000000..7785b8f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp
@@ -0,0 +1,255 @@
+#include "dbtreeview.h"
+#include "dbtreemodel.h"
+#include "dbtreeitemdelegate.h"
+#include "mainwindow.h"
+#include "services/dbmanager.h"
+#include "common/unused.h"
+#include <QDragMoveEvent>
+#include <QMenu>
+#include <QList>
+#include <QMimeData>
+#include <QDebug>
+
+DbTreeView::DbTreeView(QWidget *parent) :
+ QTreeView(parent)
+{
+ contextMenu = new QMenu(this);
+ connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint)));
+
+ setHeaderHidden(true);
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+ itemDelegate = new DbTreeItemDelegate();
+ setItemDelegate(itemDelegate);
+}
+
+DbTreeView::~DbTreeView()
+{
+ delete contextMenu;
+ delete itemDelegate;
+}
+
+void DbTreeView::setDbTree(DbTree *dbTree)
+{
+ this->dbTree = dbTree;
+}
+
+DbTree* DbTreeView::getDbTree() const
+{
+ return dbTree;
+}
+
+DbTreeItem *DbTreeView::currentItem()
+{
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(currentIndex()));
+}
+
+DbTreeItem *DbTreeView::itemAt(const QPoint &pos)
+{
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(indexAt(pos)));
+}
+
+QList<DbTreeItem *> DbTreeView::selectionItems()
+{
+ QList<DbTreeItem*> items;
+ QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
+ foreach (QModelIndex modIdx, selectedIndexes)
+ items += dynamic_cast<DbTreeItem*>(model()->itemFromIndex(modIdx));
+
+ return items;
+}
+
+DbTreeModel *DbTreeView::model() const
+{
+ return dynamic_cast<DbTreeModel*>(QTreeView::model());
+}
+
+void DbTreeView::showMenu(const QPoint &pos)
+{
+ contextMenu->clear();
+
+ DbTreeItem* itemUnderCursor = itemAt(pos);
+ if (!itemUnderCursor)
+ selectionModel()->clear();
+
+ DbTreeItem* item = getItemForAction();
+ dbTree->setupActionsForMenu(item, contextMenu);
+ if (contextMenu->actions().size() == 0)
+ return;
+
+ dbTree->updateActionStates(item);
+ contextMenu->popup(mapToGlobal(pos));
+}
+
+void DbTreeView::updateItemHidden(DbTreeItem* item)
+{
+ setRowHidden(item->index().row(), item->index().parent(), item->isHidden());
+}
+
+DbTreeItem *DbTreeView::getItemForAction(bool onlySelected) const
+{
+ QModelIndex idx = selectionModel()->currentIndex();
+ if (onlySelected && !selectionModel()->isSelected(idx))
+ return nullptr;
+
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(idx));
+}
+
+void DbTreeView::dragEnterEvent(QDragEnterEvent* e)
+{
+ QTreeView::dragEnterEvent(e);
+ if (e->mimeData()->hasUrls())
+ e->acceptProposedAction();
+}
+
+void DbTreeView::dragMoveEvent(QDragMoveEvent *event)
+{
+ QTreeView::dragMoveEvent(event);
+
+ DbTreeItem* dstItem = itemAt(event->pos());
+
+ // Depending on where we drop we need a type of item we drop ON,
+ // or type of parent item if we drop ABOVE/BELOW. If we drop on empty space,
+ // we leave type as default.
+ if (dstItem)
+ {
+ QAbstractItemView::DropIndicatorPosition dropPosition = dropIndicatorPosition();
+ switch (dropPosition)
+ {
+ case QAbstractItemView::OnItem:
+ break;
+ case QAbstractItemView::AboveItem:
+ case QAbstractItemView::BelowItem:
+ {
+ dstItem = dstItem->parentDbTreeItem();
+ break;
+ }
+ case QAbstractItemView::OnViewport:
+ dstItem = nullptr;
+ break;
+ }
+ }
+
+ //qDebug() << event->mimeData()->formats();
+ const QMimeData* data = event->mimeData();
+ if (dbTree->isMimeDataValidForItem(data, dstItem))
+ event->acceptProposedAction();
+ else
+ event->ignore();
+}
+
+void DbTreeView::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ DbTreeItem* itemUnderCursor = itemAt(event->pos());
+ if (itemUnderCursor && !handleDoubleClick(itemUnderCursor))
+ return;
+
+ QTreeView::mouseDoubleClickEvent(event);
+}
+
+bool DbTreeView::handleDoubleClick(DbTreeItem *item)
+{
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ break;
+ case DbTreeItem::Type::DB:
+ {
+ if (item->getDb()->isValid())
+ return handleDbDoubleClick(item);
+ }
+ case DbTreeItem::Type::TABLES:
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ // TODO if module for virtual table is loaded - show virtual table window
+ break;
+ case DbTreeItem::Type::TABLE:
+ return handleTableDoubleClick(item);
+ case DbTreeItem::Type::INDEXES:
+ break;
+ case DbTreeItem::Type::INDEX:
+ return handleIndexDoubleClick(item);
+ case DbTreeItem::Type::TRIGGERS:
+ break;
+ case DbTreeItem::Type::TRIGGER:
+ return handleTriggerDoubleClick(item);
+ case DbTreeItem::Type::VIEWS:
+ break;
+ case DbTreeItem::Type::VIEW:
+ return handleViewDoubleClick(item);
+ case DbTreeItem::Type::COLUMNS:
+ break;
+ case DbTreeItem::Type::COLUMN:
+ return handleColumnDoubleClick(item);
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return true;
+}
+
+bool DbTreeView::handleDbDoubleClick(DbTreeItem *item)
+{
+ if (!item->getDb()->isOpen())
+ {
+ dbTree->getAction(DbTree::CONNECT_TO_DB)->trigger();
+ return false;
+ }
+ return true;
+}
+
+bool DbTreeView::handleTableDoubleClick(DbTreeItem *item)
+{
+ dbTree->openTable(item);
+ return false;
+}
+
+bool DbTreeView::handleIndexDoubleClick(DbTreeItem *item)
+{
+ dbTree->editIndex(item);
+ return false;
+}
+
+bool DbTreeView::handleTriggerDoubleClick(DbTreeItem *item)
+{
+ dbTree->editTrigger(item);
+ return false;
+}
+
+bool DbTreeView::handleViewDoubleClick(DbTreeItem *item)
+{
+ dbTree->openView(item);
+ return false;
+}
+
+bool DbTreeView::handleColumnDoubleClick(DbTreeItem *item)
+{
+ dbTree->editColumn(item);
+ return false;
+}
+
+QPoint DbTreeView::getLastDropPosition() const
+{
+ return lastDropPosition;
+}
+
+QModelIndexList DbTreeView::getSelectedIndexes() const
+{
+ QModelIndexList idxList = selectedIndexes();
+ if (currentIndex().isValid() && !idxList.contains(currentIndex()))
+ idxList << currentIndex();
+
+ return idxList;
+}
+
+void DbTreeView::dropEvent(QDropEvent* e)
+{
+ lastDropPosition = e->pos();
+ QTreeView::dropEvent(e);
+ if (!e->isAccepted() && e->mimeData()->hasUrls() && !dbTree->getModel()->hasDbTreeItem(e->mimeData()))
+ {
+ dbTree->getModel()->dropMimeData(e->mimeData(), Qt::CopyAction, -1, -1, dbTree->getModel()->root()->index());
+ e->accept();
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h
new file mode 100644
index 0000000..3ec33b4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h
@@ -0,0 +1,57 @@
+#ifndef DBTREEVIEW_H
+#define DBTREEVIEW_H
+
+#include "dbtree.h"
+#include "guiSQLiteStudio_global.h"
+#include <QTreeView>
+#include <QList>
+#include <QUrl>
+
+class QMenu;
+class QStandardItemModel;
+class DbTreeItemDelegate;
+
+class GUI_API_EXPORT DbTreeView : public QTreeView
+{
+ Q_OBJECT
+ public:
+ explicit DbTreeView(QWidget *parent = 0);
+ ~DbTreeView();
+
+ void setDbTree(DbTree* dbTree);
+ DbTree* getDbTree() const;
+
+ DbTreeItem *currentItem();
+ DbTreeItem *itemAt(const QPoint& pos);
+ QList<DbTreeItem *> selectionItems();
+ DbTreeModel *model() const;
+ DbTreeItem *getItemForAction(bool onlySelected = false) const;
+ QPoint getLastDropPosition() const;
+ QModelIndexList getSelectedIndexes() const;
+
+ protected:
+ void dragEnterEvent(QDragEnterEvent* e);
+ void dragMoveEvent(QDragMoveEvent *event);
+ void mouseDoubleClickEvent(QMouseEvent* event);
+ void dropEvent(QDropEvent*e);
+
+ private:
+ bool handleDoubleClick(DbTreeItem* item);
+ bool handleDbDoubleClick(DbTreeItem* item);
+ bool handleTableDoubleClick(DbTreeItem* item);
+ bool handleIndexDoubleClick(DbTreeItem* item);
+ bool handleTriggerDoubleClick(DbTreeItem* item);
+ bool handleViewDoubleClick(DbTreeItem* item);
+ bool handleColumnDoubleClick(DbTreeItem* item);
+
+ QMenu* contextMenu = nullptr;
+ DbTree* dbTree = nullptr;
+ DbTreeItemDelegate* itemDelegate = nullptr;
+ QPoint lastDropPosition;
+
+ private slots:
+ void showMenu(const QPoint &pos);
+ void updateItemHidden(DbTreeItem* item);
+};
+
+#endif // DBTREEVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp b/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp
new file mode 100644
index 0000000..033eb1c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp
@@ -0,0 +1,79 @@
+#include "debugconsole.h"
+#include "ui_debugconsole.h"
+#include "iconmanager.h"
+#include <QPushButton>
+
+DebugConsole::DebugConsole(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DebugConsole)
+{
+ ui->setupUi(this);
+ ui->textEdit->setReadOnly(true);
+
+ QPushButton* resetBtn = ui->buttonBox->button(QDialogButtonBox::Reset);
+ connect(resetBtn, SIGNAL(clicked()), this, SLOT(reset()));
+
+ initFormats();
+}
+
+DebugConsole::~DebugConsole()
+{
+ delete ui;
+}
+
+void DebugConsole::debug(const QString &msg)
+{
+ message(msg, dbgFormat);
+}
+
+void DebugConsole::warning(const QString &msg)
+{
+ message(msg, wrnFormat);
+}
+
+void DebugConsole::critical(const QString &msg)
+{
+ message(msg, criFormat);
+}
+
+void DebugConsole::fatal(const QString &msg)
+{
+ message(msg, fatFormat);
+}
+
+void DebugConsole::initFormats()
+{
+ dbgFormat.setForeground(Qt::blue);
+ wrnFormat.setForeground(Qt::darkRed);
+ criFormat.setForeground(Qt::red);
+ criFormat.setFontUnderline(true);
+ fatFormat.setForeground(Qt::red);
+ fatFormat.setFontUnderline(true);
+
+ QFontMetrics fm(ui->textEdit->font());
+ int indent = fm.width(QString("X").repeated(25));
+ ui->textEdit->document()->setIndentWidth(indent);
+
+ blockFormat.setIndent(1);
+ blockFormat.setTextIndent(-indent);
+}
+
+void DebugConsole::message(const QString &msg, const QTextCharFormat &format)
+{
+ ui->textEdit->setCurrentCharFormat(format);
+ QTextCursor cur = ui->textEdit->textCursor();
+
+ cur.insertText(msg);
+ cur.mergeBlockFormat(blockFormat);
+ cur.insertBlock(blockFormat);
+}
+
+void DebugConsole::reset()
+{
+ ui->textEdit->clear();
+}
+
+void DebugConsole::showEvent(QShowEvent*)
+{
+ setWindowIcon(ICONS.SQLITESTUDIO_APP);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.h b/SQLiteStudio3/guiSQLiteStudio/debugconsole.h
new file mode 100644
index 0000000..e13525c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.h
@@ -0,0 +1,44 @@
+#ifndef DEBUGCONSOLE_H
+#define DEBUGCONSOLE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QTextCharFormat>
+
+namespace Ui {
+class DebugConsole;
+}
+
+class GUI_API_EXPORT DebugConsole : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit DebugConsole(QWidget *parent = 0);
+ ~DebugConsole();
+
+ protected:
+ void showEvent(QShowEvent*);
+
+ private:
+ void initFormats();
+ void message(const QString& msg, const QTextCharFormat& format);
+
+ Ui::DebugConsole *ui = nullptr;
+ QTextCharFormat dbgFormat;
+ QTextCharFormat wrnFormat;
+ QTextCharFormat criFormat;
+ QTextCharFormat fatFormat;
+ QTextBlockFormat blockFormat;
+
+ private slots:
+ void reset();
+
+ public slots:
+ void debug(const QString& msg);
+ void warning(const QString& msg);
+ void critical(const QString& msg);
+ void fatal(const QString& msg);
+};
+
+#endif // DEBUGCONSOLE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui b/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui
new file mode 100644
index 0000000..f24e233
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DebugConsole</class>
+ <widget class="QDialog" name="DebugConsole">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>745</width>
+ <height>344</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQLiteStudio Debug Console</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTextEdit" name="textEdit">
+ <property name="font">
+ <font>
+ <family>Courier</family>
+ </font>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DebugConsole</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DebugConsole</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp
new file mode 100644
index 0000000..df790de
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp
@@ -0,0 +1,94 @@
+#include "aboutdialog.h"
+#include "ui_aboutdialog.h"
+#include "common/utils.h"
+#include "sqlitestudio.h"
+#include "iconmanager.h"
+#include "services/extralicensemanager.h"
+#include <QDebug>
+#include <QFile>
+
+AboutDialog::AboutDialog(InitialMode initialMode, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::AboutDialog)
+{
+ init(initialMode);
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
+
+void AboutDialog::init(InitialMode initialMode)
+{
+ ui->setupUi(this);
+ ui->leftIcon->setPixmap(ICONS.SQLITESTUDIO_APP.toQIcon().pixmap(200, 200));
+
+ ui->tabWidget->setCurrentWidget(initialMode == ABOUT ? ui->about : ui->license);
+
+ QString distName;
+ switch (getDistributionType())
+ {
+ case DistributionType::PORTABLE:
+ distName = tr("Portable distribution.");
+ break;
+ case DistributionType::OSX_BOUNDLE:
+ distName = tr("MacOS X application boundle distribution.");
+ break;
+ case DistributionType::OS_MANAGED:
+ distName = tr("Operating system managed distribution.");
+ break;
+ }
+
+ QString newLabelValue = ui->aboutLabel->text().arg(SQLITESTUDIO->getVersionString(), distName);
+ ui->aboutLabel->setText(newLabelValue);
+
+ licenseContents = "";
+ int row = 1;
+
+ QHash<QString,QString> licenses = SQLITESTUDIO->getExtraLicenseManager()->getLicenses();
+ QHashIterator<QString,QString> it(licenses);
+ while (it.hasNext())
+ {
+ it.next();
+ readLicense(row++, it.key(), it.value());
+ }
+
+ buildIndex();
+
+ ui->licenseEdit->setHtml(licenseContents);
+ indexContents.clear();
+ licenseContents.clear();
+}
+
+void AboutDialog::buildIndex()
+{
+ static const QString entryTpl = QStringLiteral("<li>%1</li>");
+ QStringList entries;
+ for (const QString& idx : indexContents)
+ entries += entryTpl.arg(idx);
+
+ licenseContents.prepend("<h3>Table of contents:</h3><ol>" + entries.join("") + "</ol>");
+}
+
+void AboutDialog::readLicense(int row, const QString& title, const QString& path)
+{
+ QString rowNum = QString::number(row);
+ QString contents = readFile(path);
+ licenseContents += "<h3>" + rowNum + ". " + title + "</h3>";
+ licenseContents += "<pre>" + contents + "</pre>";
+ indexContents += title;
+}
+
+QString AboutDialog::readFile(const QString& path)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ qCritical() << "Error opening" << file.fileName();
+ return QString::null;
+ }
+ QString contents = QString::fromLatin1(file.readAll()).toHtmlEscaped();
+ file.close();
+ return contents;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h
new file mode 100644
index 0000000..3c828c0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h
@@ -0,0 +1,37 @@
+#ifndef ABOUTDIALOG_H
+#define ABOUTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QStringList>
+
+namespace Ui {
+ class AboutDialog;
+}
+
+class GUI_API_EXPORT AboutDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum InitialMode
+ {
+ ABOUT,
+ LICENSES
+ };
+
+ AboutDialog(InitialMode initialMode, QWidget *parent = 0);
+ ~AboutDialog();
+
+ private:
+ void init(InitialMode initialMode);
+ void buildIndex();
+ void readLicense(int row, const QString& title, const QString& path);
+ QString readFile(const QString& path);
+
+ Ui::AboutDialog *ui = nullptr;
+ QStringList indexContents;
+ QString licenseContents;
+};
+
+#endif // ABOUTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui
new file mode 100644
index 0000000..67fa632
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>741</width>
+ <height>447</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>About SQLiteStudio and licenses</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="about">
+ <attribute name="title">
+ <string>About</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="aboutLabel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;SQLiteStudio v%1&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Free, open-source, cross-platform SQLite database manager.&lt;br/&gt;&lt;a href=&quot;http://sqlitestudio.pl&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://sqlitestudio.pl&lt;/span&gt;&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;%2&lt;br/&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Author and active maintainer:&lt;br/&gt;SalSoft (&lt;a href=&quot;http://salsoft.com.pl&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://salsoft.com.pl&lt;/span&gt;&lt;/a&gt;)&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="license">
+ <attribute name="title">
+ <string>Licenses</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QTextEdit" name="licenseEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="leftIcon">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp
new file mode 100644
index 0000000..8f5d433
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp
@@ -0,0 +1,220 @@
+#include "bugdialog.h"
+#include "ui_bugdialog.h"
+#include "iconmanager.h"
+#include "uiutils.h"
+#include "common/utils.h"
+#include "sqlitestudio.h"
+#include "mainwindow.h"
+#include "bugreportlogindialog.h"
+#include "services/pluginmanager.h"
+#include "services/bugreporter.h"
+#include "services/notifymanager.h"
+#include <QPushButton>
+#include <QDebug>
+#include <QDesktopServices>
+
+BugDialog::BugDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::BugDialog)
+{
+ init();
+}
+
+BugDialog::~BugDialog()
+{
+ delete ui;
+}
+
+void BugDialog::setFeatureRequestMode(bool feature)
+{
+ bugMode = !feature;
+ updateState();
+}
+
+void BugDialog::init()
+{
+ ui->setupUi(this);
+ resize(width(), height() - 50);
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Send"));
+
+ connect(ui->moreDetailsGroup, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->shortDescriptionEdit, SIGNAL(textChanged(QString)), this, SLOT(validate()));
+ connect(ui->longDescriptionEdit, SIGNAL(textChanged()), this, SLOT(validate()));
+ connect(ui->emailEdit, SIGNAL(textChanged(QString)), this, SLOT(validate()));
+ connect(ui->helpButton, SIGNAL(clicked()), this, SLOT(help()));
+ connect(ui->loginButton, SIGNAL(clicked()), this, SLOT(logIn()));
+
+ ui->versionEdit->setText(SQLITESTUDIO->getVersionString());
+ ui->osEdit->setText(getOsString());
+ ui->pluginsEdit->setText(PLUGINS->getLoadedPluginNames().join(", "));
+
+ user = CFG_CORE.Internal.BugReportUser.get();
+
+ if (CFG_CORE.Internal.BugReportRecentError.get())
+ {
+ ui->shortDescriptionEdit->setText(CFG_CORE.Internal.BugReportRecentTitle.get());
+ ui->longDescriptionEdit->setPlainText(CFG_CORE.Internal.BugReportRecentContents.get());
+ }
+
+ updateState();
+ validate();
+}
+
+QString BugDialog::getMessageAboutReportHistory()
+{
+ return tr("You can see all your reported bugs and ideas by selecting menu '%1' and then '%2'.").arg(MAINWINDOW->getSQLiteStudioMenu()->title())
+ .arg(MAINWINDOW->getAction(MainWindow::BUG_REPORT_HISTORY)->text());
+ return "";
+}
+
+void BugDialog::finishedBugReport(bool success, const QString& errorMsg)
+{
+ if (success)
+ {
+ notifyInfo(tr("A bug report sent successfully.") + " " + getMessageAboutReportHistory());
+ }
+ else
+ {
+ CFG_CORE.Internal.BugReportRecentError.set(true);
+ notifyError(tr("An error occurred while sending a bug report: %1\n%2").arg(errorMsg,
+ tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this.")));
+ }
+}
+
+void BugDialog::finishedFeatureRequest(bool success, const QString& errorMsg)
+{
+ if (success)
+ {
+ notifyInfo(tr("An idea proposal sent successfully.") + " " + getMessageAboutReportHistory());
+ }
+ else
+ {
+ CFG_CORE.Internal.BugReportRecentError.set(true);
+ notifyError(tr("An error occurred while sending an idea proposal: %1\n%2").arg(errorMsg,
+ tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this.")));
+ }
+}
+
+void BugDialog::updateState()
+{
+ ui->scrollArea->setVisible(ui->moreDetailsGroup->isChecked());
+
+ ui->moreDetailsGroup->setVisible(bugMode);
+ if (bugMode)
+ {
+ setWindowTitle(tr("A bug report"));
+ ui->shortDescriptionEdit->setPlaceholderText(tr("Describe problem in few words"));
+ ui->longDescriptionEdit->setPlaceholderText(tr("Describe problem and how to reproduce it"));
+ }
+ else
+ {
+ setWindowTitle(tr("A new feature idea"));
+ ui->shortDescriptionEdit->setPlaceholderText(tr("A title for your idea"));
+ ui->longDescriptionEdit->setPlaceholderText(tr("Describe your idea in more details"));
+ }
+
+ if (user.isNull())
+ {
+ ui->currentLoginLabel->setToolTip(tr("Reporting as an unregistered user, using e-mail address."));
+ ui->currentLoginLabel->setPixmap(ICONS.USER_UNKNOWN);
+ ui->emailEdit->setEnabled(true);
+ ui->emailEdit->clear();
+ ui->loginButton->setText(tr("Log in"));
+ ui->loginButton->setIcon(ICONS.USER);
+ }
+ else
+ {
+ ui->currentLoginLabel->setToolTip(tr("Reporting as a registered user."));
+ ui->currentLoginLabel->setPixmap(ICONS.USER);
+ ui->emailEdit->setText(user);
+ ui->emailEdit->setEnabled(false);
+ ui->loginButton->setText(tr("Log out"));
+ ui->loginButton->setIcon(ICONS.USER_UNKNOWN);
+ }
+}
+
+void BugDialog::validate()
+{
+ bool emailOk = !user.isNull() || validateEmail(ui->emailEdit->text());
+ int shortSize = ui->shortDescriptionEdit->text().trimmed().size();
+ int longSize = ui->longDescriptionEdit->toPlainText().trimmed().size();
+ bool shortOk = shortSize >= 10 && shortSize <= 100;
+ bool longOk = longSize >= 30;
+
+ setValidStateWihtTooltip(ui->emailEdit, tr("Providing true email address will make it possible to contact you regarding your report. "
+ "To learn more, press 'help' button on the right side."),
+ emailOk, tr("Enter vaild e-mail address, or log in."));
+
+ setValidState(ui->shortDescriptionEdit, shortOk, tr("Short description requires at least 10 characters, but not more than 100. "
+ "Longer description can be entered in the field below."));
+
+ setValidState(ui->longDescriptionEdit, longOk, tr("Long description requires at least 30 characters."));
+
+ bool valid = shortOk && longOk && emailOk;
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
+}
+
+void BugDialog::help()
+{
+ if (user.isNull())
+ QDesktopServices::openUrl(QUrl(BUGS->getReporterEmailHelpUrl()));
+ else
+ QDesktopServices::openUrl(QUrl(BUGS->getReporterUserAndPasswordHelpUrl()));
+}
+
+void BugDialog::logIn()
+{
+ if (!user.isNull())
+ {
+ // Log out
+ user = QString();
+ updateState();
+ BUGS->clearBugReportCredentials();
+ return;
+ }
+
+ BugReportLoginDialog dialog(this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ if (!dialog.isValid())
+ return;
+
+ BUGS->useBugReportCredentials(dialog.getLogin(), dialog.getPassword());
+ user = dialog.getLogin();
+ updateState();
+}
+
+void BugDialog::accept()
+{
+ CFG_CORE.Internal.BugReportRecentError.set(false);
+ CFG_CORE.Internal.BugReportRecentTitle.set(ui->shortDescriptionEdit->text());
+ CFG_CORE.Internal.BugReportRecentContents.set(ui->longDescriptionEdit->toPlainText());
+
+ if (bugMode)
+ {
+ if (user.isNull())
+ {
+ BUGS->reportBug(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(),
+ ui->osEdit->text(), ui->pluginsEdit->text(), BugDialog::finishedBugReport);
+ }
+ else
+ {
+ BUGS->reportBug(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(), ui->osEdit->text(), ui->pluginsEdit->text(),
+ BugDialog::finishedFeatureRequest);
+ }
+ }
+ else
+ {
+ if (user.isNull())
+ {
+ BUGS->requestFeature(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest);
+ }
+ else
+ {
+ BUGS->requestFeature(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest);
+ }
+ }
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h
new file mode 100644
index 0000000..bf60104
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h
@@ -0,0 +1,42 @@
+#ifndef BUGDIALOG_H
+#define BUGDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class BugDialog;
+}
+
+class GUI_API_EXPORT BugDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit BugDialog(QWidget *parent = 0);
+ ~BugDialog();
+
+ void setFeatureRequestMode(bool feature);
+
+ private:
+ void init();
+
+ static QString getMessageAboutReportHistory();
+ static void finishedBugReport(bool success, const QString& errorMsg);
+ static void finishedFeatureRequest(bool success, const QString& errorMsg);
+
+ Ui::BugDialog *ui = nullptr;
+ bool bugMode = true;
+ QString user;
+
+ private slots:
+ void updateState();
+ void validate();
+ void help();
+ void logIn();
+
+ public slots:
+ void accept();
+};
+
+#endif // BUGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui
new file mode 100644
index 0000000..f2dbcf3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugDialog</class>
+ <widget class="QDialog" name="BugDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>516</width>
+ <height>421</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="reporterGroup">
+ <property name="title">
+ <string>Reporter</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="currentLoginLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../icons.qrc">:/icons/img/user_unknown.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="emailEdit">
+ <property name="placeholderText">
+ <string>E-mail address</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="loginButton">
+ <property name="text">
+ <string>Log in</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/user.png</normaloff>:/icons/img/user.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="helpButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/help.png</normaloff>:/icons/img/help.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="shortDescriptionGroup">
+ <property name="title">
+ <string>Short description</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="shortDescriptionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="longDescriptionGroup">
+ <property name="title">
+ <string>Detailed description</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QPlainTextEdit" name="longDescriptionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="moreDetailsGroup">
+ <property name="title">
+ <string>Show more details</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>462</width>
+ <height>209</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QGroupBox" name="versionGroup">
+ <property name="title">
+ <string>SQLiteStudio version</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QLineEdit" name="versionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="osGroup">
+ <property name="title">
+ <string>Operating system</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QLineEdit" name="osEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="pluginsGroup">
+ <property name="title">
+ <string>Loaded plugins</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QLineEdit" name="pluginsEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BugDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BugDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp
new file mode 100644
index 0000000..19727fe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp
@@ -0,0 +1,94 @@
+#include "bugreportlogindialog.h"
+#include "ui_bugreportlogindialog.h"
+#include "uiutils.h"
+#include "services/bugreporter.h"
+#include "iconmanager.h"
+#include "common/widgetcover.h"
+#include <QPushButton>
+
+BugReportLoginDialog::BugReportLoginDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::BugReportLoginDialog)
+{
+ init();
+}
+
+BugReportLoginDialog::~BugReportLoginDialog()
+{
+ delete ui;
+}
+
+bool BugReportLoginDialog::isValid() const
+{
+ return validCredentials;
+}
+
+QString BugReportLoginDialog::getLogin() const
+{
+ return ui->loginEdit->text();
+}
+
+QString BugReportLoginDialog::getPassword() const
+{
+ return ui->passwordEdit->text();
+}
+
+void BugReportLoginDialog::init()
+{
+ ui->setupUi(this);
+ connect(ui->loginEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged()));
+ connect(ui->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged()));
+ connect(ui->validationButton, SIGNAL(clicked()), this, SLOT(remoteValidation()));
+ connect(BUGS, SIGNAL(credentialsValidationResult(bool,QString)), this, SLOT(remoteValidationResult(bool,QString)));
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Abort"));
+ connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(abortRemoteValidation()));
+
+ validate();
+}
+
+void BugReportLoginDialog::credentialsChanged()
+{
+ validCredentials = false;
+ validate();
+}
+
+void BugReportLoginDialog::validate()
+{
+ QString login = ui->loginEdit->text();
+ QString pass = ui->passwordEdit->text();
+
+ bool loginOk = login.size() >= 2;
+ bool passOk = pass.size() >= 5;
+
+ setValidState(ui->loginEdit, loginOk, tr("A login must be at least 2 characters long."));
+ setValidState(ui->passwordEdit, passOk, tr("A password must be at least 5 characters long."));
+
+ bool credentialsOk = loginOk && passOk;
+ ui->validationButton->setEnabled(credentialsOk);
+ ui->validationLabel->setEnabled(credentialsOk);
+
+ bool valid = credentialsOk && validCredentials;
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
+}
+
+void BugReportLoginDialog::abortRemoteValidation()
+{
+ BUGS->abortCredentialsValidation();
+}
+
+void BugReportLoginDialog::remoteValidation()
+{
+ widgetCover->show();
+ BUGS->validateBugReportCredentials(ui->loginEdit->text(), ui->passwordEdit->text());
+}
+
+void BugReportLoginDialog::remoteValidationResult(bool success, const QString& errorMessage)
+{
+ validCredentials = success;
+ ui->validationButton->setIcon(success ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR);
+ ui->validationLabel->setText(success ? tr("Valid") : errorMessage);
+ validate();
+ widgetCover->hide();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h
new file mode 100644
index 0000000..131ba3d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h
@@ -0,0 +1,40 @@
+#ifndef BUGREPORTLOGINDIALOG_H
+#define BUGREPORTLOGINDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class BugReportLoginDialog;
+}
+
+class WidgetCover;
+
+class GUI_API_EXPORT BugReportLoginDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit BugReportLoginDialog(QWidget *parent = 0);
+ ~BugReportLoginDialog();
+
+ bool isValid() const;
+ QString getLogin() const;
+ QString getPassword() const;
+
+ private:
+ void init();
+
+ Ui::BugReportLoginDialog *ui = nullptr;
+ bool validCredentials = false;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void credentialsChanged();
+ void validate();
+ void abortRemoteValidation();
+ void remoteValidation();
+ void remoteValidationResult(bool success, const QString& errorMessage);
+};
+
+#endif // BUGREPORTLOGINDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui
new file mode 100644
index 0000000..f6597bc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugReportLoginDialog</class>
+ <widget class="QDialog" name="BugReportLoginDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>343</width>
+ <height>197</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Log in</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="credentialsGroup">
+ <property name="title">
+ <string>Credentials</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="loginLabel">
+ <property name="text">
+ <string>Login:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="loginEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="passwordLabel">
+ <property name="text">
+ <string>Password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="passwordEdit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="validationGroup">
+ <property name="title">
+ <string>Validation</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="validationButton">
+ <property name="text">
+ <string>Validate</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/test_conn_error.png</normaloff>:/icons/img/test_conn_error.png</iconset>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="validationLabel">
+ <property name="text">
+ <string>Validation result message</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BugReportLoginDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BugReportLoginDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp
new file mode 100644
index 0000000..f4e48fe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp
@@ -0,0 +1,616 @@
+#include "columndialog.h"
+#include "common/unused.h"
+#include "ui_columndialog.h"
+#include "columndialogconstraintsmodel.h"
+#include "iconmanager.h"
+#include "newconstraintdialog.h"
+#include "dialogs/constraintdialog.h"
+#include "constraints/constraintpanel.h"
+#include "datatype.h"
+#include "uiutils.h"
+#include <QDebug>
+#include <QCheckBox>
+#include <QMessageBox>
+#include <QDebug>
+#include <QPushButton>
+
+ColumnDialog::ColumnDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ColumnDialog),
+ db(db)
+{
+ init();
+}
+
+ColumnDialog::~ColumnDialog()
+{
+ delete ui;
+}
+
+void ColumnDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ setWindowIcon(ICONS.COLUMN);
+
+ ui->scale->setStrict(true);
+ ui->precision->setStrict(true);
+
+ ui->typeCombo->addItem("");
+ foreach (DataType::Enum type, DataType::getAllTypes())
+ ui->typeCombo->addItem(DataType::toString(type));
+
+ connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateDataType()));
+
+ constraintsModel = new ColumnDialogConstraintsModel();
+ ui->constraintsView->setModel(constraintsModel);
+ initActions();
+
+ setupConstraintCheckBoxes();
+
+ connect(ui->advancedCheck, SIGNAL(toggled(bool)), this, SLOT(switchMode(bool)));
+
+ connect(ui->constraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateConstraintsToolbarState()));
+ connect(ui->constraintsView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editConstraint(QModelIndex)));
+ connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateConstraints()));
+ connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateState()));
+
+ connect(ui->pkButton, SIGNAL(clicked()), this, SLOT(configurePk()));
+ connect(ui->fkButton, SIGNAL(clicked()), this, SLOT(configureFk()));
+ connect(ui->checkButton, SIGNAL(clicked()), this, SLOT(configureCheck()));
+ connect(ui->defaultButton, SIGNAL(clicked()), this, SLOT(configureDefault()));
+ connect(ui->notNullButton, SIGNAL(clicked()), this, SLOT(configureNotNull()));
+ connect(ui->collateButton, SIGNAL(clicked()), this, SLOT(configureCollate()));
+ connect(ui->uniqueButton, SIGNAL(clicked()), this, SLOT(configureUnique()));
+
+ updateState();
+}
+
+void ColumnDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ColumnDialog::createActions()
+{
+ createAction(ADD_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_ADD, tr("Add constraint", "column dialog"), this, SLOT(addConstraint()), ui->constraintsToolbar);
+ createAction(EDIT_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_EDIT, tr("Edit constraint", "column dialog"), this, SLOT(editConstraint()), ui->constraintsToolbar);
+ createAction(DEL_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_DEL, tr("Delete constraint", "column dialog"), this, SLOT(delConstraint()), ui->constraintsToolbar);
+ createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move constraint up", "column dialog"), this, SLOT(moveConstraintUp()), ui->constraintsToolbar);
+ createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move constraint down", "column dialog"), this, SLOT(moveConstraintDown()), ui->constraintsToolbar);
+ ui->constraintsToolbar->addSeparator();
+ createAction(ADD_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add a primary key", "column dialog"), this, SLOT(addPk()), ui->constraintsToolbar);
+ createAction(ADD_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add a foreign key", "column dialog"), this, SLOT(addFk()), ui->constraintsToolbar);
+ createAction(ADD_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add an unique constraint", "column dialog"), this, SLOT(addUnique()), ui->constraintsToolbar);
+ createAction(ADD_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add a check constraint", "column dialog"), this, SLOT(addCheck()), ui->constraintsToolbar);
+ createAction(ADD_NOT_NULL, ICONS.CONSTRAINT_NOT_NULL_ADD, tr("Add a not null constraint", "column dialog"), this, SLOT(addNotNull()), ui->constraintsToolbar);
+ createAction(ADD_COLLATE, ICONS.CONSTRAINT_COLLATION_ADD, tr("Add a collate constraint", "column dialog"), this, SLOT(addCollate()), ui->constraintsToolbar);
+ createAction(ADD_DEFAULT, ICONS.CONSTRAINT_DEFAULT_ADD, tr("Add a default constraint", "column dialog"), this, SLOT(addDefault()), ui->constraintsToolbar);
+}
+
+void ColumnDialog::setupDefShortcuts()
+{
+}
+
+void ColumnDialog::updateConstraintsToolbarState()
+{
+ QModelIndex idx = ui->constraintsView->selectionModel()->currentIndex();
+ bool hasSelected = idx.isValid();
+ bool isFirst = false;
+ bool isLast = false;
+ if (constraintsModel->rowCount() > 0)
+ {
+ isFirst = (idx.row() == 0);
+ isLast = (idx.row() == (constraintsModel->rowCount() - 1));
+ }
+
+ actionMap[EDIT_CONSTRAINT]->setEnabled(hasSelected);
+ actionMap[DEL_CONSTRAINT]->setEnabled(hasSelected);
+ actionMap[MOVE_CONSTRAINT_UP]->setEnabled(hasSelected && !isFirst);
+ actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(hasSelected && !isLast);
+}
+
+void ColumnDialog::updateState()
+{
+ ui->pkButton->setEnabled(ui->pkCheck->isChecked());
+ ui->fkButton->setEnabled(ui->fkCheck->isChecked());
+ ui->uniqueButton->setEnabled(ui->uniqueCheck->isChecked());
+ ui->notNullButton->setEnabled(ui->notNullCheck->isChecked());
+ ui->checkButton->setEnabled(ui->checkCheck->isChecked());
+ ui->collateButton->setEnabled(ui->collateCheck->isChecked());
+ ui->defaultButton->setEnabled(ui->defaultCheck->isChecked());
+ updateConstraintsToolbarState();
+}
+
+void ColumnDialog::addConstraint(ConstraintDialog::Constraint mode)
+{
+ NewConstraintDialog dialog(mode, column.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteStatement* constrStmt = dialog.getConstraint();
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStmt);
+ if (!constr)
+ {
+ qCritical() << "Constraint returned from ConstraintDialog was not of column type, while we're trying to add column constraint.";
+ return;
+ }
+
+ constraintsModel->appendConstraint(constr);
+ ui->constraintsView->resizeColumnToContents(0);
+ ui->constraintsView->resizeColumnToContents(1);
+}
+
+void ColumnDialog::setupConstraintCheckBoxes()
+{
+ ui->pkCheck->setIcon(ICONS.CONSTRAINT_PRIMARY_KEY);
+ ui->fkCheck->setIcon(ICONS.CONSTRAINT_FOREIGN_KEY);
+ ui->uniqueCheck->setIcon(ICONS.CONSTRAINT_UNIQUE);
+ ui->notNullCheck->setIcon(ICONS.CONSTRAINT_NOT_NULL);
+ ui->checkCheck->setIcon(ICONS.CONSTRAINT_CHECK);
+ ui->collateCheck->setIcon(ICONS.CONSTRAINT_COLLATION);
+ ui->defaultCheck->setIcon(ICONS.CONSTRAINT_DEFAULT);
+
+ connect(ui->pkCheck, SIGNAL(clicked(bool)), this, SLOT(pkToggled(bool)));
+ connect(ui->fkCheck, SIGNAL(clicked(bool)), this, SLOT(fkToggled(bool)));
+ connect(ui->uniqueCheck, SIGNAL(clicked(bool)), this, SLOT(uniqueToggled(bool)));
+ connect(ui->notNullCheck, SIGNAL(clicked(bool)), this, SLOT(notNullToggled(bool)));
+ connect(ui->checkCheck, SIGNAL(clicked(bool)), this, SLOT(checkToggled(bool)));
+ connect(ui->collateCheck, SIGNAL(clicked(bool)), this, SLOT(collateToggled(bool)));
+ connect(ui->defaultCheck, SIGNAL(clicked(bool)), this, SLOT(defaultToggled(bool)));
+
+ for (QCheckBox* cb : {
+ ui->pkCheck,
+ ui->fkCheck,
+ ui->uniqueCheck,
+ ui->notNullCheck,
+ ui->checkCheck,
+ ui->collateCheck,
+ ui->defaultCheck
+ })
+ {
+ connect(cb, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ }
+}
+
+void ColumnDialog::addConstraint()
+{
+ addConstraint(ConstraintDialog::UNKNOWN);
+}
+
+void ColumnDialog::editConstraint()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ editConstraint(idx);
+}
+
+void ColumnDialog::delConstraint()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ delConstraint(idx);
+}
+
+void ColumnDialog::editConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row());
+ editConstraint(constr);
+}
+
+void ColumnDialog::editConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ ConstraintDialog dialog(ConstraintDialog::EDIT, constraint, column.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ ui->constraintsView->resizeColumnToContents(0);
+ ui->constraintsView->resizeColumnToContents(1);
+ updateConstraints();
+}
+
+void ColumnDialog::delConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row());
+
+ QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
+ QString msg = tr("Are you sure you want to delete constraint '%1'?", "column dialog").arg(arg);
+ int btn = QMessageBox::question(this, tr("Delete constraint", "column dialog"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ constraintsModel->delConstraint(idx.row());
+}
+
+void ColumnDialog::configureConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constraint = column->getConstraint(type);
+ if (!constraint)
+ {
+ qCritical() << "Called ColumnDialog::configureConstraint(), but there's no specified type constraint in the column!";
+ return;
+ }
+ editConstraint(constraint);
+}
+
+void ColumnDialog::addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constr = new SqliteCreateTable::Column::Constraint();
+ constr->type = type;
+ constraintsModel->appendConstraint(constr);
+ constr->rebuildTokens();
+}
+
+void ColumnDialog::delAllConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constr = nullptr;
+ while ((constr = column->getConstraint(type)) != nullptr)
+ constraintsModel->delConstraint(constr);
+}
+
+void ColumnDialog::constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled)
+{
+ if (enabled)
+ addEmptyConstraint(type);
+ else
+ delAllConstraint(type);
+}
+
+void ColumnDialog::updateConstraintState(SqliteCreateTable::Column::Constraint* constraint)
+{
+ QToolButton* toolButton = getToolButtonForConstraint(constraint);
+ if (!toolButton)
+ return;
+
+ bool result = true;
+ ConstraintPanel* panel = ConstraintPanel::produce(constraint);
+ if (!panel)
+ {
+ qCritical() << "Could not produce ConstraintPanel for constraint validation in ColumnDialog::updateConstraintState().";
+ }
+ else
+ {
+ panel->setDb(db);
+ panel->setConstraint(constraint);
+ result = panel->validateOnly();
+ delete panel;
+ }
+
+ QString errMsg = tr("Correct the constraint's configuration.");
+ if (db->getDialect() == Dialect::Sqlite2 && isUnofficialSqlite2Constraint(constraint))
+ {
+ QString tooltip = tr("This constraint is not officially supported by SQLite 2,\nbut it's okay to use it.");
+ setValidStateWihtTooltip(toolButton, tooltip, result, errMsg);
+ }
+ else
+ {
+ setValidState(toolButton, result, errMsg);
+ }
+
+ if (!result)
+ {
+ QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok);
+ btn->setEnabled(false);
+ }
+}
+
+QCheckBox* ColumnDialog::getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ui->pkCheck;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ui->notNullCheck;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ui->uniqueCheck;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ui->checkCheck;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ui->defaultCheck;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ui->collateCheck;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ui->fkCheck;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return nullptr;
+}
+
+QToolButton* ColumnDialog::getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ui->pkButton;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ui->notNullButton;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ui->uniqueButton;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ui->checkButton;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ui->defaultButton;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ui->collateButton;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ui->fkButton;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return nullptr;
+}
+
+bool ColumnDialog::isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return true;
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return false;
+}
+
+void ColumnDialog::moveConstraintUp()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ constraintsModel->moveConstraintUp(idx.row());
+}
+
+void ColumnDialog::moveConstraintDown()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ constraintsModel->moveConstraintDown(idx.row());
+}
+
+void ColumnDialog::addPk()
+{
+ addConstraint(ConstraintDialog::PK);
+}
+
+void ColumnDialog::addFk()
+{
+ addConstraint(ConstraintDialog::FK);
+}
+
+void ColumnDialog::addUnique()
+{
+ addConstraint(ConstraintDialog::UNIQUE);
+}
+
+void ColumnDialog::addCheck()
+{
+ addConstraint(ConstraintDialog::CHECK);
+}
+
+void ColumnDialog::addCollate()
+{
+ addConstraint(ConstraintDialog::COLLATE);
+}
+
+void ColumnDialog::addNotNull()
+{
+ addConstraint(ConstraintDialog::NOTNULL);
+}
+
+void ColumnDialog::addDefault()
+{
+ addConstraint(ConstraintDialog::DEFAULT);
+}
+
+void ColumnDialog::configurePk()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
+}
+
+void ColumnDialog::configureFk()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY);
+}
+
+void ColumnDialog::configureUnique()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::UNIQUE);
+}
+
+void ColumnDialog::configureCheck()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::CHECK);
+}
+
+void ColumnDialog::configureCollate()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::COLLATE);
+}
+
+void ColumnDialog::configureNotNull()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL);
+}
+
+void ColumnDialog::configureDefault()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::DEFAULT);
+}
+
+void ColumnDialog::pkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::PRIMARY_KEY, enabled);
+}
+
+void ColumnDialog::fkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::FOREIGN_KEY, enabled);
+}
+
+void ColumnDialog::uniqueToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::UNIQUE, enabled);
+}
+
+void ColumnDialog::checkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::CHECK, enabled);
+}
+
+void ColumnDialog::collateToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::COLLATE, enabled);
+}
+
+void ColumnDialog::notNullToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::NOT_NULL, enabled);
+}
+
+void ColumnDialog::defaultToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::DEFAULT, enabled);
+}
+
+void ColumnDialog::switchMode(bool advanced)
+{
+ ui->constraintModesWidget->setCurrentWidget(advanced ? ui->advancedPage : ui->simplePage);
+}
+
+void ColumnDialog::updateConstraints()
+{
+ QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok);
+ btn->setEnabled(true);
+
+ for (QCheckBox* cb : {
+ ui->pkCheck,
+ ui->fkCheck,
+ ui->uniqueCheck,
+ ui->notNullCheck,
+ ui->checkCheck,
+ ui->collateCheck,
+ ui->defaultCheck
+ })
+ {
+ cb->setChecked(false);
+ }
+
+ for (QToolButton* tb : {
+ ui->pkButton,
+ ui->fkButton,
+ ui->uniqueButton,
+ ui->notNullButton,
+ ui->checkButton,
+ ui->collateButton,
+ ui->defaultButton
+ })
+ {
+ setValidState(tb, true);
+ }
+
+ foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints)
+ updateConstraint(constr);
+}
+
+void ColumnDialog::updateConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ QCheckBox* checkBox = getCheckBoxForConstraint(constraint);
+ if (checkBox)
+ {
+ checkBox->setChecked(true);
+ updateConstraintState(constraint);
+ }
+}
+
+void ColumnDialog::setColumn(SqliteCreateTable::Column* value)
+{
+ column = SqliteCreateTable::ColumnPtr::create(*value);
+ column->setParent(value->parent());
+ constraintsModel->setColumn(column.data());
+
+ ui->name->setText(value->name);
+ if (value->type)
+ {
+ ui->typeCombo->setEditText(value->type->name);
+ ui->scale->setValue(value->type->scale, false);
+ ui->precision->setValue(value->type->precision, false);
+ }
+
+ updateConstraints();
+}
+
+SqliteCreateTable::Column* ColumnDialog::getModifiedColumn()
+{
+ column->name = ui->name->text();
+ updateDataType();
+ column->rebuildTokens();
+
+ return new SqliteCreateTable::Column(*column);
+}
+
+QToolBar* ColumnDialog::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void ColumnDialog::updateDataType()
+{
+ if (!column)
+ return;
+
+ QString typeTxt = ui->typeCombo->currentText();
+ QString scaleTxt = ui->scale->getValue().toString();
+ QString precisionTxt = ui->precision->getValue().toString();
+ if (!typeTxt.isEmpty())
+ {
+ if (!column->type)
+ {
+ column->type = new SqliteColumnType();
+ column->type->setParent(column.data());
+ }
+
+ column->type->name = typeTxt;
+
+ if (!scaleTxt.isEmpty())
+ column->type->scale = ui->scale->getValue();
+
+ if (!precisionTxt.isEmpty())
+ column->type->precision = ui->precision->getValue();
+
+ column->type->rebuildTokens();
+ }
+ else if (column->type) // there was a type, but there's not now
+ {
+ delete column->type;
+ column->type = nullptr;
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h
new file mode 100644
index 0000000..c567e1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h
@@ -0,0 +1,113 @@
+#ifndef COLUMNDIALOG_H
+#define COLUMNDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "common/extactioncontainer.h"
+#include "constraintdialog.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+class ColumnDialogConstraintsModel;
+class QCheckBox;
+class QToolButton;
+
+namespace Ui {
+ class ColumnDialog;
+}
+
+class GUI_API_EXPORT ColumnDialog : public QDialog, public ExtActionContainer
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ ADD_CONSTRAINT,
+ EDIT_CONSTRAINT,
+ DEL_CONSTRAINT,
+ MOVE_CONSTRAINT_UP,
+ MOVE_CONSTRAINT_DOWN,
+ ADD_PK,
+ ADD_FK,
+ ADD_UNIQUE,
+ ADD_CHECK,
+ ADD_DEFAULT,
+ ADD_NOT_NULL,
+ ADD_COLLATE
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit ColumnDialog(Db* db, QWidget *parent = 0);
+ ~ColumnDialog();
+
+ void init();
+ void setColumn(SqliteCreateTable::Column* value);
+ SqliteCreateTable::Column* getModifiedColumn();
+ QToolBar* getToolBar(int toolbar) const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void addConstraint(ConstraintDialog::Constraint mode);
+ void setupConstraintCheckBoxes();
+ void editConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ void delConstraint(const QModelIndex& idx);
+ void configureConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void delAllConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled);
+ void updateConstraintState(SqliteCreateTable::Column::Constraint* constraint);
+ QCheckBox* getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ QToolButton* getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ bool isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint);
+
+ Ui::ColumnDialog *ui = nullptr;
+ SqliteCreateTable::ColumnPtr column;
+ ColumnDialogConstraintsModel* constraintsModel = nullptr;
+ QCheckBox* modeCheckBox = nullptr;
+ Db* db = nullptr;
+
+ private slots:
+ void updateConstraintsToolbarState();
+ void updateState();
+ void addConstraint();
+ void editConstraint();
+ void editConstraint(const QModelIndex& idx);
+ void delConstraint();
+ void moveConstraintUp();
+ void moveConstraintDown();
+ void addPk();
+ void addFk();
+ void addUnique();
+ void addCheck();
+ void addCollate();
+ void addNotNull();
+ void addDefault();
+ void configurePk();
+ void configureFk();
+ void configureUnique();
+ void configureCheck();
+ void configureCollate();
+ void configureNotNull();
+ void configureDefault();
+ void pkToggled(bool enabled);
+ void fkToggled(bool enabled);
+ void uniqueToggled(bool enabled);
+ void checkToggled(bool enabled);
+ void collateToggled(bool enabled);
+ void notNullToggled(bool enabled);
+ void defaultToggled(bool enabled);
+ void switchMode(bool advanced);
+ void updateConstraints();
+ void updateConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ void updateDataType();
+};
+
+#endif // COLUMNDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui
new file mode 100644
index 0000000..ac7e5ae
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnDialog</class>
+ <widget class="QDialog" name="ColumnDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>424</width>
+ <height>360</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Column</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="columnGroup">
+ <property name="title">
+ <string>Name and type</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="2">
+ <widget class="NumericSpinBox" name="scale">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>,</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="NumericSpinBox" name="precision">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Data type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Column name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="name"/>
+ </item>
+ <item row="0" column="2" colspan="3">
+ <widget class="QLabel" name="sizeLabel">
+ <property name="text">
+ <string>Size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="typeCombo">
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="constraintsGroup">
+ <property name="title">
+ <string>Constraints</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QStackedWidget" name="constraintModesWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="simplePage">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="uniqueCheck">
+ <property name="text">
+ <string>Unique</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="pkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="fkCheck">
+ <property name="text">
+ <string>Foreign Key</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QToolButton" name="fkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="collateCheck">
+ <property name="text">
+ <string>Collate</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="notNullCheck">
+ <property name="text">
+ <string>Not NULL</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="checkCheck">
+ <property name="text">
+ <string>Check condition</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="pkCheck">
+ <property name="text">
+ <string>Primary Key</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QCheckBox" name="defaultCheck">
+ <property name="text">
+ <string>Default</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QToolButton" name="uniqueButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QToolButton" name="checkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QToolButton" name="notNullButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QToolButton" name="collateButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QToolButton" name="defaultButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="advancedPage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="constraintsToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableView" name="constraintsView">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="advancedCheck">
+ <property name="text">
+ <string>Advanced mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>NumericSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header>common/numericspinbox.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>name</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>scale</tabstop>
+ <tabstop>precision</tabstop>
+ <tabstop>pkCheck</tabstop>
+ <tabstop>pkButton</tabstop>
+ <tabstop>fkCheck</tabstop>
+ <tabstop>fkButton</tabstop>
+ <tabstop>uniqueCheck</tabstop>
+ <tabstop>uniqueButton</tabstop>
+ <tabstop>checkCheck</tabstop>
+ <tabstop>checkButton</tabstop>
+ <tabstop>notNullCheck</tabstop>
+ <tabstop>notNullButton</tabstop>
+ <tabstop>collateCheck</tabstop>
+ <tabstop>collateButton</tabstop>
+ <tabstop>defaultCheck</tabstop>
+ <tabstop>defaultButton</tabstop>
+ <tabstop>advancedCheck</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>constraintsView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ColumnDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ColumnDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp
new file mode 100644
index 0000000..853b680
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp
@@ -0,0 +1,335 @@
+#include "columndialogconstraintsmodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+
+ColumnDialogConstraintsModel::ColumnDialogConstraintsModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+void ColumnDialogConstraintsModel::setColumn(SqliteCreateTable::Column* value)
+{
+ beginResetModel();
+ column = value;
+ endResetModel();
+}
+
+SqliteCreateTable::Column::Constraint* ColumnDialogConstraintsModel::getConstraint(int constrIdx) const
+{
+ if (column.isNull())
+ return nullptr;
+
+ return column->constraints[constrIdx];
+}
+
+void ColumnDialogConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ delete column->constraints[constrIdx];
+ column->constraints[constrIdx] = constr;
+ constr->setParent(column);
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), constrIdx, constrIdx);
+ column->constraints.insert(constrIdx, constr);
+ constr->setParent(column);
+ endInsertRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::appendConstraint(SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ column->constraints.append(constr);
+ constr->setParent(column);
+ endInsertRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::delConstraint(int constrIdx)
+{
+ if (column.isNull())
+ return;
+
+ beginRemoveRows(QModelIndex(), constrIdx, constrIdx);
+ delete column->constraints[constrIdx];
+ column->constraints.removeAt(constrIdx);
+ endRemoveRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::delConstraint(SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ int constrIdx = column->constraints.indexOf(constr);
+ if (constrIdx < -1)
+ return;
+
+ delConstraint(constrIdx);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintUp(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx-1);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintDown(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx+1);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx)
+{
+ if (column.isNull())
+ return;
+
+ if (newIdx == constrIdx)
+ return;
+
+ if (newIdx == constrIdx+1)
+ {
+ // See TableStructureModel::moveColumnTo() for details above code below.
+ int tmpIdx = newIdx;
+ newIdx = constrIdx;
+ constrIdx = tmpIdx;
+ }
+
+ beginMoveRows(QModelIndex(), constrIdx, constrIdx, QModelIndex(), newIdx);
+ if (newIdx >= column->constraints.size())
+ {
+ SqliteCreateTable::Column::Constraint* constr = column->constraints.takeAt(constrIdx);
+ column->constraints.append(constr);
+ }
+ else
+ column->constraints.move(constrIdx, newIdx);
+
+ endMoveRows();
+
+ emit constraintsChanged();
+}
+
+ColumnDialogConstraintsModel::Column ColumnDialogConstraintsModel::getColumn(int colIdx) const
+{
+ return static_cast<Column>(colIdx);
+}
+
+QIcon ColumnDialogConstraintsModel::getIcon(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ICONS.CONSTRAINT_NOT_NULL;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ICONS.CONSTRAINT_DEFAULT;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ICONS.CONSTRAINT_COLLATION;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QIcon();
+}
+
+QString ColumnDialogConstraintsModel::getName(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ return constr->name;
+}
+
+QString ColumnDialogConstraintsModel::getType(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return "NOT NULL";
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return "DEFAULT";
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return "COLLATE";
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QString ColumnDialogConstraintsModel::getDetails(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return getNotNullDetails(constr);
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return getDefaultDetails(constr);
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return getCollateDetails(constr);
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QString ColumnDialogConstraintsModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const
+{
+ TokenList tokens = constr->tokens.mid(tokenOffset + 1);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+int ColumnDialogConstraintsModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (column.isNull())
+ return 0;
+
+ return column->constraints.size();
+}
+
+int ColumnDialogConstraintsModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 3;
+}
+
+QVariant ColumnDialogConstraintsModel::data(const QModelIndex& index, int role) const
+{
+ if (column.isNull())
+ return QVariant();
+
+ switch (getColumn(index.column()))
+ {
+ case Column::TYPE:
+ {
+ if (role == Qt::DecorationRole)
+ return getIcon(index.row());
+
+ if (role == Qt::DisplayRole)
+ return getType(index.row());
+
+ break;
+ }
+ case Column::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return getName(index.row());
+
+ break;
+ }
+ case Column::DETAILS:
+ if (role == Qt::DisplayRole)
+ return getDetails(index.row());
+
+ break;
+ }
+ return QVariant();
+}
+
+QVariant ColumnDialogConstraintsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ switch (getColumn(section))
+ {
+ case Column::TYPE:
+ return tr("Type", "column dialog constraints");
+ case Column::NAME:
+ return tr("Name", "column dialog constraints");
+ case Column::DETAILS:
+ return tr("Details", "column dialog constraints");
+ }
+ return QVariant();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h
new file mode 100644
index 0000000..f37933a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h
@@ -0,0 +1,58 @@
+#ifndef COLUMNDIALOGCONSTRAINTSMODEL_H
+#define COLUMNDIALOGCONSTRAINTSMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT ColumnDialogConstraintsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit ColumnDialogConstraintsModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ void setColumn(SqliteCreateTable::Column* value);
+ SqliteCreateTable::Column::Constraint* getConstraint(int constrIdx) const;
+ void replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr);
+ void insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr);
+ void appendConstraint(SqliteCreateTable::Column::Constraint* constr);
+ void delConstraint(int constrIdx);
+ void delConstraint(SqliteCreateTable::Column::Constraint* constr);
+ void moveConstraintUp(int constrIdx);
+ void moveConstraintDown(int constrIdx);
+ void moveConstraintColumnTo(int constrIdx, int newIdx);
+
+ private:
+ enum class Column
+ {
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Column getColumn(int colIdx) const;
+ QIcon getIcon(int rowIdx) const;
+ QString getName(int rowIdx) const;
+ QString getType(int rowIdx) const;
+ QString getDetails(int rowIdx) const;
+ QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const;
+
+ QPointer<SqliteCreateTable::Column> column;
+
+ signals:
+ void constraintsChanged();
+};
+
+#endif // COLUMNDIALOGCONSTRAINTSMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp
new file mode 100644
index 0000000..932036e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp
@@ -0,0 +1,1529 @@
+#include "configdialog.h"
+#include "ui_configdialog.h"
+#include "services/config.h"
+#include "uiconfig.h"
+#include "customconfigwidgetplugin.h"
+#include "services/pluginmanager.h"
+#include "formmanager.h"
+#include "services/codeformatter.h"
+#include "plugins/codeformatterplugin.h"
+#include "configwidgets/styleconfigwidget.h"
+#include "configwidgets/combodatawidget.h"
+#include "configwidgets/listtostringlisthash.h"
+#include "iconmanager.h"
+#include "common/userinputfilter.h"
+#include "multieditor/multieditorwidget.h"
+#include "multieditor/multieditorwidgetplugin.h"
+#include "plugins/confignotifiableplugin.h"
+#include "mainwindow.h"
+#include "common/unused.h"
+#include "sqlitestudio.h"
+#include "configmapper.h"
+#include "datatype.h"
+#include "uiutils.h"
+#include <QSignalMapper>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QDebug>
+#include <QComboBox>
+#include <QStyleFactory>
+#include <QFile>
+#include <QDir>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <QListWidget>
+#include <QTableWidget>
+#include <QDesktopServices>
+#include <QtUiTools/QUiLoader>
+#include <QKeySequenceEdit>
+#include <plugins/uiconfiguredplugin.h>
+
+#define GET_FILTER_STRING(Widget, WidgetType, Method) \
+ if (qobject_cast<WidgetType*>(Widget))\
+ return qobject_cast<WidgetType*>(Widget)->Method() + " " + Widget->toolTip();\
+
+#define GET_FILTER_STRING2(Widget, WidgetType) \
+ WidgetType* w##WidgetType = qobject_cast<WidgetType*>(widget);\
+ if (w##WidgetType)\
+ return getFilterString(w##WidgetType) + " " + Widget->toolTip();
+
+ConfigDialog::ConfigDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ConfigDialog)
+{
+ init();
+}
+
+ConfigDialog::~ConfigDialog()
+{
+ // Cancel transaction on CfgMain objects from plugins
+ rollbackPluginConfigs();
+
+ // Notify plugins about dialog being closed
+ UiConfiguredPlugin* cfgPlugin = nullptr;
+ foreach (Plugin* plugin, PLUGINS->getLoadedPlugins())
+ {
+ cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ if (!cfgPlugin)
+ continue;
+
+ cfgPlugin->configDialogClosed();
+ }
+
+ // Delete UI and other resources
+ delete ui;
+ safe_delete(configMapper);
+
+ for (ConfigMapper* mapper : pluginConfigMappers.values())
+ delete mapper;
+
+ pluginConfigMappers.clear();
+
+}
+
+void ConfigDialog::configureDataEditors(const QString& dataTypeString)
+{
+ ui->categoriesWidget->setVisible(false);
+ ui->stackedWidget->setCurrentWidget(ui->dataEditorsPage);
+
+ for (int i = 0; i < ui->dataEditorsTypesList->count(); i++)
+ {
+ if (ui->dataEditorsTypesList->item(i)->text() == dataTypeString.toUpper())
+ {
+ ui->dataEditorsTypesList->setCurrentRow(i);
+ return;
+ }
+ }
+
+ addDataType(dataTypeString.toUpper());
+}
+
+QString ConfigDialog::getFilterString(QWidget *widget)
+{
+ // Common code for widgets with single method call
+ GET_FILTER_STRING(widget, QLabel, text);
+ GET_FILTER_STRING(widget, QAbstractButton, text);
+ GET_FILTER_STRING(widget, QLineEdit, text);
+ GET_FILTER_STRING(widget, QTextEdit, toPlainText);
+ GET_FILTER_STRING(widget, QPlainTextEdit, toPlainText);
+ GET_FILTER_STRING(widget, QGroupBox, title);
+ GET_FILTER_STRING(widget, QKeySequenceEdit, keySequence().toString);
+
+ // Widgets needs a little more than single method call
+ GET_FILTER_STRING2(widget, QComboBox);
+ GET_FILTER_STRING2(widget, QTreeWidget);
+ GET_FILTER_STRING2(widget, QListWidget);
+ GET_FILTER_STRING2(widget, QTableWidget);
+
+ return QString::null;
+}
+
+QString ConfigDialog::getFilterString(QComboBox *widget)
+{
+ QStringList items;
+ for (int i = 0; i < widget->count(); i++)
+ items << widget->itemText(i);
+
+ return items.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QTreeWidget *widget)
+{
+ QList<QTreeWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QTreeWidgetItem* item, items)
+ for (int i = 0; i < widget->columnCount(); i++)
+ strList << item->text(i) + " " + item->toolTip(0);
+
+ return strList.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QListWidget *widget)
+{
+ QList<QListWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QListWidgetItem* item, items)
+ strList << item->text() + " " + item->toolTip();
+
+ return strList.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QTableWidget *widget)
+{
+ QList<QTableWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QTableWidgetItem* item, items)
+ strList << item->text() + " " + item->toolTip();
+
+ return strList.join(" ");
+}
+
+void ConfigDialog::init()
+{
+ ui->setupUi(this);
+ setWindowIcon(ICONS.CONFIGURE);
+
+ ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0));
+
+ configMapper = new ConfigMapper(CfgMain::getPersistableInstances());
+ connect(configMapper, SIGNAL(modified()), this, SLOT(markModified()));
+ connect(configMapper, &ConfigMapper::notifyEnabledWidgetModified, [=](QWidget* widget, CfgEntry* key, const QVariant& value)
+ {
+ UNUSED(widget);
+ for (ConfigNotifiablePlugin* plugin : notifiablePlugins)
+ plugin->configModified(key, value);
+ });
+
+ ui->categoriesFilter->setClearButtonEnabled(true);
+ UserInputFilter* filter = new UserInputFilter(ui->categoriesFilter, this, SLOT(applyFilter(QString)));
+ filter->setDelay(500);
+
+ ui->stackedWidget->setCurrentWidget(ui->generalPage);
+ initPageMap();
+ initInternalCustomConfigWidgets();
+ initPlugins();
+ initPluginsPage();
+ initFormatterPlugins();
+ initDataEditors();
+ initShortcuts();
+
+ connect(ui->categoriesTree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(switchPage(QTreeWidgetItem*)));
+ connect(ui->previewTabs, SIGNAL(currentChanged(int)), this, SLOT(updateStylePreview()));
+ connect(ui->activeStyleCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateStylePreview()));
+ connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply()));
+ connect(ui->hideBuiltInPluginsCheck, SIGNAL(toggled(bool)), this, SLOT(updateBuiltInPluginsVisibility()));
+
+ ui->activeStyleCombo->addItems(QStyleFactory::keys());
+
+ connect(ui->stackedWidget, SIGNAL(currentChanged(int)), this, SLOT(pageSwitched()));
+
+ ui->hideBuiltInPluginsCheck->setChecked(true);
+
+#ifdef NO_AUTO_UPDATES
+ ui->updatesGroup->setVisible(false);
+#endif
+
+ load();
+ updateStylePreview();
+}
+
+void ConfigDialog::load()
+{
+ updatingDataEditorItem = true;
+ configMapper->loadToWidget(ui->stackedWidget);
+ updatingDataEditorItem = false;
+ setModified(false);
+}
+
+void ConfigDialog::save()
+{
+ MainWindow::getInstance()->setStyle(ui->activeStyleCombo->currentText());
+
+ QString loadedPlugins = collectLoadedPlugins();
+ storeSelectedFormatters();
+ CFG->beginMassSave();
+ CFG_CORE.General.LoadedPlugins.set(loadedPlugins);
+ configMapper->saveFromWidget(ui->stackedWidget, true);
+ commitPluginConfigs();
+ CFG->commitMassSave();
+}
+
+void ConfigDialog::storeSelectedFormatters()
+{
+ CodeFormatterPlugin* plugin = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QString lang;
+ QString pluginName;
+ for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i)
+ {
+ item = ui->formatterPluginsTree->topLevelItem(i);
+ lang = item->text(0);
+
+ combo = formatterLangToPluginComboMap[lang];
+ if (!combo)
+ {
+ qCritical() << "Could not find combo for lang " << lang << " in storeSelectedFormatters()";
+ continue;
+ }
+
+ pluginName = combo->currentData().toString();
+ plugin = dynamic_cast<CodeFormatterPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin for lang " << lang << " in storeSelectedFormatters()";
+ continue;
+ }
+
+ FORMATTER->setFormatter(lang, plugin);
+ }
+
+ FORMATTER->storeCurrentSettings();
+}
+
+void ConfigDialog::markModified()
+{
+ setModified(true);
+}
+
+void ConfigDialog::setModified(bool modified)
+{
+ modifiedFlag = modified;
+ updateModified();
+}
+
+void ConfigDialog::updateModified()
+{
+ ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(modifiedFlag);
+}
+
+void ConfigDialog::applyFilter(const QString &filter)
+{
+ QColor normalColor = ui->categoriesTree->palette().color(QPalette::Active, QPalette::WindowText);
+ QColor disabledColor = ui->categoriesTree->palette().color(QPalette::Disabled, QPalette::WindowText);
+ if (filter.isEmpty())
+ {
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ item->setForeground(0, normalColor);
+
+ return;
+ }
+
+ QList<QWidget*> widgets = ui->stackedWidget->findChildren<QWidget*>();
+ QList<QWidget*> matchedWidgets;
+ foreach (QWidget* widget, widgets)
+ {
+ if (getFilterString(widget).contains(filter, Qt::CaseInsensitive))
+ matchedWidgets << widget;
+ }
+
+ QHash<QWidget*, QTreeWidgetItem*> pageToCategoryItem = buildPageToCategoryItemMap();
+ QSet<QTreeWidgetItem*> matchedCategories;
+ foreach (QWidget* page, pageToCategoryItem.keys())
+ {
+ foreach (QWidget* matched, matchedWidgets)
+ {
+ if (page->isAncestorOf(matched))
+ {
+ if (!pageToCategoryItem.contains(page))
+ {
+ qCritical() << "Page" << page << "not on page-to-category-item mapping.";
+ continue;
+ }
+
+ matchedCategories << pageToCategoryItem[page];
+ break;
+ }
+ }
+ }
+
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ item->setForeground(0, disabledColor);
+
+ foreach (QTreeWidgetItem* item, matchedCategories)
+ {
+ item->setForeground(0, normalColor);
+ while ((item = item->parent()) != nullptr)
+ item->setForeground(0, normalColor);
+ }
+}
+
+QHash<QWidget*, QTreeWidgetItem*> ConfigDialog::buildPageToCategoryItemMap() const
+{
+ QHash<QString,QTreeWidgetItem*> pageNameToCategoryItem;
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ pageNameToCategoryItem[item->statusTip(0)] = item;
+
+ QWidget* page = nullptr;
+ QHash<QWidget*,QTreeWidgetItem*> pageToCategoryItem;
+ for (int i = 0; i < ui->stackedWidget->count(); i++)
+ {
+ page = ui->stackedWidget->widget(i);
+ pageToCategoryItem[page] = pageNameToCategoryItem[page->objectName()];
+ }
+ return pageToCategoryItem;
+}
+
+QList<QTreeWidgetItem *> ConfigDialog::getAllCategoryItems() const
+{
+ return ui->categoriesTree->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::getDefaultEditorsForType(DataType::Enum dataType)
+{
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ DataType modelDataType;
+ modelDataType.setType(dataType);
+
+ typedef QPair<int,MultiEditorWidgetPlugin*> PluginWithPriority;
+ QList<PluginWithPriority> sortedPlugins;
+ PluginWithPriority editorWithPrio;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ if (!plugin->validFor(modelDataType))
+ continue;
+
+ editorWithPrio.first = plugin->getPriority(modelDataType);
+ editorWithPrio.second = plugin;
+ sortedPlugins << editorWithPrio;
+ }
+
+ qSort(sortedPlugins.begin(), sortedPlugins.end(), [=](const PluginWithPriority& p1, const PluginWithPriority& p2) -> bool
+ {
+ return p1.first < p2.first;
+ });
+
+ QList<MultiEditorWidgetPlugin*> results;
+ for (const PluginWithPriority& p: sortedPlugins)
+ results << p.second;
+
+ return results;
+}
+
+void ConfigDialog::pageSwitched()
+{
+ if (ui->stackedWidget->currentWidget() == ui->dataEditorsPage)
+ {
+ updateDataTypeEditors();
+ return;
+ }
+}
+
+void ConfigDialog::updateDataTypeEditors()
+{
+ QString typeName = ui->dataEditorsTypesList->currentItem()->text();
+ DataType::Enum typeEnum = DataType::fromString(typeName);
+ bool usingCustomOrder = false;
+ QStringList editorsOrder = getPluginNamesFromDataTypeItem(ui->dataEditorsTypesList->currentItem(), &usingCustomOrder);
+ QList<MultiEditorWidgetPlugin*> sortedPlugins;
+
+ while (ui->dataEditorsSelectedTabs->count() > 0)
+ delete ui->dataEditorsSelectedTabs->widget(0);
+
+ ui->dataEditorsAvailableList->clear();
+ if (usingCustomOrder)
+ sortedPlugins = updateCustomDataTypeEditors(editorsOrder);
+ else
+ sortedPlugins = updateDefaultDataTypeEditors(typeEnum);
+
+ ui->dataEditorsAvailableList->sortItems();
+
+ for (MultiEditorWidgetPlugin* plugin : sortedPlugins)
+ addDataTypeEditor(plugin);
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::updateCustomDataTypeEditors(const QStringList& editorsOrder)
+{
+ // Building plugins list
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ QList<MultiEditorWidgetPlugin*> enabledPlugins;
+ QListWidgetItem* item = nullptr;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ item = new QListWidgetItem(plugin->getTitle());
+ item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
+ item->setCheckState(editorsOrder.contains(plugin->getName()) ? Qt::Checked : Qt::Unchecked);
+ item->setData(QListWidgetItem::UserType, plugin->getName());
+ if (item->checkState() == Qt::Checked)
+ enabledPlugins << plugin;
+
+ ui->dataEditorsAvailableList->addItem(item);
+ }
+
+ qSort(enabledPlugins.begin(), enabledPlugins.end(), [=](MultiEditorWidgetPlugin* p1, MultiEditorWidgetPlugin* p2) -> bool
+ {
+ return editorsOrder.indexOf(p1->getName()) < editorsOrder.indexOf(p2->getName());
+ });
+
+ return enabledPlugins;
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::updateDefaultDataTypeEditors(DataType::Enum typeEnum)
+{
+ // Building plugins list
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ QList<MultiEditorWidgetPlugin*> enabledPlugins = getDefaultEditorsForType(typeEnum);
+ QListWidgetItem* item = nullptr;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ item = new QListWidgetItem(plugin->getTitle());
+ item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
+ item->setCheckState(enabledPlugins.contains(plugin) ? Qt::Checked : Qt::Unchecked);
+ item->setData(QListWidgetItem::UserType, plugin->getName());
+ ui->dataEditorsAvailableList->addItem(item);
+ }
+ return enabledPlugins;
+}
+
+void ConfigDialog::addDataTypeEditor(const QString& pluginName)
+{
+ MultiEditorWidgetPlugin* plugin = dynamic_cast<MultiEditorWidgetPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin" << pluginName << " in ConfigDialog::addDataTypeEditor()";
+ return;
+ }
+
+ addDataTypeEditor(plugin);
+}
+
+void ConfigDialog::addDataTypeEditor(MultiEditorWidgetPlugin* plugin)
+{
+ MultiEditorWidget* editor = plugin->getInstance();
+ ui->dataEditorsSelectedTabs->addTab(editor, editor->getTabLabel().replace("&", "&&"));
+}
+
+void ConfigDialog::removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName)
+{
+ QStringList orderedPlugins = getPluginNamesFromDataTypeItem(item);
+ int idx = orderedPlugins.indexOf(pluginName);
+ removeDataTypeEditor(idx);
+}
+
+void ConfigDialog::removeDataTypeEditor(int idx)
+{
+ if (idx < 0 || idx > (ui->dataEditorsSelectedTabs->count() - 1))
+ {
+ qCritical() << "Index out of range in ConfigDialog::removeDataTypeEditor():" << idx << "(tabs:" << ui->dataEditorsSelectedTabs->count() << ")";
+ return;
+ }
+
+ delete ui->dataEditorsSelectedTabs->widget(idx);
+}
+
+void ConfigDialog::transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem)
+{
+ DataType::Enum dataType = DataType::fromString(typeItem->text());
+ QList<MultiEditorWidgetPlugin*> plugins = getDefaultEditorsForType(dataType);
+
+ QStringList pluginNames;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ pluginNames << plugin->getName();
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+QStringList ConfigDialog::getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists)
+{
+ QVariant data = typeItem->data(QListWidgetItem::UserType);
+ if (exists)
+ *exists = data.isValid();
+
+ return data.toStringList();
+}
+
+void ConfigDialog::setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames)
+{
+ updatingDataEditorItem = true;
+ typeItem->setData(QListWidgetItem::UserType, pluginNames);
+ updatingDataEditorItem = false;
+}
+
+void ConfigDialog::addDataType(const QString& typeStr)
+{
+ QListWidgetItem* item = new QListWidgetItem(typeStr);
+ item->setFlags(item->flags()|Qt::ItemIsEditable);
+ ui->dataEditorsTypesList->addItem(item);
+ ui->dataEditorsTypesList->setCurrentRow(ui->dataEditorsTypesList->count() - 1, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ markModified();
+}
+
+void ConfigDialog::rollbackPluginConfigs()
+{
+ CfgMain* mainCfg = nullptr;
+ for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys())
+ {
+ mainCfg = plugin->getMainUiConfig();
+ if (mainCfg)
+ mainCfg->rollback();
+ }
+}
+
+void ConfigDialog::commitPluginConfigs()
+{
+ CfgMain* mainCfg = nullptr;
+ for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys())
+ {
+ mainCfg = plugin->getMainUiConfig();
+ if (mainCfg)
+ {
+ mainCfg->commit();
+ mainCfg->begin(); // be prepared for further changes after "Apply"
+ }
+ }
+}
+
+void ConfigDialog::updateDataTypeListState()
+{
+ bool listEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0 && ui->dataEditorsTypesList->currentItem()->flags().testFlag(Qt::ItemIsEditable);
+ dataEditRenameAction->setEnabled(listEditingEnabled);
+ dataEditDeleteAction->setEnabled(listEditingEnabled);
+
+ bool orderEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0;
+ ui->dataEditorsAvailableList->setEnabled(orderEditingEnabled);
+ ui->dataEditorsSelectedTabs->setEnabled(orderEditingEnabled);
+}
+
+void ConfigDialog::dataEditorItemEdited(QListWidgetItem* item)
+{
+ if (updatingDataEditorItem)
+ return;
+
+ updatingDataEditorItem = true;
+ QString txt = item->text().toUpper();
+ if (DataType::getAllNames().contains(txt))
+ txt += "_";
+
+ while (ui->dataEditorsTypesList->findItems(txt, Qt::MatchExactly).size() > 1)
+ txt += "_";
+
+ item->setText(txt);
+ updatingDataEditorItem = false;
+}
+
+void ConfigDialog::dataEditorAvailableChanged(QListWidgetItem* item)
+{
+ QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem();
+ if (!typeItem)
+ return;
+
+ bool exists = false;
+ QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists);
+ if (!exists)
+ {
+ transformDataTypeEditorsToCustomList(typeItem);
+ pluginNames = getPluginNamesFromDataTypeItem(typeItem);
+ }
+
+ QString pluginName = item->data(QListWidgetItem::UserType).toString();
+ Qt::CheckState state = item->checkState();
+ if (pluginNames.contains(pluginName) && state == Qt::Unchecked)
+ {
+ removeDataTypeEditor(typeItem, pluginName);
+ pluginNames.removeOne(pluginName);
+
+ }
+ else if (!pluginNames.contains(pluginName) && state == Qt::Checked)
+ {
+ addDataTypeEditor(pluginName);
+ pluginNames << pluginName;
+ }
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+void ConfigDialog::dataEditorTabsOrderChanged(int from, int to)
+{
+ QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem();
+ if (!typeItem)
+ return;
+
+ bool exists = false;
+ QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists);
+ if (!exists)
+ {
+ transformDataTypeEditorsToCustomList(typeItem);
+ pluginNames = getPluginNamesFromDataTypeItem(typeItem);
+ }
+
+ int pluginSize = pluginNames.size();
+ if (from >= pluginSize || to >= pluginSize)
+ {
+ qCritical() << "Tabse moved out of range. in ConfigDialog::dataEditorTabsOrderChanged(). Range was: " << pluginSize << "and indexes were:" << from << to;
+ return;
+ }
+
+ QString pluginName = pluginNames[from];
+ pluginNames.removeAt(from);
+ pluginNames.insert(to, pluginName);
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+void ConfigDialog::addDataType()
+{
+ addDataType("");
+ renameDataType();
+}
+
+void ConfigDialog::renameDataType()
+{
+ QListWidgetItem* item = ui->dataEditorsTypesList->currentItem();
+ if (!item)
+ return;
+
+ ui->dataEditorsTypesList->editItem(item);
+}
+
+void ConfigDialog::delDataType()
+{
+ QListWidgetItem* item = ui->dataEditorsTypesList->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->dataEditorsTypesList->currentRow();
+ delete ui->dataEditorsTypesList->takeItem(row);
+
+ if (ui->dataEditorsTypesList->count() > 0)
+ {
+ if (ui->dataEditorsTypesList->count() <= row)
+ {
+ row--;
+ if (row < 0)
+ row = 0;
+ }
+
+ ui->dataEditorsTypesList->setCurrentRow(row, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ }
+
+ updateDataTypeListState();
+ markModified();
+}
+
+void ConfigDialog::dataTypesHelp()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Customizing_data_type_editors");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void ConfigDialog::updateActiveFormatterState()
+{
+ CodeFormatterPlugin* plugin = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* button = nullptr;
+ QString lang;
+ QString pluginName;
+ for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i)
+ {
+ item = ui->formatterPluginsTree->topLevelItem(i);
+ lang = item->text(0);
+
+ combo = formatterLangToPluginComboMap[lang];
+ button = formatterLangToConfigButtonMap[lang];
+ if (!button)
+ {
+ qCritical() << "Could not find button for lang " << lang << " in updateActiveFormatterState()";
+ continue;
+ }
+
+ if (!combo)
+ {
+ qCritical() << "Could not find combo for lang " << lang << " in updateActiveFormatterState()";
+ button->setEnabled(false);
+ continue;
+ }
+
+ pluginName = combo->currentData().toString();
+ plugin = dynamic_cast<CodeFormatterPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin for lang " << lang << " in updateActiveFormatterState()";
+ button->setEnabled(false);
+ continue;
+ }
+
+ button->setEnabled(dynamic_cast<UiConfiguredPlugin*>(plugin));
+ }
+}
+
+void ConfigDialog::configureFormatter(const QString& pluginTitle)
+{
+ QTreeWidgetItem* item = getItemByTitle(pluginTitle);
+ if (!item)
+ return;
+
+ ui->categoriesTree->setCurrentItem(item);
+}
+
+void ConfigDialog::activeFormatterChanged()
+{
+ markModified();
+ updateActiveFormatterState();
+}
+
+void ConfigDialog::detailsClicked(const QString& pluginName)
+{
+ static const QString details = QStringLiteral(
+ "<table>"
+ "<thead>"
+ "<tr><td colspan=2 align=\"center\"><b>%1</b></td></tr>"
+ "<tr><td colspan=2></td></tr>"
+ "</thead>"
+ "<tbody>%2</tbody>"
+ "</table>");
+ static const QString row = QStringLiteral("<tr><td>%1</td><td align=\"right\">%2</td></tr>");
+ static const QString hline = QStringLiteral("<tr><td colspan=\"2\"><hr/></td></tr>");
+
+ PluginType* type = PLUGINS->getPluginType(pluginName);
+ Q_ASSERT(type != nullptr);
+
+ // Rows
+ QStringList rows;
+ rows << row.arg(tr("Description:", "plugin details")).arg(PLUGINS->getDescription(pluginName));
+ rows << row.arg(tr("Category:", "plugin details")).arg(type->getTitle());
+ rows << row.arg(tr("Version:", "plugin details")).arg(PLUGINS->getPrintableVersion(pluginName));
+ rows << row.arg(tr("Author:", "plugin details")).arg(PLUGINS->getAuthor(pluginName));
+ rows << hline;
+ rows << row.arg(tr("Internal name:", "plugin details")).arg(pluginName);
+ rows << row.arg(tr("Dependencies:", "plugin details")).arg(PLUGINS->getDependencies(pluginName).join(", "));
+ rows << row.arg(tr("Conflicts:", "plugin details")).arg(PLUGINS->getConflicts(pluginName).join(", "));
+
+ // Message
+ QString pluginDetails = details.arg(PLUGINS->getTitle(pluginName)).arg(rows.join(""));
+ QMessageBox::information(this, tr("Plugin details"), pluginDetails);
+}
+
+void ConfigDialog::failedToLoadPlugin(const QString& pluginName)
+{
+ QTreeWidgetItem* theItem = itemToPluginNameMap.valueByRight(pluginName);
+ if (!theItem)
+ {
+ qWarning() << "Plugin" << pluginName << "failed to load, but it could not be found on the plugins list in ConfigDialog.";
+ return;
+ }
+
+ theItem->setCheckState(0, Qt::Unchecked);
+}
+
+void ConfigDialog::codeFormatterUnloaded()
+{
+ refreshFormattersPage();
+}
+
+void ConfigDialog::codeFormatterLoaded()
+{
+ refreshFormattersPage();
+}
+
+void ConfigDialog::loadUnloadPlugin(QTreeWidgetItem* item, int column)
+{
+ if (column != 0)
+ return;
+
+ QString pluginName = itemToPluginNameMap.valueByLeft(item);
+ if (PLUGINS->isBuiltIn(pluginName))
+ return;
+
+ bool wasLoaded = PLUGINS->isLoaded(pluginName);
+
+ if (wasLoaded == (item->checkState(0) == Qt::Checked))
+ return;
+
+ if (wasLoaded)
+ PLUGINS->unload(pluginName);
+ else
+ PLUGINS->load(pluginName);
+
+ markModified();
+}
+
+void ConfigDialog::pluginAboutToUnload(Plugin* plugin, PluginType* type)
+{
+ // Deinit tree item
+ QTreeWidgetItem* typeItem = getPluginsCategoryItem(type);
+ QTreeWidgetItem* pluginItem = getPluginItem(plugin);
+ if (pluginItem)
+ {
+ typeItem->removeChild(pluginItem);
+ pluginToItemMap.remove(plugin);
+ }
+
+ // Notifiable plugin
+ ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast<ConfigNotifiablePlugin*>(plugin);
+ if (notifiablePlugin && notifiablePlugins.contains(notifiablePlugin))
+ notifiablePlugins.removeOne(notifiablePlugin);
+
+ // Deinit page
+ deinitPluginPage(plugin);
+
+ // Update tree categories
+ updatePluginCategoriesVisibility();
+}
+
+void ConfigDialog::pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading)
+{
+ // Update formatters page
+ if (type->isForPluginType<CodeFormatterPlugin>())
+ codeFormatterLoaded();
+
+ // Init page
+ if (!initPluginPage(plugin, skipConfigLoading))
+ return;
+
+ // Init tree item
+ QTreeWidgetItem* typeItem = getPluginsCategoryItem(type);
+ QTreeWidgetItem* pluginItem = new QTreeWidgetItem({plugin->getTitle()});
+ pluginItem->setStatusTip(0, plugin->getName());
+ typeItem->addChild(pluginItem);
+ pluginToItemMap[plugin] = pluginItem;
+
+ // Update tree categories
+ updatePluginCategoriesVisibility();
+
+ // Notifiable plugin
+ ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast<ConfigNotifiablePlugin*>(plugin);
+ if (notifiablePlugin)
+ notifiablePlugins << notifiablePlugin;
+}
+
+void ConfigDialog::pluginUnloaded(const QString& pluginName, PluginType* type)
+{
+ UNUSED(pluginName);
+
+ // Update formatters page
+ if (type->isForPluginType<CodeFormatterPlugin>())
+ codeFormatterUnloaded();
+}
+
+void ConfigDialog::updatePluginCategoriesVisibility()
+{
+ QTreeWidgetItem* categories = getPluginsCategoryItem();
+ for (int i = 0; i < categories->childCount(); i++)
+ updatePluginCategoriesVisibility(categories->child(i));
+}
+
+void ConfigDialog::updateBuiltInPluginsVisibility()
+{
+ bool hideBuiltIn = ui->hideBuiltInPluginsCheck->isChecked();
+ QHashIterator<QTreeWidgetItem*,QString> it = itemToPluginNameMap.iterator();
+ while (it.hasNext())
+ {
+ it.next();
+ if (PLUGINS->isBuiltIn(it.value()))
+ ui->pluginsList->setItemHidden(it.key(), hideBuiltIn);
+ else
+ ui->pluginsList->setItemHidden(it.key(), false);
+ }
+}
+
+void ConfigDialog::applyShortcutsFilter(const QString &filter)
+{
+ QTreeWidgetItem* categoryItem = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QKeySequenceEdit* seqEdit = nullptr;
+ bool empty = filter.isEmpty();
+ bool visible = true;
+ int foundInCategory = 0;
+ for (int i = 0, total_i = ui->shortcutsTable->topLevelItemCount(); i < total_i; ++i)
+ {
+ foundInCategory = 0;
+ categoryItem = ui->shortcutsTable->topLevelItem(i);
+ for (int j = 0 , total_j = categoryItem->childCount(); j < total_j; ++j)
+ {
+ item = categoryItem->child(j);
+ seqEdit = dynamic_cast<QKeySequenceEdit*>(ui->shortcutsTable->itemWidget(item, 1));
+ visible = empty || item->text(0).contains(filter, Qt::CaseInsensitive) ||
+ seqEdit->keySequence().toString().contains(filter, Qt::CaseInsensitive);
+
+ item->setHidden(!visible);
+ if (visible)
+ foundInCategory++;
+ }
+
+ categoryItem->setHidden(foundInCategory == 0);
+ }
+}
+
+void ConfigDialog::updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem)
+{
+ categoryItem->setHidden(categoryItem->childCount() == 0);
+}
+
+QString ConfigDialog::collectLoadedPlugins() const
+{
+ QStringList loaded;
+ QHashIterator<QTreeWidgetItem*,QString> it = itemToPluginNameMap.iterator();
+ while (it.hasNext())
+ {
+ it.next();
+ loaded << (it.value() + "=" + ((it.key()->checkState(0) == Qt::Checked) ? "1" : "0"));
+ }
+
+ return loaded.join(",");
+}
+
+void ConfigDialog::initPageMap()
+{
+ int pages = ui->stackedWidget->count();
+ QWidget* widget = nullptr;
+ for (int i = 0; i < pages; i++)
+ {
+ widget = ui->stackedWidget->widget(i);
+ nameToPage[widget->objectName()] = widget;
+ }
+}
+
+void ConfigDialog::initInternalCustomConfigWidgets()
+{
+ QList<CustomConfigWidgetPlugin*> customWidgets;
+ customWidgets << new StyleConfigWidget();
+ customWidgets << new ListToStringListHash(&CFG_UI.General.DataEditorsOrder);
+ configMapper->setInternalCustomConfigWidgets(customWidgets);
+}
+
+void ConfigDialog::initFormatterPlugins()
+{
+ ui->formatterPluginsTree->header()->setSectionsMovable(false);
+ ui->formatterPluginsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ ui->formatterPluginsTree->resizeColumnToContents(1);
+ ui->formatterPluginsTree->resizeColumnToContents(2);
+
+ refreshFormattersPage();
+}
+
+void ConfigDialog::refreshFormattersPage()
+{
+ ui->formatterPluginsTree->clear();
+
+ QHash<QString,QVariant> activeFormatters = CFG_CORE.General.ActiveCodeFormatter.get();
+
+ QList<CodeFormatterPlugin*> plugins = PLUGINS->getLoadedPlugins<CodeFormatterPlugin>();
+ QHash<QString,QList<CodeFormatterPlugin*>> groupedPlugins;
+ for (CodeFormatterPlugin* plugin : plugins)
+ groupedPlugins[plugin->getLanguage()] << plugin;
+
+ formatterLangToPluginComboMap.clear();
+ formatterLangToConfigButtonMap.clear();
+ int row = 0;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* configButton = nullptr;
+ QStringList pluginTitles;
+ QStringList pluginNames;
+ QStringList sortedPluginNames;
+ QString selectedPluginName;
+ QModelIndex index;
+ QString groupName;
+ QHashIterator<QString,QList<CodeFormatterPlugin*>> it(groupedPlugins);
+ while (it.hasNext())
+ {
+ it.next();
+ groupName = it.key();
+
+ item = new QTreeWidgetItem({groupName});
+ ui->formatterPluginsTree->addTopLevelItem(item);
+
+ pluginNames.clear();
+ pluginTitles.clear();
+ for (CodeFormatterPlugin* plugin : it.value())
+ {
+ pluginNames << plugin->getName();
+ pluginTitles << plugin->getTitle();
+ }
+ sortedPluginNames = pluginNames;
+ qSort(sortedPluginNames);
+
+ combo = new QComboBox(ui->formatterPluginsTree);
+ for (int i = 0, total = pluginNames.size(); i < total; ++i)
+ combo->addItem(pluginTitles[i], pluginNames[i]);
+
+ connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(activeFormatterChanged()));
+ index = ui->formatterPluginsTree->model()->index(row, 1);
+ ui->formatterPluginsTree->setIndexWidget(index, combo);
+ formatterLangToPluginComboMap[groupName] = combo;
+
+ if (activeFormatters.contains(groupName) && pluginNames.contains(activeFormatters[groupName].toString()))
+ {
+ selectedPluginName = activeFormatters[groupName].toString();
+ }
+ else
+ {
+ // Pick first from sorted list and put it to combobox
+ selectedPluginName = sortedPluginNames.first();
+ }
+
+ configButton = new QToolButton(ui->formatterPluginsTree);
+ configButton->setIcon(ICONS.CONFIGURE);
+ index = ui->formatterPluginsTree->model()->index(row, 2);
+ ui->formatterPluginsTree->setIndexWidget(index, configButton);
+ connect(configButton, &QToolButton::clicked, [this, combo]() {configureFormatter(combo->currentText());});
+ formatterLangToConfigButtonMap[groupName] = configButton;
+
+ combo->setCurrentIndex(pluginNames.indexOf(selectedPluginName));
+
+ row++;
+ }
+
+ updateActiveFormatterState();
+}
+
+void ConfigDialog::applyStyle(QWidget *widget, QStyle *style)
+{
+ widget->setStyle(style);
+ foreach (QObject* child, widget->children())
+ {
+ if (!qobject_cast<QWidget*>(child))
+ continue;
+
+ applyStyle(qobject_cast<QWidget*>(child), style);
+ }
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem() const
+{
+ QTreeWidgetItem* item = nullptr;
+ for (int i = 0; i < ui->categoriesTree->topLevelItemCount(); i++)
+ {
+ item = ui->categoriesTree->topLevelItem(i);
+ if (item->statusTip(0) == ui->pluginsPage->objectName())
+ return item;
+ }
+ Q_ASSERT_X(true, "ConfigDialog", "No Plugins toplevel item in config categories tree!");
+ return nullptr;
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem(PluginType* type) const
+{
+ if (!pluginTypeToItemMap.contains(type))
+ return nullptr;
+
+ return pluginTypeToItemMap[type];
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginItem(Plugin* plugin) const
+{
+ if (!pluginToItemMap.contains(plugin))
+ return nullptr;
+
+ return pluginToItemMap[plugin];
+}
+
+QTreeWidgetItem* ConfigDialog::createPluginsTypeItem(const QString& widgetName, const QString& title) const
+{
+ if (FORMS->hasWidget(widgetName))
+ return new QTreeWidgetItem({title});
+
+ QTreeWidgetItem* pluginsCategoryItem = getPluginsCategoryItem();
+ QTreeWidgetItem* item = nullptr;
+ for (int i = 0; i < pluginsCategoryItem->childCount(); i++)
+ {
+ item = pluginsCategoryItem->child(i);
+ if (item->statusTip(0) == widgetName)
+ return item;
+ }
+ return nullptr;
+
+}
+
+QTreeWidgetItem* ConfigDialog::getItemByTitle(const QString& title) const
+{
+ QList<QTreeWidgetItem*> items = ui->categoriesTree->findItems(title, Qt::MatchExactly|Qt::MatchRecursive);
+ if (items.size() == 0)
+ return nullptr;
+
+ return items.first();
+}
+
+void ConfigDialog::switchPage(QTreeWidgetItem *item)
+{
+ if (isPluginCategoryItem((item)))
+ {
+ switchPageToPlugin(item);
+ return;
+ }
+
+ QString name = item->statusTip(0);
+ if (!nameToPage.contains(name))
+ {
+ qWarning() << "Switched page to item" << name << "but there's no such named page defined in ConfigDialog.";
+ return;
+ }
+
+ ui->stackedWidget->setCurrentWidget(nameToPage[name]);
+}
+
+void ConfigDialog::switchPageToPlugin(QTreeWidgetItem *item)
+{
+ QString pluginName = item->statusTip(0);
+ if (!nameToPage.contains(pluginName))
+ {
+ qCritical() << "No plugin page available for plugin:" << pluginName;
+ return;
+ }
+ ui->stackedWidget->setCurrentWidget(nameToPage[pluginName]);
+}
+
+void ConfigDialog::initPlugins()
+{
+ QTreeWidgetItem *item = getPluginsCategoryItem();
+
+ // Recreate
+ QTreeWidgetItem *typeItem = nullptr;
+ foreach (PluginType* pluginType, PLUGINS->getPluginTypes())
+ {
+ typeItem = createPluginsTypeItem(pluginType->getConfigUiForm(), pluginType->getTitle());
+ if (!typeItem)
+ continue;
+
+ item->addChild(typeItem);
+ pluginTypeToItemMap[pluginType] = typeItem;
+
+ foreach (Plugin* plugin, pluginType->getLoadedPlugins())
+ pluginLoaded(plugin, pluginType, true);
+ }
+
+ updatePluginCategoriesVisibility();
+
+ connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*)));
+ connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginAboutToUnload(Plugin*,PluginType*)));
+}
+
+void ConfigDialog::initPluginsPage()
+{
+ setValidStateTooltip(ui->pluginsList, tr("Plugins are loaded/unloaded immediately when checked/unchecked, "
+ "but modified list of plugins to load at startup is not saved until "
+ "you commit the whole configuration dialog."));
+
+ QTreeWidgetItem* category = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QFont font;
+ QModelIndex categoryIndex;
+ QModelIndex itemIndex;
+ int itemRow;
+ int categoryRow;
+ bool builtIn;
+ QLabel* detailsLabel = nullptr;
+ QString title;
+ QSize itemSize;
+ QStringList pluginNames;
+
+ // Font and metrics
+ item = new QTreeWidgetItem({""});
+ font = item->font(0);
+
+ QFontMetrics fm(font);
+ itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4));
+
+ delete item;
+
+ // Creating...
+ ui->pluginsList->header()->setSectionsMovable(false);
+ ui->pluginsList->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ QBrush categoryBg = ui->pluginsList->palette().button();
+ QBrush categoryFg = ui->pluginsList->palette().buttonText();
+
+ connect(ui->pluginsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(loadUnloadPlugin(QTreeWidgetItem*,int)));
+ connect(PLUGINS, SIGNAL(failedToLoad(QString)), this, SLOT(failedToLoadPlugin(QString)));
+
+ categoryRow = 0;
+ QList<PluginType*> pluginTypes = PLUGINS->getPluginTypes();
+ qSort(pluginTypes.begin(), pluginTypes.end(), PluginType::nameLessThan);
+ foreach (PluginType* pluginType, pluginTypes)
+ {
+ category = new QTreeWidgetItem({pluginType->getTitle()});
+ font.setItalic(false);
+ font.setBold(true);
+ category->setFont(0, font);
+ for (int i = 0; i < 2; i++)
+ {
+ category->setBackground(i, categoryBg);
+ category->setForeground(i, categoryFg);
+ }
+ category->setSizeHint(0, itemSize);
+ ui->pluginsList->addTopLevelItem(category);
+
+ categoryIndex = ui->pluginsList->model()->index(categoryRow, 0);
+ categoryRow++;
+
+ itemRow = 0;
+ pluginNames = pluginType->getAllPluginNames();
+ qSort(pluginNames);
+ foreach (const QString& pluginName, pluginNames)
+ {
+ builtIn = PLUGINS->isBuiltIn(pluginName);
+ title = PLUGINS->getTitle(pluginName);
+ if (builtIn)
+ title += tr(" (built-in)", "plugins manager in configuration dialog");
+
+ item = new QTreeWidgetItem({title});
+ item->setCheckState(0, PLUGINS->isLoaded(pluginName) ? Qt::Checked : Qt::Unchecked);
+ item->setSizeHint(0, itemSize);
+ if (builtIn)
+ item->setDisabled(true);
+
+ category->addChild(item);
+
+ itemToPluginNameMap.insert(item, pluginName);
+
+ // Details button
+ detailsLabel = new QLabel(QString("<a href='%1'>%2</a> ").arg(pluginName).arg(tr("Details")), ui->pluginsList);
+ detailsLabel->setAlignment(Qt::AlignRight);
+ itemIndex = ui->pluginsList->model()->index(itemRow, 1, categoryIndex);
+ ui->pluginsList->setIndexWidget(itemIndex, detailsLabel);
+
+ connect(detailsLabel, SIGNAL(linkActivated(QString)), this, SLOT(detailsClicked(QString)));
+
+ itemRow++;
+ }
+
+ if (itemRow == 0)
+ {
+ item = new QTreeWidgetItem({tr("No plugins in this category.")});
+ item->setDisabled(true);
+ item->setSizeHint(0, itemSize);
+
+ font.setItalic(true);
+ font.setBold(false);
+ item->setFont(0, font);
+
+ category->addChild(item);
+ }
+
+ category->setExpanded(true);
+ }
+}
+
+bool ConfigDialog::initPluginPage(Plugin* plugin, bool skipConfigLoading)
+{
+ if (!dynamic_cast<UiConfiguredPlugin*>(plugin))
+ return false;
+
+ UiConfiguredPlugin* cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ QString pluginName = plugin->getName();
+ QString formName = cfgPlugin->getConfigUiForm();
+ QWidget* widget = FORMS->createWidget(formName);
+ if (!widget)
+ {
+ qWarning() << "Could not load plugin UI file" << formName << "for plugin:" << pluginName;
+ return false;
+ }
+
+ nameToPage[pluginName] = widget;
+ ui->stackedWidget->addWidget(widget);
+ CfgMain* mainConfig = cfgPlugin->getMainUiConfig();
+ if (mainConfig)
+ {
+ pluginConfigMappers[cfgPlugin] = new ConfigMapper(mainConfig);
+ pluginConfigMappers[cfgPlugin]->bindToConfig(widget);
+ mainConfig->begin();
+ }
+ else if (!skipConfigLoading)
+ {
+ configMapper->loadToWidget(widget);
+ }
+
+ cfgPlugin->configDialogOpen();
+ return true;
+}
+
+void ConfigDialog::deinitPluginPage(Plugin* plugin)
+{
+ QString pluginName = plugin->getName();
+ if (!nameToPage.contains(pluginName))
+ return;
+
+ if (!dynamic_cast<UiConfiguredPlugin*>(plugin))
+ {
+ UiConfiguredPlugin* cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ CfgMain* mainCfg = cfgPlugin->getMainUiConfig();
+ if (mainCfg)
+ mainCfg->rollback();
+
+ cfgPlugin->configDialogClosed();
+
+ if (pluginConfigMappers.contains(cfgPlugin))
+ {
+ delete pluginConfigMappers[cfgPlugin];
+ pluginConfigMappers.remove(cfgPlugin);
+ }
+ }
+
+ QWidget* widget = nameToPage[pluginName];
+ nameToPage.remove(pluginName);
+ ui->stackedWidget->removeWidget(widget);
+ delete widget;
+}
+
+void ConfigDialog::initDataEditors()
+{
+ ui->dataEditorsAvailableList->setSpacing(1);
+
+ QHash<QString,QVariant> editorsOrder = CFG_UI.General.DataEditorsOrder.get();
+ QSet<QString> dataTypeSet = editorsOrder.keys().toSet();
+ dataTypeSet += DataType::getAllNames().toSet();
+ QStringList dataTypeList = dataTypeSet.toList();
+ qSort(dataTypeList);
+
+ QListWidgetItem* item = nullptr;
+ for (const QString& type : dataTypeList)
+ {
+ item = new QListWidgetItem(type);
+ if (!DataType::getAllNames().contains(type))
+ item->setFlags(item->flags()|Qt::ItemIsEditable);
+
+ ui->dataEditorsTypesList->addItem(item);
+ }
+
+ QAction* act = new QAction(ICONS.INSERT_DATATYPE, tr("Add new data type"), ui->dataEditorsTypesToolbar);
+ connect(act, SIGNAL(triggered()), this, SLOT(addDataType()));
+ ui->dataEditorsTypesToolbar->addAction(act);
+
+ dataEditRenameAction = new QAction(ICONS.RENAME_DATATYPE, tr("Rename selected data type"), ui->dataEditorsTypesToolbar);
+ connect(dataEditRenameAction, SIGNAL(triggered()), this, SLOT(renameDataType()));
+ ui->dataEditorsTypesToolbar->addAction(dataEditRenameAction);
+
+ dataEditDeleteAction = new QAction(ICONS.DELETE_DATATYPE, tr("Delete selected data type"), ui->dataEditorsTypesToolbar);
+ connect(dataEditDeleteAction, SIGNAL(triggered()), this, SLOT(delDataType()));
+ ui->dataEditorsTypesToolbar->addAction(dataEditDeleteAction);
+
+ act = new QAction(ICONS.HELP, tr("Help for configuring data type editors"), ui->dataEditorsTypesToolbar);
+ connect(act, SIGNAL(triggered()), this, SLOT(dataTypesHelp()));
+ ui->dataEditorsTypesToolbar->addAction(act);
+
+ connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeEditors()));
+ connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeListState()));
+ connect(ui->dataEditorsTypesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorItemEdited(QListWidgetItem*)));
+ connect(ui->dataEditorsAvailableList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorAvailableChanged(QListWidgetItem*)));
+ connect(ui->dataEditorsSelectedTabs->tabBar(), SIGNAL(tabMoved(int,int)), this, SLOT(dataEditorTabsOrderChanged(int,int)));
+
+ ui->dataEditorsTypesList->setCurrentRow(0, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ updateDataTypeListState();
+}
+
+void ConfigDialog::initShortcuts()
+{
+ ui->shortcutsTable->header()->setSectionsMovable(false);
+ ui->shortcutsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ ui->shortcutsTable->header()->setSectionResizeMode(1, QHeaderView::Fixed);
+ ui->shortcutsTable->header()->setSectionResizeMode(2, QHeaderView::Fixed);
+ ui->shortcutsTable->header()->resizeSection(1, 150);
+ ui->shortcutsTable->header()->resizeSection(2, 26);
+
+ ui->shortcutsFilterEdit->setClearButtonEnabled(true);
+ new UserInputFilter(ui->shortcutsFilterEdit, this, SLOT(applyShortcutsFilter(QString)));
+
+ static const QString metaName = CFG_SHORTCUTS_METANAME;
+ QList<CfgCategory*> categories;
+ for (CfgMain* cfgMain : CfgMain::getInstances())
+ {
+ if (cfgMain->getMetaName() != metaName)
+ continue;
+
+ for (CfgCategory* cat : cfgMain->getCategories().values())
+ categories << cat;
+ }
+
+ qSort(categories.begin(), categories.end(), [](CfgCategory* cat1, CfgCategory* cat2) -> bool
+ {
+ return cat1->getTitle().compare(cat2->getTitle()) < 0;
+ });
+
+ for (CfgCategory* cat : categories)
+ initShortcuts(cat);
+}
+
+void ConfigDialog::initShortcuts(CfgCategory *cfgCategory)
+{
+ QTreeWidgetItem* item = nullptr;
+ QFont font;
+ QModelIndex categoryIndex;
+ QModelIndex itemIndex;
+ QKeySequenceEdit *sequenceEdit = nullptr;
+ QToolButton* clearButton = nullptr;
+ QString title;
+ QSize itemSize;
+
+ // Font and metrics
+ item = new QTreeWidgetItem({""});
+ font = item->font(0);
+
+ QFontMetrics fm(font);
+ itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4));
+
+ delete item;
+
+ // Creating...
+ QBrush categoryBg = ui->shortcutsTable->palette().button();
+ QBrush categoryFg = ui->shortcutsTable->palette().buttonText();
+
+ QTreeWidgetItem* category = new QTreeWidgetItem({cfgCategory->getTitle()});
+ font.setItalic(false);
+ font.setBold(true);
+ category->setFont(0, font);
+ for (int i = 0; i < 3; i++)
+ {
+ category->setBackground(i, categoryBg);
+ category->setForeground(i, categoryFg);
+ }
+ category->setSizeHint(0, itemSize);
+ category->setFlags(category->flags() ^ Qt::ItemIsSelectable);
+ ui->shortcutsTable->addTopLevelItem(category);
+
+ int categoryRow = ui->shortcutsTable->topLevelItemCount() - 1;
+ categoryIndex = ui->shortcutsTable->model()->index(categoryRow, 0);
+
+ int itemRow = 0;
+ QStringList entryNames = cfgCategory->getEntries().keys();
+ qSort(entryNames);
+ foreach (const QString& entryName, entryNames)
+ {
+ // Title
+ title = cfgCategory->getEntries()[entryName]->getTitle();
+ item = new QTreeWidgetItem(category, {title});
+
+ // Key edit
+ sequenceEdit = new QKeySequenceEdit(ui->shortcutsTable);
+ sequenceEdit->setFixedWidth(150);
+ sequenceEdit->setProperty("cfg", cfgCategory->getEntries()[entryName]->getFullKey());
+ itemIndex = ui->shortcutsTable->model()->index(itemRow, 1, categoryIndex);
+ ui->shortcutsTable->setIndexWidget(itemIndex, sequenceEdit);
+ configMapper->addExtraWidget(sequenceEdit);
+
+ // Clear button
+ clearButton = new QToolButton(ui->shortcutsTable);
+ clearButton->setIcon(ICONS.CLEAR_LINEEDIT);
+ connect(clearButton, &QToolButton::clicked, [this, sequenceEdit]()
+ {
+ sequenceEdit->clear();
+ this->markModified();
+
+ });
+ itemIndex = ui->shortcutsTable->model()->index(itemRow, 2, categoryIndex);
+ ui->shortcutsTable->setIndexWidget(itemIndex, clearButton);
+
+ itemRow++;
+ }
+
+ category->setExpanded(true);
+}
+
+bool ConfigDialog::isPluginCategoryItem(QTreeWidgetItem *item) const
+{
+ return item->parent() && item->parent()->parent() && item->parent()->parent() == getPluginsCategoryItem();
+}
+
+void ConfigDialog::updateStylePreview()
+{
+ ui->previewWidget->parentWidget()->layout()->removeWidget(ui->previewWidget);
+ ui->previewTabs->currentWidget()->layout()->addWidget(ui->previewWidget);
+ ui->previewWidget->setEnabled(ui->previewTabs->currentIndex() == 0);
+
+ QStyle* previousStyle = previewStyle;
+ previewStyle = QStyleFactory::create(ui->activeStyleCombo->currentText());
+ if (!previewStyle)
+ {
+ qWarning() << "Could not create style:" << ui->activeStyleCombo->currentText();
+ return;
+ }
+
+ applyStyle(ui->activeStylePreviewGroup, previewStyle);
+
+ if (previousStyle)
+ delete previousStyle;
+}
+
+void ConfigDialog::apply()
+{
+ if (modifiedFlag)
+ save();
+
+ setModified(false);
+}
+
+void ConfigDialog::accept()
+{
+ apply();
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h
new file mode 100644
index 0000000..95e9f1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h
@@ -0,0 +1,141 @@
+#ifndef CONFIGDIALOG_H
+#define CONFIGDIALOG_H
+
+#include "config_builder.h"
+#include "datatype.h"
+#include "common/bihash.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class ConfigDialog;
+}
+
+class QListWidgetItem;
+class QTreeWidgetItem;
+class CustomConfigWidgetPlugin;
+class QSignalMapper;
+class Plugin;
+class PluginType;
+class QComboBox;
+class QToolButton;
+class QTreeWidget;
+class QListWidget;
+class QTableWidget;
+class ConfigMapper;
+class MultiEditorWidgetPlugin;
+class ConfigNotifiablePlugin;
+class UiConfiguredPlugin;
+
+class GUI_API_EXPORT ConfigDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit ConfigDialog(QWidget *parent = 0);
+ ~ConfigDialog();
+
+ void configureDataEditors(const QString& dataTypeString);
+
+ static QString getFilterString(QWidget* widget);
+ static QString getFilterString(QComboBox* widget);
+ static QString getFilterString(QTreeWidget* widget);
+ static QString getFilterString(QListWidget* widget);
+ static QString getFilterString(QTableWidget* widget);
+
+ private:
+ void init();
+ void load();
+ void initPageMap();
+ void initInternalCustomConfigWidgets();
+ void initFormatterPlugins();
+ void initPlugins();
+ void initPluginsPage();
+ bool initPluginPage(Plugin* plugin, bool skipConfigLoading);
+ void deinitPluginPage(Plugin* pluginName);
+ void initDataEditors();
+ void initShortcuts();
+ void initShortcuts(CfgCategory* cfgCategory);
+ void applyStyle(QWidget* widget, QStyle* style);
+ QTreeWidgetItem* getPluginsCategoryItem() const;
+ QTreeWidgetItem* getPluginsCategoryItem(PluginType* type) const;
+ QTreeWidgetItem* getPluginItem(Plugin* plugin) const;
+ QTreeWidgetItem* createPluginsTypeItem(const QString& widgetName, const QString& title) const;
+ QTreeWidgetItem* getItemByTitle(const QString& title) const;
+ void switchPageToPlugin(QTreeWidgetItem* item);
+ bool isPluginCategoryItem(QTreeWidgetItem *item) const;
+ void codeFormatterUnloaded();
+ void codeFormatterLoaded();
+ void updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem);
+ QString collectLoadedPlugins() const;
+ QHash<QWidget*,QTreeWidgetItem*> buildPageToCategoryItemMap() const;
+ QList<QTreeWidgetItem*> getAllCategoryItems() const;
+ QList<MultiEditorWidgetPlugin*> getDefaultEditorsForType(DataType::Enum dataType);
+ QList<MultiEditorWidgetPlugin*> updateCustomDataTypeEditors(const QStringList& editorsOrder);
+ QList<MultiEditorWidgetPlugin*> updateDefaultDataTypeEditors(DataType::Enum typeEnum);
+ void addDataTypeEditor(const QString& pluginName);
+ void addDataTypeEditor(MultiEditorWidgetPlugin* plugin);
+ void removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName);
+ void removeDataTypeEditor(int idx);
+ void transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem);
+ QStringList getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists = nullptr);
+ void setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames);
+ void addDataType(const QString& typeStr);
+ void rollbackPluginConfigs();
+ void commitPluginConfigs();
+
+ Ui::ConfigDialog *ui = nullptr;
+ QStyle* previewStyle = nullptr;
+ QHash<QString,QWidget*> nameToPage;
+ BiHash<QTreeWidgetItem*,QString> itemToPluginNameMap;
+ QHash<PluginType*,QTreeWidgetItem*> pluginTypeToItemMap;
+ QHash<Plugin*,QTreeWidgetItem*> pluginToItemMap;
+ QHash<QString,QComboBox*> formatterLangToPluginComboMap;
+ QHash<QString,QToolButton*> formatterLangToConfigButtonMap;
+ ConfigMapper* configMapper = nullptr;
+ QHash<UiConfiguredPlugin*,ConfigMapper*> pluginConfigMappers;
+ QAction* dataEditRenameAction = nullptr;
+ QAction* dataEditDeleteAction = nullptr;
+ bool updatingDataEditorItem = false;
+ bool modifiedFlag = false;
+ QList<ConfigNotifiablePlugin*> notifiablePlugins;
+
+ private slots:
+ void refreshFormattersPage();
+ void pageSwitched();
+ void updateDataTypeEditors();
+ void updateDataTypeListState();
+ void dataEditorItemEdited(QListWidgetItem* item);
+ void dataEditorAvailableChanged(QListWidgetItem* item);
+ void dataEditorTabsOrderChanged(int from, int to);
+ void addDataType();
+ void renameDataType();
+ void delDataType();
+ void dataTypesHelp();
+ void switchPage(QTreeWidgetItem* item);
+ void updateStylePreview();
+ void apply();
+ void save();
+ void storeSelectedFormatters();
+ void markModified();
+ void setModified(bool modified);
+ void updateModified();
+ void applyFilter(const QString& filter);
+ void updateActiveFormatterState();
+ void configureFormatter(const QString& pluginTitle);
+ void activeFormatterChanged();
+ void detailsClicked(const QString& pluginName);
+ void failedToLoadPlugin(const QString& pluginName);
+ void loadUnloadPlugin(QTreeWidgetItem* item, int column);
+ void pluginAboutToUnload(Plugin* plugin, PluginType* type);
+ void pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading = false);
+ void pluginUnloaded(const QString& pluginName, PluginType* type);
+ void updatePluginCategoriesVisibility();
+ void updateBuiltInPluginsVisibility();
+ void applyShortcutsFilter(const QString& filter);
+
+ public slots:
+ void accept();
+};
+
+#endif // CONFIGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui
new file mode 100644
index 0000000..82cc286
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui
@@ -0,0 +1,1923 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigDialog</class>
+ <widget class="QDialog" name="ConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>770</width>
+ <height>539</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configuration</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="1" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QWidget" name="mainWidget" native="true">
+ <property name="contextMenuPolicy">
+ <enum>Qt::PreventContextMenu</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="categoriesWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="categoriesFilter">
+ <property name="placeholderText">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="categoriesTree">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>150</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ <item>
+ <property name="text">
+ <string>General</string>
+ </property>
+ <property name="statusTip">
+ <string>generalPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_general.png</normaloff>:/icons/img/config_general.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Keyboard shortcuts</string>
+ </property>
+ <property name="statusTip">
+ <string>shortcutsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/keyboard.png</normaloff>:/icons/img/keyboard.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Look &amp; feel</string>
+ </property>
+ <property name="statusTip">
+ <string>lookAndFeelPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_look_and_feel.png</normaloff>:/icons/img/config_look_and_feel.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Style</string>
+ </property>
+ <property name="statusTip">
+ <string>stylePage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_style.png</normaloff>:/icons/img/config_style.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Fonts</string>
+ </property>
+ <property name="statusTip">
+ <string>fontsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_font.png</normaloff>:/icons/img/config_font.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Colors</string>
+ </property>
+ <property name="statusTip">
+ <string>colorsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_colors.png</normaloff>:/icons/img/config_colors.png</iconset>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>Plugins</string>
+ </property>
+ <property name="statusTip">
+ <string>pluginsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/plugin.png</normaloff>:/icons/img/plugin.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Code formatters</string>
+ </property>
+ <property name="statusTip">
+ <string>formatterPluginsPage</string>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>Data browsing</string>
+ </property>
+ <property name="statusTip">
+ <string>dataBrowsingPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/table.png</normaloff>:/icons/img/table.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Data editors</string>
+ </property>
+ <property name="statusTip">
+ <string>dataEditorsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_data_editors.png</normaloff>:/icons/img/config_data_editors.png</iconset>
+ </property>
+ </item>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>5</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <widget class="QWidget" name="dataBrowsingPage">
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <item>
+ <widget class="QGroupBox" name="dataBrowsingGroup">
+ <property name="title">
+ <string>Data browsing and editing</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="rowsPerPageLabel">
+ <property name="text">
+ <string>Number of data rows per page:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="rowsPerPageSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>99999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.NumberOfRowsPerPage</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataEditorsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QGroupBox" name="dataEditorsTypesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Data types</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <item>
+ <widget class="QToolBar" name="dataEditorsTypesToolbar"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="dataEditorsTypesList">
+ <property name="cfg" stdset="0">
+ <string>General.DataEditorsOrder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataEditorsRightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="dataEditorsAvailableGroup">
+ <property name="title">
+ <string>Available editors:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <item>
+ <widget class="QListWidget" name="dataEditorsAvailableList">
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataEditorsSelectedGroup">
+ <property name="title">
+ <string>Editors selected for this data type:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <item>
+ <widget class="QTabWidget" name="dataEditorsSelectedTabs">
+ <property name="movable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="generalPage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="schemaEditingGroup">
+ <property name="title">
+ <string>Schema editing</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_12">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="ddlHistorySizeSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Number of DDL changes kept in history.</string>
+ </property>
+ <property name="maximum">
+ <number>9999999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.DdlHistorySize</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ddlHistorySizeLabel">
+ <property name="text">
+ <string>DDL history size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QCheckBox" name="dontShowDdlPreview">
+ <property name="text">
+ <string>Don't show DDL preview dialog when commiting schema changes</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.DontShowDdlPreview</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="sqlQueriesGroup">
+ <property name="title">
+ <string>SQL queries</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="queryHistorySizeSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Number of queries kept in the history.</string>
+ </property>
+ <property name="maximum">
+ <number>999999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SqlHistorySize</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="queryHistorySizeLabel">
+ <property name="toolTip">
+ <string>Number of queries kept in the history.</string>
+ </property>
+ <property name="text">
+ <string>History size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="checkBox">
+ <property name="toolTip">
+ <string>&lt;p&gt;If there is more than one query in the SQL editor window, then (if this option is enabled) only a single query will be executed - the one under the keyboard insertion cursor. Otherwise all queries will be executed. You can always limit queries to be executed by selecting those queries before calling to execute.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Execute only the query under the cursor</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExecuteCurrentQueryOnly</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="updatesGroup">
+ <property name="title">
+ <string>Updates</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <item>
+ <widget class="QCheckBox" name="checkForUpdatesCheck">
+ <property name="text">
+ <string>Automatically check for updates at startup</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.CheckUpdatesOnStartup</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="shortcutsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="shortcutsFilterEdit">
+ <property name="placeholderText">
+ <string>Filter shortcuts by name or key combination</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="shortcutsTable">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>150</number>
+ </attribute>
+ <attribute name="headerMinimumSectionSize">
+ <number>16</number>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Action</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Key combination</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string/>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="lookAndFeelPage">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="dbListGroup">
+ <property name="title">
+ <string>Database list</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="sortColumns">
+ <property name="toolTip">
+ <string>If switched off, then columns will be sorted in the order they are typed in CREATE TABLE statement.</string>
+ </property>
+ <property name="text">
+ <string>Sort table columns alphabetically</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SortColumns</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="expandTablesCheck">
+ <property name="text">
+ <string>Expand tables node when connected to a database</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExpandTables</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QGroupBox" name="addLabelsGroup">
+ <property name="toolTip">
+ <string>&lt;p&gt;Additional labels are those displayed next to the names on the databases list (they are blue, unless configured otherwise). Enabling this option will result in labels for databases, invalid databases and aggregated nodes (column group, index group, trigger group). For more labels see options below.&lt;p&gt;</string>
+ </property>
+ <property name="title">
+ <string>Display additional labels on the list</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowDbTreeLabels</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <item>
+ <widget class="QCheckBox" name="regularTableLabelsCheck">
+ <property name="toolTip">
+ <string>For regular tables labels will show number of columns, indexes and triggers for each of tables.</string>
+ </property>
+ <property name="text">
+ <string>Display labels for regular tables</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowRegularTableLabels</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="virtTableLabelsCheck">
+ <property name="toolTip">
+ <string>Virtual tables will be marked with a 'virtual' label.</string>
+ </property>
+ <property name="text">
+ <string>Display labels for virtual tables</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowVirtualTableLabels</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="expandViewsCheck">
+ <property name="text">
+ <string>Expand views node when connected to a database</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExpandViews</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="sortObjects">
+ <property name="toolTip">
+ <string>If this option is switched off, then objects will be sorted in order they appear in sqlite_master table (which is in order they were created)</string>
+ </property>
+ <property name="text">
+ <string>Sort objects (tables, indexes, triggers and views) alphabetically</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SortObjects</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="dispSysTableCheck">
+ <property name="text">
+ <string>Display system tables and indexes on the list</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowSystemObjects</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="tableWinGroup">
+ <property name="title">
+ <string>Table windows</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <item>
+ <widget class="QCheckBox" name="openTablesOnDataCheck">
+ <property name="toolTip">
+ <string>When enabled, Table Windows will show up with the data tab, instead of the structure tab.</string>
+ </property>
+ <property name="text">
+ <string>Open Table Windows with the data tab for start</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.OpenTablesOnData</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="viewWinGroup">
+ <property name="title">
+ <string>View windows</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <item>
+ <widget class="QCheckBox" name="openViewsOnDataCheck">
+ <property name="toolTip">
+ <string>When enabled, View Windows will show up with the data tab, instead of the structure tab.</string>
+ </property>
+ <property name="text">
+ <string>Open View Windows with the data tab for start</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.OpenViewsOnData</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pluginsPage">
+ <layout class="QGridLayout" name="gridLayout_18">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="QTreeWidget" name="pluginsList">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <property name="columnCount">
+ <number>2</number>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string notr="true">2</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QWidget" name="hideBuiltInPluginsWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="hideBuiltInPluginsCheck">
+ <property name="text">
+ <string>Hide built-in plugins</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="stylePage">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="activeStyleWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="activeStyleLabel">
+ <property name="text">
+ <string>Current style:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="activeStyleCombo">
+ <property name="cfg" stdset="0">
+ <string>General.Style</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="activeStylePreviewGroup">
+ <property name="title">
+ <string>Preview</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QTabWidget" name="previewTabs">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="previewTab1">
+ <attribute name="title">
+ <string>Enabled</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="previewWidget" native="true">
+ <layout class="QGridLayout" name="gridLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="2" column="0">
+ <widget class="QProgressBar" name="previewProgressBar">
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="previewCheckBox">
+ <property name="text">
+ <string>CheckBox</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" rowspan="4">
+ <widget class="QSlider" name="previewVerticalSlider">
+ <property name="value">
+ <number>50</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" rowspan="4">
+ <widget class="QScrollBar" name="previewVerticalScrollBar">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" colspan="2">
+ <widget class="QPushButton" name="previewPushButton">
+ <property name="text">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="3">
+ <widget class="QSpinBox" name="previewSpinBox"/>
+ </item>
+ <item row="2" column="3" colspan="5">
+ <widget class="QProgressBar" name="progressBar">
+ <property name="maximum">
+ <number>0</number>
+ </property>
+ <property name="value">
+ <number>0</number>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QTreeWidget" name="previewTreeWidget">
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <item>
+ <property name="text">
+ <string>123</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>11111</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>22222</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>33333</string>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>456</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>44444</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>55555</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>66666</string>
+ </property>
+ </item>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QToolButton" name="previewToolButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="previewLineEdit"/>
+ </item>
+ <item row="0" column="6">
+ <widget class="QRadioButton" name="previewRadioButton">
+ <property name="text">
+ <string>RadioButton</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="previewComboBox">
+ <item>
+ <property name="text">
+ <string>ABC</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>XYZ</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="3" colspan="5">
+ <widget class="QTextEdit" name="previewTextEdit">
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;Abcdefgh&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="previewTab2">
+ <attribute name="title">
+ <string>Disabled</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_7"/>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="formatterPluginsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="formatterPluginsTree">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Language</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Active formatter plugin</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Configuration</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="fontsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QScrollArea" name="fontsScrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="fontsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>258</width>
+ <height>286</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QGroupBox" name="sqlEditorFontGroup">
+ <property name="title">
+ <string>SQL editor font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="FontEdit" name="sqlEditorFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.SqlEditor</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeFontGroup">
+ <property name="title">
+ <string>Database list font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <item>
+ <widget class="FontEdit" name="dbTreeFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DbTree</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeLabelGroup">
+ <property name="title">
+ <string>Database list additional label font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="FontEdit" name="dbTreeLabelFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DbTreeLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataViewFontGroup">
+ <property name="title">
+ <string>Data view font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <widget class="FontEdit" name="dataViewFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DataView</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="statusFieldFontGroup">
+ <property name="title">
+ <string>Status field font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <item>
+ <widget class="FontEdit" name="statusFieldfont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.StatusField</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="colorsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="colorsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>307</width>
+ <height>666</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QGroupBox" name="sqlEditorColorsGroup">
+ <property name="title">
+ <string>SQL editor colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="14" column="0">
+ <widget class="QLabel" name="sqlEditorCurrLineBgLabel">
+ <property name="text">
+ <string>Current line background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="sqlEditorStringFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL strings are enclosed with single quote characters.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>String foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="ColorButton" name="sqlEditorKeywordFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorKeywordFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="ColorButton" name="sqlEditorStringFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorStringFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="1">
+ <widget class="ColorButton" name="sqlEditorCommentFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorCommentFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="ColorButton" name="sqlEditorRegularFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorForeground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="14" column="1">
+ <widget class="ColorButton" name="sqlEditorCurrLineBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorCurrentLineBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="16" column="1">
+ <widget class="ColorButton" name="sqlEditorLineNumAreaBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorLineNumAreaBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="0">
+ <widget class="QLabel" name="sqlEditorBindParamFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Bind parameters are placeholders for values yet to be provided by the user. They have one of the forms:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;:param_name&lt;/li&gt;&lt;li&gt;$param_name&lt;/li&gt;&lt;li&gt;@param_name&lt;/li&gt;&lt;li&gt;?&lt;/li&gt;&lt;/ul&gt;</string>
+ </property>
+ <property name="text">
+ <string>Bind parameter foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="15" column="1">
+ <widget class="ColorButton" name="sqlEditorParBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorParenthesisBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="15" column="0">
+ <widget class="QLabel" name="sqlEditorParBgLabel">
+ <property name="text">
+ <string>Highlighted parenthesis background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="11" column="0">
+ <widget class="QLabel" name="sqlEditorBlobFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;BLOB values are binary values represented as hexadecimal numbers, like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;X'12B4'&lt;/li&gt;&lt;li&gt;x'46A2F4'&lt;/li&gt;&lt;/ul&gt;</string>
+ </property>
+ <property name="text">
+ <string>BLOB value foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="sqlEditorRegularFgLabel">
+ <property name="text">
+ <string>Regular foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="16" column="0">
+ <widget class="QLabel" name="sqlEditorLineNumAreaBgLabel">
+ <property name="text">
+ <string>Line numbers area background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="11" column="1">
+ <widget class="ColorButton" name="sqlEditorBlobFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorBlobFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="sqlEditorKeywordFgLabel">
+ <property name="text">
+ <string>Keyword foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="0">
+ <widget class="QLabel" name="sqlEditorNumberFgLabel">
+ <property name="text">
+ <string>Number foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="sqlEditorCommentFgLabel">
+ <property name="text">
+ <string>Comment foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="ColorButton" name="sqlEditorNumberFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorNumberFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="1">
+ <widget class="ColorButton" name="sqlEditorBindParamFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorBindParamFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="0">
+ <widget class="QLabel" name="sqlEditorValidObjectsLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Valid objects are name of tables, indexes, triggers, or views that exist in the SQLite database.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Valid objects foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="1">
+ <widget class="ColorButton" name="sqlEditorValidObjectsButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorValidObject</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataViewGroup">
+ <property name="title">
+ <string>Data view colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="0" column="0">
+ <widget class="QLabel" name="dataViewUncommitedLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Any data changes will be outlined with this color, until they're commited to the database.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Uncommited data outline color</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="dataViewUncommitedButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataUncommited</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="dataViewErrorLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;In case of error while commiting data changes, the problematic cell will be outlined with this color.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Commit error outline color</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="ColorButton" name="dataViewErrorButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataUncommitedError</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="dataViewNullFgLabel">
+ <property name="text">
+ <string>NULL value foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="ColorButton" name="dataViewNullFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataNullFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="dataViewDeletedRowBgLabel">
+ <property name="text">
+ <string>Deleted row background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="ColorButton" name="dataViewDeletedRowBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataDeletedBg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeGroup">
+ <property name="title">
+ <string>Database list colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbTreeLabelsLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Additional labels are those which tell you SQLite version, number of objects deeper in the tree, etc.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Additional labels foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="dbTreeLabelsButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DbTreeLabelsFg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="statusFieldGroup">
+ <property name="title">
+ <string>Status field colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="0" column="0">
+ <widget class="QLabel" name="statusFieldInfoLabel">
+ <property name="text">
+ <string>Information message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="statusFieldInfoButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldInfoFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="statusFieldWarnLabel">
+ <property name="text">
+ <string>Warning message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="ColorButton" name="statusFieldWarnButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldWarnFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="statusFieldErrorLabel">
+ <property name="text">
+ <string>Error message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="ColorButton" name="statusFieldErrorButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldErrorFg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ <widget class="QWidget" name="layoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>100</width>
+ <height>30</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_25"/>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorButton</class>
+ <extends>QPushButton</extends>
+ <header>common/colorbutton.h</header>
+ </customwidget>
+ <customwidget>
+ <class>FontEdit</class>
+ <extends>QWidget</extends>
+ <header>common/fontedit.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>categoriesFilter</tabstop>
+ <tabstop>categoriesTree</tabstop>
+ <tabstop>pluginsList</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>expandViewsCheck</tabstop>
+ <tabstop>sortObjects</tabstop>
+ <tabstop>sortColumns</tabstop>
+ <tabstop>ddlHistorySizeSpin</tabstop>
+ <tabstop>dontShowDdlPreview</tabstop>
+ <tabstop>queryHistorySizeSpin</tabstop>
+ <tabstop>checkBox</tabstop>
+ <tabstop>expandTablesCheck</tabstop>
+ <tabstop>activeStyleCombo</tabstop>
+ <tabstop>previewTabs</tabstop>
+ <tabstop>previewCheckBox</tabstop>
+ <tabstop>previewVerticalSlider</tabstop>
+ <tabstop>previewPushButton</tabstop>
+ <tabstop>previewSpinBox</tabstop>
+ <tabstop>previewTreeWidget</tabstop>
+ <tabstop>previewToolButton</tabstop>
+ <tabstop>previewLineEdit</tabstop>
+ <tabstop>previewRadioButton</tabstop>
+ <tabstop>previewComboBox</tabstop>
+ <tabstop>previewTextEdit</tabstop>
+ <tabstop>fontsScrollArea</tabstop>
+ <tabstop>scrollArea</tabstop>
+ <tabstop>sqlEditorKeywordFgButton</tabstop>
+ <tabstop>sqlEditorStringFgButton</tabstop>
+ <tabstop>sqlEditorCommentFgButton</tabstop>
+ <tabstop>sqlEditorRegularFgButton</tabstop>
+ <tabstop>sqlEditorCurrLineBgButton</tabstop>
+ <tabstop>sqlEditorLineNumAreaBgButton</tabstop>
+ <tabstop>sqlEditorParBgButton</tabstop>
+ <tabstop>sqlEditorBlobFgButton</tabstop>
+ <tabstop>sqlEditorNumberFgButton</tabstop>
+ <tabstop>sqlEditorBindParamFgButton</tabstop>
+ <tabstop>sqlEditorValidObjectsButton</tabstop>
+ <tabstop>dataViewUncommitedButton</tabstop>
+ <tabstop>dataViewErrorButton</tabstop>
+ <tabstop>dataViewNullFgButton</tabstop>
+ <tabstop>dataViewDeletedRowBgButton</tabstop>
+ <tabstop>dbTreeLabelsButton</tabstop>
+ <tabstop>statusFieldInfoButton</tabstop>
+ <tabstop>statusFieldWarnButton</tabstop>
+ <tabstop>statusFieldErrorButton</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp
new file mode 100644
index 0000000..0094ad0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp
@@ -0,0 +1,213 @@
+#include "constraintdialog.h"
+#include "ui_constraintdialog.h"
+#include "iconmanager.h"
+#include "constraints/constraintpanel.h"
+#include <QDebug>
+#include <QPushButton>
+
+ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::ConstraintDialog),
+ mode(mode),
+ db(db)
+{
+ ui->setupUi(this);
+ type = TABLE;
+ constrStatement = constraint;
+ this->createTable = createTable;
+ init();
+}
+
+ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::ConstraintDialog),
+ mode(mode),
+ db(db)
+{
+ ui->setupUi(this);
+ type = COLUMN;
+ constrStatement = constraint;
+ this->columnStmt = column;
+ createTable = dynamic_cast<SqliteCreateTable*>(column->parent());
+ init();
+}
+
+ConstraintDialog::~ConstraintDialog()
+{
+ delete ui;
+}
+
+SqliteStatement* ConstraintDialog::getConstraint()
+{
+ return constrStatement;
+}
+
+void ConstraintDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ConstraintDialog::init()
+{
+ switch (mode)
+ {
+ case ConstraintDialog::NEW:
+ setWindowTitle(tr("New constraint", "constraint dialog"));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create", "constraint dialog"));
+ break;
+ case ConstraintDialog::EDIT:
+ setWindowTitle(tr("Edit constraint", "dialog window"));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Apply", "constraint dialog"));
+ break;
+ }
+
+ connect(this, SIGNAL(accepted()), this, SLOT(storeConfiguration()));
+
+ // Panel object
+ currentPanel = createConstraintPanel();
+ if (!currentPanel)
+ {
+ qCritical() << "The constraint panel was not constructed. Probably the constraint type was invalid.";
+ return;
+ }
+
+ currentPanel->setDb(db);
+ currentPanel->setConstraint(constrStatement);
+
+ connect(currentPanel, SIGNAL(updateValidation()), this, SLOT(validate()));
+ validate();
+
+ // Put everything in place
+ updateDefinitionHeader();
+ ui->definitionWidget->layout()->addWidget(currentPanel);
+
+ adjustSize();
+ currentPanel->setFocus();
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ return getSelectedConstraint(dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement));
+ case ConstraintDialog::COLUMN:
+ return getSelectedConstraint(dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement));
+ }
+ return UNKNOWN;
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return PK;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return FK;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ break;
+ }
+ return UNKNOWN;
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return PK;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return NOTNULL;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return UNIQUE;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return CHECK;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return DEFAULT;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return COLLATE;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return FK;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return UNKNOWN;
+}
+
+ConstraintPanel* ConstraintDialog::createConstraintPanel()
+{
+ if (!constrStatement)
+ return nullptr;
+
+ if (type == COLUMN)
+ return ConstraintPanel::produce(dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement));
+ else
+ return ConstraintPanel::produce(dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement));
+}
+
+void ConstraintDialog::updateDefinitionHeader()
+{
+ switch (getSelectedConstraint())
+ {
+ case ConstraintDialog::UNKNOWN:
+ return;
+ case ConstraintDialog::PK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_PRIMARY_KEY);
+ ui->titleLabel->setText(tr("Primary key", "table constraints"));
+ break;
+ case ConstraintDialog::FK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_FOREIGN_KEY);
+ ui->titleLabel->setText(tr("Foreign key", "table constraints"));
+ break;
+ case ConstraintDialog::UNIQUE:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_UNIQUE);
+ ui->titleLabel->setText(tr("Unique", "table constraints"));
+ break;
+ case ConstraintDialog::NOTNULL:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_NOT_NULL);
+ ui->titleLabel->setText(tr("Not NULL", "table constraints"));
+ break;
+ case ConstraintDialog::CHECK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_CHECK);
+ ui->titleLabel->setText(tr("Check", "table constraints"));
+ break;
+ case ConstraintDialog::COLLATE:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_COLLATION);
+ ui->titleLabel->setText(tr("Collate", "table constraints"));
+ break;
+ case ConstraintDialog::DEFAULT:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_DEFAULT);
+ ui->titleLabel->setText(tr("Default", "table constraints"));
+ break;
+ }
+}
+
+void ConstraintDialog::validate()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(currentPanel->validate());
+}
+
+void ConstraintDialog::storeConfiguration()
+{
+ if (!currentPanel)
+ {
+ qWarning() << "Called to store constraint configuration, but there's no current panel.";
+ return;
+ }
+
+ currentPanel->storeDefinition();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h
new file mode 100644
index 0000000..fe24c0f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h
@@ -0,0 +1,79 @@
+#ifndef CONSTRAINTDIALOG_H
+#define CONSTRAINTDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+namespace Ui {
+ class ConstraintDialog;
+}
+
+class ConstraintPanel;
+
+class GUI_API_EXPORT ConstraintDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum Mode
+ {
+ NEW,
+ EDIT
+ };
+
+ enum Constraint
+ {
+ PK,
+ FK,
+ UNIQUE,
+ NOTNULL,
+ CHECK,
+ COLLATE,
+ DEFAULT,
+ UNKNOWN
+ };
+
+ enum Type
+ {
+ TABLE,
+ COLUMN
+ };
+
+ explicit ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db,
+ QWidget *parent = 0);
+ explicit ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db,
+ QWidget *parent = 0);
+ ~ConstraintDialog();
+
+ SqliteStatement* getConstraint();
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ Constraint getSelectedConstraint();
+ Constraint getSelectedConstraint(SqliteCreateTable::Constraint* constraint);
+ Constraint getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ ConstraintPanel* createConstraintPanel();
+ void updateDefinitionHeader();
+
+ Ui::ConstraintDialog *ui = nullptr;
+ Type type;
+ Mode mode;
+ Db* db = nullptr;
+ SqliteStatement* constrStatement = nullptr;
+ QPointer<SqliteCreateTable> createTable;
+ QPointer<SqliteCreateTable::Column> columnStmt;
+ QHash<int,QWidget> panels;
+ ConstraintPanel* currentPanel = nullptr;
+
+ private slots:
+ void validate();
+ void storeConfiguration();
+};
+
+#endif // CONSTRAINTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui
new file mode 100644
index 0000000..7df34d8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConstraintDialog</class>
+ <widget class="QDialog" name="ConstraintDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="titleWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="titleIcon">
+ <property name="maximumSize">
+ <size>
+ <width>18</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="titleLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="definitionWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConstraintDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConstraintDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp
new file mode 100644
index 0000000..c94546e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp
@@ -0,0 +1,219 @@
+#include "dbconverterdialog.h"
+#include "ui_dbconverterdialog.h"
+#include "common/global.h"
+#include "dblistmodel.h"
+#include "db/db.h"
+#include "common/utils_sql.h"
+#include "dbversionconverter.h"
+#include "services/dbmanager.h"
+#include "iconmanager.h"
+#include "uiutils.h"
+#include "versionconvertsummarydialog.h"
+#include "mainwindow.h"
+#include "errorsconfirmdialog.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "db/sqlquery.h"
+#include "services/notifymanager.h"
+#include "common/widgetcover.h"
+#include <QDebug>
+#include <QFileInfo>
+#include <QPushButton>
+
+DbConverterDialog::DbConverterDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DbConverterDialog)
+{
+ init();
+}
+
+DbConverterDialog::~DbConverterDialog()
+{
+ delete ui;
+ safe_delete(converter);
+}
+
+void DbConverterDialog::setDb(Db* db)
+{
+ ui->srcDbCombo->setCurrentText(db->getName());
+ srcDb = db;
+ srcDbChanged();
+}
+
+void DbConverterDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ setWindowTitle(tr("Convert database"));
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->setVisible(false);
+ widgetCover->initWithInterruptContainer();
+
+ ui->trgFileButton->setIcon(ICONS.OPEN_FILE);
+
+ converter = new DbVersionConverter();
+
+ dbListModel = new DbListModel(this);
+ ui->srcDbCombo->setModel(dbListModel);
+
+ connect(ui->srcDbCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(srcDbChanged(int)));
+ connect(ui->trgVersionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+ connect(ui->trgFileEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState()));
+ connect(ui->trgNameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState()));
+ connect(converter, SIGNAL(conversionFailed(QString)), this, SLOT(processingFailed(QString)));
+ connect(converter, SIGNAL(conversionSuccessful()), this, SLOT(processingSuccessful()));
+ connect(converter, SIGNAL(conversionAborted()), this, SLOT(processingAborted()));
+ connect(widgetCover, SIGNAL(cancelClicked()), converter, SLOT(interrupt()));
+}
+
+void DbConverterDialog::srcDbChanged()
+{
+ dontUpdateState = true;
+ ui->srcDbVersionCombo->clear();
+ ui->trgVersionCombo->clear();
+ if (srcDb)
+ {
+ // Source version
+ QList<Dialect> dialects = converter->getSupportedVersions();
+ QStringList versionNames = converter->getSupportedVersionNames();
+ Dialect dialect = srcDb->getDialect();
+ int idx = dialects.indexOf(dialect);
+ QString type = versionNames[idx];
+ ui->srcDbVersionCombo->addItem(type);
+ ui->srcDbVersionCombo->setCurrentText(type);
+
+ // Target version
+ QString oldTrgVersion = ui->trgVersionCombo->currentText();
+ versionNames.removeAt(idx);
+ ui->trgVersionCombo->addItems(versionNames);
+ if (versionNames.contains(oldTrgVersion))
+ ui->trgVersionCombo->setCurrentText(oldTrgVersion);
+ else if (versionNames.size() > 0)
+ ui->trgVersionCombo->setCurrentIndex(0);
+
+ // File
+ QString trgFile = srcDb->getPath() + "_new";
+ int i = 0;
+ while (QFileInfo(trgFile).exists())
+ {
+ trgFile = srcDb->getPath() + "_new" + QString::number(i++);
+ }
+
+ ui->trgFileEdit->setText(trgFile);
+
+ // Name
+ QString generatedName = generateUniqueName(srcDb->getName() + "_new", DBLIST->getDbNames());
+ ui->trgNameEdit->setText(generatedName);
+ }
+ else
+ {
+ ui->srcDbVersionCombo->setCurrentText("");
+ ui->trgFileEdit->setText("");
+ ui->trgVersionCombo->setCurrentText("");
+ ui->trgNameEdit->setText("");
+ }
+ dontUpdateState = false;
+ updateState();
+}
+
+bool DbConverterDialog::validate()
+{
+ bool srcDbOk = (srcDb != nullptr);
+ setValidState(ui->srcDbCombo, srcDbOk, tr("Select source database"));
+
+ QString dstDbPath = ui->trgFileEdit->text();
+ QFileInfo dstDbFi(dstDbPath);
+ bool dstDbOk = (!dstDbFi.exists() || dstDbFi.isWritable()) && dstDbFi != QFileInfo(srcDb->getPath());
+ bool dstExists = dstDbFi.exists();
+ setValidState(ui->trgFileEdit, dstDbOk, tr("Enter valid and writable file path."));
+ if (dstExists && dstDbOk)
+ setValidStateInfo(ui->trgFileEdit, tr("Entered file exists and will be overwritten."));
+
+ QString name = ui->trgNameEdit->text();
+ bool nameOk = !name.isEmpty() && !DBLIST->getDbNames().contains(name);
+ setValidState(ui->trgNameEdit, nameOk, tr("Enter a not empty, unique name (as in the list of databases on the left)."));
+
+ bool dstDialectOk = ui->trgVersionCombo->currentIndex() > -1;
+ QString msg;
+ if (!dstDialectOk && ui->trgVersionCombo->count() == 0)
+ msg = tr("No valid target dialect available. Conversion not possible.");
+ else
+ msg = tr("Select valid target dialect.");
+
+ setValidState(ui->trgVersionCombo, dstDialectOk, msg);
+
+ return (srcDbOk && nameOk && dstDbOk && dstDialectOk);
+}
+
+void DbConverterDialog::accept()
+{
+ if (!validate())
+ return;
+
+ QStringList versionNames = converter->getSupportedVersionNames();
+ QList<Dialect> dialects = converter->getSupportedVersions();
+ QString trgDialectName = ui->trgVersionCombo->currentText();
+ int idx = versionNames.indexOf(trgDialectName);
+ if (idx == -1)
+ {
+ qCritical() << "Could not find target dialect on list of supported dialects in DbConverterDialog::accept()";
+ return;
+ }
+
+ Dialect srcDialect = srcDb->getDialect();
+ Dialect trgDialect = dialects[idx];
+ QString trgFile = ui->trgFileEdit->text();
+ QString trgName = ui->trgNameEdit->text();
+ widgetCover->show();
+ converter->convert(srcDialect, trgDialect, srcDb, trgFile, trgName, &DbConverterDialog::confirmConversion, &DbConverterDialog::confirmConversionErrors);
+}
+
+void DbConverterDialog::srcDbChanged(int index)
+{
+ srcDb = dbListModel->getDb(index);
+ srcDbChanged();
+}
+
+void DbConverterDialog::updateState()
+{
+ if (dontUpdateState)
+ return;
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
+}
+
+void DbConverterDialog::processingFailed(const QString& errorMessage)
+{
+ widgetCover->hide();
+ notifyError(errorMessage);
+}
+
+void DbConverterDialog::processingSuccessful()
+{
+ notifyInfo(tr("Database %1 has been successfully converted and now is available under new name: %2").arg(srcDb->getName(), ui->trgNameEdit->text()));
+ QDialog::accept();
+}
+
+void DbConverterDialog::processingAborted()
+{
+ widgetCover->hide();
+}
+
+bool DbConverterDialog::confirmConversion(const QList<QPair<QString, QString> >& diffs)
+{
+ VersionConvertSummaryDialog dialog(MAINWINDOW);
+ dialog.setWindowTitle(tr("SQL statements conversion"));
+ dialog.setSides(diffs);
+ return dialog.exec() == QDialog::Accepted;
+}
+
+bool DbConverterDialog::confirmConversionErrors(const QSet<QString>& errors)
+{
+ ErrorsConfirmDialog dialog(MAINWINDOW);
+ dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:"));
+ dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?"));
+ dialog.setErrors(errors);
+ return dialog.exec() == QDialog::Accepted;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h
new file mode 100644
index 0000000..4267a1b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h
@@ -0,0 +1,52 @@
+#ifndef DBCONVERTERDIALOG_H
+#define DBCONVERTERDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class DbListModel;
+class Db;
+class DbVersionConverter;
+class WidgetCover;
+
+namespace Ui {
+ class DbConverterDialog;
+}
+
+class GUI_API_EXPORT DbConverterDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit DbConverterDialog(QWidget *parent = 0);
+ ~DbConverterDialog();
+
+ void setDb(Db* db);
+
+ private:
+ void init();
+ void srcDbChanged();
+ bool validate();
+
+ static bool confirmConversion(const QList<QPair<QString, QString> >& diffs);
+ static bool confirmConversionErrors(const QSet<QString>& errors);
+
+ Ui::DbConverterDialog *ui = nullptr;
+ DbListModel* dbListModel = nullptr;
+ Db* srcDb = nullptr;
+ DbVersionConverter* converter = nullptr;
+ bool dontUpdateState = false;
+ WidgetCover* widgetCover = nullptr;
+
+ public slots:
+ void accept();
+
+ private slots:
+ void srcDbChanged(int index);
+ void updateState();
+ void processingFailed(const QString& errorMessage);
+ void processingSuccessful();
+ void processingAborted();
+};
+
+#endif // DBCONVERTERDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui
new file mode 100644
index 0000000..d328e99
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbConverterDialog</class>
+ <widget class="QDialog" name="DbConverterDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>251</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="srcGroup">
+ <property name="title">
+ <string>Source database</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QComboBox" name="srcDbCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="srcDbVersionLabel">
+ <property name="text">
+ <string>Source database version:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="srcDbVersionCombo">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="trgGroup">
+ <property name="title">
+ <string>Target database</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="trgVersionLabel">
+ <property name="text">
+ <string>Target version:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="trgFileEdit">
+ <property name="toolTip">
+ <string>This is the file that will be created as a result of the conversion.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="trgFileLabel">
+ <property name="text">
+ <string>Target file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="trgNameLabel">
+ <property name="text">
+ <string>Name of the new database:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QToolButton" name="trgFileButton">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="trgVersionCombo"/>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="trgNameEdit">
+ <property name="toolTip">
+ <string>This is the name that the converted database will be added to SQLiteStudio with.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DbConverterDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DbConverterDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp
new file mode 100644
index 0000000..c9a7f28
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp
@@ -0,0 +1,590 @@
+#include "dbdialog.h"
+#include "ui_dbdialog.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "uiutils.h"
+#include "common/utils.h"
+#include "uiconfig.h"
+#include "services/dbmanager.h"
+#include "common/global.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include <QDateTimeEdit>
+#include <QSpinBox>
+#include <QDebug>
+#include <QPushButton>
+#include <QFileDialog>
+#include <QComboBox>
+
+DbDialog::DbDialog(Mode mode, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DbDialog),
+ mode(mode)
+{
+ init();
+}
+
+DbDialog::~DbDialog()
+{
+ delete ui;
+}
+
+void DbDialog::setDb(Db* db)
+{
+ this->db = db;
+}
+
+void DbDialog::setPermanent(bool perm)
+{
+ ui->permamentCheckBox->setChecked(perm);
+}
+
+QString DbDialog::getPath()
+{
+ return ui->fileEdit->text();
+}
+
+void DbDialog::setPath(const QString& path)
+{
+ ui->fileEdit->setText(path);
+}
+
+QString DbDialog::getName()
+{
+ return ui->nameEdit->text();
+}
+
+Db* DbDialog::getDb()
+{
+ if (ui->typeCombo->currentIndex() < 0)
+ return nullptr;
+
+ Db* testDb = nullptr;
+ QHash<QString, QVariant> options = collectOptions();
+ QString path = ui->fileEdit->text();
+ foreach (DbPlugin* plugin, dbPlugins)
+ {
+ if (options.contains(DB_PLUGIN) && options[DB_PLUGIN].toString() != plugin->getName())
+ continue;
+
+ testDb = plugin->getInstance("", path, options);
+ if (testDb)
+ return testDb;
+ }
+ return testDb;
+}
+
+bool DbDialog::isPermanent()
+{
+ return ui->permamentCheckBox->isChecked();
+}
+
+void DbDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DbDialog::showEvent(QShowEvent *e)
+{
+ if (db)
+ {
+ int idx = ui->typeCombo->findText(db->getTypeLabel());
+ ui->typeCombo->setCurrentIndex(idx);
+ ui->typeCombo->setEnabled(false); // converting to other type is in separate dialog, it's different feature
+
+ ui->generateCheckBox->setChecked(false);
+ ui->fileEdit->setText(db->getPath());
+ ui->nameEdit->setText(db->getName());
+ }
+ else if (ui->typeCombo->count() > 0)
+ {
+ int idx = ui->typeCombo->findText("SQLite3"); // we should have SQLite3 plugin
+ if (idx > -1)
+ ui->typeCombo->setCurrentIndex(idx);
+ else
+ ui->typeCombo->setCurrentIndex(0);
+ }
+
+ existingDatabaseNames = DBLIST->getDbNames();
+ if (mode == EDIT)
+ existingDatabaseNames.removeOne(db->getName());
+
+ updateOptions();
+ updateState();
+
+ QDialog::showEvent(e);
+}
+
+void DbDialog::init()
+{
+ ui->setupUi(this);
+
+ ui->browseButton->setIcon(ICONS.DATABASE_FILE);
+ dbPlugins = PLUGINS->getLoadedPlugins<DbPlugin>();
+ foreach (DbPlugin* dbPlugin, dbPlugins)
+ {
+ ui->typeCombo->addItem(dbPlugin->getLabel());
+ }
+
+ ui->browseButton->setVisible(true);
+ ui->testConnIcon->setVisible(false);
+
+ connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(fileChanged(QString)));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameModified(QString)));
+ connect(ui->generateCheckBox, SIGNAL(toggled(bool)), this, SLOT(generateNameSwitched(bool)));
+ connect(ui->browseButton, SIGNAL(clicked()), this, SLOT(browseClicked()));
+ connect(ui->testConnButton, SIGNAL(clicked()), this, SLOT(testConnectionClicked()));
+ connect(ui->typeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(dbTypeChanged(int)));
+
+ generateNameSwitched(true);
+}
+
+void DbDialog::updateOptions()
+{
+ setUpdatesEnabled(false);
+
+ // Remove olds
+ foreach (QWidget* w, optionWidgets)
+ {
+ ui->gridLayout->removeWidget(w);
+ delete w;
+ }
+ adjustSize();
+
+ optionWidgets.clear();
+ optionKeyToWidget.clear();
+ optionKeyToType.clear();
+ helperToKey.clear();
+
+ lastWidgetInTabOrder = ui->permamentCheckBox;
+
+ // Retrieve new list
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ int idx = ui->typeCombo->currentIndex();
+ if (idx > -1 )
+ {
+ plugin = dbPlugins[idx];
+ QList<DbPluginOption> optList = plugin->getOptionsList();
+ if (optList.size() > 0)
+ {
+ // Add new options
+ int row = ADDITIONAL_ROWS_BEGIN_INDEX;
+ foreach (DbPluginOption opt, optList)
+ addOption(opt, row++);
+ }
+ }
+ }
+
+ adjustSize();
+ setUpdatesEnabled(true);
+}
+
+void DbDialog::addOption(const DbPluginOption& option, int row)
+{
+ QLabel* label = new QLabel(option.label, this);
+ QWidget* editor = nullptr;
+ QWidget* editorHelper = nullptr; // TODO, based on plugins for Url handlers
+
+ editor = getEditor(option, editorHelper);
+ Q_ASSERT(editor != nullptr);
+
+ if (!option.toolTip.isNull())
+ editor->setToolTip(option.toolTip);
+
+ optionWidgets << label << editor;
+
+ optionKeyToWidget[option.key] = editor;
+ optionKeyToType[option.key] = option.type;
+ ui->gridLayout->addWidget(label, row, 0);
+ ui->gridLayout->addWidget(editor, row, 1);
+
+ setTabOrder(lastWidgetInTabOrder, editor);
+ lastWidgetInTabOrder = editor;
+
+ if (editorHelper)
+ {
+ ui->gridLayout->addWidget(editorHelper, row, 2);
+ optionWidgets << editorHelper;
+ helperToKey[editorHelper] = option.key;
+
+ setTabOrder(lastWidgetInTabOrder, editorHelper);
+ lastWidgetInTabOrder = editorHelper;
+ }
+
+ if (db && db->getConnectionOptions().contains(option.key))
+ setValueFor(option.type, editor, db->getConnectionOptions()[option.key]);
+}
+
+QWidget *DbDialog::getEditor(const DbPluginOption& opt, QWidget*& editorHelper)
+{
+ QWidget* editor = nullptr;
+ QLineEdit* le = nullptr;
+ editorHelper = nullptr;
+ switch (opt.type)
+ {
+ case DbPluginOption::STRING:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::PASSWORD:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ le->setEchoMode(QLineEdit::Password);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::CHOICE:
+ {
+ QComboBox* cb = new QComboBox(this);
+ editor = cb;
+ cb->setEditable(!opt.choiceReadOnly);
+ cb->addItems(opt.choiceValues);
+ cb->setCurrentText(opt.defaultValue.toString());
+ connect(cb, SIGNAL(currentIndexChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::INT:
+ {
+ QSpinBox* sb = new QSpinBox(this);
+ editor = sb;
+ if (!opt.minValue.isNull())
+ sb->setMinimum(opt.minValue.toInt());
+
+ if (!opt.maxValue.isNull())
+ sb->setMaximum(opt.maxValue.toInt());
+
+ if (!opt.defaultValue.isNull())
+ sb->setValue(opt.defaultValue.toInt());
+
+ connect(sb, SIGNAL(valueChanged(int)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::FILE:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ editorHelper = new QPushButton(tr("Browse"), this);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ connect(editorHelper, SIGNAL(pressed()), this, SLOT(browseForFile()));
+ break;
+ }
+ case DbPluginOption::BOOL:
+ {
+ QCheckBox* cb = new QCheckBox(this);
+ editor = cb;
+ if (!opt.defaultValue.isNull())
+ cb->setChecked(opt.defaultValue.toBool());
+
+ connect(cb, SIGNAL(stateChanged(int)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::DOUBLE:
+ {
+ QDoubleSpinBox* sb = new QDoubleSpinBox(this);
+ editor = sb;
+ if (!opt.minValue.isNull())
+ sb->setMinimum(opt.minValue.toDouble());
+
+ if (!opt.maxValue.isNull())
+ sb->setMaximum(opt.maxValue.toDouble());
+
+ if (!opt.defaultValue.isNull())
+ sb->setValue(opt.defaultValue.toDouble());
+
+ connect(sb, SIGNAL(valueChanged(double)), this, SLOT(propertyChanged()));
+ break;
+ }
+ default:
+ // TODO plugin based handling of custom editors
+ qWarning() << "Unhandled DbDialog option for creating editor.";
+ break;
+ }
+
+ if (le)
+ {
+ le->setPlaceholderText(opt.placeholderText);
+ le->setText(opt.defaultValue.toString());
+ }
+
+ return editor;
+}
+
+QVariant DbDialog::getValueFrom(DbPluginOption::Type type, QWidget *editor)
+{
+ QVariant value;
+ switch (type)
+ {
+ case DbPluginOption::STRING:
+ case DbPluginOption::PASSWORD:
+ case DbPluginOption::FILE:
+ value = dynamic_cast<QLineEdit*>(editor)->text();
+ break;
+ case DbPluginOption::INT:
+ value = dynamic_cast<QSpinBox*>(editor)->value();
+ break;
+ case DbPluginOption::BOOL:
+ value = dynamic_cast<QCheckBox*>(editor)->isChecked();
+ break;
+ case DbPluginOption::DOUBLE:
+ value = dynamic_cast<QDoubleSpinBox*>(editor)->value();
+ break;
+ case DbPluginOption::CHOICE:
+ value = dynamic_cast<QComboBox*>(editor)->currentText();
+ break;
+ default:
+ // TODO plugin based handling of custom editors
+ qWarning() << "Unhandled DbDialog option for value.";
+ break;
+ }
+ return value;
+}
+
+void DbDialog::setValueFor(DbPluginOption::Type type, QWidget *editor, const QVariant &value)
+{
+ switch (type)
+ {
+ case DbPluginOption::STRING:
+ case DbPluginOption::FILE:
+ case DbPluginOption::PASSWORD:
+ dynamic_cast<QLineEdit*>(editor)->setText(value.toString());
+ break;
+ case DbPluginOption::INT:
+ dynamic_cast<QSpinBox*>(editor)->setValue(value.toInt());
+ break;
+ case DbPluginOption::BOOL:
+ dynamic_cast<QCheckBox*>(editor)->setChecked(value.toBool());
+ break;
+ case DbPluginOption::DOUBLE:
+ dynamic_cast<QDoubleSpinBox*>(editor)->setValue(value.toDouble());
+ break;
+ case DbPluginOption::CHOICE:
+ dynamic_cast<QComboBox*>(editor)->setCurrentText(value.toString());
+ break;
+ default:
+ qWarning() << "Unhandled DbDialog option to set value.";
+ // TODO plugin based handling of custom editors
+ break;
+ }
+}
+
+void DbDialog::updateType()
+{
+ QFileInfo file(ui->fileEdit->text());
+ if (!file.exists() || file.isDir())
+ {
+ ui->typeCombo->setEnabled(true);
+ return;
+ }
+
+ DbPlugin* validPlugin = nullptr;
+ QHash<QString,QVariant> options;
+ QString path = ui->fileEdit->text();
+ Db* probeDb = nullptr;
+ foreach (DbPlugin* plugin, dbPlugins)
+ {
+ probeDb = plugin->getInstance("", path, options);
+ if (probeDb)
+ {
+ delete probeDb;
+ probeDb = nullptr;
+
+ validPlugin = plugin;
+ break;
+ }
+ }
+
+ if (validPlugin)
+ ui->typeCombo->setCurrentText(validPlugin->getLabel());
+
+ ui->typeCombo->setEnabled(!validPlugin);
+}
+
+QHash<QString, QVariant> DbDialog::collectOptions()
+{
+ QHash<QString, QVariant> options;
+ if (ui->typeCombo->currentIndex() < 0)
+ return options;
+
+ for (const QString key : optionKeyToWidget.keys())
+ options[key] = getValueFrom(optionKeyToType[key], optionKeyToWidget[key]);
+
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ plugin = dbPlugins[ui->typeCombo->currentIndex()];
+ options[DB_PLUGIN] = plugin->getName();
+ }
+
+ return options;
+}
+
+bool DbDialog::testDatabase()
+{
+ QString path = ui->fileEdit->text();
+ bool existed = QFile::exists(path);
+ bool res = getDb() != nullptr;
+ if (!existed)
+ {
+ QFile file(path);
+ file.remove();
+ }
+ return res;
+}
+
+bool DbDialog::validate()
+{
+ if (ui->fileEdit->text().isEmpty())
+ return false;
+
+ if (ui->nameEdit->text().isEmpty())
+ return false;
+
+ if (ui->typeCombo->count() == 0)
+ return false;
+
+ if (ui->typeCombo->currentIndex() < 0)
+ return false;
+
+ return true;
+}
+
+void DbDialog::updateState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
+}
+
+void DbDialog::propertyChanged()
+{
+ ui->testConnIcon->setVisible(false);
+}
+
+void DbDialog::typeChanged(int index)
+{
+ UNUSED(index);
+ updateOptions();
+ updateState();
+}
+
+void DbDialog::valueForNameGenerationChanged()
+{
+ if (!ui->generateCheckBox->isChecked())
+ {
+ updateState();
+ return;
+ }
+
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ plugin = dbPlugins[ui->typeCombo->currentIndex()];
+ QString generatedName = plugin->generateDbName(ui->fileEdit->text());
+ generatedName = generateUniqueName(generatedName, existingDatabaseNames);
+ ui->nameEdit->setText(generatedName);
+ }
+}
+
+void DbDialog::browseForFile()
+{
+ QString dir = getFileDialogInitPath();
+ QString path = QFileDialog::getOpenFileName(0, QString(), dir);
+ if (path.isNull())
+ return;
+
+ QString key = helperToKey[dynamic_cast<QWidget*>(sender())];
+ setValueFor(optionKeyToType[key], optionKeyToWidget[key], path);
+
+ setFileDialogInitPathByFile(path);
+}
+
+void DbDialog::generateNameSwitched(bool checked)
+{
+ if (checked)
+ {
+ ui->nameEdit->setPlaceholderText(tr("The name will be auto-generated"));
+ valueForNameGenerationChanged();
+ }
+ else
+ {
+ ui->nameEdit->setPlaceholderText(tr("Type the name"));
+ }
+
+ ui->nameEdit->setReadOnly(checked);
+}
+
+void DbDialog::fileChanged(const QString &arg1)
+{
+ UNUSED(arg1);
+ valueForNameGenerationChanged();
+ updateType();
+ propertyChanged();
+}
+
+void DbDialog::browseClicked()
+{
+ QFileInfo fileInfo(ui->fileEdit->text());
+ QString dir;
+ if (ui->fileEdit->text().isEmpty())
+ dir = getFileDialogInitPath();
+ else if (fileInfo.exists() && fileInfo.isFile())
+ dir = fileInfo.absolutePath();
+ else if (fileInfo.dir().exists())
+ dir = fileInfo.dir().absolutePath();
+ else
+ dir = getFileDialogInitPath();
+
+ QString path = getDbPath(dir);
+ if (path.isNull())
+ return;
+
+ setFileDialogInitPathByFile(path);
+
+ ui->fileEdit->setText(path);
+ updateState();
+}
+
+void DbDialog::testConnectionClicked()
+{
+ ui->testConnIcon->setPixmap(testDatabase() ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR);
+ ui->testConnIcon->setVisible(true);
+}
+
+void DbDialog::dbTypeChanged(int index)
+{
+ typeChanged(index);
+ propertyChanged();
+}
+
+void DbDialog::nameModified(const QString &arg1)
+{
+ UNUSED(arg1);
+ updateState();
+}
+
+void DbDialog::accept()
+{
+ QString name = getName();
+ QString path = getPath();
+ QHash<QString, QVariant> options = collectOptions();
+ bool perm = isPermanent();
+ bool result;
+ if (mode == ADD)
+ result = DBLIST->addDb(name, path, options, perm);
+ else
+ result = DBLIST->updateDb(db, name, path, options, perm);
+
+ if (result)
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h
new file mode 100644
index 0000000..b2c0d68
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h
@@ -0,0 +1,89 @@
+#ifndef DBDIALOG_H
+#define DBDIALOG_H
+
+#include "db/db.h"
+#include "db/dbpluginoption.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QList>
+#include <QHash>
+#include <QStringList>
+
+class DbPlugin;
+class QGridLayout;
+struct DbPluginOption;
+
+namespace Ui {
+ class DbDialog;
+}
+
+class GUI_API_EXPORT DbDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum Mode
+ {
+ ADD,
+ EDIT
+ };
+
+ DbDialog(Mode mode, QWidget *parent = 0);
+ ~DbDialog();
+
+ void setDb(Db* db);
+ void setPermanent(bool perm);
+
+ QString getPath();
+ void setPath(const QString& path);
+ QString getName();
+ QHash<QString,QVariant> collectOptions();
+ bool isPermanent();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+ void updateOptions();
+ void addOption(const DbPluginOption& option, int row);
+ QWidget* getEditor(const DbPluginOption& opt, QWidget *&editorHelper);
+ QVariant getValueFrom(DbPluginOption::Type type, QWidget* editor);
+ void setValueFor(DbPluginOption::Type type, QWidget* editor, const QVariant& value);
+ void updateType();
+ Db* getDb();
+ bool testDatabase();
+ bool validate();
+ void updateState();
+
+ Ui::DbDialog *ui = nullptr;
+ Mode mode;
+ QStringList existingDatabaseNames;
+ Db* db = nullptr;
+ QList<DbPlugin*> dbPlugins;
+ QList<QWidget*> optionWidgets;
+ QHash<QString,QWidget*> optionKeyToWidget;
+ QHash<QString,DbPluginOption::Type> optionKeyToType;
+ QHash<QWidget*,QString> helperToKey;
+ QWidget* lastWidgetInTabOrder = nullptr;
+
+ static const constexpr int ADDITIONAL_ROWS_BEGIN_INDEX = 4;
+
+ private slots:
+ void typeChanged(int index);
+ void valueForNameGenerationChanged();
+ void browseForFile();
+ void generateNameSwitched(bool checked);
+ void fileChanged(const QString &arg1);
+ void browseClicked();
+ void testConnectionClicked();
+ void propertyChanged();
+ void dbTypeChanged(int index);
+ void nameModified(const QString &arg1);
+
+ public slots:
+ void accept();
+};
+
+#endif // DBDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui
new file mode 100644
index 0000000..fb53428
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbDialog</class>
+ <widget class="QDialog" name="DbDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>455</width>
+ <height>200</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Database</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QComboBox" name="typeCombo">
+ <property name="toolTip">
+ <string>Database driver</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="nameEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="fileEdit"/>
+ </item>
+ <item row="0" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QToolButton" name="browseButton">
+ <property name="toolTip">
+ <string>Browse for database file on local computer</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="fileLabel">
+ <property name="text">
+ <string>File</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="generateCheckBox">
+ <property name="toolTip">
+ <string>Generate name basing on file path</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Permanent</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="permamentCheckBox">
+ <property name="toolTip">
+ <string extracomment="aasfd">&lt;p&gt;Enable this if you want the database to be stored in configuration file and restored every time SQLiteStudio is started.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="testConnButton">
+ <property name="text">
+ <string>Test database connection</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="testConnIcon">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>fileEdit</tabstop>
+ <tabstop>browseButton</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>generateCheckBox</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>permamentCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DbDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DbDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp
new file mode 100644
index 0000000..e86f9cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp
@@ -0,0 +1,58 @@
+#include "ddlpreviewdialog.h"
+#include "ui_ddlpreviewdialog.h"
+#include "services/codeformatter.h"
+#include "uiconfig.h"
+#include "sqlitestudio.h"
+#include "db/db.h"
+
+DdlPreviewDialog::DdlPreviewDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DdlPreviewDialog),
+ db(db)
+{
+ ui->setupUi(this);
+}
+
+DdlPreviewDialog::~DdlPreviewDialog()
+{
+ delete ui;
+}
+
+void DdlPreviewDialog::setDdl(const QString& ddl)
+{
+ QString formatted = SQLITESTUDIO->getCodeFormatter()->format("sql", ddl, db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void DdlPreviewDialog::setDdl(const QStringList& ddlList)
+{
+ QStringList fixedList;
+ QString newDdl;
+ foreach (const QString& ddl, ddlList)
+ {
+ newDdl = ddl.trimmed();
+ if (!newDdl.endsWith(";"))
+ newDdl.append(";");
+
+ fixedList << SQLITESTUDIO->getCodeFormatter()->format("sql", newDdl, db);
+ }
+ setDdl(fixedList.join("\n"));
+}
+
+void DdlPreviewDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DdlPreviewDialog::accept()
+{
+ CFG_UI.General.DontShowDdlPreview.set(ui->dontShowAgainCheck->isChecked());
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h
new file mode 100644
index 0000000..403405f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h
@@ -0,0 +1,35 @@
+#ifndef DDLPREVIEWDIALOG_H
+#define DDLPREVIEWDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class Db;
+
+namespace Ui {
+ class DdlPreviewDialog;
+}
+
+class GUI_API_EXPORT DdlPreviewDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit DdlPreviewDialog(Db* db, QWidget *parent = 0);
+ ~DdlPreviewDialog();
+
+ void setDdl(const QString& ddl);
+ void setDdl(const QStringList& ddlList);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ Ui::DdlPreviewDialog *ui = nullptr;
+ Db* db = nullptr;
+
+ public slots:
+ void accept();
+};
+
+#endif // DDLPREVIEWDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui
new file mode 100644
index 0000000..8c3678a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DdlPreviewDialog</class>
+ <widget class="QDialog" name="DdlPreviewDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>527</width>
+ <height>351</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Queries to be executed</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="dontShowAgainCheck">
+ <property name="text">
+ <string>Don't show again</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DdlPreviewDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DdlPreviewDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp
new file mode 100644
index 0000000..c0a73f3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp
@@ -0,0 +1,47 @@
+#include "errorsconfirmdialog.h"
+#include "ui_errorsconfirmdialog.h"
+#include "iconmanager.h"
+
+ErrorsConfirmDialog::ErrorsConfirmDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ErrorsConfirmDialog)
+{
+ ui->setupUi(this);
+}
+
+ErrorsConfirmDialog::~ErrorsConfirmDialog()
+{
+ delete ui;
+}
+
+void ErrorsConfirmDialog::setErrors(const QHash<QString,QSet<QString>>& errors)
+{
+ ui->list->clear();
+
+ for (const QString& key : errors.keys())
+ {
+ for (const QString& err : errors[key])
+ ui->list->addItem(QString("[%1] %2").arg(key, err));
+ }
+
+ for (int i = 0, total = ui->list->count(); i < total; ++i)
+ ui->list->item(i)->setIcon(ICONS.STATUS_ERROR);
+}
+
+void ErrorsConfirmDialog::setErrors(const QSet<QString>& errors)
+{
+ ui->list->clear();
+ ui->list->addItems(errors.toList());
+ for (int i = 0, total = ui->list->count(); i < total; ++i)
+ ui->list->item(i)->setIcon(ICONS.STATUS_ERROR);
+}
+
+void ErrorsConfirmDialog::setTopLabel(const QString& text)
+{
+ ui->topLabel->setText(text);
+}
+
+void ErrorsConfirmDialog::setBottomLabel(const QString& text)
+{
+ ui->bottomLabel->setText(text);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h
new file mode 100644
index 0000000..3e71d7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h
@@ -0,0 +1,28 @@
+#ifndef ERRORSCONFIRMDIALOG_H
+#define ERRORSCONFIRMDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class ErrorsConfirmDialog;
+}
+
+class GUI_API_EXPORT ErrorsConfirmDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit ErrorsConfirmDialog(QWidget *parent = 0);
+ ~ErrorsConfirmDialog();
+
+ void setErrors(const QHash<QString, QSet<QString> >& errors);
+ void setErrors(const QSet<QString>& errors);
+ void setTopLabel(const QString& text);
+ void setBottomLabel(const QString& text);
+
+ private:
+ Ui::ErrorsConfirmDialog *ui = nullptr;
+};
+
+#endif // ERRORSCONFIRMDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui
new file mode 100644
index 0000000..81cdb17
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ErrorsConfirmDialog</class>
+ <widget class="QDialog" name="ErrorsConfirmDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="topLabel">
+ <property name="text">
+ <string>Following errors occured:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="list">
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="bottomLabel">
+ <property name="text">
+ <string>Would you like to proceed?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ErrorsConfirmDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ErrorsConfirmDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp
new file mode 100644
index 0000000..e495dd9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp
@@ -0,0 +1,737 @@
+#include "exportdialog.h"
+#include "ui_exportdialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "services/dbmanager.h"
+#include "uiutils.h"
+#include "services/pluginmanager.h"
+#include "formmanager.h"
+#include "plugins/exportplugin.h"
+#include "configmapper.h"
+#include "selectabledbobjmodel.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "schemaresolver.h"
+#include "common/widgetcover.h"
+#include "services/notifymanager.h"
+#include "uiconfig.h"
+#include <QClipboard>
+#include <QDebug>
+#include <QDir>
+#include <QFileDialog>
+#include <QTextCodec>
+#include <QUiLoader>
+#include <QMimeData>
+
+ExportDialog::ExportDialog(QWidget *parent) :
+ QWizard(parent),
+ ui(new Ui::ExportDialog)
+{
+ init();
+}
+
+ExportDialog::~ExportDialog()
+{
+ EXPORT_MANAGER->interrupt();
+ safe_delete(configMapper);
+ delete ui;
+}
+
+void ExportDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+#ifdef Q_OS_MACX
+ resize(width() + 150, height());
+ setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_EXPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3));
+#endif
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Cancel"));
+ connect(widgetCover, SIGNAL(cancelClicked()), EXPORT_MANAGER, SLOT(interrupt()));
+ widgetCover->setVisible(false);
+
+ initPageOrder();
+
+ initModePage();
+ initTablePage();
+ initFormatPage();
+ initQueryPage();
+ initDbObjectsPage();
+
+ connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged(int)));
+ connect(EXPORT_MANAGER, SIGNAL(exportSuccessful()), this, SLOT(success()));
+ connect(EXPORT_MANAGER, SIGNAL(exportFinished()), this, SLOT(hideCoverWidget()));
+ connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QByteArray, QString)), this, SLOT(storeInClipboard(QByteArray, QString)));
+ connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QString)), this, SLOT(storeInClipboard(QString)));
+ connect(EXPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(EXPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+}
+
+void ExportDialog::setTableMode(Db* db, const QString& table)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->tablePage));
+ exportMode = ExportManager::TABLE;
+ this->db = db;
+ this->table = table;
+
+ ui->exportTableDbNameCombo->addItem(db->getName());
+ ui->exportTableDbNameCombo->setCurrentText(db->getName());
+ ui->exportTableDbNameCombo->setEnabled(false);
+ ui->exportTableNameCombo->addItem(table);
+ ui->exportTableNameCombo->setCurrentText(table);
+ ui->exportTableNameCombo->setEnabled(false);
+}
+
+void ExportDialog::setQueryMode(Db* db, const QString& query)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->queryPage));
+ exportMode = ExportManager::QUERY_RESULTS;
+ this->db = db;
+ this->query = query;
+
+ ui->queryDatabaseCombo->addItem(db->getName());
+ ui->queryDatabaseCombo->setCurrentText(db->getName());
+ ui->queryDatabaseCombo->setEnabled(false);
+ ui->queryEdit->setPlainText(query);
+ updateQueryEditDb();
+ ui->queryEdit->checkSyntaxNow();
+}
+
+void ExportDialog::setDatabaseMode(Db* db)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->databaseObjectsPage));
+ exportMode = ExportManager::DATABASE;
+ this->db = db;
+}
+
+void ExportDialog::initModePage()
+{
+ connect(ui->subjectDatabaseRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+ connect(ui->subjectTableRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+ connect(ui->subjectQueryRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+}
+
+void ExportDialog::initTablePage()
+{
+ ui->tablePage->setValidator([=]() -> bool
+ {
+ bool dbOk = ui->exportTableDbNameCombo->currentIndex() > -1;
+ bool tableOk = ui->exportTableNameCombo->currentIndex() > -1;
+
+ setValidState(ui->exportTableDbNameCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->exportTableNameCombo, tableOk, tr("Select table to export."));
+
+ return dbOk && tableOk;
+ });
+
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->exportTableDbNameCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+
+ connect(this, SIGNAL(tablePageCompleteChanged()), ui->tablePage, SIGNAL(completeChanged()));
+}
+
+void ExportDialog::initQueryPage()
+{
+ ui->queryPage->setValidator([=]() -> bool
+ {
+ bool queryOk = !ui->queryEdit->toPlainText().trimmed().isEmpty();
+ queryOk &= ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors();
+ bool dbOk = ui->queryDatabaseCombo->currentIndex() > -1;
+
+ setValidState(ui->queryDatabaseCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->queryEdit, queryOk, tr("Enter valid query to export."));
+
+ return dbOk && queryOk;
+ });
+
+ connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), ui->queryPage, SIGNAL(completeChanged()));
+ connect(ui->queryEdit, SIGNAL(textChanged()), ui->queryPage, SIGNAL(completeChanged()));
+ connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateQueryEditDb()));
+ connect(this, SIGNAL(queryPageCompleteChanged()), ui->queryPage, SIGNAL(completeChanged()));
+}
+
+void ExportDialog::initDbObjectsPage()
+{
+ selectableDbListModel = new SelectableDbObjModel(this);
+ selectableDbListModel->setSourceModel(DBTREE->getModel());
+ ui->dbObjectsTree->setModel(selectableDbListModel);
+
+ ui->databaseObjectsPage->setValidator([=]() -> bool
+ {
+ bool dbOk = ui->dbObjectsDatabaseCombo->currentIndex() > -1;
+ bool listOk = selectableDbListModel->getCheckedObjects().size() > 0;
+
+ setValidState(ui->dbObjectsDatabaseCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->dbObjectsTree, listOk, tr("Select at least one object to export."));
+
+ return listOk;
+ });
+
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateDbObjTree()));
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), ui->databaseObjectsPage, SIGNAL(completeChanged()));
+ connect(selectableDbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->databaseObjectsPage, SIGNAL(completeChanged()));
+ connect(ui->objectsSelectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsSelectAll()));
+ connect(ui->objectsDeselectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsDeselectAll()));
+}
+
+void ExportDialog::initFormatPage()
+{
+ ui->formatAndOptionsPage->setValidator([=]() -> bool
+ {
+ setValidState(ui->exportFileEdit, true);
+ bool outputFileSupported = currentPlugin && currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+ if (outputFileSupported && ui->exportFileRadio->isChecked())
+ {
+ QString path = ui->exportFileEdit->text();
+ if (path.trimmed().isEmpty())
+ {
+ setValidState(ui->exportFileEdit, false, tr("You must provide a file name to export to."));
+ return false;
+ }
+
+ QDir dir(path);
+ if (dir.exists() && QFileInfo(path).isDir())
+ {
+ setValidState(ui->exportFileEdit, false, tr("Path you provided is an existing directory. You cannot overwrite it."));
+ return false;
+ }
+
+ if (!dir.cdUp())
+ {
+ setValidState(ui->exportFileEdit, false, tr("The directory '%1' does not exist.").arg(dir.dirName()));
+ return false;
+ }
+
+ QFileInfo fi(path);
+ if (fi.exists())
+ setValidStateInfo(ui->exportFileEdit, tr("The file '%1' exists and will be overwritten.").arg(fi.fileName()));
+ }
+ return ui->formatCombo->currentIndex() > -1 && ui->encodingCombo->currentIndex() > -1 && isPluginConfigValid();
+ });
+
+ ui->exportFileButton->setIcon(ICONS.EXPORT_FILE_BROWSE);
+ connect(ui->exportFileButton, SIGNAL(clicked()), this, SLOT(browseForExportFile()));
+
+ connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected()));
+ connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->encodingCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileEdit, SIGNAL(textChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(this, SIGNAL(formatPageCompleteChanged()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateOptions()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateOptions()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions()));
+}
+
+int ExportDialog::nextId() const
+{
+ if (exportMode == ExportManager::UNDEFINED)
+ return pageId(ui->proxyPage);
+
+ QList<QWizardPage*> order = pageOrder[exportMode];
+
+ int idx = order.indexOf(currentPage());
+ idx++;
+ if (idx < order.size())
+ return pageId(order[idx]);
+
+ return -1;
+}
+
+bool ExportDialog::isPluginConfigValid() const
+{
+ return pluginConfigOk.size() == 0;
+}
+
+void ExportDialog::initPageOrder()
+{
+ setStartId(pageId(ui->exportSubjectPage));
+ pageOrder[ExportManager::DATABASE] = {ui->databaseObjectsPage, ui->formatAndOptionsPage};
+ pageOrder[ExportManager::TABLE] = {ui->tablePage, ui->formatAndOptionsPage};
+ pageOrder[ExportManager::QUERY_RESULTS] = {ui->queryPage, ui->formatAndOptionsPage};
+ updateExportMode();
+}
+
+int ExportDialog::pageId(QWizardPage* wizardPage) const
+{
+ for (int id : pageIds())
+ {
+ if (page(id) == wizardPage)
+ return id;
+ }
+ return -1;
+}
+
+void ExportDialog::tablePageDisplayed()
+{
+ if (!tablePageVisited)
+ {
+ if (table.isNull()) // table mode selected by user, not forced by setTableMode().
+ {
+ ui->exportTableDbNameCombo->setModel(dbListModel);
+ connect(ui->exportTableDbNameCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDbTables()));
+
+ ui->exportTableNameCombo->setModel(tablesModel);
+ connect(ui->exportTableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged()));
+ }
+ updateDbTables();
+ emit tablePageCompleteChanged();
+ tablePageVisited = true;
+ }
+}
+
+void ExportDialog::queryPageDisplayed()
+{
+ if (!queryPageVisited)
+ {
+ if (query.isNull()) // query mode selected by user, not forced by setQueryMode().
+ {
+ ui->queryDatabaseCombo->setModel(dbListModel);
+ connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged()));
+ }
+
+ updateQueryEditDb();
+ emit queryPageCompleteChanged();
+ queryPageVisited = true;
+ }
+}
+
+void ExportDialog::dbObjectsPageDisplayed()
+{
+ if (!dbObjectsPageVisited)
+ {
+ ui->dbObjectsDatabaseCombo->setModel(dbListModel);
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged()));
+
+ if (db)
+ ui->dbObjectsDatabaseCombo->setCurrentText(db->getName());
+
+ dbObjectsPageVisited = true;
+ }
+}
+
+void ExportDialog::formatPageDisplayed()
+{
+ if (!formatPageVisited)
+ {
+ ui->formatCombo->addItems(EXPORT_MANAGER->getAvailableFormats(exportMode));
+
+ ui->encodingCombo->addItems(textCodecNames());
+ ui->encodingCombo->setCurrentText(defaultCodecName());
+
+ formatPageVisited = true;
+ }
+ pluginSelected();
+}
+
+ExportPlugin* ExportDialog::getSelectedPlugin() const
+{
+ return EXPORT_MANAGER->getPluginForFormat(ui->formatCombo->currentText());
+}
+
+void ExportDialog::updateExportMode()
+{
+ if (ui->subjectDatabaseRadio->isChecked())
+ exportMode = ExportManager::DATABASE;
+ else if (ui->subjectTableRadio->isChecked())
+ exportMode = ExportManager::TABLE;
+ else if (ui->subjectQueryRadio->isChecked())
+ exportMode = ExportManager::QUERY_RESULTS;
+ else
+ exportMode = ExportManager::UNDEFINED;
+}
+
+void ExportDialog::pageChanged(int pageId)
+{
+ QWizardPage* wizardPage = page(pageId);
+ if (wizardPage == ui->tablePage)
+ tablePageDisplayed();
+ else if (wizardPage == ui->queryPage)
+ queryPageDisplayed();
+ else if (wizardPage == ui->databaseObjectsPage)
+ dbObjectsPageDisplayed();
+ else if (wizardPage == ui->formatAndOptionsPage)
+ formatPageDisplayed();
+ else if (wizardPage == ui->proxyPage)
+ next();
+}
+
+void ExportDialog::updateDbTables()
+{
+ if (!table.isNull())
+ return; // we don't want tables to be automatically updated if this is strictly set table
+
+ QString dbName = ui->exportTableDbNameCombo->currentText();
+ db = DBLIST->getByName(dbName);
+
+ tablesModel->setDb(db);
+}
+
+void ExportDialog::browseForExportFile()
+{
+ QStringList filters;
+ if (currentPlugin)
+ filters << currentPlugin->getFormatName()+" (*." + currentPlugin->defaultFileExtension() + ")";
+
+ filters << tr("All files (*)");
+
+ QString dir = getFileDialogInitPath();
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Pick file to export to"), dir, filters.join(";;"), 0, QFileDialog::DontConfirmOverwrite);
+ if (fileName.isNull())
+ return;
+
+ if (currentPlugin && !fileName.endsWith("." + currentPlugin->defaultFileExtension()))
+ fileName += "." + currentPlugin->defaultFileExtension();
+
+ ui->exportFileEdit->setText(fileName);
+ setFileDialogInitPathByFile(fileName);
+}
+
+void ExportDialog::pluginSelected()
+{
+ pluginConfigOk.clear();
+
+ currentPlugin = getSelectedPlugin();
+ if (!currentPlugin)
+ {
+ qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText();
+ return;
+ }
+
+ currentPlugin->setExportMode(exportMode);
+
+ updateExportOutputOptions();
+ updateOptions();
+
+ if (currentPlugin->getConfig() && !currentPlugin->getConfig()->isPersistable())
+ currentPlugin->getConfig()->reset();
+}
+
+void ExportDialog::updateExportOutputOptions()
+{
+ ExportManager::StandardConfigFlags options = currentPlugin->standardOptionsToEnable();
+ bool displayCodec = options.testFlag(ExportManager::CODEC) && !ui->exportClipboardRadio->isChecked();
+ bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD);
+ bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+
+ bool enabled = outputFileSupported && ui->exportFileRadio->isChecked();
+ ui->exportFileEdit->setEnabled(enabled);
+ ui->exportFileButton->setEnabled(enabled);
+
+ ui->exportClipboardRadio->setVisible(clipboardSupported);
+ ui->exportFileRadio->setVisible(outputFileSupported);
+ ui->exportFileEdit->setVisible(outputFileSupported);
+ ui->exportFileButton->setVisible(outputFileSupported);
+ if (!clipboardSupported && outputFileSupported)
+ ui->exportFileRadio->setChecked(true);
+
+ ui->encodingCombo->setVisible(displayCodec);
+ ui->encodingLabel->setVisible(displayCodec);
+ if (displayCodec)
+ {
+ QString codec = currentPlugin->getDefaultEncoding();
+ int idx = ui->encodingCombo->findText(codec);
+ if (idx > -1)
+ ui->encodingCombo->setCurrentIndex(idx);
+ }
+
+ ui->exportToGroup->setVisible(clipboardSupported || outputFileSupported || displayCodec);
+}
+
+void ExportDialog::updateQueryEditDb()
+{
+ Db* db = getDbForExport(ui->queryDatabaseCombo->currentText());
+ ui->queryEdit->setDb(db);
+}
+
+void ExportDialog::updateOptions()
+{
+ ui->optionsGroup->setVisible(false);
+
+ if (!currentPlugin)
+ {
+ qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText();
+ return;
+ }
+
+ int optionsRow = 0;
+ updatePluginOptions(currentPlugin, optionsRow);
+ ui->optionsGroup->setVisible(optionsRow > 0);
+}
+
+void ExportDialog::updateDbObjTree()
+{
+ selectableDbListModel->setDbName(ui->dbObjectsDatabaseCombo->currentText());
+
+ QModelIndex root = selectableDbListModel->index(0, 0);
+ if (root.isValid())
+ {
+ root = setupNewDbObjTreeRoot(root);
+ ui->dbObjectsTree->setRootIndex(root);
+
+ ui->dbObjectsTree->expand(root);
+ QModelIndex child;
+ for (int i = 0; (child = root.child(i, 0)).isValid(); i++)
+ ui->dbObjectsTree->expand(child);
+ }
+ dbObjectsSelectAll();
+}
+
+void ExportDialog::dbObjectsSelectAll()
+{
+ selectableDbListModel->setRootChecked(true);
+}
+
+void ExportDialog::dbObjectsDeselectAll()
+{
+ selectableDbListModel->setRootChecked(false);
+}
+
+void ExportDialog::hideCoverWidget()
+{
+ widgetCover->hide();
+}
+
+void ExportDialog::storeInClipboard(const QByteArray& bytes, const QString& mimeType)
+{
+ QMimeData* mimeData = new QMimeData;
+ mimeData->setData(mimeType, bytes);
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void ExportDialog::storeInClipboard(const QString& str)
+{
+ QApplication::clipboard()->setText(str);
+}
+
+void ExportDialog::success()
+{
+ QWizard::accept();
+}
+
+void ExportDialog::accept()
+{
+ doExport();
+}
+
+void ExportDialog::updatePluginOptions(ExportPlugin* plugin, int& optionsRow)
+{
+ safe_delete(pluginOptionsWidget);
+
+ QString formName = plugin->getExportConfigFormName();
+ CfgMain* cfgMain = plugin->getConfig();
+ if (formName.isNull() || !cfgMain)
+ {
+ if (!formName.isNull())
+ {
+ qWarning() << "FormName is given, but cfgMain is null in ExportDialog::updatePluginOptions() for plugin:" << plugin->getName()
+ << ", formName:" << formName;
+ }
+ return;
+ }
+
+ if (!FORMS->hasWidget(formName))
+ {
+ qWarning() << "Export plugin" << plugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it."
+ << "Available forms are:" << FORMS->getAvailableForms();
+ return;
+ }
+
+ safe_delete(configMapper);
+
+ QGridLayout* grid = dynamic_cast<QGridLayout*>(ui->optionsGroup->layout());
+
+ pluginOptionsWidget = FORMS->createWidget(formName);
+
+ if (pluginOptionsWidget->layout())
+ pluginOptionsWidget->layout()->setMargin(0);
+
+ grid->addWidget(pluginOptionsWidget, 1, 0, 1, 2);
+ optionsRow++;
+
+ configMapper = new ConfigMapper(cfgMain);
+ configMapper->bindToConfig(pluginOptionsWidget);
+ connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation()));
+ plugin->validateOptions();
+}
+
+void ExportDialog::updateValidation()
+{
+ if (!currentPlugin)
+ return;
+
+ currentPlugin->validateOptions();
+ emit formatPageCompleteChanged();
+}
+
+void ExportDialog::doExport()
+{
+ widgetCover->show();
+
+ ExportManager::StandardExportConfig stdConfig = getExportConfig();
+ QString format = ui->formatCombo->currentText();
+ switch (exportMode)
+ {
+ case ExportManager::DATABASE:
+ exportDatabase(stdConfig, format);
+ break;
+ case ExportManager::TABLE:
+ exportTable(stdConfig, format);
+ break;
+ case ExportManager::QUERY_RESULTS:
+ exportQuery(stdConfig, format);
+ break;
+ case ExportManager::UNDEFINED:
+ qCritical() << "Finished export dialog with undefined mode.";
+ notifyInternalError();
+ break;
+ case ExportManager::FILE:
+ case ExportManager::CLIPBOARD:
+ break;
+ }
+}
+
+void ExportDialog::exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->dbObjectsDatabaseCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ EXPORT_MANAGER->exportDatabase(db, selectableDbListModel->getCheckedObjects());
+}
+
+void ExportDialog::exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->exportTableDbNameCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ // TODO when dbnames are fully supported, pass the dbname below
+ EXPORT_MANAGER->exportTable(db, QString::null, ui->exportTableNameCombo->currentText());
+}
+
+void ExportDialog::exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->queryDatabaseCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ EXPORT_MANAGER->exportQueryResults(db, ui->queryEdit->toPlainText());
+}
+
+ExportManager::StandardExportConfig ExportDialog::getExportConfig() const
+{
+ bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD);
+ bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+ bool clipboard = clipboardSupported && ui->exportClipboardRadio->isChecked();
+
+ ExportManager::StandardExportConfig stdConfig;
+ stdConfig.intoClipboard = clipboard;
+
+ if (clipboard)
+ stdConfig.outputFileName = QString::null;
+ else if (outputFileSupported)
+ stdConfig.outputFileName = ui->exportFileEdit->text();
+
+ if (exportMode == ExportManager::DATABASE)
+ stdConfig.exportData = ui->exportDbDataCheck->isChecked();
+ else if (exportMode == ExportManager::TABLE)
+ stdConfig.exportData = ui->exportTableDataCheck->isChecked();
+ else
+ stdConfig.exportData = false;
+
+ if (ui->encodingCombo->isVisible() && ui->encodingCombo->currentIndex() > -1)
+ stdConfig.codec = ui->encodingCombo->currentText();
+ else
+ stdConfig.codec = defaultCodecName();
+
+ return stdConfig;
+}
+
+Db* ExportDialog::getDbForExport(const QString& name)
+{
+ Db* db = DBLIST->getByName(name);
+ if (!db)
+ {
+ qCritical() << "Could not find db selected in combo:" << name;
+ notifyInternalError();
+ return nullptr;
+ }
+ return db;
+}
+
+void ExportDialog::notifyInternalError()
+{
+ notifyError(tr("Internal error during export. This is a bug. Please report it."));
+}
+
+QModelIndex ExportDialog::setupNewDbObjTreeRoot(const QModelIndex& root)
+{
+ QModelIndex newRoot = root;
+ DbTreeItem* item = nullptr;
+ while (newRoot.isValid())
+ {
+ item = selectableDbListModel->getItemForIndex(newRoot);
+ if (item->getType() == DbTreeItem::Type::DB)
+ return newRoot;
+
+ newRoot = newRoot.child(0, 0);
+ }
+ return newRoot;
+}
+
+void ExportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, errorMsg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+
+ emit formatPageCompleteChanged();
+ }
+}
+
+void ExportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h
new file mode 100644
index 0000000..296aa4d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h
@@ -0,0 +1,105 @@
+#ifndef EXPORTDIALOG_H
+#define EXPORTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include "services/exportmanager.h"
+#include <QWizard>
+
+namespace Ui {
+ class ExportDialog;
+}
+
+class DbListModel;
+class DbObjListModel;
+class SelectableDbObjModel;
+class WidgetCover;
+class ConfigMapper;
+
+class GUI_API_EXPORT ExportDialog : public QWizard
+{
+ Q_OBJECT
+
+ public:
+ explicit ExportDialog(QWidget *parent = 0);
+ ~ExportDialog();
+
+ void setTableMode(Db* db, const QString& table);
+ void setQueryMode(Db* db, const QString& query);
+ void setDatabaseMode(Db* db);
+ int nextId() const;
+ bool isPluginConfigValid() const;
+
+ private:
+ void init();
+ void initModePage();
+ void initTablePage();
+ void initQueryPage();
+ void initDbObjectsPage();
+ void initFormatPage();
+ void initPageOrder();
+ int pageId(QWizardPage* wizardPage) const;
+ void tablePageDisplayed();
+ void queryPageDisplayed();
+ void dbObjectsPageDisplayed();
+ void formatPageDisplayed();
+ ExportPlugin* getSelectedPlugin() const;
+ void updatePluginOptions(ExportPlugin* plugin, int& optionsRow);
+ void doExport();
+ void exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ void exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ void exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ ExportManager::StandardExportConfig getExportConfig() const;
+ Db* getDbForExport(const QString& name);
+ void notifyInternalError();
+ QModelIndex setupNewDbObjTreeRoot(const QModelIndex& root);
+
+ QHash<ExportManager::ExportMode,QList<QWizardPage*>> pageOrder;
+
+ Ui::ExportDialog *ui = nullptr;
+ ExportManager::ExportMode exportMode = ExportManager::UNDEFINED;
+ Db* db = nullptr;
+ QString query;
+ QString table;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ SelectableDbObjModel* selectableDbListModel = nullptr;
+ QWidget* pluginOptionsWidget = nullptr;
+ bool tablePageVisited = false;
+ bool queryPageVisited = false;
+ bool dbObjectsPageVisited = false;
+ bool formatPageVisited = false;
+ WidgetCover* widgetCover = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ ExportPlugin* currentPlugin = nullptr;
+
+ private slots:
+ void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void updateExportMode();
+ void pageChanged(int pageId);
+ void updateDbTables();
+ void browseForExportFile();
+ void pluginSelected();
+ void updateExportOutputOptions();
+ void updateQueryEditDb();
+ void updateOptions();
+ void updateDbObjTree();
+ void dbObjectsSelectAll();
+ void dbObjectsDeselectAll();
+ void hideCoverWidget();
+ void storeInClipboard(const QByteArray& bytes, const QString& mimeType);
+ void storeInClipboard(const QString& str);
+ void success();
+ void updateValidation();
+
+ public slots:
+ void accept();
+
+ signals:
+ void formatPageCompleteChanged();
+ void tablePageCompleteChanged();
+ void queryPageCompleteChanged();
+};
+
+#endif // EXPORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui
new file mode 100644
index 0000000..9f84232
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui
@@ -0,0 +1,438 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportDialog</class>
+ <widget class="QWizard" name="ExportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>515</width>
+ <height>414</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Export dialog</string>
+ </property>
+ <property name="options">
+ <set>QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton</set>
+ </property>
+ <widget class="QWizardPage" name="exportSubjectPage">
+ <property name="title">
+ <string>What do you want to export?</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QFrame" name="exportSubjectFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="subjectDatabaseRadio">
+ <property name="text">
+ <string>A database</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="subjectTableRadio">
+ <property name="text">
+ <string>A single table</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="subjectQueryRadio">
+ <property name="text">
+ <string>Query results</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="proxyPage"/>
+ <widget class="VerifiableWizardPage" name="tablePage">
+ <property name="title">
+ <string>Table to export</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QFrame" name="exportTableFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="2">
+ <widget class="QComboBox" name="exportTableNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="exportTableDbNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="exportTableDbNameLabel">
+ <property name="text">
+ <string>Database</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="exportTableNameLabel">
+ <property name="text">
+ <string>Table</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="exportTableSeparatorLabel">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="tableOptionsGroup">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QCheckBox" name="exportTableDataCheck">
+ <property name="toolTip">
+ <string>When this option is unchecked, then only table DDL (CREATE TABLE statement) is exported.</string>
+ </property>
+ <property name="text">
+ <string>Export table data</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="exportTableIndexesCheck">
+ <property name="text">
+ <string>Export table indexes</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="exportTableTriggersCheck">
+ <property name="text">
+ <string>Export table triggers</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note, that exporting table indexes and triggers may be unsupported by some output formats.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="databaseObjectsPage">
+ <property name="title">
+ <string>Select database objects to export</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0" colspan="2">
+ <widget class="QTreeView" name="dbObjectsTree">
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QCheckBox" name="exportDbDataCheck">
+ <property name="text">
+ <string>Export data from tables</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QPushButton" name="objectsSelectAllButton">
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="objectsDeselectAllButton">
+ <property name="text">
+ <string>Deselect all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="dbObjectsDatabaseCombo"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbObjectsDatabaseLabel">
+ <property name="text">
+ <string>Database:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="queryPage">
+ <property name="title">
+ <string>Query to export results for</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="2" column="0" colspan="2">
+ <widget class="SqlEditor" name="queryEdit"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="queryDatabaseCombo"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="queryDatabaseLabel">
+ <property name="text">
+ <string>Database:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="queryLabel">
+ <property name="text">
+ <string>Query to be executed for results:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="formatAndOptionsPage">
+ <property name="title">
+ <string>Export format and options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QScrollArea" name="formatScrollArea">
+ <property name="styleSheet">
+ <string notr="true">#formatScrollArea { background: transparent; }</string>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="formatScrollAreaContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>298</width>
+ <height>288</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#formatScrollAreaContents { background: transparent; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="formatGroup">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="title">
+ <string>Export format</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QComboBox" name="formatCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="exportToGroup">
+ <property name="title">
+ <string>Output</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="exportFileEdit">
+ <property name="placeholderText">
+ <string>Exported file path</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="exportClipboardRadio">
+ <property name="text">
+ <string>Clipboard</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="exportFileButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="exportFileRadio">
+ <property name="text">
+ <string>File</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="3">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="encodingLabel">
+ <property name="text">
+ <string>Exported text encoding:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="encodingCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="optionsGroup">
+ <property name="title">
+ <string>Export format options</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>VerifiableWizardPage</class>
+ <extends>QWizardPage</extends>
+ <header>common/verifiablewizardpage.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp
new file mode 100644
index 0000000..32ec30f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp
@@ -0,0 +1,376 @@
+#include "importdialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "common/widgetstateindicator.h"
+#include "uiutils.h"
+#include "common/widgetcover.h"
+#include "services/dbmanager.h"
+#include "services/pluginmanager.h"
+#include "services/importmanager.h"
+#include "sqlitestudio.h"
+#include "plugins/importplugin.h"
+#include "ui_importdialog.h"
+#include "configmapper.h"
+#include "formmanager.h"
+#include "common/utils.h"
+#include "uiconfig.h"
+#include <QDir>
+#include <QDebug>
+#include <QFileDialog>
+
+ImportDialog::ImportDialog(QWidget *parent) :
+ QWizard(parent),
+ ui(new Ui::ImportDialog)
+{
+ init();
+}
+
+ImportDialog::~ImportDialog()
+{
+ IMPORT_MANAGER->interrupt();
+ safe_delete(configMapper);
+ delete ui;
+}
+
+void ImportDialog::setDbAndTable(Db* db, const QString& table)
+{
+ if (!db)
+ return;
+
+ ui->dbNameCombo->setCurrentText(db->getName());
+ ui->tableNameCombo->setCurrentText(table);
+}
+
+void ImportDialog::setDb(Db* db)
+{
+ if (!db)
+ return;
+
+ ui->dbNameCombo->setCurrentText(db->getName());
+}
+
+bool ImportDialog::isPluginConfigValid() const
+{
+ return pluginConfigOk.size() == 0;
+}
+
+void ImportDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+#ifdef Q_OS_MACX
+ resize(width() + 150, height());
+ setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_IMPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3));
+#endif
+
+ initTablePage();
+ initDataSourcePage();
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Cancel"));
+ connect(widgetCover, SIGNAL(cancelClicked()), IMPORT_MANAGER, SLOT(interrupt()));
+ widgetCover->setVisible(false);
+
+ connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged()));
+ connect(IMPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(IMPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+ connect(IMPORT_MANAGER, SIGNAL(importSuccessful()), this, SLOT(success()));
+ connect(IMPORT_MANAGER, SIGNAL(importFinished()), this, SLOT(hideCoverWidget()));
+}
+
+void ImportDialog::initTablePage()
+{
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->dbNameCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+ ui->dbNameCombo->setModel(dbListModel);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setIncludeSystemObjects(false);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+ ui->tableNameCombo->setModel(tablesModel);
+ refreshTables();
+
+ connect(ui->dbNameCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables()));
+ connect(ui->tableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged()));
+
+ ui->tablePage->setValidator([=]() -> bool
+ {
+ bool valid = !ui->tableNameCombo->currentText().isEmpty();
+ setValidStateWihtTooltip(ui->tableNameCombo, tr("If you type table name that doesn't exist, it will be created."), valid, tr("Enter the table name"));
+ return valid;
+ });
+}
+
+void ImportDialog::initDataSourcePage()
+{
+ ui->inputFileButton->setIcon(ICONS.OPEN_FILE);
+ connect(ui->inputFileButton, SIGNAL(clicked()), this, SLOT(browseForInputFile()));
+
+ ui->codecCombo->addItems(textCodecNames());
+ ui->codecCombo->setCurrentText(defaultCodecName());
+
+ ui->dsPage->setValidator([=]() -> bool
+ {
+ setValidState(ui->dsTypeCombo, true);
+ if (!currentPlugin)
+ {
+ setValidState(ui->dsTypeCombo, false, tr("Select import plugin."));
+ return false;
+ }
+
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME))
+ {
+ QString path = ui->inputFileEdit->text();
+ if (path.trimmed().isEmpty())
+ {
+ setValidState(ui->inputFileEdit, false, tr("You must provide a file to import from."));
+ return false;
+ }
+
+ QFileInfo file(path);
+ if (!file.exists())
+ {
+ setValidState(ui->inputFileEdit, false, tr("The file '%1' does not exist.").arg(path));
+ return false;
+ }
+
+ if (file.exists() && file.isDir())
+ {
+ setValidState(ui->inputFileEdit, false, tr("Path you provided is a directory. A regular file is required."));
+ return false;
+ }
+ setValidState(ui->inputFileEdit, true);
+ }
+ return ui->dsTypeCombo->currentIndex() > -1 && ui->codecCombo->currentIndex() > -1 && isPluginConfigValid();
+ });
+
+ connect(this, SIGNAL(dsPageCompleteChanged()), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected()));
+ connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->codecCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->inputFileEdit, SIGNAL(textChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+
+ ui->dsTypeCombo->addItems(IMPORT_MANAGER->getImportDataSourceTypes());
+}
+
+void ImportDialog::removeOldOptions()
+{
+ safe_delete(configMapper);
+ safe_delete(pluginOptionsWidget);
+}
+
+void ImportDialog::updateStandardOptions()
+{
+ bool showFileName = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME);
+ bool showCodec = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC);
+
+ if (!showFileName && !showCodec)
+ {
+ ui->dsOptionsGroup->setVisible(false);
+ return;
+ }
+
+ ui->dsOptionsGroup->setVisible(true);
+
+ int row = 0;
+ QGridLayout* grid = dynamic_cast<QGridLayout*>(ui->dsOptionsGroup->layout());
+ if (showFileName)
+ {
+ grid->addWidget(ui->inputFileLabel, row, 0);
+ grid->addWidget(ui->inputFileWidget, row, 1);
+ row++;
+ }
+ else
+ {
+ grid->removeWidget(ui->inputFileLabel);
+ grid->removeWidget(ui->inputFileWidget);
+ }
+
+ ui->inputFileLabel->setVisible(showFileName);
+ ui->inputFileWidget->setVisible(showFileName);
+
+ if (showCodec)
+ {
+ grid->addWidget(ui->codecLabel, row, 0);
+ grid->addWidget(ui->codecCombo, row, 1);
+ row++;
+ }
+ else
+ {
+ grid->removeWidget(ui->codecLabel);
+ grid->removeWidget(ui->codecCombo);
+ }
+
+ ui->codecLabel->setVisible(showCodec);
+ ui->codecCombo->setVisible(showCodec);
+}
+
+void ImportDialog::updatePluginOptions(int& rows)
+{
+ QString formName = currentPlugin->getImportConfigFormName();
+ CfgMain* cfgMain = currentPlugin->getConfig();
+ ui->dsPluginOptionsGroup->setVisible(false);
+ if (formName.isNull() || !cfgMain)
+ {
+ if (!formName.isNull())
+ {
+ qWarning() << "FormName is given, but cfgMain is null in ImportDialog::updatePluginOptions() for plugin:" << currentPlugin->getName()
+ << ", formName:" << formName;
+ }
+ return;
+ }
+
+ if (!FORMS->hasWidget(formName))
+ {
+ qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it."
+ << "Available forms are:" << FORMS->getAvailableForms();
+ return;
+ }
+
+ pluginOptionsWidget = FORMS->createWidget(formName);
+ if (!pluginOptionsWidget)
+ {
+ qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager returned null.";
+ return;
+ }
+
+ ui->dsPluginOptionsGroup->setVisible(true);
+
+ if (pluginOptionsWidget->layout())
+ pluginOptionsWidget->layout()->setMargin(0);
+
+ ui->dsPluginOptionsGroup->layout()->addWidget(pluginOptionsWidget);
+ rows++;
+
+ configMapper = new ConfigMapper(cfgMain);
+ configMapper->bindToConfig(pluginOptionsWidget);
+ connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation()));
+ updateValidation();
+}
+
+void ImportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, errorMsg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+ }
+}
+
+void ImportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
+
+void ImportDialog::refreshTables()
+{
+ Db* db = DBLIST->getByName(ui->dbNameCombo->currentText());
+ if (db)
+ tablesModel->setDb(db);
+}
+
+void ImportDialog::pluginSelected()
+{
+ ui->dsPluginOptionsGroup->setVisible(false);
+ removeOldOptions();
+ currentPlugin = IMPORT_MANAGER->getPluginForDataSourceType(ui->dsTypeCombo->currentText());
+ if (!currentPlugin)
+ return;
+
+ updateStandardOptions();
+
+ int rows = 0;
+ updatePluginOptions(rows);
+ ui->dsPluginOptionsGroup->setVisible(rows > 0);
+}
+
+void ImportDialog::updateValidation()
+{
+ if (!currentPlugin)
+ return;
+
+ currentPlugin->validateOptions();
+ emit dsPageCompleteChanged();
+}
+
+void ImportDialog::pageChanged()
+{
+ if (currentPage() == ui->dsPage)
+ updateValidation();
+}
+
+void ImportDialog::browseForInputFile()
+{
+ if (!currentPlugin)
+ {
+ qCritical() << "Called ImportDialog::browseForInputFile(), but no ImportPlugin is selected.";
+ return;
+ }
+
+ QString dir = getFileDialogInitPath();
+ QString filter = currentPlugin->getFileFilter();
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Pick file to import from"), dir, filter);
+ if (fileName.isNull())
+ return;
+
+ ui->inputFileEdit->setText(fileName);
+ setFileDialogInitPathByFile(fileName);
+}
+
+void ImportDialog::success()
+{
+ QWizard::accept();
+}
+
+void ImportDialog::hideCoverWidget()
+{
+ widgetCover->hide();
+}
+
+void ImportDialog::accept()
+{
+ if (!currentPlugin)
+ {
+ qCritical() << "Called ImportDialog::accept(), but no ImportPlugin is selected.";
+ return;
+ }
+
+ ImportManager::StandardImportConfig stdConfig;
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME))
+ stdConfig.inputFileName = ui->inputFileEdit->text();
+
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC))
+ stdConfig.codec = ui->codecCombo->currentText();
+
+ Db* db = DBLIST->getByName(ui->dbNameCombo->currentText());;
+ if (!db)
+ {
+ qCritical() << "Called ImportDialog::accept(), but no database is selected.";
+ return;
+ }
+
+ QString table = ui->tableNameCombo->currentText();
+
+ widgetCover->show();
+ IMPORT_MANAGER->configure(currentPlugin->getDataSourceTypeName(), stdConfig);
+ IMPORT_MANAGER->importToTable(db, table);
+}
+
+void ImportDialog::showEvent(QShowEvent* e)
+{
+ QWizard::showEvent(e);
+ ui->tableNameCombo->setFocus();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h
new file mode 100644
index 0000000..c50703f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h
@@ -0,0 +1,69 @@
+#ifndef IMPORTDIALOG_H
+#define IMPORTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWizard>
+
+namespace Ui {
+ class ImportDialog;
+}
+
+class DbListModel;
+class DbObjListModel;
+class ImportPlugin;
+class ConfigMapper;
+class CfgEntry;
+class WidgetCover;
+class Db;
+
+class GUI_API_EXPORT ImportDialog : public QWizard
+{
+ Q_OBJECT
+
+ public:
+ explicit ImportDialog(QWidget *parent = 0);
+ ~ImportDialog();
+
+ void setDbAndTable(Db* db, const QString& table);
+ void setDb(Db* db);
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+ void initTablePage();
+ void initDataSourcePage();
+ void removeOldOptions();
+ void updateStandardOptions();
+ void updatePluginOptions(int& rows);
+ bool isPluginConfigValid() const;
+
+ Ui::ImportDialog *ui = nullptr;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QWidget* pluginOptionsWidget = nullptr;
+ ImportPlugin* currentPlugin = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void refreshTables();
+ void pluginSelected();
+ void updateValidation();
+ void pageChanged();
+ void browseForInputFile();
+ void success();
+ void hideCoverWidget();
+
+ public slots:
+ void accept();
+
+ signals:
+ void dsPageCompleteChanged();
+};
+
+#endif // IMPORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui
new file mode 100644
index 0000000..b853ab8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ImportDialog</class>
+ <widget class="QWizard" name="ImportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>511</width>
+ <height>406</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Import data</string>
+ </property>
+ <property name="options">
+ <set>QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton</set>
+ </property>
+ <widget class="VerifiableWizardPage" name="tablePage">
+ <property name="title">
+ <string>Table to import to</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QFrame" name="existingTableFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="3">
+ <widget class="QLabel" name="tableNameLabel">
+ <property name="text">
+ <string>Table</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbNameLabel">
+ <property name="text">
+ <string>Database</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="tableSeparatorLabel">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="dbNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QComboBox" name="tableNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="dsPage">
+ <property name="title">
+ <string>Data source to import from</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="styleSheet">
+ <string notr="true">#scrollArea { background: transparent; }</string>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>269</width>
+ <height>280</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#scrollAreaWidgetContents { background: transparent; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="dsTypeGroup">
+ <property name="title">
+ <string>Data source type</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QComboBox" name="dsTypeCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dsOptionsGroup">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="inputFileLabel">
+ <property name="text">
+ <string>Input file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="inputFileWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="inputFileEdit"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="inputFileButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="codecLabel">
+ <property name="text">
+ <string>Text encoding:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="codecCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dsPluginOptionsGroup">
+ <property name="title">
+ <string>Data source options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4"/>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>VerifiableWizardPage</class>
+ <extends>QWizardPage</extends>
+ <header>common/verifiablewizardpage.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp
new file mode 100644
index 0000000..d835dd1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp
@@ -0,0 +1,468 @@
+#include "indexdialog.h"
+#include "ui_indexdialog.h"
+#include "schemaresolver.h"
+#include "parser/ast/sqliteindexedcolumn.h"
+#include "services/notifymanager.h"
+#include "common/utils_sql.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "uiutils.h"
+#include "sqlite3.h"
+#include "windows/editorwindow.h"
+#include "services/codeformatter.h"
+#include <QDebug>
+#include <QGridLayout>
+#include <QSignalMapper>
+#include <QScrollBar>
+#include <QPushButton>
+#include <QMessageBox>
+
+IndexDialog::IndexDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ db(db),
+ ui(new Ui::IndexDialog)
+{
+ init();
+}
+
+IndexDialog::IndexDialog(Db* db, const QString& index, QWidget* parent) :
+ QDialog(parent),
+ db(db),
+ index(index),
+ ui(new Ui::IndexDialog)
+{
+ existingIndex = true;
+ init();
+}
+
+IndexDialog::~IndexDialog()
+{
+ delete ui;
+}
+
+void IndexDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void IndexDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ if (!db || !db->isOpen())
+ {
+ qCritical() << "Created IndexDialog for null or closed database.";
+ notifyError(tr("Tried to open index dialog for closed or inexisting database."));
+ reject();
+ return;
+ }
+
+ ui->columnsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ ui->partialIndexEdit->setDb(db);
+
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+
+ columnStateSignalMapping = new QSignalMapper(this);
+ connect(columnStateSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ SchemaResolver resolver(db);
+ ui->tableCombo->addItem(QString::null);
+ ui->tableCombo->addItems(resolver.getTables());
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateTable(QString)));
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateValidation()));
+ if (existingIndex)
+ ui->tableCombo->setEnabled(false);
+
+ if (db->getDialect() == Dialect::Sqlite3)
+ {
+ connect(ui->partialIndexCheck, SIGNAL(toggled(bool)), this, SLOT(updatePartialConditionState()));
+ connect(ui->partialIndexEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->partialIndexEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->partialIndexEdit->setVirtualSqlExpression("SELECT %1");
+ updatePartialConditionState();
+ ui->columnsTable->setColumnHidden(1, false);
+ }
+ else
+ {
+ ui->partialIndexCheck->setVisible(false);
+ ui->partialIndexEdit->setVisible(false);
+ ui->columnsTable->setColumnHidden(1, true);
+ }
+
+ readCollations();
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+
+ if (index.isNull())
+ createIndex = SqliteCreateIndexPtr::create();
+ else
+ readIndex();
+
+ originalCreateIndex = SqliteCreateIndexPtr::create(*createIndex);
+
+ ui->nameEdit->setText(index);
+ setTable(createIndex->table);
+
+ if (!index.isNull())
+ applyIndex();
+
+ updateValidation();
+
+ ui->nameEdit->setFocus();
+}
+
+void IndexDialog::readIndex()
+{
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(index, SchemaResolver::INDEX);
+ if (!parsedObject.dynamicCast<SqliteCreateIndex>())
+ {
+ notifyError(tr("Could not process index %1 correctly. Unable to open an index dialog.").arg(index));
+ reject();
+ return;
+ }
+
+ createIndex = parsedObject.dynamicCast<SqliteCreateIndex>();
+}
+
+void IndexDialog::buildColumns()
+{
+ // Clean up
+ ui->columnsTable->setRowCount(0);
+ columnCheckBoxes.clear();
+ sortComboBoxes.clear();
+ collateComboBoxes.clear();
+
+ totalColumns = tableColumns.size();
+ ui->columnsTable->setRowCount(totalColumns);
+
+ int row = 0;
+ foreach (const QString& column, tableColumns)
+ buildColumn(column, row++);
+}
+
+void IndexDialog::updateTable(const QString& value)
+{
+ table = value;
+
+ SchemaResolver resolver(db);
+ tableColumns = resolver.getTableColumns(table);
+
+ buildColumns();
+}
+
+void IndexDialog::updateValidation()
+{
+ bool tableOk = ui->tableCombo->currentIndex() > 0;
+ bool colSelected = false;
+
+ if (tableOk)
+ {
+ foreach (QCheckBox* cb, columnCheckBoxes)
+ {
+ if (cb->isChecked())
+ {
+ colSelected = true;
+ break;
+ }
+ }
+ }
+
+ bool partialConditionOk = (!ui->partialIndexCheck->isChecked() ||
+ (ui->partialIndexEdit->isSyntaxChecked() && !ui->partialIndexEdit->haveErrors()));
+
+ setValidState(ui->tableCombo, tableOk, tr("Pick the table for the index."));
+ setValidState(ui->columnsTable, colSelected, tr("Select at least one column."));
+ setValidState(ui->partialIndexCheck, partialConditionOk, tr("Enter a valid condition."));
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(colSelected && partialConditionOk);
+}
+
+void IndexDialog::setTable(const QString& value)
+{
+ ui->tableCombo->setCurrentText(value);
+}
+
+void IndexDialog::readCollations()
+{
+ SchemaResolver resolver(db);
+ QStringList collList = resolver.getCollations();
+
+ if (collList.size() > 0)
+ collList.prepend("");
+
+ collations.setStringList(collList);
+}
+
+void IndexDialog::buildColumn(const QString& name, int row)
+{
+ int col = 0;
+
+ QWidget* checkParent = new QWidget();
+ QHBoxLayout* layout = new QHBoxLayout();
+ QMargins margins = layout->contentsMargins();
+ margins.setTop(0);
+ margins.setBottom(0);
+ margins.setLeft(4);
+ margins.setRight(4);
+ layout->setContentsMargins(margins);
+ checkParent->setLayout(layout);
+
+ QCheckBox* check = new QCheckBox(name);
+ checkParent->layout()->addWidget(check);
+
+ ui->columnsTable->setCellWidget(row, col++, checkParent);
+ columnStateSignalMapping->setMapping(check, row);
+ connect(check, SIGNAL(toggled(bool)), columnStateSignalMapping, SLOT(map()));
+ connect(check, SIGNAL(toggled(bool)), this, SLOT(updateValidation()));
+ columnCheckBoxes << check;
+
+ QComboBox* collation = nullptr;
+ if (db->getDialect() == Dialect::Sqlite3)
+ {
+ collation = new QComboBox();
+ collation->setEditable(true);
+ collation->lineEdit()->setPlaceholderText(tr("default", "index dialog"));
+ collation->setModel(&collations);
+ ui->columnsTable->setCellWidget(row, col++, collation);
+ collateComboBoxes << collation;
+ }
+ else
+ {
+ col++;
+ }
+
+ QComboBox* sortOrder = new QComboBox();
+ sortOrder->setToolTip(tr("Sort order", "table constraints"));
+ ui->columnsTable->setCellWidget(row, col++, sortOrder);
+ sortComboBoxes << sortOrder;
+
+ QStringList sortList = {"", sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)};
+ sortOrder->addItems(sortList);
+
+ totalColumns++;
+
+ updateColumnState(row);
+}
+
+void IndexDialog::updateColumnState(int row)
+{
+ bool enabled = columnCheckBoxes[row]->isChecked();
+ sortComboBoxes[row]->setEnabled(enabled);
+ if (db->getDialect() == Dialect::Sqlite3)
+ collateComboBoxes[row]->setEnabled(enabled);
+}
+
+void IndexDialog::updatePartialConditionState()
+{
+ ui->partialIndexEdit->setEnabled(ui->partialIndexCheck->isChecked());
+ updateValidation();
+}
+
+void IndexDialog::updateDdl()
+{
+ rebuildCreateIndex();
+ QString formatted = FORMATTER->format("sql", createIndex->detokenize(), db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void IndexDialog::tabChanged(int tab)
+{
+ if (tab == 1)
+ updateDdl();
+}
+
+void IndexDialog::applyColumnValues()
+{
+ Dialect dialect = db->getDialect();
+ int row;
+ foreach (SqliteIndexedColumn* idxCol, createIndex->indexedColumns)
+ {
+ row = indexOf(tableColumns, idxCol->name, Qt::CaseInsensitive);
+ if (row == -1)
+ {
+ qCritical() << "Cannot find column from index in the table columns! Indexed column:" << idxCol->name
+ << ", table columns:" << tableColumns << ", index name:" << index << ", table name:" << table;
+ continue;
+ }
+
+ columnCheckBoxes[row]->setChecked(true);
+ updateColumnState(row);
+ sortComboBoxes[row]->setCurrentText(sqliteSortOrder(idxCol->sortOrder));
+ if (dialect == Dialect::Sqlite3)
+ collateComboBoxes[row]->setCurrentText(idxCol->collate);
+ }
+}
+
+void IndexDialog::applyIndex()
+{
+ applyColumnValues();
+
+ ui->partialIndexCheck->setChecked(createIndex->where != nullptr);
+ if (createIndex->where)
+ ui->partialIndexEdit->setPlainText(createIndex->where->detokenize());
+}
+
+SqliteIndexedColumn* IndexDialog::addIndexedColumn(const QString& name)
+{
+ SqliteIndexedColumn* idxCol = new SqliteIndexedColumn();
+ idxCol->name = name;
+ idxCol->setParent(createIndex.data());
+ createIndex->indexedColumns << idxCol;
+ return idxCol;
+}
+
+void IndexDialog::rebuildCreateIndex()
+{
+ createIndex = SqliteCreateIndexPtr::create();
+ createIndex->index = ui->nameEdit->text();
+ if (ui->tableCombo->currentIndex() > -1)
+ createIndex->table = ui->tableCombo->currentText();
+
+ createIndex->uniqueKw = ui->uniqueCheck->isChecked();
+
+ SqliteIndexedColumn* idxCol = nullptr;
+ int i = -1;
+ for (const QString& column : tableColumns)
+ {
+ i++;
+
+ if (!columnCheckBoxes[i]->isChecked())
+ continue;
+
+ idxCol = addIndexedColumn(column);
+ if (!collateComboBoxes[i]->currentText().isEmpty())
+ idxCol->collate = collateComboBoxes[i]->currentText();
+
+ if (sortComboBoxes[i]->currentIndex() > 0)
+ idxCol->sortOrder = sqliteSortOrder(sortComboBoxes[i]->currentText());
+ }
+
+ if (ui->partialIndexCheck->isChecked())
+ {
+ if (createIndex->where)
+ delete createIndex->where;
+
+ Parser parser(db->getDialect());
+ SqliteExpr* expr = parser.parseExpr(ui->partialIndexEdit->toPlainText());
+
+ if (expr)
+ {
+ expr->setParent(createIndex.data());
+ createIndex->where = expr;
+ }
+ else
+ {
+ qCritical() << "Could not parse expression from partial index condition: " << ui->partialIndexEdit->toPlainText()
+ << ", the CREATE INDEX statement will be incomplete.";
+ }
+ }
+
+ createIndex->rebuildTokens();
+}
+
+void IndexDialog::queryDuplicates()
+{
+ static QString queryTpl = QStringLiteral("SELECT %1 FROM %2 GROUP BY %3 HAVING %4;\n");
+ static QString countTpl = QStringLiteral("count(%1) AS %2");
+ static QString countColNameTpl = QStringLiteral("count(%1)");
+ static QString countConditionTpl = QStringLiteral("count(%1) > 1");
+
+ Dialect dialect = db->getDialect();
+
+ QStringList cols;
+ QStringList grpCols;
+ QStringList countCols;
+ QString wrappedCol;
+ QString countColName;
+ int i = 0;
+ for (const QString& column : tableColumns)
+ {
+ if (!columnCheckBoxes[i++]->isChecked())
+ continue;
+
+ wrappedCol = wrapObjIfNeeded(column, dialect);
+ cols << wrappedCol;
+ grpCols << wrappedCol;
+ countColName = wrapObjIfNeeded(countColNameTpl.arg(column), dialect);
+ cols << countTpl.arg(wrappedCol, countColName);
+ countCols << countConditionTpl.arg(wrappedCol);
+ }
+
+ EditorWindow* editor = MAINWINDOW->openSqlEditor();
+ editor->setCurrentDb(db);
+
+ QString sqlCols = cols.join(", ");
+ QString sqlGrpCols = grpCols.join(", ");
+ QString sqlCntCols = countCols.join(" AND ");
+ QString sqlTable = wrapObjIfNeeded(ui->tableCombo->currentText(), dialect);
+ editor->setContents(queryTpl.arg(sqlCols, sqlTable, sqlGrpCols, sqlCntCols));
+ editor->execute();
+}
+
+void IndexDialog::accept()
+{
+ rebuildCreateIndex();
+
+ Dialect dialect = db->getDialect();
+
+ QStringList sqls;
+ if (existingIndex)
+ sqls << QString("DROP INDEX %1").arg(wrapObjIfNeeded(originalCreateIndex->index, dialect));
+
+ sqls << createIndex->detokenize();
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ ChainExecutor executor;
+ executor.setDb(db);
+ executor.setAsync(false);
+ executor.setQueries(sqls);
+ executor.exec();
+
+ if (executor.getSuccessfulExecution())
+ {
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ QDialog::accept();
+ DBTREE->refreshSchema(db);
+ return;
+ }
+
+ if (executor.getErrors().size() == 1 && executor.getErrors().first().first == SQLITE_CONSTRAINT)
+ {
+ int res = QMessageBox::critical(this,
+ tr("Error", "index dialog"),
+ tr("Cannot create unique index, because values in selected columns are not unique. "
+ "Would you like to execute SELECT query to see problematic values?"),
+ QMessageBox::Yes,
+ QMessageBox::No);
+ if (res == QMessageBox::Yes)
+ {
+ QDialog::reject();
+ queryDuplicates();
+ }
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Error", "index dialog"), tr("An error occurred while executing SQL statements:\n%1")
+ .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok);
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h
new file mode 100644
index 0000000..1f9d1f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h
@@ -0,0 +1,72 @@
+#ifndef INDEXDIALOG_H
+#define INDEXDIALOG_H
+
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include <QDialog>
+#include <QStringListModel>
+
+namespace Ui {
+ class IndexDialog;
+}
+
+class QGridLayout;
+class QSignalMapper;
+class QCheckBox;
+class QComboBox;
+
+class GUI_API_EXPORT IndexDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ IndexDialog(Db* db, QWidget *parent = 0);
+ IndexDialog(Db* db, const QString& index, QWidget *parent = 0);
+ ~IndexDialog();
+
+ void setTable(const QString& value);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void readIndex();
+ void readCollations();
+ void buildColumn(const QString& name, int row);
+ void applyColumnValues();
+ void applyIndex();
+ SqliteIndexedColumn* addIndexedColumn(const QString& name);
+ void rebuildCreateIndex();
+ void queryDuplicates();
+
+ bool existingIndex = false;
+ Db* db = nullptr;
+ QString table;
+ QString index;
+ SqliteCreateIndexPtr createIndex;
+ SqliteCreateIndexPtr originalCreateIndex;
+ QStringList tableColumns;
+ QSignalMapper* columnStateSignalMapping = nullptr;
+ QStringListModel collations;
+ QList<QCheckBox*> columnCheckBoxes;
+ QList<QComboBox*> sortComboBoxes;
+ QList<QComboBox*> collateComboBoxes;
+ int totalColumns = 0;
+ Ui::IndexDialog *ui = nullptr;
+
+ private slots:
+ void updateValidation();
+ void buildColumns();
+ void updateTable(const QString& value);
+ void updateColumnState(int row);
+ void updatePartialConditionState();
+ void updateDdl();
+ void tabChanged(int tab);
+
+ public slots:
+ void accept();
+};
+
+#endif // INDEXDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui
new file mode 100644
index 0000000..e231550
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IndexDialog</class>
+ <widget class="QDialog" name="IndexDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>491</width>
+ <height>410</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Index dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="indexTab">
+ <attribute name="title">
+ <string>Index</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0">
+ <widget class="QLabel" name="tableLabel">
+ <property name="text">
+ <string>On table:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Index name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="QCheckBox" name="partialIndexCheck">
+ <property name="text">
+ <string>Partial index condition</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="SqlEditor" name="partialIndexEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QCheckBox" name="uniqueCheck">
+ <property name="text">
+ <string>Unique index</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QTableWidget" name="columnsTable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Collation</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="tableCombo"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddlTab">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>partialIndexCheck</tabstop>
+ <tabstop>partialIndexEdit</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>ddlEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>IndexDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>IndexDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp
new file mode 100644
index 0000000..fb78367
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp
@@ -0,0 +1,97 @@
+#include "messagelistdialog.h"
+#include "iconmanager.h"
+#include "ui_messagelistdialog.h"
+#include <QDebug>
+#include <QLinearGradient>
+
+MessageListDialog::MessageListDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::MessageListDialog)
+{
+ ui->setupUi(this);
+ ui->message->setVisible(false);
+}
+
+MessageListDialog::MessageListDialog(const QString& message, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::MessageListDialog)
+{
+ ui->setupUi(this);
+ ui->buttonBox->clear();
+ ui->buttonBox->addButton(QDialogButtonBox::Yes);
+ ui->buttonBox->addButton(QDialogButtonBox::No);
+ ui->message->setText(message);
+}
+
+MessageListDialog::~MessageListDialog()
+{
+ delete ui;
+}
+
+void MessageListDialog::addMessage(const QString& text, const QBrush& background)
+{
+ addMessage(QIcon(), text, background);
+}
+
+void MessageListDialog::addMessage(const QIcon& icon, const QString& text, const QBrush& background)
+{
+ QListWidgetItem* item = new QListWidgetItem();
+ item->setText(text);
+ item->setBackground(background);
+ item->setIcon(icon);
+ ui->listWidget->addItem(item);
+}
+
+void MessageListDialog::addInfo(const QString& text)
+{
+ addMessage(ICONS.STATUS_INFO, text, getGradient(0, 0, 1, 0.2));
+}
+
+void MessageListDialog::addWarning(const QString& text)
+{
+ addMessage(ICONS.STATUS_WARNING, text, getGradient(0.8, 0.8, 0, 0.4));
+}
+
+void MessageListDialog::addError(const QString& text)
+{
+ addMessage(ICONS.STATUS_ERROR, text, getGradient(0.6, 0, 0, 0.6));
+}
+
+void MessageListDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QBrush MessageListDialog::getGradient(qreal r, qreal g, qreal b, qreal a) const
+{
+ QLinearGradient gradient(0, 0, 20, 120);
+ gradient.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0));
+ gradient.setColorAt(1, QColor::fromRgbF(r, g, b, a));
+
+ return QBrush(gradient);
+}
+
+void MessageListDialog::showEvent(QShowEvent*)
+{
+ adjustSize();
+}
+
+void MessageListDialog::resizeEvent(QResizeEvent*)
+{
+ QFontMetrics metrics = ui->listWidget->fontMetrics();
+ QRect rect = ui->listWidget->rect();
+ int cnt = ui->listWidget->count();
+ QListWidgetItem* item = nullptr;
+ for (int row = 0; row < cnt; row++)
+ {
+ item = ui->listWidget->item(row);
+ item->setSizeHint(metrics.boundingRect(rect, Qt::TextWordWrap|Qt::TextLongestVariant, item->text()).size() + QSize(0, 10));
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h
new file mode 100644
index 0000000..e20d9f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h
@@ -0,0 +1,37 @@
+#ifndef MESSAGELISTDIALOG_H
+#define MESSAGELISTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class MessageListDialog;
+}
+
+class GUI_API_EXPORT MessageListDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit MessageListDialog(QWidget *parent = 0);
+ explicit MessageListDialog(const QString& message, QWidget *parent = 0);
+ ~MessageListDialog();
+
+ void addMessage(const QString& text, const QBrush& background = QBrush());
+ void addMessage(const QIcon& icon, const QString& text, const QBrush& background = QBrush());
+ void addInfo(const QString& text);
+ void addWarning(const QString& text);
+ void addError(const QString& text);
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent*);
+ void resizeEvent(QResizeEvent*);
+
+ private:
+ QBrush getGradient(qreal r, qreal g, qreal b, qreal a) const;
+
+ Ui::MessageListDialog *ui = nullptr;
+};
+
+#endif // MESSAGELISTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui
new file mode 100644
index 0000000..10ee6b8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MessageListDialog</class>
+ <widget class="QDialog" name="MessageListDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="message">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="listWidget">
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>MessageListDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>MessageListDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp
new file mode 100644
index 0000000..36b400b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp
@@ -0,0 +1,275 @@
+#include "newconstraintdialog.h"
+#include "ui_newconstraintdialog.h"
+#include "iconmanager.h"
+#include <QCommandLinkButton>
+
+NewConstraintDialog::NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::NewConstraintDialog),
+ type(ConstraintDialog::TABLE),
+ db(db),
+ createTable(createTable)
+{
+ ui->setupUi(this);
+ init();
+}
+
+NewConstraintDialog::NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::NewConstraintDialog),
+ type(ConstraintDialog::COLUMN),
+ db(db),
+ columnStmt(column)
+{
+ ui->setupUi(this);
+ createTable = dynamic_cast<SqliteCreateTable*>(column->parent());
+ init();
+}
+
+NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget* parent) :
+ NewConstraintDialog(createTable, db, parent)
+{
+ predefinedConstraintType = constraintType;
+}
+
+NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ NewConstraintDialog(column, db, parent)
+{
+ predefinedConstraintType = constraintType;
+}
+
+NewConstraintDialog::~NewConstraintDialog()
+{
+ delete ui;
+ if (constraintDialog)
+ delete constraintDialog;
+}
+
+SqliteStatement* NewConstraintDialog::getConstraint()
+{
+ return constrStatement;
+}
+
+void NewConstraintDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void NewConstraintDialog::init()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ {
+ initTable();
+ break;
+ }
+ case ConstraintDialog::COLUMN:
+ {
+ initColumn();
+ break;
+ }
+ }
+ adjustSize();
+ setMaximumSize(size());
+ setMinimumSize(size());
+}
+
+void NewConstraintDialog::initTable()
+{
+ addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createTablePk()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createTableFk()));
+
+ addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createTableUnique()));
+ addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createTableCheck()));
+}
+
+void NewConstraintDialog::initColumn()
+{
+ addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createColumnPk()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createColumnFk()));
+
+ addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createColumnUnique()));
+ addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createColumnCheck()));
+ addButton(ICONS.CONSTRAINT_NOT_NULL, tr("Not NULL", "new constraint dialog"), SLOT(createColumnNotNull()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_COLLATION, tr("Collate", "new constraint dialog"), SLOT(createColumnCollate()));
+
+ addButton(ICONS.CONSTRAINT_DEFAULT, tr("Default", "new constraint dialog"), SLOT(createColumnDefault()));
+}
+
+void NewConstraintDialog::addButton(const Icon& icon, const QString text, const char* slot)
+{
+ QCommandLinkButton* btn = new QCommandLinkButton();
+ btn->setIcon(icon);
+ btn->setText(text);
+ connect(btn, SIGNAL(clicked()), this, slot);
+ ui->container->layout()->addWidget(btn);
+}
+
+int NewConstraintDialog::createColumnConstraint(ConstraintDialog::Constraint constraintType)
+{
+ SqliteCreateTable::Column::Constraint* constraint = new SqliteCreateTable::Column::Constraint();
+ switch (constraintType)
+ {
+ case ConstraintDialog::PK:
+ constraint->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY;
+ break;
+ case ConstraintDialog::FK:
+ constraint->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY;
+ break;
+ case ConstraintDialog::UNIQUE:
+ constraint->type = SqliteCreateTable::Column::Constraint::UNIQUE;
+ break;
+ case ConstraintDialog::NOTNULL:
+ constraint->type = SqliteCreateTable::Column::Constraint::NOT_NULL;
+ break;
+ case ConstraintDialog::CHECK:
+ constraint->type = SqliteCreateTable::Column::Constraint::CHECK;
+ break;
+ case ConstraintDialog::COLLATE:
+ constraint->type = SqliteCreateTable::Column::Constraint::COLLATE;
+ break;
+ case ConstraintDialog::DEFAULT:
+ constraint->type = SqliteCreateTable::Column::Constraint::DEFAULT;
+ break;
+ case ConstraintDialog::UNKNOWN:
+ break;
+ }
+
+ constrStatement = constraint;
+ constrStatement->setParent(columnStmt);
+
+ return editConstraint();
+}
+
+int NewConstraintDialog::createTableConstraint(ConstraintDialog::Constraint constraintType)
+{
+ SqliteCreateTable::Constraint* constraint = new SqliteCreateTable::Constraint();
+ switch (constraintType)
+ {
+ case ConstraintDialog::PK:
+ constraint->type = SqliteCreateTable::Constraint::PRIMARY_KEY;
+ break;
+ case ConstraintDialog::FK:
+ constraint->type = SqliteCreateTable::Constraint::FOREIGN_KEY;
+ break;
+ case ConstraintDialog::UNIQUE:
+ constraint->type = SqliteCreateTable::Constraint::UNIQUE;
+ break;
+ case ConstraintDialog::CHECK:
+ constraint->type = SqliteCreateTable::Constraint::CHECK;
+ break;
+ case ConstraintDialog::NOTNULL:
+ case ConstraintDialog::COLLATE:
+ case ConstraintDialog::DEFAULT:
+ case ConstraintDialog::UNKNOWN:
+ break;
+ }
+
+ constrStatement = constraint;
+ constrStatement->setParent(createTable);
+
+ return editConstraint();
+}
+
+int NewConstraintDialog::editConstraint()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement),
+ createTable.data(), db, parentWidget());
+ break;
+ case ConstraintDialog::COLUMN:
+ constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement),
+ columnStmt.data(), db, parentWidget());
+ break;
+ }
+
+ connect(constraintDialog, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(constraintDialog, SIGNAL(accepted()), this, SLOT(accept()));
+
+ hide();
+ return constraintDialog->exec();
+}
+
+void NewConstraintDialog::createTablePk()
+{
+ createTableConstraint(ConstraintDialog::PK);
+}
+
+void NewConstraintDialog::createTableFk()
+{
+ createTableConstraint(ConstraintDialog::FK);
+}
+
+void NewConstraintDialog::createTableUnique()
+{
+ createTableConstraint(ConstraintDialog::UNIQUE);
+}
+
+void NewConstraintDialog::createTableCheck()
+{
+ createTableConstraint(ConstraintDialog::CHECK);
+}
+
+void NewConstraintDialog::createColumnPk()
+{
+ createColumnConstraint(ConstraintDialog::PK);
+}
+
+void NewConstraintDialog::createColumnFk()
+{
+ createColumnConstraint(ConstraintDialog::FK);
+}
+
+void NewConstraintDialog::createColumnUnique()
+{
+ createColumnConstraint(ConstraintDialog::UNIQUE);
+}
+
+void NewConstraintDialog::createColumnCheck()
+{
+ createColumnConstraint(ConstraintDialog::CHECK);
+}
+
+void NewConstraintDialog::createColumnNotNull()
+{
+ createColumnConstraint(ConstraintDialog::NOTNULL);
+}
+
+void NewConstraintDialog::createColumnDefault()
+{
+ createColumnConstraint(ConstraintDialog::DEFAULT);
+}
+
+void NewConstraintDialog::createColumnCollate()
+{
+ createColumnConstraint(ConstraintDialog::COLLATE);
+}
+
+int NewConstraintDialog::exec()
+{
+ if (predefinedConstraintType == ConstraintDialog::UNKNOWN)
+ return QDialog::exec();
+
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ return createTableConstraint(predefinedConstraintType);
+ case ConstraintDialog::COLUMN:
+ return createColumnConstraint(predefinedConstraintType);
+ }
+
+ return QDialog::Rejected;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h
new file mode 100644
index 0000000..e374312
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h
@@ -0,0 +1,65 @@
+#ifndef NEWCONSTRAINTDIALOG_H
+#define NEWCONSTRAINTDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "db/db.h"
+#include "dialogs/constraintdialog.h"
+#include "iconmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+namespace Ui {
+ class NewConstraintDialog;
+}
+
+class GUI_API_EXPORT NewConstraintDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0);
+ ~NewConstraintDialog();
+
+ SqliteStatement* getConstraint();
+ int exec();
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void initTable();
+ void initColumn();
+ void addButton(const Icon& icon, const QString text, const char* slot);
+ int createColumnConstraint(ConstraintDialog::Constraint constraintType);
+ int createTableConstraint(ConstraintDialog::Constraint constraintType);
+ int editConstraint();
+
+ Ui::NewConstraintDialog *ui = nullptr;
+ ConstraintDialog::Type type;
+ Db* db = nullptr;
+ ConstraintDialog::Constraint predefinedConstraintType = ConstraintDialog::UNKNOWN;
+ SqliteStatement* constrStatement = nullptr;
+ QPointer<SqliteCreateTable> createTable;
+ QPointer<SqliteCreateTable::Column> columnStmt;
+ ConstraintDialog* constraintDialog = nullptr;
+
+ private slots:
+ void createTablePk();
+ void createTableFk();
+ void createTableUnique();
+ void createTableCheck();
+ void createColumnPk();
+ void createColumnFk();
+ void createColumnUnique();
+ void createColumnCheck();
+ void createColumnNotNull();
+ void createColumnDefault();
+ void createColumnCollate();
+};
+
+#endif // NEWCONSTRAINTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui
new file mode 100644
index 0000000..20fc5ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewConstraintDialog</class>
+ <widget class="QDialog" name="NewConstraintDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>New constraint</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="container" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NewConstraintDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>NewConstraintDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp
new file mode 100644
index 0000000..1c73de0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp
@@ -0,0 +1,68 @@
+#include "newversiondialog.h"
+#include "services/pluginmanager.h"
+#include "sqlitestudio.h"
+#include "ui_newversiondialog.h"
+#include "services/config.h"
+#include <QInputDialog>
+
+NewVersionDialog::NewVersionDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::NewVersionDialog)
+{
+ init();
+}
+
+NewVersionDialog::~NewVersionDialog()
+{
+ delete ui;
+}
+
+void NewVersionDialog::setUpdates(const QList<UpdateManager::UpdateEntry>& updates)
+{
+ QTableWidgetItem* item = nullptr;
+ QString currVersion;
+ int row = 0;
+ ui->updateList->setRowCount(updates.size());
+ for (const UpdateManager::UpdateEntry& entry : updates)
+ {
+ if (entry.compontent == "SQLiteStudio")
+ currVersion = SQLITESTUDIO->getVersionString();
+ else
+ currVersion = PLUGINS->getPrintableVersion(entry.compontent);
+
+ item = new QTableWidgetItem(entry.compontent);
+ ui->updateList->setItem(row, 0, item);
+
+ item = new QTableWidgetItem(currVersion);
+ ui->updateList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(entry.version);
+ ui->updateList->setItem(row, 2, item);
+
+ row++;
+ }
+ ui->updateList->resizeColumnsToContents();
+}
+
+void NewVersionDialog::init()
+{
+ ui->setupUi(this);
+
+ connect(ui->abortButton, SIGNAL(clicked()), this, SLOT(reject()));
+ connect(ui->updateButton, SIGNAL(clicked()), this, SLOT(installUpdates()));
+ connect(ui->checkOnStartupCheck, &QCheckBox::clicked, [=](bool checked)
+ {
+ CFG_CORE.General.CheckUpdatesOnStartup.set(checked);
+ });
+}
+
+void NewVersionDialog::installUpdates()
+{
+ UPDATES->update();
+ close();
+}
+
+void NewVersionDialog::showEvent(QShowEvent*)
+{
+ ui->checkOnStartupCheck->setChecked(CFG_CORE.General.CheckUpdatesOnStartup.get());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h
new file mode 100644
index 0000000..784c6cf
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h
@@ -0,0 +1,34 @@
+#ifndef NEWVERSIONDIALOG_H
+#define NEWVERSIONDIALOG_H
+
+#include "services/updatemanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class NewVersionDialog;
+}
+
+class GUI_API_EXPORT NewVersionDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit NewVersionDialog(QWidget *parent = 0);
+ ~NewVersionDialog();
+
+ void setUpdates(const QList<UpdateManager::UpdateEntry>& updates);
+
+ protected:
+ void showEvent(QShowEvent*);
+
+ private:
+ void init();
+
+ Ui::NewVersionDialog *ui = nullptr;
+
+ private slots:
+ void installUpdates();
+};
+
+#endif // NEWVERSIONDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui
new file mode 100644
index 0000000..6f50e7f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewVersionDialog</class>
+ <widget class="QDialog" name="NewVersionDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>307</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQLiteStudio updates</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="topLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>New updates are available!</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="updateList">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderDefaultSectionSize">
+ <number>20</number>
+ </attribute>
+ <attribute name="verticalHeaderMinimumSectionSize">
+ <number>20</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Component</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Current version</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Update version</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="checkOnStartupWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="checkOnStartupCheck">
+ <property name="text">
+ <string>Check for updates on startup</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="updateButton">
+ <property name="text">
+ <string>Update to new version!</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/get_update.png</normaloff>:/icons/img/get_update.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="description">
+ <string>The update will be automatically downloaded and installed. This will also restart application at the end.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="abortButton">
+ <property name="text">
+ <string>Not now.</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/abort24.png</normaloff>:/icons/img/abort24.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="description">
+ <string>Don't install the update and close this window.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp
new file mode 100644
index 0000000..5dc506f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp
@@ -0,0 +1,119 @@
+#include "populateconfigdialog.h"
+#include "ui_populateconfigdialog.h"
+#include "plugins/populateplugin.h"
+#include "services/populatemanager.h"
+#include "sqlitestudio.h"
+#include "formmanager.h"
+#include "configmapper.h"
+#include "uiutils.h"
+#include <QPushButton>
+#include <QDebug>
+#include <QSpinBox>
+
+PopulateConfigDialog::PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::PopulateConfigDialog),
+ engine(engine),
+ column(column),
+ pluginName(pluginName)
+{
+ init();
+}
+
+PopulateConfigDialog::~PopulateConfigDialog()
+{
+ safe_delete(configMapper);
+ delete ui;
+}
+
+int PopulateConfigDialog::exec()
+{
+ QString formName = engine->getPopulateConfigFormName();
+ if (formName.isNull())
+ {
+ qCritical() << "Null form name from populating engine.";
+ return QDialog::Rejected;
+ }
+
+ innerWidget = FORMS->createWidget(formName);
+ if (!innerWidget)
+ return QDialog::Rejected;
+
+ configMapper->bindToConfig(innerWidget);
+ ui->contents->layout()->addWidget(innerWidget);
+ adjustSize();
+ validateEngine();
+ return QDialog::exec();
+}
+
+void PopulateConfigDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+ QString headerString = tr("Configuring <b>%1</b> for column <b>%2</b>").arg(pluginName, column);
+ ui->headerLabel->setText(headerString );
+
+ configMapper = new ConfigMapper(engine->getConfig());
+ connect(configMapper, SIGNAL(modified()), this, SLOT(validateEngine()));
+
+ connect(POPULATE_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(validationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(POPULATE_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+ connect(POPULATE_MANAGER, SIGNAL(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant)), this, SLOT(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant)));
+}
+
+void PopulateConfigDialog::validateEngine()
+{
+ engine->validateOptions();
+}
+
+void PopulateConfigDialog::validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, msg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+ }
+ updateState();
+}
+
+void PopulateConfigDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
+
+
+void PopulateConfigDialog::widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setProperty(propName.toLatin1().constData(), value);
+}
+
+void PopulateConfigDialog::updateState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(pluginConfigOk.size() == 0);
+}
+
+
+void PopulateConfigDialog::showEvent(QShowEvent* e)
+{
+ QVariant prop = innerWidget->property("initialSize");
+ if (prop.isValid())
+ resize(prop.toSize() + QSize(0, ui->headerLabel->height() + ui->line->height()));
+
+ QDialog::showEvent(e);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h
new file mode 100644
index 0000000..45bc333
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h
@@ -0,0 +1,47 @@
+#ifndef POPULATECONFIGDIALOG_H
+#define POPULATECONFIGDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class PopulateEngine;
+class ConfigMapper;
+class CfgEntry;
+
+namespace Ui {
+ class PopulateConfigDialog;
+}
+
+class GUI_API_EXPORT PopulateConfigDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent = 0);
+ ~PopulateConfigDialog();
+
+ int exec();
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+
+ Ui::PopulateConfigDialog *ui = nullptr;
+ PopulateEngine* engine = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ QString column;
+ QString pluginName;
+ QWidget* innerWidget = nullptr;
+
+ private slots:
+ void validateEngine();
+ void validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value);
+ void updateState();
+};
+
+#endif // POPULATECONFIGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui
new file mode 100644
index 0000000..ac90f02
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateConfigDialog</class>
+ <widget class="QDialog" name="PopulateConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Populating configuration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="headerLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="contents" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PopulateConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PopulateConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp
new file mode 100644
index 0000000..ca3fd31
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp
@@ -0,0 +1,332 @@
+#include "populatedialog.h"
+#include "ui_populatedialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "services/dbmanager.h"
+#include "schemaresolver.h"
+#include "services/pluginmanager.h"
+#include "plugins/populateplugin.h"
+#include "populateconfigdialog.h"
+#include "uiutils.h"
+#include "services/populatemanager.h"
+#include "common/widgetcover.h"
+#include <QPushButton>
+#include <QGridLayout>
+#include <QCheckBox>
+#include <QToolButton>
+#include <QDebug>
+#include <QSignalMapper>
+
+PopulateDialog::PopulateDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::PopulateDialog)
+{
+ init();
+}
+
+PopulateDialog::~PopulateDialog()
+{
+ delete ui;
+}
+
+void PopulateDialog::setDbAndTable(Db* db, const QString& table)
+{
+ ui->databaseCombo->setCurrentText(db->getName());
+ ui->tableCombo->setCurrentText(table);
+}
+
+void PopulateDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Populate", "populate dialog button"));
+
+ plugins = PLUGINS->getLoadedPlugins<PopulatePlugin>();
+ qSort(plugins.begin(), plugins.end(), [](PopulatePlugin* p1, PopulatePlugin* p2) -> bool
+ {
+ return p1->getTitle().compare(p2->getTitle()) < 0;
+ });
+
+ for (PopulatePlugin* plugin : plugins)
+ pluginTitles << plugin->getTitle();
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->setVisible(false);
+
+ ui->scrollArea->setAutoFillBackground(false);
+ ui->scrollArea->viewport()->setAutoFillBackground(false);
+ ui->columnsWidget->setAutoFillBackground(false);
+
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->databaseCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+ ui->databaseCombo->setModel(dbListModel);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setIncludeSystemObjects(false);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+ ui->tableCombo->setModel(tablesModel);
+ refreshTables();
+
+ connect(ui->databaseCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables()));
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshColumns()));
+ connect(POPULATE_MANAGER, SIGNAL(populatingFinished()), widgetCover, SLOT(hide()));
+ connect(POPULATE_MANAGER, SIGNAL(populatingSuccessful()), this, SLOT(finished()));
+}
+
+PopulateEngine* PopulateDialog::getEngine(int selectedPluginIndex)
+{
+ if (selectedPluginIndex < 0 || selectedPluginIndex >= plugins.size())
+ {
+ qCritical() << "Selected populate plugin out of range!";
+ return nullptr;
+ }
+
+ return plugins[selectedPluginIndex]->createEngine();
+}
+
+void PopulateDialog::deleteEngines(const QList<PopulateEngine*>& engines)
+{
+ for (PopulateEngine* engine : engines)
+ delete engine;
+}
+
+void PopulateDialog::rebuildEngines()
+{
+ int row = 0;
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ pluginSelected(entry.combo, entry.combo->currentIndex());
+ updateColumnState(row++, false);
+ }
+}
+
+void PopulateDialog::refreshTables()
+{
+ db = DBLIST->getByName(ui->databaseCombo->currentText());
+ if (db)
+ tablesModel->setDb(db);
+
+ updateState();
+}
+
+void PopulateDialog::refreshColumns()
+{
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ delete entry.check;
+ delete entry.combo;
+ delete entry.button;
+ }
+ columnEntries.clear();
+ safe_delete(buttonMapper);
+ safe_delete(checkMapper);
+
+ delete ui->columnsLayout;
+ ui->columnsLayout = new QGridLayout();
+ ui->columnsWidget->setLayout(ui->columnsLayout);
+
+ if (!db)
+ {
+ qCritical() << "No Db while refreshing columns in PopulateDialog!";
+ return;
+ }
+
+ buttonMapper = new QSignalMapper(this);
+ connect(buttonMapper, SIGNAL(mapped(int)), this, SLOT(configurePlugin(int)));
+
+ checkMapper = new QSignalMapper(this);
+ connect(checkMapper, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ SchemaResolver resolver(db);
+ QStringList columns = resolver.getTableColumns(ui->tableCombo->currentText());
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* btn = nullptr;
+ int row = 0;
+ for (const QString& column : columns)
+ {
+ check = new QCheckBox(column);
+ connect(check, SIGNAL(toggled(bool)), checkMapper, SLOT(map()));
+ checkMapper->setMapping(check, row);
+
+ combo = new QComboBox();
+ combo->addItems(pluginTitles);
+ connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(pluginSelected(int)));
+
+ btn = new QToolButton();
+ btn->setText(tr("Configure"));
+ connect(btn, SIGNAL(clicked()), buttonMapper, SLOT(map()));
+ buttonMapper->setMapping(btn, row);
+
+ ui->columnsLayout->addWidget(check, row, 0);
+ ui->columnsLayout->addWidget(combo, row, 1);
+ ui->columnsLayout->addWidget(btn, row, 2);
+ columnEntries << ColumnEntry(check, combo, btn);
+ row++;
+ }
+
+ rebuildEngines();
+
+ QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+ ui->columnsLayout->addItem(spacer, row, 0, 1, 3);
+
+ updateState();
+}
+
+void PopulateDialog::pluginSelected(int index)
+{
+ QComboBox* cb = dynamic_cast<QComboBox*>(sender());
+ pluginSelected(cb, index);
+}
+
+void PopulateDialog::pluginSelected(QComboBox* combo, int index)
+{
+ if (!combo)
+ return;
+
+ ColumnEntry* entry = nullptr;
+
+ int columnIndex = 0;
+ for (ColumnEntry& e : columnEntries)
+ {
+ if (e.combo == combo)
+ {
+ entry = &e;
+ break;
+ }
+ columnIndex++;
+ }
+
+ if (!entry)
+ return;
+
+ safe_delete(entry->engine);
+
+ if (index < 0 || index >= plugins.size())
+ return;
+
+ entry->engine = plugins[index]->createEngine();
+ updateColumnState(columnIndex);
+}
+
+void PopulateDialog::configurePlugin(int index)
+{
+ if (index < 0 || index >= columnEntries.size())
+ {
+ qCritical() << "Plugin configure index out of range:" << index << "," << columnEntries.size();
+ return;
+ }
+
+ PopulateEngine* engine = columnEntries[index].engine;
+ if (!engine->getConfig())
+ {
+ qWarning() << "Called config on populate plugin, but it has no CfgMain.";
+ return;
+ }
+
+ engine->getConfig()->savepoint();
+
+ PopulateConfigDialog dialog(engine, columnEntries[index].check->text(), columnEntries[index].combo->currentText(), this);
+ if (dialog.exec() != QDialog::Accepted)
+ engine->getConfig()->restore();
+
+ engine->getConfig()->release();
+
+ updateColumnState(index);
+}
+
+void PopulateDialog::updateColumnState(int index, bool updateGlobalState)
+{
+ if (index < 0 || index >= columnEntries.size())
+ {
+ qCritical() << "Column update called but index out of range:" << index << "," << columnEntries.size();
+ return;
+ }
+
+ bool checked = columnEntries[index].check->isChecked();
+ bool hasConfig = columnEntries[index].engine->getConfig() != nullptr;
+ columnEntries[index].combo->setEnabled(checked);
+ columnEntries[index].button->setEnabled(checked && hasConfig);
+
+ bool valid = true;
+ if (checked && hasConfig)
+ {
+ valid = columnEntries[index].engine->validateOptions();
+ setValidState(columnEntries[index].button, valid, tr("Populating configuration for this column is invalid or incomplete."));
+ }
+
+ if (valid == columnsValid.contains(index)) // if state changed
+ {
+ if (!valid)
+ columnsValid[index] = false;
+ else
+ columnsValid.remove(index);
+ }
+
+ if (updateGlobalState)
+ updateState();
+}
+
+void PopulateDialog::updateState()
+{
+ bool columnsOk = columnsValid.size() == 0;
+ bool dbOk = !ui->databaseCombo->currentText().isNull();
+ bool tableOk = !ui->tableCombo->currentText().isNull();
+
+ bool colCountOk = false;
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ if (entry.check->isChecked())
+ {
+ colCountOk = true;
+ break;
+ }
+ }
+
+ setValidState(ui->databaseCombo, dbOk, tr("Select database with table to populate"));
+ setValidState(ui->tableCombo, tableOk, tr("Select table to populate"));
+ setValidState(ui->columnsGroup, (!tableOk || colCountOk), tr("You have to select at least one column."));
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(columnsOk && tableOk && colCountOk);
+}
+
+void PopulateDialog::finished()
+{
+ QDialog::accept();
+}
+
+void PopulateDialog::accept()
+{
+ if (!db)
+ return;
+
+ QHash<QString,PopulateEngine*> engines;
+ for (ColumnEntry& entry : columnEntries)
+ {
+ if (!entry.check->isChecked())
+ continue;
+
+ if (!entry.engine)
+ return;
+
+ engines[entry.check->text()] = entry.engine;
+// entry.engine = nullptr; // to avoid deleting it in the entry's destructor - worker will delete it after it's done
+ }
+
+ QString table = ui->tableCombo->currentText();
+ qint64 rows = ui->rowsSpin->value();
+
+ widgetCover->show();
+ POPULATE_MANAGER->populate(db, table, engines, rows);
+}
+
+PopulateDialog::ColumnEntry::ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button) :
+ check(check), combo(combo), button(button)
+{
+}
+
+PopulateDialog::ColumnEntry::~ColumnEntry()
+{
+ safe_delete(engine);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h
new file mode 100644
index 0000000..0ecc318
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h
@@ -0,0 +1,76 @@
+#ifndef POPULATEDIALOG_H
+#define POPULATEDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class PopulateDialog;
+}
+
+class PopulatePlugin;
+class PopulateEngine;
+class QGridLayout;
+class DbListModel;
+class DbObjListModel;
+class Db;
+class QComboBox;
+class QCheckBox;
+class QToolButton;
+class QSignalMapper;
+class WidgetCover;
+
+class GUI_API_EXPORT PopulateDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit PopulateDialog(QWidget *parent = 0);
+ ~PopulateDialog();
+ void setDbAndTable(Db* db, const QString& table);
+
+ private:
+ struct GUI_API_EXPORT ColumnEntry
+ {
+ ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button);
+ ~ColumnEntry();
+
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* button = nullptr;
+ PopulateEngine* engine = nullptr;
+ };
+
+ void init();
+ PopulateEngine* getEngine(int selectedPluginIndex);
+ void deleteEngines(const QList<PopulateEngine*>& engines);
+ void rebuildEngines();
+
+ Ui::PopulateDialog *ui = nullptr;
+ QGridLayout* columnsGrid = nullptr;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ Db* db = nullptr;
+ QStringList pluginTitles;
+ QList<PopulatePlugin*> plugins;
+ QList<ColumnEntry> columnEntries;
+ QSignalMapper* checkMapper = nullptr;
+ QSignalMapper* buttonMapper = nullptr;
+ QHash<int,bool> columnsValid;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void refreshTables();
+ void refreshColumns();
+ void pluginSelected(int index);
+ void pluginSelected(QComboBox* combo, int index);
+ void configurePlugin(int index);
+ void updateColumnState(int index, bool updateGlobalState = true);
+ void updateState();
+ void finished();
+
+ public:
+ void accept();
+};
+
+#endif // POPULATEDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui
new file mode 100644
index 0000000..811b185
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateDialog</class>
+ <widget class="QDialog" name="PopulateDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>447</width>
+ <height>358</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Populate table</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="databaseGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Database</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QComboBox" name="databaseCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="tableGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Table</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QComboBox" name="tableCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QGroupBox" name="columnsGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="columnsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>409</width>
+ <height>144</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="columnsLayout"/>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QGroupBox" name="rowsGroup">
+ <property name="title">
+ <string>Number of rows to populate:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QSpinBox" name="rowsSpin">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PopulateDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PopulateDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp
new file mode 100644
index 0000000..a55464d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp
@@ -0,0 +1,42 @@
+#include "quitconfirmdialog.h"
+#include "ui_quitconfirmdialog.h"
+
+QuitConfirmDialog::QuitConfirmDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::QuitConfirmDialog)
+{
+ init();
+}
+
+QuitConfirmDialog::~QuitConfirmDialog()
+{
+ delete ui;
+}
+
+void QuitConfirmDialog::addMessage(const QString& msg)
+{
+ ui->itemList->addItem(msg);
+}
+
+void QuitConfirmDialog::setMessages(const QStringList& messages)
+{
+ for (const QString& msg : messages)
+ addMessage(msg);
+}
+
+int QuitConfirmDialog::getMessageCount() const
+{
+ return ui->itemList->count();
+}
+
+void QuitConfirmDialog::init()
+{
+ ui->setupUi(this);
+
+ QStyle* style = QApplication::style();
+ int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize);
+ QIcon icon = style->standardIcon(QStyle::SP_MessageBoxQuestion);
+
+ if (!icon.isNull())
+ ui->iconLabel->setPixmap(icon.pixmap(iconSize, iconSize));
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h
new file mode 100644
index 0000000..0907c9b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h
@@ -0,0 +1,28 @@
+#ifndef QUITCONFIRMDIALOG_H
+#define QUITCONFIRMDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+ class QuitConfirmDialog;
+}
+
+class QuitConfirmDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit QuitConfirmDialog(QWidget *parent = 0);
+ ~QuitConfirmDialog();
+
+ void addMessage(const QString& msg);
+ void setMessages(const QStringList& messages);
+ int getMessageCount() const;
+
+ private:
+ void init();
+
+ Ui::QuitConfirmDialog *ui = nullptr;
+};
+
+#endif // QUITCONFIRMDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui
new file mode 100644
index 0000000..6f97934
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QuitConfirmDialog</class>
+ <widget class="QDialog" name="QuitConfirmDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>489</width>
+ <height>186</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Uncommited changes</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLabel" name="msgLabel">
+ <property name="text">
+ <string>Are you sure you want to quit the application?
+
+Following items are pending:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="iconLabel">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QListWidget" name="itemList"/>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QuitConfirmDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QuitConfirmDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp
new file mode 100644
index 0000000..87a6d88
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp
@@ -0,0 +1,75 @@
+#include "searchtextdialog.h"
+#include "ui_searchtextdialog.h"
+#include "searchtextlocator.h"
+#include "common/unused.h"
+
+SearchTextDialog::SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::SearchTextDialog), textLocator(textLocator)
+{
+ ui->setupUi(this);
+ connect(textLocator, SIGNAL(replaceAvailable(bool)), this, SLOT(setReplaceAvailable(bool)));
+}
+
+SearchTextDialog::~SearchTextDialog()
+{
+ delete ui;
+}
+
+void SearchTextDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void SearchTextDialog::showEvent(QShowEvent* e)
+{
+ UNUSED(e);
+ ui->findEdit->setFocus();
+ ui->findEdit->selectAll();
+ configModifiedState = true;
+ setReplaceAvailable(false);
+}
+
+void SearchTextDialog::applyConfigToLocator()
+{
+ if (!configModifiedState)
+ return;
+
+ textLocator->setCaseSensitive(ui->caseSensitiveCheck->isChecked());
+ textLocator->setSearchBackwards(ui->backwardsCheck->isChecked());
+ textLocator->setRegularExpression(ui->regExpCheck->isChecked());
+ textLocator->setLookupString(ui->findEdit->text());
+ configModifiedState = false;
+}
+
+void SearchTextDialog::setReplaceAvailable(bool available)
+{
+ ui->replaceButton->setEnabled(available);
+}
+
+void SearchTextDialog::on_findButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->find();
+}
+
+void SearchTextDialog::on_replaceButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->setReplaceString(ui->replaceEdit->text());
+ textLocator->replaceAndFind();
+}
+
+void SearchTextDialog::on_replaceAllButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->setReplaceString(ui->replaceEdit->text());
+ textLocator->replaceAll();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h
new file mode 100644
index 0000000..54f6f72
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h
@@ -0,0 +1,39 @@
+#ifndef SEARCHTEXTDIALOG_H
+#define SEARCHTEXTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class SearchTextDialog;
+}
+
+class SearchTextLocator;
+
+class GUI_API_EXPORT SearchTextDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent = 0);
+ ~SearchTextDialog();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent* e);
+
+ private:
+ void applyConfigToLocator();
+
+ Ui::SearchTextDialog *ui = nullptr;
+ SearchTextLocator* textLocator = nullptr;
+ bool configModifiedState = false;
+
+ private slots:
+ void setReplaceAvailable(bool available);
+ void on_findButton_clicked();
+ void on_replaceButton_clicked();
+ void on_replaceAllButton_clicked();
+};
+
+#endif // SEARCHTEXTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui
new file mode 100644
index 0000000..ce9e12e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SearchTextDialog</class>
+ <widget class="QDialog" name="SearchTextDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>403</width>
+ <height>184</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="findEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="findLabel">
+ <property name="text">
+ <string>Find:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" rowspan="3" colspan="2">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="caseSensitiveCheck">
+ <property name="text">
+ <string>Case sensitive</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="backwardsCheck">
+ <property name="text">
+ <string>Search backwards</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="regExpCheck">
+ <property name="text">
+ <string>Regular expression matching</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="replaceButton">
+ <property name="text">
+ <string>Replace &amp;&amp;
+find next</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0" colspan="3">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="replaceLabel">
+ <property name="text">
+ <string>Replace with:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="replaceEdit"/>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="replaceAllButton">
+ <property name="text">
+ <string>Replace all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="findButton">
+ <property name="text">
+ <string>Find</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>findEdit</tabstop>
+ <tabstop>replaceEdit</tabstop>
+ <tabstop>caseSensitiveCheck</tabstop>
+ <tabstop>regExpCheck</tabstop>
+ <tabstop>backwardsCheck</tabstop>
+ <tabstop>findButton</tabstop>
+ <tabstop>replaceButton</tabstop>
+ <tabstop>replaceAllButton</tabstop>
+ <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SearchTextDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SearchTextDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp
new file mode 100644
index 0000000..b1451eb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp
@@ -0,0 +1,248 @@
+#include "sortdialog.h"
+#include "ui_sortdialog.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include <QComboBox>
+#include <QDebug>
+#include <QPushButton>
+
+SortDialog::SortDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::SortDialog)
+{
+ ui->setupUi(this);
+
+ initActions();
+ ui->list->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateButtons()));
+ connect(ui->list, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*,int)));
+ connect(ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(reset()));
+ connect(ui->list->model(), &QAbstractItemModel::rowsInserted, [=](const QModelIndex & parent, int start, int end)
+ {
+ UNUSED(parent);
+ UNUSED(end);
+ rebuildComboForItem(ui->list->topLevelItem(start));
+ });
+}
+
+SortDialog::~SortDialog()
+{
+ delete ui;
+}
+
+void SortDialog::setColumns(const QStringList& columns)
+{
+ originalColumns = columns;
+ ui->list->clear();
+
+ QTreeWidgetItem* item = nullptr;
+ for (int row = 0, total = columns.size(); row < total; ++row)
+ {
+ item = new QTreeWidgetItem({columns[row], "ASC"});
+ item->setData(2, Qt::UserRole, row);
+ fixItemFlags(item);
+ ui->list->insertTopLevelItem(row, item);
+ item->setCheckState(0, Qt::Unchecked);
+ }
+ ui->list->setHeaderLabels({tr("Column"), tr("Order")});
+ updateButtons();
+}
+
+QueryExecutor::SortList SortDialog::getSortOrder() const
+{
+ QueryExecutor::SortList sortOrder;
+
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ if (item->checkState(0) != Qt::Checked)
+ continue;
+
+ combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ sortOrder << QueryExecutor::Sort((combo->currentText() == "ASC" ? Qt::AscendingOrder : Qt::DescendingOrder), item->data(2, Qt::UserRole).toInt());
+ }
+ return sortOrder;
+}
+
+void SortDialog::setSortOrder(const QueryExecutor::SortList& sortOrder)
+{
+ // Translate sort order into more usable (in here) form
+ QHash<int,QueryExecutor::Sort::Order> checkedColumns;
+ QList<int> checkedColumnsOrder;
+ for (const QueryExecutor::Sort& sort : sortOrder)
+ {
+ checkedColumns[sort.column] = sort.order;
+ checkedColumnsOrder << sort.column;
+ }
+
+ // Select proper columns and set order
+ bool checked;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ checked = checkedColumns.contains(item->data(2, Qt::UserRole).toInt());
+ item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
+
+ combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ combo->setCurrentText(checkedColumns[row] == QueryExecutor::Sort::DESC ? "DESC" : "ASC");
+ }
+
+ // Get selected items as an ordered list of items (in order as defined in the sort order), so we can easly relocate them
+ QList<QTreeWidgetItem*> orderedItems;
+ for (int row : checkedColumnsOrder)
+ orderedItems << ui->list->topLevelItem(row);
+
+ // Move selected items in front, in the same order as they were mentioned in the sort order
+ int newRow = 0;
+ for (QTreeWidgetItem* itemToMove : orderedItems)
+ {
+ ui->list->takeTopLevelItem(ui->list->indexOfTopLevelItem(itemToMove));
+ ui->list->insertTopLevelItem(newRow++, itemToMove);
+ }
+
+ updateState();
+}
+
+QToolBar* SortDialog::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void SortDialog::updateState(QTreeWidgetItem* item)
+{
+ QComboBox* combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ if (!combo)
+ return;
+
+ combo->setEnabled(item->checkState(0) == Qt::Checked);
+}
+
+void SortDialog::updateState()
+{
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ updateState(ui->list->topLevelItem(row));
+}
+
+void SortDialog::fixItemFlags(QTreeWidgetItem* item)
+{
+ Qt::ItemFlags flags = item->flags();
+ flags |= Qt::ItemNeverHasChildren;
+ flags |= Qt::ItemIsUserCheckable;
+ flags ^= Qt::ItemIsDropEnabled;
+ flags ^= Qt::ItemIsEditable;
+ item->setFlags(flags);
+}
+
+void SortDialog::rebuildComboForItem(QTreeWidgetItem* item)
+{
+ QComboBox* combo = new QComboBox();
+ combo->addItems({"ASC", "DESC"});
+ combo->setCurrentText(item->text(1));
+ combo->setEnabled(item->checkState(0) == Qt::Checked);
+ ui->list->setItemWidget(item, 1, combo);
+ item->setSizeHint(1, combo->sizeHint()); // bug in Qt? without this comboboxes were misaligned vertically
+
+ connect(combo, &QComboBox::currentTextChanged, [item](const QString& newText)
+ {
+ item->setText(1, newText);
+ });
+
+ updateSortLabel();
+}
+
+void SortDialog::updateSortLabel()
+{
+ QStringList entries;
+ QTreeWidgetItem* item = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ if (item->checkState(0) != Qt::Checked)
+ continue;
+
+ entries << item->text(0) + " " + item->text(1);
+ }
+
+ if (entries.size() == 0)
+ {
+ ui->sortByLabel->setVisible(false);
+ }
+ else
+ {
+ static QString label = tr("Sort by: %1");
+ ui->sortByLabel->setText(label.arg(entries.join(", ")));
+ ui->sortByLabel->setVisible(true);
+ }
+}
+
+void SortDialog::itemChanged(QTreeWidgetItem* item, int column)
+{
+ if (column == 0)
+ updateState(item);
+
+ updateSortLabel();
+}
+
+void SortDialog::reset()
+{
+ setColumns(originalColumns);
+}
+
+void SortDialog::updateButtons()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ actionMap[MOVE_UP]->setEnabled(item && ui->list->itemAbove(item) != nullptr);
+ actionMap[MOVE_DOWN]->setEnabled(item && ui->list->itemBelow(item) != nullptr);
+}
+
+void SortDialog::moveCurrentUp()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->list->indexOfTopLevelItem(item);
+ if (row < 1)
+ return;
+
+ ui->list->takeTopLevelItem(row);
+ ui->list->insertTopLevelItem(row - 1, item);
+
+ QModelIndex idx = ui->list->model()->index(row - 1, 0);
+ ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current);
+ updateButtons();
+}
+
+void SortDialog::moveCurrentDown()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->list->indexOfTopLevelItem(item);
+ if (row + 1 >= ui->list->topLevelItemCount())
+ return;
+
+ ui->list->takeTopLevelItem(row);
+ ui->list->insertTopLevelItem(row + 1, item);
+
+ QModelIndex idx = ui->list->model()->index(row + 1, 0);
+ ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current);
+ updateButtons();
+}
+
+void SortDialog::createActions()
+{
+ createAction(MOVE_UP, ICONS.MOVE_UP, tr("Move column up"), this, SLOT(moveCurrentUp()), ui->toolbar, this);
+ createAction(MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move column down"), this, SLOT(moveCurrentDown()), ui->toolbar, this);
+}
+
+void SortDialog::setupDefShortcuts()
+{
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h
new file mode 100644
index 0000000..a103d90
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h
@@ -0,0 +1,60 @@
+#ifndef SORTDIALOG_H
+#define SORTDIALOG_H
+
+#include "db/queryexecutor.h"
+#include "common/extactioncontainer.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class SortDialog;
+}
+
+class QTreeWidgetItem;
+
+class GUI_API_EXPORT SortDialog : public QDialog, public ExtActionContainer
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ MOVE_UP,
+ MOVE_DOWN
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit SortDialog(QWidget *parent = 0);
+ ~SortDialog();
+
+ void setColumns(const QStringList& columns);
+ QueryExecutor::SortList getSortOrder() const;
+ void setSortOrder(const QueryExecutor::SortList& sortOrder);
+ QToolBar* getToolBar(int toolbar) const;
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void updateState(QTreeWidgetItem* item);
+ void updateState();
+ void fixItemFlags(QTreeWidgetItem* item);
+ void rebuildComboForItem(QTreeWidgetItem* item);
+ void updateSortLabel();
+
+ Ui::SortDialog *ui = nullptr;
+ QStringList originalColumns;
+
+ private slots:
+ void itemChanged(QTreeWidgetItem* item, int column);
+ void reset();
+ void updateButtons();
+ void moveCurrentUp();
+ void moveCurrentDown();
+};
+
+#endif // SORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui
new file mode 100644
index 0000000..23bee81
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SortDialog</class>
+ <widget class="QDialog" name="SortDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>457</width>
+ <height>357</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Sort by columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QToolBar" name="toolbar"/>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Order</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="sortByLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SortDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SortDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp
new file mode 100644
index 0000000..462e57f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp
@@ -0,0 +1,52 @@
+#include "triggercolumnsdialog.h"
+#include "ui_triggercolumnsdialog.h"
+
+#include <QCheckBox>
+
+TriggerColumnsDialog::TriggerColumnsDialog(QWidget *parent) :
+ QDialog(parent, Qt::Popup),
+ ui(new Ui::TriggerColumnsDialog)
+{
+ ui->setupUi(this);
+}
+
+TriggerColumnsDialog::~TriggerColumnsDialog()
+{
+ delete ui;
+}
+
+void TriggerColumnsDialog::addColumn(const QString& name, bool checked)
+{
+ QCheckBox* cb = new QCheckBox(name);
+ cb->setChecked(checked);
+ ui->mainWidget->layout()->addWidget(cb);
+ checkBoxList << cb;
+}
+
+QStringList TriggerColumnsDialog::getCheckedColumns() const
+{
+ QStringList columns;
+ foreach (QCheckBox* cb, checkBoxList)
+ {
+ if (cb->isChecked())
+ columns << cb->text();
+ }
+ return columns;
+}
+
+void TriggerColumnsDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void TriggerColumnsDialog::showEvent(QShowEvent*)
+{
+ adjustSize();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h
new file mode 100644
index 0000000..1ba0d69
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h
@@ -0,0 +1,33 @@
+#ifndef TRIGGERCOLUMNSDIALOG_H
+#define TRIGGERCOLUMNSDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class TriggerColumnsDialog;
+}
+
+class QCheckBox;
+
+class GUI_API_EXPORT TriggerColumnsDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit TriggerColumnsDialog(QWidget *parent = 0);
+ ~TriggerColumnsDialog();
+
+ void addColumn(const QString& name, bool checked);
+ QStringList getCheckedColumns() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent*);
+
+ private:
+ QList<QCheckBox*> checkBoxList;
+ Ui::TriggerColumnsDialog *ui = nullptr;
+};
+
+#endif // TRIGGERCOLUMNSDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui
new file mode 100644
index 0000000..4326fca
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TriggerColumnsDialog</class>
+ <widget class="QDialog" name="TriggerColumnsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>334</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Triggering columns:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="mainWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>239</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TriggerColumnsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TriggerColumnsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp
new file mode 100644
index 0000000..0707bd3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp
@@ -0,0 +1,413 @@
+#include "triggerdialog.h"
+#include "ui_triggerdialog.h"
+#include "parser/ast/sqliteselect.h"
+#include "services/notifymanager.h"
+#include "parser/ast/sqliteexpr.h"
+#include "triggercolumnsdialog.h"
+#include "common/utils_sql.h"
+#include "schemaresolver.h"
+#include "parser/parser.h"
+#include "iconmanager.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "uiutils.h"
+#include "services/codeformatter.h"
+#include <QDebug>
+#include <QMessageBox>
+#include <QPushButton>
+
+TriggerDialog::TriggerDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ db(db),
+ ui(new Ui::TriggerDialog)
+{
+ init();
+}
+
+TriggerDialog::~TriggerDialog()
+{
+ delete ui;
+}
+
+void TriggerDialog::setParentTable(const QString& name)
+{
+ forTable = true;
+ table = name;
+ initTrigger();
+}
+
+void TriggerDialog::setParentView(const QString& name)
+{
+ forTable = false;
+ view = name;
+ initTrigger();
+}
+
+void TriggerDialog::setTrigger(const QString& name)
+{
+ trigger = name;
+ originalTriggerName = name;
+ existingTrigger = true;
+ initTrigger();
+}
+
+void TriggerDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void TriggerDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateDdlTab(int)));
+ connect(ui->actionColumns, SIGNAL(clicked()), this, SLOT(showColumnsDialog()));
+
+ // On object combo
+ ui->onCombo->setEnabled(false);
+ connect(ui->onCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(tableChanged(QString)));
+
+ // Action combo
+ ui->actionCombo->addItems({
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::DELETE),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::INSERT),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE_OF)
+ });
+ connect(ui->actionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+
+ // Scope combo
+ ui->scopeCombo->addItems({
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::null),
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_ROW)
+ });
+ if (db->getDialect() == Dialect::Sqlite2)
+ {
+ ui->scopeCombo->addItems({
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT)
+ });
+ }
+
+ // Precondition
+ connect(ui->preconditionCheck, SIGNAL(clicked()), this, SLOT(updateState()));
+ connect(ui->preconditionEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->preconditionEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->preconditionEdit->setDb(db);
+
+ // Code
+ connect(ui->codeEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->codeEdit->setDb(db);
+}
+
+void TriggerDialog::initTrigger()
+{
+ // Name edit
+ ui->nameEdit->setText(trigger);
+
+ if (trigger.isNull())
+ {
+ createTrigger = SqliteCreateTriggerPtr::create();
+ createTrigger->event = new SqliteCreateTrigger::Event();
+ }
+ else
+ {
+ parseDdl();
+ readTrigger();
+ }
+
+ // Event combo
+ if (forTable)
+ {
+ ui->whenCombo->addItems({
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::null),
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::BEFORE),
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::AFTER)
+ });
+ }
+ else
+ {
+ ui->whenCombo->addItems({
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::INSTEAD_OF)
+ });
+ ui->whenCombo->setEnabled(false);
+ ui->onLabel->setText(tr("On view:"));
+ }
+
+ if (!view.isNull() || !table.isNull())
+ {
+ readColumns();
+ QString target = getTargetObjectName();
+ ui->onCombo->addItem(target);
+ ui->onCombo->setCurrentText(target);
+ }
+
+ // Precondition and code edits
+ setupVirtualSqls();
+
+ updateState();
+}
+
+void TriggerDialog::parseDdl()
+{
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(trigger, SchemaResolver::TRIGGER);
+ if (!parsedObject.dynamicCast<SqliteCreateTrigger>())
+ {
+ notifyError(tr("Could not process trigger %1 correctly. Unable to open a trigger dialog.").arg(trigger));
+ reject();
+ return;
+ }
+
+ createTrigger = parsedObject.dynamicCast<SqliteCreateTrigger>();
+ ddl = createTrigger->detokenize();
+}
+
+void TriggerDialog::readTrigger()
+{
+ if (!createTrigger)
+ return;
+
+ forTable = createTrigger->eventTime != SqliteCreateTrigger::Time::INSTEAD_OF;
+ if (forTable)
+ table = createTrigger->table;
+ else
+ view = createTrigger->table;
+
+ ui->onCombo->addItem(createTrigger->table);
+ ui->onCombo->setCurrentText(createTrigger->table);
+ ui->whenCombo->setCurrentText(SqliteCreateTrigger::time(createTrigger->eventTime));
+ ui->actionCombo->setCurrentText(SqliteCreateTrigger::Event::typeToString(createTrigger->event->type));
+ ui->scopeCombo->setCurrentText(SqliteCreateTrigger::scopeToString(createTrigger->scope));
+ if (createTrigger->precondition)
+ {
+ ui->preconditionCheck->setChecked(true);
+ ui->preconditionEdit->setPlainText(createTrigger->precondition->detokenize());
+ }
+
+ if (createTrigger->queries.size() > 0)
+ {
+ QStringList sqls;
+ foreach (SqliteQuery* query, createTrigger->queries)
+ sqls << query->detokenize();
+
+ ui->codeEdit->setPlainText(sqls.join(";\n")+";");
+ }
+}
+
+void TriggerDialog::setupVirtualSqls()
+{
+ Dialect dialect = db->getDialect();
+ static QString preconditionVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 WHEN %3 BEGIN SELECT 1; END;");
+ static QString codeVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 BEGIN %3 END;");
+ ui->codeEdit->setVirtualSqlCompleteSemicolon(true);
+ if (!trigger.isNull())
+ {
+ if (createTrigger) // if false, then there was a parsing error in parseDdl().
+ {
+ ui->preconditionEdit->setVirtualSqlExpression(
+ preconditionVirtSql.arg(wrapObjIfNeeded(trigger, dialect),
+ wrapObjIfNeeded(createTrigger->table, dialect),
+ "%1"));
+
+ ui->codeEdit->setVirtualSqlExpression(
+ codeVirtSql.arg(
+ wrapObjIfNeeded(trigger, dialect),
+ wrapObjIfNeeded(createTrigger->table, dialect),
+ "%1"));
+ }
+ }
+ else if (!table.isNull() || !view.isNull())
+ {
+ ui->preconditionEdit->setVirtualSqlExpression(
+ preconditionVirtSql.arg("trig",
+ wrapObjIfNeeded(getTargetObjectName(), dialect),
+ "%1"));
+
+ ui->codeEdit->setVirtualSqlExpression(
+ codeVirtSql.arg("trig",
+ wrapObjIfNeeded(getTargetObjectName(), dialect),
+ "%1"));
+ }
+ else
+ {
+ qCritical() << "TriggerDialog is in invalid state. Called initTrigger() but none of trigger/table/view values are set.";
+ }
+}
+
+void TriggerDialog::readColumns()
+{
+ SchemaResolver resolver(db);
+ if (!table.isNull())
+ targetColumns = resolver.getTableColumns(table);
+ else if (!view.isNull())
+ targetColumns = resolver.getViewColumns(view);
+ else
+ targetColumns.clear();
+
+ if (createTrigger)
+ selectedColumns = createTrigger->event->columnNames;
+}
+
+QString TriggerDialog::getTargetObjectName() const
+{
+ if (!table.isNull())
+ return table;
+
+ return view;
+}
+
+void TriggerDialog::rebuildTrigger()
+{
+ /*
+ * Trigger is not rebuilt into SqliteCreateTrigger, because it's impossible to parse
+ * precondition or queries if they are invalid and we still need an invalid queries
+ * to be presented on the DDL tab.
+ */
+ static const QString tempDdl = QStringLiteral("CREATE TRIGGER %1%2 %3%4 ON %5%6%7 BEGIN %8 END;");
+
+ Dialect dialect = db->getDialect();
+ QString trigName = wrapObjIfNeeded(ui->nameEdit->text(), dialect);
+ QString when = ui->whenCombo->currentText();
+ QString action = ui->actionCombo->currentText();
+ QString columns = "";
+ QString target = wrapObjIfNeeded(getTargetObjectName(), dialect);
+ QString scope = ui->scopeCombo->currentText();
+ QString precondition = "";
+ QString queries = ui->codeEdit->toPlainText();
+
+ // Columns
+ SqliteCreateTrigger::Event::Type actionType = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ if (actionType == SqliteCreateTrigger::Event::UPDATE_OF)
+ {
+ QStringList colNames;
+ foreach (const QString& colName, selectedColumns)
+ colNames << wrapObjIfNeeded(colName, dialect);
+
+ columns = " "+colNames.join(", ");
+ }
+
+ // Precondition
+ if (ui->preconditionCheck->isChecked())
+ precondition = " WHEN "+ui->preconditionEdit->toPlainText();
+
+ // Queries
+ if (!queries.trimmed().endsWith(";"))
+ queries += ";";
+
+ // When
+ if (!when.isNull())
+ when.prepend(" ");
+
+ // Scope
+ if (!scope.isNull())
+ scope.prepend(" ");
+
+ ddl = tempDdl.arg(trigName).arg(when).arg(action).arg(columns).arg(target).arg(scope).arg(precondition).arg(queries);
+}
+
+void TriggerDialog::updateState()
+{
+ SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ ui->actionColumns->setEnabled(type == SqliteCreateTrigger::Event::UPDATE_OF);
+ ui->preconditionEdit->setEnabled(ui->preconditionCheck->isChecked());
+ updateValidation();
+}
+
+void TriggerDialog::updateValidation()
+{
+ SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ bool columnsOk = (type != SqliteCreateTrigger::Event::UPDATE_OF || selectedColumns.size() > 0);
+
+ bool preconditionOk = (!ui->preconditionCheck->isChecked() ||
+ (ui->preconditionEdit->isSyntaxChecked() && !ui->preconditionEdit->haveErrors()));
+
+ bool codeOk = (ui->codeEdit->isSyntaxChecked() && !ui->codeEdit->haveErrors());
+
+ setValidState(ui->preconditionCheck, preconditionOk, tr("Enter a valid condition."));
+ setValidState(ui->codeEdit, codeOk, tr("Enter a valid trigger code."));
+ ui->actionColumns->setIcon(columnsOk ? ICONS.TRIGGER_COLUMNS : ICONS.TRIGGER_COLUMNS_INVALID);
+
+ QPushButton* okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
+ okButton->setEnabled(columnsOk && preconditionOk && codeOk);
+}
+
+void TriggerDialog::showColumnsDialog()
+{
+ TriggerColumnsDialog dialog(this);
+ foreach (const QString& colName, targetColumns)
+ dialog.addColumn(colName, selectedColumns.contains(colName, Qt::CaseInsensitive));
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ QStringList newColumns = dialog.getCheckedColumns();
+ selectedColumns = newColumns;
+ updateValidation();
+}
+
+void TriggerDialog::updateDdlTab(int tabIdx)
+{
+ if (tabIdx != 1)
+ return;
+
+ rebuildTrigger();
+ QString formatted = FORMATTER->format("sql", ddl, db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void TriggerDialog::tableChanged(const QString& newValue)
+{
+ ui->preconditionEdit->setTriggerContext(newValue);
+ ui->codeEdit->setTriggerContext(newValue);
+}
+
+void TriggerDialog::accept()
+{
+ rebuildTrigger();
+
+ Dialect dialect = db->getDialect();
+
+ QStringList sqls;
+ if (existingTrigger)
+ sqls << QString("DROP TRIGGER %1").arg(wrapObjIfNeeded(originalTriggerName, dialect));
+
+ sqls << ddl;
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ ChainExecutor executor;
+ executor.setDb(db);
+ executor.setAsync(false);
+ executor.setQueries(sqls);
+ executor.exec();
+
+ if (executor.getSuccessfulExecution())
+ {
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ QDialog::accept();
+ DBTREE->refreshSchema(db);
+ return;
+ }
+
+ QMessageBox::critical(this, tr("Error", "trigger dialog"), tr("An error occurred while executing SQL statements:\n%1")
+ .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h
new file mode 100644
index 0000000..d8e7ed4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h
@@ -0,0 +1,62 @@
+#ifndef TRIGGERDIALOG_H
+#define TRIGGERDIALOG_H
+
+#include "db/db.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class TriggerDialog;
+}
+
+class GUI_API_EXPORT TriggerDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit TriggerDialog(Db* db, QWidget *parent = 0);
+ ~TriggerDialog();
+
+ void setParentTable(const QString& name);
+ void setParentView(const QString& name);
+ void setTrigger(const QString& name);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void initTrigger();
+ void parseDdl();
+ void readTrigger();
+ void setupVirtualSqls();
+ void readColumns();
+ QString getTargetObjectName() const;
+ void rebuildTrigger();
+
+ QString originalTriggerName;
+ QString trigger;
+ QString table;
+ QString view;
+ Db* db = nullptr;
+ bool forTable = true;
+ bool existingTrigger = false;
+ QStringList targetColumns;
+ QStringList selectedColumns;
+ QString ddl;
+ SqliteCreateTriggerPtr createTrigger;
+ Ui::TriggerDialog *ui = nullptr;
+
+ private slots:
+ void updateState();
+ void updateValidation();
+ void showColumnsDialog();
+ void updateDdlTab(int tabIdx);
+ void tableChanged(const QString& newValue);
+
+ public slots:
+ void accept();
+};
+
+#endif // TRIGGERDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui
new file mode 100644
index 0000000..bf3da0a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TriggerDialog</class>
+ <widget class="QDialog" name="TriggerDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>490</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Trigger dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="triggerTab">
+ <attribute name="title">
+ <string>Trigger</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="2">
+ <widget class="QLabel" name="onLabel">
+ <property name="text">
+ <string>On table:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QComboBox" name="whenCombo"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="actionLabel">
+ <property name="text">
+ <string>Action:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QComboBox" name="actionCombo"/>
+ </item>
+ <item row="3" column="2">
+ <widget class="QComboBox" name="onCombo"/>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="6" column="0" colspan="3">
+ <widget class="QCheckBox" name="preconditionCheck">
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Pre-condition:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="3">
+ <widget class="QComboBox" name="scopeCombo">
+ <property name="toolTip">
+ <string>The scope is still not fully supported by the SQLite database.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Trigger name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="whenLabel">
+ <property name="text">
+ <string>When:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QToolButton" name="actionColumns">
+ <property name="toolTip">
+ <string>List of columns for UPDATE OF action.</string>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="3">
+ <widget class="QLabel" name="scopeLabel">
+ <property name="text">
+ <string>Scope:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="codeLabel">
+ <property name="text">
+ <string>Code:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0" colspan="3">
+ <widget class="SqlEditor" name="codeEdit">
+ <property name="toolTip">
+ <string>Trigger statements to be executed.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0" colspan="3">
+ <widget class="SqlEditor" name="preconditionEdit">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.&lt;/p&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddlTab">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlView" name="ddlEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>whenCombo</tabstop>
+ <tabstop>actionCombo</tabstop>
+ <tabstop>actionColumns</tabstop>
+ <tabstop>onCombo</tabstop>
+ <tabstop>scopeCombo</tabstop>
+ <tabstop>preconditionCheck</tabstop>
+ <tabstop>preconditionEdit</tabstop>
+ <tabstop>codeEdit</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>ddlEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TriggerDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TriggerDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp
new file mode 100644
index 0000000..5521245
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp
@@ -0,0 +1,31 @@
+#include "versionconvertsummarydialog.h"
+#include "ui_versionconvertsummarydialog.h"
+
+VersionConvertSummaryDialog::VersionConvertSummaryDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::VersionConvertSummaryDialog)
+{
+ ui->setupUi(this);
+
+ ui->diffTable->setLeftLabel(tr("Before"));
+ ui->diffTable->setRightLabel(tr("After"));
+ ui->diffTable->horizontalHeader()->setVisible(true);
+}
+
+VersionConvertSummaryDialog::~VersionConvertSummaryDialog()
+{
+ delete ui;
+}
+
+void VersionConvertSummaryDialog::setSides(const QList<QPair<QString, QString> >& data)
+{
+ ui->diffTable->setSides(data);
+}
+
+
+void VersionConvertSummaryDialog::showEvent(QShowEvent* e)
+{
+ QDialog::showEvent(e);
+ ui->diffTable->updateSizes();
+
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h
new file mode 100644
index 0000000..fc63076
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h
@@ -0,0 +1,28 @@
+#ifndef VERSIONCONVERTSUMMARYDIALOG_H
+#define VERSIONCONVERTSUMMARYDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class VersionConvertSummaryDialog;
+}
+
+class GUI_API_EXPORT VersionConvertSummaryDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit VersionConvertSummaryDialog(QWidget *parent = 0);
+ ~VersionConvertSummaryDialog();
+
+ void setSides(const QList<QPair<QString, QString>>& data);
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ Ui::VersionConvertSummaryDialog *ui = nullptr;
+};
+
+#endif // VERSIONCONVERTSUMMARYDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui
new file mode 100644
index 0000000..a67db1e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VersionConvertSummaryDialog</class>
+ <widget class="QDialog" name="VersionConvertSummaryDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>497</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Database version convert</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Following changes to the SQL statements will be made:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="SqlCompareView" name="diffTable">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlCompareView</class>
+ <extends>QTableWidget</extends>
+ <header>sqlcompareview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VersionConvertSummaryDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VersionConvertSummaryDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp b/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp
new file mode 100644
index 0000000..9df36d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp
@@ -0,0 +1,185 @@
+#include "formmanager.h"
+#include "services/config.h"
+#include "services/pluginmanager.h"
+#include "sqlitestudio.h"
+#include "uiloader.h"
+#include "common/unused.h"
+#include "common/global.h"
+#include <QFile>
+#include <QDir>
+#include <QRegularExpression>
+#include <QApplication>
+#include <QDebug>
+
+FormManager::FormManager()
+{
+ init();
+}
+
+FormManager::~FormManager()
+{
+ if (uiLoader)
+ {
+ delete uiLoader;
+ uiLoader = nullptr;
+ }
+}
+
+QWidget* FormManager::createWidget(const QString& name)
+{
+ if (!widgetNameToFullPath.contains(name))
+ {
+ qCritical() << "Asked for widget name which isn't managed by FormManager:" << name << ", while available widgets are:"
+ << widgetNameToFullPath.keys();
+ return nullptr;
+ }
+ return createWidgetByFullPath(widgetNameToFullPath[name]);
+}
+
+bool FormManager::hasWidget(const QString& name)
+{
+ return widgetNameToFullPath.contains(name);
+}
+
+QStringList FormManager::getAvailableForms() const
+{
+ return widgetNameToFullPath.keys();
+}
+
+QWidget* FormManager::createWidgetByFullPath(const QString& path)
+{
+ QWidget* widget = uiLoader->load(path);
+ if (!widget)
+ {
+ qCritical() << "Error occured while loading ui file:" << path << ". Error message: "
+ << uiLoader->errorString();
+ return nullptr;
+ }
+ return widget;
+}
+
+void FormManager::rescanResources(Plugin* plugin, PluginType* pluginType)
+{
+ UNUSED(pluginType);
+ rescanResources(plugin->getName());
+}
+
+void FormManager::rescanResources(const QString& pluginName)
+{
+ if (PLUGINS->isBuiltIn(pluginName))
+ return;
+
+ for (const QString& widgetName : resourceForms)
+ widgetNameToFullPath.remove(widgetName);
+
+ resourceForms.clear();
+ loadRecurently(":/forms", "");
+}
+
+void FormManager::pluginsAboutToMassUnload()
+{
+ disconnect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*)));
+ disconnect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString)));
+}
+
+void FormManager::pluginsInitiallyLoaded()
+{
+ load();
+
+ connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*)));
+ connect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString)));
+ connect(PLUGINS, SIGNAL(aboutToQuit()), this, SLOT(pluginsAboutToMassUnload()));
+ disconnect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded()));
+}
+
+void FormManager::init()
+{
+ uiLoader = new UiLoader();
+
+ if (PLUGINS->arePluginsInitiallyLoaded())
+ pluginsInitiallyLoaded();
+ else
+ connect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded()));
+}
+
+void FormManager::load()
+{
+ QStringList dirs;
+ dirs += qApp->applicationDirPath() + "/forms";
+ dirs += ":/forms";
+ dirs += QDir(CFG->getConfigDir()).absoluteFilePath("forms");
+
+ QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_FORMS");
+ if (!envDirs.isNull())
+ dirs += envDirs.split(PATH_LIST_SEPARATOR);
+
+ dirs += PLUGINS->getPluginDirs();
+
+#ifdef FORMS_DIR
+ dirs += STRINGIFY(FORMS_DIR);
+#endif
+
+ foreach (QString dirPath, dirs)
+ loadRecurently(dirPath, "");
+}
+
+void FormManager::loadRecurently(const QString& path, const QString& prefix)
+{
+ static const QStringList fileExtensions = {"*.ui", "*.UI"};
+
+ QDir dir(path);
+ QString fullPath;
+ QString widgetName;
+ foreach (QFileInfo entry, dir.entryInfoList(fileExtensions, QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot|QDir::Readable))
+ {
+ fullPath = entry.absoluteFilePath();
+ if (entry.isDir())
+ {
+ loadRecurently(fullPath, prefix+entry.fileName()+"_");
+ continue;
+ }
+
+ qDebug() << "Loading form file:" << fullPath;
+
+ widgetName = getWidgetName(fullPath);
+ if (widgetName.isNull())
+ continue;
+
+ if (widgetNameToFullPath.contains(widgetName))
+ {
+ qCritical() << "Widget named" << widgetName << "was already loaded by FormManager from file" << widgetNameToFullPath[widgetName]
+ << "therefore file" << fullPath << "will be ignored";
+ continue;
+ }
+
+ widgetNameToFullPath[widgetName] = fullPath;
+ if (fullPath.startsWith(":/"))
+ resourceForms << widgetName;
+ }
+}
+
+QString FormManager::getWidgetName(const QString& path)
+{
+ static const QRegularExpression re(R"(<widget class\=\"\w+\" name\=\"(\w+)\">)");
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ qWarning() << "Could not open" << path << "for reading. Form file ignored.";
+ return QString::null;
+ }
+
+ QString contents = file.readAll();
+ file.close();
+
+ QRegularExpressionMatch match = re.match(contents);
+ if (!match.hasMatch())
+ {
+ qWarning() << "Could not match widget in" << path << " document. File ignored.";
+ return QString::null;
+ }
+
+ QString widgetName = match.captured(1);
+
+ return widgetName;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/formmanager.h b/SQLiteStudio3/guiSQLiteStudio/formmanager.h
new file mode 100644
index 0000000..41f98ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/formmanager.h
@@ -0,0 +1,43 @@
+#ifndef FORMMANAGER_H
+#define FORMMANAGER_H
+
+#include "guiSQLiteStudio_global.h"
+#include "mainwindow.h"
+#include <QHash>
+#include <QString>
+
+class UiLoader;
+
+class GUI_API_EXPORT FormManager : public QObject
+{
+ Q_OBJECT
+
+ public:
+ FormManager();
+ virtual ~FormManager();
+
+ QWidget* createWidget(const QString& name);
+ bool hasWidget(const QString& name);
+ QStringList getAvailableForms() const;
+
+ private:
+ void init();
+ void load();
+ void loadRecurently(const QString& path, const QString& prefix = "");
+ QString getWidgetName(const QString& path);
+ QWidget* createWidgetByFullPath(const QString& path);
+
+ UiLoader* uiLoader = nullptr;
+ QHash<QString,QString> widgetNameToFullPath;
+ QStringList resourceForms;
+
+ private slots:
+ void rescanResources(Plugin* plugin, PluginType* pluginType);
+ void rescanResources(const QString& pluginName);
+ void pluginsAboutToMassUnload();
+ void pluginsInitiallyLoaded();
+};
+
+#define FORMS MainWindow::getInstance()->getFormManager()
+
+#endif // FORMMANAGER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui b/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui
new file mode 100644
index 0000000..67f1bd6
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Active SQL formatter plugin</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QComboBox" name="comboBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/formview.cpp b/SQLiteStudio3/guiSQLiteStudio/formview.cpp
new file mode 100644
index 0000000..ab51f3e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/formview.cpp
@@ -0,0 +1,274 @@
+#include "formview.h"
+#include "common/unused.h"
+#include "datagrid/sqlquerymodel.h"
+#include "datagrid/sqlqueryview.h"
+#include "widgetresizer.h"
+#include "datagrid/sqlqueryitem.h"
+#include "uiconfig.h"
+#include "common/datawidgetmapper.h"
+#include <QGroupBox>
+#include <QVBoxLayout>
+#include <QResizeEvent>
+#include <QDebug>
+
+CFG_KEYS_DEFINE(FormView)
+
+FormView::FormView(QWidget *parent) :
+ QScrollArea(parent)
+{
+ init();
+}
+
+void FormView::init()
+{
+ setWidgetResizable(true);
+ initActions();
+
+ dataMapper = new DataWidgetMapper(this);
+ dataMapper->setSubmitFilter([](QWidget* w) -> bool
+ {
+ return dynamic_cast<MultiEditor*>(w)->isModified();
+ });
+ connect(dataMapper, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int)));
+
+ contents = new QWidget();
+ QVBoxLayout *contentsLayout = new QVBoxLayout();
+ contentsLayout->setSpacing(spacing);
+ contentsLayout->setMargin(margins);
+ contents->setLayout(contentsLayout);
+
+ connect(CFG_UI.General.DataEditorsOrder, SIGNAL(changed(QVariant)), this, SLOT(reload()));
+
+ setWidget(contents);
+}
+
+SqlQueryModel* FormView::getModel() const
+{
+ return model.data();
+}
+
+void FormView::setModel(SqlQueryModel* value)
+{
+ if (!model.isNull())
+ {
+ disconnect(model.data(), SIGNAL(loadingEnded(bool)), this, SLOT(dataLoaded(bool)));
+ disconnect(value, SIGNAL(commitStatusChanged(bool)), this, SLOT(gridCommitRollbackStatusChanged()));
+ }
+
+ model = value;
+ connect(value, SIGNAL(loadingEnded(bool)), this, SLOT(dataLoaded(bool)));
+ connect(value, SIGNAL(commitStatusChanged(bool)), this, SLOT(gridCommitRollbackStatusChanged()));
+}
+
+void FormView::load()
+{
+ reloadInternal();
+ dataMapper->toFirst();
+}
+
+void FormView::reload()
+{
+ int idx = dataMapper->getCurrentIndex();
+ reloadInternal();
+ dataMapper->setCurrentIndex(idx);
+}
+
+void FormView::focusFirstEditor()
+{
+ if (editors.size() == 0)
+ return;
+
+ editors.first()->focusThisEditor();
+}
+
+void FormView::reloadInternal()
+{
+ // Cleanup
+ dataMapper->clearMapping();
+ foreach (QWidget* widget, widgets)
+ {
+ contents->layout()->removeWidget(widget);
+ delete widget;
+ }
+ widgets.clear();
+ editors.clear();
+ readOnly.clear();
+
+ // Recreate
+ dataMapper->setModel(model.data());
+ int i = 0;
+ foreach (SqlQueryModelColumnPtr column, model->getColumns())
+ addColumn(i++, column->displayName, column->dataType, (column->editionForbiddenReason.size() > 0));
+}
+
+bool FormView::isModified() const
+{
+ return valueModified;
+}
+
+void FormView::addColumn(int colIdx, const QString& name, const DataType& dataType, bool readOnly)
+{
+ // Group with label
+ QString groupLabel = name;
+ if (!dataType.toString().isEmpty())
+ groupLabel += " (" + dataType.toString() + ")";
+
+ QGroupBox* group = new QGroupBox(groupLabel);
+ QFont font = group->font();
+ font.setBold(true);
+ group->setFont(font);
+
+ QVBoxLayout *vbox = new QVBoxLayout();
+ vbox->setSpacing(spacing);
+ vbox->setMargin(margins);
+ group->setLayout(vbox);
+
+ // MultiEditor
+ MultiEditor* multiEditor = new MultiEditor();
+ font.setBold(false);
+ multiEditor->setFont(font);
+ multiEditor->setReadOnly(readOnly);
+ dataMapper->addMapping(multiEditor, colIdx, "value");
+ vbox->addWidget(multiEditor);
+ widgets << group;
+ editors << multiEditor;
+ contents->layout()->addWidget(group);
+ this->readOnly << readOnly;
+
+ connect(multiEditor, SIGNAL(modified()), this, SLOT(editorValueModified()));
+
+ // MultiEditor editors
+ multiEditor->setDataType(dataType);
+
+ // Resizer
+ WidgetResizer* resizer = new WidgetResizer(Qt::Vertical);
+ resizer->setWidget(group);
+ resizer->setWidgetMinimumSize(0, minimumFieldHeight);
+ widgets << resizer;
+ contents->layout()->addWidget(resizer);
+}
+
+bool FormView::isCurrentRowModifiedInGrid()
+{
+ if (!model)
+ return false;
+
+ QModelIndex startIdx = model->index(gridView->getCurrentIndex().row(), 0);
+ QModelIndex endIdx = model->index(gridView->getCurrentIndex().row(), model->columnCount() - 1);
+ return model->findIndexes(startIdx, endIdx, SqlQueryItem::DataRole::UNCOMMITED, true, 1).size() > 0;
+}
+
+void FormView::updateDeletedState()
+{
+ SqlQueryItem* item = model->itemFromIndex(dataMapper->getCurrentIndex(), 0);
+ if (!item)
+ return;
+
+ bool deleted = item->isDeletedRow();
+ int i = 0;
+ foreach (MultiEditor* editor, editors)
+ {
+ editor->setDeletedRow(deleted);
+ editor->setReadOnly(readOnly[i++] || deleted);
+ }
+}
+
+void FormView::dataLoaded(bool successful)
+{
+ if (successful)
+ load();
+}
+
+void FormView::currentIndexChanged(int index)
+{
+ valueModified = false;
+ emit commitStatusChanged();
+
+ if (gridView.isNull())
+ return;
+
+ if (currentIndexUpdating)
+ return;
+
+ currentIndexUpdating = true;
+ gridView->setCurrentRow(index);
+ currentIndexUpdating = false;
+
+ // If row was deleted, we need to make fields readonly
+ updateDeletedState();
+
+ emit currentRowChanged();
+}
+
+void FormView::editorValueModified()
+{
+ valueModified = true;
+ emit commitStatusChanged();
+}
+
+void FormView::gridCommitRollbackStatusChanged()
+{
+ valueModified = isCurrentRowModifiedInGrid();
+ emit commitStatusChanged();
+}
+
+void FormView::copyDataToGrid()
+{
+ dataMapper->submit();
+}
+
+void FormView::updateFromGrid()
+{
+ currentIndexUpdating = true;
+
+ dataMapper->setCurrentIndex(gridView->getCurrentIndex().row());
+
+ // Already modified in grid?
+ valueModified = isCurrentRowModifiedInGrid();
+
+ currentIndexUpdating = false;
+
+ updateDeletedState();
+
+ emit currentRowChanged();
+}
+
+SqlQueryView* FormView::getGridView() const
+{
+ return gridView.data();
+}
+
+void FormView::setGridView(SqlQueryView* value)
+{
+ gridView = value;
+}
+
+int FormView::getCurrentRow()
+{
+ return dataMapper->getCurrentIndex();
+}
+
+void FormView::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit row", "form view"), this, SIGNAL(requestForCommit()), this);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback row", "form view"), this, SIGNAL(requestForRollback()), this);
+ createAction(FIRST_ROW, ICONS.PAGE_FIRST, tr("First row", "form view"), this, SIGNAL(requestForFirstRow()), this);
+ createAction(PREV_ROW, ICONS.PAGE_PREV, tr("Previous row", "form view"), this, SIGNAL(requestForPrevRow()), this);
+ createAction(NEXT_ROW, ICONS.PAGE_NEXT, tr("Next row", "form view"), this, SIGNAL(requestForNextRow()), this);
+ createAction(LAST_ROW, ICONS.PAGE_LAST, tr("Last row", "form view"), this, SIGNAL(requestForLastRow()), this);
+ createAction(INSERT_ROW, ICONS.INSERT_ROW, tr("Insert new row", "form view"), this, SIGNAL(requestForRowInsert()), this);
+ createAction(DELETE_ROW, ICONS.DELETE_ROW, tr("Delete current row", "form view"), this, SIGNAL(requestForRowDelete()), this);
+}
+
+void FormView::setupDefShortcuts()
+{
+ setShortcutContext({ROLLBACK, COMMIT, NEXT_ROW, PREV_ROW, FIRST_ROW, LAST_ROW, INSERT_ROW, DELETE_ROW}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(FormView, Action);
+}
+
+QToolBar* FormView::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/formview.h b/SQLiteStudio3/guiSQLiteStudio/formview.h
new file mode 100644
index 0000000..a6a9708
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/formview.h
@@ -0,0 +1,114 @@
+#ifndef FORMVIEW_H
+#define FORMVIEW_H
+
+#include "guiSQLiteStudio_global.h"
+#include "datagrid/sqlquerymodelcolumn.h"
+#include "multieditor/multieditor.h"
+#include <QWidget>
+#include <QPointer>
+#include <QScrollArea>
+#include <common/extactioncontainer.h>
+
+class SqlQueryModel;
+class SqlQueryView;
+class DataWidgetMapper;
+
+CFG_KEY_LIST(FormView, QObject::tr("Data form view"),
+ CFG_KEY_ENTRY(COMMIT, Qt::CTRL + Qt::Key_Return, QObject::tr("Commit changes for current row"))
+ CFG_KEY_ENTRY(ROLLBACK, Qt::CTRL + Qt::Key_Backspace, QObject::tr("Rollback changes for current row"))
+ CFG_KEY_ENTRY(FIRST_ROW, Qt::CTRL + Qt::Key_PageUp, QObject::tr("Go to first row on current page"))
+ CFG_KEY_ENTRY(NEXT_ROW, Qt::CTRL + Qt::Key_Right, QObject::tr("Go to next row"))
+ CFG_KEY_ENTRY(PREV_ROW, Qt::CTRL + Qt::Key_Left, QObject::tr("Go to previous row"))
+ CFG_KEY_ENTRY(LAST_ROW, Qt::CTRL + Qt::Key_PageDown, QObject::tr("Go to last row on current page"))
+ CFG_KEY_ENTRY(INSERT_ROW, Qt::Key_Insert, QObject::tr("Insert new row"))
+ CFG_KEY_ENTRY(DELETE_ROW, Qt::CTRL + Qt::Key_Delete, QObject::tr("Delete current row"))
+)
+
+class GUI_API_EXPORT FormView : public QScrollArea, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ FIRST_ROW,
+ NEXT_ROW,
+ PREV_ROW,
+ LAST_ROW,
+ INSERT_ROW,
+ DELETE_ROW
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit FormView(QWidget *parent = 0);
+
+ void init();
+
+ SqlQueryModel* getModel() const;
+ void setModel(SqlQueryModel* value);
+
+ bool isModified() const;
+
+ SqlQueryView* getGridView() const;
+ void setGridView(SqlQueryView* value);
+
+ int getCurrentRow();
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void reloadInternal();
+ void addColumn(int colIdx, const QString& name, const DataType& dataType, bool readOnly);
+ bool isCurrentRowModifiedInGrid();
+ void updateDeletedState();
+
+ static const int margins = 2;
+ static const int spacing = 2;
+ static const int minimumFieldHeight = 40;
+
+ DataWidgetMapper* dataMapper = nullptr;
+ QPointer<SqlQueryView> gridView;
+ QPointer<SqlQueryModel> model;
+ QWidget* contents = nullptr;
+ QList<QWidget*> widgets;
+ QList<MultiEditor*> editors;
+ QList<bool> readOnly;
+ bool valueModified = false;
+ bool currentIndexUpdating = false;
+
+ private slots:
+ void dataLoaded(bool successful);
+ void currentIndexChanged(int index);
+ void editorValueModified();
+ void gridCommitRollbackStatusChanged();
+
+ public slots:
+ void copyDataToGrid();
+ void updateFromGrid();
+ void load();
+ void reload();
+ void focusFirstEditor();
+
+ signals:
+ void commitStatusChanged();
+ void currentRowChanged();
+ void requestForCommit();
+ void requestForRollback();
+ void requestForNextRow();
+ void requestForPrevRow();
+ void requestForFirstRow();
+ void requestForLastRow();
+ void requestForRowInsert();
+ void requestForRowDelete();
+};
+
+#endif // FORMVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro
new file mode 100644
index 0000000..ac3cb5b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro
@@ -0,0 +1,364 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2013-02-28T23:22:10
+#
+#-------------------------------------------------
+
+QT += core gui uitools widgets xml svg
+
+include($$PWD/../dirs.pri)
+include($$PWD/../utils.pri)
+
+OBJECTS_DIR = $$OBJECTS_DIR/guiSQLiteStudio
+MOC_DIR = $$MOC_DIR/guiSQLiteStudio
+UI_DIR = $$UI_DIR/guiSQLiteStudio
+
+linux: {
+ portable: {
+ DESTDIR = $$DESTDIR/lib
+ }
+}
+
+TARGET = guiSQLiteStudio
+TEMPLATE = lib
+
+CONFIG += c++11
+QMAKE_CXXFLAGS += -pedantic
+
+DEFINES += GUISQLITESTUDIO_LIBRARY
+
+SOURCES +=\
+ mainwindow.cpp \
+ iconmanager.cpp \
+ dbtree/dbtreemodel.cpp \
+ dbtree/dbtreeitem.cpp \
+ dbtree/dbtree.cpp \
+ dbtree/dbtreeview.cpp \
+ actionentry.cpp \
+ uiutils.cpp \
+ dbtree/dbtreeitemdelegate.cpp \
+ dbtree/dbtreeitemfactory.cpp \
+ sqleditor.cpp \
+ datagrid/sqlquerymodel.cpp \
+ dblistmodel.cpp \
+ mdiarea.cpp \
+ statusfield.cpp \
+ common/tablewidget.cpp \
+ datagrid/sqlqueryitem.cpp \
+ datagrid/sqlqueryview.cpp \
+ datagrid/sqlquerymodelcolumn.cpp \
+ datagrid/sqlqueryitemdelegate.cpp \
+ common/extlineedit.cpp \
+ common/intvalidator.cpp \
+ common/widgetcover.cpp \
+ mdiwindow.cpp \
+ mdichild.cpp \
+ taskbar.cpp \
+ multieditor/multieditor.cpp \
+ multieditor/multieditorwidget.cpp \
+ widgetresizer.cpp \
+ multieditor/multieditortext.cpp \
+ multieditor/multieditornumeric.cpp \
+ common/numericspinbox.cpp \
+ multieditor/multieditordatetime.cpp \
+ multieditor/multieditordate.cpp \
+ multieditor/multieditortime.cpp \
+ formview.cpp \
+ multieditor/multieditorbool.cpp \
+ multieditor/multieditorhex.cpp \
+ qhexedit2/xbytearray.cpp \
+ qhexedit2/qhexedit_p.cpp \
+ qhexedit2/qhexedit.cpp \
+ qhexedit2/commands.cpp \
+ multieditor/multieditordialog.cpp \
+ completer/completerwindow.cpp \
+ completer/completermodel.cpp \
+ completer/completeritemdelegate.cpp \
+ completer/completerview.cpp \
+ dialogs/searchtextdialog.cpp \
+ searchtextlocator.cpp \
+ windows/tablewindow.cpp \
+ windows/editorwindow.cpp \
+ datagrid/sqltablemodel.cpp \
+ dataview.cpp \
+ windows/tablestructuremodel.cpp \
+ windows/tableconstraintsmodel.cpp \
+ dialogs/columndialog.cpp \
+ dialogs/columndialogconstraintsmodel.cpp \
+ common/extactioncontainer.cpp \
+ common/extaction.cpp \
+ constraints/tableprimarykeypanel.cpp \
+ constraints/constraintpanel.cpp \
+ constraints/tableforeignkeypanel.cpp \
+ constraints/tableuniquepanel.cpp \
+ constraints/tablepkanduniquepanel.cpp \
+ constraints/tablecheckpanel.cpp \
+ constraints/columncheckpanel.cpp \
+ constraints/constraintcheckpanel.cpp \
+ constraints/columnforeignkeypanel.cpp \
+ constraints/columnprimarykeypanel.cpp \
+ constraints/columnuniquepanel.cpp \
+ constraints/columnuniqueandnotnullpanel.cpp \
+ constraints/columnnotnullpanel.cpp \
+ constraints/columncollatepanel.cpp \
+ constraints/columndefaultpanel.cpp \
+ dialogs/constraintdialog.cpp \
+ dialogs/newconstraintdialog.cpp \
+ windows/constrainttabmodel.cpp \
+ dialogs/messagelistdialog.cpp \
+ windows/viewwindow.cpp \
+ dialogs/configdialog.cpp \
+ uiconfig.cpp \
+ dialogs/indexdialog.cpp \
+ sqlview.cpp \
+ dialogs/triggerdialog.cpp \
+ dialogs/triggercolumnsdialog.cpp \
+ dbobjectdialogs.cpp \
+ common/fontedit.cpp \
+ configwidgets/styleconfigwidget.cpp \
+ common/colorbutton.cpp \
+ formmanager.cpp \
+ configwidgets/combodatawidget.cpp \
+ dialogs/ddlpreviewdialog.cpp \
+ windows/ddlhistorywindow.cpp \
+ common/userinputfilter.cpp \
+ datagrid/sqlqueryrownummodel.cpp \
+ windows/functionseditor.cpp \
+ windows/functionseditormodel.cpp \
+ sqlitesyntaxhighlighter.cpp \
+ windows/collationseditor.cpp \
+ selectabledbmodel.cpp \
+ windows/collationseditormodel.cpp \
+ qtscriptsyntaxhighlighter.cpp \
+ icon.cpp \
+ configmapper.cpp \
+ dialogs/exportdialog.cpp \
+ dbobjlistmodel.cpp \
+ common/verifiablewizardpage.cpp \
+ selectabledbobjmodel.cpp \
+ common/widgetstateindicator.cpp \
+ configwidgets/listtostringlisthash.cpp \
+ dialogs/versionconvertsummarydialog.cpp \
+ sqlcompareview.cpp \
+ dialogs/errorsconfirmdialog.cpp \
+ dialogs/sortdialog.cpp \
+ dialogs/importdialog.cpp \
+ dialogs/populatedialog.cpp \
+ dialogs/populateconfigdialog.cpp \
+ common/configradiobutton.cpp \
+ uiloader.cpp \
+ common/fileedit.cpp \
+ uiscriptingcombo.cpp \
+ uiscriptingedit.cpp \
+ uicustomicon.cpp \
+ uiurlbutton.cpp \
+ common/configcombobox.cpp \
+ dialogs/dbconverterdialog.cpp \
+ dialogs/dbdialog.cpp \
+ uidebug.cpp \
+ debugconsole.cpp \
+ common/extactionprototype.cpp \
+ dialogs/bugdialog.cpp \
+ dialogs/aboutdialog.cpp \
+ dialogs/bugreportlogindialog.cpp \
+ windows/bugreporthistorywindow.cpp \
+ dialogs/newversiondialog.cpp \
+ dialogs/quitconfirmdialog.cpp \
+ common/datawidgetmapper.cpp
+
+HEADERS += mainwindow.h \
+ iconmanager.h \
+ dbtree/dbtreemodel.h \
+ dbtree/dbtreeitem.h \
+ dbtree/dbtree.h \
+ dbtree/dbtreeview.h \
+ actionentry.h \
+ uiutils.h \
+ dbtree/dbtreeitemdelegate.h \
+ dbtree/dbtreeitemfactory.h \
+ sqleditor.h \
+ datagrid/sqlquerymodel.h \
+ dblistmodel.h \
+ mdiarea.h \
+ statusfield.h \
+ common/tablewidget.h \
+ datagrid/sqlqueryitem.h \
+ datagrid/sqlqueryview.h \
+ datagrid/sqlquerymodelcolumn.h \
+ datagrid/sqlqueryitemdelegate.h \
+ common/extlineedit.h \
+ common/intvalidator.h \
+ common/widgetcover.h \
+ mdiwindow.h \
+ mdichild.h \
+ taskbar.h \
+ multieditor/multieditor.h \
+ multieditor/multieditorwidgetplugin.h \
+ multieditor/multieditorwidget.h \
+ widgetresizer.h \
+ multieditor/multieditortext.h \
+ multieditor/multieditornumeric.h \
+ common/numericspinbox.h \
+ multieditor/multieditordatetime.h \
+ multieditor/multieditordate.h \
+ multieditor/multieditortime.h \
+ formview.h \
+ multieditor/multieditorbool.h \
+ multieditor/multieditorhex.h \
+ qhexedit2/xbytearray.h \
+ qhexedit2/qhexedit_p.h \
+ qhexedit2/qhexedit.h \
+ qhexedit2/commands.h \
+ multieditor/multieditordialog.h \
+ completer/completerwindow.h \
+ completer/completermodel.h \
+ completer/completeritemdelegate.h \
+ completer/completerview.h \
+ dialogs/searchtextdialog.h \
+ searchtextlocator.h \
+ windows/tablewindow.h \
+ windows/editorwindow.h \
+ datagrid/sqltablemodel.h \
+ dataview.h \
+ windows/tablestructuremodel.h \
+ windows/tableconstraintsmodel.h \
+ dialogs/columndialog.h \
+ dialogs/columndialogconstraintsmodel.h \
+ common/extaction.h \
+ common/extactioncontainer.h \
+ constraints/tableprimarykeypanel.h \
+ constraints/constraintpanel.h \
+ constraints/tableforeignkeypanel.h \
+ constraints/tableuniquepanel.h \
+ constraints/tablepkanduniquepanel.h \
+ constraints/tablecheckpanel.h \
+ constraints/columncheckpanel.h \
+ constraints/constraintcheckpanel.h \
+ constraints/columnforeignkeypanel.h \
+ constraints/columnprimarykeypanel.h \
+ constraints/columnuniquepanel.h \
+ constraints/columnuniqueandnotnullpanel.h \
+ constraints/columnnotnullpanel.h \
+ constraints/columncollatepanel.h \
+ constraints/columndefaultpanel.h \
+ dialogs/constraintdialog.h \
+ dialogs/newconstraintdialog.h \
+ windows/constrainttabmodel.h \
+ dialogs/messagelistdialog.h \
+ windows/viewwindow.h \
+ uiconfig.h \
+ dialogs/indexdialog.h \
+ sqlview.h \
+ dialogs/triggerdialog.h \
+ dialogs/triggercolumnsdialog.h \
+ dbobjectdialogs.h \
+ common/fontedit.h \
+ customconfigwidgetplugin.h \
+ configwidgets/styleconfigwidget.h \
+ common/colorbutton.h \
+ formmanager.h \
+ configwidgets/combodatawidget.h \
+ dialogs/ddlpreviewdialog.h \
+ windows/ddlhistorywindow.h \
+ common/userinputfilter.h \
+ datagrid/sqlqueryrownummodel.h \
+ windows/functionseditor.h \
+ windows/functionseditormodel.h \
+ syntaxhighlighterplugin.h \
+ sqlitesyntaxhighlighter.h \
+ windows/collationseditor.h \
+ selectabledbmodel.h \
+ windows/collationseditormodel.h \
+ qtscriptsyntaxhighlighter.h \
+ icon.h \
+ configmapper.h \
+ dialogs/exportdialog.h \
+ dbobjlistmodel.h \
+ common/verifiablewizardpage.h \
+ selectabledbobjmodel.h \
+ common/widgetstateindicator.h \
+ configwidgets/listtostringlisthash.h \
+ dialogs/versionconvertsummarydialog.h \
+ sqlcompareview.h \
+ dialogs/errorsconfirmdialog.h \
+ dialogs/sortdialog.h \
+ dialogs/importdialog.h \
+ dialogs/populatedialog.h \
+ dialogs/populateconfigdialog.h \
+ common/configradiobutton.h \
+ uiloader.h \
+ common/fileedit.h \
+ uiscriptingcombo.h \
+ uiloaderpropertyhandler.h \
+ uiscriptingedit.h \
+ uicustomicon.h \
+ uiurlbutton.h \
+ common/configcombobox.h \
+ dialogs/dbconverterdialog.h \
+ dialogs/configdialog.h \
+ dialogs/dbdialog.h \
+ uidebug.h \
+ debugconsole.h \
+ common/extactionprototype.h \
+ dialogs/bugdialog.h \
+ dialogs/aboutdialog.h \
+ dialogs/bugreportlogindialog.h \
+ windows/bugreporthistorywindow.h \
+ dialogs/newversiondialog.h \
+ guiSQLiteStudio_global.h \
+ dialogs/quitconfirmdialog.h \
+ common/datawidgetmapper.h
+
+FORMS += mainwindow.ui \
+ dbtree/dbtree.ui \
+ statusfield.ui \
+ completer/completerwindow.ui \
+ dialogs/searchtextdialog.ui \
+ windows/tablewindow.ui \
+ windows/editorwindow.ui \
+ dialogs/columndialog.ui \
+ constraints/tableforeignkeypanel.ui \
+ constraints/tablepkanduniquepanel.ui \
+ constraints/constraintcheckpanel.ui \
+ constraints/columnforeignkeypanel.ui \
+ constraints/columnprimarykeypanel.ui \
+ constraints/columnuniqueandnotnullpanel.ui \
+ constraints/columncollatepanel.ui \
+ constraints/columndefaultpanel.ui \
+ dialogs/constraintdialog.ui \
+ dialogs/newconstraintdialog.ui \
+ dialogs/messagelistdialog.ui \
+ windows/viewwindow.ui \
+ dialogs/configdialog.ui \
+ dialogs/indexdialog.ui \
+ dialogs/triggerdialog.ui \
+ dialogs/triggercolumnsdialog.ui \
+ common/fontedit.ui \
+ forms/sqlformatterplugin.ui \
+ dialogs/ddlpreviewdialog.ui \
+ windows/ddlhistorywindow.ui \
+ windows/functionseditor.ui \
+ windows/collationseditor.ui \
+ dialogs/exportdialog.ui \
+ dialogs/versionconvertsummarydialog.ui \
+ dialogs/errorsconfirmdialog.ui \
+ dialogs/sortdialog.ui \
+ dialogs/importdialog.ui \
+ dialogs/populatedialog.ui \
+ dialogs/populateconfigdialog.ui \
+ dialogs/dbconverterdialog.ui \
+ dialogs/dbdialog.ui \
+ debugconsole.ui \
+ dialogs/bugdialog.ui \
+ dialogs/aboutdialog.ui \
+ dialogs/bugreportlogindialog.ui \
+ windows/bugreporthistorywindow.ui \
+ dialogs/newversiondialog.ui \
+ dialogs/quitconfirmdialog.ui
+
+RESOURCES += \
+ icons.qrc
+
+OTHER_FILES +=
+
+LIBS += -lcoreSQLiteStudio
diff --git a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h
new file mode 100644
index 0000000..9e1992e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h
@@ -0,0 +1,12 @@
+#ifndef GUISQLITESTUDIO_GLOBAL_H
+#define GUISQLITESTUDIO_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(GUISQLITESTUDIO_LIBRARY)
+# define GUI_API_EXPORT Q_DECL_EXPORT
+#else
+# define GUI_API_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // GUISQLITESTUDIO_GLOBAL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/icon.cpp b/SQLiteStudio3/guiSQLiteStudio/icon.cpp
new file mode 100644
index 0000000..bdc21ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/icon.cpp
@@ -0,0 +1,385 @@
+#include "icon.h"
+#include "iconmanager.h"
+#include "common/global.h"
+#include <QPainter>
+#include <QMovie>
+#include <QDebug>
+#include <QBuffer>
+
+QHash<QString,Icon*> Icon::instances;
+
+
+Icon::Icon(const QString& name, const QString& fileName) :
+ name(name)
+{
+ this->fileName = fileName;
+ instances[name] = this;
+}
+
+Icon::Icon(const Icon& other) :
+ loaded(other.loaded), movie(other.movie), name(other.name), attr(other.attr), filePath(other.filePath), copyFrom(other.copyFrom),
+ aliased(other.aliased), movieHandle(other.movieHandle), iconHandle(other.iconHandle)
+{
+ instances[name] = this;
+}
+
+Icon::Icon(const QString& name) :
+ name(name)
+{
+ instances[name] = this;
+}
+
+Icon::~Icon()
+{
+ for (QIcon* icon : dynamicallyAttributed.values())
+ delete icon;
+
+ dynamicallyAttributed.clear();
+
+ safe_delete(iconHandle);
+ safe_delete(movieHandle);
+}
+
+void Icon::load()
+{
+ if (aliased)
+ {
+ aliased->load();
+ return;
+ }
+
+ if (loaded)
+ return;
+
+ if (copyFrom) // currently copyFrom works only on icons, not movies
+ {
+ if (!copyFrom->loaded)
+ copyFrom->load();
+
+ // Get base icon
+ QIcon* icon = copyFrom->toQIconPtr();
+ if (!icon)
+ {
+ qWarning() << "No QIcon in icon to copy from, while copying icon named" << copyFrom->name;
+ return;
+ }
+
+ iconHandle = new QIcon(mergeAttribute(icon, attr));
+ }
+ else
+ {
+ filePath = IconManager::getInstance()->getFilePathForName(fileName);
+ if (!filePath.isNull())
+ {
+ if (IconManager::getInstance()->isMovie(fileName))
+ movieHandle = IconManager::getInstance()->getMovie(fileName);
+ else
+ iconHandle = IconManager::getInstance()->getIcon(fileName);
+ }
+ else
+ qWarning() << "No file path for icon" << name;
+ }
+
+ loaded = true;
+}
+
+QString Icon::toImgSrc() const
+{
+ if (aliased)
+ return aliased->toImgSrc();
+
+ if (!filePath.isNull())
+ return getPath();
+ else
+ return toBase64Url();
+}
+
+QString Icon::toBase64Url() const
+{
+ static const QString urlTempl = QStringLiteral("data:image/png;base64,%1");
+ return urlTempl.arg(QString(toBase64()));
+}
+
+QByteArray Icon::toBase64() const
+{
+ return toPixmapBytes().toBase64();
+}
+
+QByteArray Icon::toPixmapBytes() const
+{
+ if (aliased)
+ return aliased->toPixmapBytes();
+
+ QByteArray byteArray;
+ if (!loaded)
+ {
+ qCritical() << "Referring to a movie that was not yet loaded:" << name;
+ return byteArray;
+ }
+
+ QBuffer buffer(&byteArray);
+ iconHandle->pixmap(16, 16).save(&buffer, "PNG");
+ return byteArray;
+}
+
+QString Icon::toUrl() const
+{
+ if (aliased)
+ return aliased->toUrl();
+
+ if (filePath.isNull())
+ return toBase64Url();
+
+ return filePath;
+}
+
+QIcon* Icon::toQIconPtr() const
+{
+ if (aliased)
+ return aliased->toQIconPtr();
+
+ if (!loaded)
+ {
+ qCritical() << "Referring to an icon that was not yet loaded:" << name;
+ return nullptr;
+ }
+
+ return iconHandle;
+}
+
+QIcon Icon::toQIcon() const
+{
+ return *toQIconPtr();
+}
+
+Icon* Icon::toIconPtr()
+{
+ return this;
+}
+
+QPixmap Icon::toQPixmap() const
+{
+ return toQIconPtr()->pixmap(16, 16);
+}
+
+QMovie* Icon::toQMoviePtr() const
+{
+ if (aliased)
+ return aliased->toQMoviePtr();
+
+ if (!loaded)
+ {
+ qCritical() << "Referring to a movie that was not yet loaded:" << name;
+ return nullptr;
+ }
+
+ if (!movieHandle)
+ return nullptr; // this is not a movie
+
+ if (movieHandle->state() != QMovie::Running)
+ movieHandle->start();
+
+ return movieHandle;
+}
+
+QVariant Icon::toQVariant() const
+{
+ return QVariant::fromValue<QIcon>(operator QIcon());
+}
+
+QIcon* Icon::with(Icon::Attributes attr)
+{
+ if (dynamicallyAttributed.contains(attr))
+ return dynamicallyAttributed[attr];
+
+ if (aliased)
+ return aliased->with(attr);
+
+ if (!loaded)
+ {
+ qCritical() << "Referring to a icon that was not yet loaded:" << name;
+ return nullptr;
+ }
+
+ if (movieHandle)
+ return nullptr; // this is a movie
+
+ QIcon* merged = new QIcon(mergeAttribute(iconHandle, attr));
+ dynamicallyAttributed[attr] = merged;
+ return merged;
+}
+
+Icon::operator Icon*()
+{
+ return this;
+}
+
+void Icon::init()
+{
+ qRegisterMetaType<const Icon*>();
+ qRegisterMetaTypeStreamOperators<const Icon*>();
+}
+
+QString Icon::getFileName() const
+{
+ return fileName;
+}
+
+QString Icon::getName() const
+{
+ return name;
+}
+
+QString Icon::getPath() const
+{
+ if (aliased)
+ aliased->getPath();
+
+ return filePath;
+}
+
+bool Icon::isNull() const
+{
+ if (aliased)
+ return aliased->isNull();
+
+ return (!iconHandle || iconHandle->isNull()) && !movieHandle;
+}
+
+bool Icon::isMovie() const
+{
+ if (aliased)
+ return aliased->isMovie();
+
+ return movieHandle != nullptr;
+}
+
+Icon& Icon::createFrom(const QString& name, Icon* copy, Icon::Attributes attr)
+{
+ Icon* newIcon = new Icon(name);
+ newIcon->copyFrom = copy;
+ newIcon->attr = attr;
+ newIcon->name = name;
+
+ return *newIcon;
+}
+
+Icon& Icon::aliasOf(const QString& name, Icon* other)
+{
+ Icon* newIcon = new Icon(name);
+ newIcon->aliased = other;
+ newIcon->name = name;
+ return *newIcon;
+}
+
+QIcon Icon::merge(const QIcon& icon, Icon::Attributes attr)
+{
+ return mergeAttribute(&icon, attr);
+}
+
+void Icon::loadAll()
+{
+ for (Icon* icon : instances.values())
+ icon->load();
+}
+
+void Icon::reloadAll()
+{
+ for (Icon* icon : instances.values())
+ {
+ icon->loaded = false;
+ icon->load();
+ }
+}
+
+QString Icon::getIconNameForAttribute(Icon::Attributes attr)
+{
+ switch (attr)
+ {
+ case PLUS:
+ return "plus_small";
+ case MINUS:
+ return "minus_small";
+ case EDIT:
+ return "edit_small";
+ case DELETE:
+ return "delete_small";
+ case DENIED:
+ return "denied_small";
+ case INFO:
+ return "info_small";
+ case WARNING:
+ return "warn_small";
+ case QUESTION:
+ return "question_small";
+ case ERROR:
+ return "error_small";
+ case SORT_ASC:
+ return "sort_ind_asc";
+ case SORT_DESC:
+ return "sort_ind_desc";
+ default:
+ qWarning() << "Unhandled icon attribute:" << attr;
+ }
+ return QString::null;
+}
+
+QIcon Icon::mergeAttribute(const QIcon* icon, Icon::Attributes attr)
+{
+ QString attribName = getIconNameForAttribute(attr);
+ QIcon* attrIcon = IconManager::getInstance()->getIcon(attribName);
+ if (!attrIcon)
+ {
+ qWarning() << "No attribute icon for attribute:" << attribName;
+ return *icon;
+ }
+
+ // Merge icons
+ QPixmap attrPixmap = attrIcon->pixmap(16, 16);
+ QPixmap newPixmap = icon->pixmap(16, 16);
+
+ QPainter painter(&newPixmap);
+ painter.drawPixmap(0, 0, attrPixmap);
+
+ // Create new icon
+ return QIcon(newPixmap);
+}
+
+Icon::operator QVariant() const
+{
+ return toQVariant();
+}
+
+Icon::operator QMovie*() const
+{
+ return toQMoviePtr();
+}
+
+Icon::operator QIcon*() const
+{
+ return toQIconPtr();
+}
+
+Icon::operator QPixmap() const
+{
+ return toQPixmap();
+}
+
+Icon::operator QIcon() const
+{
+ return toQIcon();
+}
+
+QDataStream& operator<<(QDataStream& out, const Icon* icon)
+{
+ out << reinterpret_cast<qint64>(icon);
+ return out;
+}
+
+QDataStream& operator>>(QDataStream& in, const Icon*& icon)
+{
+ qint64 ptr;
+ in >> ptr;
+ icon = reinterpret_cast<const Icon*>(ptr);
+ return in;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/icon.h b/SQLiteStudio3/guiSQLiteStudio/icon.h
new file mode 100644
index 0000000..d6512c9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/icon.h
@@ -0,0 +1,104 @@
+#ifndef ICONS_H
+#define ICONS_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QString>
+#include <QIcon>
+#include <QVariant>
+
+class QMovie;
+
+#define DEF_ICONS(TypeName, ObjName, Defs) \
+ struct GUI_API_EXPORT TypeName \
+ { \
+ Defs \
+ }; \
+ TypeName ObjName;
+
+#define DEF_ICON(E,N) Icon E = Icon(#E, N);
+#define DEF_ICO2(E,Src,Attr) Icon E = Icon::createFrom(#E, Src, Icon::Attr);
+#define DEF_ICO3(E,Src) Icon E = Icon::aliasOf(#E, &Src);
+
+class GUI_API_EXPORT Icon
+{
+ public:
+ enum Attributes
+ {
+ NONE,
+ PLUS,
+ MINUS,
+ EDIT,
+ DELETE,
+ DENIED,
+ INFO,
+ WARNING,
+ QUESTION,
+ ERROR,
+ SORT_ASC,
+ SORT_DESC
+ };
+
+ Icon(const QString& name, const QString& fileName);
+ Icon(const Icon& other);
+ ~Icon();
+
+ QString getFileName() const;
+ QString getName() const;
+ QString getPath() const;
+ bool isNull() const;
+ bool isMovie() const;
+ void load();
+ QString toImgSrc() const;
+ QString toBase64Url() const;
+ QByteArray toBase64() const;
+ QByteArray toPixmapBytes() const;
+ QString toUrl() const;
+ QIcon* toQIconPtr() const;
+ QIcon toQIcon() const;
+ Icon* toIconPtr();
+ QPixmap toQPixmap() const;
+ QMovie* toQMoviePtr() const;
+ QVariant toQVariant() const;
+ QIcon* with(Attributes attr);
+
+ operator Icon*();
+ operator QIcon() const;
+ operator QIcon*() const;
+ operator QPixmap() const;
+ operator QMovie*() const;
+ operator QVariant() const;
+
+ static void init();
+ static void loadAll();
+ static void reloadAll();
+ static Icon& createFrom(const QString& name, Icon* copy, Attributes attr);
+ static Icon& aliasOf(const QString& name, Icon* other);
+ static QIcon merge(const QIcon& icon, Attributes attr);
+
+ private:
+ explicit Icon(const QString& name);
+
+ static QString getIconNameForAttribute(Attributes attr);
+ static QIcon mergeAttribute(const QIcon* icon, Attributes attr);
+
+ bool loaded = false;
+ bool movie = false;
+ QString name;
+ Attributes attr = NONE;
+ QString fileName;
+ QString filePath;
+ Icon* copyFrom = nullptr;
+ Icon* aliased = nullptr;
+ QMovie* movieHandle = nullptr;
+ QIcon* iconHandle = nullptr;
+ QHash<int,QIcon*> dynamicallyAttributed;
+
+ static QHash<QString,Icon*> instances;
+};
+
+GUI_API_EXPORT QDataStream &operator<<(QDataStream &out, const Icon* icon);
+GUI_API_EXPORT QDataStream &operator>>(QDataStream &in, const Icon*& icon);
+
+Q_DECLARE_METATYPE(const Icon*)
+
+#endif // ICONS_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp b/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp
new file mode 100644
index 0000000..efe22a1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp
@@ -0,0 +1,164 @@
+#include "iconmanager.h"
+#include "sqlitestudio.h"
+#include "services/pluginmanager.h"
+#include "common/unused.h"
+#include "common/global.h"
+#include <QApplication>
+#include <QDir>
+#include <QString>
+#include <QIcon>
+#include <QMovie>
+#include <QDebug>
+#include <QPainter>
+
+IconManager* IconManager::instance = nullptr;
+
+IconManager* IconManager::getInstance()
+{
+ if (instance == nullptr)
+ instance = new IconManager();
+
+ return instance;
+}
+
+QString IconManager::getFilePathForName(const QString& name)
+{
+ return paths[name];
+}
+
+IconManager::IconManager()
+{
+}
+
+void IconManager::init()
+{
+ Icon::init();
+
+ iconDirs += qApp->applicationDirPath() + "/img";
+ iconDirs += ":/icons";
+
+ QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_ICONS");
+ if (!envDirs.isNull())
+ iconDirs += envDirs.split(PATH_LIST_SEPARATOR);
+
+#ifdef ICONS_DIR
+ iconDirs += STRINGIFY(ICONS_DIR);
+#endif
+
+ iconFileExtensions << "*.png" << "*.PNG" << "*.jpg" << "*.JPG" << "*.svg" << "*.SVG";
+ movieFileExtensions << "*.gif" << "*.GIF" << "*.mng" << "*.MNG";
+
+ foreach (QString dirPath, iconDirs)
+ {
+ loadRecurently(dirPath, "", false);
+ loadRecurently(dirPath, "", true);
+ }
+
+ Icon::loadAll();
+
+ if (PLUGINS->arePluginsInitiallyLoaded())
+ enableRescanning();
+ else
+ connect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded()));
+}
+
+void IconManager::rescanResources(const QString& pluginName)
+{
+ if (!pluginName.isNull() && PLUGINS->isBuiltIn(pluginName))
+ return;
+
+ for (const QString& name : resourceMovies)
+ {
+ delete movies[name];
+ movies.remove(name);
+ }
+
+ for (const QString& name : resourceIcons)
+ icons.remove(name);
+
+ resourceMovies.clear();
+ resourceIcons.clear();
+ loadRecurently(":/icons", "", true);
+ loadRecurently(":/icons", "", false);
+
+ Icon::reloadAll();
+}
+
+void IconManager::rescanResources(Plugin* plugin, PluginType* pluginType)
+{
+ UNUSED(pluginType);
+ rescanResources(plugin->getName());
+}
+
+void IconManager::pluginsAboutToMassUnload()
+{
+ disconnect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*)));
+ disconnect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString)));
+}
+
+void IconManager::pluginsInitiallyLoaded()
+{
+ Icon::reloadAll();
+ enableRescanning();
+ disconnect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded()));
+}
+
+void IconManager::loadRecurently(QString dirPath, const QString& prefix, bool movie)
+{
+ QStringList extensions = movie ? movieFileExtensions : iconFileExtensions;
+ QString path;
+ QString name;
+ QDir dir(dirPath);
+ foreach (QFileInfo entry, dir.entryInfoList(extensions, QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot|QDir::Readable))
+ {
+ if (entry.isDir())
+ {
+ loadRecurently(entry.absoluteFilePath(), prefix+entry.fileName()+"_", movie);
+ continue;
+ }
+
+ path = entry.absoluteFilePath();
+ name = entry.baseName();
+ paths[name] = path;
+ if (movie)
+ movies[name] = new QMovie(path);
+ else
+ icons[name] = new QIcon(path);
+
+ if (path.startsWith(":/"))
+ {
+ if (movie)
+ resourceMovies << name;
+ else
+ resourceIcons << name;
+ }
+ }
+}
+
+void IconManager::enableRescanning()
+{
+ connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*)));
+ connect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString)));
+ connect(PLUGINS, SIGNAL(aboutToQuit()), this, SLOT(pluginsAboutToMassUnload()));
+}
+
+QMovie* IconManager::getMovie(const QString& name)
+{
+ if (!movies.contains(name))
+ qCritical() << "Movie missing:" << name;
+
+ return movies[name];
+}
+
+QIcon* IconManager::getIcon(const QString& name)
+{
+ if (!icons.contains(name))
+ qCritical() << "Icon missing:" << name;
+
+ return icons[name];
+}
+
+bool IconManager::isMovie(const QString& name)
+{
+ return movies.contains(name);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/iconmanager.h b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h
new file mode 100644
index 0000000..8cb7dbe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h
@@ -0,0 +1,275 @@
+#ifndef ICONMANAGER_H
+#define ICONMANAGER_H
+
+#include "icon.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStringList>
+#include <QHash>
+#include <QIcon>
+#include <QVariant>
+
+class QMovie;
+class PluginType;
+class Plugin;
+
+class GUI_API_EXPORT IconManager : public QObject
+{
+ Q_OBJECT
+
+ public:
+ DEF_ICONS(Icons, iconEnums,
+ DEF_ICON(ABORT24, "abort24")
+ DEF_ICON(ACT_ABORT, "act_abort")
+ DEF_ICON(ACT_CLEAR, "act_clear")
+ DEF_ICON(ACT_COPY, "act_copy")
+ DEF_ICON(ACT_CUT, "act_cut")
+ DEF_ICON(ACT_DEL_LINE, "act_del_line")
+ DEF_ICON(ACT_DELETE, "act_delete")
+ DEF_ICON(ACT_PASTE, "act_paste")
+ DEF_ICON(ACT_REDO, "act_redo")
+ DEF_ICON(ACT_SEARCH, "act_search")
+ DEF_ICON(ACT_SELECT_ALL, "act_select_all")
+ DEF_ICON(ACT_UNDO, "act_undo")
+ DEF_ICON(APPLY_FILTER, "apply_filter")
+ DEF_ICON(APPLY_FILTER_RE, "apply_filter_re")
+ DEF_ICON(APPLY_FILTER_SQL, "apply_filter_sql")
+ DEF_ICON(APPLY_FILTER_TXT, "apply_filter_txt")
+ DEF_ICON(BUG, "bug")
+ DEF_ICON(BUG_LIST, "bug_list")
+ DEF_ICON(CLEAR_HISTORY, "clear_history")
+ DEF_ICON(CLEAR_LINEEDIT, "clear_lineedit")
+ DEF_ICON(CLOSE, "close")
+ DEF_ICON(COLUMN, "column")
+ DEF_ICON(COLUMN_CONSTRAINT, "column_constraint")
+ DEF_ICO2(COLUMN_CONSTRAINT_ADD, COLUMN_CONSTRAINT, PLUS)
+ DEF_ICO2(COLUMN_CONSTRAINT_DEL, COLUMN_CONSTRAINT, MINUS)
+ DEF_ICO2(COLUMN_CONSTRAINT_EDIT, COLUMN_CONSTRAINT, EDIT)
+ DEF_ICON(COLUMNS, "columns")
+ DEF_ICON(COMMIT, "commit")
+ DEF_ICON(COMPLETE, "complete")
+ DEF_ICON(COMPLETER_BLOB, "completer_blob")
+ DEF_ICON(COMPLETER_NO_VALUE, "completer_no_value")
+ DEF_ICON(COMPLETER_NUMBER, "completer_number")
+ DEF_ICON(COMPLETER_OPERATOR, "completer_operator")
+ DEF_ICON(COMPLETER_OTHER, "completer_other")
+ DEF_ICON(COMPLETER_PRAGMA, "completer_pragma")
+ DEF_ICON(COMPLETER_STRING, "completer_string")
+ DEF_ICON(CONFIGURE, "configure")
+ DEF_ICON(CONFIGURE_CONSTRAINT, "configure_constraint")
+ DEF_ICON(CONSTRAINT_CHECK, "check")
+ DEF_ICO2(CONSTRAINT_CHECK_ADD, CONSTRAINT_CHECK, PLUS)
+ DEF_ICON(CONSTRAINT_COLLATION, "collation")
+ DEF_ICO2(CONSTRAINT_COLLATION_ADD, CONSTRAINT_COLLATION, PLUS)
+ DEF_ICON(CONSTRAINT_DEFAULT, "default")
+ DEF_ICO2(CONSTRAINT_DEFAULT_ADD, CONSTRAINT_DEFAULT, PLUS)
+ DEF_ICON(CONSTRAINT_FOREIGN_KEY, "fk")
+ DEF_ICO2(CONSTRAINT_FOREIGN_KEY_ADD, CONSTRAINT_FOREIGN_KEY, PLUS)
+ DEF_ICON(CONSTRAINT_NOT_NULL, "not_null")
+ DEF_ICO2(CONSTRAINT_NOT_NULL_ADD, CONSTRAINT_NOT_NULL, PLUS)
+ DEF_ICON(CONSTRAINT_PRIMARY_KEY, "pk")
+ DEF_ICO2(CONSTRAINT_PRIMARY_KEY_ADD, CONSTRAINT_PRIMARY_KEY, PLUS)
+ DEF_ICON(CONSTRAINT_UNIQUE, "unique")
+ DEF_ICO2(CONSTRAINT_UNIQUE_ADD, CONSTRAINT_UNIQUE, PLUS)
+ DEF_ICON(CONVERT_DB, "convert_db")
+ DEF_ICON(VACUUM_DB, "vacuum_db")
+ DEF_ICON(INTEGRITY_CHECK, "integrity_check")
+ DEF_ICON(DATABASE, "database")
+ DEF_ICO2(DATABASE_ADD, DATABASE, PLUS)
+ DEF_ICON(DATABASE_CONNECT, "database_connect")
+ DEF_ICON(DATABASE_CONNECTED, "database_connected")
+ DEF_ICO2(DATABASE_DEL, DATABASE, MINUS)
+ DEF_ICON(DATABASE_DISCONNECT, "database_disconnect")
+ DEF_ICO2(DATABASE_EDIT, DATABASE, EDIT)
+ DEF_ICON(DATABASE_EXPORT, "database_export")
+ DEF_ICON(DATABASE_EXPORT_WIZARD, "database_export_wizard")
+ DEF_ICON(DATABASE_FILE, "database_file")
+ DEF_ICON(DATABASE_IMPORT_WIZARD, "database_import_wizard")
+ DEF_ICO2(DATABASE_INVALID, DATABASE, WARNING)
+ DEF_ICON(DATABASE_NETWORK, "database_network")
+ DEF_ICON(DATABASE_OFFLINE, "database_offline")
+ DEF_ICON(DATABASE_ONLINE, "database_online")
+ DEF_ICON(DATABASE_RELOAD, "database_reload")
+ DEF_ICON(DDL_HISTORY, "ddl_history")
+ DEF_ICON(DELETE_ROW, "delete_row")
+ DEF_ICO3(DELETE_COLLATION, DELETE_ROW)
+ DEF_ICO3(DELETE_DATATYPE, DELETE_ROW)
+ DEF_ICO3(DELETE_FN_ARG, DELETE_ROW)
+ DEF_ICO3(DELETE_FUNCTION, DELETE_ROW)
+ DEF_ICON(DELETE_SELECTED, "delete_selected")
+ DEF_ICON(DIRECTORY, "directory")
+ DEF_ICO2(DIRECTORY_ADD, DIRECTORY, PLUS)
+ DEF_ICO2(DIRECTORY_DEL, DIRECTORY, MINUS)
+ DEF_ICO2(DIRECTORY_EDIT, DIRECTORY, EDIT)
+ DEF_ICON(DIRECTORY_OPEN, "directory_open")
+ DEF_ICON(DIRECTORY_OPEN_WITH_DB, "directory_open_with_db")
+ DEF_ICON(DIRECTORY_WITH_DB, "directory_with_db")
+ DEF_ICON(ERASE, "erase")
+ DEF_ICON(EXEC_QUERY, "exec_query")
+ DEF_ICON(EXPLAIN_QUERY, "explain_query")
+ DEF_ICON(EXPORT, "export")
+ DEF_ICON(EXPORT_FILE_BROWSE, "export_file_browse")
+ DEF_ICON(FEATURE_REQUEST, "feature_request")
+ DEF_ICON(FONT_BROWSE, "font_browse")
+ DEF_ICON(FORMAT_SQL, "format_sql")
+ DEF_ICON(FUNCTION, "function")
+ DEF_ICON(GET_UPDATE, "get_update")
+ DEF_ICON(GO_BACK, "go_back")
+ DEF_ICON(HELP, "help")
+ DEF_ICON(HOMEPAGE, "homepage")
+ DEF_ICON(IMPORT, "import")
+ DEF_ICON(INDEX, "index")
+ DEF_ICO2(INDEX_ADD, INDEX, PLUS)
+ DEF_ICO2(INDEX_DEL, INDEX, MINUS)
+ DEF_ICO2(INDEX_EDIT, INDEX, EDIT)
+ DEF_ICON(INDEXES, "indexes")
+ DEF_ICON(INDICATOR_ERROR, "indicator_error")
+ DEF_ICON(INDICATOR_HINT, "indicator_hint")
+ DEF_ICON(INDICATOR_INFO, "indicator_info")
+ DEF_ICON(INDICATOR_WARN, "indicator_warn")
+ DEF_ICON(INFO_BALLOON, "info_balloon")
+ DEF_ICON(INSERT_ROW, "insert_row")
+ DEF_ICON(INSERT_ROWS, "insert_rows")
+ DEF_ICO3(INSERT_FN_ARG, INSERT_ROW)
+ DEF_ICO3(INSERT_DATATYPE, INSERT_ROW)
+ DEF_ICON(KEYWORD, "keyword")
+ DEF_ICON(KEYBOARD, "keyboard")
+ DEF_ICON(LOADING, "loading")
+ DEF_ICON(LICENSES, "licenses")
+ DEF_ICON(MOVE_DOWN, "move_down")
+ DEF_ICON(MOVE_UP, "move_up")
+ DEF_ICO3(NEW_COLLATION, INSERT_ROW)
+ DEF_ICO3(NEW_FUNCTION, INSERT_ROW)
+ DEF_ICON(OPEN_FILE, "open_sql_file")
+ DEF_ICON(OPEN_FORUM, "open_forum")
+ DEF_ICON(OPEN_SQL_EDITOR, "open_sql_editor")
+ DEF_ICO3(OPEN_SQL_FILE, OPEN_FILE)
+ DEF_ICON(OPEN_VALUE_EDITOR, "open_value_editor")
+ DEF_ICON(PAGE_FIRST, "page_first")
+ DEF_ICON(PAGE_LAST, "page_last")
+ DEF_ICON(PAGE_NEXT, "page_next")
+ DEF_ICON(PAGE_PREV, "page_prev")
+ DEF_ICO3(MOVE_LEFT, PAGE_PREV)
+ DEF_ICO3(MOVE_RIGHT, PAGE_NEXT)
+ DEF_ICON(RELOAD, "reload")
+ DEF_ICON(RENAME_FN_ARG, "rename_fn_arg")
+ DEF_ICO3(RENAME_DATATYPE, RENAME_FN_ARG)
+ DEF_ICON(RESULTS_BELOW, "results_below")
+ DEF_ICON(RESULTS_IN_TAB, "results_in_tab")
+ DEF_ICON(ROLLBACK, "rollback")
+ DEF_ICON(SAVE_SQL_FILE, "save_sql_file")
+ DEF_ICON(SET_NULL, "set_null")
+ DEF_ICON(SORT_COLUMNS, "sort_columns")
+ DEF_ICON(SORT_COUNT_01, "sort_cnt_01")
+ DEF_ICON(SORT_COUNT_02, "sort_cnt_02")
+ DEF_ICON(SORT_COUNT_03, "sort_cnt_03")
+ DEF_ICON(SORT_COUNT_04, "sort_cnt_04")
+ DEF_ICON(SORT_COUNT_05, "sort_cnt_05")
+ DEF_ICON(SORT_COUNT_06, "sort_cnt_06")
+ DEF_ICON(SORT_COUNT_07, "sort_cnt_07")
+ DEF_ICON(SORT_COUNT_08, "sort_cnt_08")
+ DEF_ICON(SORT_COUNT_09, "sort_cnt_09")
+ DEF_ICON(SORT_COUNT_10, "sort_cnt_10")
+ DEF_ICON(SORT_COUNT_11, "sort_cnt_11")
+ DEF_ICON(SORT_COUNT_12, "sort_cnt_12")
+ DEF_ICON(SORT_COUNT_13, "sort_cnt_13")
+ DEF_ICON(SORT_COUNT_14, "sort_cnt_14")
+ DEF_ICON(SORT_COUNT_15, "sort_cnt_15")
+ DEF_ICON(SORT_COUNT_16, "sort_cnt_16")
+ DEF_ICON(SORT_COUNT_17, "sort_cnt_17")
+ DEF_ICON(SORT_COUNT_18, "sort_cnt_18")
+ DEF_ICON(SORT_COUNT_19, "sort_cnt_19")
+ DEF_ICON(SORT_COUNT_20, "sort_cnt_20")
+ DEF_ICON(SORT_COUNT_20_PLUS, "sort_cnt_20p")
+ DEF_ICON(SORT_INDICATOR_ASC, "sort_ind_asc")
+ DEF_ICON(SORT_INDICATOR_DESC, "sort_ind_desc")
+ DEF_ICON(SORT_RESET, "sort_reset")
+ DEF_ICON(SQLITE_DOCS, "sqlite_docs")
+ DEF_ICON(SQLITESTUDIO_APP, "sqlitestudio")
+ DEF_ICON(SQLITESTUDIO_APP16, "sqlitestudio_16")
+ DEF_ICON(STATUS_ERROR, "status_error")
+ DEF_ICON(STATUS_INFO, "status_info")
+ DEF_ICON(STATUS_WARNING, "status_warn")
+ DEF_ICON(TABLE, "table")
+ DEF_ICO2(TABLE_ADD, TABLE, PLUS)
+ DEF_ICON(TABLE_COLUMN_ADD, "table_column_add")
+ DEF_ICON(TABLE_COLUMN_DELETE, "table_column_delete")
+ DEF_ICON(TABLE_COLUMN_EDIT, "table_column_edit")
+ DEF_ICON(TABLE_CONSTRAINT, "table_constraint")
+ DEF_ICO2(TABLE_CONSTRAINT_ADD, TABLE_CONSTRAINT, PLUS)
+ DEF_ICO2(TABLE_CONSTRAINT_DELETE, TABLE_CONSTRAINT, MINUS)
+ DEF_ICO2(TABLE_CONSTRAINT_EDIT, TABLE_CONSTRAINT, EDIT)
+ DEF_ICON(TABLE_CREATE_SIMILAR, "table_create_similar")
+ DEF_ICO2(TABLE_DEL, TABLE, MINUS)
+ DEF_ICO2(TABLE_EDIT, TABLE, EDIT)
+ DEF_ICON(TABLE_EXPORT, "table_export")
+ DEF_ICON(TABLE_IMPORT, "table_import")
+ DEF_ICON(TABLE_POPULATE, "table_populate")
+ DEF_ICON(TABLES, "tables")
+ DEF_ICON(TABS_AT_BOTTOM, "tabs_at_bottom")
+ DEF_ICON(TABS_ON_TOP, "tabs_on_top")
+ DEF_ICON(TEST_CONN_ERROR, "test_conn_error")
+ DEF_ICON(TEST_CONN_OK, "test_conn_ok")
+ DEF_ICON(TIP, "tip")
+ DEF_ICON(TRIGGER, "trigger")
+ DEF_ICO2(TRIGGER_ADD, TRIGGER, PLUS)
+ DEF_ICON(TRIGGER_COLUMNS, "trigger_columns")
+ DEF_ICO2(TRIGGER_COLUMNS_INVALID, TRIGGER_COLUMNS, WARNING)
+ DEF_ICO2(TRIGGER_DEL, TRIGGER, MINUS)
+ DEF_ICO2(TRIGGER_EDIT, TRIGGER, EDIT)
+ DEF_ICON(TRIGGERS, "triggers")
+ DEF_ICON(USER, "user")
+ DEF_ICON(USER_UNKNOWN, "user_unknown")
+ DEF_ICON(USER_MANUAL, "user_manual")
+ DEF_ICON(VIEW, "view")
+ DEF_ICO2(VIEW_ADD, VIEW, PLUS)
+ DEF_ICO2(VIEW_DEL, VIEW, MINUS)
+ DEF_ICO2(VIEW_EDIT, VIEW, EDIT)
+ DEF_ICON(VIEWS, "views")
+ DEF_ICON(VIRTUAL_TABLE, "virtual_table")
+ DEF_ICON(WIN_CASCADE, "win_cascade")
+ DEF_ICON(WIN_TILE, "win_tile")
+ DEF_ICON(WIN_TILE_HORIZONTAL, "win_tile_horizontal")
+ DEF_ICON(WIN_TILE_VERTICAL, "win_tile_vertical")
+ DEF_ICON(WIN_CLOSE, "window_close")
+ DEF_ICON(WIN_CLOSE_ALL, "window_close_all")
+ DEF_ICON(WIN_CLOSE_OTHER, "window_close_other")
+ DEF_ICON(WIN_RESTORE, "window_restore")
+ DEF_ICON(WIN_RENAME, "window_rename")
+ )
+
+ static IconManager* getInstance();
+
+ QString getFilePathForName(const QString& name);
+ bool isMovie(const QString& name);
+ QMovie* getMovie(const QString& name);
+ QIcon* getIcon(const QString& name);
+ void init();
+
+ private:
+ IconManager();
+ void loadRecurently(QString dirPath, const QString& prefix, bool movie);
+ void enableRescanning();
+
+ static IconManager* instance;
+ QHash<QString,QIcon*> icons;
+ QHash<QString,QMovie*> movies;
+ QHash<QString,QString> paths;
+ QStringList iconDirs;
+ QStringList iconFileExtensions;
+ QStringList movieFileExtensions;
+ QStringList resourceIcons;
+ QStringList resourceMovies;
+
+ private slots:
+ void rescanResources(Plugin* plugin, PluginType* pluginType);
+ void pluginsAboutToMassUnload();
+ void pluginsInitiallyLoaded();
+
+ public slots:
+ void rescanResources(const QString& pluginName = QString());
+};
+
+#define ICONMANAGER IconManager::getInstance()
+#define ICONS ICONMANAGER->iconEnums
+
+#endif // ICONMANAGER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/icons.qrc b/SQLiteStudio3/guiSQLiteStudio/icons.qrc
new file mode 100644
index 0000000..f923fe9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/icons.qrc
@@ -0,0 +1,194 @@
+<RCC>
+ <qresource prefix="/icons">
+ <file>img/act_abort.png</file>
+ <file>img/act_clear.png</file>
+ <file>img/act_copy.png</file>
+ <file>img/act_cut.png</file>
+ <file>img/act_del_line.png</file>
+ <file>img/act_delete.png</file>
+ <file>img/act_paste.png</file>
+ <file>img/act_redo.png</file>
+ <file>img/act_search.png</file>
+ <file>img/act_select_all.png</file>
+ <file>img/act_undo.png</file>
+ <file>img/apply_filter_re.png</file>
+ <file>img/apply_filter_sql.png</file>
+ <file>img/apply_filter_txt.png</file>
+ <file>img/apply_filter.png</file>
+ <file>img/check.png</file>
+ <file>img/clear_history.png</file>
+ <file>img/clear_lineedit.png</file>
+ <file>img/collation.png</file>
+ <file>img/column_constraint.png</file>
+ <file>img/column.png</file>
+ <file>img/columns.png</file>
+ <file>img/commit.png</file>
+ <file>img/complete.png</file>
+ <file>img/completer_blob.png</file>
+ <file>img/completer_no_value.png</file>
+ <file>img/completer_number.png</file>
+ <file>img/completer_operator.png</file>
+ <file>img/completer_other.png</file>
+ <file>img/completer_pragma.png</file>
+ <file>img/completer_string.png</file>
+ <file>img/configure_constraint.png</file>
+ <file>img/configure.png</file>
+ <file>img/database_connect.png</file>
+ <file>img/database_connected.png</file>
+ <file>img/database_disconnect.png</file>
+ <file>img/database_export.png</file>
+ <file>img/database_file.png</file>
+ <file>img/database_invalid.png</file>
+ <file>img/database_network.png</file>
+ <file>img/database_reload.png</file>
+ <file>img/database.png</file>
+ <file>img/ddl_history.png</file>
+ <file>img/default.png</file>
+ <file>img/delete_row.png</file>
+ <file>img/delete_selected.png</file>
+ <file>img/delete_small.png</file>
+ <file>img/denied_small.png</file>
+ <file>img/directory_open_with_db.png</file>
+ <file>img/directory_open.png</file>
+ <file>img/directory_with_db.png</file>
+ <file>img/directory.png</file>
+ <file>img/edit_small.png</file>
+ <file>img/erase.png</file>
+ <file>img/error_small.png</file>
+ <file>img/exec_query.png</file>
+ <file>img/explain_query.png</file>
+ <file>img/export_file_browse.png</file>
+ <file>img/export.png</file>
+ <file>img/fk.png</file>
+ <file>img/font_browse.png</file>
+ <file>img/format_sql.png</file>
+ <file>img/function.png</file>
+ <file>img/help.png</file>
+ <file>img/import.png</file>
+ <file>img/index.png</file>
+ <file>img/indexes.png</file>
+ <file>img/indicator_error.png</file>
+ <file>img/indicator_hint.png</file>
+ <file>img/indicator_info.png</file>
+ <file>img/indicator_warn.png</file>
+ <file>img/info_balloon.png</file>
+ <file>img/info_small.png</file>
+ <file>img/insert_row.png</file>
+ <file>img/insert_rows.png</file>
+ <file>img/keyword.png</file>
+ <file>img/loading.gif</file>
+ <file>img/minus_small.png</file>
+ <file>img/move_down.png</file>
+ <file>img/move_up.png</file>
+ <file>img/not_null.png</file>
+ <file>img/open_sql_editor.png</file>
+ <file>img/open_sql_file.png</file>
+ <file>img/open_value_editor.png</file>
+ <file>img/page_first.png</file>
+ <file>img/page_last.png</file>
+ <file>img/page_next.png</file>
+ <file>img/page_prev.png</file>
+ <file>img/pk.png</file>
+ <file>img/plus_small.png</file>
+ <file>img/question_small.png</file>
+ <file>img/reload.png</file>
+ <file>img/rename_fn_arg.png</file>
+ <file>img/results_below.png</file>
+ <file>img/results_in_tab.png</file>
+ <file>img/rollback.png</file>
+ <file>img/save_sql_file.png</file>
+ <file>img/set_null.png</file>
+ <file>img/sort_cnt_01.png</file>
+ <file>img/sort_cnt_02.png</file>
+ <file>img/sort_cnt_03.png</file>
+ <file>img/sort_cnt_04.png</file>
+ <file>img/sort_cnt_05.png</file>
+ <file>img/sort_cnt_06.png</file>
+ <file>img/sort_cnt_07.png</file>
+ <file>img/sort_cnt_08.png</file>
+ <file>img/sort_cnt_09.png</file>
+ <file>img/sort_cnt_10.png</file>
+ <file>img/sort_cnt_11.png</file>
+ <file>img/sort_cnt_12.png</file>
+ <file>img/sort_cnt_13.png</file>
+ <file>img/sort_cnt_14.png</file>
+ <file>img/sort_cnt_15.png</file>
+ <file>img/sort_cnt_16.png</file>
+ <file>img/sort_cnt_17.png</file>
+ <file>img/sort_cnt_18.png</file>
+ <file>img/sort_cnt_19.png</file>
+ <file>img/sort_cnt_20.png</file>
+ <file>img/sort_cnt_20p.png</file>
+ <file>img/sort_columns.png</file>
+ <file>img/sort_ind_asc.png</file>
+ <file>img/sort_ind_desc.png</file>
+ <file>img/sort_reset.png</file>
+ <file>img/sql.png</file>
+ <file>img/status_error.png</file>
+ <file>img/status_info.png</file>
+ <file>img/status_warn.png</file>
+ <file>img/table_column_add.png</file>
+ <file>img/table_column_delete.png</file>
+ <file>img/table_column_edit.png</file>
+ <file>img/table_constraint.png</file>
+ <file>img/table_create_similar.png</file>
+ <file>img/table_export.png</file>
+ <file>img/table_import.png</file>
+ <file>img/table_populate.png</file>
+ <file>img/table.png</file>
+ <file>img/tables.png</file>
+ <file>img/tabs_at_bottom.png</file>
+ <file>img/tabs_on_top.png</file>
+ <file>img/test_conn_error.png</file>
+ <file>img/test_conn_ok.png</file>
+ <file>img/trigger_columns.png</file>
+ <file>img/trigger.png</file>
+ <file>img/triggers.png</file>
+ <file>img/unique.png</file>
+ <file>img/view.png</file>
+ <file>img/views.png</file>
+ <file>img/virtual_table.png</file>
+ <file>img/warn_small.png</file>
+ <file>img/win_cascade.png</file>
+ <file>img/win_tile_horizontal.png</file>
+ <file>img/win_tile_vertical.png</file>
+ <file>img/win_tile.png</file>
+ <file>img/convert_db.png</file>
+ <file>img/window_rename.png</file>
+ <file>img/window_close.png</file>
+ <file>img/window_close_other.png</file>
+ <file>img/window_close_all.png</file>
+ <file>img/window_restore.png</file>
+ <file>img/integrity_check.png</file>
+ <file>img/vacuum_db.png</file>
+ <file>img/bug.png</file>
+ <file>img/feature_request.png</file>
+ <file>img/user_unknown.png</file>
+ <file>img/user.png</file>
+ <file>img/open_forum.png</file>
+ <file>img/user_manual.png</file>
+ <file>img/sqlite_docs.png</file>
+ <file>img/tip.png</file>
+ <file>img/licenses.png</file>
+ <file>img/homepage.png</file>
+ <file>img/bug_list.png</file>
+ <file>img/get_update.png</file>
+ <file>img/sqlitestudio.svg</file>
+ <file>img/sqlitestudio_16.png</file>
+ <file>img/keyboard.png</file>
+ <file>img/config_general.png</file>
+ <file>img/config_colors.png</file>
+ <file>img/config_font.png</file>
+ <file>img/config_look_and_feel.png</file>
+ <file>img/config_style.png</file>
+ <file>img/plugin.png</file>
+ <file>img/config_data_editors.png</file>
+ <file>img/database_export_wizard.svg</file>
+ <file>img/database_import_wizard.svg</file>
+ <file>img/database_offline.png</file>
+ <file>img/database_online.png</file>
+ <file>img/abort24.png</file>
+ <file>img/close.png</file>
+ <file>img/go_back.png</file>
+ </qresource>
+</RCC>
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/abort24.png b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png
new file mode 100644
index 0000000..87b6373
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png
new file mode 100644
index 0000000..6b9fa6d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png
new file mode 100644
index 0000000..2c6152e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png
new file mode 100644
index 0000000..3836257
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png
new file mode 100644
index 0000000..85f80b5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png b/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png
new file mode 100644
index 0000000..b9b8620
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png
new file mode 100644
index 0000000..70b59dc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png
new file mode 100644
index 0000000..0cf8887
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png
new file mode 100644
index 0000000..56776f5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_search.png b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png
new file mode 100644
index 0000000..7d57954
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png b/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png
new file mode 100644
index 0000000..775f1ec
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png
new file mode 100644
index 0000000..aa4dd4e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png
new file mode 100644
index 0000000..1f69604
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png
new file mode 100644
index 0000000..765cc19
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png
new file mode 100644
index 0000000..9a8f226
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png
new file mode 100644
index 0000000..1747ca3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug.png b/SQLiteStudio3/guiSQLiteStudio/img/bug.png
new file mode 100644
index 0000000..242d539
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/bug.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png
new file mode 100644
index 0000000..bbf004c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/check.png b/SQLiteStudio3/guiSQLiteStudio/img/check.png
new file mode 100644
index 0000000..0023b3a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/check.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png
new file mode 100644
index 0000000..0cade7e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png
new file mode 100644
index 0000000..4671248
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/close.png b/SQLiteStudio3/guiSQLiteStudio/img/close.png
new file mode 100644
index 0000000..6b9fa6d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/close.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/collation.png b/SQLiteStudio3/guiSQLiteStudio/img/collation.png
new file mode 100644
index 0000000..94fa577
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/collation.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column.png b/SQLiteStudio3/guiSQLiteStudio/img/column.png
new file mode 100644
index 0000000..7d10ac9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/column.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png
new file mode 100644
index 0000000..4270875
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/columns.png b/SQLiteStudio3/guiSQLiteStudio/img/columns.png
new file mode 100644
index 0000000..4cbaa8a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/columns.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/commit.png b/SQLiteStudio3/guiSQLiteStudio/img/commit.png
new file mode 100644
index 0000000..3b0e3fc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/commit.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/complete.png b/SQLiteStudio3/guiSQLiteStudio/img/complete.png
new file mode 100644
index 0000000..358d9aa
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/complete.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png
new file mode 100644
index 0000000..739efb5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png
new file mode 100644
index 0000000..440e626
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png
new file mode 100644
index 0000000..e914d5c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png
new file mode 100644
index 0000000..29bac66
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png
new file mode 100644
index 0000000..b723492
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png
new file mode 100644
index 0000000..57de2d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png
new file mode 100644
index 0000000..2fc5ccc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png
new file mode 100644
index 0000000..df7ccc1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png b/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png
new file mode 100644
index 0000000..7fc1c7f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_font.png b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png
new file mode 100644
index 0000000..b6911ca
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_general.png b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png
new file mode 100644
index 0000000..07f3522
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png b/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png
new file mode 100644
index 0000000..b697614
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_style.png b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png
new file mode 100644
index 0000000..99a3802
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure.png b/SQLiteStudio3/guiSQLiteStudio/img/configure.png
new file mode 100644
index 0000000..89ab15f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/configure.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png
new file mode 100644
index 0000000..40a7bf9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png
new file mode 100644
index 0000000..15b5b1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database.png b/SQLiteStudio3/guiSQLiteStudio/img/database.png
new file mode 100755
index 0000000..d588f42
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png
new file mode 100644
index 0000000..7af930e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png
new file mode 100644
index 0000000..71bc905
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png
new file mode 100644
index 0000000..abc3282
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_export.png b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png
new file mode 100644
index 0000000..417a4a2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg b/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg
new file mode 100644
index 0000000..dc6b01f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="186.69"
+ height="206.07"
+ id="svg3055"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="database-309919.svg">
+ <metadata
+ id="metadata3085">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1033"
+ id="namedview3083"
+ showgrid="false"
+ inkscape:zoom="2.930408"
+ inkscape:cx="42.20224"
+ inkscape:cy="87.323742"
+ inkscape:window-x="1364"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg3055" />
+ <defs
+ id="defs3057">
+ <filter
+ id="filter6093"
+ style="color-interpolation-filters:sRGB"
+ height="1.3855"
+ width="1.1285"
+ y="-.19277"
+ x="-.064257">
+ <feGaussianBlur
+ id="feGaussianBlur6095"
+ stdDeviation="0.892504" />
+ </filter>
+ <linearGradient
+ id="linearGradient6112"
+ y2=".99995"
+ x2="0.5"
+ y1=".21006"
+ x1=".37178">
+ <stop
+ id="stop5468"
+ stop-color="#e3e3e3"
+ offset="0" />
+ <stop
+ id="stop5470"
+ stop-color="#a5a5a8"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6114"
+ y2="0.5"
+ xlink:href="#linearGradient6035"
+ spreadMethod="reflect"
+ y1=".85309"
+ x1=".51071" />
+ <linearGradient
+ id="linearGradient6110"
+ y2=".48579"
+ spreadMethod="reflect"
+ x2="0.5"
+ y1=".48579">
+ <stop
+ id="stop5497"
+ stop-color="#c6c6c6"
+ offset="0" />
+ <stop
+ id="stop5501"
+ stop-color="#e3e3e3"
+ offset=".20485" />
+ <stop
+ id="stop5499"
+ stop-color="#a5a5a8"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6035">
+ <stop
+ id="stop6037"
+ stop-color="#fff"
+ offset="0" />
+ <stop
+ id="stop6039"
+ stop-color="#fff"
+ stop-opacity="0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-6"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-4" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-9" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-9"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-8" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-4" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-9-3"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-8-3" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-4-1" />
+ </linearGradient>
+ </defs>
+ <path
+ id="path6079"
+ filter="url(#filter6093)"
+ fill="#2e3436"
+ d="m176.06 167.87c0 15.228-37.033 27.572-82.716 27.572s-82.716-12.344-82.716-27.572 37.033-27.572 82.716-27.572 82.716 12.344 82.716 27.572z" />
+ <path
+ id="path5488"
+ fill="url(#linearGradient6110)"
+ d="m10.662 28.604v126.36h0.5249c-0.33844 1.0132-0.5249 1.975-0.5249 3.0182 0 15.228 36.99 27.558 82.672 27.558 45.683 0 82.672-12.33 82.672-27.558 0-1.0447-0.18556-2.0038-0.52492-3.0182h0.52492v-126.36h-165.34z" />
+ <path
+ id="path5460"
+ fill="url(#linearGradient6112)"
+ d="m176.06 27.572c0 15.228-37.033 27.572-82.716 27.572s-82.716-12.344-82.716-27.572 37.033-27.572 82.716-27.572 82.716 12.344 82.716 27.572z" />
+ <path
+ id="path6026"
+ fill="#565656"
+ d="m10.531 119.28c0.61871 15.05 37.507 27.164 82.804 27.164 45.168 0 81.85-12.042 82.672-27.033-14.494 12.329-46.02 20.865-82.672 20.865-36.727 0-68.35-8.6271-82.804-20.996z" />
+ <path
+ id="path6030"
+ fill="url(#linearGradient6114)"
+ d="m10.531 27.949c0.6173 15.05 37.508 27.161 82.805 27.161 45.167 0 81.852-12.039 82.67-27.03-0.90294 0.76839-1.852 1.4906-2.8849 2.2298-0.037933 0.050491-0.092529 0.084066-0.1302 0.1302-9.629 11.666-41.589 20.211-79.655 20.211-38.306 0-70.488-8.6966-79.916-20.471-0.10496-0.075609-0.15955-0.18889-0.2645-0.2645-0.91961-0.66766-1.8141-1.2725-2.6245-1.9653z" />
+ <g
+ id="g6071"
+ transform="matrix(4.1992 0 0 4.1992 -914.56 -1633.8)">
+ <g
+ id="g6067">
+ <path
+ id="path6022"
+ fill="#565656"
+ d="m220.3 406.96c0.14734 3.584 8.9318 6.4688 19.719 6.4688 10.756 0 19.492-2.8677 19.688-6.4375-3.4515 2.9359-10.959 4.9688-19.688 4.9688-8.7461 0-16.277-2.0544-19.719-5z" />
+ <path
+ id="path6043"
+ fill="url(#linearGradient6114)"
+ d="m220.3 406.96c0.147 3.584 8.932 6.468 19.719 6.468 10.756 0 19.492-2.867 19.687-6.437-0.049011 0.041992-0.10504 0.083984-0.15601 0.125-1.242 3.227-9.511 5.719-19.531 5.719-10.113 0-18.468-2.511-19.594-5.781-0.040009-0.031982-0.085999-0.060974-0.125-0.093994z" />
+ </g>
+ <path
+ id="path6054"
+ fill="url(#linearGradient6114)"
+ d="m220.3 417.48c0.147 3.584 8.932 6.468 19.719 6.468 10.756 0 19.492-2.867 19.687-6.437-0.049011 0.041992-0.10504 0.083984-0.15601 0.125-1.242 3.227-9.511 5.719-19.531 5.719-10.113 0-18.468-2.511-19.594-5.781-0.040009-0.031982-0.085999-0.060974-0.125-0.093994z" />
+ </g>
+ <path
+ id="path6060"
+ fill="#565656"
+ d="m11.056 164.82c4.7284 13.732 39.813 24.277 82.279 24.277 42.079 0 76.8-10.464 82.016-24.014-14.766 12.043-45.892 20.34-82.016 20.34-36.314 0-67.608-8.4562-82.279-20.602z" />
+ <g
+ transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,261.34303,316.19589)"
+ id="g3153">
+ <path
+ style="fill:url(#linearGradient7124);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157" />
+ </g>
+ <g
+ transform="matrix(-0.70710678,-0.70710678,0.70710678,-0.70710678,296.91217,-74.582918)"
+ id="g3153-6">
+ <path
+ style="fill:url(#linearGradient7124-6);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-7" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-8" />
+ </g>
+ <g
+ transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,-73.797832,-110.37774)"
+ id="g3153-64">
+ <path
+ style="fill:url(#linearGradient7124-9);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-2" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-0" />
+ </g>
+ <g
+ transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,-109.22498,280.26704)"
+ id="g3153-64-6">
+ <path
+ style="fill:url(#linearGradient7124-9-3);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-2-7" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-0-0" />
+ </g>
+</svg>
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_file.png b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png
new file mode 100644
index 0000000..a36f74b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg b/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg
new file mode 100644
index 0000000..83ad166
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="186.69"
+ height="206.07"
+ id="svg3055"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="database_export_wizard.svg">
+ <metadata
+ id="metadata3085">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1033"
+ id="namedview3083"
+ showgrid="false"
+ inkscape:zoom="2.930408"
+ inkscape:cx="42.20224"
+ inkscape:cy="87.323742"
+ inkscape:window-x="1364"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg3055" />
+ <defs
+ id="defs3057">
+ <filter
+ id="filter6093"
+ style="color-interpolation-filters:sRGB"
+ height="1.3855"
+ width="1.1285"
+ y="-.19277"
+ x="-.064257">
+ <feGaussianBlur
+ id="feGaussianBlur6095"
+ stdDeviation="0.892504" />
+ </filter>
+ <linearGradient
+ id="linearGradient6112"
+ y2=".99995"
+ x2="0.5"
+ y1=".21006"
+ x1=".37178">
+ <stop
+ id="stop5468"
+ stop-color="#e3e3e3"
+ offset="0" />
+ <stop
+ id="stop5470"
+ stop-color="#a5a5a8"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6114"
+ y2="0.5"
+ xlink:href="#linearGradient6035"
+ spreadMethod="reflect"
+ y1=".85309"
+ x1=".51071" />
+ <linearGradient
+ id="linearGradient6110"
+ y2=".48579"
+ spreadMethod="reflect"
+ x2="0.5"
+ y1=".48579">
+ <stop
+ id="stop5497"
+ stop-color="#c6c6c6"
+ offset="0" />
+ <stop
+ id="stop5501"
+ stop-color="#e3e3e3"
+ offset=".20485" />
+ <stop
+ id="stop5499"
+ stop-color="#a5a5a8"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6035">
+ <stop
+ id="stop6037"
+ stop-color="#fff"
+ offset="0" />
+ <stop
+ id="stop6039"
+ stop-color="#fff"
+ stop-opacity="0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-6"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-4" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-9" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-9"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-8" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-4" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7124-9-3"
+ x1="34.04665"
+ x2="-27.52286"
+ y1="-157.41988"
+ y2="-258.10107">
+ <stop
+ offset="0"
+ stop-color="#003dae"
+ id="stop3149-8-3" />
+ <stop
+ offset="1"
+ stop-color="#003dae"
+ stop-opacity="0"
+ id="stop3151-4-1" />
+ </linearGradient>
+ </defs>
+ <path
+ id="path6079"
+ filter="url(#filter6093)"
+ fill="#2e3436"
+ d="m176.06 167.87c0 15.228-37.033 27.572-82.716 27.572s-82.716-12.344-82.716-27.572 37.033-27.572 82.716-27.572 82.716 12.344 82.716 27.572z" />
+ <path
+ id="path5488"
+ fill="url(#linearGradient6110)"
+ d="m10.662 28.604v126.36h0.5249c-0.33844 1.0132-0.5249 1.975-0.5249 3.0182 0 15.228 36.99 27.558 82.672 27.558 45.683 0 82.672-12.33 82.672-27.558 0-1.0447-0.18556-2.0038-0.52492-3.0182h0.52492v-126.36h-165.34z" />
+ <path
+ id="path5460"
+ fill="url(#linearGradient6112)"
+ d="m176.06 27.572c0 15.228-37.033 27.572-82.716 27.572s-82.716-12.344-82.716-27.572 37.033-27.572 82.716-27.572 82.716 12.344 82.716 27.572z" />
+ <path
+ id="path6026"
+ fill="#565656"
+ d="m10.531 119.28c0.61871 15.05 37.507 27.164 82.804 27.164 45.168 0 81.85-12.042 82.672-27.033-14.494 12.329-46.02 20.865-82.672 20.865-36.727 0-68.35-8.6271-82.804-20.996z" />
+ <path
+ id="path6030"
+ fill="url(#linearGradient6114)"
+ d="m10.531 27.949c0.6173 15.05 37.508 27.161 82.805 27.161 45.167 0 81.852-12.039 82.67-27.03-0.90294 0.76839-1.852 1.4906-2.8849 2.2298-0.037933 0.050491-0.092529 0.084066-0.1302 0.1302-9.629 11.666-41.589 20.211-79.655 20.211-38.306 0-70.488-8.6966-79.916-20.471-0.10496-0.075609-0.15955-0.18889-0.2645-0.2645-0.91961-0.66766-1.8141-1.2725-2.6245-1.9653z" />
+ <g
+ id="g6071"
+ transform="matrix(4.1992 0 0 4.1992 -914.56 -1633.8)">
+ <g
+ id="g6067">
+ <path
+ id="path6022"
+ fill="#565656"
+ d="m220.3 406.96c0.14734 3.584 8.9318 6.4688 19.719 6.4688 10.756 0 19.492-2.8677 19.688-6.4375-3.4515 2.9359-10.959 4.9688-19.688 4.9688-8.7461 0-16.277-2.0544-19.719-5z" />
+ <path
+ id="path6043"
+ fill="url(#linearGradient6114)"
+ d="m220.3 406.96c0.147 3.584 8.932 6.468 19.719 6.468 10.756 0 19.492-2.867 19.687-6.437-0.049011 0.041992-0.10504 0.083984-0.15601 0.125-1.242 3.227-9.511 5.719-19.531 5.719-10.113 0-18.468-2.511-19.594-5.781-0.040009-0.031982-0.085999-0.060974-0.125-0.093994z" />
+ </g>
+ <path
+ id="path6054"
+ fill="url(#linearGradient6114)"
+ d="m220.3 417.48c0.147 3.584 8.932 6.468 19.719 6.468 10.756 0 19.492-2.867 19.687-6.437-0.049011 0.041992-0.10504 0.083984-0.15601 0.125-1.242 3.227-9.511 5.719-19.531 5.719-10.113 0-18.468-2.511-19.594-5.781-0.040009-0.031982-0.085999-0.060974-0.125-0.093994z" />
+ </g>
+ <path
+ id="path6060"
+ fill="#565656"
+ d="m11.056 164.82c4.7284 13.732 39.813 24.277 82.279 24.277 42.079 0 76.8-10.464 82.016-24.014-14.766 12.043-45.892 20.34-82.016 20.34-36.314 0-67.608-8.4562-82.279-20.602z" />
+ <g
+ transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,27.195064,10.727331)"
+ id="g3153">
+ <path
+ style="fill:url(#linearGradient7124);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157" />
+ </g>
+ <g
+ transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,-8.5563892,159.56505)"
+ id="g3153-6">
+ <path
+ style="fill:url(#linearGradient7124-6);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-7" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-8" />
+ </g>
+ <g
+ transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,160.35013,195.09082)"
+ id="g3153-64">
+ <path
+ style="fill:url(#linearGradient7124-9);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-2" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-0" />
+ </g>
+ <g
+ transform="matrix(-0.70710678,-0.70710678,0.70710678,-0.70710678,196.24358,46.119074)"
+ id="g3153-64-6">
+ <path
+ style="fill:url(#linearGradient7124-9-3);fill-rule:evenodd;stroke:#003dae;stroke-width:4;stroke-linejoin:bevel"
+ inkscape:connector-curvature="0"
+ d="m 70.08345,-180.7205 -19.83086,-0.029 -0.01686,-40.01259 -49.98703,0.0152 -0.0066,39.97428 -19.99585,0.0189 44.96289,44.66165 44.87431,-44.62843 z"
+ id="path3155-2-7" />
+ <path
+ style="fill:#003dae;fill-rule:evenodd"
+ inkscape:connector-curvature="0"
+ d="m -0.43765,-162.63407 c 0,0 39.27911,7.11183 49.45448,-35.66848 l 1.53178,17.72483 17.28718,0.8753 -42.88972,42.67089 -25.38371,-25.60254 z"
+ id="path3157-0-0" />
+ </g>
+</svg>
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png
new file mode 100644
index 0000000..7ccd0a3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_network.png b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png
new file mode 100644
index 0000000..03f6f90
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png
new file mode 100644
index 0000000..6888e5e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_online.png b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png
new file mode 100644
index 0000000..d588f42
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png
new file mode 100644
index 0000000..007b6d4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png
new file mode 100644
index 0000000..57de2d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/default.png b/SQLiteStudio3/guiSQLiteStudio/img/default.png
new file mode 100644
index 0000000..f231987
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/default.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png
new file mode 100644
index 0000000..6dc019a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png
new file mode 100644
index 0000000..ee473b9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png
new file mode 100644
index 0000000..de13448
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png
new file mode 100644
index 0000000..c9fb206
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory.png b/SQLiteStudio3/guiSQLiteStudio/img/directory.png
new file mode 100644
index 0000000..260b415
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/directory.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png
new file mode 100644
index 0000000..dbaa6ee
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png
new file mode 100644
index 0000000..abcf5dc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png
new file mode 100644
index 0000000..33c6d88
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png
new file mode 100644
index 0000000..8b941e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/erase.png b/SQLiteStudio3/guiSQLiteStudio/img/erase.png
new file mode 100644
index 0000000..bc6a3fa
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/erase.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/error_small.png b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png
new file mode 100644
index 0000000..2a8f0f4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png
new file mode 100644
index 0000000..2dfaef5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png
new file mode 100644
index 0000000..298343e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/export.png b/SQLiteStudio3/guiSQLiteStudio/img/export.png
new file mode 100644
index 0000000..b8df9d6
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/export.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png b/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png
new file mode 100644
index 0000000..c619461
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png
new file mode 100644
index 0000000..49f5acc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/fk.png b/SQLiteStudio3/guiSQLiteStudio/img/fk.png
new file mode 100644
index 0000000..968bcb9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/fk.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png
new file mode 100644
index 0000000..d5ab25d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png
new file mode 100644
index 0000000..6db18b4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/function.png b/SQLiteStudio3/guiSQLiteStudio/img/function.png
new file mode 100644
index 0000000..18fa585
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/function.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/get_update.png b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png
new file mode 100644
index 0000000..bc7f13f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/go_back.png b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png
new file mode 100644
index 0000000..2a361a0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/help.png b/SQLiteStudio3/guiSQLiteStudio/img/help.png
new file mode 100644
index 0000000..4ecaf37
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/help.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/homepage.png b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png
new file mode 100644
index 0000000..73377f9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/import.png b/SQLiteStudio3/guiSQLiteStudio/img/import.png
new file mode 100644
index 0000000..4cab099
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/import.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/index.png b/SQLiteStudio3/guiSQLiteStudio/img/index.png
new file mode 100644
index 0000000..7ac2093
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/index.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indexes.png b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png
new file mode 100644
index 0000000..90ddfab
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png
new file mode 100644
index 0000000..bb51f6f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png
new file mode 100644
index 0000000..0df7fb3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png
new file mode 100644
index 0000000..d98378b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png
new file mode 100644
index 0000000..62e4690
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png
new file mode 100644
index 0000000..f14f2a3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_small.png b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png
new file mode 100644
index 0000000..0e0db2a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png
new file mode 100644
index 0000000..10d1f60
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png
new file mode 100644
index 0000000..55a2a7c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png
new file mode 100644
index 0000000..57de2d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png
new file mode 100644
index 0000000..985b883
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyword.png b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png
new file mode 100644
index 0000000..1ca13e8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/licenses.png b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png
new file mode 100644
index 0000000..8918410
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/loading.gif b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif
new file mode 100644
index 0000000..5b33f7e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png
new file mode 100644
index 0000000..46d56ae
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_down.png b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png
new file mode 100644
index 0000000..9c53794
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_up.png b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png
new file mode 100644
index 0000000..a76885b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/not_null.png b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png
new file mode 100644
index 0000000..94af62e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png
new file mode 100644
index 0000000..6aa2603
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png
new file mode 100644
index 0000000..e999199
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png
new file mode 100644
index 0000000..dbaa6ee
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png b/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png
new file mode 100644
index 0000000..803ed0b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_first.png b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png
new file mode 100644
index 0000000..37a6036
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_last.png b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png
new file mode 100644
index 0000000..16d38fc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_next.png b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png
new file mode 100644
index 0000000..5102071
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png
new file mode 100644
index 0000000..058be1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/pk.png b/SQLiteStudio3/guiSQLiteStudio/img/pk.png
new file mode 100644
index 0000000..8284636
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/pk.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plugin.png b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png
new file mode 100644
index 0000000..172881f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png
new file mode 100644
index 0000000..0f8a230
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/question_small.png b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png
new file mode 100644
index 0000000..b6862db
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/reload.png b/SQLiteStudio3/guiSQLiteStudio/img/reload.png
new file mode 100644
index 0000000..0a85ddc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/reload.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png b/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png
new file mode 100644
index 0000000..250d047
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/results_below.png b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png
new file mode 100644
index 0000000..50e735d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png b/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png
new file mode 100644
index 0000000..00eb6fc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/rollback.png b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png
new file mode 100644
index 0000000..933272b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png b/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png
new file mode 100644
index 0000000..c619461
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/set_null.png b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png
new file mode 100644
index 0000000..1c28f6c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png
new file mode 100644
index 0000000..912f7d2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png
new file mode 100644
index 0000000..6800b00
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png
new file mode 100644
index 0000000..a3991b0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png
new file mode 100644
index 0000000..614958a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png
new file mode 100644
index 0000000..fddfd80
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png
new file mode 100644
index 0000000..599aa1c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png
new file mode 100644
index 0000000..1fcbf6d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png
new file mode 100644
index 0000000..e174c80
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png
new file mode 100644
index 0000000..43e957b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png
new file mode 100644
index 0000000..618c22e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png
new file mode 100644
index 0000000..b498310
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png
new file mode 100644
index 0000000..f17cf83
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png
new file mode 100644
index 0000000..dece042
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png
new file mode 100644
index 0000000..60fa094
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png
new file mode 100644
index 0000000..dfb92a0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png
new file mode 100644
index 0000000..d93ff1e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png
new file mode 100644
index 0000000..6755cd8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png
new file mode 100644
index 0000000..9418d92
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png
new file mode 100644
index 0000000..0971931
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png
new file mode 100644
index 0000000..0ccac28
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png
new file mode 100644
index 0000000..31f56cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png
new file mode 100644
index 0000000..aa79bc1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png
new file mode 100644
index 0000000..b8e6af1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png
new file mode 100644
index 0000000..6d88b78
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png
new file mode 100644
index 0000000..5717d70
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sql.png b/SQLiteStudio3/guiSQLiteStudio/img/sql.png
new file mode 100644
index 0000000..ea232a7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sql.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png
new file mode 100644
index 0000000..32df661
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns
new file mode 100644
index 0000000..70ac0cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico
new file mode 100644
index 0000000..0aef62d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg
new file mode 100644
index 0000000..af50fa0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="665.4375"
+ height="664.125"
+ id="svg2"
+ inkscape:version="0.48.5 r10040"
+ sodipodi:docname="sqlitestudio.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1280"
+ inkscape:window-height="971"
+ id="namedview3121"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.4899813"
+ inkscape:cx="282.48639"
+ inkscape:cy="414.39859"
+ inkscape:window-x="1272"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ transform="translate(-40.59375,-212.0625)">
+ <g
+ transform="matrix(0.6691035,0,0,0.75072635,-262.9902,391.98527)"
+ id="g3201">
+ <path
+ d="M 861.51791,621.25254 C 730.37518,609.89015 614.64246,567.85816 596.4511,524.98508 c -8.89306,-20.95906 -9.73372,-625.76799 -0.89866,-646.5711 12.79194,-30.12006 54.0607,-54.95052 124.89135,-75.14433 76.07686,-21.68946 113.94432,-25.81036 237.92045,-25.89153 99.13206,-0.0649 121.98956,1.21806 167.74286,9.41516 110.2356,19.74967 178.0344,51.51085 194.9427,91.32338 4.8927,11.5206 5.94,67.662367 5.5907,299.72497 -0.2361,157.052 -1.9294,289.39496 -3.7624,294.09541 -1.8331,4.70049 -1.4617,14.54427 0.8252,21.87502 25.6992,82.38091 -215.2952,148.8314 -462.18539,127.44048 z M 989.26424,404.76216 c -1.5005,-2.38857 -12.5431,-4.34285 -24.5391,-4.34285 -11.996,0 -21.81089,1.95428 -21.81089,4.34285 0,2.38858 11.0426,4.34286 24.5391,4.34286 15.23528,0 23.5047,-1.64655 21.81089,-4.34286 z m 6.06965,-393.571412 c 7.58701,-1.4928567 13.79461,-5.401428 13.79461,-8.6857136 0,-4.6875496 -9.96558,-5.9714284 -46.34997,-5.9714284 -25.4925,0 -46.35,0.8375634 -46.35,1.8612182 0,1.02369828 3.12152,4.9322696 6.93674,8.685714 7.29836,7.1803058 45.32765,9.3521688 71.96862,4.1102098 z"
+ id="path3325"
+ style="fill:#e5e9f9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 887.73568,623.38054 C 767.26055,613.77883 671.26631,588.43566 622.52139,553.36205 588.95485,529.20977 590.6468,547.13808 589.96471,208.38681 c -0.43613,-216.6373387 0.83774,-308.065813 4.47798,-321.37142 10.15833,-37.13016 59.60371,-66.41235 149.66714,-88.6348 68.2246,-16.83389 133.41415,-22.71398 236.32584,-21.31656 104.62883,1.42074 168.82503,10.29914 237.03723,32.78253 61.211,20.17574 93.8305,42.26516 104.8745,71.01935 5.5102,14.34637 5.9848,576.86686 0.4987,591.07113 -1.8154,4.70049 -1.3513,14.79546 1.0313,22.43329 14.1338,45.30681 -66.7417,94.60858 -191.1489,116.52459 -71.6276,12.61812 -176.35929,17.95558 -244.99282,12.48562 z M 1020.1642,404.76216 c 1.7635,-2.80718 -16.8756,-4.34285 -52.71085,-4.34285 -34.01653,0 -55.4391,1.67816 -55.4391,4.34285 0,2.65279 20.51314,4.34286 52.71089,4.34286 30.37898,0 53.86676,-1.83994 55.43906,-4.34286 z M 1063.8947,6.9454754 c 4.0738,-1.537936 6.7188,-4.8274764 5.8776,-7.31007064 C 1068.7775,-3.300714 1029.7546,-5.1309677 958.12503,-5.6010385 897.5602,-5.9984099 842.04782,-7.8723527 834.76425,-9.7651435 l -13.24286,-3.4413665 9.44476,9.6985114 c 7.38842,7.5868844 16.99959,10.4761437 44.14286,13.269947 37.41239,3.8507676 176.62679,1.7738396 188.78569,-2.816473 z M 635.92413,-67.116739 c -8.30645,-5.975598 -13.52564,-4.940695 -9.26749,1.837593 1.35025,2.14941 5.78484,3.908006 9.85463,3.908006 6.62752,0 6.56625,-0.599574 -0.58714,-5.745599 z"
+ id="path3323"
+ style="fill:#dce0f1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 887.73568,623.38054 C 767.26055,613.77883 671.26631,588.43566 622.52139,553.36205 588.86846,529.14767 590.58624,547.9039 590.41752,202.81931 c -0.12889,-263.43527 0.72386,-306.44532 5.8548,-295.314272 9.24965,20.066127 35.62527,42.793427 50.86776,43.83163 14.72349,1.002853 17.40133,-4.198848 5.53489,-10.751481 -6.19364,-3.42013 21.15194,-18.173206 137.94643,-74.422797 80.11928,-38.58638 153.12052,-73.13037 162.22499,-76.76443 9.10446,-3.6341 16.55357,-8.38748 16.55357,-10.56309 0,-6.41138 99.02184,-0.68347 156.07034,9.02788 110.6781,18.84067 181.1852,52.14765 196.8771,93.00316 5.5102,14.34637 5.9848,576.86686 0.4987,591.07113 -1.8154,4.70049 -1.3513,14.79546 1.0313,22.43329 14.1338,45.30681 -66.7417,94.60858 -191.1489,116.52459 -71.6276,12.61812 -176.35929,17.95558 -244.99282,12.48562 z M 1251.9142,447.93442 c 0,-54.07448 -1.5304,-81.84006 -4.4143,-80.08654 -2.6846,1.63235 -4.4142,34.06415 -4.4142,82.7706 0,50.49579 1.6309,80.08654 4.4142,80.08654 2.7885,0 4.4143,-30.48543 4.4143,-82.7706 z M 673.57525,419.13276 c 0.0386,-27.79159 -1.82249,-49.75007 -4.34671,-51.28488 -2.85961,-1.73875 -4.39049,17.67208 -4.3467,55.11511 0.0455,38.92242 1.46515,55.67165 4.3467,51.28489 2.35352,-3.58286 4.30954,-28.38466 4.34671,-55.11512 z m 351.00325,-14.3706 c 5.4896,-2.32078 -15.7961,-3.84781 -56.28211,-4.03759 -40.67111,-0.1907 -65.11071,1.32483 -65.11071,4.03759 0,5.36756 108.69642,5.36756 121.39282,0 z M 673.64283,254.9336 c 0,-50.66668 -1.63051,-80.34286 -4.41429,-80.34286 -2.77782,0 -4.41428,28.71111 -4.41428,77.44761 0,68.10928 1.33717,83.2381 7.35716,83.2381 0.80927,0 1.47141,-36.15428 1.47141,-80.34285 z m 578.27137,-2.42775 c 0,-49.04818 -1.6354,-77.91511 -4.4143,-77.91511 -2.7843,0 -4.4142,29.76165 -4.4142,80.59917 0,52.62687 1.5319,79.66763 4.4142,77.91511 2.6775,-1.62796 4.4143,-33.34033 4.4143,-80.59917 z M 673.64283,57.333604 c 0,-55.0095365 -1.61859,-86.85714 -4.41429,-86.85714 -2.79062,0 -4.41428,30.8825338 -4.41428,83.961888 0,73.869128 1.30199,89.752388 7.35716,89.752388 0.80927,0 1.47141,-39.08571 1.47141,-86.857136 z M 1067.6178,6.8940994 c 10.0196,-1.7016183 16.5536,-5.2465187 16.5536,-8.9809849 0,-5.2046101 -11.9112,-5.7163724 -76.1464,-3.271561 -68.03314,2.589385 -161.25164,-1.6693074 -207.77984,-9.4924865 l -14.03805,-2.360343 11.03571,8.9494124 c 12.25216,9.9358923 24.58872,12.9388476 74.30188,18.0866097 36.08117,3.7362029 168.4681,1.7574679 196.0731,-2.9306467 z"
+ id="path3321"
+ style="fill:#e2e3e6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 889.94282,623.6858 C 761.54781,612.77103 667.55213,587.36501 617.98191,550.17761 c -19.57325,-14.68377 -30.41301,-38.8598 -25.0964,-55.97287 2.29693,-7.39341 2.64857,-19.0344 0.78141,-25.86884 -5.38075,-19.69551 -4.99573,-568.52671 0.39641,-565.248026 2.49508,1.51709 4.53651,5.177815 4.53651,8.134953 0,10.469456 34.70997,39.171832 48.50722,40.111583 14.75219,1.004807 17.44367,-4.191378 5.56778,-10.749223 -6.19364,-3.42013 21.15193,-18.173206 137.94642,-74.422797 80.11928,-38.58638 153.12053,-73.13037 162.22499,-76.76443 9.10447,-3.6341 16.55358,-8.30711 16.55358,-10.38448 0,-5.19903 89.49507,-0.70201 144.83707,7.2779 99.5513,14.35458 185.2679,50.06389 204.3426,85.12868 3.1593,5.80762 4.5467,104.561844 4.2958,305.75903 -0.2039,163.44946 -0.7405,298.62131 -1.1925,300.38195 -0.4521,1.7606 0.687,9.46096 2.5314,17.11186 12.3554,51.25418 -74.5011,98.86449 -220.2248,120.71592 -43.3566,6.50138 -175.09784,11.60798 -214.04671,8.29698 z M 1251.9142,447.93442 c 0,-54.07448 -1.5304,-81.84006 -4.4143,-80.08654 -2.6846,1.63235 -4.4142,34.06415 -4.4142,82.7706 0,50.49579 1.6309,80.08654 4.4142,80.08654 2.7885,0 4.4143,-30.48543 4.4143,-82.7706 z M 673.57525,419.13276 c 0.0386,-27.79159 -1.82249,-49.75007 -4.34671,-51.28488 -2.85961,-1.73875 -4.39049,17.67208 -4.3467,55.11511 0.0455,38.92242 1.46515,55.67165 4.3467,51.28489 2.35352,-3.58286 4.30954,-28.38466 4.34671,-55.11512 z m 351.00325,-14.3706 c 5.4896,-2.32078 -15.7961,-3.84781 -56.28211,-4.03759 -40.67111,-0.1907 -65.11071,1.32483 -65.11071,4.03759 0,5.36756 108.69642,5.36756 121.39282,0 z M 673.64283,254.9336 c 0,-50.66668 -1.63051,-80.34286 -4.41429,-80.34286 -2.77782,0 -4.41428,28.71111 -4.41428,77.44761 0,68.10928 1.33717,83.2381 7.35716,83.2381 0.80927,0 1.47141,-36.15428 1.47141,-80.34285 z m 578.27137,-2.42775 c 0,-49.04818 -1.6354,-77.91511 -4.4143,-77.91511 -2.7843,0 -4.4142,29.76165 -4.4142,80.59917 0,52.62687 1.5319,79.66763 4.4142,77.91511 2.6775,-1.62796 4.4143,-33.34033 4.4143,-80.59917 z M 673.64283,57.333604 c 0,-55.0095365 -1.61859,-86.85714 -4.41429,-86.85714 -2.79062,0 -4.41428,30.8825338 -4.41428,83.961888 0,73.869128 1.30199,89.752388 7.35716,89.752388 0.80927,0 1.47141,-39.08571 1.47141,-86.857136 z M 1067.6178,6.8940994 c 10.0196,-1.7016183 16.5536,-5.2465187 16.5536,-8.9809849 0,-5.2046101 -11.9112,-5.7163724 -76.1464,-3.271561 -68.03314,2.589385 -161.25164,-1.6693074 -207.77984,-9.4924865 l -14.03805,-2.360343 11.03571,8.9494124 c 12.25216,9.9358923 24.58872,12.9388476 74.30188,18.0866097 36.08117,3.7362029 168.4681,1.7574679 196.0731,-2.9306467 z"
+ id="path3319"
+ style="fill:#dadff0"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 889.94282,623.6858 C 761.54781,612.77103 667.55213,587.36501 617.98191,550.17761 c -19.57325,-14.68377 -30.41301,-38.8598 -25.0964,-55.97287 2.29693,-7.39341 2.64857,-19.0344 0.78141,-25.86884 -5.38075,-19.69551 -4.99573,-568.52671 0.39641,-565.248026 2.49508,1.51709 4.53651,5.177815 4.53651,8.134953 0,11.678637 52.74069,52.869681 63.17276,49.33859 2.88676,-0.977142 10.71135,-0.259702 17.38795,1.594611 18.21997,5.059949 14.60321,-2.295895 -5.51785,-11.22242 -9.71143,-4.308375 -17.65714,-9.207465 -17.65714,-10.886848 0,-1.679383 17.38124,-11.001716 38.62499,-20.716297 21.24375,-9.71458 97.69419,-46.098213 169.88987,-80.852573 l 131.26483,-63.1897 34.27085,2.29967 c 145.5667,9.76793 263.7139,48.18852 288.5434,93.8322 3.1593,5.80762 4.5467,104.561844 4.2958,305.75903 -0.2039,163.44946 -0.7405,298.62131 -1.1925,300.38195 -0.4521,1.7606 0.687,9.46096 2.5314,17.11186 12.3554,51.25418 -74.5011,98.86449 -220.2248,120.71592 -43.3566,6.50138 -175.09784,11.60798 -214.04671,8.29698 z M 681.45956,510.02581 c -1.25635,-5.78547 -3.38099,-4.32379 -9.09321,6.2558 -7.03169,13.02327 -7.01911,13.89036 0.22204,15.30991 8.00041,1.56834 11.85827,-7.81002 8.87117,-21.56571 z m 573.86284,-63.41457 c 0.7192,-48.47193 -0.4639,-80.93479 -2.9497,-80.93479 -17.5909,0 -18.1156,2.52524 -18.1156,87.18181 l 0,81.90972 9.9321,-3.611 c 9.7516,-3.54534 9.954,-5.08214 11.1332,-84.54574 z m -572.851,-37.93746 c 0,-21.25994 -1.82398,-38.65447 -4.05324,-38.65447 -2.2293,0 -7.19537,-1.18621 -11.03571,-2.63607 -6.17515,-2.33129 -6.97749,4.82396 -6.93935,61.88571 l 0.0441,64.52178 10.99259,-23.23124 c 9.12093,-19.27786 10.99157,-29.80924 10.99157,-61.88571 z m 346.5214,-3.91162 c 5.454,-2.30575 -17.746,-3.84903 -60.69641,-4.03759 -43.61398,-0.19152 -69.525,1.31328 -69.525,4.03759 0,5.37399 117.50981,5.37399 130.22141,0 z M 677.05613,178.23305 c -15.63602,-9.76287 -16.65616,-5.23878 -16.65616,73.86549 0,76.19008 0.16907,77.58197 9.93215,81.87197 l 9.93214,4.36427 1.20616,-78.2962 c 0.91998,-59.71754 -0.12758,-79.12889 -4.41429,-81.80553 z m 579.27237,71.75369 0,-79.55263 -11.0357,4.94682 -11.0357,4.94686 0,79.55267 0,79.55263 11.0357,-4.94682 11.0357,-4.94686 0,-79.55267 z M 682.4714,60.373604 c 0,-84.28682 -1.72501,-94.239997 -16.33286,-94.239997 -8.18585,0 -8.40246,171.932973 -0.22071,175.180953 16.20444,6.43281 16.55357,4.72563 16.55357,-80.940956 z M 1074.2392,6.8236582 C 1089.0415,4.6554869 1093,2.2643532 1093,-4.5090271 c 0,-6.7220479 -2.1551,-8.0613419 -9.9322,-6.1727629 -5.4627,1.3265688 -39.7286,3.7250853 -76.1464,5.3300315 -69.58346,3.0665782 -170.45915,-2.7722193 -224.89184,-13.0170195 -26.12749,-4.91746 -29.43079,-4.884498 -22.33129,0.222789 4.58255,3.296619 10.93193,5.993881 14.10969,5.993881 3.17775,0 5.77772,1.815749 5.77772,4.0349484 0,16.2887972 212.04502,27.0407996 294.65352,14.9408178 z m 181.7215,-34.7152792 c 5.725,-5.079622 -3.5163,-4.307679 -12.8817,1.07603 l -11.0291,6.340094 11.0358,-2.892083 c 6.0696,-1.590658 11.8634,-3.626459 12.875,-4.524041 z"
+ id="path3317"
+ style="fill:#d8ddee"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 889.94282,623.6858 C 761.54781,612.77103 667.55213,587.36501 617.98191,550.17761 c -19.57325,-14.68377 -30.41301,-38.8598 -25.0964,-55.97287 2.29693,-7.39341 2.64857,-19.0344 0.78141,-25.86884 -1.86715,-6.83444 -3.52057,-137.77158 -3.67432,-290.97142 l -0.27942,-278.54516 11.02565,17.979649 c 6.1058,9.956824 20.8918,24.208344 33.13632,31.938456 l 22.11063,13.958767 0,86.626013 0,86.626015 15.30821,6.29271 c 8.41954,3.46095 16.36526,6.29267 17.65714,6.29267 1.29193,0 2.34893,-38.92285 2.34893,-86.495249 0,-65.0655953 -1.36706,-87.069115 -5.51786,-88.811512 -5.43248,-2.280478 9.91775,-0.523315 32.48694,3.718788 6.33547,1.190812 20.24047,5.952798 30.9,10.582196 36.32595,15.7762102 112.20778,24.112802 213.60948,23.467801 106.87428,-0.679831 135.96688,-4.0907109 138.34108,-16.2193985 1.4094,-7.1997625 -0.4339,-7.8730785 -15.3986,-5.6248245 -9.3498,1.4046532 -44.8096,3.8638394 -78.7996,5.4648771 -105.07834,4.9494238 -240.96095,-9.7786371 -303.87317,-32.9362271 -48.3668,-17.803499 -56.36147,-11.598512 128.74378,-99.923621 92.26647,-44.02598 166.84671,-81.49667 165.73382,-83.26819 -2.63418,-4.19322 54.18977,-0.30327 114.29997,7.82453 99.9927,13.52055 187.3797,49.49218 206.7538,85.10729 3.1593,5.80762 4.5468,104.561844 4.2958,305.75903 -0.2039,163.44946 -0.7404,298.62131 -1.1925,300.38195 -0.4521,1.7606 0.6871,9.46096 2.5314,17.11186 12.3555,51.25418 -74.501,98.86449 -220.2248,120.71592 -43.3566,6.50138 -175.09782,11.60798 -214.04669,8.29698 z M 691.29997,513.07727 c 0,-25.06184 -4.56124,-24.99839 -17.65153,0.24538 -8.12017,15.65925 -8.18917,16.90218 -1.0405,18.74442 17.48,4.50467 18.69203,3.27334 18.69203,-18.98997 z m 555.54123,17.20289 13.9016,-4.76768 0,-82.14775 0,-82.1477 -15.9891,6.57257 c -8.794,3.61491 -16.7397,6.57256 -17.6571,6.57256 -0.9175,0 -1.6681,36.15429 -1.6681,80.34286 0,44.18857 1.69,80.34285 3.7556,80.34285 2.0655,0 10.0112,-2.14545 17.6571,-4.76771 z M 690.23052,405.84788 c 0.97181,-17.31714 0.85938,-31.48571 -0.24985,-31.48571 -1.10926,0 -9.2119,-2.95766 -18.00591,-6.57257 l -15.98907,-6.57257 0.0539,72.80114 0.0539,72.80113 16.18502,-34.74285 c 12.6652,-27.1872 16.56933,-41.5902 17.95201,-66.22857 z M 1031.2,404.76216 c 6.5508,-2.06403 -19.1186,-3.65299 -63.16414,-3.90996 -48.45076,-0.28272 -73.25304,1.07417 -71.47161,3.90996 3.24361,5.16336 118.24795,5.16336 134.63575,0 z m -339.90003,-142.50994 0,-77.36708 -15.59977,-7.31863 c -8.57987,-4.02522 -16.52559,-7.31862 -17.65715,-7.31862 -1.13155,0 -2.05736,35.77702 -2.05736,79.50451 l 0,79.5045 12.13928,4.71921 c 23.8333,9.26532 23.175,11.30272 23.175,-71.72389 z m 554.78303,72.1518 14.6598,-5.2153 0,-79.47042 c 0,-43.70872 -0.7506,-79.47041 -1.6681,-79.47041 -0.9174,0 -8.8631,2.95766 -17.6571,6.57256 l -15.9891,6.57257 0,78.11314 c 0,42.96224 1.3488,78.11315 2.9973,78.11315 1.6485,0 9.5942,-2.34688 17.6572,-5.21529 z m 6.9348,-249.546604 c -3.0348,-1.204752 -8.0009,-1.204752 -11.0357,0 -3.0348,1.204752 -0.5518,2.190493 5.5178,2.190493 6.0697,0 8.5527,-0.985741 5.5179,-2.190493 z m 7.3572,-112.716856 c 6.0534,-5.476864 -4.9952,-5.118491 -17.7249,0.574951 l -15.0144,6.715273 15.45,-2.813042 c 8.4975,-1.547187 16.2776,-3.561882 17.2893,-4.477182 z"
+ id="path3315"
+ style="fill:#d7dcec"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 889.94282,623.6858 C 761.54781,612.77103 667.55213,587.36501 617.98191,550.17761 c -19.57325,-14.68377 -30.41301,-38.8598 -25.0964,-55.97287 2.29693,-7.39341 2.64857,-19.0344 0.78141,-25.86884 -1.86715,-6.83444 -3.5629,-137.7516 -3.76834,-290.92703 l -0.37345,-278.50078 12.57365,19.498474 c 6.91555,10.724164 20.876,24.900465 31.0232,31.502954 l 18.44947,12.004482 0,86.260692 0,86.260738 21.78617,9.22053 c 11.9824,5.07129 22.90776,9.2205 24.27857,9.2205 1.37077,0 2.49235,-39.31363 2.49235,-87.363649 l 0,-87.363648 9.93214,2.933426 c 5.46268,1.613415 32.55002,7.617154 60.19413,13.3416479 88.79401,18.3873091 187.85729,23.8342071 274.18799,15.0758807 55.8811,-5.669209 66.2143,-8.1263106 66.2143,-15.7450281 0,-5.4806425 -6.3617,-5.9111065 -43.0393,-2.9121027 C 926.82157,2.355423 762.73309,-12.924138 689.50358,-44.366205 l -21.66072,-9.300272 32.69643,-14.796765 c 17.98301,-8.13821 97.25532,-45.949118 176.16067,-84.024248 161.75174,-78.05199 145.19714,-73.76523 235.89864,-61.08504 101.084,14.13167 186.637,49.43285 205.9811,84.99259 3.1592,5.80762 4.5467,104.561844 4.2958,305.75903 -0.204,163.44946 -0.7405,298.62131 -1.1926,300.38195 -0.4521,1.7606 0.6871,9.46096 2.5314,17.11186 12.3555,51.25418 -74.501,98.86449 -220.2248,120.71592 -43.3566,6.50138 -175.09781,11.60798 -214.04668,8.29698 z M 699.9698,507.90502 699.81089,476.4193 688.96843,489.44788 c -5.96339,7.16571 -13.74454,18.89142 -17.2915,26.05714 -6.3526,12.83383 -6.23765,13.10479 7.69052,18.12674 7.77674,2.804 15.62935,5.24686 17.45025,5.42857 1.82089,0.18153 3.23929,-13.83817 3.15197,-31.15531 z m 546.9415,22.22096 22.6601,-9.2647 0,-81.63511 0,-81.63516 -25.3822,10.15568 -25.3821,10.15573 -1.201,80.74413 c -0.6705,45.07986 0.5318,80.74418 2.7221,80.74418 2.1576,0 14.12,-4.16914 26.5831,-9.26475 z M 681.06351,452.07529 c 11.19185,-24.48837 19.06503,-48.22756 19.06503,-57.48457 0,-16.23803 -5.10026,-20.27294 -39.00948,-30.86117 l -9.54766,-2.98129 0.29885,77.37838 c 0.23396,60.63132 1.36247,75.02854 5.21354,66.52124 2.70313,-5.97143 13.49402,-29.6291 23.97981,-52.57259 z m 352.34359,-47.31313 c 7.7728,-1.94581 -18.3176,-3.52744 -63.1641,-3.82905 -49.94265,-0.3357 -75.46199,0.99039 -73.67875,3.82905 3.17744,5.058 116.63825,5.058 136.84285,0 z m -333.27856,-139.12577 0,-78.32572 -20.23027,-8.53142 c -11.1266,-4.69223 -22.09677,-8.53136 -24.37811,-8.53136 -2.47995,0 -3.66412,31.8228 -2.94477,79.13732 l 1.20315,79.13736 19.86429,7.48361 c 10.92536,4.11599 21.35411,7.58993 23.175,7.71982 1.82089,0.12985 3.31071,-35.01042 3.31071,-78.08961 z m 544.99866,69.32803 24.4442,-8.99775 0,-79.96746 0,-79.96746 -22.9474,8.89869 c -12.6211,4.89427 -24.5397,9.8668 -26.4857,11.05005 -3.6558,2.22285 -5.1315,157.98168 -1.4968,157.98168 1.1229,0 13.0414,-4.04898 26.4857,-8.99775 z m 24.4442,-208.65769 c 0,-2.38857 -1.9865,-3.13507 -4.4143,-1.65884 -2.4279,1.47622 -4.4143,4.6383 -4.4143,7.02687 0,2.38857 1.9864,3.13507 4.4143,1.65884 2.4278,-1.47622 4.4143,-4.6383 4.4143,-7.02687 z m -9.9322,-41.683698 c -6.6766,-0.995079 -17.6019,-0.995079 -24.2785,0 -6.6766,0.995035 -1.214,1.80919 12.1392,1.80919 13.3533,0 18.8159,-0.814155 12.1393,-1.80919 z m 3.8625,-109.803711 c 3.3383,0 6.0697,-2.931428 6.0697,-6.514285 0,-8.142944 -2.3956,-7.889582 -24.2786,2.567887 l -17.6571,8.437998 14.8982,-2.245778 c 8.194,-1.235196 17.6295,-2.245822 20.9678,-2.245822 z"
+ id="path3313"
+ style="fill:#dcdde1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,623.70165 c -136.81257,-13.3918 -207.54249,-31.1709 -261.14569,-65.64315 -26.69146,-17.16527 -38.5688,-33.10985 -38.16105,-51.22882 0.38448,-17.08514 0.87023,-173.27213 0.93865,-301.83894 0.0309,-56.13142 0.13287,-145.993648 0.2291,-199.6938131 l 0.17472,-97.6367189 10.60055,15.347743 c 5.83035,8.441211 18.74213,20.648331 28.69286,27.126875 l 18.0923,11.779174 0,86.260692 0,86.260738 21.78618,9.22053 c 11.9824,5.07129 22.90776,9.2205 24.27857,9.2205 1.37081,0 2.49239,-39.31363 2.49239,-87.363649 l 0,-87.363648 9.93214,2.933426 c 5.46268,1.613415 32.55002,7.617154 60.19413,13.3416479 88.79401,18.3873091 187.85729,23.8342071 274.18799,15.0758807 55.8811,-5.669209 66.2143,-8.1263106 66.2143,-15.7450281 0,-5.4806425 -6.3617,-5.9111065 -43.0393,-2.9121027 C 927.03535,2.3379647 761.5411,-13.043914 690.18342,-44.23757 l -20.98083,-9.171679 175.48082,-83.648981 c 196.74379,-93.78473 180.29739,-88.97589 263.76659,-77.12399 81.586,11.58454 150.1969,34.1766 186.4814,61.40432 30.093,22.58168 28.2295,0.60605 27.9152,329.19164 -0.1567,163.77791 -0.6675,299.25638 -1.1352,301.06322 -0.4677,1.80685 0.6586,9.54504 2.5029,17.19594 12.4149,51.50085 -77.3961,100.203 -222.5184,120.66563 -45.9595,6.48045 -175.82804,11.66356 -209.54594,8.36312 z M 699.9698,507.90502 699.81089,476.4193 688.96843,489.44788 c -5.96339,7.16571 -13.74454,18.89142 -17.2915,26.05714 -6.3526,12.83383 -6.23765,13.10479 7.69052,18.12674 7.77674,2.804 15.62935,5.24686 17.45025,5.42857 1.82089,0.18153 3.23929,-13.83817 3.15197,-31.15531 z m 546.9415,22.22096 22.6601,-9.2647 0,-81.63511 0,-81.63516 -25.3822,10.15568 -25.3821,10.15573 -1.201,80.74413 c -0.6705,45.07986 0.5318,80.74418 2.7221,80.74418 2.1576,0 14.12,-4.16914 26.5831,-9.26475 z M 681.06351,452.07529 c 11.19185,-24.48837 19.06503,-48.22756 19.06503,-57.48457 0,-16.23803 -5.10026,-20.27294 -39.00948,-30.86117 l -9.54766,-2.98129 0.29885,77.37838 c 0.23396,60.63132 1.36247,75.02854 5.21354,66.52124 2.70313,-5.97143 13.49402,-29.6291 23.97981,-52.57259 z m 352.34359,-47.31313 c 7.7728,-1.94581 -18.3176,-3.52744 -63.1641,-3.82905 -49.94265,-0.3357 -75.46199,0.99039 -73.67875,3.82905 3.17744,5.058 116.63825,5.058 136.84285,0 z m -333.27856,-139.12577 0,-78.32572 -20.23027,-8.53142 c -11.1266,-4.69223 -22.09677,-8.53136 -24.37811,-8.53136 -2.47995,0 -3.66412,31.8228 -2.94477,79.13732 l 1.20315,79.13736 19.86429,7.48361 c 10.92536,4.11599 21.35411,7.58993 23.175,7.71982 1.82089,0.12985 3.31071,-35.01042 3.31071,-78.08961 z m 544.99866,69.32803 24.4442,-8.99775 0,-79.96746 0,-79.96746 -22.9474,8.89869 c -12.6211,4.89427 -24.5397,9.8668 -26.4857,11.05005 -3.6558,2.22285 -5.1315,157.98168 -1.4968,157.98168 1.1229,0 13.0414,-4.04898 26.4857,-8.99775 z m 24.4442,-208.65769 c 0,-2.38857 -1.9865,-3.13507 -4.4143,-1.65884 -2.4279,1.47622 -4.4143,4.6383 -4.4143,7.02687 0,2.38857 1.9864,3.13507 4.4143,1.65884 2.4278,-1.47622 4.4143,-4.6383 4.4143,-7.02687 z m -9.9322,-41.683698 c -6.6766,-0.995079 -17.6019,-0.995079 -24.2785,0 -6.6766,0.995035 -1.214,1.80919 12.1392,1.80919 13.3533,0 18.8159,-0.814155 12.1393,-1.80919 z m 3.8625,-109.803711 c 3.3383,0 6.0697,-2.931428 6.0697,-6.514285 0,-8.142944 -2.3956,-7.889582 -24.2786,2.567887 l -17.6571,8.437998 14.8982,-2.245778 c 8.194,-1.235196 17.6295,-2.245822 20.9678,-2.245822 z"
+ id="path3311"
+ style="fill:#d4d9e9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,623.70165 c -136.81257,-13.3918 -207.54249,-31.1709 -261.14569,-65.64315 -26.69146,-17.16527 -38.5688,-33.10985 -38.16105,-51.22882 0.38448,-17.08514 0.87023,-173.27213 0.93865,-301.83894 0.0309,-56.13142 0.13287,-145.993648 0.2291,-199.6938131 l 0.17472,-97.6367189 10.88832,15.76431 c 5.98855,8.67034 17.90712,20.031949 26.48571,25.248024 l 15.5974,9.483758 0,86.702708 0,86.702752 23.175,10.61494 c 12.74624,5.83819 25.7378,10.63401 28.87008,10.65733 4.5035,0.0347 5.4271,-17.99849 4.41429,-86.181046 l -1.2808,-86.22343 26.48571,6.191437 c 91.07033,21.2890763 174.91165,29.432324 264.85713,25.724827 60.94537,-2.512169 120.50117,-9.1445368 125.40107,-13.9651515 1.1136,-1.0954857 -0.022,-4.0065461 -2.5261,-6.4689461 -3.1249,-3.0743084 -28.756,-2.8620294 -81.8004,0.6773554 -118.20538,7.88732321 -248.4528,-4.0831538 -325.53947,-29.9188538 -13.21214,-4.428064 -24.67754,-9.985575 -25.47864,-12.35 -0.8011,-2.364468 77.16343,-41.790835 173.25448,-87.614232 130.62529,-62.29185 178.82493,-83.30877 191.01433,-83.28963 24.3979,0.0382 99.8648,11.86536 139.9033,21.92541 39.6231,9.95574 85.1708,29.48157 107.0243,45.88033 30.093,22.58168 28.2295,0.60605 27.9152,329.19164 -0.1567,163.77791 -0.6675,299.25638 -1.1352,301.06322 -0.4677,1.80685 0.6586,9.54504 2.5029,17.19594 12.4149,51.50085 -77.3961,100.203 -222.5184,120.66563 -45.9595,6.48045 -175.82804,11.66356 -209.54594,8.36312 z M 704.54283,506.1462 c 0,-32.91972 -0.0861,-33.16805 -8.78915,-25.41913 -8.94807,7.96689 -32.94413,45.51423 -30.22501,47.29402 6.60725,4.32479 25.00313,10.83656 31.28916,11.07572 6.71037,0.25536 7.725,-4.07243 7.725,-32.95061 z m 542.36847,23.97978 22.6601,-9.2647 0,-81.99357 0,-81.99354 -16.5536,6.99509 c -9.1045,3.84729 -22.0162,8.95041 -28.6929,11.34028 l -12.1392,4.34516 0,79.91804 c 0,64.67713 1.1505,79.91799 6.0327,79.91799 3.318,0 16.2298,-4.16914 28.6929,-9.26475 z M 683.22739,448.71826 c 11.7235,-24.78985 21.31544,-50.3556 21.31544,-56.81278 0,-10.4057 -3.26181,-13.01354 -28.69286,-22.94018 l -28.69285,-11.19988 0.28251,83.21265 c 0.27148,79.95526 0.56009,82.61765 7.37742,68.01266 3.90232,-8.36 16.68706,-35.48262 28.41056,-60.27247 z m 350.17971,-43.9561 c 7.7784,-1.94725 -18.1778,-3.52722 -62.90357,-3.82905 -48.16961,-0.32502 -76.14643,1.08181 -76.14643,3.82905 0,5.10269 118.6669,5.10269 139.05,0 z M 704.54283,266.25538 c 0,-58.10912 -1.39161,-78.25581 -5.51786,-79.88377 -3.03482,-1.19732 -15.94661,-6.26361 -28.69285,-11.25842 l -23.175,-9.08144 0,79.97602 0,79.97597 23.175,8.76293 c 12.74624,4.81961 25.65803,8.86477 28.69285,8.98919 4.21728,0.17285 5.51786,-18.08961 5.51786,-77.48048 z m 540.58437,68.70904 24.4442,-8.99775 0,-80.49546 c 0,-62.70243 -1.2197,-79.99756 -5.5179,-78.243 -3.0348,1.23884 -15.9466,6.59419 -28.6928,11.90082 l -23.175,9.64831 0,77.59243 c 0,46.81691 1.6851,77.5924 4.2487,77.5924 2.3368,0 15.2485,-4.04898 28.6928,-8.99775 z m 22.4185,-204.09118 c 4.751,-7.56282 -0.66,-9.16447 -6.7143,-1.98746 -3.4818,4.12741 -3.8167,6.61925 -0.8896,6.61925 2.5818,0 6.0036,-2.08431 7.6039,-4.63179 z m -7.9065,-46.250208 c -6.6766,-0.995079 -17.6019,-0.995079 -24.2785,0 -6.6766,0.995035 -1.214,1.80919 12.1392,1.80919 13.3533,0 18.8159,-0.814155 12.1393,-1.80919 z m -6.6214,-107.819112 c 9.7418,-1.736796 16.5536,-5.433001 16.5536,-8.982201 0,-7.6641 1.5995,-7.940871 -29.0466,5.025293 -14.3726,6.080956 -21.1659,10.122983 -15.0963,8.982245 6.0697,-1.140738 18.4848,-3.402108 27.5893,-5.025337 z"
+ id="path3309"
+ style="fill:#d3d7e8"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,623.70165 c -136.81257,-13.3918 -207.54249,-31.1709 -261.14569,-65.64315 -26.69146,-17.16527 -38.5688,-33.10985 -38.16105,-51.22882 0.38448,-17.08514 0.87023,-173.27213 0.93865,-301.83894 0.16995,-319.50281 -0.44765,-298.912592 8.53361,-284.423605 4.45322,7.184128 15.37116,18.520722 24.26206,25.192436 l 16.16529,12.130338 0,86.043071 0,86.04303 25.38214,11.14733 c 13.96018,6.13107 30.84482,12.47264 37.52143,14.09248 l 12.13928,2.94511 0,-85.724737 0,-85.72474 14.34643,2.458882 c 7.89054,1.3523661 38.18357,6.7545327 67.31786,12.0047428 43.3187,7.8064158 70.66803,9.6381462 150.0857,10.0521942 C 1066.1268,11.835141 1123.9,5.2111989 1123.9,-8.7643321 c 0,-6.9537829 -2.0332,-8.2037439 -9.9322,-6.1060569 -5.4627,1.450688 -42.8153,4.825696 -83.0058,7.5000706 -110.9266,7.38129343 -238.56178,-3.7519676 -310.91828,-27.1205776 -15.80905,-5.105767 -28.74375,-10.673961 -28.74375,-12.373755 0,-1.699794 80.82433,-41.775244 179.60964,-89.056489 l 179.60969,-85.96599 51.0367,7.06465 c 88.4692,12.24617 155.8025,33.8501 193.3754,62.04458 30.093,22.58168 28.2295,0.60605 27.9152,329.19164 -0.1567,163.77791 -0.6675,299.25638 -1.1352,301.06322 -0.4677,1.80685 0.6586,9.54504 2.5029,17.19594 12.4149,51.50085 -77.3961,100.203 -222.5184,120.66563 -45.9595,6.48045 -175.82804,11.66356 -209.54594,8.36312 z M 717.60192,499.2193 l -0.18364,-44.51428 -15.30216,17.0501 c -15.24774,16.98952 -39.425,54.27129 -36.58737,56.4184 4.93972,3.73768 38.0756,15.14928 44.53202,15.33628 6.88104,0.19934 7.70492,-4.63943 7.54124,-44.2905 z m 531.41988,29.79669 24.9639,-10.86157 0,-81.04648 c 0,-75.391 -0.5391,-80.84515 -7.725,-78.16135 -4.2488,1.58683 -21.1334,8.02342 -37.5215,14.3035 l -29.7964,11.41837 0,80.07542 0,80.07542 12.5575,-2.47087 c 6.9067,-1.35896 23.7913,-7.35858 37.5215,-13.33244 z M 691.64398,432.43963 716.26655,383.66621 682.9481,371.41419 c -18.3252,-6.73859 -34.868,-12.90345 -36.76191,-13.69963 -4.4153,-1.85622 -4.54587,157.41376 -0.13242,161.81894 1.82089,1.81757 7.28357,-6.06098 12.13928,-17.50789 4.85572,-11.4469 19.90874,-42.76059 33.45115,-69.58598 z m 341.76312,-27.42832 c 12.0406,-2.45024 -8.051,-3.75201 -63.1641,-4.09257 -52.88213,-0.32702 -79.87813,1.00185 -78.09304,3.84342 3.29527,5.24557 115.72964,5.44391 141.25714,0.24928 z m -315.62142,-134.19159 0,-77.48534 -29.79642,-11.38163 c -16.38804,-6.25992 -33.27268,-12.64062 -37.52143,-14.17934 -7.17705,-2.5992 -7.725,2.7691 -7.725,75.68562 0,75.65452 0.33902,78.66182 9.40985,83.43775 12.04804,6.34361 52.89942,20.67599 60.11515,21.09091 4.22014,0.24277 5.51785,-17.90573 5.51785,-77.16797 z m 529.71422,63.01364 24.2786,-9.45418 1.199,-80.88202 1.199,-80.88198 -37.088,15.49484 -37.0881,15.49484 -0.7664,77.87954 -0.7664,77.87959 12.3769,-3.03822 c 6.8073,-1.67101 23.3022,-7.29257 36.6554,-12.49241 z m 26.4858,-210.01135 c 0,-6.2089 -1.0363,-6.11197 -8.8286,0.82588 -4.8557,4.32327 -8.8286,9.5806 -8.8286,11.68302 0,2.10242 3.9729,1.73076 8.8286,-0.82588 4.8557,-2.55664 8.8286,-7.81402 8.8286,-11.68302 z m -12.1393,-39.23003 c -7.8906,-0.963332 -20.8023,-0.963332 -28.6929,0 -7.8905,0.963376 -1.4346,1.751605 14.3464,1.751605 15.7811,0 22.237,-0.788229 14.3465,-1.751605 z m -6.6215,-107.948616 c 14.0945,-2.204347 18.7608,-4.897005 18.7608,-10.82583 0,-7.361143 -1.5581,-7.220391 -23.175,2.093648 -12.7463,5.491933 -26.1547,11.258943 -29.7965,12.815597 -3.6418,1.55661 -1.6553,1.93483 4.4143,0.840473 6.0697,-1.094356 19.4781,-3.310125 29.7964,-4.923888 z"
+ id="path3307"
+ style="fill:#d1d5e5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,623.70165 c -136.81257,-13.3918 -207.54249,-31.1709 -261.14569,-65.64315 -26.69146,-17.16527 -38.5688,-33.10985 -38.16105,-51.22882 0.38448,-17.08514 0.87023,-173.27213 0.93865,-301.83894 0.17039,-320.17122 -0.47626,-298.932048 8.66162,-284.457131 4.52368,7.165714 13.45514,17.766367 19.84778,23.557002 l 11.62299,10.528475 0,86.339081 0,86.339123 23.48462,10.21792 c 12.91651,5.61987 33.774,13.22417 46.34999,16.8984 l 22.86539,6.6804 0,-86.915723 c 0,-80.73749 0.54913,-86.755995 7.725,-84.669122 54.42769,15.8288453 173.32802,26.208056 259.33927,22.638662 93.07877,-3.8627105 136.17827,-10.613508 138.33637,-21.6680334 1.426,-7.3048156 -0.3527,-7.9171156 -15.8961,-5.4718256 -79.8022,12.554374 -211.12146,12.6998162 -298.74273,0.330925 -46.04267,-6.499563 -111.51717,-22.282461 -114.92195,-27.70239 -1.09814,-1.748087 76.88675,-40.7958 173.29977,-86.772763 96.41299,-45.97691 176.35641,-85.28185 177.65211,-87.34432 3.4226,-5.44826 90.2828,6.69261 138.8304,19.40497 61.356,16.06627 113.0515,44.06181 126.2854,68.38941 3.0812,5.66417 4.5143,106.966735 4.3243,305.67494 -0.1563,163.4617 -0.667,298.68147 -1.1347,300.48831 -0.4677,1.80685 0.6586,9.54504 2.5029,17.19593 12.4149,51.50086 -77.3961,100.203 -222.5184,120.66563 -45.9595,6.48046 -175.82804,11.66357 -209.54594,8.36313 z m -165.5357,-126.38057 0,-50.75536 -13.84232,12.75537 c -13.76494,12.68405 -47.95768,60.85059 -47.95768,67.55687 0,2.94867 48.12789,20.19116 58.48928,20.95455 1.82089,0.13419 3.31072,-22.59598 3.31072,-50.51143 z m 495.02484,42.60521 c 13.2431,-4.48256 32.4252,-12.3394 42.6267,-17.45967 l 18.5484,-9.30957 0,-80.7192 c 0,-62.85734 -1.221,-80.23081 -5.5178,-78.51204 -19.1058,7.6426 -73.0109,28.38187 -79.4572,30.57006 -6.9833,2.37051 -7.725,10.34882 -7.725,83.10144 0,44.26353 1.6755,80.47914 3.7233,80.47914 2.0477,0 14.5585,-3.66755 27.8016,-8.15016 z m -527.8662,-111.9146 22.42386,-44.0648 -37.83056,-14.53741 c -20.80678,-7.99559 -39.32033,-15.20069 -41.14123,-16.01141 -1.82089,-0.81069 -3.31071,34.74602 -3.31071,79.01498 0,72.07175 0.80786,80.95263 7.725,84.92296 6.63255,3.80699 9.27949,0.92029 18.71741,-20.41277 6.04585,-13.66576 21.08316,-44.67593 33.41623,-68.91155 z m 340.6264,-21.70781 c 44.5417,-5.26385 18.9286,-8.04362 -62.50708,-6.7838 -50.91057,0.78757 -81.24157,2.85542 -79.6251,5.42857 2.91126,4.63435 106.06243,5.61792 142.13218,1.35523 z m -307.78504,-133.456 0,-79.79999 -13.33185,-2.62322 c -7.33248,-1.44278 -28.18998,-9.37705 -46.35,-17.6317 l -33.01815,-15.00848 0,79.94874 0,79.94874 14.22561,7.23729 c 13.37992,6.80708 67.93947,26.69997 75.16367,27.40538 1.82089,0.17762 3.31072,-35.58676 3.31072,-79.47676 z m 497.19584,69.48441 c 16.9553,-5.67355 37.1675,-13.5409 44.916,-17.48295 l 14.0881,-7.16737 0,-80.31957 0,-80.31962 -18.7091,9.39025 c -10.29,5.16462 -28.6645,12.75819 -40.8321,16.87457 -14.7572,4.99246 -21.7362,9.65521 -20.9612,14.00441 0.639,3.58602 -1.844,8.97434 -5.5179,11.97399 -5.4502,4.45008 -6.6797,18.14619 -6.6797,74.40791 0,37.92469 0.6453,68.95397 1.434,68.95397 0.7887,0 15.3065,-4.64199 32.2619,-10.31559 z m 45.9805,-209.11021 c 10.3016,-5.24096 13.0236,-9.52758 13.0236,-20.50919 l 0,-13.883331 -7.3106,10.737711 c -4.0208,5.90577 -11.3196,15.1349 -16.2195,20.50919 -10.4318,11.44186 -7.5163,12.31473 10.5065,3.14562 z m -1.3228,-48.652118 c -9.1045,-0.936928 -24.0027,-0.936928 -33.1071,0 -9.1045,0.936971 -1.6554,1.703529 16.5535,1.703529 18.209,0 25.6581,-0.766558 16.5536,-1.703529 z m -8.8286,-107.895894 c 20.4593,-2.625257 23.175,-4.159154 23.175,-13.089761 l 0,-10.115991 -27.5893,12.149577 c -15.1741,6.682267 -30.5689,13.452216 -34.2107,15.044351 -3.6418,1.592092 -1.6553,2.015173 4.4143,0.940142 6.0697,-1.075031 21.4645,-3.292754 34.2107,-4.928318 z"
+ id="path3305"
+ style="fill:#cfd4e4"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,623.70165 c -136.81257,-13.3918 -207.54249,-31.1709 -261.14569,-65.64315 -26.69146,-17.16527 -38.5688,-33.10985 -38.16105,-51.22882 0.38448,-17.08514 0.87023,-173.27213 0.93865,-301.83894 0.17039,-319.93489 -0.4661,-298.92384 8.61686,-284.457131 4.49904,7.165714 12.4373,16.751441 17.64064,21.30167 9.07029,7.93175 9.4606,11.824774 9.4606,94.356993 0,85.507378 0.0693,86.128628 10.34899,92.755178 12.26129,7.90387 79.7683,32.61472 89.09953,32.61472 5.49547,0 6.49434,-13.09927 6.49434,-85.169026 l 0,-85.1690281 14.34642,2.6227382 C 848.07169,11.814383 964.3991,18.076652 1046.65,9.8276993 c 71.2844,-7.1490809 94.9071,-12.3195996 94.9071,-20.7731883 0,-5.614402 -3.9255,-6.285069 -23.175,-3.959643 -141.42237,17.0847561 -298.72927,9.9068386 -388.22035,-17.71447 l -25.1454,-7.761076 185.47371,-88.416052 185.47374,-88.4161 29.4859,3.07212 c 44.248,4.61014 123.4307,25.47909 153.6851,40.50429 29.4892,14.64524 52.2894,31.90275 59.387,44.95013 3.0812,5.66417 4.5143,106.966738 4.3243,305.67494 -0.1563,163.46171 -0.667,298.68147 -1.1347,300.48831 -0.4677,1.80685 0.6586,9.54504 2.5029,17.19594 12.4149,51.50085 -77.3961,100.203 -222.5184,120.66563 -45.9595,6.48045 -175.82804,11.66356 -209.54594,8.36312 z m -157.60902,-157.05377 -1.30526,-29.31429 -16.98467,17.37143 c -17.08951,17.47865 -51.43675,65.65622 -51.43675,72.14836 0,1.91815 15.39482,8.27006 34.21071,14.11537 l 34.21071,10.62784 1.30526,-27.81721 c 0.7179,-15.29945 0.7179,-41.00865 0,-57.1315 z m 521.78756,58.40239 28.6929,-13.88811 1.201,-81.42857 c 0.6605,-44.78571 0.4436,-81.42857 -0.4819,-81.42857 -0.9256,0 -16.3643,6.64275 -34.3082,14.76163 -17.9439,8.11893 -41.0675,17.22256 -51.3859,20.23029 l -18.7607,5.46866 0,81.59277 0,81.59277 23.175,-6.50638 c 12.7462,-3.57851 36.0867,-12.75601 51.8678,-20.39449 z M 666.69324,485.10502 c 6.61265,-16.72 20.64239,-46.42688 31.17731,-66.01529 l 19.15433,-35.6153 -16.17319,-5.23861 c -8.89523,-2.88123 -28.58832,-10.71261 -43.76243,-17.40304 l -27.58929,-12.16448 0,81.66899 c 0,80.45898 0.147,81.736 9.93215,86.19004 12.4455,5.66508 12.71724,5.35187 27.26112,-31.42231 z m 375.54246,-79.65729 c 39.3307,-5.16123 15.0454,-7.22752 -69.6684,-5.92765 -51.34031,0.78775 -81.917,2.85482 -80.30018,5.42857 2.92927,4.66297 115.38588,5.03728 149.96858,0.49908 z m -306.79287,-130.6172 0,-77.81736 -29.79643,-9.64331 c -16.38804,-5.30384 -39.16593,-14.25747 -50.61755,-19.89693 -11.45159,-5.6395 -21.88034,-10.25361 -23.175,-10.25361 -1.29462,0 -2.35388,35.88741 -2.35388,79.74979 l 0,79.74975 26.69111,11.78443 c 29.61748,13.07647 59.72648,23.05935 71.52675,23.71521 7.08598,0.3939 7.725,-6.00786 7.725,-77.38797 z m 477.14487,72.04487 c 10.7042,-3.17489 31.8774,-11.25895 47.0515,-17.96453 l 27.5893,-12.19201 0,-79.74975 c 0,-43.86238 -1.0723,-79.74979 -2.383,-79.74979 -1.3106,0 -10.723,4.1859 -20.9162,9.30196 -10.1933,5.1161 -28.4887,12.66994 -40.6563,16.78632 -14.8293,5.01686 -21.7372,9.64609 -20.9526,14.04119 0.6437,3.60622 -3.8257,9.77747 -9.9321,13.71379 -11.069,7.13531 -11.1026,7.36036 -11.1026,74.37112 0,74.60212 -1.7156,71.23467 31.302,61.4417 z m 60.2944,-215.66563 c 12.7755,-7.66336 14.3464,-10.75248 14.3464,-28.21233 l 0,-19.606694 -34.2107,0.862926 -34.2107,0.862882 32.0036,1.498807 c 37.5789,1.759856 38.7733,4.175179 16.3513,33.065169 -17.1714,22.12486 -15.9725,24.54144 5.7201,11.52924 z m -17.6572,-153.196017 c 29.0055,-4.462807 32.0036,-5.860208 32.0036,-14.916411 0,-5.495755 -0.4966,-9.979321 -1.1036,-9.963469 -0.6069,0.01737 -19.9746,7.956678 -43.0392,17.64633 -23.0647,9.689609 -36.9697,16.389031 -30.9,14.887575 6.0696,-1.501456 25.4373,-4.945776 43.0392,-7.654025 z"
+ id="path3303"
+ style="fill:#d6d7d9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 894.3571,623.68037 C 765.3079,612.74515 677.22533,590.00304 624.13737,553.91212 602.10276,538.9323 587.32179,509.12375 593.40935,491.94354 c 2.45836,-6.93793 3.64505,-13.42577 2.63709,-14.41741 -1.64683,-1.62019 -2.52519,-207.42923 -2.01569,-472.30681 0.18628,-96.959451 0.25117,-97.613659 8.36798,-84.685711 4.49904,7.165714 12.4373,16.751441 17.64064,21.30167 9.07029,7.93175 9.4606,11.824774 9.4606,94.356993 0,85.507378 0.0693,86.128628 10.34899,92.755178 12.26129,7.90387 79.7683,32.61472 89.09953,32.61472 5.49547,0 6.49434,-13.09927 6.49434,-85.169026 l 0,-85.1690281 14.34642,2.6227382 C 848.07169,11.814383 964.3991,18.076652 1046.65,9.8276993 c 71.2844,-7.1490809 94.9071,-12.3195996 94.9071,-20.7731883 0,-5.614402 -3.9255,-6.285069 -23.175,-3.959643 -141.42237,17.0847561 -298.72927,9.9068386 -388.22035,-17.71447 l -25.1454,-7.761076 185.56402,-88.459182 185.56403,-88.45921 27.1885,2.89634 c 84.704,9.02336 181.0255,44.72608 206.98,76.7196 l 12.665,15.61183 -0.2211,296.40872 c -0.1219,163.02482 -1.227,298.00976 -2.4563,299.96661 -1.2293,1.9568 -0.1523,8.94707 2.3931,15.53388 20.0001,51.75296 -72.0653,103.92548 -221.0919,125.29047 -46.4677,6.66177 -169.59229,11.74252 -207.24451,8.55199 z m -159.81616,-157.03249 -1.30526,-29.31429 -16.98467,17.37143 c -17.08951,17.47865 -51.43675,65.65622 -51.43675,72.14836 0,1.91815 15.39482,8.27006 34.21071,14.11537 l 34.21071,10.62784 1.30526,-27.81721 c 0.7179,-15.29945 0.7179,-41.00865 0,-57.1315 z m 521.78756,58.40239 28.6929,-13.88811 1.201,-81.42857 c 0.6605,-44.78571 0.4436,-81.42857 -0.4819,-81.42857 -0.9256,0 -16.3643,6.64275 -34.3082,14.76163 -17.9439,8.11893 -41.0675,17.22256 -51.3859,20.23029 l -18.7607,5.46866 0,81.59277 0,81.59277 23.175,-6.50638 c 12.7462,-3.57851 36.0867,-12.75601 51.8678,-20.39449 z M 666.69324,485.10502 c 6.61265,-16.72 20.64239,-46.42688 31.17731,-66.01529 l 19.15433,-35.6153 -16.17319,-5.23861 c -8.89523,-2.88123 -28.58832,-10.71261 -43.76243,-17.40304 l -27.58929,-12.16448 0,81.66899 c 0,80.45898 0.147,81.736 9.93215,86.19004 12.4455,5.66508 12.71724,5.35187 27.26112,-31.42231 z m 375.54246,-79.65729 c 39.3307,-5.16123 15.0454,-7.22752 -69.6684,-5.92765 -51.34031,0.78775 -81.917,2.85482 -80.30018,5.42857 2.92927,4.66297 115.38588,5.03728 149.96858,0.49908 z m -306.79287,-130.6172 0,-77.81736 -29.79643,-9.64331 c -16.38804,-5.30384 -39.16593,-14.25747 -50.61755,-19.89693 -11.45159,-5.6395 -21.88034,-10.25361 -23.175,-10.25361 -1.29462,0 -2.35388,35.88741 -2.35388,79.74979 l 0,79.74975 26.69111,11.78443 c 29.61748,13.07647 59.72648,23.05935 71.52675,23.71521 7.08598,0.3939 7.725,-6.00786 7.725,-77.38797 z m 477.14487,72.04487 c 10.7042,-3.17489 31.8774,-11.25895 47.0515,-17.96453 l 27.5893,-12.19201 0,-79.74975 c 0,-43.86238 -1.0723,-79.74979 -2.383,-79.74979 -1.3106,0 -10.723,4.1859 -20.9162,9.30196 -10.1933,5.1161 -28.4887,12.66994 -40.6563,16.78632 -14.8293,5.01686 -21.7372,9.64609 -20.9526,14.04119 0.6437,3.60622 -3.8257,9.77747 -9.9321,13.71379 -11.069,7.13531 -11.1026,7.36036 -11.1026,74.37112 0,74.60212 -1.7156,71.23467 31.302,61.4417 z m 60.2944,-215.66563 c 12.7755,-7.66336 14.3464,-10.75248 14.3464,-28.21233 l 0,-19.606694 -34.2107,0.862926 -34.2107,0.862882 32.0036,1.498807 c 37.5789,1.759856 38.7733,4.175179 16.3513,33.065169 -17.1714,22.12486 -15.9725,24.54144 5.7201,11.52924 z m -17.6572,-153.196017 c 29.0055,-4.462807 32.0036,-5.860208 32.0036,-14.916411 0,-5.495755 -0.4966,-9.979321 -1.1036,-9.963469 -0.6069,0.01737 -19.9746,7.956678 -43.0392,17.64633 -23.0647,9.689609 -36.9697,16.389031 -30.9,14.887575 6.0696,-1.501456 25.4373,-4.945776 43.0392,-7.654025 z"
+ id="path3301"
+ style="fill:#ccd0e1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 894.3571,623.68037 C 765.3079,612.74515 677.22533,590.00304 624.13737,553.91212 602.10276,538.9323 587.32179,509.12375 593.40935,491.94354 c 2.45836,-6.93793 3.64505,-13.42577 2.63709,-14.41741 -1.63589,-1.60942 -2.51314,-204.08423 -2.0322,-469.0621747 l 0.17145,-94.4696473 15.45,16.824576 15.45,16.824575 0,87.194667 0,87.194664 27.58928,12.84122 c 25.15715,11.70921 82.48013,31.03102 92.06143,31.03102 2.18785,0 3.94928,-37.77091 3.94928,-84.685712 0,-77.8164848 0.62661,-84.6732046 7.725,-84.5316709 4.24875,0.084686 25.60286,2.99817822 47.45357,6.4743313 57.86833,9.2061616 189.37007,12.8994146 244.99285,6.8807356 78.6157,-8.5066579 101.5286,-13.7628177 101.5286,-23.290568 0,-6.722699 -2.2833,-7.826133 -12.1393,-5.866462 C 1079.7087,-7.4753722 971.32618,-2.3450249 894.3571,-7.5696556 814.36425,-12.999573 722.19997,-29.298185 722.19997,-38.014473 c 0,-1.525298 82.71868,-42.249137 183.81928,-90.497457 l 183.81935,-87.72422 29.1699,4.3131 c 86.0569,12.72453 166.8294,44.07005 191.3044,74.23992 l 12.665,15.61183 -0.2211,296.40872 c -0.1219,163.02482 -1.227,298.00976 -2.4563,299.96661 -1.2293,1.9568 -0.1523,8.94707 2.3931,15.53388 20.0001,51.75296 -72.0653,103.92548 -221.0919,125.29047 -46.4677,6.66177 -169.59229,11.74252 -207.24451,8.55199 z m -145.67142,-134.4535 0,-63.19244 -15.12775,11.3518 c -20.5312,15.4065 -68.66532,78.35508 -68.72716,89.87959 -0.0177,3.51719 64.43824,24.6415 76.12991,24.9498 7.01271,0.18501 7.725,-5.62313 7.725,-62.98875 z m 461.93142,54.0082 c 16.6438,-5.05135 41.6834,-14.85088 55.6436,-21.77682 l 25.3821,-12.59255 0,-82.28216 0,-82.2822 -32.0036,15.13195 c -17.6019,8.32256 -45.4119,19.07756 -61.8,23.9 l -29.7964,8.7681 0,80.15898 c 0,65.66638 1.1131,80.15893 6.1564,80.15893 3.3861,0 19.7741,-4.13292 36.4179,-9.18423 z M 666.69324,485.10502 c 6.61265,-16.72 20.59512,-46.33937 31.07216,-65.82086 l 19.04919,-35.42082 -25.00035,-8.96809 c -13.75019,-4.93244 -34.38923,-13.64443 -45.86447,-19.35993 l -20.86408,-10.39181 0,81.7849 0,81.78494 12.13928,6.32689 c 6.67661,3.47981 13.33313,5.00793 14.79232,3.39586 1.45914,-1.61211 8.06335,-16.61108 14.67595,-33.33108 z M 1046.65,405.44773 c 12.1392,-1.59296 20.085,-3.90744 17.6571,-5.1432 -7.7972,-3.96881 -174.36428,0.61277 -174.36428,4.79599 0,4.54979 122.61168,4.82144 156.70718,0.34699 z M 747.5475,268.69411 746.47854,199.94035 708.95711,187.98741 C 688.32033,181.41328 661.00694,170.791 648.26069,164.38229 l -23.175,-11.65214 0,80.32774 0,80.32774 18.76071,9.56579 c 10.31839,5.2612 34.65214,14.92567 54.075,21.47656 l 35.31428,11.91072 7.6904,-9.44541 c 6.6958,-8.22385 7.55213,-18.33706 6.62142,-78.19918 z m 466.4474,77.43015 c 18.6098,-5.9766 43.6937,-15.78095 55.742,-21.78746 l 21.9059,-10.92094 0,-80.34286 0,-80.34285 -23.175,11.55903 c -12.7462,6.35747 -33.1071,14.87233 -45.2464,18.92192 -14.914,4.97522 -22.5076,10.05254 -23.4164,15.657 -0.7398,4.56173 -8.1342,13.17978 -16.4321,19.15121 l -15.087,10.85715 -0.1214,64.05714 c -0.098,51.45451 1.0703,64.05714 5.9364,64.05714 3.3319,0 21.2842,-4.88993 39.8941,-10.86648 z m 60.4933,-215.76178 c 16.0576,-8.87884 17.1546,-10.76659 17.1546,-29.52009 l 0,-20.034732 -29.7964,1.529945 c -43.1335,2.214727 -47.8655,4.312457 -11.0357,4.892229 17.6019,0.277074 32.0035,2.261673 32.0035,4.410214 0,2.148542 -6.8181,13.874254 -15.1515,26.057144 -8.3333,12.18288 -14.0664,22.1507 -12.7403,22.1507 1.3262,0 10.1308,-4.26842 19.5658,-9.48541 z m -79.132,-141.13838 c -3.1866,-1.254521 -6.9939,-1.100263 -8.4607,0.342651 -1.4669,1.4430883 1.1404,2.4695226 5.7937,2.2809557 5.1424,-0.2084571 6.1884,-1.2374536 2.667,-2.6237807 z m 41.108,-7.977351 c 14.5672,-3.460475 32.9416,-6.322287 40.8322,-6.359593 12.9823,-0.06123 14.3464,-1.306678 14.3464,-13.096406 0,-13.407529 -3.7675,-16.270601 -11.4564,-8.706125 -2.4164,2.377323 -21.1882,11.086705 -41.715,19.354159 -41.1485,16.573081 -41.5178,18.1937913 -2.0072,8.807965 z"
+ id="path3299"
+ style="fill:#cacee0"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 894.3571,623.68037 C 765.3079,612.74515 677.22533,590.00304 624.13737,553.91212 602.10276,538.9323 587.32179,509.12375 593.40935,491.94354 c 2.45836,-6.93793 3.64505,-13.42577 2.63709,-14.41741 -1.63615,-1.60968 -2.51278,-204.00997 -2.0322,-469.2007987 l 0.17145,-94.6082273 13.14945,14.734054 13.14945,14.734054 1.19698,88.559931 1.19698,88.559887 28.69285,13.89128 c 15.78107,7.64021 45.5775,18.66204 66.21428,24.49298 l 37.52143,10.60169 1.19601,-86.703793 1.19605,-86.7037501 12.0468,2.3936525 c 118.639,23.5730276 389.46823,14.6944036 389.46823,-12.7679994 0,-7.490603 -2.7475,-7.637826 -37.5214,-2.010569 -56.7467,9.1830143 -155.46848,12.9128336 -225.635,8.524724 -62.63306,-3.916996 -149.30986,-17.221078 -163.24668,-25.056852 -3.02246,-1.699316 75.22128,-41.449139 180.13595,-91.513683 l 185.47933,-88.5095 26.3511,2.70967 c 70.7963,7.27993 161.2236,42.69533 185.5354,72.66399 l 12.665,15.61183 -0.2211,296.40872 c -0.1219,163.02482 -1.227,298.00976 -2.4563,299.96661 -1.2293,1.9568 -0.1523,8.94707 2.3931,15.53388 20.0001,51.75296 -72.0653,103.92548 -221.0919,125.29047 -46.4677,6.66177 -169.59229,11.74252 -207.24451,8.55199 z m -136.84285,-134.38528 0,-67.4671 -9.41262,4.95598 c -15.87841,8.36034 -47.17498,41.61317 -65.69013,69.79614 -9.6785,14.73218 -17.59724,28.37397 -17.59724,30.31505 0,1.94108 17.38125,8.67064 38.625,14.95454 21.24375,6.28394 40.61142,12.05564 43.03928,12.82602 2.42786,0.77038 5.90411,1.555 7.725,1.74357 1.82089,0.18848 3.31071,-30.01731 3.31071,-67.1242 z m 459.08565,51.66202 c 18.209,-5.66473 43.0393,-15.66898 55.1786,-22.23165 l 22.0714,-11.93213 1.201,-81.41558 c 0.6605,-44.77859 0.4389,-81.41558 -0.4925,-81.41558 -0.9314,0 -18.3565,7.66093 -38.7224,17.02426 -20.3659,9.36337 -50.4373,20.5206 -66.8253,24.79393 l -29.7965,7.76963 0,81.68884 0,81.68879 12.1393,-2.83549 c 6.6766,-1.55957 27.0375,-7.47032 45.2464,-13.13502 z M 666.69324,485.10502 c 6.61265,-16.72 20.64239,-46.42688 31.17731,-66.01529 l 19.15433,-35.6153 -16.17319,-5.08948 c -8.89523,-2.79919 -29.95892,-11.68945 -46.8082,-19.75613 -16.84928,-8.06664 -31.25089,-14.66665 -32.00357,-14.66665 -0.75268,0 -1.36852,36.18299 -1.36852,80.40669 l 0,80.40669 14.23351,8.51583 c 7.82843,4.68372 15.47817,7.09783 16.99942,5.36473 1.52129,-1.73311 8.17631,-16.83109 14.78891,-33.55109 z m 398.27806,-81.72015 c 1.6579,-1.63105 2.1867,-3.51324 1.1751,-4.18261 -5.644,-3.73446 -176.20358,2.57801 -176.20358,6.52141 0,5.0454 169.82948,2.77609 175.02848,-2.3388 z m 149.4215,-57.82861 c 19.4229,-5.86959 45.2464,-15.96391 57.3857,-22.43177 l 22.0714,-11.75972 1.2007,-81.32547 1.2006,-81.32547 -15.6876,9.10615 c -8.6282,5.0084 -30.026,14.58096 -47.5506,21.27239 -21.5772,8.23884 -32.302,14.73319 -33.2227,20.11803 -0.7479,4.37347 -10.1834,14.53146 -20.9679,22.57326 l -19.6082,14.62153 0,62.0576 0,62.05765 9.9322,-2.14607 c 5.4627,-1.18034 25.8235,-6.94853 45.2464,-12.81811 z m -468.07339,-4.77363 c 10.67361,-15.45349 11.19484,-19.05541 11.19484,-77.36148 l 0,-61.15329 -35.75134,-10.16337 c -19.66326,-5.58987 -49.62151,-16.70289 -66.574,-24.6957 -16.95245,-7.99277 -31.69876,-14.53233 -32.76963,-14.53233 -1.07086,0 -1.40564,35.62958 -0.74389,79.17684 l 1.20316,79.17684 19.86269,10.77515 c 14.575,7.90669 76.28261,31.37367 91.43888,34.7736 0.51943,0.11639 5.98211,-7.08181 12.13929,-15.99626 z M 1276.9085,128.80769 c 18.9813,-10.94378 19.1486,-11.25312 19.1486,-35.415952 0,-23.07234 -0.4189,-24.002667 -7.8368,-17.398311 -5.308,4.725984 -18.4834,7.467369 -40.8321,8.495888 l -32.9954,1.51848 34.2107,0.862882 c 18.8159,0.474631 34.2107,2.620828 34.2107,4.769369 0,2.148542 -6.8181,13.874254 -15.1515,26.057144 -8.3333,12.18288 -13.9705,22.1507 -12.5271,22.1507 1.4434,0 11.2412,-4.9681 21.7729,-11.0402 z m -50.6561,-145.272212 c 20.1837,-4.760422 44.1467,-8.668994 53.2511,-8.685714 15.8959,-0.0304 16.5536,-0.62055 16.5536,-14.883839 l 0,-14.853353 -26.1502,12.855943 c -14.3826,7.070779 -39.7095,17.517912 -56.2821,23.215828 -37.4504,12.8760928 -34.4029,13.4434871 12.6276,2.351135 z"
+ id="path3297"
+ style="fill:#c9cddd"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 894.3571,623.68037 C 765.3079,612.74515 677.22533,590.00304 624.13737,553.91212 c -22.61973,-15.37763 -36.83355,-44.73773 -30.42043,-62.83663 2.62751,-7.41534 3.81235,-15.01855 2.6329,-16.89601 -1.9147,-3.04795 -2.87326,-197.02609 -2.30664,-466.7887315 l 0.20085,-95.5428545 8.77039,13.028571 c 8.02279,11.918016 8.77286,20.517394 8.79948,100.88357 l 0.0309,87.854995 23.175,13.15195 c 23.19473,13.16316 88.04769,35.69564 119.18571,41.4098 l 16.55357,3.03774 0,-85.75931 0,-85.75935268 19.85192,2.67698048 C 896.69164,16.677557 1035.022,16.759811 1108.4519,2.5617521 c 21.8508,-4.224905 47.6743,-7.6208455 57.3857,-7.5465392 9.7115,0.074263 14.1809,-0.6741417 9.9322,-1.6631839 -4.2488,-0.9890423 -7.725,-5.772612 -7.725,-10.630141 0,-6.236343 -2.034,-8.064034 -6.9211,-6.219015 -3.8066,1.437095 -30.1268,5.653053 -58.4893,9.368802 -68.7672,9.0091265 -212.28507,9.0622831 -276.07635,0.102491 -48.49053,-6.810902 -84.59307,-14.674209 -81.47182,-17.744913 0.9372,-0.922076 85.91224,-41.891807 188.83342,-91.043873 l 187.12935,-89.36744 35.6372,7.1453 c 73.0343,14.64355 133.8122,41.62668 154.8327,68.73996 l 11.3447,14.633 -0.1351,296.1603 c -0.074,162.88819 -1.1534,297.7815 -2.3981,299.76288 -1.2447,1.98143 -0.1806,8.9918 2.365,15.57861 20.0001,51.75296 -72.0653,103.92548 -221.0919,125.29047 -46.4677,6.66177 -169.59228,11.74252 -207.2445,8.552 z M 768.8617,418.82125 c -5.25026,-5.16531 -44.27753,23.46415 -64.37732,47.22571 -18.98218,22.44032 -39.67012,54.86366 -39.67012,62.17333 0,2.20809 59.25534,20.7437 88.28571,27.61654 l 15.45,3.65772 1.21591,-69.29375 c 0.66872,-38.11161 0.26177,-70.23238 -0.90418,-71.37955 z m 487.4668,107.11149 c 50.3731,-23.07842 48.5572,-18.9313 48.5572,-110.8906 l 0,-79.40597 -32.0036,16.87552 c -29.867,15.74889 -63.3176,27.86373 -108.15,39.16879 l -18.7607,4.73076 0,82.03362 0,82.03366 40.8321,-10.70007 c 22.4577,-5.88504 53.7439,-16.61559 69.525,-23.84571 z M 666.67951,485.10502 c 6.60506,-16.72 20.67444,-46.26489 31.26533,-65.65531 l 19.25608,-35.25532 -31.71116,-12.00582 c -17.4412,-6.60323 -41.14675,-17.5917 -52.67907,-24.41885 l -20.96786,-12.41297 0,79.54568 c 0,76.97449 0.32092,79.83174 9.93214,88.39534 13.47691,12.00804 27.02572,18.81503 30.24232,15.19392 1.45914,-1.64269 8.05713,-16.66667 14.66222,-33.38667 z M 1072.2327,400.5307 c 4.3048,-4.51253 0.6944,-5.06095 -18.9613,-2.88036 -13.3532,1.4814 -55.56484,2.71047 -93.80358,2.73123 -48.96754,0.026 -69.525,1.49772 -69.525,4.97535 0,7.38802 175.05938,2.75324 182.28988,-4.82622 z m 143.868,-54.78874 c 18.4835,-6.02581 45.525,-17.19707 60.0921,-24.82503 l 26.4857,-13.86905 1.201,-81.42857 c 0.6605,-44.78571 -0.026,-81.42857 -1.5257,-81.42857 -1.4997,0 -8.9403,4.35294 -16.5348,9.67315 -7.5945,5.32022 -29.7519,15.98259 -49.2386,23.69411 -23.4857,9.29402 -35.9327,16.64778 -36.9202,21.81256 -0.8194,4.2854 -13.1743,16.5859 -27.4554,27.33447 l -25.9655,19.54285 -0.1338,58.09792 -0.1337,58.09792 18.2614,-2.87289 c 10.0438,-1.58006 33.3843,-7.80307 51.8679,-13.82887 z m -461.89716,-12.54148 16.55357,-23.46068 0,-52.20713 0,-52.20714 -18.76071,-4.7477 c -43.25426,-10.94617 -78.056,-23.60316 -108.24694,-39.36812 l -32.10046,-16.76209 1.20047,81.30011 1.20051,81.30015 24.27857,12.8093 c 20.49773,10.81458 90.05583,37.25186 97.11428,36.91068 1.21393,-0.0586 9.65625,-10.66397 18.76071,-23.56738 z M 867.87139,206.90586 c 0,-1.05332 -1.98643,-1.91512 -4.41428,-1.91512 -2.42786,0 -4.41429,2.06963 -4.41429,4.59917 0,2.52954 1.98643,3.39134 4.41429,1.91512 2.42785,-1.47623 4.41428,-3.54586 4.41428,-4.59917 z m 413.83931,-80.21514 23.175,-13.07569 0,-87.131171 c 0,-47.922168 -1.4899,-86.465719 -3.3108,-85.652388 -1.8209,0.81333 -16.2225,8.100731 -32.0035,16.194209 -15.7811,8.093479 -41.6047,19.33757 -57.3857,24.986889 -28.7386,10.2878371 -26.8998,10.2177869 26.4857,-1.008629 15.781,-3.318594 36.373,-6.067753 45.7598,-6.109227 l 17.067,-0.07557 -1.7443,42.724984 c -2.4632,60.335573 -6.4939,65.580876 -51.2521,66.697033 l -34.109,0.850592 30.9,1.542018 c 16.995,0.848074 32.4609,3.075786 34.3687,4.950467 1.9077,1.874681 -3.5879,13.500863 -12.2126,25.836003 -8.6245,12.33511 -14.1583,22.40915 -12.2971,22.38674 1.8611,-0.0217 13.8126,-5.92483 26.5589,-13.11643 z M 1159.2142,0.876463 c 5.7063,-3.6280227 5.5898,-4.2190421 -0.843,-4.2763679 -4.1055,-0.034743 -8.6922,1.8877965 -10.1927,4.2763679 -3.4527,5.4963198 2.3909,5.4963198 11.0357,0 z"
+ id="path3295"
+ style="fill:#c5cad9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 889.94282,623.54605 c -112.81245,-8.02868 -214.29255,-34.61379 -265.80545,-69.63393 -22.61973,-15.37763 -36.83355,-44.73773 -30.42043,-62.83663 2.62751,-7.41534 3.81235,-15.01855 2.6329,-16.89601 -1.17941,-1.87746 -2.22604,-86.17584 -2.32584,-187.32971 -0.33704,-341.566914 0.0822,-375.328675 4.57598,-368.48759 2.41179,3.671582 4.3086,46.437128 4.3467,98.001432 l 0.0675,91.487148 27.37885,15.93455 c 28.52128,16.59944 74.44936,33.14199 121.60328,43.79949 l 27.58929,6.23561 0,-87.014832 0,-87.01482925 13.49125,2.65457135 c 68.2324,13.4256819 240.80725,13.8874579 317.41725,0.8493325 20.5471,-3.49686842 38.2594,-5.4714786 39.3607,-4.3880661 1.1013,1.08341253 -1.3084,1.9698765 -5.3548,1.9698765 -4.0465,0 -7.3572,2.1633508 -7.3572,4.8074993 0,2.9217439 3.0301,3.5323496 7.725,1.5566536 23.1769,-9.7532316 99.8888,-28.5742189 126.3128,-30.9903239 l 30.5378,-2.79224 -2.146,43.8434 c -2.9956,61.203319 -6.5211,65.825161 -51.0706,66.95222 l -34.109,0.862882 32.0036,1.498807 c 37.9242,1.776011 40.033,6.036875 17.7448,35.853719 -16.4145,21.95913 -12.2452,21.78207 26.398,-1.12098 l 23.175,-13.73537 0,-90.451596 0,-90.451592 -9.9321,9.431339 c -19.665,18.673373 -126.8504,61.899653 -134.5952,54.2801972 -1.4385,-1.4152502 -0.298,-2.5731862 2.5345,-2.5731862 2.8325,0 5.15,-2.931428 5.15,-6.514285 0,-7.966668 2.0391,-7.983388 -46.35,0.379999 -51.5929,8.9171019 -156.54029,13.3803863 -231.75005,9.8560276 C 840.79284,-11.147258 757.3816,-23.239161 757.64514,-28.889001 c 0.24367,-5.22424 369.16656,-178.692189 379.89066,-178.625009 14.3019,0.0895 71.3729,14.44886 100.0095,25.16277 34.2251,12.80474 61.1867,29.5637 73.8599,45.91026 l 11.233,14.4889 0.031,296.21998 c 0.018,162.92097 -1.0126,297.87912 -2.2864,299.90697 -1.2739,2.02786 -0.2335,9.07623 2.312,15.66304 20.0001,51.75296 -72.0654,103.92548 -221.092,125.29047 -47.322,6.78406 -167.47213,11.56246 -211.65988,8.41767 z M 779.58568,487.27645 c 0,-40.60572 -1.48982,-73.81025 -3.31071,-73.78784 -11.80142,0.14549 -52.82894,30.1423 -71.63737,52.3772 -19.10759,22.58859 -39.82334,55.02483 -39.82334,62.35448 0,3.10354 95.5469,31.52011 109.25356,32.49304 4.21353,0.29923 5.51786,-17.06039 5.51786,-73.43688 z m 423.73992,57.83591 c 45.9947,-13.47958 77.1496,-28.43916 96.0422,-46.11628 l 14.3464,-13.42347 0,-79.71196 0,-79.71193 -14.4594,10.85028 c -21.2069,15.91353 -66.2133,35.4289 -110.738,48.01737 -21.5153,6.08304 -41.8762,11.06008 -45.2464,11.06008 -5.0132,0 -6.1276,15.06958 -6.1276,82.85937 l 0,82.85932 14.3464,-2.84783 c 7.8906,-1.56629 31.2169,-7.792 51.8364,-13.83495 z M 660.42884,500.30502 c 11.37924,-29.52535 39.7747,-87.79138 48.98798,-100.52086 5.13258,-7.09145 8.61867,-13.53408 7.74685,-14.31701 -0.87182,-0.78293 -17.17881,-7.77423 -36.23775,-15.53622 -19.05895,-7.76199 -44.38591,-20.55674 -56.28215,-28.43277 l -21.62951,-14.32005 0,79.19725 0,79.19725 14.34643,13.3086 c 17.06677,15.83219 31.01216,23.73458 34.60676,19.61048 1.43178,-1.64269 5.23941,-9.82667 8.46139,-18.18667 z m 415.20026,-98.43025 c 2.9429,-2.89525 -26.9013,-3.70311 -89.53242,-2.42366 -53.96442,1.10243 -95.82535,3.76747 -98.361,6.2621 -2.94288,2.89525 26.90127,3.70311 89.53243,2.42361 53.96439,-1.10243 95.82539,-3.76747 98.36099,-6.26205 z m 127.728,-52.27974 c 20.6368,-5.78426 53.4128,-18.79215 72.8357,-28.90641 l 35.3143,-18.38952 1.1998,-83.67083 1.1999,-83.67087 -21.0642,14.30154 c -11.5852,7.86587 -36.9555,20.60573 -56.3784,28.31079 -23.4164,9.28932 -35.8085,16.63431 -36.7813,21.80083 -0.8068,4.2854 -15.2084,18.1505 -32.0035,30.81131 l -30.5366,23.01975 0,56.12882 0,56.12887 14.3464,-2.67373 c 7.8906,-1.47053 31.2311,-7.4063 51.8679,-13.19055 z m -444.73927,-22.08999 20.96785,-29.06201 0,-46.25494 0,-46.25495 -30.47848,-6.77151 c -42.2278,-9.38196 -97.8708,-31.14324 -124.19376,-48.57064 l -22.08815,-14.62371 1.19803,83.05011 1.19808,83.05006 30.9,16.62016 c 24.80829,13.34356 89.24728,37.92478 99.32143,37.8876 1.21393,-0.004 11.64267,-13.08607 23.175,-29.07017 z M 865.66425,209.3336 c 1.5005,-2.38857 -1.3619,-4.34286 -6.3609,-4.34286 -4.999,0 -9.0891,1.95429 -9.0891,4.34286 0,2.38857 2.8624,4.34286 6.3609,4.34286 3.49849,0 7.58859,-1.95429 9.0891,-4.34286 z"
+ id="path3293"
+ style="fill:#c4c8d7"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 870.48191,621.80539 C 754.87328,611.59963 652.8138,581.1929 614.664,545.58916 c -15.9949,-14.9274 -25.84904,-37.85156 -22.28168,-51.83499 3.53836,-13.86992 5.76744,-13.62038 21.10033,2.36199 6.95581,7.25044 18.39618,16.10757 25.42311,19.68252 12.13209,6.1722 12.97292,6.00022 16.67801,-3.41114 13.69364,-34.78389 42.10734,-94.92982 51.31253,-108.61798 5.98917,-8.90594 10.88938,-16.94613 10.88938,-17.86712 0,-0.92099 -9.43553,-5.1578 -20.96785,-9.41505 -32.50239,-11.99867 -69.01828,-30.83937 -84.31722,-43.50427 l -13.90063,-11.50736 0,77.10986 c 0,42.41043 -1.0408,76.0859 -2.31296,74.83438 -3.01751,-2.96874 -3.0082,-551.270371 0.009,-554.239191 1.2669,-1.246444 2.30351,39.872899 2.30351,91.376273 l 0,93.642508 21.0082,13.26278 c 34.89872,22.0321 96.26594,44.21476 150.04536,54.23751 l 14.34643,2.67372 0,-87.093652 0,-87.09360841 12.13928,2.52967081 c 70.3548,14.6611376 217.502,14.6256996 317.8284,-0.076434 15.578,-2.28286623 18.7607,-1.541584 18.7607,4.3696524 0,6.3786582 1.2701,6.4727242 12.2146,0.904704 19.6824,-10.0134557 94.6934,-28.7616568 126.9202,-31.7223128 l 29.7215,-2.730528 -1.896,40.850564 c -1.0715,23.087236 -4.6899,46.515863 -8.3212,53.879134 -6.2196,12.61144 -7.6253,13.106959 -43.9072,15.476639 l -37.4819,2.448025 34.2107,0.809118 c 40.2437,0.951737 42.7069,5.103335 20.586,34.696388 -7.4936,10.02483 -11.8265,18.22697 -9.6288,18.22697 2.1978,0 16.7713,-7.59809 32.3855,-16.88459 l 28.3895,-16.88464 1.1933,-92.367357 1.1934,-92.367357 -12.2128,12.015122 c -6.717,6.608308 -23.1824,17.463018 -36.5898,24.12153 -30.2571,15.026589 -92.1263,35.7321593 -98.7485,33.0478828 -2.7068,-1.0971798 -1.2796,-2.1491498 3.1715,-2.3377168 4.4542,-0.18848 7.2376,-2.783598 6.1905,-5.77144 -2.3583,-6.729691 -9.4045,-6.492832 -78.5919,2.642021 -63.231,8.3484043 -201.8621,9.8780454 -257.9343,2.845961 -52.47256,-6.580601 -86.07856,-13.706448 -86.07856,-18.25216 0,-2.218765 1.98642,-2.826331 4.41428,-1.350107 2.42786,1.476224 4.41429,0.499081 4.41429,-2.171429 0,-2.670509 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476224 4.41429,0.2888 4.41429,-2.638937 0,-2.92765 1.15005,-4.191509 2.55569,-2.808613 3.21691,3.164857 113.89409,-47.893591 115.90347,-53.469472 0.81435,-2.259675 3.29738,-3.003867 5.51786,-1.653717 2.22109,1.350064 4.03788,0.269779 4.03788,-2.400731 0,-2.670512 1.98642,-3.647652 4.41428,-2.171432 2.42786,1.476227 4.41429,0.49908 4.41429,-2.17142 0,-2.67051 1.98642,-3.64766 4.41428,-2.17143 2.42786,1.47622 4.41429,0.49908 4.41429,-2.17143 0,-2.67051 1.98643,-3.64765 4.41428,-2.17143 2.42786,1.47622 4.41429,0.2888 4.41429,-2.63894 0,-2.92765 1.15005,-4.19151 2.55569,-2.80861 3.21691,3.16486 113.89412,-47.89359 115.90342,-53.46947 0.8144,-2.25968 3.2974,-3.00387 5.5179,-1.65372 2.2205,1.35011 4.0373,0.26982 4.0373,-2.40069 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4142,0.49909 4.4142,-2.17142 0,-2.67051 1.9865,-3.64766 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.2888 4.4143,-2.63893 0,-2.92765 1.2391,-4.10383 2.7537,-2.6138 1.5145,1.49004 10.762,-1.31024 20.5498,-6.22283 16.109,-8.08519 20.3466,-8.45789 44.6998,-3.93107 65.9095,12.25137 130.9582,41.16594 150.144,66.74012 8.6202,11.4905 9.3248,19.41157 11.3955,128.114277 1.2139,63.726562 1.1819,198.923653 -0.071,300.437933 -1.2531,101.51428 -2.7429,149.71865 -3.3107,107.12078 l -1.0325,-77.45064 -17.2282,13.42421 c -9.4755,7.38329 -29.8364,19.18187 -45.2464,26.21913 -29.1126,13.2947 -100.0703,34.91509 -114.5906,34.91509 -7.8794,0 -8.3348,4.53438 -8.3348,82.9976 l 0,82.9976 16.5536,-2.89369 c 30.1231,-5.26571 91.0626,-24.83606 116.1822,-37.31122 13.605,-6.75666 31.0361,-19.34374 38.7357,-27.9713 l 13.9995,-15.68649 4.5611,11.8025 c 19.8726,51.42312 -72.792,103.84648 -221.159,125.11693 -58.6122,8.40286 -173.8555,11.73222 -231.11969,6.67701 z M 783.99997,487.27645 c 0,-56.18936 -1.31833,-73.81884 -5.51786,-73.78784 -11.9628,0.0886 -51.66409,26.62332 -69.41972,46.39774 -17.31229,19.28064 -44.1101,60.15092 -44.20673,67.42121 -0.0565,4.25752 94.2608,32.53581 111.41931,33.40577 7.06983,0.35829 7.725,-5.86998 7.725,-73.43688 z m 293.36283,-85.6252 c 6.027,-3.31834 -18.8165,-3.99487 -88.41524,-2.40777 -54.76685,1.24892 -98.71433,4.0756 -101.21188,6.50998 -6.13718,5.9819 178.56892,1.98612 189.62712,-4.10221 z m 125.0964,-51.78827 c 36.3881,-10.25657 80.2934,-29.6932 103.53,-45.8323 l 12.1393,-8.43139 0,-84.14516 0,-84.1452 -14.3464,13.04182 c -14.8486,13.49829 -66.7641,40.07202 -88.6077,45.35523 -6.9017,1.66927 -14.0948,7.24132 -16.123,12.48932 -2.0139,5.21152 -17.7605,20.68238 -34.9923,34.37979 l -31.3306,24.90429 0,54.29531 0,54.29535 16.5536,-2.94211 c 9.1045,-1.61815 33.0341,-7.58736 53.1771,-13.26495 z m -441.63423,-25.29688 23.175,-31.98354 0,-42.7685 0,-42.7685 -38.625,-9.10501 C 696.30092,186.37235 642.96911,164.53924 618.02093,145.80403 l -19.42095,-14.58449 0,82.18987 0,82.18988 12.13928,8.42449 c 20.44348,14.18746 59.12225,32.19264 91.59643,42.63869 16.99499,5.46683 31.89321,9.92786 33.10714,9.91339 1.21393,-0.013 12.63589,-14.41885 25.38214,-32.00976 z M 865.66425,209.3336 c 1.55343,-2.47282 -2.24815,-4.34286 -8.82857,-4.34286 -6.58042,0 -10.382,1.87004 -8.82857,4.34286 1.5005,2.38857 5.47336,4.34286 8.82857,4.34286 3.35521,0 7.32806,-1.95429 8.82857,-4.34286 z M 1138.3383,-1.7473609 c 1.4669,-1.443088 5.2742,-1.5973462 8.4608,-0.3426514 3.5214,1.38632678 2.4754,2.41541018 -2.667,2.62378045 -4.6534,0.18848 -7.2606,-0.8378674 -5.7938,-2.28095535 z"
+ id="path3291"
+ style="fill:#c3c7d8"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 870.48191,621.80539 C 754.87328,611.59963 652.8138,581.1929 614.664,545.58916 c -15.9949,-14.9274 -25.84904,-37.85156 -22.28168,-51.83499 3.53836,-13.86992 5.76744,-13.62038 21.10033,2.36199 6.95581,7.25044 18.39618,16.10757 25.42311,19.68252 12.13209,6.1722 12.97292,6.00022 16.67801,-3.41114 13.78157,-35.00725 42.13912,-94.97707 51.4775,-108.86322 11.28273,-16.7775 11.00375,-24.8193 -0.86114,-24.8193 -11.1911,0 -73.48797,-29.98378 -91.04658,-43.82112 l -16.55357,-13.04533 0,76.92845 c 0,42.31068 -1.0408,75.9045 -2.31296,74.65298 -3.01751,-2.96874 -3.0082,-551.270371 0.009,-554.239191 1.2669,-1.246444 2.30351,39.872899 2.30351,91.376273 l 0,93.642508 22.29413,14.07459 c 12.26178,7.74106 34.34848,18.86481 49.0816,24.7195 26.02512,10.34164 104.59495,31.59706 116.79802,31.59706 4.92537,0 6.05543,-15.90727 6.05543,-85.23825 l 0,-85.2382534 23.175,2.7283999 c 82.2535,9.6836595 254.74269,7.1241965 302.37859,-4.4868661 7.2032,-1.75573025 9.9321,-0.5016869 9.9321,4.564169 0,8.5615956 -0.4818,8.5635056 20.4288,-0.081211 29.6551,-12.260059 90.9765,-27.5315424 122.4885,-30.5046184 l 30.3529,-2.86368 -1.8958,40.845916 c -1.0737,23.134095 -4.6864,46.495496 -8.3313,53.874531 -6.2661,12.685442 -7.3532,13.055714 -41.2759,14.05887 -45.2383,1.337644 -49.415,5.483856 -5.5243,5.483856 39.8882,0 42.4009,4.211486 20.2181,33.887317 -7.4936,10.02483 -11.8265,18.22697 -9.6287,18.22697 2.1977,0 16.7712,-7.59809 32.3854,-16.8846 l 28.3895,-16.88463 1.1941,-92.625109 1.1941,-92.625063 -14.4369,13.509803 c -17.1208,16.021234 -48.1128,31.837051 -86.9935,44.394378 -34.1067,11.015484 -31.1793,11.002629 -31.1793,0.1368 0,-7.122024 -1.9015,-8.38458 -9.1973,-6.106447 -56.3127,17.583707 -257.68279,25.2076359 -345.93248,13.097057 -29.0132,-3.981487 -55.05899,-8.583048 -57.87954,-10.225647 -2.82059,-1.642642 79.5434,-43.399777 183.03102,-92.793692 l 188.1594,-89.80712 22.013,4.33856 c 63.2967,12.47516 126.7664,41.28099 145.2533,65.92344 8.6202,11.49051 9.3248,19.41157 11.3955,128.1142838 1.2139,63.7265622 1.1819,198.9236562 -0.071,300.4379362 -1.2531,101.51428 -2.7429,149.71865 -3.3107,107.12078 l -1.0325,-77.4506 -16.5536,12.82611 c -30.7647,23.83724 -83.4575,45.24588 -144.5678,58.7367 l -26.4857,5.84705 -1.2031,81.65926 -1.2031,81.65931 18.8602,-2.8913 c 29.858,-4.5772 91.4727,-24.07137 118.4889,-37.48841 13.605,-6.75666 31.0361,-19.34374 38.7357,-27.9713 l 13.9995,-15.68649 4.5611,11.8025 c 19.8726,51.42312 -72.792,103.84648 -221.159,125.11693 -58.6122,8.40286 -173.85553,11.73222 -231.11972,6.67701 z M 792.82854,489.44788 c 0,-73.51897 -0.29311,-76 -8.9753,-76 -16.77093,0 -51.4964,21.15605 -72.56309,44.20811 -18.73092,20.4962 -46.32492,61.76007 -46.43449,69.43781 -0.0534,3.76122 45.53923,18.51456 90.45145,29.26899 19.42286,4.6509 35.81089,8.59768 36.41786,8.77066 0.60696,0.17285 1.10357,-33.88558 1.10357,-75.68557 z m 284.53426,-87.79663 c 6.027,-3.31834 -18.8165,-3.99487 -88.41524,-2.40777 -54.76685,1.24892 -98.71433,4.0756 -101.21188,6.50998 -6.13718,5.9819 178.56892,1.98612 189.62712,-4.10221 z m 125.0964,-51.72612 c 36.4333,-10.33114 80.368,-29.80716 103.53,-45.89445 l 12.1393,-8.43139 0,-84.14516 0,-84.1452 -14.3464,13.04182 c -14.8486,13.49829 -66.7641,40.07202 -88.6077,45.35523 -7.0093,1.69532 -14.0968,7.24654 -16.1999,12.6884 -2.0563,5.321 -18.7046,21.19622 -36.9963,35.27828 l -33.2576,25.60379 -0.203,53.58174 -0.2031,53.58174 18.7607,-3.06489 c 10.3184,-1.68568 35.2413,-7.73815 55.3842,-13.44991 z m -437.21995,-30.16254 27.58929,-36.53758 0,-37.08201 c 0,-25.94253 -1.65757,-37.54829 -5.51786,-38.63418 -3.03482,-0.85368 -22.48628,-5.45242 -43.22543,-10.2194 -47.89584,-11.00905 -101.09972,-32.73784 -126.06432,-51.48539 l -19.42095,-14.58449 0,82.18987 0,82.18988 12.13928,8.42449 c 20.44348,14.18746 59.12225,32.19264 91.59643,42.63869 16.99499,5.46683 31.89321,9.87175 33.10714,9.78871 1.21393,-0.083 14.62232,-16.59288 29.79642,-36.68859 z m 100.425,-110.42899 c 1.55343,-2.47282 -2.24815,-4.34286 -8.82857,-4.34286 -6.58042,0 -10.382,1.87004 -8.82857,4.34286 1.5005,2.38857 5.47336,4.34286 8.82857,4.34286 3.35521,0 7.32806,-1.95429 8.82857,-4.34286 z"
+ id="path3289"
+ style="fill:#c1c5d5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 870.48191,621.80539 C 754.87328,611.59963 652.8138,581.1929 614.664,545.58916 c -15.9949,-14.9274 -25.84904,-37.85156 -22.28168,-51.83499 3.53836,-13.86992 5.76744,-13.62038 21.10033,2.36199 6.95581,7.25044 18.43715,16.12837 25.51409,19.72882 12.83109,6.52783 12.88653,6.49999 19.74708,-9.92543 22.52394,-53.92643 38.60213,-87.95588 48.53247,-102.71882 12.59431,-18.72331 11.45992,-21.10485 -13.76918,-28.90683 -23.78536,-7.35546 -58.53046,-24.54522 -76.61482,-37.90429 -13.77968,-10.17918 -30.3969,-38.42351 -25.46226,-43.27826 1.19671,-1.17735 8.76218,2.72701 16.81216,8.67629 21.46729,15.86532 63.33926,35.53912 98.07602,46.08171 16.75755,5.08592 30.825,8.73023 31.26104,8.09851 11.50328,-16.66606 56.05498,-72.40003 61.29209,-76.67609 5.42922,-4.43297 7.20005,-13.51897 7.20005,-36.94329 l 0,-31.06442 -51.86785,-11.89213 c -86.16023,-19.75457 -145.73989,-49.99497 -158.7212,-80.56095 -3.42548,-8.0656 -5.34596,-15.53266 -4.26773,-16.59341 1.07819,-1.06078 14.31328,5.92622 29.41133,15.52663 15.098,9.60045 38.66596,21.59433 52.3732,26.65315 25.093,9.26084 92.09674,27.21873 118.72583,31.82012 l 14.34642,2.47899 0,-86.58515 0,-86.58519 29.79643,3.0937645 c 40.0762,4.1610645 197.99788,3.9349325 240.57858,-0.3443886 32.6419,-3.2805942 34.2107,-3.0316182 34.2107,5.4285711 0,4.876725 0.808,8.866811 1.7956,8.866811 0.9876,0 14.4623,-5.591124 29.9435,-12.42474 32.1733,-14.2017068 117.3797,-35.346817 142.4332,-35.346817 l 16.845,0 -2.1477,40.394824 c -1.1812,22.217188 -4.9811,46.157187 -8.4441,53.199998 -5.8519,11.901122 -7.8464,12.729001 -28.2503,11.726192 -12.0746,-0.593452 -28.9063,0.672187 -37.4038,2.812564 -13.1439,3.310777 -10.2723,3.957993 19.2393,4.336169 l 34.6892,0.444535 -2.9212,11.450681 c -1.6067,6.297876 -8.017,18.023596 -14.2453,26.057146 -6.2283,8.03354 -9.9965,14.60646 -8.3738,14.60646 4.9391,0 30.1426,-13.53526 49.6572,-26.66788 22.2526,-14.975214 23.5297,-15.141111 19.7907,-2.57102 -7.4039,24.891 -35.4142,46.64481 -85.791,66.62829 -20.8136,8.25638 -37.1942,17.49051 -38.7457,21.84192 -1.4783,4.14613 -21.4024,22.35764 -44.2758,40.47004 l -41.5879,32.93163 -1.2502,48.99307 -1.2503,48.99312 16.7003,-2.77513 c 66.3065,-11.01831 147.753,-40.47291 181.0931,-65.49111 l 14.3857,-10.79495 0,10.9334 c 0,32.23572 -86.5203,76.85567 -182.9074,94.32833 l -28.9783,5.25303 0,81.76362 c 0,47.801 1.7137,81.76362 4.1257,81.76362 2.2691,0 18.6572,-2.97212 36.4178,-6.6047 71.9209,-14.71 130.2697,-39.47887 152.9994,-64.94773 l 13.9995,-15.68649 4.5611,11.8025 c 19.8726,51.42312 -72.792,103.84648 -221.159,125.11693 -58.6122,8.40286 -173.8555,11.73222 -231.11969,6.67701 z m -64.41052,-134.72572 0,-78.36824 -16.55357,2.96039 c -27.46109,4.91104 -56.04977,21.71616 -78.22767,45.98417 -18.99211,20.782 -46.33035,61.79747 -46.43449,69.66546 -0.0446,3.385 73.52006,26.36201 105.90145,33.07698 38.52435,7.98882 35.31428,14.6535 35.31428,-73.31876 z m 273.68571,-86.2461 c 8.9394,-4.03134 3.9083,-4.54827 -26.4857,-2.72149 -20.6368,1.24032 -66.82126,2.26688 -102.63216,2.28121 -45.56836,0.0174 -65.11071,1.50376 -65.11071,4.9496 0,6.65886 178.66117,2.51095 194.22857,-4.50932 z M 865.66425,209.3336 c 1.61148,-2.5652 -3.25086,-4.31563 -11.87876,-4.27637 -11.05504,0.0504 -12.99684,1.09023 -7.98553,4.27637 8.58107,5.45576 16.43699,5.45576 19.86429,0 z M 839.57061,-12.490026 c -65.42395,-8.833935 -75.31565,-1.350672 133.73877,-101.175754 l 189.67862,-90.57302 40.1975,11.34398 c 84.1348,23.74335 119.3573,51.23633 119.3573,93.164571 0,14.385366 -2.7558,19.531174 -17.4582,32.599005 -19.8216,17.617841 -44.0644,30.650451 -79.0312,42.486126 -29.0086,9.818939 -27.1106,9.743287 -27.1106,1.080807 0,-7.766418 -1.278,-7.669225 -68.4214,5.205783 -42.4224,8.134605 -244.04918,12.2014735 -290.95079,5.868502 z"
+ id="path3287"
+ style="fill:#bec2d1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 870.48191,621.8027 C 754.89442,611.60589 652.81539,581.19438 614.664,545.58916 c -15.9949,-14.9274 -25.84904,-37.85156 -22.28168,-51.83499 3.10731,-12.18015 9.59975,-14.17726 13.50878,-4.15542 3.72945,9.56158 41.39937,34.08925 45.52307,29.64104 1.90427,-2.05413 12.74006,-25.23191 24.07957,-51.5062 11.3395,-26.27428 25.66925,-55.31853 31.8439,-64.54276 12.52872,-18.71654 11.38895,-21.09795 -13.83053,-28.89693 -23.78536,-7.35546 -58.53046,-24.54522 -76.61482,-37.90429 -13.77968,-10.17918 -30.3969,-38.42351 -25.46226,-43.27826 1.19671,-1.17735 8.76218,2.72701 16.81216,8.67629 21.46729,15.86532 63.33926,35.53912 98.07602,46.08171 16.75755,5.08592 30.8084,8.73023 31.22409,8.09851 7.05615,-10.72273 49.00127,-63.52101 65.00782,-81.82832 17.94367,-20.52291 21.17842,-26.97766 21.17842,-42.26013 0,-15.23014 -1.54584,-18.34223 -9.93215,-19.99542 -131.91884,-26.00577 -199.16483,-54.37905 -217.12338,-91.61149 -4.21083,-8.73005 -6.62355,-16.88863 -5.36159,-18.13017 1.26191,-1.24153 8.88079,2.60715 16.93077,8.55261 32.48393,23.99159 97.63667,48.51497 163.43239,61.51566 57.61507,11.38428 52.05404,19.59731 52.05329,-76.876997 l -6.1e-4,-83.599998 124.70418,0 c 68.58736,0 129.81546,-1.416162 136.06256,-3.1470508 10.6177,-2.9418514 11.2327,-2.3047108 9.4311,9.7714278 -1.06,7.105175 -0.9863,12.91848 0.1638,12.91848 1.15,0 18.7567,-7.550882 39.126,-16.779757 38.6408,-17.5072727 123.0208,-39.677384 151.013,-39.677384 l 16.2435,0 -0.1934,31.4857133 c -0.109,17.7970277 -3.4677,40.9262157 -7.725,53.1999987 l -7.5318,21.714285 -22.0714,-1.087756 c -12.1393,-0.598272 -29.0239,0.66741 -37.5214,2.812608 -13.2106,3.33501 -10.4914,3.964768 18.7607,4.344898 39.5618,0.514064 40.5225,1.981038 21.0651,32.165722 -7.2301,11.21621 -12.1171,20.3931 -10.8599,20.3931 5.0598,0 40.3645,-19.03422 54.0919,-29.1632 8.0499,-5.93981 15.4419,-10.00711 16.4265,-9.0384 0.9847,0.96868 -2.1026,9.47177 -6.8606,18.89573 -10.5069,20.81036 -38.6021,40.22511 -83.7951,57.90509 -18.209,7.12355 -33.7276,15.89829 -34.4858,19.49947 -0.7583,3.60119 -25.092,25.27439 -54.075,48.16268 L 1093,287.72434 l 0,42.67977 0,42.67978 35.1754,-5.97634 c 72.8966,-12.38522 142.9644,-37.72435 180.021,-65.10233 l 14.3464,-10.59935 0,10.9334 c 0,23.10578 -46.858,55.25195 -110.3571,75.70876 -24.2295,7.80573 -105.2065,26.42681 -115.875,26.64608 -1.8209,0.0391 -3.3107,37.2367 -3.3107,82.66507 l 0,82.59701 9.9321,-2.09599 c 5.4627,-1.15286 26.8168,-5.32651 47.4536,-9.27491 70.4369,-13.47641 129.5744,-38.13206 154.1658,-64.27493 13.7323,-14.59856 16.2899,-14.31741 19.6394,2.15892 7.9043,38.88225 -49.5875,80.39744 -142.9052,103.19262 -85.9854,21.00405 -217.49387,30.37238 -310.80379,22.1408 z m -46.75337,-132.35482 0,-80.34286 -14.94311,0 c -43.79298,0 -95.27092,34.24382 -124.99619,83.14912 -23.23984,38.23521 -23.90733,36.12154 15.48222,49.02721 27.41073,8.9809 99.68146,25.76999 121.14636,28.14332 1.8209,0.20151 3.31072,-35.78822 3.31072,-79.97679 z m 259.33926,-87.2493 c 26.7843,-5.16232 14.5017,-8.53919 -19.8643,-5.46128 -18.8159,1.6852 -66.37291,3.20308 -105.6823,3.37301 -48.24686,0.20846 -72.47797,1.91095 -74.56853,5.23879 -2.29309,3.6503 21.41264,4.24002 91.33589,2.27218 51.93804,-1.46171 100.88874,-3.90192 108.77924,-5.4227 z M 865.66425,209.3336 c 1.64728,-2.62222 -3.93366,-4.22191 -14.0859,-4.0376 -13.42697,0.24364 -15.03563,1.0571 -7.98553,4.0376 12.13258,5.12917 18.84927,5.12917 22.07143,0 z M 843.59282,-12.441646 c -16.995,-2.225367 -33.87964,-4.647682 -37.52143,-5.382972 -3.64178,-0.735289 5.55472,-7.486347 20.43673,-15.002356 14.88197,-7.516009 28.29036,-12.453185 29.79643,-10.97149 1.50602,1.481696 2.73827,0.298789 2.73827,-2.628991 0,-2.927651 1.98643,-4.115161 4.41429,-2.638937 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98642,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98642,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.81679,-3.750795 4.03726,-2.400688 2.22047,1.350151 4.70351,0.976926 5.51786,-0.829399 1.98078,-4.393668 57.24042,-29.635826 61.85652,-28.255626 1.9972,0.59714 3.6312,-0.98392 3.6312,-3.51346 0,-2.52954 1.9864,-3.39134 4.4143,-1.91511 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4142,-2.17143 2.4279,1.47623 4.4143,0.49909 4.4143,-2.17142 0,-2.67051 1.9865,-3.64766 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4142,0.49908 4.4142,-2.17142 0,-2.67051 1.9865,-3.64766 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.8167,-3.75079 4.0372,-2.40068 2.2205,1.35015 4.7035,0.97692 5.5179,-0.8294 1.9807,-4.39367 57.2404,-29.63583 61.8565,-28.25563 1.9972,0.59714 3.6312,-0.73642 3.6312,-2.96352 0,-13.29184 89.929,15.43573 122.8246,39.23597 51.4546,37.22784 40.6532,82.418962 -27.3533,114.441669 -41.9239,19.741021 -55.7427,23.020443 -55.7427,13.228734 0,-6.001004 -2.3688,-6.842085 -12.1393,-4.310373 -57.471,14.891787 -110.0885,20.258907 -212.98929,21.7254896 -59.48249,0.8477692 -122.05499,-0.2792456 -139.04999,-2.5046556 z"
+ id="path3285"
+ style="fill:#bcc0ce"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 873.23757,621.58108 c -112.6289,-8.5107 -220.08453,-40.09286 -258.57357,-75.99717 -15.99366,-14.91959 -25.84935,-37.84505 -22.28168,-51.82974 3.10731,-12.18015 9.59975,-14.17726 13.50878,-4.15542 1.42983,3.66585 11.60131,12.75822 22.6033,20.20531 19.07453,12.91123 20.27998,13.16824 25.95459,5.53337 3.27306,-4.40375 5.95098,-9.57531 5.95098,-11.49238 0,-6.53695 40.66109,-92.51618 49.58055,-104.83973 10.16248,-14.04094 8.11898,-17.10634 -16.47341,-24.71142 -23.78536,-7.35546 -58.53046,-24.54522 -76.61482,-37.90429 -13.77968,-10.17918 -30.3969,-38.42351 -25.46226,-43.27826 1.19671,-1.17735 8.76218,2.72701 16.81216,8.67629 21.46729,15.86532 63.33926,35.53912 98.07602,46.08171 16.75755,5.08592 30.825,8.73023 31.26104,8.09851 8.68255,-12.57934 59.51027,-75.70325 72.61111,-90.17712 13.77222,-15.21555 17.71815,-23.10535 17.82074,-35.6318 0.12492,-15.24103 -0.74063,-16.2332 -16.42185,-18.82715 -111.91639,-18.51273 -197.37458,-54.69107 -214.87055,-90.96453 -4.23595,-8.78221 -6.66924,-16.98339 -5.40728,-18.22493 1.26191,-1.24153 8.88079,2.60715 16.93077,8.55261 35.98216,26.57529 102.89426,50.81547 175.86147,63.70919 l 44.03916,7.78201 -4e-4,-85.226268 -3.9e-4,-85.226267 128.01414,0 128.01413,0 5e-4,11.942857 c 3e-4,6.568571 0.716,11.942857 1.5904,11.942857 0.8744,0 19.2489,-8.052221 40.8321,-17.89383 42.1631,-19.2256545 128.6436,-42.906168 156.6918,-42.906168 l 17.1863,0 0,29.6117705 c 0,32.6010025 -10.5501,74.3977435 -18.9318,75.0027035 -2.9407,0.212366 -14.2857,0.169372 -25.2111,-0.09554 -10.9253,-0.26448 -25.8236,1.34798 -33.1071,3.583683 -10.8735,3.337659 -7.122,4.131707 20.9678,4.438226 40.3327,0.440061 41.3107,2.272704 18.5699,34.797136 l -15.6409,22.36993 23.3659,-11.51127 c 12.8512,-6.33119 29.8217,-16.2778 37.7123,-22.10362 l 14.3464,-10.59236 0,9.70512 c 0,16.84585 -38.0144,48.13353 -77.4436,63.73981 -19.3164,7.64551 -37.6037,13.89041 -40.6385,13.87759 -3.0349,-0.013 -5.5179,3.71931 -5.5179,8.29369 0,5.42814 -17.7526,22.114 -51.1091,48.0379 -28.11,21.84644 -53.6908,40.69548 -56.8461,41.88673 -4.0994,1.54762 -5.9527,14.47357 -6.4922,45.27954 l -0.7551,43.11358 21.1834,-2.68632 c 69.2401,-8.78056 166.9268,-42.12267 203.2334,-69.36697 12.7293,-9.55199 14.3823,-9.89442 14.3566,-2.97395 -0.136,36.60486 -83.4053,78.94159 -195.303,99.29834 l -43.0393,7.82987 0,81.7178 c 0,48.67005 1.6946,81.71802 4.1902,81.71802 2.3046,0 25.4084,-4.02843 51.3417,-8.95206 81.5876,-15.49002 149.0782,-43.53089 168.7434,-70.10917 8.1038,-10.95269 12.6938,-9.71597 15.3872,4.14582 11.7845,60.64904 -108.3316,113.37823 -289.1942,126.95213 -64.9265,4.87277 -94.24826,4.828 -161.40213,-0.24624 z m -45.09475,-134.64972 0,-82.85932 -12.13928,2.36799 c -6.67661,1.30238 -20.085,3.67614 -29.79643,5.27505 -25.75775,4.24084 -45.86288,15.67259 -69.26146,39.38202 -18.5253,18.77135 -52.13139,67.5957 -52.13139,75.73865 0,7.09558 122.94858,40.27613 157.81071,42.58892 4.23449,0.28098 5.51785,-18.9058 5.51785,-82.49331 z m 259.33928,-84.72058 c 7.8905,-1.52751 14.3464,-4.73771 14.3464,-7.13375 0,-2.46935 -4.3023,-3.11852 -9.9321,-1.49859 -5.4627,1.57185 -54.1982,4.04897 -108.3012,5.50474 -56.5643,1.52204 -99.5082,4.46025 -101.04953,6.91383 -2.88699,4.59561 178.99733,1.23533 204.93643,-3.78623 z M 865.66425,209.3336 c 1.68705,-2.68549 -5.37311,-4.17766 -18.50018,-3.90996 -17.22525,0.35134 -19.14732,1.08858 -10.19268,3.90996 15.83171,4.98816 25.55929,4.98816 28.69286,0 z m -4.41429,-220.02603 c -31.05763,-3.709669 -41.93571,-6.458914 -41.93571,-10.598482 0,-2.421317 1.98643,-3.194562 4.41429,-1.718338 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41429,1.02018 4.41429,-1.01345 0,-4.092404 75.28722,-38.625066 80.56071,-36.951502 1.82089,0.57786 3.31071,-1.018965 3.31071,-3.548505 0,-2.529541 1.98643,-3.391337 4.41429,-1.915113 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.670512 1.98643,-3.647652 4.41429,-2.171432 2.42786,1.476227 4.41429,0.49908 4.41429,-2.17142 0,-2.67051 1.98642,-3.64766 4.41428,-2.17143 2.42786,1.47622 4.41433,0.49908 4.41433,-2.17143 0,-2.67051 1.9864,-3.64765 4.4142,-2.17143 2.4279,1.47622 4.4143,1.02018 4.4143,-1.01345 0,-4.0924 75.2873,-38.62507 80.5607,-36.9515 1.8209,0.57786 3.3108,-1.01897 3.3108,-3.54851 0,-2.52954 1.9864,-3.39133 4.4142,-1.91511 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17142 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64766 4.4143,-2.17143 2.4278,1.47622 4.4142,0.49908 4.4142,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49909 4.4143,-2.17142 0,-2.67051 1.9864,-3.64766 4.4143,-2.17143 2.4278,1.47622 4.4142,0.49908 4.4142,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47622 4.4143,0.91304 4.4143,-1.25148 0,-2.16452 4.4375,-5.88353 9.8611,-8.26446 17.5056,-7.68481 77.9622,12.03384 116.1531,37.88465 22.9163,15.51173 32.9001,32.10306 32.9001,54.67436 0,22.44649 -27.4329,47.85672 -71.0841,65.842923 -29.8016,12.279559 -32.7677,12.771127 -33.9816,5.631557 -1.2792,-7.522871 -2.6661,-7.51627 -36.2297,0.172411 -65.0106,14.892786 -133.4353,21.2249754 -222.00568,20.5449274 -46.6119,-0.3577645 -90.70819,-1.3623976 -97.99176,-2.2323584 z"
+ id="path3283"
+ style="fill:#b9bdcd"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 873.23757,621.58108 c -112.6289,-8.5107 -220.08453,-40.09286 -258.57357,-75.99717 -15.99366,-14.91959 -25.84935,-37.84505 -22.28168,-51.82974 3.10731,-12.18015 9.59975,-14.17726 13.50878,-4.15542 3.34452,8.57471 40.16858,33.60042 45.06266,30.62469 2.0473,-1.24483 7.29884,-11.61002 11.67009,-23.03373 9.61166,-25.11891 44.82614,-96.26529 51.6527,-104.35768 6.26347,-7.42489 1.54314,-11.63807 -20.76944,-18.53813 -45.91779,-14.1998 -85.94971,-40.36973 -97.09287,-63.47216 -9.58977,-19.88195 -6.27552,-22.41336 11.82795,-9.0341 21.46729,15.86532 63.33926,35.53912 98.07602,46.08171 16.75755,5.08592 30.8084,8.73023 31.22409,8.09851 9.98353,-15.17125 51.02592,-65.52263 73.83153,-90.57753 37.94759,-41.69026 38.74688,-49.63938 5.40344,-53.73795 -36.80079,-4.52356 -108.1669,-22.34335 -139.6261,-34.86406 -38.85326,-15.46349 -71.97439,-38.88542 -80.76734,-57.11535 -9.62089,-19.946481 -6.19863,-22.354768 12.17482,-8.56746 20.97655,15.74073 74.49173,39.21804 113.64132,49.85487 28.51002,7.74614 103.82077,22.31608 115.34965,22.31608 2.10985,0 3.8365,-38.59714 3.83707,-85.771429 l 10e-4,-85.771425 116.9783,0 116.97831,0 -7e-4,14.114285 c -4e-4,7.762857 0.7442,14.114285 1.6547,14.114285 0.9106,0 21.2715,-8.960096 45.2465,-19.911304 48.0331,-21.9404615 108.1163,-39.348239 150.6373,-43.643846 l 27.5893,-2.787159 0,30.2114759 c 0,16.6163351 -3.0566,40.3376281 -6.7925,52.7140321 l -6.7924,22.502514 -27.4183,0.513803 c -45.6544,0.855587 -59.823,7.060748 -17.4861,7.658108 42.7583,0.603266 43.6891,2.168301 20.777,34.93768 l -15.6409,22.36992 23.3659,-11.51126 c 12.8512,-6.33119 29.8217,-16.27781 37.7123,-22.10362 l 14.3464,-10.59236 0,9.70511 c 0,16.84586 -38.0144,48.13354 -77.4436,63.73981 -19.3164,7.64551 -37.6037,13.89041 -40.6385,13.8776 -3.0349,-0.013 -5.5179,3.68552 -5.5179,8.21859 0,7.29844 -72.6938,68.09522 -104.8393,87.68142 -5.4627,3.32841 -9.9321,8.513 -9.9321,11.52129 0,3.0083 -1.9864,5.46962 -4.4143,5.46962 -2.6004,0 -4.4143,15.33584 -4.4143,37.32181 l 0,37.32182 23.175,-2.88522 c 37.1739,-4.62801 111.8974,-23.02778 143.8109,-35.4117 16.1974,-6.28533 40.8718,-18.84018 54.832,-27.89968 18.3052,-11.87919 25.3821,-14.63178 25.3821,-9.87236 0,32.24723 -71.3983,72.95644 -162.8576,92.85649 -44.422,9.66555 -55.1935,10.41265 -50.8687,3.52823 2.1888,-3.48436 -1.2881,-4.13727 -11.8788,-2.23071 -8.2354,1.48261 -59.2395,3.99438 -113.34252,5.5817 -56.28016,1.65124 -99.51596,4.71178 -101.04953,7.15303 -1.73839,2.76723 31.67175,3.57491 95.05833,2.29798 l 97.73882,-1.96896 0,82.29184 0,82.29184 23.175,-2.9716 c 90.3463,-11.58457 173.8931,-40.31617 204.3427,-70.27307 8.2356,-8.1023 15.8016,-14.73145 16.8134,-14.73145 1.0119,0 2.8839,5.37381 4.16,11.94173 11.7845,60.64904 -108.3316,113.37823 -289.1942,126.95213 -64.92652,4.87277 -94.24828,4.828 -161.40215,-0.24624 z m -31.85189,-134.30463 0,-82.51429 -10.22499,0 c -19.73397,0 -62.48584,9.55542 -77.9207,17.41599 -26.61179,13.55267 -77.29158,71.65662 -85.77884,98.34473 -2.70145,8.49481 0.25338,10.41908 31.94076,20.8012 35.95718,11.78109 108.37305,27.45311 129.84448,28.10059 l 12.13929,0.36606 0,-82.51428 z M 867.87139,209.3336 c 4.86035,-3.09016 1.72775,-4.22756 -11.77824,-4.27637 -10.11984,-0.0347 -21.5418,-1.2527 -25.38214,-2.70256 -3.84039,-1.44982 -6.98247,-0.60248 -6.98247,1.88298 0,4.00394 10.26374,6.90088 30.9,8.72163 3.64178,0.32137 9.60107,-1.31024 13.24285,-3.62568 z m 215.11241,192.57991 c 2.9886,-1.1896 8.9479,-1.26199 13.2429,-0.16112 4.2949,1.10109 1.8497,2.07441 -5.4339,2.16292 -7.2836,0.0886 -10.7976,-0.81238 -7.809,-2.00202 z M 853.52496,-11.001425 c -11.53232,-0.894628 -20.91404,-2.985236 -20.84823,-4.645771 0.0658,-1.660578 82.9055,-42.42828 184.08827,-90.594904 l 183.9686,-87.57576 34.1208,12.25107 c 60.618,21.76484 87.6884,46.46775 87.6884,80.01932 0,21.190359 -20.2734,42.606246 -56.384,59.561368 -23.8593,11.202703 -30.4759,12.715798 -37.292,8.527938 -6.5809,-4.043417 -18.462,-2.87128 -57.7302,5.695353 -27.1941,5.932603 -68.7332,12.849124 -92.3091,15.370065 -39.8381,4.2598219 -177.37695,5.1091544 -225.30254,1.391321 z"
+ id="path3281"
+ style="fill:#b5b9c9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 873.23757,621.58108 c -112.6289,-8.5107 -220.08453,-40.09286 -258.57357,-75.99717 -15.99366,-14.91959 -25.84935,-37.84505 -22.28168,-51.82974 3.10731,-12.18015 9.59975,-14.17726 13.50878,-4.15542 3.483,8.92982 40.26464,33.54201 45.43165,30.40034 2.25027,-1.36826 8.14175,-13.13814 13.09219,-26.15529 4.95045,-13.01715 18.44178,-41.80573 29.98068,-63.97458 11.53894,-22.1689 21.02555,-42.07486 21.08139,-44.23547 0.0561,-2.16066 -9.85383,-7.25761 -22.0215,-11.32661 -50.26229,-16.80825 -85.7139,-40.00106 -97.04127,-63.4854 -4.06851,-8.435 -6.71346,-16.00912 -5.87766,-16.8314 0.83575,-0.82223 13.29825,6.12482 27.69439,15.43799 14.39613,9.31313 42.18869,22.57148 61.76119,29.46294 59.38764,20.91047 56.14561,21.11966 74.35917,-4.79803 23.46864,-33.39553 101.07853,-119.26832 111.31292,-123.16403 6.35847,-2.42036 0.84529,-4.28362 -19.7052,-6.65951 -19.53577,-2.25859 -29.49073,-1.77558 -31.56836,1.53177 -2.21791,3.53057 -10.57689,3.41761 -31.05909,-0.41952 -102.1633,-19.14036 -175.59675,-54.35915 -189.76029,-91.00931 -5.84915,-15.135504 -1.95915,-15.983446 14.98734,-3.26691 20.97655,15.74073 74.49173,39.21804 113.64132,49.85487 37.33717,10.14444 105.06657,22.31607 124.17822,22.31607 l 12.66463,0 0,-84.529973 0,-84.529976 100.425,-1.241449 100.42498,-1.241449 -1.5128,20.383416 -1.5128,20.383417 53.3887,-25.897977 c 55.7967,-27.06603081 115.4307,-45.427674 163.2149,-50.254759 l 28.178,-2.846526 -2.3809,36.4876424 c -1.3094,20.0682116 -4.6474,43.8162136 -7.4176,52.7733556 l -5.0368,16.285714 -27.3817,0.513804 c -45.6286,0.856237 -59.7668,7.061007 -17.4497,7.658107 42.7583,0.603266 43.6891,2.168301 20.777,34.937673 l -15.6409,22.36993 23.3659,-11.51126 c 12.8512,-6.33119 29.8217,-16.27781 37.7123,-22.10363 13.8966,-10.2603 14.3464,-10.31354 14.3464,-1.69814 0,21.78295 -51.5698,58.57915 -104.8393,74.80515 -12.329,3.75548 -18.7607,8.21772 -18.7607,13.01606 0,4.1962 -22.0592,24.01426 -51.8679,46.59842 -28.5273,21.61327 -60.2772,46.67751 -70.5554,55.69823 -18.6194,16.34147 -18.6879,16.51345 -18.7607,47.10206 l -0.073,30.70074 32.0036,-3.75896 c 77.9702,-9.15787 164.4844,-35.24836 205.9341,-62.1045 26.605,-17.23793 26.9194,-17.33816 26.9194,-8.57906 0,13.14405 -32.3557,41.63206 -64.6296,56.90403 -31.3649,14.8418 -125.1017,41.14066 -144.4163,40.5175 -9.2398,-0.29792 -9.2547,-0.40084 -0.6326,-4.39688 6.2712,-2.90645 2.4351,-3.4256 -13.2429,-1.79225 -12.1393,1.26468 -65.3387,3.52944 -118.2211,5.03281 -53.94548,1.53355 -97.30824,4.57771 -98.78919,6.93515 -1.68171,2.67702 32.40788,3.60513 93.94253,2.55755 53.12006,-0.90436 93.10576,-0.9837 88.85706,-0.17632 -7.0321,1.33621 -7.725,8.8312 -7.725,83.55513 l 0,82.0873 12.1393,-1.99159 c 6.6766,-1.09536 27.5596,-4.11547 46.4067,-6.71137 84.7318,-11.67051 184.8356,-50.56618 194.6057,-75.61474 4.0677,-10.42859 10.4483,-7.83807 12.9966,5.27657 11.7547,60.49604 -108.7879,113.41245 -289.1943,126.95213 -64.92648,4.87278 -94.24824,4.828 -161.4021,-0.24624 z m -14.19475,-129.765 0,-82.31747 -16.18078,-2.98645 c -20.01217,-3.69351 -67.91754,4.61854 -89.26997,15.48928 -27.35117,13.92476 -88.77781,84.90546 -88.77781,102.58597 0,12.28316 95.38304,36.82365 190.91785,49.12006 1.82089,0.23451 3.31071,-36.61676 3.31071,-81.89139 z m 208.29908,-207.2777 c -3.1866,-1.25453 -6.9939,-1.10027 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7937,2.28095 5.1425,-0.20845 6.1884,-1.23745 2.667,-2.62378 z m 15.6419,117.37513 c 2.9886,-1.1896 8.9479,-1.26199 13.2429,-0.16112 4.2949,1.10109 1.8497,2.07441 -5.4339,2.16292 -7.2836,0.0886 -10.7976,-0.81238 -7.809,-2.00202 z M 822.54096,210.8278 c 2.9886,-1.18959 8.94789,-1.26199 13.24286,-0.16112 4.29497,1.10109 1.84972,2.07441 -5.43386,2.16292 -7.28357,0.0886 -10.7976,-0.81238 -7.809,-2.00201 z m 43.83756,-221.78458 c -10.10156,-1.488514 22.76946,-18.750285 169.68098,-89.10561 l 182.2096,-87.25946 21.7778,7.60964 c 32.065,11.20418 63.1965,31.46352 73.464,47.80782 19.8304,31.5671 6.5578,60.494433 -38.6277,84.188365 -30.433,15.958176 -31.7975,16.253012 -31.7975,6.870704 0,-4.910816 -2.2892,-6.077003 -7.725,-3.935323 -22.2984,8.785426 -93.6513,23.910424 -141.6621,30.028685 -50.4561,6.4299034 -193.26024,8.8140451 -227.32008,3.795179 z"
+ id="path3279"
+ style="fill:#b1b5c5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 874.49282,621.53691 C 757.1254,612.24285 655.20175,582.30433 614.48986,545.16499 c -15.77467,-14.39044 -25.67715,-37.41853 -22.10754,-51.41082 3.33575,-13.07561 6.73916,-13.60752 13.98905,-2.18646 6.7388,10.61598 39.7336,31.60393 44.82102,28.5106 2.03878,-1.23963 8.25781,-13.52431 13.82003,-27.29929 13.51618,-33.47305 43.41635,-92.85445 51.05032,-101.38539 5.45562,-6.09667 2.01548,-8.39326 -31.12393,-20.7777 -46.46322,-17.36361 -76.52442,-37.44146 -87.65672,-58.54575 -4.72077,-8.94954 -7.82785,-17.01505 -6.9046,-17.92336 0.92329,-0.90831 13.45734,5.96834 27.85348,15.28151 14.39613,9.31313 42.68283,22.8096 62.85924,29.99216 58.67032,20.88589 54.63447,21.14763 72.96929,-4.73271 24.24511,-34.22297 101.53378,-119.55625 111.60475,-123.22119 6.71942,-2.44524 3.55575,-4.03039 -13.24286,-6.63527 -33.89125,-5.25534 -49.99982,-5.04962 -41.93571,0.53556 8.99269,6.22831 -26.64953,1.42636 -70.37457,-9.48133 -75.4159,-18.81334 -135.10428,-51.9222 -146.4275,-81.22263 -5.92048,-15.320079 -2.19297,-16.338652 14.55858,-3.97823 46.43095,34.25984 146.05286,64.54971 245.28277,74.5778 l 27.58929,2.78811 0,-87.07276 0,-87.072806 77.24999,0 77.24996,0 0,26.471711 0,26.471711 25.3822,-14.189286 c 79.1323,-44.236993 150.9742,-69.976716 213.1588,-76.371095 l 27.4958,-2.827373 -2.382,36.5078796 c -1.3101,20.0793294 -4.6485,43.8364514 -7.4187,52.7935934 l -5.0368,16.285714 -27.3817,0.513803 c -45.6286,0.856238 -59.7668,7.061008 -17.4497,7.658108 42.7583,0.603266 43.6891,2.168301 20.777,34.93768 l -15.6409,22.36992 23.3659,-11.51126 c 12.8512,-6.33119 29.8217,-16.27781 37.7123,-22.10362 13.8966,-10.26031 14.3464,-10.31355 14.3464,-1.69815 0,21.93391 -52.6695,59.29151 -104.8393,74.36075 -12.6455,3.65264 -18.7607,7.84884 -18.7607,12.8734 0,4.37425 -21.4314,23.81583 -51.8679,47.05212 -46.5538,35.54081 -53.903,39.67221 -71.7321,40.32464 -16.2644,0.59519 -17.2644,1.04437 -5.5178,2.47851 7.8905,0.96333 14.3464,3.42261 14.3464,5.46505 0,5.02278 -28.0621,21.79333 -36.4667,21.79333 -9.2654,0 -12.0905,9.31864 -12.0905,39.88167 l 0,26.1427 32.0036,-3.15418 c 84.8445,-8.36213 184.1401,-36.7394 228.0056,-65.16079 12.8016,-8.29447 24.0955,-15.08083 25.0975,-15.08083 6.6689,0 -0.6873,15.73599 -14.2714,30.52872 -20.2621,22.06471 -66.0847,44.03683 -120.9068,57.97523 -48.8126,12.41054 -81.9738,17.87776 -68.2642,11.25464 6.3896,-3.08681 1.5117,-3.67735 -17.6571,-2.13755 -14.5672,1.17014 -68.2007,3.38717 -119.18573,4.92675 -50.985,1.53959 -95.09218,4.66701 -98.01599,6.94979 -3.06801,2.39543 -17.26418,2.89174 -33.57108,1.17366 -31.69894,-3.33975 -71.2109,3.05107 -96.27221,15.57144 -29.01497,14.49555 -96.41996,96.85866 -86.91825,106.20661 11.49453,11.3085 124.077,38.08186 183.45254,43.627 l 32.00357,2.98884 0,-83.07543 0,-83.07542 77.24999,0 77.24996,0 0,82.51428 0,82.51429 12.1393,-0.0261 c 23.0157,-0.0486 100.4377,-13.13041 141.7831,-23.95685 51.508,-13.48757 93.3439,-32.82944 113.1447,-52.30976 8.3341,-8.19927 15.9808,-14.90776 16.9926,-14.90776 4.4734,0 6.0007,24.4769 2.1543,34.52306 -19.9708,52.15928 -130.589,92.26465 -287.9586,104.4012 -63.341,4.88497 -94.42565,4.82235 -159.37678,-0.32094 z M 1052.1678,406.22883 c 3.0348,-1.20475 8.0009,-1.20475 11.0357,0 3.0348,1.20476 0.5518,2.1905 -5.5178,2.1905 -6.0697,0 -8.5527,-0.98574 -5.5179,-2.1905 z m 33.1991,-4.09049 c 1.4668,-1.44309 5.2741,-1.59735 8.4607,-0.34265 3.5214,1.38633 2.4755,2.41541 -2.6669,2.62378 -4.6534,0.18848 -7.2606,-0.83787 -5.7938,-2.28096 z M 827.03925,210.80027 c 3.03482,-1.20475 8.00089,-1.20475 11.03571,0 3.03483,1.20475 0.55179,2.19049 -5.51785,2.19049 -6.06965,0 -8.55268,-0.98574 -5.51786,-2.19049 z M 881.48209,-9.5286749 c 1.01162,-0.9457001 82.03067,-40.0151281 180.04231,-86.8209641 l 178.203,-85.101541 30.7024,15.87258 c 48.0014,24.81587 63.2029,55.13717 43.5446,86.855358 -11.151,17.991849 -48.1165,39.970744 -53.313,31.698731 -2.9436,-4.685856 -11.081,-3.573564 -42.6107,5.824379 -82.8991,24.70951 -151.1354,33.390881 -262.45532,33.390881 -41.77393,0 -75.12487,-0.7737234 -74.11329,-1.7194239 z"
+ id="path3277"
+ style="fill:#aeb1c1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.53691 c -86.04965,-6.81407 -171.7798,-26.37573 -219.44773,-50.07296 -42.55014,-21.15297 -68.78966,-53.69335 -62.66277,-77.70978 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.51658,-32.17158 37.98692,-83.37209 49.35313,-99.20979 l 8.08596,-11.26694 -37.07073,-13.85358 c -46.28604,-17.29743 -76.35068,-37.39643 -87.46991,-58.47596 -4.72077,-8.94954 -7.82785,-17.01505 -6.9046,-17.92336 0.92329,-0.90831 13.45734,5.96834 27.85348,15.28151 22.70567,14.68872 78.37709,37.82481 109.52018,45.51475 8.47274,2.09213 14.07897,-2.96839 37.19367,-33.57302 14.96942,-19.82006 44.87007,-54.11361 66.4459,-76.20789 24.32695,-24.91154 36.4375,-40.17143 31.88081,-40.17143 -4.04137,0 -20.0545,-1.13731 -35.58475,-2.52732 -21.37485,-1.91321 -26.62772,-1.34633 -21.61539,2.33254 15.36768,11.27932 -79.73448,-10.47471 -131.2691,-30.02694 -37.7059,-14.30568 -73.73619,-41.00283 -81.11868,-60.1061 -5.92203,-15.324074 -2.1905,-16.339304 14.55858,-3.96099 37.51198,27.72306 95.42604,48.98708 172.31172,63.26692 66.65324,12.3794 104.08479,15.44055 111.79792,9.1428 5.2017,-4.24719 6.41956,-20.7946 6.41956,-87.224072 l 0,-81.982544 59.59285,0 59.59286,0 0,32.477926 0,32.477882 32.0036,-19.113391 c 77.5863,-46.336678 157.5963,-76.150695 222.795,-83.019922 l 28.8833,-3.043083 -2.376,36.396008 c -1.3068,20.017791 -4.6425,43.724579 -7.4127,52.681721 l -5.0368,16.285714 -27.3817,0.513803 c -44.2685,0.830746 -60.4306,7.04494 -19.9174,7.658108 18.6726,0.28272 35.2487,2.580873 36.8359,5.10733 1.5871,2.526413 -2.7652,12.74937 -9.6717,22.71766 -6.9065,9.96824 -14.211,20.71868 -16.2322,23.88979 -4.5568,7.14922 38.2394,-13.61681 57.75,-28.02206 13.8966,-10.26031 14.3464,-10.31355 14.3464,-1.69815 0,21.6693 -52.9607,59.21134 -104.8393,74.31714 -12.7212,3.70411 -18.7607,7.8862 -18.7607,12.9911 0,4.48274 -20.5881,23.09196 -50.8897,45.99832 -45.3463,34.27926 -53.4006,38.74749 -73.9393,41.01907 -19.5488,2.16209 -20.5354,2.65678 -6.496,3.25714 9.1045,0.38955 16.5536,2.50279 16.5536,4.69654 0,4.49889 -27.2899,22.06849 -34.2779,22.06849 -2.4985,0 -10.7064,5.32091 -18.2396,11.82421 -11.8356,10.21752 -13.6968,14.72814 -13.6968,33.19454 l 0,21.37033 38.625,-2.87797 c 86.1224,-6.41705 192.6432,-35.73941 239.0413,-65.80171 26.8961,-17.42658 26.9194,-17.43327 26.9194,-7.76815 0,14.05457 -40.9733,47.03579 -76.5204,61.59465 -42.0435,17.21951 -112.9619,34.11879 -117.0317,27.8877 -1.8603,-2.84826 -11.4991,-3.1282 -27.1622,-0.78884 -13.3532,1.99433 -65.9935,4.81727 -116.97854,6.27322 -50.985,1.4559 -95.67964,3.82579 -99.32143,5.26645 -3.64178,1.44065 -27.47892,2.80444 -52.97142,3.03066 -40.08101,0.35568 -49.63374,1.92675 -70.62857,11.61575 -13.35321,6.16243 -32.86127,19.78732 -43.35124,30.27753 -20.95616,20.95659 -53.76304,66.92269 -53.76304,75.32799 0,7.16893 13.92252,12.94679 62.6655,26.00642 46.4133,12.43538 118.97458,24.07475 150.32377,24.11301 l 20.96786,0.0256 0,-82.31968 0,-82.31964 59.59285,0.0447 59.59286,0.0447 0,82.27474 0,82.27473 20.9679,-0.0261 c 30.8432,-0.0391 103.249,-11.55478 150.6116,-23.95685 51.508,-13.48757 93.3439,-32.82944 113.1447,-52.30976 8.3341,-8.19927 15.9808,-14.90777 16.9926,-14.90777 4.4734,0 6.0007,24.47691 2.1543,34.52307 -19.9708,52.15927 -130.589,92.26465 -287.9586,104.4012 -63.341,4.88497 -94.42565,4.82235 -159.37678,-0.32094 z M 1032.3035,310.68598 c -3.0348,-1.20475 -8.0009,-1.20475 -11.0357,0 -3.0348,1.20475 -0.5518,2.19049 5.5179,2.19049 6.0696,0 8.5526,-0.98574 5.5178,-2.19049 z m 79.5491,87.1095 c 1.4668,-1.44308 5.2742,-1.59734 8.4607,-0.34265 3.5215,1.38633 2.4755,2.41541 -2.6669,2.62378 -4.6534,0.18848 -7.2606,-0.83787 -5.7938,-2.28095 z M 824.92406,211.05263 c 1.46682,-1.44309 5.27414,-1.59734 8.46073,-0.34265 3.5214,1.38633 2.47544,2.41541 -2.66698,2.62378 -4.65337,0.18848 -7.26058,-0.83787 -5.79375,-2.28095 z m 87.09019,-222.852228 c 0,-2.864375 1.98642,-4.000162 4.41428,-2.523938 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.614427 4.41428,-1.915114 0,-2.52954 1.48983,-4.140697 3.31072,-3.580381 5.29158,1.628397 62.90357,-24.28973 62.90357,-28.298621 0,-1.998018 1.9864,-2.424964 4.4143,-0.94874 2.4278,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4142,0.499081 4.4142,-2.171429 0,-2.67051 1.9865,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.670512 1.9864,-3.647652 4.4142,-2.171432 2.4279,1.476227 4.4143,0.49908 4.4143,-2.17142 0,-2.67051 1.9865,-3.64766 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4142,0.49908 4.4142,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17142 2.4279,1.47622 4.4143,0.61442 4.4143,-1.91512 0,-2.52954 1.4898,-4.14069 3.3107,-3.58038 5.2916,1.6284 62.9036,-24.28973 62.9036,-28.29862 0,-1.99802 1.9864,-2.42496 4.4143,-0.94874 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4142,-2.17143 2.4279,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17142 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64766 4.4143,-2.17143 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4142,0.57469 4.4142,-2.0034 0,-5.21686 31.3592,9.31834 48.4607,22.46186 36.424,27.99402 34.6702,64.156812 -4.4897,92.580637 -11.5652,8.394395 -15.6171,9.519369 -18.1635,5.042926 -2.6008,-4.572117 -12.3149,-2.791893 -47.0465,8.62183 -65.9593,21.676025 -135.5793,32.194555 -231.4788,34.9729843 -62.75671,1.8181805 -82.76785,1.138784 -82.76785,-2.8100453 z"
+ id="path3275"
+ style="fill:#acb0be"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.386 c -85.80316,-6.42799 -172.81235,-26.44518 -221.77975,-51.02227 -40.43119,-20.2927 -66.31338,-53.15857 -60.33075,-76.60956 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.55803,-32.27807 37.99879,-83.38902 49.44618,-99.3383 l 8.17892,-11.39544 -32.63958,-11.71168 c -50.86175,-18.25016 -86.1842,-42.99785 -95.73089,-67.07117 -2.26263,-5.70556 -3.54422,-10.93414 -2.848,-11.61914 0.69622,-0.68496 13.04456,6.37444 27.4407,15.68761 22.70567,14.68872 78.37709,37.82481 109.52018,45.51475 8.46965,2.09135 14.09923,-2.97959 37.27158,-33.57302 15.01228,-19.82006 44.79123,-53.88708 66.17544,-75.70451 l 38.88036,-39.66805 -37.52142,-4.1486 c -40.46872,-4.47449 -46.95083,-3.84034 -30.4373,2.97781 15.36777,6.34509 -15.37305,1.57012 -60.05556,-9.32841 -78.75442,-19.20902 -129.33251,-46.28 -146.11267,-78.20417 -9.20127,-17.505407 -5.30425,-19.595581 12.29061,-6.5922 48.81308,36.07507 139.58747,63.27052 250.01542,74.90308 25.21802,2.65653 28.94058,1.98516 38.625,-6.96568 10.56956,-9.76891 10.71735,-10.97297 10.71735,-87.319873 l 0,-77.414423 50.58577,0 50.58577,0 1.2821,34.480504 1.2821,34.480547 33.1071,-19.932845 c 79.7519,-48.01632 161.07,-78.666338 228.6758,-86.191293 l 27.3528,-3.044516 -0.1947,27.7037794 c -0.1872,26.6373916 -7.0308,60.8594956 -14.4416,72.2181076 -2.1502,3.295707 -14.2792,5.598637 -30.8706,5.861467 -43.0175,0.681394 -56.0972,7.301645 -15.45,7.819922 18.6727,0.237989 35.2488,2.499966 36.8359,5.026423 1.5871,2.526413 -2.7651,12.74937 -9.6716,22.71766 -6.9065,9.96824 -14.211,20.71868 -16.2323,23.88979 -4.5568,7.14922 38.2394,-13.61681 57.75,-28.02206 13.7672,-10.16476 14.3464,-10.2389 14.3464,-1.83677 0,21.01439 -54.7562,59.9065 -104.8393,74.46493 -12.4843,3.62906 -18.7607,7.9089 -18.7607,12.79314 0,4.23107 -21.5541,23.713 -50.8908,45.99828 -47.3439,35.96424 -52.6511,38.83479 -76.1464,41.1857 -22.7856,2.27992 -23.4208,2.59851 -6.4949,3.25715 10.3184,0.40171 18.7607,2.52498 18.7607,4.71873 -10e-5,5.38115 -27.9319,22.06849 -36.939,22.06849 -3.9622,0 -7.204,1.7949 -7.204,3.98865 0,2.19375 -6.9525,8.21599 -15.45,13.38278 -13.8852,8.44273 -15.45,11.33746 -15.45,28.58277 l 0,19.18865 22.6085,0 c 27.5992,0 107.1094,-11.18794 142.1874,-20.00732 44.9211,-11.29417 97.6436,-31.84009 121.699,-47.42613 26.8961,-17.42658 26.9194,-17.43327 26.9194,-7.76815 0,4.02196 -9.5227,16.34273 -21.1615,27.37954 -23.4748,22.26061 -66.7702,41.61877 -126.3196,56.47972 -39.8454,9.94371 -43.9046,10.3859 -40.126,4.37091 3.3216,-5.2876 3.0967,-5.28495 -35.3143,0.41257 -27.8643,4.13332 -143.2491,10.00156 -267.06429,13.58238 -38.81508,1.12258 -50.29686,3.16203 -70.62857,12.54542 -33.38092,15.40586 -70.0774,53.24504 -92.73995,95.6278 l -8.4315,15.76835 34.07422,11.07946 c 46.75633,15.20308 113.42039,28.04426 167.69685,32.3026 l 45.4211,3.56358 -1.27824,-83.06574 -1.2782,-83.06574 51.86785,0.39086 51.86783,0.39085 0,82.63406 0,82.63406 18.7607,-1.28214 c 126.6997,-8.65888 251.4183,-44.32494 277.8119,-79.44645 11.9832,-15.94588 15.1834,-16.11013 18.1328,-0.93063 11.8026,60.74233 -110.2162,113.69917 -292.6339,127.00477 -66.3626,4.84046 -86.34003,4.77749 -156.70718,-0.49409 z m 39.72857,-327.00801 0,-32.21362 -17.65714,-1.67131 c -15.88119,-1.50323 -19.79993,0.29879 -38.96137,17.91698 -22.02088,20.24727 -32.01196,34.08913 -27.68106,38.34995 2.49253,2.45223 46.04621,8.11393 71.05671,9.23695 l 13.24286,0.59467 0,-32.21362 z m 118.08211,16.30799 c -3.0348,-1.20475 -8.0009,-1.20475 -11.0357,0 -3.0348,1.20475 -0.5518,2.19049 5.5179,2.19049 6.0696,0 8.5526,-0.98574 5.5178,-2.19049 z m -9.1044,-30.49046 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4608,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28096 5.1424,-0.20846 6.1884,-1.23746 2.667,-2.62378 z m 76.4223,-153.78022 c 16.995,-2.35044 38.3491,-6.22787 47.4535,-8.61645 16.0286,-4.20514 16.5536,-4.96397 16.5536,-23.925707 0,-15.302056 2.4088,-21.77908 11.0192,-29.629793 10.4114,-9.492877 47.7633,-19.938317 71.2972,-19.938317 7.2648,0 11.0304,-3.194388 13.0368,-11.059389 1.5518,-6.082649 1.9966,-11.870853 0.9885,-12.862674 -4.647,-4.571813 -80.7018,20.47618 -118.4131,38.998291 -48.4697,23.806196 -112.1262,61.240929 -119.9901,70.563049 -5.8305,6.91157 6.6401,6.34773 78.0544,-3.52901 z M 940.7071,-17.573731 c 9.71143,-5.159357 19.14697,-8.981549 20.96786,-8.493803 1.82089,0.48779 3.31071,-1.182777 3.31071,-3.712317 0,-2.529541 1.98643,-3.391337 4.41429,-1.915113 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98642,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41433,0.499081 4.41433,-2.171429 0,-2.67051 1.9864,-3.647652 4.4142,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9865,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4142,0.499081 4.4142,-2.171429 0,-2.67051 1.9865,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4143,0.288799 4.4143,-2.638938 0,-2.92765 1.3245,-4.019922 2.9433,-2.427266 1.6188,1.592613 15.3344,-3.805168 30.4791,-11.995014 15.1447,-8.189888 28.7184,-13.727208 30.1638,-12.305228 1.4454,1.42203 2.628,0.19022 2.628,-2.73747 0,-2.92765 1.9865,-4.11516 4.4143,-2.63894 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17142 2.4278,1.47622 4.4142,0.49908 4.4142,-2.17143 0,-2.67051 1.9865,-3.64766 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4279,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49909 4.4143,-2.17142 0,-2.67051 1.9864,-3.64766 4.4142,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17143 2.4278,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.6705 1.9864,-3.64765 4.4143,-2.17142 2.4278,1.47622 4.4142,0.2888 4.4142,-2.63894 0,-2.92765 1.2392,-4.10383 2.7538,-2.61379 1.5145,1.49003 10.3743,-1.11568 19.6884,-5.79051 22.0949,-11.08957 25.7186,-10.80038 47.8426,3.81781 22.5271,14.88458 35.5898,33.26798 35.629,50.14141 0.057,24.361082 -28.9455,55.444646 -45.7963,49.083012 -3.6808,-1.389627 -22.1893,3.027102 -41.1299,9.814901 -67.6199,24.233142 -169.0646,39.4742246 -260.49572,39.1370017 l -52.04182,-0.1919543 17.65714,-9.3806144 z"
+ id="path3273"
+ style="fill:#a9adbc"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.386 c -85.80316,-6.42799 -172.81235,-26.44518 -221.77975,-51.02227 -40.43119,-20.2927 -66.31338,-53.15857 -60.33075,-76.60956 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.55803,-32.27807 37.99879,-83.38902 49.44618,-99.3383 l 8.17892,-11.39544 -32.63958,-11.71168 c -50.86175,-18.25016 -86.1842,-42.99785 -95.73089,-67.07117 -2.26263,-5.70556 -3.54422,-10.93414 -2.848,-11.61914 0.69622,-0.68496 13.04456,6.37444 27.4407,15.68761 22.70567,14.68872 78.37709,37.82481 109.52018,45.51475 8.46965,2.09135 14.09923,-2.97959 37.27158,-33.57302 15.01228,-19.82006 44.79123,-53.88708 66.17544,-75.70451 l 38.88036,-39.66805 -37.52142,-4.07772 c -23.61312,-2.5662 -38.56316,-2.55061 -40.33174,0.0434 -5.92958,8.69248 -114.84232,-21.58809 -149.48254,-41.56006 -20.88257,-12.03992 -39.21351,-28.95682 -46.7441,-43.13825 -9.10089,-17.13865 -7.83554,-18.395604 8.1191,-8.06538 62.08728,40.20005 143.8994,64.79195 254.21994,76.416 27.39334,2.88635 28.30749,2.6441 43.03928,-11.40343 l 15.05148,-14.35375 0,-72.976675 0,-72.976631 41.93571,0 41.93576,0 0,36.914285 c 0,20.302856 0.6947,36.914284 1.5438,36.914284 0.8491,0 20.2168,-11.090658 43.0393,-24.645931 83.5992,-49.653143 160.3283,-78.449455 230.7197,-86.588708 l 24.8686,-2.875492 0,24.1523214 c 0,26.2343736 -7.8513,67.1562036 -14.5514,75.8435246 -2.4736,3.207243 -15.2549,5.605716 -31.2355,5.861467 -42.8206,0.685303 -55.6597,7.303643 -15.1701,7.819922 18.6726,0.237989 35.2487,2.499966 36.8359,5.026423 1.5871,2.526413 -2.7652,12.74937 -9.6717,22.71766 -6.9065,9.96824 -14.211,20.71868 -16.2322,23.88979 -3.9635,6.21828 35.9094,-12.37297 55.1207,-25.70081 14.1192,-9.79518 16.9757,-10.09227 16.9757,-1.76558 0,18.72679 -57.6413,58.35271 -104.8393,72.07249 -12.5693,3.65373 -18.7607,7.89735 -18.7607,12.85867 0,4.34703 -21.7465,23.7917 -52.6577,47.08404 -50.2579,37.87041 -53.7283,39.71044 -76.1465,40.37381 -17.688,0.52336 -19.6732,1.13609 -8.0386,2.48103 22.7583,2.6309 24.5731,7.54241 6.6343,17.95528 -10.8353,6.28954 -21.2553,8.76888 -32.2846,7.68173 -8.957,-0.88286 -11.816,-0.75717 -6.3533,0.27925 14.1326,2.68154 12.516,6.10532 -9.8447,20.85045 -17.4819,11.52785 -19.7688,14.90446 -19.8547,29.31428 l -0.097,16.2858 27.0227,0 c 31.3712,0 109.3556,-10.64286 146.6017,-20.00732 44.9211,-11.29417 97.6436,-31.84009 121.699,-47.42613 26.8961,-17.42658 26.9194,-17.43327 26.9194,-7.76815 0,4.02196 -9.5227,16.34273 -21.1615,27.37954 -26.3538,24.99071 -79.6911,46.96561 -152.4691,62.81708 -8.9744,1.95468 -11.6033,1.28266 -9.3036,-2.37819 2.365,-3.76474 -1.8786,-4.35467 -16.1837,-2.24973 -44.2119,6.50555 -116.0214,11.06364 -176.46421,11.20092 -35.40628,0.0803 -65.81762,1.56556 -67.58081,3.30022 -1.7632,1.73467 -23.44838,3.21641 -48.1893,3.29276 -39.29716,0.12116 -48.16198,1.60008 -70.12817,11.69883 -29.67366,13.64217 -61.84449,45.21487 -84.554,82.98196 l -16.05939,26.70762 20.88641,8.0715 c 44.68603,17.26881 115.7129,30.74357 209.71096,39.78496 l 25.38214,2.44142 0,-82.25419 0,-82.25423 41.9456,-1.38372 41.94557,-1.38372 -0.01,83.34733 -0.01,83.34733 32.0035,-2.2546 c 121.8909,-8.58682 247.6608,-45.66296 274.0591,-80.7909 11.3083,-15.04778 14.5555,-15.05712 17.4714,-0.0504 11.8026,60.74233 -110.2163,113.69917 -292.634,127.00477 -66.36258,4.84046 -86.34001,4.77749 -156.70716,-0.49409 z m 44.05219,-303.88721 c -4.46673,-20.00767 -3.63133,-42.50805 1.81806,-48.96792 7.25722,-8.60294 1.36852,-11.42584 -23.83484,-11.42584 -22.36723,0 -23.42247,0.59353 -50.64819,28.48753 -16.45959,16.86358 -26.18537,30.08102 -23.8356,32.39276 3.5931,3.53496 55.77552,10.95977 87.00985,12.38023 11.73975,0.53391 12.30985,-0.23886 9.49072,-12.86676 z m 109.29279,-37.40815 c -5.491,-1.03877 -13.4367,-0.99934 -17.6572,0.0877 -4.2204,1.08698 0.2724,1.93692 9.9836,1.88871 9.7115,-0.0482 13.1645,-0.93758 7.6736,-1.97634 z m 76.1979,-151.55112 c 21.8507,-3.45274 46.0422,-8.28908 53.7588,-10.7474 12.8096,-4.08076 13.8241,-5.71997 11.6599,-18.839875 -4.7057,-28.52914 15.1931,-44.456698 61.8701,-49.522554 26.7561,-2.903851 27.2343,-3.189264 28.5944,-17.067515 0.9865,-10.066178 -0.4544,-14.114285 -5.024,-14.114285 -17.6179,0 -88.821,24.568715 -126.2566,43.564936 -49.2486,24.990623 -111.6859,64.744573 -114.1668,72.690263 -1.9582,6.27165 22.3962,4.65003 89.5642,-5.96357 z M 951.74282,-11.732327 c 0,-2.901376 1.98642,-4.067433 4.41428,-2.591209 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41428,-2.171428 2.42786,1.476223 4.41429,0.499081 4.41429,-2.171429 0,-2.67051 1.98643,-3.647652 4.41429,-2.171428 2.42785,1.476223 4.41428,0.499081 4.41428,-2.171429 0,-2.67051 1.98643,-3.647652 4.41433,-2.171428 2.4278,1.476223 4.4142,0.499081 4.4142,-2.171429 0,-2.67051 1.9865,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4143,0.879906 4.4143,-1.325136 0,-4.142435 39.3688,-20.804761 45.2464,-19.149872 1.8209,0.512674 3.3107,-1.137481 3.3107,-3.667021 0,-2.529541 1.9864,-3.391337 4.4143,-1.915113 2.4278,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4142,0.499081 4.4142,-2.171429 0,-2.67051 1.9865,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4143,-2.171428 2.4278,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.67051 1.9864,-3.647652 4.4142,-2.171428 2.4279,1.476223 4.4143,0.499081 4.4143,-2.171429 0,-2.670512 1.9864,-3.647652 4.4143,-2.171432 2.4279,1.476227 4.4143,0.49908 4.4143,-2.17142 0,-2.67051 1.9864,-3.64766 4.4143,-2.17143 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4142,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47623 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4143,-2.17142 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64766 4.4143,-2.17143 2.4278,1.47622 4.4142,0.8799 4.4142,-1.32514 0,-4.14243 39.3689,-20.80476 45.2465,-19.14987 1.8209,0.51268 3.3107,-1.13748 3.3107,-3.66702 0,-2.52954 1.9864,-3.39134 4.4143,-1.91511 2.4278,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9864,-3.64765 4.4142,-2.17143 2.4279,1.47622 4.4143,0.49908 4.4143,-2.17143 0,-2.67051 1.9865,-3.64765 4.4143,-2.17143 2.4279,1.47623 4.4143,0.78649 4.4143,-1.53272 0,-10.89041 25.1439,-0.22713 42.7894,18.14637 25.7969,26.86143 26.1503,43.595288 1.4638,69.325934 -12.6922,13.22908 -17.5773,16.186349 -17.6571,10.688987 -0.1391,-9.57448 1.8599,-9.903321 -32.2246,5.301238 -56.6957,25.291062 -145.2424,42.427021 -240.4679,46.5363623 -47.91912,2.0678948 -62.90358,1.4578971 -62.90358,-2.5606783 z"
+ id="path3271"
+ style="fill:#a6a9b9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.386 c -85.80316,-6.42799 -172.81235,-26.44518 -221.77975,-51.02227 -40.43119,-20.2927 -66.31338,-53.15857 -60.33075,-76.60956 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.7369,-32.73789 38.05026,-83.46255 49.85534,-99.90365 6.54656,-9.11748 7.20187,-12.36681 2.75668,-13.66893 -69.08736,-20.23723 -115.00856,-49.63594 -124.72961,-79.85176 -2.9604,-9.20182 -1.31625,-8.75472 22.17644,6.03019 39.46482,24.83684 118.49435,54.21913 125.28586,46.57987 1.75331,-1.97213 15.2529,-19.22 29.99913,-38.32857 14.74623,-19.10858 43.35777,-51.63822 63.5812,-72.28812 l 36.76985,-37.54526 -39.72857,-4.36075 c -25.67847,-2.81855 -40.67097,-2.86841 -42.39334,-0.14114 -3.28294,5.19866 -65.41256,-8.82247 -106.47883,-24.02964 -32.63446,-12.08482 -48.26209,-20.80445 -66.71835,-37.22627 -12.35532,-10.99342 -27.23243,-34.27713 -23.95902,-37.49753 0.924,-0.90905 9.14472,3.33748 18.26825,9.43681 49.98468,33.41585 138.39479,60.94001 229.28704,71.3825 l 47.73432,5.48412 23.99782,-23.31624 23.99782,-23.31623 0,-62.034068 0,-62.034065 14.34643,-0.0595 c 7.89054,-0.03474 18.81589,-1.237714 24.27857,-2.677979 9.84704,-2.596204 9.93214,-2.22046 9.93214,43.852824 l 0,46.471438 16.55358,-12.652525 C 1024.7594,73.6432 1088.7164,37.019887 1131.5469,17.394951 c 41.3966,-18.9679926 101.8547,-36.121322 144.1465,-40.897596 l 25.9862,-2.934816 -2.277,34.2853356 c -1.2524,18.8569454 -5.0006,42.1024784 -8.3292,51.6567644 -5.7577,16.526655 -7.0181,17.423716 -25.9163,18.446502 -31.7176,1.716644 -57.3849,5.724624 -57.3957,8.962441 0,1.645074 14.125,2.333417 31.401,1.529641 40.2282,-1.871641 44.4621,2.114407 27.8028,26.175097 -7.0641,10.20263 -14.4976,21.14476 -16.5188,24.31587 -3.9635,6.21828 35.9094,-12.37297 55.1207,-25.70081 14.1192,-9.79479 16.9757,-10.09188 16.9757,-1.76519 0,18.72679 -57.6413,58.35271 -104.8393,72.07249 -12.5693,3.65373 -18.7607,7.89735 -18.7607,12.85867 0,4.34703 -21.7465,23.7917 -52.6577,47.08404 -51.6932,38.95196 -53.1284,39.6915 -78.3536,40.37381 -16.5342,0.44718 -20.1873,1.28105 -10.2458,2.33876 27.4841,2.92413 29.2468,5.17616 12.7062,16.2339 -12.2329,8.17795 -19.7875,9.94154 -37.203,8.68493 -12.0193,-0.86722 -17.8804,-0.55692 -13.0247,0.68956 4.8558,1.24649 11.6732,3.20425 15.1499,4.3505 3.9573,1.30477 -4.7094,9.36429 -23.175,21.55152 -25.30872,16.70354 -29.49629,21.29615 -29.49629,32.34903 l 0,12.88169 34.21069,-2.35148 c 125.6232,-8.63477 225.6965,-33.91902 281.8696,-71.21638 22.83,-15.15844 23.8197,-15.43586 23.8197,-6.67671 0,12.14849 -31.8883,41.29032 -59.0341,53.9497 -45.788,21.35305 -82.0844,31.15535 -121.9516,32.93462 -20.6368,0.92104 -58.3789,3.67154 -83.8714,6.11227 -25.4925,2.44073 -75.31878,4.5035 -110.72501,4.58393 -35.40628,0.0803 -65.7823,1.53081 -67.50237,3.22301 -1.72003,1.69219 -24.39842,3.25445 -50.3964,3.47168 -42.52445,0.35524 -49.62835,1.596 -70.77331,12.36055 -28.54878,14.53372 -60.21761,46.35887 -81.7275,82.1309 l -16.00668,26.61993 14.26918,5.97868 c 42.56033,17.83233 140.61353,36.78448 218.42992,42.21904 l 40.83214,2.85169 0,-82.84034 0,-82.8403 24.27857,0 24.27857,0 0,82.77633 0,82.77629 43.03929,-2.90294 c 54.0329,-3.64444 134.576,-16.72821 175.2466,-28.46782 45.4948,-13.13206 93.5636,-36.90847 104.7724,-51.82379 11.9832,-15.94589 15.1834,-16.11013 18.1328,-0.93063 11.8026,60.74232 -110.2162,113.69916 -292.6339,127.00477 -66.3626,4.84046 -86.34003,4.77749 -156.70718,-0.49409 z m 48.6117,-300.22383 c -7.448,-22.86984 -7.50693,-38.2769 -0.18849,-49.26546 4.10348,-6.16134 6.71236,-11.93877 5.79759,-12.83879 -0.91482,-0.89997 -14.41366,-2.01274 -29.9975,-2.47278 l -28.33424,-0.83643 -27.57886,29.93475 c -15.16837,16.46412 -25.97631,31.51138 -24.01764,33.43835 4.28914,4.21973 51.02225,10.49885 83.81161,11.26111 22.66559,0.52688 23.55224,0.12812 20.50757,-9.22075 z M 1032.3642,280.1051 c -5.4293,-1.03082 -15.3614,-1.05753 -22.0714,-0.0591 -6.71,0.9982 -2.2678,1.84163 9.8714,1.87424 12.1393,0.0304 17.6293,-0.78414 12.2,-1.81496 z m 61.4634,-99.79529 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1883,-1.23745 2.6669,-2.62378 z m 31.7894,-53.68814 c 24.0091,-4.53615 44.6851,-9.26322 45.9469,-10.50454 1.2617,-1.24137 2.4755,-11.33299 2.6972,-22.425909 0.5202,-26.028696 12.1066,-35.069439 53.6683,-41.876954 16.8335,-2.757193 32.0657,-7.040423 33.8494,-9.518283 4.4715,-6.211719 4.202,-23.0346 -0.4141,-25.841302 -6.0311,-3.667109 -77.264,17.049883 -109.238,31.770214 -52.978,24.39031 -142.9982,80.151294 -142.9982,88.577124 0,5.60906 61.2502,0.25623 116.4885,-10.18035 z m -2.2318,-208.198039 c 83.4778,-40.639501 155.2547,-73.890021 159.5041,-73.890021 13.3398,0 39.6535,32.76134 39.6535,49.36969 0,16.60187 -16.7643,41.59271 -22.15,33.019433 -2.3785,-3.786059 -8.5137,-2.183502 -22.5619,5.893257 -49.4523,28.431555 -159.3978,53.140501 -254.8438,57.2730768 l -51.38,2.2246285 151.7781,-73.8900643 z"
+ id="path3269"
+ style="fill:#a4a8b6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.386 c -85.80316,-6.42799 -172.81235,-26.44518 -221.77975,-51.02227 -40.43119,-20.2927 -66.31338,-53.15857 -60.33075,-76.60956 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.7369,-32.73789 38.05026,-83.46255 49.85534,-99.90365 8.2266,-11.45733 8.24973,-12.06454 0.54954,-14.42649 -34.86593,-10.69468 -71.29319,-26.81832 -86.68827,-38.37053 -18.33248,-13.7564 -40.31585,-41.13494 -36.24482,-45.14009 1.18294,-1.1638 9.68468,2.78902 18.89279,8.78403 37.50006,24.41481 121.83799,56.17386 128.73942,48.47927 1.88569,-2.10238 15.49361,-19.45682 30.23984,-38.56539 14.74623,-19.10858 43.35777,-51.64609 63.5812,-72.30558 l 36.76985,-37.56267 -26.48571,-2.7274 c -14.56714,-1.50007 -35.21086,-3.91474 -45.87498,-5.36599 -13.74718,-1.87077 -18.50022,-1.22343 -16.33413,2.22471 3.81597,6.07444 -12.52068,3.23721 -62.99605,-10.94078 -43.55712,-12.2347 -86.1209,-32.11612 -103.62822,-48.40444 -12.496,-11.62592 -27.14821,-34.86885 -23.95902,-38.00639 0.924,-0.90904 9.14472,3.33749 18.26825,9.43681 49.89807,33.35797 138.39386,60.94058 228.86504,71.3333 l 47.31235,5.43495 28.51872,-25.58685 28.51871,-25.58681 0.31518,-59.714285 0.31558,-59.714284 15.44999,0 15.45,0 0,45.599999 c 0,25.079999 1.15553,45.6 2.56779,45.6 1.41231,0 10.84785,-6.53118 20.96786,-14.513787 71.29571,-56.237305 195.77701,-109.288561 277.88201,-118.427365 l 26.4477,-2.943806 -2.277,34.2853365 c -1.2524,18.8569455 -5.0006,42.1024785 -8.3292,51.6567645 -5.7577,16.526655 -7.0181,17.423715 -25.9163,18.446502 -31.719,1.716688 -57.3851,5.72471 -57.3931,8.962441 0,1.645074 15.3908,2.991055 34.2108,2.991055 31.7531,0 34.1147,0.625676 32.7841,8.685714 -0.7886,4.777145 -7.2445,15.899675 -14.3464,24.716765 -7.1019,8.81708 -12.9126,17.15572 -12.9126,18.53032 0,3.14805 42.4165,-17.74639 58.0672,-28.60406 14.1192,-9.79518 16.9757,-10.09227 16.9757,-1.76558 0,19.40323 -71.5173,66.69038 -112.2262,74.20383 -11.0629,2.04184 -14.2633,4.57403 -12.7895,10.11903 1.4224,5.35101 -12.8618,18.57553 -51.6659,47.83331 l -53.6318,40.4376 -49.2755,-2.30084 -49.27537,-2.3008 44.14287,5.54982 c 24.2786,3.05238 45.08,5.83958 46.2254,6.19378 1.1454,0.35395 -4.6875,5.20778 -12.962,10.78584 -11.7385,7.9132 -19.9564,9.89337 -37.3968,9.01099 -12.2938,-0.62203 -16.8897,-0.17024 -10.213,1.00372 6.6766,1.17405 12.1392,3.94353 12.1392,6.15439 0,2.21091 -13.905,13.11639 -30.9,24.23445 -23.68256,15.49301 -30.89996,22.60175 -30.89996,30.43461 0,11.86938 -4.32132,11.51148 68.88396,5.70469 114.3684,-9.07188 204.5923,-34.50491 267.7053,-75.46287 10.7973,-7.00698 12.1393,-7.10392 12.1393,-0.87669 0,12.59628 -31.4196,41.56939 -59.2504,54.63687 -33.8625,15.89959 -109.7533,37.69648 -111.6596,32.07018 -0.8709,-2.57019 -16.0834,-2.03055 -39.9517,1.4172 -21.2,3.06232 -80.2604,6.74507 -131.24543,8.1839 -50.985,1.43883 -95.67964,3.66767 -99.32142,4.95294 -3.64179,1.28531 -27.47893,2.78073 -52.97143,3.32324 -36.83637,0.78389 -50.60153,2.92574 -67.06332,10.43515 -31.41378,14.33008 -71.23757,56.55477 -93.32815,98.95482 l -7.133,13.69085 42.93012,13.44688 c 50.73281,15.8909 125.92941,28.87674 191.91221,33.14164 l 47.45357,3.06723 0,-83.03303 0,-83.03304 15.44999,0 15.45,0 0,83.03304 0,83.03303 47.45356,-3.06723 c 129.027,-8.33989 253.5943,-43.59234 285.3869,-80.76428 l 11.4469,-13.38382 4.3988,11.38233 c 8.7739,22.70393 -16.2136,55.46653 -58.485,76.68278 -51.3316,25.76369 -135.7026,44.43294 -232.8154,51.51636 -66.3626,4.84046 -86.34003,4.77749 -156.70718,-0.49409 z m 55.17857,-292.1274 c 0,-0.92125 -2.96742,-7.27268 -6.59424,-14.11429 -8.34984,-15.7511 -8.3298,-26.53659 0.079,-42.3997 3.67021,-6.92395 6.14102,-13.0307 5.49066,-13.57056 -0.6504,-0.53986 -12.60238,-2.34375 -26.56,-4.00863 l -25.37747,-3.0271 -26.90771,25.25037 c -26.86442,25.20967 -37.64062,39.26928 -33.34088,43.49949 5.87201,5.77695 113.21066,13.71322 113.21066,8.37042 z M 1093.8276,180.30981 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1883,-1.23745 2.6669,-2.62378 z m 2.2917,-47.2092 c 25.4034,-3.71102 53.4683,-9.13981 62.3665,-12.06398 14.245,-4.68134 16.0123,-6.81933 14.788,-17.88997 -2.5338,-22.913605 2.8489,-32.872862 22.5141,-41.655769 10.2327,-4.570162 24.9315,-8.352313 32.664,-8.404775 26.4598,-0.17936 36.7052,-6.843257 36.7052,-23.873597 0,-18.299062 -0.074,-18.306879 -48.5882,-5.106592 -54.4725,14.821607 -158.9961,68.64537 -198.6118,102.273933 l -15.45,13.11504 23.712,0.17632 c 13.0417,0.0968 44.4967,-2.85977 69.9002,-6.57079 z m -75.9551,-157.621131 c 19.4229,-9.302965 88.3713,-42.406913 153.2188,-73.564306 l 117.9044,-56.649793 15.6277,17.01805 c 16.4764,17.94226 19.1627,30.66561 10.4478,49.48334 -3.6037,7.781314 -6.2082,9.547901 -8.5591,5.805618 -2.5976,-4.134921 -5.2283,-3.773986 -11.3737,1.560519 -38.4801,33.402867 -155.2137,64.378425 -266.2301,70.644864 l -46.35004,2.6162671 35.31424,-16.9145591 z"
+ id="path3267"
+ style="fill:#a2a6b4"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 874.49282,621.386 c -85.80316,-6.42799 -172.81235,-26.44518 -221.77975,-51.02227 -40.43119,-20.2927 -66.31338,-53.15857 -60.33075,-76.60956 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.88703,7.40179 37.2533,32.74592 44.40295,32.74592 2.68291,0 9.63007,-12.21428 15.43816,-27.14285 12.7369,-32.73789 38.05026,-83.46255 49.85534,-99.90365 8.2266,-11.45733 8.24973,-12.06454 0.54954,-14.42649 -34.86593,-10.69468 -71.29319,-26.81832 -86.68827,-38.37053 -18.33248,-13.7564 -40.31585,-41.13494 -36.24482,-45.14009 1.18294,-1.1638 9.68468,2.78902 18.89279,8.78403 37.50006,24.41481 121.83799,56.17386 128.73942,48.47927 1.88569,-2.10238 15.51868,-19.45682 30.29555,-38.56539 14.77686,-19.10858 43.41719,-51.61929 63.64512,-72.24599 l 36.77806,-37.50314 -42.80877,-4.78908 c -23.54483,-2.63399 -48.40396,-3.72161 -55.24249,-2.41693 -15.63963,2.98376 -81.52894,-15.63459 -117.77459,-33.27957 -30.07589,-14.64147 -48.83347,-30.251 -58.70726,-48.85458 -8.43327,-15.88948 -5.69871,-18.219635 8.49295,-7.23711 40.49238,31.33592 148.14952,65.13299 236.02165,74.09482 l 47.54782,4.84928 32.00357,-30.26541 32.00357,-30.26542 0,-54.786309 c 0,-45.277367 1.12493,-54.78627 6.48131,-54.78627 5.25049,0 6.71753,9.470859 7.725,49.869157 l 1.24369,49.869162 24.27857,-18.721453 C 1071.4208,37.614949 1191.0342,-14.121507 1275.3029,-23.501166 l 26.5186,-2.95171 -2.5059,34.2932405 c -1.3783,18.8612885 -5.1583,42.1103825 -8.4001,51.6646685 -5.6166,16.553407 -6.8295,17.422065 -25.7584,18.446502 -31.719,1.716688 -57.3851,5.72471 -57.3931,8.962441 0,1.645074 15.3908,2.991055 34.2108,2.991055 31.7531,0 34.1147,0.625676 32.7841,8.685714 -0.7886,4.777145 -7.2445,15.899675 -14.3464,24.716765 -7.1019,8.81708 -12.9126,17.08089 -12.9126,18.36403 0,3.7022 22.2412,-6.71545 47.5393,-22.26722 26.5408,-16.31568 27.5036,-16.59349 27.5036,-7.93613 0,19.40323 -71.5173,66.69038 -112.2262,74.20383 -10.8779,2.0077 -14.2596,4.61846 -12.8691,9.93528 1.3306,5.08744 -13.8298,19.16903 -51.4627,47.80066 l -53.3489,40.58864 -51.6858,-2.28308 -51.68586,-2.28308 41.93576,4.21083 c 55.9454,5.61762 58.2517,6.64366 40.2039,17.88575 -10.2956,6.41323 -21.6225,9.12674 -37.4168,8.9637 -12.4583,-0.12854 -17.1888,0.63966 -10.5121,1.70727 6.6766,1.0676 12.1392,3.85337 12.1392,6.19057 0,2.33724 -15.8914,14.20071 -35.3142,26.36327 -23.46334,14.69271 -35.31433,24.82907 -35.31433,30.20492 0,9.63085 0.8724,9.67097 77.71253,3.57586 113.5273,-9.00517 204.1654,-34.23869 265.4982,-73.91425 12.1341,-7.8495 14.3464,-8.21222 14.3464,-2.35231 0,12.49531 -31.4542,41.51263 -59.038,54.46416 -33.729,15.83688 -105.0075,36.47127 -108.3913,31.37814 -1.4073,-2.11827 -20.0683,-1.26408 -42.2493,1.934 -21.8507,3.15049 -81.4435,6.93516 -132.42853,8.41038 -50.985,1.47527 -95.67964,3.73972 -99.32142,5.03211 -3.64179,1.29239 -27.47893,2.72997 -52.97143,3.19461 -37.34039,0.68057 -50.37631,2.70473 -67.06332,10.41334 -31.52145,14.56139 -71.29398,56.78282 -93.32815,99.07456 l -7.133,13.69085 42.93012,13.44688 c 52.67849,16.50034 127.49586,29.17892 196.32649,33.26954 l 51.86786,3.08252 0,-83.10461 c 0,-70.97557 0.96637,-83.10465 6.62142,-83.10465 5.65506,0 6.62143,12.12908 6.62143,83.10465 l 0,83.10461 51.86783,-3.08252 c 131.0845,-7.79039 257.4451,-43.06147 289.8012,-80.89218 l 11.4469,-13.38382 4.3988,11.38233 c 8.7739,22.70393 -16.2136,55.46653 -58.485,76.68278 -51.3316,25.76369 -135.7026,44.43294 -232.8154,51.51636 -66.3626,4.84046 -86.34003,4.77749 -156.70718,-0.49409 z m 77.25,-242.2744 c 0,-2.16496 -3.97286,-2.91419 -8.82857,-1.66492 -4.85572,1.24927 -8.82858,3.02059 -8.82858,3.93628 0,0.91569 3.97286,1.66492 8.82858,1.66492 4.85571,0 8.82857,-1.77132 8.82857,-3.93628 z m -25.81638,-54.69229 c -6.14958,-34.734 -5.72365,-46.39175 2.00201,-54.79035 11.01559,-11.97513 2.60505,-16.71384 -29.81823,-16.8003 l -24.93979,-0.0665 -28.03164,28.48562 c -29.15428,29.62637 -34.93916,39.58306 -25.04723,43.11011 9.38676,3.34687 75.05107,10.03513 91.92269,9.36281 12.8657,-0.51272 15.19278,-2.06855 13.91219,-9.3014 z m 75.20116,-18.16665 c -3.18655,-1.25452 -6.99387,-1.10026 -8.46069,0.34265 -1.46683,1.44309 1.14038,2.46952 5.79375,2.28096 5.14244,-0.20846 6.18834,-1.23746 2.66694,-2.62378 z m 92.7,-125.94285 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1883,-1.23745 2.6669,-2.62378 z m -38.3491,-40.55199 c 45.9239,-5.0418 107.0227,-16.48948 117.7144,-22.05538 6.1904,-3.22257 6.5676,-5.88804 2.3785,-16.80677 -4.2317,-11.029507 -3.6431,-14.736657 3.9221,-24.705468 10.4763,-13.804943 32.1204,-22.40823 57.5338,-22.86905 18.9505,-0.34352 25.2656,-4.778793 29.1583,-20.478221 4.976,-20.068299 -2.1425,-21.874971 -43.9075,-11.143554 -47.0212,12.081915 -88.544,30.865249 -150.7485,68.192843 -51.2123,30.7314 -66.8154,42.30629 -66.8154,49.56607 0,5.75003 1.0608,5.75633 50.7643,0.29966 z m 46.35,-195.720409 c 54.6268,-26.303034 120.7007,-57.814851 146.8309,-70.026271 l 47.5094,-22.20259 10.7509,9.9365 c 16.8526,15.576 19.6081,44.659594 4.5874,48.419379 -2.4279,0.607696 -16.1608,8.574146 -30.5175,17.703266 -28.4324,18.079357 -86.0833,37.834145 -141.6397,48.534554 -33.5779,6.467296 -112.0552,15.9761114 -128.9438,15.6236886 -4.3444,-0.090766 36.7956,-21.6854916 91.4224,-47.9885266 z"
+ id="path3265"
+ style="fill:#a0a4b2"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 881.11425,621.68409 c -89.74371,-6.40245 -176.85341,-25.75344 -226.06916,-50.22014 -42.55014,-21.15297 -68.78966,-53.69335 -62.66277,-77.70978 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.89869,7.43167 37.26615,32.74592 44.45689,32.74592 2.71258,0 9.73306,-12.21428 15.60114,-27.14285 13.27879,-33.78183 38.25526,-84.05018 49.63842,-99.90365 6.56078,-9.13733 7.21153,-12.38096 2.75668,-13.74076 -34.6434,-10.57472 -73.94948,-27.8411 -88.87515,-39.04106 -17.93162,-13.45561 -40.36714,-41.11961 -36.46301,-44.96056 1.07408,-1.0567 13.53327,5.11089 27.6871,13.7058 25.78627,15.65864 80.22589,38.14822 105.06251,43.40238 12.51812,2.64823 14.86281,0.71787 42.84409,-35.27316 16.28068,-20.94109 46.0771,-54.90345 66.21428,-75.47195 l 36.61305,-37.39726 -49.62045,-6.15018 c -28.3828,-3.51789 -50.18804,-4.47483 -50.94664,-2.23587 -2.1283,6.2815 -73.03258,-13.25058 -109.11147,-30.05705 -35.45933,-16.51788 -54.23713,-31.345 -64.72671,-51.10882 -8.37995,-15.78894 -5.71204,-18.229928 8.17698,-7.48162 45.21067,34.98723 156.07836,68.42046 252.98544,76.29015 l 35.31428,2.8678 22.07143,-22.72022 C 1006.9524,70.143556 1159.5152,-9.5573378 1275.7606,-23.272601 l 26.0536,-3.073918 -2.5023,34.2400401 c -1.3762,18.8320609 -5.1574,42.0572259 -8.4027,51.6115119 l -5.9004,17.371428 -36.7123,2.684363 c -49.3698,3.609913 -55.0857,10.704447 -8.4613,10.502027 33.9357,-0.147223 36.2621,0.420823 34.9237,8.527895 -0.7886,4.777144 -7.2445,15.899674 -14.3464,24.716764 -7.1019,8.81708 -12.9126,17.02309 -12.9126,18.23552 0,3.94775 32.637,-11.01609 52.0892,-23.88254 21.8705,-14.46601 22.9537,-14.75824 22.9537,-6.1923 0,19.40323 -71.5173,66.69038 -112.2262,74.20383 -10.8748,2.0071 -14.2606,4.61998 -12.8743,9.93528 1.3275,5.08949 -13.8174,19.10858 -51.46,47.63472 l -53.341,40.42275 -53.8957,-2.15167 -53.89564,-2.15167 48.55714,5.58639 c 26.7064,3.07248 49.5504,5.91488 50.7643,6.31638 1.2139,0.40128 -4.4844,4.94434 -12.6629,10.09519 -10.4813,6.60114 -21.5553,9.29615 -37.5214,9.13133 -12.4583,-0.12854 -17.1888,0.63966 -10.5121,1.70727 6.6766,1.0676 12.1392,3.80499 12.1392,6.08308 0,4.44353 -46.3692,35.05433 -81.98935,54.12559 l -21.74632,11.64312 64.42376,-2.78569 c 136.85841,-5.91775 245.06171,-32.44887 311.89411,-76.47545 11.8575,-7.81124 14.3464,-8.27293 14.3464,-2.66122 0,10.14105 -24.2644,35.38582 -46.1273,47.99101 -10.6011,6.11218 -36.8388,16.78062 -58.3059,23.70774 -50.5098,16.29866 -64.8407,19.51515 -61.2119,13.73863 3.4865,-5.54996 4.4706,-5.59625 -44.0335,2.07206 -21.8507,3.45453 -81.4435,7.45482 -132.42853,8.88957 -50.985,1.43471 -95.67964,3.61682 -99.32142,4.8491 -3.64179,1.23229 -27.47893,2.68467 -52.97143,3.22757 -49.35789,1.05115 -67.7429,6.29819 -95.77631,27.33429 -16.47433,12.36225 -50.84264,55.68529 -63.42626,79.95213 l -8.28641,15.97993 42.91233,13.44123 c 148.65058,46.5612 371.92483,45.70002 516.41533,-1.99185 43.0587,-14.21235 73.7875,-30.57046 87.463,-46.5599 l 10.6571,-12.46031 4.3988,11.38233 c 5.967,15.44046 -2.9027,35.38499 -23.7416,53.38569 -45.5855,39.37695 -139.7847,65.44351 -270.4941,74.85036 -63.26318,4.55292 -80.44892,4.52569 -147.15045,-0.23278 z m 50.75951,-290.7429 c 1.49786,-2.38435 -0.37124,-8.68206 -4.15379,-13.99485 -9.43766,-13.25614 -8.62477,-34.76991 1.80685,-47.81694 4.77617,-5.97369 7.42474,-12.10011 5.8857,-13.61425 -1.53904,-1.51414 -16.60019,-2.75298 -33.4692,-2.75298 l -30.67094,0 -30.26973,31.48571 c -16.64834,17.31714 -30.32535,32.81975 -30.39337,34.45019 -0.42024,10.0839 115.28251,21.76553 121.26448,12.24312 z m 69.52974,-24.59807 c -3.03479,-1.20475 -8.00086,-1.20475 -11.03569,0 -3.03482,1.20475 -0.55178,2.1905 5.51786,2.1905 6.06963,0 8.55263,-0.98575 5.51783,-2.1905 z m 92.4241,-126.03331 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1883,-1.23745 2.6669,-2.62378 z m 16.8295,-47.42226 c 20.6368,-3.59728 44.871,-8.43752 53.8539,-10.75609 l 16.3325,-4.21552 -2.5668,-21.321823 c -2.4803,-20.603252 -2.0675,-21.62061 12.2488,-30.18594 8.1486,-4.875247 26.0445,-10.113949 39.7688,-11.641549 30.9127,-3.440802 34.0379,-5.318306 37.5416,-22.553455 2.2056,-10.849195 1.1278,-14.467273 -4.9984,-16.780105 -13.4564,-5.080144 -73.0074,12.43208 -121.2804,35.665062 -70.0858,33.731014 -143.70169,81.34497 -138.5202,89.59323 3.3228,5.28929 53.297,1.66557 107.6202,-7.80381 z"
+ id="path3263"
+ style="fill:#9ea2b0"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 903.18568,623.33816 c -98.35651,-5.2787 -197.06903,-26.1708 -250.60032,-53.03853 -40.30498,-20.22934 -66.17777,-53.12544 -60.20304,-76.54546 3.08696,-12.10046 8.42149,-14.06613 12.16021,-4.48079 2.89869,7.43167 37.26615,32.74592 44.45689,32.74592 2.71258,0 9.73306,-12.21428 15.60114,-27.14285 13.27879,-33.78183 38.25526,-84.05018 49.63842,-99.90365 8.23287,-11.46601 8.2556,-12.07562 0.54954,-14.7374 -72.44134,-25.02232 -104.30988,-44.21788 -118.62479,-71.45198 -8.46947,-16.11313 -5.13611,-15.73296 24.40866,2.78394 24.15996,15.14202 78.83075,37.70199 103.83472,42.84754 12.49318,2.57097 14.91852,0.56991 42.84409,-35.34946 16.28068,-20.94109 46.0771,-54.90345 66.21428,-75.47195 l 36.61305,-37.39726 -50.76428,-6.38226 c -27.92035,-3.51024 -51.87545,-4.75747 -53.23359,-2.77157 -7.55814,11.05179 -126.49179,-33.26229 -151.32122,-56.38153 -12.25896,-11.41459 -27.16613,-34.86059 -24.08933,-37.88765 0.85235,-0.83852 11.42757,5.15701 23.50051,13.32345 41.00734,27.73844 118.24577,52.46176 202.93649,64.95807 70.71226,10.43376 71.6578,10.2659 99.47221,-17.65919 C 1011.1501,68.448582 1161.5059,-9.8037081 1275.0892,-23.190044 l 25.3822,-2.991446 -0.155,21.1289978 c -0.1549,21.1055032 -9.2015,70.0578402 -14.5082,78.5052612 -1.5439,2.457666 -18.8136,5.380105 -38.377,6.494309 -48.6481,2.770612 -54.3158,10.318584 -7.5958,10.115773 33.9644,-0.147657 36.2574,0.413874 34.8358,8.527895 -0.837,4.777144 -8.6141,17.225554 -17.2824,27.663084 -14.6722,17.6667 -15.0244,18.75797 -5.0997,15.80023 13.6475,-4.06717 56.0267,-27.5259 59.3854,-32.87239 3.9467,-6.28255 10.8684,-4.82635 10.8684,2.28652 0,18.9078 -46.0269,51.16141 -98.9925,69.36954 -17.7692,6.10861 -26.8146,11.56776 -26.8146,16.18339 0,3.83088 -23.2644,24.81478 -51.6986,46.63086 l -51.6987,39.66562 -56.4514,-2.01852 -56.45143,-2.01847 41.93573,4.09583 c 23.0646,2.25273 46.7577,5.20461 52.6512,6.5598 l 10.7156,2.46401 -12.9227,8.38089 c -8.3117,5.39057 -21.5855,8.62331 -37.2013,9.06024 -13.3532,0.37349 -18.8159,1.49347 -12.1392,2.4885 6.6766,0.99508 12.1392,3.65795 12.1392,5.91753 0,4.50099 -35.75572,28.03905 -77.24996,50.85382 l -26.48571,14.56264 64.00714,-2.48794 c 134.91283,-5.24395 245.30013,-32.24332 312.31073,-76.38729 11.8575,-7.81124 14.3464,-8.27293 14.3464,-2.66122 0,10.14105 -24.2644,35.38582 -46.1273,47.99101 -26.9363,15.53036 -98.5056,38.47033 -110.659,35.46937 -5.4995,-1.35793 -29.5821,0.51406 -53.5171,4.15993 -23.9349,3.64592 -84.2398,7.69685 -134.01087,9.00205 -49.77107,1.30516 -93.4725,3.38404 -97.11428,4.61967 -3.64179,1.23563 -27.47893,2.69075 -52.97143,3.23365 -49.07696,1.04515 -67.77592,6.32294 -95.13244,26.85115 -17.39829,13.05558 -50.62033,54.34317 -62.98501,78.2763 l -9.26828,17.93978 35.52895,11.55248 c 135.68167,44.11765 338.84686,48.98721 485.77406,11.64333 51.4263,-13.07083 99.4719,-34.33754 119.1218,-52.72767 l 17.4888,-16.3674 4.1142,10.64612 c 8.6733,22.44341 -17.4007,55.3217 -61.1015,77.04676 -51.1742,25.44032 -133.6889,43.2757 -233.4187,50.45305 -54.90557,3.95144 -73.90991,4.16736 -125.07902,1.42116 z m 32.38108,-290.96643 c 1.62383,-1.5976 -0.19997,-6.33162 -4.05267,-10.52005 -11.76941,-12.79458 -12.93059,-39.27593 -2.21222,-50.45079 15.265,-15.9151 11.58304,-18.63872 -25.1971,-18.63872 l -34.39518,0 -17.54264,18.45714 c -39.13259,41.17263 -43.39772,46.73418 -39.09004,50.97216 8.29095,8.15679 115.49055,17.0663 122.48985,10.18026 z m 67.95994,-26.16198 c -4.295,-1.10108 -10.25429,-1.02869 -13.24289,0.16112 -2.9886,1.18964 0.52543,2.09052 7.809,2.00202 7.28359,-0.0886 9.72879,-1.06183 5.43389,-2.16292 z m 90.5768,-125.80948 c -3.0348,-1.20475 -8.0009,-1.20475 -11.0357,0 -3.0348,1.20475 -0.5518,2.19049 5.5179,2.19049 6.0696,0 8.5526,-0.98574 5.5178,-2.19049 z m -9.0546,-42.76715 c 27.4377,-3.60666 59.8189,-9.14979 71.9582,-12.31808 l 22.0714,-5.76046 -1.2311,-19.37839 c -1.7287,-27.20956 10.3204,-37.541782 51.7986,-44.417523 33.4185,-5.539748 39.7467,-10.288706 39.8578,-29.910776 0.057,-9.989961 -1.8133,-11.942857 -11.4346,-11.942857 -19.2592,0 -63.8135,12.839222 -101.0621,29.123069 C 1095.9313,69.728465 1000.3,129.04881 1000.3,140.23392 c 0,5.73032 27.8788,4.87477 84.7489,-2.6008 z"
+ id="path3261"
+ style="fill:#9da0ae"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 883.32139,621.71653 c -86.78459,-5.8156 -180.25514,-26.28288 -227.42642,-49.79958 -43.56418,-21.71845 -67.95461,-51.54489 -63.00041,-77.04163 2.53556,-13.04946 7.76256,-15.56333 11.64797,-5.60194 2.96923,7.61251 37.34428,32.74592 44.78664,32.74592 2.89396,0 10.84095,-14.16857 17.66005,-31.48571 14.62303,-37.13551 35.22353,-78.79463 47.25484,-95.56079 8.22867,-11.46697 8.25114,-12.0754 0.54446,-14.7374 -72.44134,-25.02232 -104.30988,-44.21788 -118.62479,-71.45198 -8.46947,-16.11313 -5.13611,-15.73296 24.40866,2.78394 24.11471,15.11367 78.71965,37.65857 103.83472,42.87052 12.4964,2.59329 14.91879,0.60083 42.97634,-35.34947 16.35338,-20.95372 46.46923,-55.19767 66.92405,-76.09767 23.40967,-23.91915 34.47403,-38.03487 29.85938,-38.09411 -4.03214,-0.0517 -27.84271,-2.76149 -52.91236,-6.02159 -25.06966,-3.26014 -49.19589,-4.9975 -53.61389,-3.86088 -12.27776,3.15877 -71.95148,-14.37716 -105.51996,-31.00844 -29.73741,-14.73318 -57.93498,-40.30375 -57.93498,-52.53753 0,-8.60433 0.36948,-8.48473 26.91942,8.71767 45.1437,29.24958 148.51581,58.22903 233.82249,65.55 l 35.34832,3.03357 31.83702,-30.0269 C 1026.9827,59.835133 1163.0919,-10.344003 1275.0892,-23.25536 l 25.3822,-2.92613 -0.155,21.1289978 c -0.1598,21.7512862 -9.2558,70.1441322 -14.8621,79.0685732 -1.8097,2.880574 -14.137,5.031808 -28.8345,5.031808 -29.1167,0 -53.2627,3.898452 -53.2627,8.599465 0,1.744004 16.388,2.965433 36.4178,2.714285 34.1053,-0.427771 36.3213,0.09511 34.8962,8.229107 -0.837,4.777144 -8.6141,17.225554 -17.2824,27.663084 -14.6722,17.6667 -15.0244,18.75797 -5.0997,15.80023 13.6475,-4.06717 56.0266,-27.5259 59.3853,-32.87239 3.9467,-6.28255 10.8685,-4.82635 10.8685,2.28652 0,18.9078 -46.0269,51.16141 -98.9925,69.36954 -17.9283,6.16329 -26.8146,11.55108 -26.8146,16.25787 0,3.87183 -23.2591,24.73835 -51.6868,46.37008 l -51.6868,39.33034 -60.8775,-1.73306 -60.8775,-1.73306 50.7643,4.32753 c 27.9203,2.38014 52.4665,5.99188 54.5469,8.02612 2.0932,2.0467 -1.4464,7.06926 -7.9252,11.24565 -9.5422,6.15114 -18.4068,7.15108 -47.9254,5.4059 -35.13201,-2.07701 -35.45685,-2.0097 -10.8356,2.24587 13.9602,2.41285 25.3821,6.00048 25.3821,7.97249 0,3.95747 -20.4601,17.36421 -70.62853,46.28004 l -33.10714,19.08213 66.35386,-2.66291 c 132.91491,-5.33412 243.33951,-32.57443 309.96401,-76.46408 11.8575,-7.81124 14.3464,-8.27293 14.3464,-2.66122 0,10.57968 -24.8482,35.84078 -47.7009,48.49352 -29.0786,16.09988 -95.7109,37.47868 -107.7875,34.58334 -5.526,-1.32483 -29.9116,0.647 -54.1902,4.38181 -24.2785,3.73482 -84.8646,7.85849 -134.63567,9.16369 -49.77107,1.30516 -93.4725,3.47433 -97.11428,4.82031 -3.64179,1.34598 -20.88491,1.99806 -38.31803,1.44908 -42.19673,-1.32887 -75.66765,6.02641 -100.21408,22.02224 -22.01956,14.34915 -57.97789,56.30566 -72.60171,84.71233 l -9.24744,17.96302 40.66563,13.20584 c 135.16781,43.89465 351.36478,46.89552 492.34468,6.83392 50.4225,-14.32834 98.6865,-37.44233 112.9114,-54.07408 l 11.4469,-13.38382 4.3988,11.38233 c 5.967,15.44046 -2.9027,35.38499 -23.7416,53.38569 -45.5377,39.33565 -140.7911,65.56946 -271.8387,74.86734 -65.00606,4.61216 -71.67892,4.60208 -143.59871,-0.21758 z m 45.24643,-284.83448 c 11.86582,-3.12846 12.57003,-8.91115 2.42785,-19.9368 -11.92907,-12.96816 -9.57957,-35.87782 5.24016,-51.09601 l 12.74444,-13.08707 -19.58658,-0.0478 c -10.77263,-0.0261 -27.11528,-1.27181 -36.31704,-2.76788 -16.18524,-2.63147 -17.80365,-1.66431 -49.66071,29.67731 -18.11164,17.81852 -32.93026,35.50376 -32.93026,39.30042 0,7.45265 12.94979,10.54741 68.09358,16.27325 17.17532,1.78335 33.21427,3.48119 35.64213,3.77286 2.42786,0.29184 8.88375,-0.648 14.34643,-2.08827 z m 157.81068,-84.11988 c 1.6833,-2.6795 -5.4025,-4.22595 -18.5002,-4.0376 -15.9234,0.22887 -19.0221,1.23793 -12.3998,4.0376 12.2685,5.18663 27.6418,5.18663 30.9,0 z m 9.8482,-72.49527 c -4.295,-1.10109 -10.2543,-1.02869 -13.2429,0.16112 -2.9886,1.18964 0.5254,2.09052 7.809,2.00201 7.2836,-0.0886 9.7288,-1.06182 5.4339,-2.16291 z m 0.3293,-43.14324 c 51.8287,-7.48852 89.1439,-17.35067 89.1439,-23.56009 0,-2.32842 -2.3723,-7.97079 -5.2718,-12.53857 -12.5777,-19.814108 16.2255,-43.691396 52.705,-43.691396 23.5894,0 36.4383,-9.950397 36.4383,-28.218539 0,-8.083576 -1.9712,-15.895985 -4.3805,-17.360918 -7.9184,-4.8146213 -87.111,18.830628 -124.5031,37.173944 -46.4153,22.769947 -116.6225,65.249469 -133.3414,80.679559 -11.80919,10.89887 -12.15788,12.05742 -4.4314,14.72389 9.895,3.41479 31.7777,1.73041 93.6408,-7.20788 z"
+ id="path3259"
+ style="fill:#999daa"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 885.52853,621.91804 c -130.58864,-8.75898 -237.11268,-40.86876 -275.9738,-83.18734 -14.54427,-15.83827 -17.62505,-24.80192 -15.44572,-44.93997 1.41549,-13.08021 3.52684,-13.23438 13.33009,-0.97319 9.19323,11.49819 35.17249,29.20176 42.85238,29.20176 2.36469,0 9.87864,-14.16857 16.69774,-31.48571 14.62303,-37.13551 35.22353,-78.79463 47.25484,-95.56079 6.59847,-9.19522 7.2347,-12.42118 2.7516,-13.95208 C 677.977,367.69649 649.5791,355.86238 635.21078,346.9388 615.0239,334.40158 594.18569,311.54769 594.18569,301.94533 c 0,-5.36651 2.69452,-4.86448 14.34643,2.67303 31.9415,20.66262 108.20164,52.37238 125.95263,52.37238 2.2587,0 18.2314,-18.07715 35.49487,-40.17143 17.26348,-22.09429 47.118,-56.16261 66.34336,-75.70742 l 34.95522,-35.53595 -29.29266,-2.98246 c -16.111,-1.64034 -43.62811,-5.38735 -61.1492,-8.32673 -17.52105,-2.93942 -32.41927,-3.73559 -33.10714,-1.76937 -2.10376,6.01356 -52.19209,-8.85391 -90.0373,-26.72529 -35.04391,-16.54854 -63.50621,-40.88652 -63.50621,-54.3039 0,-8.60433 0.36948,-8.48473 26.91942,8.71767 45.1392,29.24667 148.5155,58.22899 233.80245,65.54831 l 35.32828,3.03183 29.64992,-27.93295 C 1026.3475,60.536287 1162.4453,-9.9773789 1275.0892,-23.201639 l 25.3822,-2.979851 0,16.4303739 c 0,20.7915581 -8.6583,71.1888941 -13.9501,81.1990051 -3.1292,5.919271 -9.7187,7.6 -29.7963,7.6 -30.4292,0 -53.3679,3.938016 -53.3679,9.161995 0,2.053346 11.7283,2.527369 26.0629,1.05336 14.3346,-1.474009 31.2471,-1.399963 37.5833,0.164594 11.1266,2.747422 11.2974,3.268739 4.9973,15.254592 -3.5876,6.82545 -11.9557,18.76196 -18.5958,26.5256 l -12.0729,14.11564 10.809,-3.22336 c 13.7678,-4.10569 56.1577,-27.54492 59.5334,-32.91864 3.9467,-6.28255 10.8685,-4.82635 10.8685,2.28652 0,18.97576 -44.8803,50.42751 -98.1681,68.79554 -18.903,6.51576 -27.639,11.80306 -27.639,16.72795 0,4.08359 -22.5595,24.18663 -52.1112,46.43704 l -52.1111,39.23615 -62.6603,-1.6753 -62.66028,-1.67535 46.34998,3.99869 c 25.4925,2.19926 51.2673,5.16713 57.2774,6.59528 l 10.9273,2.59668 -10.9273,8.59008 c -9.5532,7.5098 -15.9237,8.34893 -50.656,6.67237 -39.35265,-1.89961 -39.46751,-1.87555 -12.1392,2.54457 15.1741,2.45427 27.5892,5.95575 27.5892,7.78101 0,4.14213 -64.8131,45.26933 -89.69444,56.91561 l -18.45551,8.63855 68.42142,-2.35578 c 132.14813,-4.54992 245.07933,-32.21319 312.31073,-76.50264 11.8575,-7.81124 14.3464,-8.27293 14.3464,-2.66122 0,27.77839 -99.6022,81.56311 -147.8786,79.85372 -7.2835,-0.25796 -37.08,2.99862 -66.2142,7.23672 -29.1343,4.23811 -93.6933,9.36186 -143.46433,11.38615 -49.77107,2.02429 -93.47249,3.83022 -97.11428,4.01323 -3.64178,0.18284 -19.19631,0.31747 -34.56562,0.29879 -60.75911,-0.0743 -94.21661,15.27665 -130.1183,59.69987 -23.16851,28.66767 -44.50739,61.50193 -41.73959,64.22495 5.97204,5.87536 91.73856,29.66583 134.22456,37.23205 71.53429,12.73933 234.94226,14.02682 309.53186,2.43877 92.6642,-14.39605 177.797,-45.17362 201.4504,-72.82915 l 11.4469,-13.38381 4.3988,11.38232 c 9.5661,24.75359 -18.6376,58.23915 -68.1699,80.93644 -79.6182,36.4836 -234.2993,56.32221 -368.80197,47.30066 z M 940.73346,335.2345 c 1.51498,-2.41163 -1.34742,-9.51516 -6.36095,-15.78563 -16.14255,-20.1899 -11.59164,-45.58667 10.15105,-56.64827 12.64755,-6.43451 13.05781,-7.29005 4.98289,-10.39129 -4.83965,-1.85874 -24.5067,-3.51689 -43.70456,-3.68474 l -34.90521,-0.30526 -32.29107,33.65714 c -17.76013,18.51143 -32.34581,36.06278 -32.41269,39.00299 -0.14611,6.43359 9.36054,8.90433 52.8499,13.73532 47.71264,5.30016 78.52533,5.45845 81.69064,0.41996 z m 150.24084,-82.76126 c 1.6003,-2.54748 1.8084,-5.71524 0.4624,-7.03951 -4.1325,-4.06557 -51.6951,3.35829 -49.0001,7.64825 3.5106,5.58834 44.9714,5.06833 48.5377,-0.60874 z m 5.3364,-72.19044 c -4.2488,-1.09232 -11.2013,-1.09232 -15.45,0 -4.2488,1.09235 -0.7725,1.98611 7.725,1.98611 8.4975,0 11.9737,-0.89376 7.725,-1.98611 z m 14.3464,-43.16101 c 73.5379,-12.34318 81.4764,-16.06909 74.2679,-34.85764 -9.56,-24.917619 5.216,-38.336699 48.648,-44.180274 15.1374,-2.03667 29.275,-5.481511 31.417,-7.655241 2.1419,-2.173687 5.2593,-11.260116 6.9275,-20.192027 4.1854,-22.4088812 -3.6725,-24.3353292 -50.4403,-12.365894 -59.3486,15.189316 -153.68,62.834583 -209.038,105.581626 -27.93466,21.57097 -23.08963,26.62819 22.0715,23.03799 21.2437,-1.68885 55.5096,-5.90468 76.1464,-9.36854 z"
+ id="path3257"
+ style="fill:#9598a5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 884.6858,621.75814 C 752.30465,612.40771 648.42534,581.05962 609.55473,538.7307 c -14.54427,-15.83827 -17.62505,-24.80192 -15.44572,-44.93997 1.41549,-13.08021 3.52684,-13.23438 13.33009,-0.97319 9.08314,11.36048 35.14283,29.20176 42.65325,29.20176 2.25517,0 12.64022,-20.45802 23.07784,-45.46228 10.43767,-25.00431 25.65208,-56.36712 33.80985,-69.69521 9.85657,-16.10367 13.04625,-24.83663 9.50824,-26.03265 -63.65739,-21.51925 -91.49445,-36.25535 -110.52925,-58.51088 -17.24471,-20.16254 -15.84976,-29.66588 2.57309,-17.52964 40.38197,26.60208 131.00628,59.65709 134.58653,49.09014 4.00385,-11.81727 50.33495,-67.45487 88.21632,-105.93631 l 41.08318,-41.73403 -60.76268,-8.13682 c -33.41946,-4.47528 -65.43442,-7.57551 -71.14438,-6.88943 -13.99673,1.68178 -63.25879,-14.29017 -94.61302,-30.67585 -26.77895,-13.99464 -51.71238,-37.87949 -51.71238,-49.53763 0,-6.08681 1.12282,-6.15096 9.93214,-0.56739 62.86433,39.84493 155.51753,67.86852 249.72483,75.53101 l 36.7356,2.98793 27.27154,-26.0214 C 1022.6891,62.865666 1162.7754,-10.003436 1275.7793,-23.286455 l 26.0723,-3.064667 -2.7529,29.8994843 c -1.514,16.4447497 -5.3008,39.6709567 -8.415,51.6138127 l -5.6623,21.714285 -22.0715,1.064869 c -36.5743,1.76459 -59.5928,5.733049 -59.5928,10.274027 0,2.674027 9.8911,3.191479 26.234,1.372386 14.4288,-1.606032 31.3413,-1.658928 37.5833,-0.117691 10.9427,2.701995 11.1156,3.246894 4.8262,15.212329 -3.5876,6.82546 -11.9557,18.76197 -18.5958,26.52561 l -12.0729,14.11563 10.809,-3.22335 c 13.7678,-4.1057 56.1577,-27.54492 59.5334,-32.91864 3.5921,-5.71798 10.8685,-5.08731 10.8685,0.94201 0,9.62746 -22.514,33.95606 -41.6144,44.9685 -24.2475,13.98009 -71.9506,32.50607 -83.9262,32.59362 -6.8624,0.0504 -7.6085,0.99299 -3.0392,3.84061 3.3307,2.07575 5.3171,5.78186 4.4142,8.23579 -0.9028,2.45393 -25.0244,21.99935 -53.6034,43.43422 l -51.9619,38.97249 -67.2238,-1.39753 -67.22386,-1.39758 50.76426,4.00377 c 27.9204,2.20205 55.6816,5.15653 61.6917,6.56549 l 10.9273,2.56181 -10.9273,8.52542 c -9.592,7.48361 -16.0522,8.28977 -52.8631,6.59701 l -41.93571,-1.92849 29.79641,4.61637 c 16.388,2.53901 29.7964,6.14419 29.7964,8.01153 0,3.97563 -72.36912,48.63218 -93.6048,57.76039 -14.07327,6.04943 -13.23566,6.26375 25.81929,6.60653 129.75271,1.13878 268.63271,-30.64872 340.36771,-77.90499 11.8575,-7.81124 14.3464,-8.27293 14.3464,-2.66122 0,12.29806 -25.9025,36.11589 -56.2398,51.71361 -32.8241,16.8763 -73.302,30.02404 -82.1505,26.68352 -3.1183,-1.17722 -22.9558,0.82523 -44.0832,4.4499 -50.0459,8.58605 -132.9274,14.43679 -247.91934,17.50107 -50.985,1.35862 -100.09242,4.1648 -109.12758,6.23599 -38.41559,8.80614 -78.10381,43.44377 -108.90011,95.04178 -7.54729,12.64509 -13.7223,23.79047 -13.7223,24.76748 0,4.47341 90.23294,29.52266 136.35455,37.85291 74.54885,13.46468 248.48318,13.50081 322.82068,0.0669 91.8897,-16.60565 164.3855,-44.22449 187.731,-71.52012 l 10.7954,-12.62204 4.3988,11.38232 c 9.5661,24.75359 -18.6376,58.23915 -68.1699,80.93644 -79.5271,36.44187 -236.9265,56.51499 -369.6447,47.14076 z M 948.69948,336.8229 c 1.56331,-1.53802 -3.07169,-9.42313 -10.29999,-17.52252 -7.2283,-8.09938 -13.14239,-19.63271 -13.14239,-25.62958 0,-13.109 16.81578,-30.75972 33.14643,-34.79211 22.27038,-5.49905 1.41641,-10.32423 -44.91716,-10.39289 l -44.87788,-0.0665 -31.26855,33.38541 c -30.92648,33.02013 -36.47776,43.166 -25.75069,47.06337 19.91898,7.23689 131.27317,13.69741 137.11023,7.95481 z m 137.39172,-79.61039 c 19.8801,-7.43614 12.6357,-13.15056 -16.2662,-12.8308 -29.7994,0.32963 -50.4325,5.58153 -38.625,9.83149 20.6965,7.44944 40.1519,8.51253 54.8912,2.99931 z m 87.4695,-68.12653 c -3.0349,-1.20475 -8.0009,-1.20475 -11.0358,0 -3.0348,1.20476 -0.5517,2.1905 5.5179,2.1905 6.0696,0 8.5527,-0.98574 5.5179,-2.1905 z m -77.25,-8.80318 c -4.2488,-1.09232 -11.2013,-1.09232 -15.45,0 -4.2488,1.09235 -0.7725,1.98611 7.725,1.98611 8.4975,0 11.9737,-0.89376 7.725,-1.98611 z m 28.6928,-4.22539 c -3.0348,-1.20475 -8.0009,-1.20475 -11.0357,0 -3.0348,1.20475 -0.5518,2.1905 5.5179,2.1905 6.0696,0 8.5526,-0.98575 5.5178,-2.1905 z m -9.5513,-38.2954 c 41.483,-7.23572 64.8743,-13.32136 74.4865,-19.37887 5.5296,-3.48466 5.2983,-5.3097 -1.6267,-12.838 -10.7091,-11.641857 -6.2467,-28.767914 9.5958,-36.827822 6.6439,-3.380133 23.6133,-7.56339 37.7096,-9.296103 28.9035,-3.552804 40.0755,-14.094221 37.5854,-35.46403 -1.9146,-16.4315908 -13.1495,-17.5066216 -58.1469,-5.564069 -70.8787,18.81165 -223.58451,105.407044 -223.58451,126.788844 0,5.93825 72.15421,1.62001 123.98081,-7.41995 z"
+ id="path3255"
+ style="fill:#9195a1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 884.6858,621.75814 C 752.30465,612.40771 648.42534,581.05962 609.55473,538.7307 c -14.54427,-15.83827 -17.62505,-24.80192 -15.44572,-44.93997 1.42154,-13.13588 3.18813,-13.04685 14.99326,0.75575 10.78683,12.612 40.49665,31.07874 43.89269,27.28235 0.97464,-1.08954 11.03541,-23.3423 22.35735,-49.4506 11.32189,-26.1083 26.60904,-57.10114 33.97145,-68.87293 7.36241,-11.77184 12.77486,-21.81522 12.02761,-22.31864 -0.74721,-0.50342 -10.29747,-3.44371 -21.22283,-6.534 -50.60515,-14.31393 -105.94285,-52.12401 -105.94285,-72.38661 0,-5.85991 2.21226,-5.49719 14.34643,2.35231 35.20715,22.77516 107.62266,52.64624 126.5803,52.21382 3.82352,-0.0873 12.4131,-8.64433 19.08799,-19.01581 14.96297,-23.24961 69.32948,-84.56281 99.16599,-111.8369 l 22.17265,-20.26847 -27.00881,-2.80162 c -14.85486,-1.54088 -45.35095,-5.74651 -67.76908,-9.34587 -22.41812,-3.59931 -42.58284,-5.43604 -44.81043,-4.08159 -6.13797,3.73212 -61.22009,-14.02495 -90.05266,-29.03065 -26.52575,-13.80512 -51.71238,-37.78234 -51.71238,-49.22924 0,-5.1594 4.11557,-4.0997 18.76071,4.83078 58.66356,35.77233 150.95281,62.56411 240.64089,69.85855 l 36.48014,2.96699 27.527,-25.74563 C 1027.7565,60.099613 1159.7164,-8.2038864 1277.9917,-23.411877 l 23.8703,-3.069271 -2.9005,29.9644978 c -1.5952,16.4804912 -5.3844,39.7359692 -8.4203,51.6788262 l -5.5198,21.714285 -22.0715,1.064868 c -36.7976,1.77536 -59.5928,5.742386 -59.5928,10.370873 0,2.776562 8.752,3.313469 23.8204,1.461241 13.1012,-1.610375 29.9583,-1.601689 37.4603,0.01737 13.3583,2.886524 13.5078,3.198819 7.2399,15.123477 -3.52,6.69682 -11.8328,18.52811 -18.4729,26.29175 l -12.0729,14.11563 10.809,-3.1494 c 5.945,-1.73219 23.7208,-10.60434 39.5019,-19.71592 15.7811,-9.11157 29.3359,-15.94801 30.1219,-15.19205 3.7746,3.63028 -14.1364,29.05111 -27.5728,39.1337 -17.844,13.39007 -82.9598,41.19044 -96.7022,41.28581 -6.6427,0.046 -7.8439,1.15189 -3.9152,3.60435 9.9263,6.19652 1.6087,15.007 -49.8334,52.7863 l -50.2644,36.91428 -66.4527,-1.5996 c -36.54892,-0.87982 -66.43531,0.0973 -66.41412,2.17143 0.0221,2.07406 19.8382,4.03338 44.03782,4.35406 24.1997,0.3205 51.4217,1.95289 60.4935,3.62724 l 16.4941,3.04425 -10.8712,8.38398 c -9.5389,7.35641 -16.2815,8.14767 -55.0141,6.45604 l -44.1429,-1.92792 32.0036,4.63292 c 17.6019,2.54812 32.0035,6.14119 32.0035,7.98456 0,4.51896 -52.51159,36.87897 -85.72583,52.82816 l -26.83841,12.88756 72.83571,-2.40868 c 136.02173,-4.49816 240.16773,-30.10916 312.31073,-76.80165 11.9292,-7.72086 14.3464,-8.14133 14.3464,-2.49566 0,23.22755 -80.2534,71.89877 -122.7196,74.42567 -11.4096,0.67891 -38.6653,4.35488 -60.5682,8.16887 -52.2392,9.09646 -138.484,15.99187 -196.34075,15.69786 -60.31526,-0.30661 -139.11498,3.84547 -159.88013,8.42419 -38.1189,8.40525 -78.05123,43.20352 -108.91185,94.90927 -7.54729,12.6451 -13.7223,23.79048 -13.7223,24.76749 0,4.4734 90.23294,29.52265 136.35455,37.85291 74.54885,13.46468 248.48318,13.50081 322.82068,0.0669 91.8897,-16.60566 164.3855,-44.22449 187.731,-71.52013 l 10.7954,-12.62203 4.3988,11.38232 c 9.5661,24.75359 -18.6376,58.23915 -68.1699,80.93644 -79.5271,36.44186 -236.9265,56.51498 -369.6447,47.14075 z M 824.55622,371.39552 c -3.18659,-1.25453 -6.99391,-1.10027 -8.46073,0.34265 -1.46683,1.44309 1.14038,2.46952 5.79375,2.28095 5.14242,-0.20845 6.18838,-1.23745 2.66698,-2.62378 z m -26.48572,-4.34286 c -3.18658,-1.25452 -6.9939,-1.10026 -8.46073,0.34265 -1.46682,1.44309 1.14039,2.46952 5.79375,2.28096 5.14243,-0.20846 6.18839,-1.23746 2.66698,-2.62378 z m -22.07143,-4.34286 c -3.18658,-1.25452 -6.9939,-1.10026 -8.46072,0.34265 -1.46683,1.44309 1.14038,2.46953 5.79375,2.28096 5.14242,-0.20846 6.18838,-1.23745 2.66697,-2.62378 z m 165.8116,-21.48489 c 12.74961,-3.36146 12.5786,-9.16469 -0.56997,-19.34 -12.34543,-9.55377 -16.92229,-27.60142 -10.51068,-41.44576 5.01529,-10.82917 32.17392,-23.33412 50.67785,-23.33412 7.27859,0 10.21598,-1.53212 7.63566,-3.98266 -3.41613,-3.24429 -85.25283,-6.99574 -112.92881,-5.17673 -3.96372,0.26057 -19.47588,12.90649 -34.47147,28.10215 -34.13271,34.58807 -42.21747,45.74201 -36.92757,50.94631 2.24413,2.20782 26.80778,6.71927 54.58586,10.0254 60.17457,7.16198 69.49375,7.63696 82.50913,4.20541 z M 1095.218,254.30249 c 13.57,-13.35046 -1.0335,-15.08343 -63.447,-7.52903 -33.28487,4.02869 -11.7339,12.53452 35.5315,14.02373 13.9848,0.44063 22.9974,-1.65623 27.9155,-6.4947 z m 78.3427,-65.21651 c -3.0349,-1.20475 -8.0009,-1.20475 -11.0358,0 -3.0348,1.20476 -0.5517,2.1905 5.5179,2.1905 6.0696,0 8.5527,-0.98574 5.5179,-2.1905 z m -56.2822,-10.31971 c 8.3441,-2.19432 2.6289,-2.5946 -15.45,-1.08207 -15.781,1.32032 -29.5205,3.1187 -30.5321,3.99639 -3.3472,2.90415 32.495,0.63245 45.9821,-2.91432 z M 1093,141.8532 c 56.2834,-7.89636 85.7525,-14.42545 94.9397,-21.03458 7.0925,-5.10225 7.1271,-6.43512 0.4947,-19.05329 -6.8169,-12.969073 -6.6927,-14.068989 2.8173,-24.946108 8.0943,-9.257973 15.6716,-12.077051 40.6545,-15.125216 20.976,-2.559289 32.5079,-6.170939 36.3927,-11.397741 7.8917,-10.618025 7.3681,-39.170051 -0.7747,-42.2441862 -6.568,-2.4795977 -39.6823,4.7737552 -79.6171,17.4393072 -47.2716,14.992411 -152.322,73.838687 -185.4,103.855774 -16.29348,14.78574 -16.97531,16.28567 -8.82857,19.42169 9.78237,3.76561 36.39597,1.91251 99.32147,-6.91565 z"
+ id="path3253"
+ style="fill:#91949c"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 889.94282,621.6322 c -131.56023,-7.92945 -241.067,-40.08201 -279.66379,-82.11275 -15.21547,-16.56921 -18.3625,-25.46899 -16.17002,-45.72872 1.42154,-13.13588 3.18813,-13.04685 14.99326,0.75575 10.78683,12.612 40.49665,31.07874 43.89269,27.28235 0.97464,-1.08954 11.03541,-23.3423 22.35735,-49.4506 11.32189,-26.1083 26.60904,-57.10114 33.97145,-68.87293 7.36241,-11.77184 12.77486,-21.81522 12.02761,-22.31864 -0.74721,-0.50342 -10.29747,-3.44371 -21.22283,-6.534 -50.7272,-14.34845 -105.94285,-52.12896 -105.94285,-72.48984 0,-6.47616 0.78442,-6.47511 9.93214,0.013 28.68041,20.34255 111.9605,55.09031 130.99459,54.65616 3.82352,-0.0873 12.4131,-8.64433 19.08799,-19.01581 15.04101,-23.37091 69.37751,-84.60589 99.55732,-112.19706 12.41023,-11.34571 19.77119,-20.62857 16.3578,-20.62857 -11.25365,0 -105.6448,-13.07843 -122.53342,-16.97766 -9.10446,-2.10208 -16.55357,-2.17877 -16.55357,-0.17068 0,5.69735 -43.06979,-7.75604 -77.91973,-24.33924 -31.36248,-14.92335 -58.92312,-39.46167 -58.92312,-52.46127 0,-5.61358 2.48502,-5.15415 14.34643,2.65227 53.1005,34.9474 154.60651,64.86104 245.05517,72.21737 l 36.48014,2.96699 27.527,-25.74563 C 1027.7565,60.099613 1159.7164,-8.2038864 1277.9917,-23.411877 l 23.8703,-3.069271 -2.9005,29.9644978 c -1.5952,16.4804912 -5.3844,39.7359692 -8.4203,51.6788252 l -5.5198,21.714285 -22.0715,1.064869 c -36.7976,1.77536 -59.5928,5.742386 -59.5928,10.370873 0,2.776562 8.752,3.313469 23.8204,1.461241 13.1012,-1.610375 29.9583,-1.601689 37.4603,0.01737 13.3583,2.886524 13.5078,3.198819 7.2399,15.123477 -3.52,6.69682 -11.8328,18.52811 -18.4729,26.29175 l -12.0729,14.11563 10.809,-3.19187 c 11.6252,-3.43294 38.6262,-17.40448 58.2626,-30.14777 6.6766,-4.33287 12.1393,-6.57899 12.1393,-4.99137 0,8.46792 -15.1113,29.47245 -28.3509,39.40734 -17.844,13.39007 -82.9598,41.19044 -96.7022,41.28581 -6.6427,0.046 -7.8439,1.15189 -3.9152,3.60435 9.9263,6.19652 1.6087,15.007 -49.8334,52.7863 l -50.2644,36.91428 -66.4527,-1.5996 c -36.54892,-0.87982 -66.43531,0.0973 -66.41412,2.17143 0.0221,2.07406 19.8382,4.03338 44.03782,4.35406 24.1997,0.3205 51.4217,1.95289 60.4935,3.62724 l 16.4941,3.04425 -10.8712,8.38398 c -9.5389,7.35641 -16.2815,8.14767 -55.0141,6.45604 l -44.1429,-1.92792 32.0036,4.63292 c 17.6019,2.54812 32.0035,6.14119 32.0035,7.98456 0,4.51896 -52.51159,36.87897 -85.72583,52.82816 l -26.83841,12.88756 72.83571,-2.40868 c 136.02173,-4.49816 240.16773,-30.10916 312.31073,-76.80165 11.9292,-7.72086 14.3464,-8.14133 14.3464,-2.49566 0,23.31093 -80.1966,71.88032 -122.6119,74.25738 -11.4688,0.64274 -38.773,4.34181 -60.6759,8.22012 -52.8899,9.36519 -138.6645,16.19312 -198.5479,15.80504 -52.59007,-0.34091 -116.02146,2.52368 -148.26163,6.6953 -41.42428,5.35995 -85.15943,41.08338 -118.3232,96.64802 -7.54729,12.6451 -13.7223,23.79048 -13.7223,24.76749 0,4.4734 90.23294,29.52265 136.35455,37.85291 74.54885,13.46468 248.48318,13.50081 322.82068,0.0669 91.1403,-16.47025 164.389,-44.2287 187.2535,-70.96185 12.3347,-14.42176 15.7141,-12.29698 15.7141,9.88026 0,43.98497 -77.0841,84.87649 -199.4709,105.81505 -32.49,5.5586 -159.75072,16.07405 -173.53623,14.33921 -2.42785,-0.30574 -29.24464,-2.05209 -59.59285,-3.88126 z m -65.3866,-250.23668 c -3.18659,-1.25453 -6.99391,-1.10027 -8.46073,0.34265 -1.46683,1.44309 1.14038,2.46952 5.79375,2.28095 5.14242,-0.20845 6.18838,-1.23745 2.66698,-2.62378 z m -26.48572,-4.34286 c -3.18658,-1.25452 -6.9939,-1.10026 -8.46073,0.34265 -1.46682,1.44309 1.14039,2.46952 5.79375,2.28096 5.14243,-0.20846 6.18839,-1.23746 2.66698,-2.62378 z m -22.07143,-4.34286 c -3.18658,-1.25452 -6.9939,-1.10026 -8.46072,0.34265 -1.46683,1.44309 1.14038,2.46953 5.79375,2.28096 5.14242,-0.20846 6.18838,-1.23745 2.66697,-2.62378 z m 165.8116,-21.48489 c 12.74961,-3.36146 12.5786,-9.16469 -0.56997,-19.34 -12.34543,-9.55377 -16.92229,-27.60142 -10.51068,-41.44576 5.01529,-10.82917 32.17392,-23.33412 50.67785,-23.33412 7.27859,0 10.21598,-1.53212 7.63566,-3.98266 -3.41613,-3.24429 -85.25283,-6.99574 -112.92881,-5.17673 -3.96372,0.26057 -19.47588,12.90649 -34.47147,28.10215 -34.13271,34.58807 -42.21747,45.74201 -36.92757,50.94631 2.24413,2.20782 26.80778,6.71927 54.58586,10.0254 60.17457,7.16198 69.49375,7.63696 82.50913,4.20541 z M 1095.218,254.30249 c 13.57,-13.35046 -1.0335,-15.08343 -63.447,-7.52903 -33.28487,4.02869 -11.7339,12.53452 35.5315,14.02373 13.9848,0.44063 22.9974,-1.65623 27.9155,-6.4947 z M 970.45206,210.60493 c -5.49098,-1.03877 -13.43669,-0.99934 -17.65714,0.0877 -4.22046,1.08698 0.27236,1.93692 9.98361,1.88871 9.71143,-0.0482 13.1645,-0.93758 7.67353,-1.97635 z m 203.10864,-21.51895 c -3.0349,-1.20475 -8.0009,-1.20475 -11.0358,0 -3.0348,1.20476 -0.5517,2.1905 5.5179,2.1905 6.0696,0 8.5527,-0.98574 5.5179,-2.1905 z m -56.2822,-10.31971 c 8.3441,-2.19432 2.6289,-2.5946 -15.45,-1.08207 -15.781,1.32032 -29.5205,3.1187 -30.5321,3.99639 -3.3472,2.90415 32.495,0.63245 45.9821,-2.91432 z M 1093,141.8532 c 56.2834,-7.89636 85.7525,-14.42545 94.9397,-21.03458 7.0925,-5.10225 7.1271,-6.43512 0.4947,-19.05329 -6.8169,-12.969073 -6.6927,-14.068989 2.8173,-24.946108 8.0943,-9.257973 15.6716,-12.077051 40.6545,-15.125216 20.976,-2.559289 32.5079,-6.170939 36.3927,-11.397741 7.8917,-10.618025 7.3681,-39.170051 -0.7747,-42.2441862 -6.568,-2.4795977 -39.6823,4.7737552 -79.6171,17.4393072 -47.2716,14.992411 -152.322,73.838687 -185.4,103.855774 -16.29348,14.78574 -16.97531,16.28567 -8.82857,19.42169 9.78237,3.76561 36.39597,1.91251 99.32147,-6.91565 z m 141.2571,6.6804 c 3.6418,-2.31544 4.635,-4.20983 2.2071,-4.20983 -2.4278,0 -7.3939,1.89439 -11.0357,4.20983 -3.6418,2.31544 -4.635,4.20984 -2.2071,4.20984 2.4278,0 7.3939,-1.8944 11.0357,-4.20984 z"
+ id="path3251"
+ style="fill:#8d919d"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 889.94282,621.6322 c -131.56023,-7.92945 -241.067,-40.08201 -279.66379,-82.11275 -15.21547,-16.56921 -18.3625,-25.46899 -16.17002,-45.72872 1.41549,-13.08021 3.52684,-13.23438 13.33009,-0.97319 9.39823,11.7546 35.22842,29.20176 43.23263,29.20176 2.57379,0 8.61752,-10.26 13.43055,-22.8 14.50759,-37.79879 34.88234,-80.23315 46.82784,-97.5281 6.19841,-8.97421 11.26985,-17.59986 11.26985,-19.16811 0,-1.56829 -6.61693,-4.07269 -14.70425,-5.56533 -26.15548,-4.82739 -84.04354,-35.24737 -99.03012,-52.03998 -18.453,-20.67669 -19.72555,-33.09765 -2.14062,-20.894 28.6145,19.85801 110.01697,53.23652 128.78744,52.8084 3.82352,-0.0873 12.4131,-8.64433 19.08799,-19.01581 14.96297,-23.24961 69.32948,-84.56281 99.16599,-111.8369 l 22.17265,-20.26847 -27.00881,-2.80162 c -14.85486,-1.54088 -45.87992,-5.96517 -68.94456,-9.83175 -23.06464,-3.86657 -46.90178,-6.77155 -52.97142,-6.45552 -40.92635,2.13108 -132.42857,-50.14207 -132.42857,-75.6534 0,-6.00882 1.5103,-5.88248 12.13929,1.01532 58.45851,37.93733 153.35541,66.2895 247.50823,73.94752 l 36.72606,2.98715 25.07394,-23.78835 C 977.91909,106.03788 1039.7964,63.817316 1126.7232,21.098021 1166.3636,1.6171373 1229.983,-16.984405 1277.9775,-23.126855 l 23.8561,-3.053202 -2.8863,29.8139736 c -1.5874,16.3976724 -5.3702,39.5854024 -8.4061,51.5282594 l -5.5198,21.714285 -22.0715,1.064868 c -61.258,2.955488 -84.7829,14.094569 -26.8908,12.732866 17.986,-0.422994 34.287,0.77368 36.2242,2.659435 4.1361,4.026046 -9.311,30.1545 -20.7698,40.35669 -4.375,3.89537 -20.4763,11.65823 -35.7805,17.25087 -37.394,13.66489 -27.3941,13.36224 14.7516,-0.44644 18.5785,-6.08712 46.3953,-18.59468 61.8151,-27.79459 15.4198,-9.19991 28.679,-16.1269 29.465,-15.3933 3.8053,3.55181 -14.1674,29.03378 -27.5728,39.09314 -25.8036,19.36293 -83.048,40.74768 -108.5562,40.55329 -12.4445,-0.0947 -23.8113,0.99335 -25.2596,2.41824 -1.4483,1.42484 6.6367,2.59064 17.9667,2.59064 11.33,0 20.6,1.58866 20.6,3.53035 0,5.53671 -20.7384,23.10782 -66.2143,56.10177 l -41.9357,30.4254 -62.3178,-2.13851 c -34.27479,-1.17617 -65.51231,-0.37956 -69.41674,1.77002 -4.57223,2.51734 9.75283,3.99552 40.24636,4.15295 26.03988,0.13462 55.21708,1.71586 64.83808,3.51419 l 17.4926,3.26978 -10.8712,8.35149 c -9.5897,7.36696 -16.3355,8.11749 -57.2213,6.36619 -45.21604,-1.93674 -45.59404,-1.8762 -15.44999,2.47465 16.99499,2.45298 32.55439,6.03266 34.57639,7.9549 3.7028,3.52001 -54.54807,40.20821 -90.34373,56.90128 l -19.27551,8.98898 72.83571,-2.23497 c 133.75643,-4.10434 250.32803,-32.77671 314.51783,-77.35996 10.1243,-7.03187 12.1393,-7.32609 12.1393,-1.77249 0,10.81706 -24.2137,34.76418 -48.0647,47.53539 -26.7814,14.34042 -70.7114,29.67314 -75.9284,26.50098 -2.0743,-1.26129 -24.1886,1.56816 -49.1428,6.28763 -53.3941,10.09819 -151.083,18.25837 -218.32157,18.23683 -138.80112,-0.0443 -177.24482,8.19719 -214.15615,45.91078 -16.01737,16.36553 -56.52921,73.92033 -56.52921,80.31054 0,5.71902 83.32622,29.75561 136.35455,39.33334 74.54885,13.46468 248.48318,13.50082 322.82068,0.0669 91.1403,-16.47024 164.389,-44.22869 187.2535,-70.96185 12.3347,-14.42176 15.7141,-12.29697 15.7141,9.88026 0,43.98498 -77.0841,84.8765 -199.4709,105.81506 -32.49,5.5586 -159.75072,16.07404 -173.53623,14.3392 -2.42785,-0.30573 -29.24464,-2.05208 -59.59285,-3.88125 z M 871.18211,375.71136 c -4.24875,-1.09232 -11.20125,-1.09232 -15.45,0 -4.24875,1.09236 -0.7725,1.98612 7.725,1.98612 8.4975,0 11.97375,-0.89376 7.725,-1.98612 z m -39.81258,-4.35875 c -4.29497,-1.10109 -10.25425,-1.02869 -13.24285,0.16112 -2.98861,1.18964 0.52543,2.09052 7.809,2.00201 7.28357,-0.0886 9.72882,-1.06183 5.43385,-2.16291 z m -28.60885,-4.20949 c -3.03482,-1.20475 -8.00089,-1.20475 -11.03571,0 -3.03482,1.20475 -0.55179,2.19049 5.51785,2.19049 6.06965,0 8.55268,-0.98574 5.51786,-2.19049 z m -22.07143,-4.34286 c -3.03482,-1.20475 -8.00089,-1.20475 -11.03571,0 -3.03482,1.20476 -0.55179,2.1905 5.51786,2.1905 6.06964,0 8.55267,-0.98574 5.51785,-2.1905 z m 170.99177,-23.05145 c 5.43976,-3.38382 4.121,-6.42599 -7.725,-17.82026 -16.43827,-15.81134 -19.30054,-37.29715 -6.48834,-48.70445 13.49703,-12.01698 47.70461,-16.70301 98.73882,-13.52595 41.0748,2.55703 49.2359,1.92931 59.3047,-4.56122 21.8888,-14.10999 7.3287,-17.24826 -51.7936,-11.16353 -34.1999,3.5198 -73.04377,4.47957 -105.56262,2.60823 -28.2983,-1.62844 -56.57224,-1.69636 -62.83104,-0.15113 -12.40374,3.06297 -73.66683,67.93384 -73.66683,78.00527 0,6.59993 11.14753,9.38326 59.59285,14.87932 44.04318,4.99663 82.7969,5.18251 90.43106,0.43385 z m 23.23679,-129.13724 c -5.46267,-1.03516 -14.4016,-1.03516 -19.86428,0 -5.46268,1.03512 -0.99321,1.88207 9.93214,1.88207 10.92536,0 15.39482,-0.84695 9.93214,-1.88207 z m 142.36069,-31.90805 c 8.3534,-2.23632 1.8342,-2.65088 -17.6571,-1.12285 -16.995,1.33239 -31.7277,3.15899 -32.7393,4.05914 -3.3804,3.00799 36.9753,0.65672 50.3964,-2.93629 z m 25.1063,-7.07943 c -3.1866,-1.25452 -6.9939,-1.10026 -8.4608,0.34266 -1.4668,1.44308 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20846 6.1884,-1.23745 2.667,-2.62378 z m 22.0714,-4.34285 c -3.1866,-1.25452 -6.9939,-1.10027 -8.4607,0.34265 -1.4669,1.44309 1.1404,2.46952 5.7937,2.28095 5.1424,-0.20845 6.1884,-1.23745 2.667,-2.62378 z m 17.6571,-4.34286 c -3.1865,-1.25452 -6.9939,-1.10026 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28096 5.1424,-0.20846 6.1883,-1.23746 2.6669,-2.62378 z m -71.4562,-21.38167 c 46.2814,-7.74114 82.4888,-17.70869 86.2863,-23.75382 1.5754,-2.50769 -0.3099,-8.01018 -4.1896,-12.22774 -18.8305,-20.470843 -1.005,-38.950134 41.2046,-42.715608 29.9031,-2.667643 37.8217,-8.646628 42.1982,-31.861587 4.8856,-25.9152179 -3.71,-28.9672475 -49.7841,-17.676862 -83.1992,20.38789 -249.74622,115.094697 -237.28465,134.931747 4.17485,6.6457 60.27895,3.55545 121.56925,-6.69613 z"
+ id="path3249"
+ style="fill:#8a8d99"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 892.14996,621.64735 C 707.72773,611.06051 580.97338,556.22786 593.3754,492.40054 l 1.95792,-10.07647 13.77261,12.67702 c 17.24317,15.87149 34.04597,27.01821 40.72783,27.01821 2.81963,0 9.68817,-11.23714 15.26354,-24.97142 12.90177,-31.78225 39.21139,-85.32229 52.27825,-106.38619 l 10.09419,-16.2719 -10.35988,0.76634 c -36.29898,2.68505 -122.92417,-48.68643 -122.92417,-72.89794 0,-5.55525 2.01194,-5.26263 12.13929,1.76559 28.62977,19.86861 109.35416,52.96696 129.18247,52.96696 4.04075,0 9.43598,-4.39715 11.98942,-9.77143 6.88973,-14.5011 71.25204,-88.65634 101.47123,-116.91036 l 26.33735,-24.62461 -26.89205,-2.92526 c -14.7906,-1.6089 -49.73593,-6.76709 -77.65629,-11.46263 -27.92036,-4.69554 -58.26866,-9.12777 -67.44066,-9.84938 -37.30989,-2.93534 -109.13076,-49.27036 -109.13076,-70.40535 0,-5.61171 2.48891,-5.15002 14.34643,2.66121 44.92851,29.59718 118.33194,54.04221 201.95356,67.25522 65.1632,10.2964 79.7393,9.24681 95.18484,-6.85407 7.13084,-7.43341 33.02583,-29.82053 57.54445,-49.74916 C 1063.9751,42.457451 1176.6916,-10.455701 1277.2964,-23.086206 l 23.175,-2.90954 -0.3722,16.693247 c -0.2048,9.18127746 -3.6808,32.327533 -7.725,51.436104 l -7.353,34.742856 -38.5079,2.273398 c -54.3545,3.208894 -62.5728,12.926601 -10.9319,12.926601 44.1126,0 45.5723,1.728197 24.8612,29.43515 -12.1101,16.20068 -17.8763,19.48327 -54.4976,31.02432 -22.5251,7.09866 -53.6762,15.02473 -69.2249,17.61355 -15.5486,2.58877 -38.2023,6.38282 -50.3416,8.43122 l -22.0714,3.72435 17.6571,0.44636 c 21.2374,0.53686 101.798,-16.96433 148.5355,-32.26826 18.5702,-6.08074 46.3802,-18.58308 61.8,-27.78299 15.4198,-9.19991 28.679,-16.1269 29.465,-15.3933 4.601,4.29448 -15.435,29.79139 -32.4737,41.32467 -28.6245,19.37549 -81.2799,39.99129 -95.2854,37.3064 -6.3573,-1.21873 -17.2827,-0.52978 -24.2786,1.5309 -11.3921,3.35569 -10.4928,3.78576 8.6158,4.12007 14.9485,0.26144 20.8037,1.99841 19.5585,5.80184 -0.9774,2.98572 -24.959,23.12007 -53.2924,44.74302 l -51.5152,39.3145 -66.5885,-1.86105 c -42.79152,-1.19597 -68.23136,-0.2445 -71.18571,2.66187 -3.18694,3.13537 10.96116,4.49234 46.11651,4.4232 27.8927,-0.0547 57.2646,1.31576 65.271,3.04582 l 14.557,3.14553 -10.6856,8.08784 c -9.3379,7.06778 -16.8099,7.8488 -59.2427,6.19256 l -48.55717,-1.89531 35.31428,5.24244 c 20.05019,2.97646 34.49839,7.11955 33.42679,9.58529 -1.9435,4.4718 -49.11895,34.41997 -74.80219,47.48636 -7.96271,4.05106 -22.76823,9.13299 -32.90113,11.29317 -39.65158,8.45311 143.43292,0.6939 195.75892,-8.29638 79.1083,-13.59184 150.3916,-38.44349 190.8283,-66.52892 6.6766,-4.63726 12.1319,-6.83262 12.1228,-4.8785 -0.047,10.07056 -20.0474,33.4743 -38.3836,44.91465 -23.7274,14.80402 -66.3871,31.5106 -71.9008,28.15808 -2.0394,-1.24001 -16.8477,0.74337 -32.9072,4.40757 -57.5696,13.13528 -125.2288,20.17739 -207.76962,21.62521 -46.15242,0.80956 -89.85384,2.63425 -97.11428,4.05493 -10.971,2.14672 -11.70956,1.82665 -4.37227,-1.89479 9.28523,-4.70939 10.41096,-4.78609 -62.54874,4.26212 -39.27863,4.87121 -68.94214,19.27148 -93.34087,45.31268 -18.50547,19.75123 -53.78895,70.15455 -53.78895,76.83891 0,5.87892 82.36157,29.788 136.35455,39.58292 43.59852,7.9093 67.73332,9.2978 161.60972,9.2978 93.63586,0 118.06246,-1.39862 161.21096,-9.2307 91.2992,-16.57217 164.4104,-44.29441 187.2535,-71.00263 12.3347,-14.42176 15.7141,-12.29697 15.7141,9.88026 0,43.98498 -77.0841,84.87649 -199.4709,105.81506 -31.3291,5.35995 -163.24909,16.39459 -171.32908,14.33104 -1.21393,-0.31008 -28.03072,-2.0461 -59.59286,-3.85794 z m -14.28573,-245.9994 c -5.42931,-1.03082 -15.36145,-1.05752 -22.07143,-0.0591 -6.70998,0.99821 -2.26784,1.84163 9.87145,1.87425 12.13928,0.0304 17.62929,-0.78415 12.19998,-1.81497 z m -67.37855,-8.27018 c -18.20893,-3.32459 -36.08678,-5.41107 -39.72857,-4.63666 -8.90878,1.8944 37.94229,10.57734 57.38571,10.63531 9.03344,0.0261 1.70021,-2.46439 -17.65714,-5.99865 z m 148.09619,-28.10493 c 1.62022,-2.5791 -4.22182,-11.14842 -12.98233,-19.04287 -16.1912,-14.59061 -19.55382,-26.63092 -11.54777,-41.34838 7.51563,-13.81576 31.55866,-18.14676 93.78403,-16.89376 60.8833,1.22603 78.407,-2.47426 78.407,-16.55627 0,-6.22201 -5.6767,-6.70325 -49.6607,-4.20984 -27.3134,1.5484 -80.45036,2.81595 -118.08214,2.81682 l -68.42143,0.002 -34.21071,35.14457 c -35.72852,36.70374 -41.50642,49.78191 -23.91293,54.12615 37.53166,9.2674 141.88502,13.5105 146.62698,5.96197 z M 979.3321,210.49657 c -10.31839,-0.9143 -27.20303,-0.9143 -37.52143,0 -10.31839,0.91435 -1.87607,1.66245 18.76072,1.66245 20.63678,0 29.0791,-0.7481 18.76071,-1.66245 z m 68.4214,-4.15663 c -4.2487,-1.09232 -11.2012,-1.09232 -15.45,0 -4.2487,1.09236 -0.7725,1.98612 7.725,1.98612 8.4975,0 11.9738,-0.89376 7.725,-1.98612 z m 8.7446,-21.73018 c -4.295,-1.10109 -10.2543,-1.0287 -13.2429,0.16112 -2.9886,1.18964 0.5255,2.09052 7.809,2.00201 7.2836,-0.0886 9.7289,-1.06183 5.4339,-2.16292 z m 71.3295,-45.28779 c 48.5772,-9.22275 75.5295,-18.01417 75.5295,-24.63655 0,-2.14025 -3.9729,-7.42855 -8.8286,-11.75182 -4.8557,-4.323268 -8.8286,-10.347158 -8.8286,-13.386463 0,-11.052658 22.6915,-23.165538 48.8514,-26.077293 35.1332,-3.910569 40.9414,-8.811874 41.3274,-34.874488 0.2967,-20.0168795 -0.369,-21.2818669 -11.8828,-22.5842463 -41.4221,-4.685465 -177.512,55.8732443 -250.4531,111.4492203 -24.40971,18.5985 -31.42687,26.72112 -27.91456,32.3122 3.90554,6.21697 86.30606,0.16112 142.19936,-10.45056 z"
+ id="path3247"
+ style="fill:#858894"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,621.60788 c -69.01453,-3.73195 -158.0892,-19.80786 -204.04306,-36.82496 -62.3562,-23.09101 -93.92121,-50.07357 -93.92121,-80.28618 0,-16.55323 5.11766,-22.29814 11.21591,-12.59059 4.5509,7.24436 38.051,30.11315 44.1123,30.11315 2.99554,0 10.00533,-11.23714 15.57731,-24.97142 12.28822,-30.28887 38.84028,-84.16648 52.43624,-106.4 5.47738,-8.95714 8.27281,-16.5591 6.21213,-16.89324 -2.06072,-0.33396 -11.23003,-1.03064 -20.3762,-1.54788 -36.29117,-2.05222 -109.17769,-48.74957 -109.17769,-69.94857 0,-5.55525 2.01194,-5.26263 12.13929,1.76559 19.49432,13.52873 58.31928,31.70867 91.26601,42.73562 32.07173,10.73407 46.68041,12.7614 46.68041,6.47802 0,-7.14065 54.77135,-72.3972 92.5326,-110.24668 20.85132,-20.9 35.87495,-38.03817 33.38586,-38.08477 -15.05704,-0.28229 -102.29299,-13.42481 -129.11882,-19.45266 -17.54127,-3.94157 -33.87156,-5.96361 -36.28949,-4.49342 -2.41797,1.47019 -19.63266,-3.56722 -38.25495,-11.19423 -35.64941,-14.60073 -72.34091,-43.93143 -72.34091,-57.82836 0,-6.98952 1.29868,-6.82971 14.34643,1.76563 44.92851,29.59718 118.33194,54.04221 201.95356,67.25522 65.1632,10.2964 79.7393,9.24681 95.18484,-6.85407 7.13084,-7.43341 32.82944,-29.65689 57.10801,-49.38558 99.95957,-81.226841 215.06297,-135.318558 314.51787,-147.804706 l 23.175,-2.90954 0,12.638495 c 0,15.3876545 -7.6854,59.156357 -13.6703,77.853095 l -4.3778,13.676568 -37.1073,1.523431 c -21.8216,0.895932 -39.3794,3.759655 -42.6231,6.952046 -4.4125,4.342683 2.1661,5.428571 32.8885,5.428571 44.0011,0 45.5405,1.770713 25.1979,28.98471 -11.379,15.22271 -18.1274,19.22396 -48.7712,28.91743 -37.6202,11.90025 -78.1307,20.67326 -143.2867,31.03032 l -39.7286,6.31512 35.3143,-2.33884 c 75.6501,-5.01036 179.3409,-33.26034 227.4665,-61.97209 15.7092,-9.37206 29.2051,-16.43985 29.9911,-15.70625 5.0534,4.71665 -16.4427,30.69058 -35.7769,43.22962 -26.7162,17.32661 -83.8366,38.60531 -94.9057,35.35464 -4.1741,-1.22582 -14.5418,-0.56132 -23.0393,1.47661 -14.748,3.537 -14.3803,3.72557 8.0928,4.14991 16.7065,0.31529 23.0182,2.0209 21.7359,5.8731 -0.9938,2.98572 -24.1791,22.47589 -51.5227,43.31153 l -49.7158,37.88296 -67.7125,-0.27403 c -40.20818,-0.16286 -70.97967,1.59526 -75.75584,4.32783 -5.99156,3.42799 6.59503,4.74596 49.34244,5.16678 31.5621,0.31095 62.3518,1.96836 68.4214,3.68361 l 11.0357,3.11861 -13.7175,7.09718 c -10.9874,5.68467 -23.2872,6.74476 -61.8,5.3263 -42.68268,-1.57199 -44.8602,-1.32392 -19.3896,2.20921 53.2645,7.3885 52.9723,7.14734 28.2593,23.32727 -45.01138,29.46949 -73.83238,43.10072 -98.33904,46.51043 -15.5284,2.16053 8.23957,2.89148 63.45831,1.95155 146.15483,-2.48781 261.41163,-29.38342 329.96783,-76.99933 13.8859,-9.64449 14.7836,-8.09656 5.5225,9.52267 -11.8044,22.45774 -61.676,52.47443 -90.4848,54.46077 -8.5044,0.58642 -33.3405,5.0312 -55.1913,9.87735 -66.7508,14.80432 -118.863,19.37049 -233.95708,20.49963 -60.69643,0.59545 -114.33,2.01478 -119.18571,3.15405 -4.85572,1.13926 -16.66459,3.46625 -26.24191,5.17108 -26.79957,4.77058 -56.0858,21.29985 -76.67628,43.2764 -18.50547,19.75127 -53.78895,70.15459 -53.78895,76.83895 0,4.8395 74.49584,27.67403 116.97857,35.8564 109.84671,21.15701 252.43476,21.14759 361.97136,-0.0217 77.2594,-14.93287 146.5683,-42.73654 167.4788,-67.18513 12.3347,-14.42176 15.7141,-12.29697 15.7141,9.88026 0,43.8229 -76.5438,84.56894 -198.4942,105.66297 -32.8291,5.67851 -60.2672,8.06955 -143.61293,12.51494 -14.56714,0.77694 -54.29571,-0.0912 -88.28571,-1.92922 z M 829.24639,393.20026 c -3.03482,-1.20475 -8.00089,-1.20475 -11.03571,0 -3.03482,1.20475 -0.55179,2.1905 5.51786,2.1905 6.06964,0 8.55267,-0.98575 5.51785,-2.1905 z m 20.96786,-21.27283 c -88.20219,-12.31634 -119.11349,-12.48107 -44.14286,-0.23538 20.63679,3.37083 48.44679,6.1203 61.8,6.10992 20.75372,-0.0174 18.19017,-0.86905 -17.65714,-5.87467 z m 113.59029,-35.31051 c 0.95931,-2.84557 -4.06198,-8.1277 -11.15843,-11.73805 -16.4177,-8.35253 -25.67949,-28.62989 -18.82891,-41.22314 9.48731,-17.44039 21.29217,-20.03368 91.4649,-20.09292 59.4011,-0.0499 67.9545,-0.97949 76.1464,-8.2731 5.0758,-4.51913 9.2286,-9.86024 9.2286,-11.8692 0,-2.00892 -54.1302,-2.4502 -120.28929,-0.98062 l -120.28928,2.67199 -30.12326,28.79657 c -35.29477,33.74035 -45.44992,49.38363 -36.69763,56.52989 14.45904,11.8057 156.80301,17.28378 160.5469,6.17858 z m 21.98782,-126.15453 c -14.04881,-0.86444 -35.89953,-0.85641 -48.55714,0.0174 -12.65761,0.87426 -1.16312,1.58153 25.54331,1.57172 26.70643,-0.009 37.06265,-0.72508 23.01383,-1.58953 z m 64.22904,-4.18586 c -5.4293,-1.03082 -15.3615,-1.05753 -22.0715,-0.0591 -6.7099,0.9982 -2.2678,1.84163 9.8715,1.87425 12.1393,0.0304 17.6293,-0.78415 12.2,-1.81497 z m -66.27501,-17.37924 c -5.46268,-1.03517 -14.40161,-1.03517 -19.86429,0 -5.46268,1.03512 -0.99321,1.88206 9.93214,1.88206 10.92536,0 15.39482,-0.84694 9.93215,-1.88206 z M 1121.601,142.24093 c 74.9033,-14.24444 92.5117,-22.94757 75.1347,-37.1359 -8.4077,-6.864796 -8.5518,-23.417509 -0.2631,-30.185331 3.4972,-2.855471 20.5053,-7.257435 37.7957,-9.782198 36.5647,-5.339152 45.5914,-13.910519 43.3145,-41.129071 -1.228,-14.6793341 -2.8668,-16.7837089 -14.0492,-18.0411832 -44.3283,-4.98477466 -189.3634,61.3218792 -262.1302,119.8398832 -31.57305,25.39051 -27.16388,28.82167 33.1071,25.76373 28.5273,-1.44743 67.718,-5.64588 87.0903,-9.32993 z M 864.65263,402.13834 c 1.46682,-1.44309 5.27414,-1.59735 8.46073,-0.34265 3.5214,1.38633 2.47544,2.41541 -2.66698,2.62378 -4.65337,0.18848 -7.26058,-0.83787 -5.79375,-2.28096 z"
+ id="path3245"
+ style="fill:#828591"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 892.14996,621.55741 c -69.01925,-3.67584 -157.93912,-19.7018 -204.04306,-36.77449 -62.3562,-23.09101 -93.92121,-50.07357 -93.92121,-80.28618 0,-20.16792 3.41732,-21.29819 17.99126,-5.95054 10.68756,11.255 30.83453,23.4731 38.70582,23.4731 2.29027,0 9.48445,-13.19143 15.98718,-29.31428 6.50268,-16.12286 22.53634,-49.32552 35.63039,-73.78367 26.0781,-48.71091 26.22858,-47.61491 -6.63013,-48.28184 -17.44256,-0.35394 -73.10564,-29.69954 -88.00783,-46.39761 -17.95179,-20.11511 -18.87455,-32.24984 -1.5374,-20.21812 114.24554,79.28463 398.29062,103.49527 588.20352,50.13563 39.4409,-11.08167 93.344,-34.27213 115.1215,-49.52815 7.091,-4.96758 12.8928,-7.8247 12.8928,-6.34913 0,8.16049 -15.153,29.29995 -27.8114,38.79874 -18.0808,13.56778 -64.1072,33.92618 -69.4104,30.70165 -2.159,-1.31276 -19.406,1.39883 -38.3268,6.02571 -77.7118,19.00365 -184.8782,28.58903 -272.77281,24.39787 -93.97621,-4.48118 -151.2276,7.77315 -187.91132,40.22128 -19.72413,17.44674 -65.9101,79.17841 -65.9101,88.09473 0,5.02907 72.75891,27.61279 116.97857,36.30915 119.76464,23.55318 282.91116,20.09983 399.49286,-8.45615 54.6405,-13.38382 113.7404,-39.93531 129.2475,-58.0663 13.0427,-15.24951 16.4239,-13.38629 16.4239,9.05038 0,54.52479 -127.5196,106.62457 -280.3071,114.52309 -24.2786,1.25509 -54.07503,2.88601 -66.21431,3.62425 -12.13929,0.73824 -49.88143,-0.13897 -83.87143,-1.94912 z M 826.95525,393.06689 c -4.29497,-1.10108 -10.25426,-1.02869 -13.24286,0.16112 -2.9886,1.18964 0.52543,2.09052 7.809,2.00202 7.28357,-0.0886 9.72882,-1.06183 5.43386,-2.16292 z m 60.78043,-17.27701 C 837.2234,369.63462 746.45665,356.24542 744.9071,354.7209 c -3.72769,-3.66733 48.32852,-67.21648 88.51534,-108.05767 22.58869,-22.95655 39.68231,-41.72417 37.98577,-41.70584 -13.72746,0.14852 -109.69919,-14.60038 -136.91565,-21.04093 -18.73153,-4.43262 -34.62296,-6.51142 -35.31428,-4.61949 -2.82024,7.71838 -67.55927,-24.1072 -86.05672,-42.30534 -23.59025,-23.20848 -26.39941,-37.567621 -4.58944,-23.45898 57.6744,37.30901 153.33268,65.6825 244.74847,72.59554 l 37.2288,2.81535 22.89174,-21.62517 C 1022.6694,64.095476 1161.232,-8.6903733 1275.1195,-22.689399 c 24.496,-3.011077 25.3507,-2.775824 25.3185,6.967636 -0.049,14.8923952 -8.4312,64.485651 -13.5579,80.217607 l -4.4568,13.676568 -37.1073,1.523431 c -21.8216,0.895932 -39.3794,3.759655 -42.6231,6.952046 -4.4125,4.342683 2.1661,5.428571 32.8885,5.428571 43.2538,0 44.7041,1.446606 26.8548,26.78652 -8.3095,11.79655 -18.1407,18.82238 -35.0448,25.0447 -32.0066,11.78143 -86.5354,25.18904 -125.9788,30.97586 -71.5504,10.49725 -79.4842,12.51828 -40.3148,10.2696 77.9289,-4.47384 180.2107,-32.0324 230.7148,-62.16323 15.6877,-9.35929 29.1661,-16.41665 29.9521,-15.68305 4.5476,4.24462 -15.3335,29.72269 -31.9921,40.9987 -24.6828,16.7074 -84.9718,39.75147 -97.087,37.10919 -5.056,-1.10265 -16.1453,-0.28055 -24.6428,1.82674 -14.8681,3.68725 -14.5632,3.84833 8.0928,4.27615 16.6977,0.31529 23.0219,2.0229 21.751,5.8731 -0.9854,2.98572 -24.2709,22.44875 -51.7454,43.25117 l -49.9536,37.82259 -67.1394,-0.40649 c -41.72289,-0.25276 -70.31708,1.4086 -75.53318,4.38819 -6.46477,3.69291 4.79374,4.92459 48.99208,5.3597 31.5621,0.31095 62.3518,1.96835 68.4214,3.68361 l 11.0357,3.11861 -13.2428,6.84408 c -10.3987,5.3742 -24.1457,6.52775 -64.0072,5.3712 -45.35127,-1.31589 -47.50835,-1.06587 -20.22988,2.34436 57.69698,7.21305 57.35018,6.76813 22.33968,28.66242 -60.45328,37.80518 -77.02045,43.08601 -119.08832,37.95974 z m 74.00501,-35.67492 c 4.34078,-4.34312 3.20265,-7.16233 -5.58359,-13.83096 -6.06964,-4.60677 -13.51875,-10.40796 -16.55357,-12.89155 -3.03482,-2.48355 -5.51786,-11.2335 -5.51786,-19.44423 0,-26.22031 12.36574,-30.32891 91.28153,-30.32891 63.8856,0 67.8889,-0.50377 77.2651,-9.72318 15.7136,-15.45071 6.6276,-18.28621 -44.9466,-14.02651 -25.4925,2.10554 -78.53423,3.91343 -117.87046,4.01753 l -71.52039,0.18931 -32.21532,32.78388 c -34.8676,35.483 -42.85349,47.51576 -36.00613,54.25231 5.906,5.81044 99.49084,16.96068 130.76729,15.58039 13.99629,-0.61764 27.90128,-3.57782 30.9,-6.57808 z m 73.87351,-130.65564 c 16.3881,-1.82877 -2.5424,-2.28369 -44.14281,-1.06078 -40.05965,1.17761 -73.66339,2.84691 -74.67502,3.70954 -3.21218,2.73912 88.94643,0.68469 118.81783,-2.64876 z m 54.075,-7.34477 c -3.0348,-1.20475 -8.0009,-1.20475 -11.0357,0 -3.0348,1.20476 -0.5518,2.1905 5.5179,2.1905 6.0696,0 8.5527,-0.98574 5.5178,-2.1905 z m -103.619,-13.30916 c -9.04028,-0.93319 -24.93171,-0.94609 -35.31429,-0.0304 -10.38257,0.91743 -2.98595,1.68095 16.43691,1.69671 19.42285,0.0174 27.91766,-0.73485 18.87738,-1.668 z m 61.8573,-36.08527 c 54.9288,-5.76541 132.068,-20.73154 147.7046,-28.65687 14.4004,-7.29873 15.3312,-12.62911 3.3107,-18.95822 -11.792,-6.208765 -11.5217,-24.333548 0.457,-30.640592 5.107,-2.688967 21.495,-6.58147 36.4178,-8.649929 33.5055,-4.644295 43.6461,-14.545488 41.6956,-40.711201 C 1276.2761,8.5079919 1275.2639,7.3031531 1261.4669,6.0020331 1241.3487,4.1048126 1174.9266,23.446812 1136.044,42.524852 1055.0116,82.284143 978.59589,136.23693 983.93351,149.9214 c 3.33865,8.55969 7.41079,8.73775 63.99399,2.79872 z"
+ id="path3243"
+ style="fill:#80838e"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 893.86001,621.26084 c -170.59787,-8.56468 -299.67432,-58.85761 -299.67432,-116.7641 0,-20.16792 3.41732,-21.29819 17.99126,-5.95054 10.68756,11.255 30.83453,23.4731 38.70582,23.4731 2.29027,0 9.48445,-13.19143 15.98718,-29.31428 6.50268,-16.12286 22.49493,-49.24848 35.53831,-73.61251 26.22019,-48.97727 26.53365,-47.35056 -9.78851,-50.79254 -35.38822,-3.35342 -98.43406,-45.65242 -98.43406,-66.04178 0,-5.55525 2.01194,-5.26263 12.13929,1.76559 44.49025,30.87554 119.02154,55.41854 215.19641,70.86378 56.40058,9.05764 236.47261,7.69702 291.34281,-2.20139 81.9774,-14.78847 158.5515,-41.2703 196.7858,-68.05491 7.091,-4.96758 12.8928,-7.8247 12.8928,-6.34913 0,8.16049 -15.153,29.29995 -27.8114,38.79874 -17.1653,12.88078 -62.8516,33.49484 -66.4304,29.97396 -1.3667,-1.34455 -19.9533,1.66371 -41.3036,6.685 -83.8357,19.71697 -183.9882,28.70003 -272.77601,24.46627 -62.85241,-2.99705 -106.8495,1.53898 -146.88385,15.14346 -19.86843,6.75171 -30.93743,14.47378 -51.759,36.10868 -26.15627,27.17799 -58.63517,74.95089 -54.03381,79.47776 5.06027,4.97843 65.46275,23.09396 105.35081,31.59619 125.23681,26.69455 297.41596,23.07864 421.01156,-8.84158 45.0183,-11.62661 92.8156,-33.00267 113.2359,-50.64179 8.7691,-7.57481 17.1714,-13.77237 18.6719,-13.77237 1.5003,0 2.7279,7.81149 2.7279,17.35892 0,56.44077 -129.2719,108.42112 -286.9286,115.37416 -27.9203,1.23137 -56.72353,2.74529 -64.0071,3.36428 -7.28357,0.61899 -42.26974,-0.33179 -77.74709,-2.11297 z M 829.19492,393.00492 c -5.49097,-1.03877 -13.43668,-0.99933 -17.65714,0.0877 -4.22045,1.08697 0.27236,1.93691 9.98361,1.88871 9.71143,-0.0482 13.16451,-0.93758 7.67353,-1.97635 z m -30.84853,-4.14751 c -3.03482,-1.20476 -8.00089,-1.20476 -11.03571,0 -3.03482,1.20475 -0.55178,2.19049 5.51786,2.19049 6.06964,0 8.55268,-0.98574 5.51785,-2.19049 z m 29.79643,-21.90368 c -44.91535,-5.9515 -82.4404,-11.5543 -83.38894,-12.45062 -3.63318,-3.43312 49.39581,-67.92759 88.66856,-107.83988 22.58869,-22.95655 39.68231,-41.72417 37.98577,-41.70584 -13.9295,0.1507 -109.91788,-14.65171 -137.82221,-21.25342 -19.23022,-4.54954 -37.51917,-7.30721 -40.64219,-6.12821 -10.40324,3.92751 -70.94194,-28.22136 -85.20935,-45.25009 -18.41442,-21.97838 -18.03258,-30.97413 0.79766,-18.79302 58.24676,37.67923 155.43922,66.28199 246.9395,72.67176 l 38.37365,2.67976 11.70081,-13.68061 c 6.43546,-7.52434 32.56357,-30.43787 58.06242,-50.91891 101.5118,-81.535706 215.8083,-135.487105 311.4807,-147.028378 24.5119,-2.956965 25.381,-2.716197 25.3488,7.021965 -0.049,14.8923952 -8.4312,64.485651 -13.5579,80.217607 l -4.4568,13.676568 -37.1073,1.523431 c -21.8216,0.895932 -39.3794,3.759655 -42.6231,6.952046 -4.4125,4.342683 2.1661,5.428571 32.8885,5.428571 43.6353,0 45.7374,2.182503 26.265,27.26919 -13.305,17.14113 -36.9948,27.65879 -94.4415,41.92963 -53.8494,13.37722 -143.237,25.12482 -204.62637,26.89267 -27.38248,0.78853 -30.35691,1.40304 -13.24286,2.73591 34.48347,2.68567 150.30783,-7.19477 194.22853,-16.56874 58.7734,-12.54395 116.8624,-32.7378 148.5408,-51.63813 15.4169,-9.19817 28.6737,-16.12373 29.4597,-15.39013 4.5173,4.21626 -15.2763,29.68404 -31.7223,40.81608 -21.8717,14.80458 -54.8583,28.82749 -58.7667,24.98224 -1.4383,-1.41503 -7.6815,1.34789 -13.8737,6.13985 -7.3289,5.67168 -15.2113,7.93431 -22.5839,6.48262 -6.2289,-1.22651 -18.2778,-0.52553 -26.7753,1.5577 -16.5732,4.06309 -8.9928,6.17163 17.0484,4.74214 10.6243,-0.5832 14.3104,0.83422 12.9732,4.98859 -1.0275,3.19179 -24.568,22.82623 -52.3123,43.63208 l -50.4442,37.82885 -66.4585,-0.54555 c -42.53452,-0.34917 -69.5669,1.22829 -75.09327,4.38198 -6.81279,3.88777 2.07661,4.96541 42.12954,5.10733 27.92033,0.099 58.33273,1.41026 67.58313,2.91406 l 16.8188,2.73422 -11.913,7.68833 c -10.1051,6.52163 -19.6913,7.43862 -63.1688,6.04261 l -51.25584,-1.64577 38.3062,5.33794 c 21.06834,2.93586 39.42104,6.43477 40.78364,7.77532 3.1665,3.11526 -74.22631,48.65524 -94.09497,55.36799 -18.12554,6.12378 -10.84903,6.56258 -110.80201,-6.68174 z m 126.91071,-21.38597 c 14.0885,-3.71444 12.01079,-15.30835 -4.58432,-25.58107 -12.07647,-7.47558 -14.17639,-11.3267 -14.17639,-25.9986 0,-26.06614 11.72972,-29.93593 88.61598,-29.23547 54.7144,0.49848 63.8418,-0.45804 76.4702,-8.01357 19.3384,-11.57006 15.5994,-19.3839 -8.785,-18.35916 -10.7021,0.44974 -63.6899,1.79425 -117.75074,2.98775 -54.06083,1.19355 -101.84728,3.28007 -106.19217,4.63678 -11.81153,3.68817 -72.07699,71.79151 -70.52863,79.70133 1.6109,8.22941 16.18789,12.27465 63.1275,17.51826 43.62277,4.87308 80.69676,5.79941 93.80357,2.34375 z m 84.97497,-136.18205 c 16.5268,-1.88671 -4.062,-2.34097 -48.55711,-1.07138 -42.4875,1.21231 -78.07768,2.91471 -79.0893,3.78315 -3.27902,2.81495 98.10001,0.6612 127.64641,-2.71177 z m 51.7839,-7.40452 c -4.295,-1.10109 -10.2543,-1.0287 -13.2429,0.16112 -2.9886,1.18963 0.5255,2.09052 7.809,2.00201 7.2836,-0.0886 9.7288,-1.06183 5.4339,-2.16292 z m 28.5009,-4.29995 c -3.1865,-1.25453 -6.9939,-1.10027 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1884,-1.23745 2.6669,-2.62378 z m -64.8348,-44.89485 c 76.087,-8.35331 152.2929,-26.52717 152.2929,-36.31936 0,-3.12012 -3.9729,-9.21016 -8.8286,-13.53343 -18.9253,-16.850022 -4.7949,-30.708643 37.2894,-36.572065 16.6134,-2.314699 32.0588,-6.031099 34.323,-8.258681 5.7737,-5.680283 12.4355,-36.239361 9.7694,-44.814636 -3.6982,-11.8951722 -18.4022,-12.2678761 -60.3023,-1.528469 -66.2797,16.988128 -175.7719,74.593043 -222.61701,117.120991 -17.67264,16.0439 -20.23473,20.17618 -14.76248,23.80937 8.35201,5.54517 23.02719,5.56459 72.83569,0.0964 z"
+ id="path3241"
+ style="fill:#7e818b"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 898.77139,621.28451 c -174.32565,-8.16709 -304.5857,-58.05367 -304.5857,-116.64914 0,-20.3095 3.40024,-21.45845 17.99126,-6.07913 5.88588,6.20385 17.73148,14.82747 26.32358,19.16355 l 15.62198,7.88376 10.27447,-26.76355 c 5.65094,-14.71994 21.81906,-48.644 35.92915,-75.38674 l 25.65468,-48.6232 -14.45957,-4.69484 c -7.95273,-2.58218 -17.35092,-3.60331 -20.88482,-2.26914 -9.94296,3.7537 -67.68667,-26.71869 -82.87049,-43.73223 -17.87176,-20.02547 -18.73793,-32.1139 -1.44095,-20.11007 44.45856,30.85356 118.67731,55.30519 215.19641,70.89727 46.72897,7.54879 231.20501,7.83694 275.89281,0.43081 88.0073,-14.58518 171.9909,-42.5276 211.9442,-70.51653 14.9373,-10.46416 15.9462,-9.12491 6.5676,8.71789 -11.1049,21.127 -52.4143,47.29093 -78.9362,49.99549 -7.5727,0.77221 -35.2704,6.33871 -61.5505,12.36998 -26.2801,6.03127 -67.0019,13.29657 -90.4929,16.14509 -47.6032,5.7724 -174.80783,7.50715 -239.14644,3.26136 -23.06464,-1.52209 -33.98999,-1.4427 -24.27857,0.17632 l 17.65715,2.9438 -22.07143,2.94324 c -50.1782,6.69126 -68.87689,15.38753 -99.32143,46.19163 -35.58995,36.01036 -61.92448,77.68086 -52.65417,83.31754 10.48309,6.37405 74.93912,25.10253 110.03989,31.97341 90.29659,17.67526 249.2356,20.10943 337.6928,5.17174 87.4072,-14.76033 156.1096,-39.20636 188.2788,-66.99426 8.7691,-7.57481 17.1714,-13.77237 18.6719,-13.77237 1.5003,0 2.7279,7.81149 2.7279,17.35892 0,73.79465 -194.6767,127.3822 -423.77141,116.64914 z M 796.05525,388.72404 c -4.29497,-1.10109 -10.25425,-1.0287 -13.24286,0.16112 -2.9886,1.18964 0.52543,2.09052 7.80901,2.00201 7.28357,-0.0886 9.72882,-1.06183 5.43385,-2.16292 z m -24.47046,-4.29995 c -3.18659,-1.25452 -6.99391,-1.10027 -8.46073,0.34265 -1.46682,1.44309 1.14039,2.46952 5.79375,2.28095 5.14242,-0.20845 6.18839,-1.23745 2.66698,-2.62378 z m 69.80089,-16.92811 c -23.06464,-3.26939 -54.35089,-7.25314 -69.525,-8.85279 -15.1741,-1.59964 -27.53636,-4.09158 -27.47164,-5.53757 0.58162,-13.00169 119.88285,-143.77202 131.16255,-143.77202 17.64875,0 -0.31209,-3.93102 -38.5802,-8.44399 -23.06464,-2.72002 -61.79999,-9.04057 -86.07856,-14.04571 -24.27857,-5.00515 -51.09536,-9.76548 -59.59286,-10.57851 -22.57258,-2.15974 -69.40815,-27.04232 -84.25485,-44.76252 -17.80228,-21.24773 -17.07962,-29.98078 1.487,-17.97022 58.42055,37.79167 154.96963,66.42552 244.73205,72.58082 l 40.5713,2.78212 11.56048,-13.51649 c 18.69203,-21.85477 111.57745,-93.112763 153.31985,-117.620808 76.7645,-45.070517 149.3593,-71.759285 218.5806,-80.358967 22.3882,-2.781426 23.175,-2.521766 23.175,7.647815 0,16.8270944 -13.5926,82.662027 -18.1936,88.119435 -2.3073,2.736695 -19.9264,5.678633 -39.1538,6.53765 -19.939,0.89085 -37.3193,3.884208 -40.4528,6.967072 -4.3826,4.311632 2.2754,5.405163 32.9102,5.405163 43.6577,0 45.6915,2.12648 26.265,27.46106 -10.2088,13.3135 -18.4574,17.84432 -51.8679,28.48996 -61.8136,19.69577 -133.7918,31.7492 -236.16426,39.5478 -49.59714,3.77824 -49.16264,3.82176 18.89774,1.89314 80.85772,-2.2913 120.86962,-7.14057 179.36002,-21.73778 54.6736,-13.6447 94.6438,-28.83136 124.3008,-47.22805 13.1141,-8.13491 24.658,-13.98987 25.653,-13.01103 0.9949,0.97888 -3.2414,9.28785 -9.414,18.46435 -11.7619,17.48595 -69.647,51.75796 -78.4147,46.42697 -2.4417,-1.48469 -9.7971,1.4466 -16.3451,6.51398 -7.9419,6.14601 -15.364,8.35948 -22.2932,6.64848 -13.5024,-3.33406 -43.5282,2.04892 -36.2188,6.49327 2.9773,1.81032 13.3705,2.24147 23.0959,0.95812 26.8896,-3.54829 20.4812,4.78252 -36.617,47.60157 l -50.3774,37.77895 -66.3315,-0.65594 c -43.87287,-0.43385 -69.32065,1.06669 -75.16004,4.43184 -7.11989,4.10309 0.99635,5.12257 41.93571,5.26762 27.92033,0.099 58.33273,1.41026 67.58313,2.91406 l 16.8188,2.73422 -11.913,7.68833 c -10.09,6.5119 -19.7564,7.44574 -63.1688,6.10267 -47.23231,-1.46124 -48.83024,-1.26403 -20.35584,2.51248 56.78124,7.53077 56.20054,7.03564 28.59054,24.37832 -13.2969,8.35227 -35.54159,20.90664 -49.4325,27.8986 -29.13455,14.66483 -52.53936,15.78077 -122.62232,5.84653 z m 119.4759,-23.6866 c 11.98245,-6.30904 10.65988,-11.57341 -4.89584,-19.48744 -25.13339,-12.78667 -28.75704,-43.38992 -6.32819,-53.4438 7.0422,-3.15674 36.17278,-5.0874 76.76055,-5.0874 59.4907,0 66.464,-0.81468 77.0423,-9.00092 6.397,-4.95051 11.631,-12.05924 11.631,-15.79723 0,-5.92053 -4.9772,-6.33653 -38.625,-3.22822 -21.2438,1.96241 -75.3739,3.71818 -120.2893,3.90171 -54.06935,0.22105 -84.75967,2.06633 -90.82476,5.46136 -14.00909,7.84181 -68.08952,68.97013 -68.08952,76.96316 0,9.54586 14.29858,14.23041 58.04958,19.01837 51.92012,5.68193 95.55811,5.97147 105.56918,0.70041 z M 1081.9642,205.25674 c 13.7782,-3.03374 10.384,-3.40449 -15.45,-1.68768 -18.2089,1.21014 -61.9103,3.84578 -97.11424,5.857 l -64.00714,3.65677 79.45714,-1.96909 c 43.70144,-1.08303 87.40284,-3.71866 97.11424,-5.857 z m 38.3491,-7.5755 c -3.1865,-1.25453 -6.9939,-1.10027 -8.4607,0.34265 -1.4668,1.44309 1.1404,2.46952 5.7938,2.28095 5.1424,-0.20845 6.1884,-1.23745 2.6669,-2.62378 z m -59.143,-45.09515 c 74.9344,-8.09951 151.0154,-27.0715 151.0154,-37.65804 0,-1.64034 -5.3005,-7.14052 -11.7789,-12.22262 -7.9781,-6.258579 -10.8469,-11.629477 -8.8906,-16.644913 3.7675,-9.659035 29.4161,-20.041199 49.5107,-20.041199 22.4487,0 31.8858,-6.355511 37.1553,-25.022543 6.8256,-24.179812 5.719,-31.1335946 -5.7746,-36.2856996 -33.3733,-14.9597964 -212.1053,65.5831336 -278.43084,125.4706946 -30.83882,27.8454 -19.16308,31.73843 67.19354,22.40432 z"
+ id="path3239"
+ style="fill:#7b7e88"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 821.52139,615.08881 c -136.80515,-18.35578 -227.3357,-62.34097 -227.3357,-110.45344 0,-20.3095 3.40024,-21.45845 17.99126,-6.07913 5.88588,6.20385 17.75302,14.83828 26.37143,19.18756 l 15.66987,7.90778 10.46614,-26.78757 c 13.22652,-33.85261 52.11532,-109.88283 60.3458,-117.98018 5.35991,-5.27314 3.89335,-6.74454 -10.53129,-10.56582 -9.21708,-2.44177 -24.24622,-5.38271 -33.39809,-6.53548 -32.88471,-4.14217 -86.91512,-42.3885 -86.91512,-61.52434 0,-5.40803 2.47668,-4.92389 13.18437,2.57727 39.38068,27.58778 113.55427,52.64363 200.90848,67.86691 51.65932,9.0027 197.66066,12.63428 253.82146,6.31347 108.0349,-12.15913 196.1684,-38.77459 248.3035,-74.98516 6.6766,-4.63726 12.1393,-7.22412 12.1393,-5.74855 0,8.13699 -15.1342,29.28584 -27.7102,38.72278 -16.4883,12.3728 -52.1087,28.96416 -56.4789,26.30694 -1.6035,-0.97497 -22.0161,2.99414 -45.3614,8.82034 -75.619,18.87193 -137.8656,25.34935 -241.24948,25.10458 -64.09097,-0.152 -109.851,-2.4918 -140.9311,-7.20684 -25.31315,-3.84012 -48.15707,-6.7818 -50.76428,-6.53704 -7.74827,0.72739 47.08625,12.15236 61.47395,12.8083 7.85871,0.35829 -0.009,3.16781 -19.3479,6.91023 -17.92487,3.46855 -40.01121,10.03087 -49.08072,14.58292 -35.10672,17.62032 -102.32887,104.36758 -87.74708,113.23379 10.29924,6.26231 75.04626,25.03526 109.82571,31.84321 90.29659,17.67526 249.2356,20.10943 337.6928,5.17174 87.4072,-14.76033 156.1096,-39.20636 188.2788,-66.99426 8.7691,-7.57481 17.1714,-13.77237 18.6719,-13.77237 1.5003,0 2.7279,7.81149 2.7279,17.35892 0,49.08549 -91.269,92.54693 -232.6349,110.77851 -62.5676,8.0692 -207.12925,7.89409 -268.38651,-0.32528 z m 55.17857,-243.66407 c -8.4975,-0.85107 -34.89722,-4.44687 -58.66607,-7.99068 -23.76886,-3.54381 -50.08903,-6.44332 -58.48929,-6.44332 -8.40025,0 -15.22028,-1.46572 -15.15556,-3.25715 0.2419,-6.69282 41.4316,-56.83366 81.82187,-99.60251 30.30531,-32.08993 45.08817,-44.60683 53.85512,-45.6 6.64615,-0.75288 -15.72607,-4.80893 -49.71607,-9.01347 -33.98999,-4.20449 -78.68463,-11.75155 -99.32142,-16.77129 -20.63678,-5.01969 -42.4875,-9.63827 -48.55714,-10.26351 -26.85069,-2.76597 -71.06104,-29.83369 -81.65854,-49.99545 -9.39007,-17.8646 -8.38357,-19.2042 6.56227,-8.73405 50.26305,35.21132 176.73489,70.95942 263.44218,74.46362 20.68719,0.83605 23.89581,-0.34742 34.47464,-12.71623 18.77934,-21.95683 111.57565,-93.172217 153.41925,-117.739672 74.9833,-44.024714 149.59,-71.294686 220.7923,-80.703225 20.0767,-2.652921 20.9679,-2.318304 20.9679,7.872818 0,17.6181894 -13.6658,82.86536 -18.5756,88.688697 -2.8847,3.421563 -16.3053,5.428571 -36.2998,5.428571 -21.0591,0 -34.6033,2.143374 -40.2916,6.376183 -7.748,5.765403 -4.8691,6.389385 30.0563,6.514285 21.2437,0.076 38.625,1.212309 38.625,2.525198 0,5.602325 -13.0128,26.461855 -20.8739,33.460935 -20.321,18.09269 -120.4799,42.97478 -204.2547,50.7422 -132.18468,12.25593 -135.47076,12.86523 -59.59286,11.0494 38.84576,-0.92959 86.48336,-3.90288 105.86146,-6.60731 81.271,-11.34233 179.167,-43.15905 212.7432,-69.14271 6.384,-4.94048 12.5664,-8.03915 13.7386,-6.88595 3.0076,2.95896 -11.6011,24.75702 -22.7916,34.008 -15.2249,12.58616 -50.1965,29.15977 -61.5295,29.15977 -5.7789,0 -14.9186,3.76421 -20.3104,8.36486 -6.7717,5.77804 -14.1475,7.75708 -23.8505,6.39955 -7.7258,-1.08094 -23.9791,0.99642 -36.1184,4.61641 l -22.0714,6.58173 32.476,-2.43291 c 25.7534,-1.92927 32.0738,-1.24527 30.5328,3.30444 -1.0688,3.15556 -24.5725,22.68686 -52.2305,43.40294 l -50.2872,37.6656 -69.4695,-0.69681 c -50.12908,-0.50282 -70.99033,0.79935 -74.93203,4.67726 -4.34043,4.2702 4.91959,5.38388 45.08112,5.42188 27.79891,0.0261 58.11201,1.27819 67.36241,2.78199 l 16.8188,2.73422 -11.535,7.4444 c -9.6254,6.21202 -20.4481,7.23068 -65.376,6.1533 -46.18722,-1.10756 -49.62144,-0.76178 -24.15861,2.43244 16.32531,2.04796 35.85791,5.05799 43.40581,6.68895 16.4347,3.55129 16.9592,3.09798 -42.0029,36.30411 -41.43659,23.33617 -49.31406,24.80163 -103.92094,19.33249 z m 88.7056,-30.13178 c 5.08552,-6.02849 4.3874,-8.12909 -4.97733,-14.97608 -29.47612,-21.55143 -34.03966,-34.9521 -17.05508,-50.08161 8.20042,-7.30477 17.3361,-8.3699 78.05225,-9.10011 60.0546,-0.72226 70.2007,-1.88072 80.1018,-9.14597 6.2354,-4.57542 12.04,-11.84206 12.8992,-16.14805 1.4577,-7.30551 -1.7898,-7.63908 -48.5571,-4.98807 -27.5657,1.56256 -82.89541,2.83888 -122.95506,2.83627 l -72.83571,-0.004 -36.41785,37.25841 c -41.45831,42.41521 -46.44407,56.09851 -22.07143,60.57461 28.59592,5.25173 76.91036,9.65803 112.0238,10.21661 28.14151,0.44771 37.15274,-0.94114 41.79251,-6.44128 z M 1086.3785,205.2904 c 13.3974,-3.07722 8.0728,-3.49487 -22.0714,-1.73119 -21.8507,1.2784 -67.53857,3.93124 -101.52857,5.89516 -59.45063,3.43499 -58.61156,3.50078 22.07143,1.7312 46.12924,-1.01176 91.81714,-3.66459 101.52854,-5.89517 z m 36.3339,-7.65207 c -4.295,-1.10109 -10.2543,-1.02869 -13.2429,0.16112 -2.9886,1.18964 0.5255,2.09052 7.809,2.00201 7.2836,-0.0886 9.7288,-1.06183 5.4339,-2.16291 z m -58.4053,-44.78858 c 72.3863,-7.8316 147.8786,-26.44114 147.8786,-36.45342 0,-2.96018 -5.0186,-9.67506 -11.1524,-14.92206 -9.0466,-7.738621 -10.287,-11.130697 -6.5695,-17.964573 4.9749,-9.145275 29.369,-17.427581 51.4436,-17.466146 22.0237,-0.03909 32.9556,-11.78013 33.8958,-36.406127 1.1825,-30.9780329 -4.8269,-33.2073083 -55.399,-20.5508333 -49.4164,12.3672843 -106.2754,37.8523413 -163.7731,73.4056183 -54.5427,33.726061 -82.40257,56.231181 -82.40257,66.564531 0,6.95622 3.42513,8.15732 23.17497,8.12696 12.7463,-0.0217 41.0529,-1.96987 62.9036,-4.33395 z"
+ id="path3237"
+ style="fill:#787b85"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 822.00312,615.16095 C 685.5075,596.87522 594.18569,552.57048 594.18569,504.63537 c 0,-20.3095 3.40024,-21.45845 17.99126,-6.07913 5.88588,6.20385 17.75302,14.83828 26.37143,19.18756 l 15.66987,7.90778 10.53024,-26.78757 c 16.30239,-41.47124 54.70465,-114.44774 63.55238,-120.76981 4.71803,-3.37119 -44.57352,-18.03688 -49.84647,-14.83077 -6.7245,4.08876 -56.95501,-23.74114 -70.75009,-39.19863 -21.66033,-24.27053 -17.09026,-29.35632 11.2777,-12.55046 79.5905,47.15139 196.63687,71.53354 343.39645,71.53354 140.25204,0 263.16504,-26.38003 336.98934,-72.3259 12.7462,-7.93283 23.1676,-12.98193 23.1585,-11.22016 -0.089,17.30186 -47.7202,55.93361 -73.4366,59.56172 -6.944,0.97966 -31.9569,6.70428 -55.5843,12.72144 -72.8355,18.54882 -137.7981,25.51394 -237.3483,25.44776 -49.77107,-0.0347 -106.38428,-2.15241 -125.80714,-4.70953 -19.42285,-2.55716 -44.25321,-5.66556 -55.17856,-6.90753 l -19.86429,-2.2582 22.07143,5.94259 c 12.13928,3.26844 29.02393,6.30014 37.52143,6.73716 12.37597,0.63654 8.86282,2.28113 -17.65715,8.26585 -44.13054,9.95887 -55.99,16.77515 -86.86779,49.92753 -32.49051,34.88391 -53.90262,71.4023 -45.02934,76.79752 10.29924,6.26231 75.04626,25.03526 109.82571,31.84321 131.5766,25.75571 325.192,18.28495 433.8102,-16.7388 43.62,-14.06517 81.8597,-33.09652 95.5699,-47.56367 14.5884,-15.39369 17.9913,-14.24626 17.9913,6.0665 0,49.08549 -91.269,92.54693 -232.6349,110.77851 -62.3361,8.03932 -207.026,7.90274 -267.90478,-0.25276 z m 63.52541,-243.73621 c -8.4975,-0.85107 -34.89722,-4.44687 -58.66607,-7.99068 -23.76885,-3.54381 -52.07546,-6.44332 -62.90357,-6.44332 -10.82811,0 -19.68749,-1.29174 -19.68749,-2.87054 0,-5.99788 49.27375,-66.00804 84.59069,-103.02243 l 36.80944,-38.57864 101.52495,-0.9599 c 69.82332,-0.6602 113.24112,-3.09807 139.04632,-7.80733 56.3661,-10.28636 92.7,-13.44596 92.7,-8.06117 0,2.51977 -22.9969,21.76597 -51.1042,42.76928 l -51.1043,38.18787 -70.0672,0 c -50.16765,0 -71.63449,1.54176 -75.58572,5.42857 -4.4149,4.3429 3.80441,5.42857 41.09819,5.42857 25.63923,0 55.94333,1.25765 67.34253,2.79481 l 20.7258,2.79484 -11.5151,7.43159 c -9.6214,6.20937 -20.3701,7.2058 -65.3562,6.05863 l -53.84093,-1.37291 38.3062,5.30906 c 21.06833,2.91996 39.63053,6.612 41.24923,8.20444 3.3747,3.32015 -22.7925,20.36782 -63.3226,41.25397 -27.85418,14.35392 -37.48395,15.72748 -80.23997,11.44529 z m 79.74734,-27.61536 c 12.58482,-6.6262 10.53915,-11.69505 -8.59576,-21.29902 -20.29089,-10.18413 -26.55233,-26.24432 -16.25813,-41.70102 8.80244,-13.21679 33.97592,-16.90162 94.65272,-13.85493 39.915,2.00418 49.0358,1.17075 62.8023,-5.73878 14.0338,-7.04364 24.7796,-19.81003 20.3461,-24.17178 -0.8087,-0.79566 -56.9721,-0.20368 -124.80749,1.31515 l -123.33708,2.76179 -30.01352,28.45366 c -36.1605,34.28117 -50.69127,55.35454 -43.23015,62.69492 11.63614,11.44786 150.54179,20.96436 168.44101,11.54001 z M 898.77139,206.26476 c -49.92535,-3.00664 -131.64702,-14.95319 -173.88365,-25.41935 -24.01424,-5.95067 -44.53714,-9.95865 -45.60646,-8.90663 -3.14778,3.0968 -32.63119,-10.30252 -52.98683,-24.08097 -16.03908,-10.85658 -32.10876,-31.20669 -32.10876,-40.66139 0,-1.47557 5.46268,1.11129 12.13929,5.74855 41.11717,28.55807 113.69102,53.62899 190.91784,65.95337 90.25104,14.40286 91.28231,14.37303 107.39688,-3.10949 22.98659,-24.93794 112.7914,-93.79151 153.4964,-117.686083 74.6627,-43.82833 150.8738,-71.730057 221.3674,-81.044964 17.2613,-2.280912 20.9679,-1.572418 20.9679,4.007762 0,14.8162216 -14.1938,87.368816 -17.8134,91.054512 -2.1224,2.161136 -15.3706,4.625099 -29.4406,5.47543 -63.6241,3.845166 -75.9287,14.480953 -16.7532,14.480953 20.6368,0 37.5215,1.665269 37.5215,3.700635 0,9.856115 -22.0792,34.102155 -37.676,41.373575 -43.7912,20.4159 -184.766,45.73102 -258.2592,46.37607 -15.68316,0.13766 -36.46054,1.86721 -46.17197,3.84342 -13.96313,2.84145 -7.96037,3.6969 28.69286,4.08898 137.97321,1.47583 280.82471,-30.58488 349.83211,-78.51417 13.9846,-9.71306 15.4097,-6.29097 4.2888,10.29852 -14.7357,21.98168 -50.1438,43.20804 -75.386,45.19208 -8.1482,0.64048 -19.8427,5.70738 -25.9876,11.25981 -6.6284,5.98923 -11.8893,8.13652 -12.9346,5.27935 -1.2213,-3.3384 -16.9494,-1.71161 -51.2623,5.3022 -27.2252,5.56498 -81.2833,12.12026 -120.129,14.56724 -68.84541,4.33673 -79.81226,4.45638 -130.22141,1.42059 z m 185.40001,-55.30459 c 74.9431,-9.84621 132.4285,-26.06978 132.4285,-37.37406 0,-2.37845 -4.966,-6.55051 -11.0357,-9.27126 -15.2113,-6.818591 -14.6303,-22.257535 1.1036,-29.327489 6.6766,-3.000132 22.5058,-6.512331 35.1761,-7.804939 29.116,-2.970384 38.1382,-12.056683 38.4963,-38.76977 0.396,-29.5483649 -5.5714,-31.5176334 -57.9791,-19.132587 -66.3979,15.691263 -184.6472,78.106023 -232.07919,122.496835 -9.53031,8.91928 -16.02385,18.2926 -14.43008,20.82965 4.00372,6.37332 52.93987,5.62951 108.31957,-1.64638 z"
+ id="path3235"
+ style="fill:#767982"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 822.00312,615.16095 C 685.5075,596.87522 594.18569,552.57048 594.18569,504.63537 c 0,-20.3095 3.40024,-21.45845 17.99126,-6.07913 5.88588,6.20385 17.876,14.89999 26.64476,19.32475 l 15.94316,8.04497 12.31696,-31.26761 c 17.61786,-44.72426 43.56105,-94.52723 56.19805,-107.88291 l 10.85508,-11.47244 -29.14249,-7.77389 c -16.02836,-4.27567 -33.16519,-7.95264 -38.08187,-8.17108 -21.84974,-0.97076 -72.72491,-41.6113 -72.72491,-58.0947 0,-2.73058 10.75832,1.80593 23.90737,10.0812 78.80242,49.5936 214.47597,75.07328 375.11089,70.44631 136.29235,-3.92577 236.55195,-27.0976 305.79425,-70.67466 12.9496,-8.14967 23.5446,-13.76764 23.5446,-12.48441 0,17.89031 -42.2021,53.04861 -70.2786,58.54866 -8.69,1.70236 -35.4439,8.23397 -59.4533,14.51474 -118.9208,31.10928 -304.58639,34.80045 -439.71093,8.74174 -8.4975,-1.63873 -14.21215,-1.64381 -12.69919,-0.013 1.5129,1.63252 21.33799,6.45614 44.05576,10.71917 l 41.305,7.751 -28.49722,5.51578 c -39.76565,7.6968 -56.69421,17.3997 -86.85513,49.78243 -25.52322,27.40334 -53.28568,70.39241 -48.50015,75.1005 6.61379,6.50673 85.79261,29.01385 130.70258,37.15301 69.35681,12.5698 252.08878,13.70154 320.25258,1.98356 87.1679,-14.98498 168.172,-44.4253 191.6873,-69.66724 14.5473,-15.61535 17.9913,-14.49138 17.9913,5.87158 0,49.08549 -91.269,92.54694 -232.6349,110.77851 -62.3361,8.03933 -207.026,7.90274 -267.90478,-0.25275 z m 50.28256,-247.61824 c -25.4925,-3.14458 -64.72446,-7.09237 -87.18214,-8.77279 -22.45768,-1.68043 -40.83214,-4.05632 -40.83214,-5.27979 0,-9.45196 108.08585,-131.64511 123.73494,-139.88472 5.406,-2.8464 22.82499,-3.09433 49.35621,-0.70255 46.59005,4.20007 138.65565,-0.44636 191.06305,-9.6427 53.8981,-9.45796 90.5172,-12.41302 90.5172,-7.30456 0,2.43361 -23.5095,21.89026 -52.2432,43.23701 l -52.2433,38.81229 -68.046,-0.67853 c -46.86909,-0.46734 -70.79364,0.96989 -76.87463,4.61802 -7.31341,4.3875 -0.87389,5.32564 37.52143,5.46631 25.4925,0.0934 56.9359,1.4205 69.8741,2.9491 l 23.5241,2.7793 -12.4884,7.5699 c -10.7757,6.53179 -18.8449,7.1414 -58.8384,4.44522 -54.25132,-3.65742 -74.89318,1.44105 -23.64174,5.83945 47.01244,4.03456 51.62074,7.47632 28.90234,21.58578 -35.38794,21.9779 -76.76882,41.77429 -86.18789,41.23174 -5.26103,-0.30313 -30.42303,-3.12386 -55.91553,-6.26848 z m 96.95382,-25.56176 c 6.53455,-7.74626 2.66473,-14.66847 -11.07319,-19.80711 -16.34928,-6.1154 -26.36662,-24.10703 -20.48878,-36.79877 7.16536,-15.47173 32.58282,-20.3799 97.29017,-18.78707 52.4816,1.29187 56.6843,0.77121 70.1715,-8.69296 15.834,-11.11103 18.5009,-20.50533 6.6215,-23.32458 -4.2488,-1.00832 -25.6029,-0.42907 -47.4536,1.28675 -21.8507,1.71603 -73.55246,3.29023 -114.89278,3.49826 -84.82632,0.4269 -80.41428,-0.99296 -117.92176,37.94449 -33.21591,34.48229 -41.59515,48.31811 -33.77833,55.77488 13.62897,13.00112 161.58995,20.68377 171.52527,8.90611 z M 889.94282,204.91978 c -65.20478,-4.21105 -110.5142,-10.9311 -165.53571,-24.55147 -24.27857,-6.01008 -49.8243,-11.65736 -56.76833,-12.54956 -17.72852,-2.27778 -55.34207,-27.03372 -65.17339,-42.89479 -11.436,-18.45002 -10.34272,-21.84322 3.85959,-11.97899 41.11717,28.55807 113.69102,53.62899 190.91784,65.95337 92.04651,14.68941 90.53567,14.77166 111.75687,-6.08374 58.98094,-57.96437 146.17881,-117.706102 223.50431,-153.12918 40.609,-18.6030621 103.929,-36.926444 147.6297,-42.720597 l 21.598,-2.863637 -2.6125,16.6447813 c -8.9628,57.1026197 -13.144,77.8803677 -16.3391,81.1940977 -1.9882,2.062162 -15.2091,4.494032 -29.3797,5.404165 -63.8076,4.09831 -76.0323,14.73223 -16.9362,14.73223 20.6368,0 37.514,1.465714 37.5049,3.257143 -0.056,10.991687 -20.3149,33.137957 -36.9283,40.367937 -50.9375,22.16747 -145.2308,39.53598 -255.94687,47.14463 -33.62798,2.31096 -62.26566,5.30745 -63.6393,6.65886 -4.17145,4.104 86.34217,2.74125 138.02387,-2.07802 105.5713,-9.84434 202.734,-38.23264 254.925,-74.48204 14.237,-9.88833 15.3896,-6.26322 3.6021,11.32835 -9.9527,14.85331 -41.331,36.59061 -57.4342,39.78751 -30.2117,5.99784 -43.7247,11.25691 -46.3735,18.04788 -2.0186,5.17529 -3.874,5.89052 -6.0977,2.35065 -2.3926,-3.80864 -12.731,-3.03804 -41.1176,3.06485 -73.7334,15.85207 -186.09416,23.01067 -273.03978,17.39557 z m 229.54288,-58.56916 c 53.9796,-10.01563 87.3251,-19.6741 93.5984,-27.11063 4.5057,-5.34115 3.3583,-8.12762 -6.5462,-15.89733 -6.6051,-5.181597 -12.0094,-11.745434 -12.0094,-14.586358 0,-8.853999 18.8296,-17.571025 44.5682,-20.632566 13.5872,-1.616151 26.6982,-4.216523 29.1356,-5.778605 8.4778,-5.433088 14.5819,-21.920658 14.5819,-39.386152 0,-15.5481231 -1.3995,-17.9206259 -12.1393,-20.5791492 C 1230.1194,-7.659379 1075.3288,61.393133 999.8373,123.20124 c -39.87058,32.64369 -37.10194,35.87352 28.0519,32.72469 29.7413,-1.4374 70.9597,-5.74626 91.5965,-9.57531 z"
+ id="path3233"
+ style="fill:#737680"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 822.00312,615.16095 C 685.5075,596.87522 594.18569,552.57048 594.18569,504.63537 c 0,-20.3095 3.40024,-21.45845 17.99126,-6.07913 5.88588,6.20385 17.876,14.89999 26.64476,19.32475 l 15.94316,8.04497 12.31696,-31.26761 c 17.47413,-44.35942 43.78795,-94.48355 56.50246,-107.62916 l 10.85076,-11.21868 -32.5102,-9.40989 c -17.8806,-5.17542 -36.13601,-9.40988 -40.5676,-9.40988 -19.36944,0 -67.06928,-40.83398 -67.15505,-57.48892 -0.009,-1.76177 10.57853,3.46469 23.52805,11.61436 49.14711,30.93031 124.01869,53.34987 219.34894,65.68194 69.21008,8.9531 219.40391,6.61239 280.19931,-4.36683 78.8356,-14.2371 139.3689,-34.66199 181.7197,-61.31511 25.6957,-16.17136 27.8445,-15.4022 14.9762,5.36057 -9.06,14.61805 -42.0025,36.06412 -58.6849,38.2048 -5.4982,0.7055 -27.4062,6.34414 -48.6845,12.53023 -112.5949,32.73407 -295.05174,40.49662 -424.31583,18.05235 -21.57774,-3.74654 -40.09218,-5.96587 -41.14326,-4.9318 -2.28576,2.24878 55.70651,15.5767 70.21119,16.13615 5.59034,0.21541 -2.74749,3.80182 -18.52856,7.9694 -46.23704,12.21055 -60.14177,21.4386 -93.98204,62.37225 -28.09154,33.97999 -41.24686,59.44602 -33.32388,64.5084 12.60799,8.05587 78.95837,26.14952 129.51306,35.31803 45.8085,8.30771 67.89423,9.58538 163.32856,9.44853 84.00726,-0.12029 120.89706,-1.95033 154.49996,-7.66375 88.5029,-15.04791 166.8017,-43.57896 191.6873,-69.84825 14.5875,-15.39851 17.9913,-14.25157 17.9913,6.06228 0,48.8617 -91.8458,92.62132 -232.4634,110.7564 -62.509,8.0616 -207.10577,7.93709 -268.07628,-0.23104 z m 39.24684,-249.77486 c -14.56714,-1.84593 -45.92428,-4.78948 -69.68254,-6.54126 -23.75826,-1.75178 -44.03104,-4.51305 -45.05065,-6.13615 -2.66632,-4.24432 47.92303,-65.60111 88.932,-107.86033 l 35.07309,-36.14238 28.47117,3.57096 c 43.87804,5.50335 137.54077,2.204 196.21407,-6.91179 81.1987,-12.61544 103.7357,-14.66635 103.7357,-9.44016 0,2.52333 -23.1899,21.71824 -51.533,42.65537 l -51.5331,38.06753 -68.7562,0 c -46.32189,0 -71.63693,1.7282 -77.58483,5.29651 -7.29222,4.37482 0.004,5.36304 41.93572,5.67885 27.92031,0.21019 58.71001,1.80628 68.42141,3.54668 l 17.6572,3.16438 -11.9599,7.82635 c -7.7027,5.04049 -16.3449,6.97133 -24.2786,5.4244 -25.6315,-4.99767 -84.44388,-7.35307 -86.87734,-3.47941 -1.3886,2.21051 11.50866,5.36799 28.75611,7.03999 50.29143,4.87542 52.97773,7.10209 27.58153,22.86297 -49.20195,30.5348 -63.90112,36.04801 -94.30813,35.37222 -15.80041,-0.35134 -40.64656,-2.1488 -55.21371,-3.99473 z m 96.01071,-17.30941 c 21.55831,-5.89018 21.96377,-17.33903 0.96294,-27.19159 -24.31817,-11.4089 -29.16497,-36.39362 -9.24012,-47.63189 6.24361,-3.52158 31.7825,-5.29103 76.36671,-5.29103 61.242,0 68.0553,-0.78497 79.4571,-9.1544 6.8592,-5.03493 12.4712,-12.85207 12.4712,-17.37142 0,-6.64336 -2.9589,-8.06851 -15.45,-7.44158 -8.4975,0.42647 -64.1175,2.17291 -123.59997,3.88095 -76.21719,2.18859 -110.58315,4.74983 -116.39063,8.67447 -16.07312,10.86209 -69.00936,72.64375 -69.00936,80.54054 0,9.7161 6.33882,11.45368 68.95855,18.90281 61.48522,7.31415 75.23007,7.61407 95.47358,2.08314 z M 893.88716,204.53383 c -69.78968,-3.80004 -131.98497,-13.43571 -184.93005,-28.65057 -20.63678,-5.93038 -41.81365,-11.29082 -47.05972,-11.91206 -16.17571,-1.91559 -50.15816,-24.08292 -59.14326,-38.58016 -11.72192,-18.91306 -10.728,-22.3774 3.57085,-12.44607 41.11717,28.55807 113.69102,53.62899 190.91784,65.95337 91.91805,14.66891 90.10197,14.77322 112.29837,-6.4501 C 973.55852,111.23723 1058.923,53.041776 1136.248,17.895598 1173.9319,0.76728358 1237.9067,-17.43793 1280.0272,-23.0195 l 21.4915,-2.847915 -2.3096,14.457675 c -10.0144,62.686665 -12.3264,75.917396 -14.0994,80.686201 -1.2329,3.315988 -10.2873,5.428571 -23.2664,5.428571 -11.6864,0 -31.7948,2.691572 -44.6855,5.981243 -20.0506,5.116928 -22.5475,5.10568 -17.2788,-0.07774 3.3872,-3.332448 20.9003,-8.466877 38.9177,-11.409814 37.9595,-6.20021 44.8894,-13.718912 42.958,-46.607933 -1.0819,-18.4237018 -1.937,-19.6184218 -14.9312,-20.8624332 -19.6561,-1.88175991 -79.6502,14.0991722 -118.766,31.6364102 -72.0047,32.282584 -174.24326,101.128035 -174.24326,117.331965 0,10.18483 23.41178,11.44487 83.86366,4.51367 90.0113,-10.32037 163.3363,-29.00638 163.3363,-41.62429 0,-2.37845 -5.0224,-6.57578 -11.161,-9.32741 -8.0964,-3.62933 -9.6114,-5.970394 -5.5178,-8.526122 7.1118,-4.440007 69.656,-4.798336 69.6337,-0.399109 -0.052,10.231081 -19.6598,32.206411 -34.0589,38.170981 -55.349,22.92733 -138.2567,38.75865 -237.4031,45.33235 -101.28437,6.71544 -109.27003,13.47762 -13.24286,11.21395 138.89686,-3.2743 254.96056,-31.14133 321.13926,-77.10591 13.9846,-9.71306 15.4097,-6.29097 4.2888,10.29852 -12.6961,18.9392 -41.1815,38.07622 -60.016,40.31978 -20.05,2.38831 -46.5015,12.49909 -43.8291,16.75318 1.5994,2.54596 0.082,3.41505 -3.5607,2.03971 -3.4911,-1.31797 -30.1371,2.23058 -59.2134,7.88567 -29.0763,5.65506 -76.7031,11.35306 -105.8374,12.66217 -29.1343,1.30911 -60.91717,2.94724 -70.6286,3.64023 -9.71143,0.69303 -44.68541,-0.2115 -77.71994,-2.0104 z"
+ id="path3231"
+ style="fill:#71747d"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 822.73448,615.25128 C 684.62094,596.72435 594.18569,552.82744 594.18569,504.31465 c 0,-19.98796 3.44495,-21.09057 17.99126,-5.75841 5.88588,6.20385 17.876,14.89999 26.64476,19.32475 l 15.94316,8.04497 12.31696,-31.26761 c 26.04636,-66.12074 58.41336,-119.83037 68.51267,-113.68966 2.64694,1.60942 21.9978,6.34913 43.00185,10.53273 l 38.18927,7.60652 -24.11781,5.58813 c -13.26484,3.07348 -31.17227,9.07301 -39.79438,13.33231 -27.25411,13.4635 -65.06379,55.31175 -85.64756,94.79579 -9.29362,17.82717 -8.96837,18.10611 38.55353,33.06695 94.88617,29.87204 255.0711,41.12542 373.7825,26.25917 104.8662,-13.13241 194.0557,-42.00307 223.6537,-72.39677 6.6827,-6.86241 13.7652,-12.47707 15.7388,-12.47707 1.9736,0 3.5884,7.81149 3.5884,17.35892 0,48.61898 -92.5443,92.7114 -232.4634,110.7564 -62.2272,8.0253 -207.03863,7.94921 -267.34492,-0.14071 z m 60.58691,-219.31037 c -68.10752,-4.05467 -127.49057,-13.64712 -178.29767,-28.80139 -20.37228,-6.07644 -41.25039,-11.50276 -46.39577,-12.05842 -14.97163,-1.61689 -46.34907,-23.23533 -55.75715,-38.41561 -12.92777,-20.8594 -10.73819,-22.28173 11.31809,-7.35216 38.16945,25.83635 103.1649,47.77738 187.50786,63.29849 57.70327,10.61881 213.96755,13.07282 280.75705,4.40913 91.8346,-11.9125 175.834,-37.75419 220.0858,-67.70762 22.603,-15.29967 24.7189,-13.28067 9.3704,8.94121 -12.7222,18.41949 -35.3974,29.92289 -88.6886,44.99278 -103.8861,29.37725 -215.4136,40.10463 -339.90001,32.69359 z m 2.20714,-28.86966 c -8.4975,-0.85689 -35.31428,-4.25196 -59.59285,-7.5445 -24.27857,-3.29258 -50.90629,-5.69874 -59.1727,-5.34697 -8.26642,0.35177 -15.75039,-1.4176 -16.631,-3.93185 -3.02025,-8.623 98.82641,-126.61335 118.00718,-136.71249 4.97693,-2.62048 20.80563,-2.28595 43.90488,0.92785 40.28989,5.60555 143.05366,-0.0217 203.02736,-11.11358 52.6357,-9.73656 83.8714,-12.56376 83.8714,-7.59136 0,2.43165 -23.9661,22.2366 -53.258,44.01099 l -53.2581,39.58987 -55.9955,-2.80079 c -54.67164,-2.73457 -90.67491,0.61946 -88.15166,8.21217 0.69088,2.07888 21.12042,3.63892 45.39899,3.46668 24.27857,-0.17241 53.00127,1.05823 63.82827,2.73444 l 19.6853,3.0476 -10.8568,6.95491 c -9.443,6.04921 -17.3381,6.37462 -60.6296,2.49888 -34.63992,-3.10119 -51.18215,-3.06953 -54.40806,0.10423 -3.23735,3.18497 5.70511,5.51343 29.65353,7.7213 54.26363,5.00262 56.13693,6.82801 25.56903,24.91531 -58.1073,34.38258 -66.02061,36.40079 -120.99167,30.85735 z m 86.80653,-25.52549 c 6.78485,-9.12869 4.32618,-12.49631 -16.64265,-22.79505 -11.65252,-5.72306 -15.28844,-10.43958 -16.34831,-21.20691 -1.09478,-11.12245 1.03944,-15.39491 10.84687,-21.71429 10.37216,-6.68322 21.87195,-7.86734 76.40583,-7.86734 66.1462,0 74.3016,-1.73593 90.4061,-19.2432 14.7879,-16.07613 4.926,-18.30024 -59.6546,-13.45352 -32.5905,2.4459 -89.00309,4.45182 -125.36131,4.45759 l -66.10578,0.009 -34.36247,35.82857 c -35.08607,36.58301 -44.6581,53.40155 -34.06028,59.8454 3.20367,1.94794 21.84701,5.62369 41.42962,8.16834 19.5826,2.54466 41.56403,5.44656 48.8476,6.44863 7.28357,1.00211 27.96458,1.29847 45.95783,0.65855 25.74495,-0.91556 33.97764,-2.86229 38.64155,-9.13733 z M 850.21425,202.09154 c -40.21414,-3.92816 -107.25195,-16.40645 -141.20964,-26.28445 -20.66292,-6.01069 -42.06743,-11.50575 -47.56565,-12.21124 -16.68242,-2.14069 -49.62486,-23.58676 -58.68483,-38.20481 -11.72192,-18.91306 -10.728,-22.3774 3.57085,-12.44607 50.44442,35.03631 144.21091,63.45614 245.87967,74.52395 l 39.51187,4.30134 33.32384,-31.54169 C 1031.3397,59.614125 1173.8152,-13.254673 1280.6071,-21.625225 c 17.1497,-1.344245 17.632,-1.009584 16.7816,11.6445455 -1.2533,18.650052 -8.4698,63.9187335 -11.8696,74.4571975 -2.4861,7.706443 -7.6745,9.318164 -37.2087,11.558818 -18.8759,1.432057 -38.2021,3.820411 -42.9473,5.307492 -8.2847,2.596334 -8.2767,2.483203 0.2013,-2.847828 4.8557,-3.053333 20.6953,-6.948007 35.1991,-8.654836 34.2237,-4.027523 42.9996,-13.564741 41.1523,-44.722394 l -1.3087,-22.0698785 -16.6944,-1.356144 c -41.4758,-3.3691884 -140.3385,36.7377905 -222.0822,90.0951305 -48.64633,31.753362 -69.14084,50.095812 -67.08767,60.042992 1.36887,6.63207 6.63432,7.43002 42.86287,6.49553 66.0041,-1.70249 181.0674,-23.01384 197.7356,-36.62345 8.3939,-6.85359 6.8896,-15.63168 -3.2197,-18.78837 -4.891,-1.52721 -7.7534,-4.590311 -6.3609,-6.806949 2.9995,-4.774754 68.2325,-5.514082 68.2084,-0.773028 -0.01,1.791428 -3.8317,9.412877 -8.4948,16.936577 -5.9722,9.6358 -15.6741,16.3281 -32.8211,22.63984 -59.8853,22.04338 -141.9266,37.0569 -238.97467,43.73222 -88.1961,6.0664 -103.93912,12.46986 -31.8296,12.94666 141.52317,0.93571 278.46347,-29.96193 348.55457,-78.64393 14.0932,-9.78845 15.0865,-7.57433 4.5069,10.04577 -8.3832,13.96207 -37.0375,35.30969 -48.8715,36.40973 -17.1306,1.59235 -57.6687,16.97289 -55.1794,20.93553 1.5232,2.42466 0.4313,2.90906 -2.5164,1.11663 -2.8637,-1.74122 -19.5587,0.0591 -37.0999,4.00068 -62.9138,14.13696 -123.0164,19.37848 -215.0861,18.75763 -49.77107,-0.3357 -97.44535,-1.28935 -105.94285,-2.1194 z"
+ id="path3229"
+ style="fill:#6d707a"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 852.42139,618.85012 C 817.16127,615.66541 736.97507,600.25466 708.95711,591.2781 634.56267,567.4432 594.18569,536.96199 594.18569,504.63537 c 0,-19.60144 3.70151,-21.49306 15.8694,-8.1099 8.96365,9.85885 41.40317,31.40607 43.75692,29.06449 0.77355,-0.7696 7.58758,-16.7177 15.14232,-35.44032 17.4121,-43.15193 28.46486,-65.06912 45.46109,-90.14794 l 13.52745,-19.96047 31.04072,8.01761 c 17.07243,4.40969 34.76855,8.01761 39.3248,8.01761 4.5562,0 7.05637,1.95429 5.55586,4.34286 -1.5005,2.38857 -7.08042,4.36196 -12.39981,4.38528 -31.26533,0.13724 -83.61302,41.11735 -112.23926,87.86616 -22.56583,36.85166 -22.68219,35.98209 6.25036,46.70669 38.3642,14.22069 109.93584,29.15777 167.86526,35.03366 66.60534,6.75592 209.6707,3.04899 266.1449,-6.89598 84.9736,-14.96366 160.6303,-43.14785 185.0658,-68.94203 14.5875,-15.39851 17.9913,-14.25157 17.9913,6.06228 0,48.61898 -92.5443,92.7114 -232.4634,110.7564 -46.2605,5.96609 -187.18758,8.01683 -237.65801,3.45835 z m 37.52143,-223.00684 c -76.14484,-3.8522 -131.76788,-13.47371 -201.33733,-34.82684 -59.68193,-18.31831 -77.57758,-28.00922 -87.86498,-47.581 -9.7384,-18.52728 -8.63227,-18.68067 16.62018,-2.30436 70.92821,45.99706 193.31472,71.74721 341.00355,71.74721 147.94076,0 272.70096,-26.1694 341.00356,-71.52816 12.7462,-8.46457 23.1619,-13.87252 23.1459,-12.01764 -0.1112,12.92461 -38.5073,48.97253 -52.1623,48.97253 -3.4833,0 -19.5274,4.68234 -35.6534,10.40518 -83.7451,29.71978 -213.9918,43.74843 -344.75518,37.13308 z m -17.65714,-34.84339 c -15.78107,-1.84363 -50.04696,-4.76985 -76.14643,-6.50265 -26.09946,-1.73284 -47.45357,-4.14955 -47.45357,-5.37051 0,-5.65083 51.45269,-67.60143 84.71328,-101.99733 l 36.9432,-38.2042 19.73247,3.46134 c 41.42524,7.26656 157.60627,2.62739 229.77837,-9.17511 37.8338,-6.18709 71.1064,-11.24926 73.9393,-11.24926 12.4239,0 1.7979,10.43002 -48.3352,47.44393 l -53.4856,39.48917 -60.1822,-2.48703 c -59.46929,-2.45753 -84.51215,0.3913 -87.76217,9.98358 -1.19623,3.53066 9.35873,4.32579 37.23379,2.80488 21.39688,-1.16745 51.73458,-0.55993 67.41728,1.35002 l 28.5139,3.47259 -11.2806,7.26191 c -9.4663,6.09398 -16.1821,6.6407 -41.7568,3.39924 -40.16354,-5.09048 -76.82617,-5.14611 -76.82617,-0.11652 0,2.21673 11.42196,5.24782 25.38214,6.73582 13.96018,1.48799 33.21853,3.81906 42.79643,5.18016 16.326,2.32004 16.8777,2.86124 8.8286,8.66 -4.7222,3.40193 -22.8938,13.91434 -40.3814,23.36092 -34.48308,18.62734 -47.11243,20.04094 -111.66862,12.49905 z m 92.66088,-13.95369 c 16.16194,-8.04466 16.10751,-18.25029 -0.13243,-24.86828 -18.58935,-7.57533 -26.31417,-15.86042 -26.31417,-28.22254 0,-20.07911 13.51729,-23.82178 86.03604,-23.82178 63.8203,0 67.058,-0.43038 82.8893,-11.01371 10.2838,-6.875 16.4747,-14.45824 16.4747,-20.18012 0,-8.816 -1.3077,-9.04292 -34.2108,-5.93591 -18.8159,1.7768 -74.5837,3.93233 -123.9286,4.79009 -49.34487,0.85776 -93.49055,2.73756 -98.10149,4.17735 -4.61094,1.43979 -23.39598,18.6253 -41.74444,38.19004 -49.33551,52.60572 -46.51457,58.75521 30.92101,67.40553 52.53874,5.86911 95.69129,5.66126 108.11088,-0.52067 z M 852.42139,202.06882 c -59.89766,-7.32961 -111.78705,-17.21343 -147.52193,-28.09976 -23.26077,-7.08624 -46.92509,-13.59939 -52.58739,-14.4737 -15.03351,-2.32125 -44.51268,-23.71612 -51.81214,-37.60328 -9.02978,-17.17912 -7.96253,-18.53318 6.57861,-8.34653 38.3201,26.8448 115.18288,53.39729 196.78571,67.98034 63.80422,11.40226 246.03815,11.4506 308.99995,0.0821 82.0148,-14.80892 158.56,-41.28363 196.7858,-68.06229 21.1624,-14.825127 15.2333,-0.3774 -9.4513,23.03034 -12.2893,11.65362 -24.6891,20.41938 -27.5551,19.4795 -2.8661,-0.93988 -17.145,2.9369 -31.7309,8.61505 -70.8304,27.57363 -162.7234,40.34797 -284.7556,39.58484 -49.77107,-0.31138 -96.45214,-1.29517 -103.73571,-2.18646 z M 916.613,167.73897 c 96.8691,-97.856116 257.165,-181.265636 363.9941,-189.403194 20.288,-1.545449 20.0366,-2.668642 11.362,50.769258 -7.6323,47.015856 -6.6114,45.585797 -32.6273,45.703619 -12.5827,0.05689 -30.5696,1.85935 -39.9708,4.005243 -11.6092,2.649881 -15.65,2.481682 -12.5947,-0.524183 2.4743,-2.434215 16.4517,-5.812436 31.061,-7.507106 31.1475,-3.61317 38.8288,-8.783168 44.8768,-30.204788 6.6819,-23.667094 5.5934,-30.119884 -6.383,-37.8401378 -10.2051,-6.5784295 -13.7424,-6.3037438 -55.6732,4.3232707 C 1151.707,24.535957 1037.5888,85.006245 987.70653,130.49997 c -10.56815,9.63841 -18.36034,20.08181 -17.31592,23.20758 1.04442,3.12573 9.69108,6.3315 19.21486,7.12389 27.14103,2.2582 111.83243,-7.53889 162.45853,-18.79315 68.3924,-15.20377 86.3118,-26.91798 60.6225,-39.62983 -8.3812,-4.14725 -8.7663,-5.272485 -2.6011,-7.599996 9.2002,-3.47333 63.9003,-3.597405 63.9003,-0.145051 0,1.422894 -3.4364,8.310277 -7.6364,15.305267 -14.8483,24.72936 -144.0937,56.75359 -253.8796,62.9059 -27.33012,1.53159 -60.53725,5.47564 -73.79361,8.76462 -41.7127,10.3492 -44.39509,8.65922 -22.06309,-13.90036 z"
+ id="path3227"
+ style="fill:#696c75"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 852.42139,619.08798 c -150.11232,-15.58031 -258.2357,-63.50165 -258.2357,-114.45261 0,-20.26755 3.38726,-21.44559 17.91595,-6.23074 11.19366,11.72228 38.91882,30.05882 41.37969,27.36721 0.79501,-0.86962 11.86732,-25.56936 24.6051,-54.88833 12.73773,-29.31898 29.0354,-61.28731 36.217,-71.0408 11.95111,-16.23113 14.24137,-17.46281 27.03057,-14.53733 7.68523,1.75799 24.89847,5.64259 38.25168,8.63239 13.35322,2.98984 21.29893,6.47172 17.65714,7.73749 -3.64178,1.26581 -17.51306,5.89817 -30.82504,10.29418 -28.21638,9.31786 -64.28056,42.53416 -87.04282,80.1694 -22.7138,37.55503 -22.84905,36.50766 6.10058,47.2386 37.00708,13.71765 107.841,28.76756 164.23151,34.89395 64.71687,7.03095 213.50145,3.35129 269.77865,-6.67202 86.9961,-15.49458 158.3727,-41.80691 183.5544,-67.66567 6.7792,-6.96147 13.9407,-12.65725 15.9143,-12.65725 1.9736,0 3.5884,7.81149 3.5884,17.35892 0,43.42214 -71.0132,82.227 -192.0214,104.92933 -39.2839,7.37 -68.7898,9.32424 -154.50001,10.23286 -58.26857,0.61768 -113.88857,0.29835 -123.6,-0.70958 z M 894.3571,395.62935 c -75.96345,-3.33783 -140.03022,-13.96571 -198.64284,-32.95238 -25.4925,-8.2579 -49.86708,-15.74247 -54.16571,-16.6324 -17.70734,-3.6659 -47.36286,-33.23016 -47.36286,-47.21711 0,-1.17557 10.42875,4.62901 23.175,12.89911 69.88441,45.34295 193.60822,71.16219 341.00355,71.16219 148.09196,0 270.78886,-25.60492 341.00356,-71.16219 12.7462,-8.2701 23.1619,-13.68317 23.1459,-12.02906 -0.1141,11.80124 -33.2287,44.26466 -45.1525,44.26466 -3.4473,0 -19.0165,5.09647 -34.5984,11.32552 -78.6034,31.42261 -208.6621,46.48203 -348.4057,40.34166 z M 748.68568,349.77734 c 0,-6.27343 49.97475,-66.55025 84.65651,-102.10804 l 36.73634,-37.6643 26.48572,4.44353 c 40.85364,6.85403 153.53515,1.18786 228.28035,-11.47908 35.7235,-6.05399 67.0098,-11.00728 69.525,-11.00728 10.7633,0 2.7355,9.53696 -23.5767,28.00913 -28.4627,19.98196 -37.2904,23.07008 -44.9065,15.70933 -2.5444,-2.4591 -54.0541,-2.88605 -130.97951,-1.08572 l -126.78591,2.96726 -28.96694,28.22857 c -39.87402,38.85763 -52.74705,57.68109 -46.85473,68.51283 3.81262,7.00863 2.72273,9.25988 -6.47991,13.38503 -11.94792,5.35578 -37.13372,6.77247 -37.13372,2.08874 z m 229.54285,-14.48842 c 0,-5.62383 -5.04407,-10.84347 -14.34643,-14.84576 -14.14522,-6.0859 -13.91303,-6.17137 16.55357,-6.09385 16.995,0.0434 35.77763,1.41712 41.73913,3.05303 10.356,2.84183 9.9276,3.49991 -9.6147,14.7671 -24.40671,14.0719 -34.33157,14.9737 -34.33157,3.11948 z m 23.17497,-31.82958 c -27.31336,-2.35826 -49.66068,-5.66804 -49.66068,-7.35502 0,-4.70988 51.19688,-7.68556 90.03808,-5.23323 l 34.7936,2.19679 -10.548,7.97917 c -5.8014,4.3885 -11.5412,7.69133 -12.7551,7.3396 -1.2139,-0.35177 -24.5545,-2.56906 -51.8679,-4.92731 z M 836.97139,200.15258 c -52.65351,-5.90242 -105.16677,-16.98865 -145.67142,-30.75307 -20.63678,-7.01285 -41.94547,-13.42247 -47.35261,-14.24353 -12.27551,-1.86404 -31.66486,-16.62776 -41.91113,-31.91249 -11.28688,-16.83708 -9.64799,-19.98912 5.04231,-9.69794 21.76401,15.2466 75.66809,38.44245 115.12143,49.53875 78.85084,22.17689 117.73138,26.54749 236.16427,26.54749 118.43286,0 157.31346,-4.3706 236.16426,-26.54749 39.1629,-11.01461 93.3235,-34.2684 114.7102,-49.25065 11.9437,-8.36703 12.3876,-8.39005 10.2994,-0.53408 -2.8347,10.66427 -28.3009,34.89082 -39.246,37.33545 -4.6825,1.04589 -22.4186,7.01276 -39.4136,13.25979 -40.8925,15.03123 -94.1446,27.23992 -151.6909,34.77694 -51.5438,6.75085 -197.55685,7.60813 -252.21621,1.48083 z m 81.66428,-33.97421 C 990.52125,92.009493 1124.8853,13.009059 1216.5999,-9.0122224 c 82.5661,-19.8246646 91.5132,-18.7460286 79.5812,9.59410941 -5.2166,12.39012799 -5.4219,12.45383699 -13.7456,4.26485929 -10.7393,-10.5655197 -19.5195,-10.4377094 -63.3278,0.9219017 -59.7104,15.483023 -150.222,61.21752 -209.9792,106.100032 -35.51727,26.67639 -42.63804,35.42699 -36.5865,44.96026 4.25228,6.69885 2.70106,8.86802 -10.90756,15.2525 -8.72568,4.09366 -25.94289,8.53919 -38.26038,9.87896 l -22.39553,2.43595 17.65714,-18.21798 z m 303.48213,-43.64254 c 11.2644,-7.04859 9.4468,-16.84017 -3.7302,-20.09384 -8.4636,-2.08987 -9.9046,-3.795352 -5.5178,-6.530527 8.1214,-5.063814 61.1159,-4.833426 61.1159,0.265783 0,11.208564 -20.8762,26.272934 -39.7286,28.668414 -15.5977,1.98195 -18.2051,1.48587 -12.1393,-2.30975 z"
+ id="path3225"
+ style="fill:#666871"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 852.42139,619.10804 C 742.94376,607.62079 652.26497,578.25743 613.54224,541.7549 c -16.80823,-15.84453 -19.35655,-20.6317 -19.35655,-36.36257 0,-21.0266 3.28626,-22.30835 17.91595,-6.9877 11.59611,12.14375 38.88663,29.97887 41.67492,27.2357 0.95741,-0.94196 10.82718,-23.04958 21.93282,-49.12809 11.10559,-26.07846 25.17833,-55.78621 31.27265,-66.01716 12.68202,-21.29003 27.55092,-31.5796 33.03016,-22.85745 1.89567,3.01759 14.5915,6.39577 28.21297,7.5071 22.93553,1.87121 39.64669,9.43799 21.5532,9.75923 -4.39204,0.0782 -18.67274,3.77229 -31.73488,8.2096 -20.30143,6.89659 -27.54571,12.70807 -49.89912,40.02968 -21.87848,26.74114 -47.74439,67.33722 -47.74439,74.93396 0,3.73655 50.49647,20.61524 87.63937,29.29396 122.02586,28.51211 298.98966,28.42665 421.52086,-0.20368 57.4749,-13.42938 105.2426,-33.10964 127.8205,-52.66188 10.9425,-9.47611 21.0804,-17.22928 22.5288,-17.22928 1.4483,0 2.6333,7.81149 2.6333,17.35892 0,43.44759 -71.2694,82.36901 -192.0214,104.86614 -38.9769,7.26169 -69.4596,9.28459 -154.50001,10.25292 -58.26857,0.6635 -113.88857,0.37262 -123.6,-0.64639 z M 803.86425,387.52749 c -58.33244,-9.06958 -121.14362,-25.80404 -148.62572,-39.59747 -9.77103,-4.90413 -18.83863,-7.86092 -20.1502,-6.57057 -6.41392,6.31013 -40.90264,-29.63905 -40.90264,-42.6347 0,-1.23207 8.80385,3.60536 19.56416,10.74988 80.65129,53.55016 233.4788,81.10237 404.20725,72.87179 117.962,-5.6868 224.8772,-32.93766 284.4723,-72.50708 15.3903,-10.2187 18.6228,-11.15046 16.9582,-4.88789 -3.1413,11.81779 -25.6956,31.1356 -44.2397,37.89121 -119.2281,43.43505 -155.9605,49.66691 -303.5408,51.49739 -96.1638,1.19272 -123.35955,0.0882 -167.74285,-6.81256 z m -55.17857,-37.5549 c 0,-5.87202 33.66679,-47.22418 72.65067,-89.23529 l 41.16347,-44.35989 100.90364,-0.65894 c 83.43634,-0.54486 110.07344,-2.33081 153.87504,-10.31694 86.7179,-15.81086 83.0673,-15.4088 80.864,-8.90594 -1.0918,3.22249 -11.6593,12.41805 -23.4834,20.43453 -18.5379,12.56841 -24.8805,14.4952 -46.0608,13.99278 -64.1677,-1.52222 -254.94658,6.05954 -263.70969,10.48014 -12.78527,6.44957 -70.4534,69.02302 -74.13796,80.44426 -1.59846,4.95472 -1.75159,10.84663 -0.34035,13.09315 3.83827,6.10992 -15.45918,17.70743 -29.46398,17.70743 -6.74335,0 -12.26064,-1.20388 -12.26064,-2.67529 z M 983.82571,330.9336 c -4.15759,-4.77715 -10.09741,-8.68572 -13.19964,-8.68572 -3.10223,0 -5.6404,-1.95428 -5.6404,-4.34286 0,-5.01682 16.30867,-5.54904 46.26283,-1.50979 l 21.0094,2.83306 -17.0826,10.17328 c -20.77879,12.37453 -21.86695,12.42769 -31.34959,1.53203 z m 23.09569,-28.14567 c -50.8766,-5.30741 -44.11872,-10.82596 13.2428,-10.81424 38.0384,0.009 53.8763,4.66072 39.2214,11.52273 -5.0028,2.3425 -25.9256,2.05995 -52.4642,-0.70849 z M 836.97139,200.17764 c -45.87228,-5.10759 -112.4503,-19.43428 -148.51519,-31.95848 -67.55645,-23.4602 -73.44338,-26.55561 -86.76728,-45.62332 -10.93591,-15.65018 -9.2011,-19.26152 4.63606,-9.65087 78.38782,54.44449 214.39725,82.11418 382.49099,77.81371 138.74903,-3.54968 238.71493,-26.41651 310.34033,-70.9893 16.9184,-10.52839 22.2701,-12.33879 20.3293,-6.8773 -3.2927,9.26605 -28.1691,35.64152 -33.6158,35.64152 -2.2226,0 -22.1938,6.65704 -44.3805,14.79338 -47.1305,17.28383 -106.8571,30.5216 -167.0761,37.03054 -49.4045,5.34002 -188.81479,5.2344 -237.44181,-0.17979 z m 79.69476,-32.57803 c 45.53318,-46.9234 129.87755,-104.794397 211.64805,-145.217623 41.0442,-20.2901755 122.0849,-43.219809 152.7526,-43.219809 21.0224,0 25.2348,7.278151 13.2239,22.8481615 -5.2241,6.7720341 -6.5341,6.8964135 -11.4359,1.0857143 -19.1685,-22.7229568 -142.4965,21.5111692 -242.8263,87.0946942 -41.63216,27.214122 -70.12174,50.653652 -71.18033,58.562992 -0.30326,2.26749 -0.80009,6.56558 -1.10357,9.55129 -0.78565,7.7295 -37.46139,24.97143 -53.11736,24.97143 l -13.17342,0 15.21233,-15.67685 z m 315.98035,-55.94368 c 1.4986,-3.84213 -0.6715,-7.68504 -5.3337,-9.44515 -7.3599,-2.778563 -7.3188,-3.176108 0.6456,-6.245639 4.6782,-1.803024 15.9549,-3.41557 25.0594,-3.583422 13.1125,-0.241897 16.5536,1.319708 16.5536,7.511884 0,11.738527 -8.9192,18.239997 -25.0229,18.239997 -10.9756,0 -13.8239,-1.55022 -11.902,-6.47767 z"
+ id="path3223"
+ style="fill:#61646c"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 824.25136,614.89065 C 716.28726,600.47015 632.93182,569.30863 603.349,532.3087 c -5.68512,-7.11047 -9.16331,-17.83229 -9.16331,-28.2465 0,-18.09756 4.2118,-21.01018 13.80594,-9.54743 7.24477,8.65588 39.12042,31.84739 43.77268,31.84739 1.80539,0 8.26495,-12.21429 14.35455,-27.14286 17.58514,-43.1098 47.0814,-101.93957 53.90363,-107.50994 4.51992,-3.69047 10.26882,-3.87639 20.76736,-0.67158 7.98447,2.43734 23.45619,5.80862 34.38155,7.49173 l 19.86428,3.0602 -19.86428,5.86316 c -10.92536,3.22474 -24.24101,5.89282 -29.59037,5.92908 -5.34932,0.0347 -13.29504,5.01392 -17.65714,11.06143 -4.36207,6.04752 -17.10518,23.2098 -28.31795,38.13841 -29.22174,38.90544 -41.62658,63.91943 -34.08332,68.72793 21.0463,13.41608 112.58997,35.15551 181.90471,43.19796 58.47741,6.78502 222.09787,2.84314 273.21377,-6.58216 85.0966,-15.69101 149.6854,-39.17231 178.7101,-64.9701 9.7021,-8.62344 18.8893,-15.67897 20.4159,-15.67897 1.5266,0 2.7757,7.74913 2.7757,17.22029 0,49.89804 -89.5844,91.89898 -236.3973,110.83306 -63.5488,8.19575 -198.94585,7.96866 -261.89414,-0.43915 z m -4.93711,-225.15282 c -101.45939,-14.30081 -198.85517,-47.91734 -216.91922,-74.8705 -12.52479,-18.68814 -10.06493,-19.31773 13.78431,-3.5281 68.75616,45.52083 195.47211,72.33949 342.1849,72.42149 145.14486,0.0812 273.67516,-27.06382 341.84366,-72.19557 21.2954,-14.09887 25.7854,-10.718 9.9103,7.46229 -17.9433,20.54892 -110.3241,53.34913 -188.2938,66.85454 -55.122,9.54791 -244.96009,11.96762 -302.51015,3.85585 z m -70.62857,-39.86191 c 0,-5.13621 48.58897,-64.27194 81.13678,-98.74845 l 32.29253,-34.20607 107.71748,-1.04038 c 85.89273,-0.82953 118.00293,-2.85317 158.48173,-9.98775 27.9204,-4.92106 54.2406,-10.2778 58.4893,-11.9039 14.7594,-5.64866 8.16,10.02849 -8.481,20.14673 -13.8254,8.40634 -26.9559,10.65503 -89.3893,15.30849 -40.2508,3.00009 -106.18802,5.56528 -146.52722,5.70043 -84.86322,0.28446 -78.27115,-2.37845 -125.21915,50.58087 -18.34489,20.6938 -28.77258,36.25191 -28.77258,42.92879 0,12.0169 -14.8124,23.9932 -29.67508,23.9932 -5.52942,0 -10.05349,-1.2474 -10.05349,-2.77196 z m 245.60365,-25.45661 c 2.74745,-7.0439 21.59857,-8.97877 25.58907,-2.62647 2.7798,4.42506 -7.4354,9.14076 -19.8009,9.14076 -5.59001,0 -7.49345,-2.14225 -5.78817,-6.51429 z m 10.42487,-23.88571 c -7.86577,-2.4783 -1.9177,-3.60141 20.7074,-3.90997 17.4586,-0.23798 32.9707,1.52139 34.4712,3.90997 3.2585,5.18693 -38.7159,5.18693 -55.1786,0 z M 859.04282,202.04268 C 814.26276,197.52693 746.96822,185.58959 715.14409,176.51654 668.08317,163.0995 614.30022,137.77708 603.09301,123.75999 c -14.39242,-18.00092 -10.3225,-20.03012 11.09588,-5.53237 38.0364,25.74629 105.18007,48.54668 187.46822,63.65969 60.22348,11.06065 253.06809,11.0738 313.41429,0.0217 85.5752,-15.67315 148.1001,-37.03341 188.552,-64.41473 l 20.6921,-14.00615 -8.8394,14.89023 c -19.7864,33.33081 -127.6473,69.36637 -245.4772,82.01224 -39.4855,4.23772 -174.80785,5.29768 -210.95608,1.65237 z m 55.45627,-32.28145 c 7.13084,-7.43341 32.82944,-29.56227 57.10801,-49.17526 96.8554,-78.242866 212.0799,-131.831507 301.275,-140.116983 29.853,-2.773088 34.3102,1.102825 21.265,18.4917115 -5.0689,6.7567038 -7.4347,7.3525003 -11.9186,3.0014787 -8.6267,-8.3711174 -33.5656,-6.4986512 -76.4844,5.7426033 C 1121.0464,31.862183 977.80877,118.55408 965.58284,153.05786 c -4.99057,14.08436 -33.04737,29.94374 -53.29089,30.12323 l -10.75801,0.0955 12.96515,-13.51523 z m 319.75801,-61.75799 c 0,-7.29266 9.2477,-11.583923 24.9633,-11.583923 8.0894,0 10.6111,2.010091 9.5344,7.600003 -1.9188,9.96095 -34.4977,13.72329 -34.4977,3.98392 z"
+ id="path3221"
+ style="fill:#5e6068"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 825.93568,615.36498 C 716.56747,600.17844 632.6964,569.01418 603.74597,532.80522 c -8.75547,-10.95068 -14.01632,-45.52877 -6.92694,-45.52877 1.44833,0 11.58627,7.75317 22.52875,17.22928 33.40268,28.92643 115.1748,55.46654 208.60311,67.70467 26.0547,3.41287 83.46125,6.09646 130.41335,6.09646 46.95216,0 104.35866,-2.68359 130.41336,-6.09646 93.4283,-12.23813 175.2004,-38.77824 208.6031,-67.70467 10.9425,-9.47611 21.0804,-17.22928 22.5288,-17.22928 1.4483,0 2.6333,8.15215 2.6333,18.11588 0,15.73087 -2.5483,20.51804 -19.3566,36.36257 -32.0718,30.23297 -96.7046,54.56139 -185.776,69.92798 -56.0317,9.66655 -232.33704,11.89374 -291.47452,3.6821 z M 671.0056,488.36216 c 23.03926,-54.44974 48.2485,-98.88672 55.60866,-98.02275 3.64178,0.42733 19.44916,3.51337 35.12747,6.85763 23.02994,4.91233 26.42192,6.62876 17.65714,8.93486 -5.96688,1.56999 -17.95175,2.88123 -26.63302,2.91384 -17.49116,0.0656 -28.8082,9.51294 -41.69006,34.80214 -4.25846,8.36 -13.25791,22.10783 -19.99887,30.55069 -6.74093,8.44291 -15.7756,22.61148 -20.07701,31.48572 -14.40858,29.7262 -14.4043,16.53364 0.004,-17.52213 z m 177.00151,-95.34061 c -100.66606,-9.09216 -225.04558,-49.65926 -247.05944,-80.57984 -9.91259,-13.9232 -5.51887,-13.69537 17.92112,0.92928 44.22429,27.59239 136.46966,55.10495 220.30975,65.7083 57.4378,7.26417 193.55166,6.17506 250.38996,-2.00353 85.1299,-12.2496 164.7508,-36.53907 207.7815,-63.38674 13.5368,-8.4459 20.7785,-10.96172 20.7785,-7.21856 0,9.22084 -12.9558,18.3123 -52.9714,37.17142 -76.8544,36.22099 -177.3097,52.83985 -312.53681,51.70454 -47.82583,-0.40154 -94.90176,-1.44774 -104.61318,-2.32487 z m -96.1732,-43.06689 c -3.83791,-3.77577 43.05438,-62.36386 82.5979,-103.19931 l 33.1523,-34.23535 23.31864,3.83535 c 59.27277,9.74902 228.71295,-1.14925 281.23715,-18.089 23.5841,-7.60617 22.4207,-7.56039 22.2499,-0.87561 -0.076,2.98572 -5.3281,9.15188 -11.6707,13.70254 -15.349,11.01262 -105.3058,19.5892 -208.90486,19.91721 -42.48749,0.13463 -85.19571,2.00853 -94.90713,4.16445 -14.13592,3.13815 -24.91971,11.45207 -54.075,41.68974 -23.96776,24.85756 -36.41786,41.19052 -36.41786,47.77564 0,15.086 -27.57829,34.17077 -36.58034,25.31434 z M 859.04282,202.30668 C 805.06767,195.9414 746.11564,185.34652 714.46654,176.32337 652.71859,158.71908 594.18569,125.77638 594.18569,108.62852 c 0,-1.77631 8.83201,2.6346 19.62666,9.80192 110.54563,73.3991 376.36993,96.897 564.63725,49.91189 47.5645,-11.87051 97.187,-31.7991 124.7498,-50.09998 l 19.4544,-12.91714 -6.7397,12.82229 c -13.084,24.89221 -106.1797,60.79153 -198.5039,76.5465 -38.3627,6.54655 -221.6211,11.94616 -258.36738,7.61268 z m 57.62333,-34.53136 c 44.9757,-45.82938 133.91765,-107.010425 210.01895,-144.467002 62.0907,-30.5607279 173.7863,-55.492766 173.7863,-38.791614 0,11.703174 -10.8497,17.0399373 -24.3649,11.9846346 -9.6694,-3.6168182 -21.3095,-2.2464297 -56.1887,6.6149529 C 1131.4905,25.582108 984.54171,111.47209 962.37268,153.64862 c -6.87053,13.07113 -35.71113,29.34798 -52.32796,29.53247 -7.24724,0.0803 -6.2116,-2.32908 6.62143,-15.40577 z m 335.24805,-59.99235 c 0,-0.91569 3.9756,-2.68771 8.8347,-3.93785 4.8591,-1.25009 7.7215,-0.5009 6.3609,1.66493 -2.376,3.78223 -15.1956,5.69974 -15.1956,2.27292 z"
+ id="path3219"
+ style="fill:#595c63"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 859.04282,619.00581 c -79.35923,-7.75191 -162.73237,-28.55832 -207.8024,-51.85875 -39.44707,-20.39345 -53.06214,-34.99969 -55.92608,-59.99766 -2.60421,-22.73047 1.47273,-24.81035 16.86261,-8.60254 37.58296,39.58058 153.58972,72.40684 280.46176,79.36189 180.53679,9.89689 359.98819,-24.67742 411.91279,-79.36189 14.5738,-15.3484 17.9913,-14.21821 17.9913,5.94988 0,47.86567 -95.7782,92.95234 -235.2868,110.75906 -47.1129,6.01342 -182.3002,8.23483 -228.21318,3.75001 z M 672.07646,486.39168 c 8.40012,-19.59519 22.36719,-48.90948 31.03791,-65.14286 16.8035,-31.4597 25.93392,-36.30268 43.62404,-23.13904 8.42184,6.26687 8.41169,6.37852 -0.59399,6.51428 -10.76304,0.16242 -24.25045,13.59896 -35.01248,34.88096 -4.22756,8.36 -16.3785,28.16569 -27.00205,44.0126 -10.62359,15.84695 -19.31563,30.99267 -19.31563,33.65714 0,2.66452 -1.80244,4.84454 -4.00539,4.84454 -2.20295,0 2.86747,-16.03244 11.26759,-35.62762 z M 821.52139,389.26025 C 728.68606,376.80411 639.12718,346.15114 606.03593,315.5069 c -15.32344,-14.19037 -12.39818,-19.00873 4.44916,-7.32849 57.89812,40.14077 178.90516,71.57967 298.5636,77.56999 148.05921,7.41208 324.11631,-26.91395 396.94051,-77.39193 11.5568,-8.01053 15.7505,-6.48992 8.761,3.1768 -5.205,7.19872 -45.4865,31.06963 -69.3679,41.10749 -33.464,14.0656 -99.1354,29.67869 -155.3029,36.92245 -62.7406,8.09148 -207.20897,7.92849 -268.55801,-0.30313 z m -68.38196,-44.21237 c 0.0759,-6.27599 44.28434,-59.25715 79.77175,-95.60187 33.28093,-34.08492 36.79748,-36.49377 49.59891,-33.97491 23.59581,4.64282 23.74788,10.46663 0.43039,16.49235 -17.7976,4.59934 -27.18286,11.23662 -52.57776,37.18319 -17.00215,17.37143 -35.35423,39.61202 -40.78239,49.42354 -5.4282,9.81147 -12.12732,20.51561 -14.88696,23.78695 -5.26933,6.24642 -21.62196,8.28782 -21.55416,2.69075 z M 1090.0657,219.45841 c 1.5411,-1.51605 20.2129,-6.26041 41.493,-10.54298 21.2802,-4.28263 44.1539,-9.09972 50.8305,-10.70476 14.5735,-3.50338 15.5606,1.99642 2.0685,11.52529 -6.5266,4.60938 -24.371,7.84438 -53.4936,9.69778 -24.0351,1.52964 -42.4394,1.54071 -40.8984,0.0261 z m -248.68002,-19.3371 c -48.13942,-5.4419 -98.16303,-15.97476 -143.84678,-30.28804 -41.75676,-13.0829 -90.46884,-40.47212 -98.61575,-55.44847 -3.69706,-6.79622 0.0543,-5.51985 17.26316,5.87333 58.74977,38.89597 171.13519,65.7096 310.39027,74.05492 26.2182,1.57124 53.03499,4.44852 59.59285,6.39394 16.05157,4.76186 -101.69264,4.28553 -144.78375,-0.58568 z m 160.10982,2.24561 c 1.4668,-1.44309 5.2741,-1.59735 8.4607,-0.34265 3.5214,1.38632 2.4754,2.41541 -2.667,2.62378 -4.6533,0.18848 -7.2605,-0.83787 -5.7937,-2.28096 z m 25.2902,-1.55479 c 0,-2.74338 18.3855,-6.32259 90.4928,-17.61675 62.3733,-9.76952 93.6279,-18.52476 134.6357,-37.71498 23.0647,-10.79348 47.7623,-23.70423 54.8836,-28.69065 11.4483,-8.01613 12.5986,-8.1707 9.9321,-1.33452 -1.6586,4.2524 -3.0157,8.54892 -3.0157,9.54786 0,0.9989 -1.813,1.81623 -4.029,1.81623 -2.2159,0 -14.2763,6.63944 -26.8008,14.75433 -30.414,19.70593 -94.2681,40.00757 -157.8107,50.17407 -60.5632,9.68978 -98.288,13.16889 -98.288,9.06441 z M 914.64079,169.68227 c 4.62507,-5.08818 28.27346,-25.35742 52.55203,-45.04268 99.53638,-80.705003 216.61088,-136.015108 305.11228,-144.145762 23.428,-2.152363 28.1663,-1.475616 28.1663,4.022876 0,11.5620312 -11.369,17.6520195 -22.2541,11.9207511 -11.3322,-5.9666512 -27.814,-3.5452913 -79.165,11.6301277 C 1119.8159,31.483833 986.51467,111.54961 962.82956,149.95198 c -9.17514,14.87628 -31.50595,28.94323 -45.98163,28.96538 -10.52158,0.0174 -10.54127,-0.0665 -2.20714,-9.23509 z"
+ id="path3217"
+ style="fill:#565960"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 839.17854,617.11545 C 693.91336,599.91044 594.18569,552.98361 594.18569,501.83422 c 0,-17.90738 2.99191,-18.02546 19.86429,-0.78384 27.93404,28.54538 110.28681,56.79623 207.47141,71.17244 65.6825,9.71618 207.45791,9.69356 273.68571,-0.0439 97.1808,-14.28821 170.159,-39.01701 204.055,-69.14449 9.7511,-8.667 18.9784,-15.75819 20.505,-15.75819 1.5266,0 2.7757,7.37187 2.7757,16.38195 0,44.47512 -76.2102,84.77318 -203.0571,107.37154 -49.6855,8.8517 -224.84458,12.65444 -280.30716,6.08552 z M 671.83809,486.94769 c 25.68854,-59.92421 46.09671,-95.2141 55.06243,-95.2141 12.96979,0 13.89922,7.36353 2.36213,18.71394 -5.91973,5.82399 -15.88754,20.5378 -22.15062,32.69746 -9.90482,19.22986 -43.3161,72.63806 -48.84248,78.07501 -1.06852,1.05123 5.03732,-14.3713 13.56854,-34.27231 z M 909.8071,392.0894 c 4.85572,-1.89005 38.625,-4.76068 75.04286,-6.37918 77.95034,-3.46434 141.48044,-12.04573 200.09034,-27.02751 42.6203,-10.89449 110.5387,-39.32943 125.2713,-52.44651 4.3543,-3.87682 7.9169,-5.64259 7.9169,-3.9239 0,3.99334 -23.9662,23.98647 -41.5991,34.70277 -36.4523,22.15374 -123.8051,45.56943 -203.3937,54.52149 -46.2802,5.20552 -176.41333,5.64602 -163.3286,0.55284 z M 719.99283,369.45812 c -52.37934,-14.62891 -99.8507,-39.99632 -116.90251,-62.46956 -6.32947,-8.34189 -6.25032,-8.38028 2.39908,-1.16497 15.17388,12.65799 81.72678,41.21775 119.79051,51.40557 19.90286,5.32704 37.22178,12.33832 38.48643,15.58069 3.06563,7.85958 -6.1604,7.15312 -43.77351,-3.35173 z m 33.10714,-23.78617 c 0,-6.09564 58.98311,-75.68419 87.69583,-103.46392 30.28504,-29.301 31.4339,-29.94583 43.43811,-24.37946 l 12.33034,5.71764 -22.07143,9.86445 c -24.68367,11.03198 -74.06986,61.50927 -89.73373,91.71619 -8.39694,16.19299 -16.71646,23.17817 -27.60588,23.17817 -2.22926,0 -4.05324,-1.18486 -4.05324,-2.63307 z M 1155.9035,208.00772 c 11.5323,-2.45996 20.9679,-6.09945 20.9679,-8.0877 0,-1.98824 3.9728,-3.61499 8.8285,-3.61499 10.8753,0 11.2412,3.75496 1.1036,11.32595 -4.2863,3.20112 -17.5498,5.56455 -29.7964,5.30936 l -22.0714,-0.45991 20.9678,-4.47271 z m -323.34639,-9.4375 C 725.50106,185.46856 641.01994,156.17017 607.45697,120.50454 c -10.29044,-10.93514 -9.33162,-10.70414 14.72377,3.54686 54.44853,32.25665 162.171,60.41635 253.68934,66.31686 29.73683,1.91724 44.71375,4.55283 43.66765,7.68455 -2.28849,6.85112 -33.69998,7.03799 -86.98062,0.51741 z m 286.92859,-15.29376 c 0,-5.22568 25.1606,-5.22568 37.5214,0 6.4839,2.74112 2.6738,3.81346 -14.3464,4.03759 -12.7463,0.1679 -23.175,-1.64902 -23.175,-4.03759 z M 914.64079,169.74012 c 4.62507,-5.05643 28.44826,-25.42613 52.94053,-45.26599 99.68628,-80.750649 213.24108,-134.4513786 303.55958,-143.555049 28.183,-2.840706 30.1148,-2.488631 28.5233,5.198009 -1.4356,6.9338053 -5.0214,8.0374121 -22.5471,6.9392338 -30.7262,-1.9253622 -97.2942,17.1330052 -148.9918,42.6562362 -76.0388,37.540655 -159.42889,94.4772 -165.37714,112.91515 -3.38916,10.5055 -33.53312,30.30589 -46.13749,30.30589 -10.27549,0 -10.29513,-0.0916 -1.96988,-9.19348 z m 321.80741,-12.50339 c 4.3855,-4.76755 18.6982,-13.49864 31.8059,-19.40245 13.1077,-5.90377 29.6916,-14.83898 36.8533,-19.85602 20.36,-14.26299 14.9894,-3.65543 -5.8761,11.60607 -17.7369,12.97307 -61.2677,36.3207 -67.7187,36.3207 -1.671,0 0.5501,-3.90071 4.9356,-8.6683 z"
+ id="path3215"
+ style="fill:#53555c"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 839.17854,616.85093 C 696.37274,601.00688 594.18569,553.03316 594.18569,501.83422 c 0,-16.78687 6.15524,-18.92013 13.81027,-4.78634 4.97733,9.18991 4.78954,9.39498 -3.15542,3.44618 -4.64626,-3.47884 -1.49521,1.09523 7.00229,10.16463 75.50733,80.58923 382.49051,109.36296 582.68567,54.61551 44.7078,-12.22623 92.0451,-35.18092 108.15,-52.44382 l 13.2429,-14.19506 -13.2429,7.94686 -13.2428,7.94682 12.9651,-13.62627 c 16.4667,-17.30633 20.142,-17.13635 20.142,0.93149 0,29.684 -34.2455,59.80818 -92.6062,81.46145 -34.0619,12.63776 -109.1057,28.60635 -159.9377,34.0331 -50.4078,5.3815 -180.435,5.11228 -230.82036,-0.47784 z M 662.70198,511.16216 c 18.88983,-50.61852 46.38787,-106.18346 56.7252,-114.62385 13.00978,-10.62245 16.65634,-0.29314 4.79316,13.57708 -6.29768,7.36327 -19.24937,28.64826 -28.78153,47.30001 -18.21224,35.63627 -38.26312,68.55547 -32.73683,53.74676 z m 73.93637,-148.10953 c 1.46682,-1.44309 5.27414,-1.59735 8.46073,-0.34265 3.5214,1.38632 2.47544,2.41541 -2.66698,2.62378 -4.65337,0.18848 -7.26058,-0.83787 -5.79375,-2.28096 z m 16.46162,-17.24892 c 0,-5.39934 41.883,-55.61784 75.69599,-90.76093 26.55453,-27.59912 38.48321,-37.02347 46.86157,-37.02347 10.9597,0 11.04057,0.21628 3.24958,8.68572 -4.39438,4.77714 -10.45515,8.68571 -13.46843,8.68571 -8.07196,0 -71.62682,67.93544 -81.66539,87.29425 -8.95676,17.27263 -30.67332,33.6406 -30.67332,23.11872 z M 881.11425,198.47646 c 3.02242,-3.58286 10.15581,-6.51429 15.85196,-6.51429 7.39234,0 9.62941,1.86452 7.8158,6.51429 -1.48125,3.79761 -8.09204,6.51428 -15.85196,6.51428 -11.3316,0 -12.49389,-0.96876 -7.8158,-6.51428 z m 300.17145,2.42774 c 0,-2.52954 1.9864,-4.59917 4.4142,-4.59917 2.4279,0 4.4143,0.86179 4.4143,1.91511 0,1.05332 -1.9864,3.12295 -4.4143,4.59917 -2.4278,1.47623 -4.4142,0.61443 -4.4142,-1.91511 z M 912.52648,171.81205 c 14.26498,-16.91008 98.31512,-81.118833 138.70722,-105.963189 58.6574,-36.078893 107.7054,-58.7348393 155.0188,-71.6053304 46.4178,-12.6268566 94.2189,-18.5092566 94.2189,-11.5945166 0,7.9489922 -9.5456,11.0617784 -30.9719,10.0999224 -68.3632,-3.0688794 -226.2037,74.2328886 -301.06411,147.4450336 -28.75708,28.12391 -44.44488,38.73963 -57.249,38.73963 -2.73558,0 -2.18088,-2.94763 1.34009,-7.12155 z"
+ id="path3213"
+ style="fill:#4e5057"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 863.45711,618.95695 C 726.94144,605.19036 622.97412,568.53478 600.80248,526.35334 c -7.40434,-14.0867 -8.91217,-39.07689 -2.3578,-39.07689 5.55648,0 12.71509,13.8118 10.41048,20.0861 -2.18229,5.94125 36.72142,31.45006 66.99481,43.92796 128.57454,52.99497 393.68933,56.85229 540.10673,7.85831 55.2476,-18.48689 95.354,-44.53378 95.4946,-62.01856 0.031,-3.81407 2.5388,-7.7543 5.5736,-8.75616 9.5551,-3.15417 6.3622,26.78193 -4.5622,42.77441 -11.3743,16.65099 -46.2473,38.68682 -82.5261,52.14716 -34.0619,12.63776 -109.1057,28.60635 -159.9377,34.0331 -39.5592,4.2233 -170.56647,5.25603 -206.54179,1.62818 z M 1297.0556,506.47743 c 4.1909,-4.30477 8.57,-6.89202 9.7313,-5.7495 1.1613,1.14251 -2.2676,4.66457 -7.6199,7.82682 -9.2978,5.49337 -9.3919,5.40082 -2.1114,-2.07732 z m -631.73741,0.34187 c -0.14434,-5.79971 39.49722,-89.26386 48.67425,-102.48235 3.98337,-5.73756 8.65342,-9.09459 10.37795,-7.46002 1.72448,1.63456 -1.97156,9.55919 -8.21344,17.61028 -6.24189,8.05109 -20.2098,33.09661 -31.0398,55.65667 -10.82997,22.56005 -19.73954,39.06399 -19.79896,36.67542 z m 92.19606,-165.35845 c 0,-5.43517 42.168,-55.96153 74.97444,-89.83555 25.7016,-26.53803 35.9555,-34.52989 39.7057,-30.94673 3.76631,3.59858 1.75525,6.92169 -7.88458,13.02858 -15.3565,9.72847 -63.20241,60.5237 -84.42246,89.62645 -13.30571,18.24846 -22.3731,25.5951 -22.3731,18.12725 z M 916.50817,167.98204 c 11.66775,-13.83126 105.17723,-84.295372 138.97033,-104.721174 76.0483,-45.966362 154.1821,-75.527886 217.4875,-82.285328 25.1586,-2.685536 27.4049,-2.266146 24.7208,4.615198 -2.3518,6.0296222 -6.5047,7.1003536 -20.4178,5.2644108 -58.1019,-7.6670098 -232.6667,73.5299102 -303.2415,141.0495232 -28.22887,27.00684 -48.99663,42.68607 -56.53932,42.68607 -5.36773,0 -5.54522,-1.19693 -0.98001,-6.6087 z"
+ id="path3211"
+ style="fill:#494c52"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 863.45711,619.22686 c -176.1307,-20.83833 -269.27142,-61.8987 -269.27142,-118.70618 0,-7.28431 1.98643,-13.24423 4.41429,-13.24423 4.36153,0 6.10288,5.87671 4.77987,16.13111 -1.14449,8.87076 39.6006,35.10652 76.88441,49.50583 183.33044,70.80385 541.29784,46.8388 624.62144,-41.81694 8.4975,-9.04126 12.7092,-14.89604 9.3594,-13.01055 -4.0784,2.29555 -5.4456,1.52465 -4.1384,-2.3335 4.0964,-12.09025 12.4361,-9.13529 12.4361,4.40639 0,48.61064 -68.9707,86.60239 -198.6428,109.42015 -38.719,6.81321 -73.8076,9.23448 -147.87861,10.20428 -53.41286,0.69938 -104.06678,0.44897 -112.56428,-0.55636 z M 766.34283,328.76217 c 3.02241,-3.58286 6.48851,-6.51429 7.70244,-6.51429 1.21393,0 -0.26574,2.93143 -3.28816,6.51429 -3.02242,3.58285 -6.48851,6.51428 -7.70244,6.51428 -1.21393,0 0.26574,-2.93143 3.28816,-6.51428 z m 21.13162,-26.05715 c 22.67866,-28.36589 74.89197,-82.6866 77.22594,-80.34285 1.18925,1.19429 -18.82499,23.66857 -44.47609,49.94286 -25.6511,26.27428 -40.38855,39.95428 -32.74985,30.39999 z M 925.68524,161.04238 C 1047.291,55.509647 1175.2642,-10.347955 1273.9857,-18.200101 c 27.0117,-2.148455 27.5289,-2.023815 8.8285,2.127566 -10.9253,2.425355 -38.7353,8.5994646 -61.8,13.7202575 -72.5888,16.1161685 -169.4617,68.4912415 -254.75774,137.7367475 -23.61948,19.17489 -44.79719,34.86342 -47.06163,34.86342 -2.26444,0 0.65623,-4.14248 6.49041,-9.20551 z"
+ id="path3209"
+ style="fill:#45484e"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 845.79996,617.38662 C 691.54479,599.06415 594.26396,554.21407 594.2022,501.39073 c -0.0177,-14.74969 5.97703,-18.96109 10.49638,-7.37443 1.50743,3.8648 0.76742,5.60802 -1.73486,4.08654 -11.09434,-6.74571 -2.64067,7.13367 12.10092,19.86753 80.42714,69.47329 337.99827,98.57109 532.24116,60.1272 77.8628,-15.41032 146.6843,-45.58605 164.3034,-72.04109 4.0893,-6.14006 4.9849,-9.67393 1.9902,-7.8531 -3.4726,2.11145 -4.7377,1.22364 -3.4927,-2.45098 4.0964,-12.09025 12.4361,-9.13529 12.4361,4.40639 0,48.61064 -68.9707,86.60239 -198.6428,109.42015 -57.6806,10.14978 -220.01298,14.70731 -278.10004,7.80768 z"
+ id="path3207"
+ style="fill:#3c4248"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 836.97139,615.41527 C 717.18856,601.07815 620.8817,564.55398 600.47124,525.72315 c -6.72689,-12.79792 -7.15044,-38.4467 -0.63482,-38.4467 2.07502,0 2.63793,3.51775 1.25087,7.81727 -5.89686,18.27869 50.80278,52.98776 117.72842,72.06824 73.63426,20.99306 124.66397,26.53568 241.75568,26.25825 112.62341,-0.26665 150.53291,-4.19554 227.09741,-23.53528 69.052,-17.44218 134.9817,-57.27616 127.318,-76.9242 -1.8553,-4.75652 -1.0638,-6.06997 2.3915,-3.96898 7.1555,4.35076 6.4572,22.31381 -1.4523,37.36159 -36.076,68.63447 -276.3456,113.31279 -478.95461,89.06193 z"
+ id="path3205"
+ style="fill:#363c42"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 852.93561,617.43426 C 762.40524,607.27132 679.35324,584.69641 637.46908,558.86692 612.38221,543.3961 594.18569,521.45174 594.18569,506.66861 c 0,-6.0136 3.8082,-4.06466 16.54788,8.46891 31.61255,31.10098 105.94276,58.14304 199.94422,72.7416 24.32938,3.77842 77.29851,5.74916 152.10074,5.659 99.49477,-0.11986 121.60867,-1.41368 166.13877,-9.71979 88.1118,-16.43528 153.1761,-42.83455 180.1993,-73.11425 12.0086,-13.45574 13.4262,-14.05062 13.4262,-5.63421 0,41.86453 -80.124,83.14109 -202.5688,104.35508 -58.4896,10.13349 -208.13474,14.62183 -267.03839,8.00931 z"
+ id="path3203"
+ style="fill:#2b3238"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <path
+ style="fill:#ffffff"
+ d="m 317.05059,186.48307 c -3.17531,-1.61131 -7.33003,-6.67593 -6.31183,-7.69413 0.42963,-0.42963 10.46069,-0.66852 27.65438,-0.6586 22.11015,0.0128 27.46313,0.19449 29.65563,1.00673 4.49266,1.66437 3.63302,4.85845 -2.00428,7.44711 -2.42783,1.11487 -4.77082,1.23414 -24.49695,1.24699 -20.60552,0.0134 -21.96089,-0.0612 -24.49695,-1.3481 l 0,0 z"
+ id="path3129"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 329.70331,485.21979 c -5.1428,-2.25233 0.0787,-3.73332 13.18653,-3.74012 8.52841,-0.004 10.8777,0.20901 13.03845,1.18452 1.9047,0.85992 2.43244,1.39311 1.90293,1.92262 -0.94253,0.94253 -26.12703,1.50927 -28.12791,0.63298 z"
+ id="path3131"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png
new file mode 100644
index 0000000..b319f7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_error.png b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png
new file mode 100644
index 0000000..5177258
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_info.png b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png
new file mode 100644
index 0000000..fa9a60b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png
new file mode 100644
index 0000000..567704b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table.png b/SQLiteStudio3/guiSQLiteStudio/img/table.png
new file mode 100644
index 0000000..b0cd69f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png
new file mode 100644
index 0000000..86649bf
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png
new file mode 100644
index 0000000..a3fa27c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png
new file mode 100644
index 0000000..670a79e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png
new file mode 100644
index 0000000..710190d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png b/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png
new file mode 100644
index 0000000..79c8e80
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_export.png b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png
new file mode 100644
index 0000000..e783a13
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_import.png b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png
new file mode 100644
index 0000000..d753ad4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png
new file mode 100644
index 0000000..0626d0a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tables.png b/SQLiteStudio3/guiSQLiteStudio/img/tables.png
new file mode 100644
index 0000000..4ac0456
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/tables.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png b/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png
new file mode 100644
index 0000000..864bd01
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png b/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png
new file mode 100644
index 0000000..d956ac2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png
new file mode 100644
index 0000000..0496522
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png
new file mode 100644
index 0000000..b3f7858
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tip.png b/SQLiteStudio3/guiSQLiteStudio/img/tip.png
new file mode 100644
index 0000000..845e110
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/tip.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png
new file mode 100644
index 0000000..efc599d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png
new file mode 100644
index 0000000..2ad2087
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/triggers.png b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png
new file mode 100644
index 0000000..284c03f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/unique.png b/SQLiteStudio3/guiSQLiteStudio/img/unique.png
new file mode 100644
index 0000000..ed7ec0e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/unique.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user.png b/SQLiteStudio3/guiSQLiteStudio/img/user.png
new file mode 100644
index 0000000..2d44726
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/user.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png
new file mode 100644
index 0000000..069fae7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png
new file mode 100644
index 0000000..09f73c2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png
new file mode 100644
index 0000000..463222b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/view.png b/SQLiteStudio3/guiSQLiteStudio/img/view.png
new file mode 100644
index 0000000..8b4b28f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/view.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/views.png b/SQLiteStudio3/guiSQLiteStudio/img/views.png
new file mode 100644
index 0000000..c64d4d9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/views.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png
new file mode 100644
index 0000000..fe899d8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png
new file mode 100644
index 0000000..f108a40
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png
new file mode 100644
index 0000000..ab3e4b2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png
new file mode 100644
index 0000000..d12ce37
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png
new file mode 100644
index 0000000..3c53cac
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png
new file mode 100644
index 0000000..b16177a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png
new file mode 100644
index 0000000..923f0f5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png
new file mode 100644
index 0000000..60d9802
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png
new file mode 100644
index 0000000..96722d9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png
new file mode 100644
index 0000000..1faecb8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png
new file mode 100644
index 0000000..80cfa3c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png
Binary files differ
diff --git a/SQLiteStudio3/guiSQLiteStudio/license.txt b/SQLiteStudio3/guiSQLiteStudio/license.txt
new file mode 100644
index 0000000..f166cc5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/license.txt
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it! \ No newline at end of file
diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp
new file mode 100644
index 0000000..c53d95c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp
@@ -0,0 +1,909 @@
+#include "mainwindow.h"
+#include "common/unused.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "iconmanager.h"
+#include "windows/editorwindow.h"
+#include "windows/tablewindow.h"
+#include "windows/viewwindow.h"
+#include "windows/functionseditor.h"
+#include "windows/collationseditor.h"
+#include "windows/ddlhistorywindow.h"
+#include "mdiarea.h"
+#include "statusfield.h"
+#include "uiconfig.h"
+#include "common/extaction.h"
+#include "dbobjectdialogs.h"
+#include "services/notifymanager.h"
+#include "dialogs/configdialog.h"
+#include "services/pluginmanager.h"
+#include "formmanager.h"
+#include "customconfigwidgetplugin.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "qtscriptsyntaxhighlighter.h"
+#include "services/exportmanager.h"
+#include "services/importmanager.h"
+#include "dialogs/exportdialog.h"
+#include "dialogs/importdialog.h"
+#include "multieditor/multieditorwidgetplugin.h"
+#include "multieditor/multieditor.h"
+#include "dialogs/dbdialog.h"
+#include "uidebug.h"
+#include "services/dbmanager.h"
+#include "services/updatemanager.h"
+#include "dialogs/aboutdialog.h"
+#include "dialogs/bugdialog.h"
+#include "windows/bugreporthistorywindow.h"
+#include "dialogs/newversiondialog.h"
+#include "dialogs/quitconfirmdialog.h"
+#include "common/widgetcover.h"
+#include <QMdiSubWindow>
+#include <QDebug>
+#include <QStyleFactory>
+#include <QUiLoader>
+#include <QInputDialog>
+#include <QProgressBar>
+#include <QLabel>
+
+CFG_KEYS_DEFINE(MainWindow)
+MainWindow* MainWindow::instance = nullptr;
+
+MainWindow::MainWindow() :
+ QMainWindow(),
+ ui(new Ui::MainWindow)
+{
+ init();
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::init()
+{
+ ui->setupUi(this);
+ connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp()));
+
+#ifdef Q_OS_WIN
+ setWindowIcon(ICONS.SQLITESTUDIO_APP.toQIcon().pixmap(256, 256));
+#else
+ setWindowIcon(ICONS.SQLITESTUDIO_APP);
+#endif
+
+ setWindowTitle(QString("SQLiteStudio (%1)").arg(SQLITESTUDIO->getVersionString()));
+
+#ifdef Q_OS_MACX
+ ui->centralWidget->layout()->setMargin(0);
+#endif
+
+ Committable::init(MainWindow::confirmQuit);
+
+ DbTreeModel::staticInit();
+ dbTree = new DbTree(this);
+ addDockWidget(Qt::LeftDockWidgetArea, dbTree);
+
+ statusField = new StatusField(this);
+ addDockWidget(Qt::BottomDockWidgetArea, statusField);
+ if (!statusField->hasMessages())
+ statusField->close();
+
+ initActions();
+
+ ui->mdiArea->setTaskBar(ui->taskBar);
+ addToolBar(Qt::BottomToolBarArea, ui->taskBar);
+
+ addToolBar(Qt::TopToolBarArea, ui->viewToolbar);
+ insertToolBar(ui->viewToolbar, ui->mainToolBar);
+ insertToolBar(ui->mainToolBar, ui->structureToolbar);
+ insertToolBar(ui->structureToolbar, ui->dbToolbar);
+
+ formManager = new FormManager();
+
+ initMenuBar();
+
+ PLUGINS->registerPluginType<CustomConfigWidgetPlugin>(tr("Configuration widgets"));
+ PLUGINS->registerPluginType<SyntaxHighlighterPlugin>(tr("Syntax highlighting engines"));
+ PLUGINS->registerPluginType<MultiEditorWidgetPlugin>(tr("Data editors"));
+ PLUGINS->loadBuiltInPlugin(new SqliteHighlighterPlugin);
+ PLUGINS->loadBuiltInPlugin(new JavaScriptHighlighterPlugin);
+ MultiEditor::loadBuiltInEditors();
+
+ updateWindowActions();
+
+ qApp->installEventFilter(this);
+
+ if (isDebugEnabled())
+ {
+ if (isDebugConsoleEnabled())
+ notifyInfo(tr("Running in debug mode. Press %1 or use 'Help / Open debug console' menu entry to open the debug console.").arg(shortcuts[OPEN_DEBUG_CONSOLE]->get()));
+ else
+ notifyInfo(tr("Running in debug mode. Debug messages are printed to the standard output."));
+ }
+
+ connect(UPDATES, SIGNAL(updatesAvailable(QList<UpdateManager::UpdateEntry>)), this, SLOT(updatesAvailable(QList<UpdateManager::UpdateEntry>)));
+ connect(UPDATES, SIGNAL(noUpdatesAvailable()), this, SLOT(noUpdatesAvailable()));
+ connect(statusField, SIGNAL(linkActivated(QString)), this, SLOT(statusFieldLinkClicked(QString)));
+
+ // Widget cover
+ widgetCover = new WidgetCover(this);
+ widgetCover->setVisible(false);
+
+ updatingBusyBar = new QProgressBar();
+ updatingBusyBar->setRange(0, 100);
+ updatingBusyBar->setTextVisible(true);
+ updatingBusyBar->setValue(0);
+ updatingBusyBar->setFixedWidth(300);
+
+ updatingSubBar = new QProgressBar();
+ updatingSubBar->setRange(0, 100);
+ updatingSubBar->setTextVisible(true);
+ updatingSubBar->setValue(0);
+ updatingSubBar->setFixedWidth(300);
+
+ updatingLabel = new QLabel();
+
+ widgetCover->getContainerLayout()->addWidget(updatingLabel, 0, 0);
+ widgetCover->getContainerLayout()->addWidget(updatingBusyBar, 1, 0);
+ widgetCover->getContainerLayout()->addWidget(updatingSubBar, 2, 0);
+ connect(UPDATES, SIGNAL(updatingProgress(QString,int,int)), this, SLOT(handleUpdatingProgress(QString,int,int)));
+ connect(UPDATES, SIGNAL(updatingError(QString)), this, SLOT(handleUpdatingError()));
+}
+
+void MainWindow::cleanUp()
+{
+ if (SQLITESTUDIO->getImmediateQuit())
+ return;
+
+ for (MdiWindow* win : getMdiArea()->getWindows())
+ delete win;
+
+ removeDockWidget(dbTree);
+ removeDockWidget(statusField);
+
+ safe_delete(dbTree);
+ safe_delete(statusField);
+
+ delete ui;
+
+ safe_delete(formManager);
+}
+
+EditorWindow* MainWindow::openSqlEditor()
+{
+ EditorWindow* win = new EditorWindow(ui->mdiArea);
+ if (win->isInvalid())
+ {
+ delete win;
+ return nullptr;
+ }
+
+ ui->mdiArea->addSubWindow(win);
+ return win;
+}
+
+void MainWindow::updateWindowActions()
+{
+ bool hasActiveTask = ui->mdiArea->activeSubWindow();
+ actionMap[MDI_CASCADE]->setEnabled(hasActiveTask);
+ actionMap[MDI_TILE]->setEnabled(hasActiveTask);
+ actionMap[MDI_TILE_HORIZONTAL]->setEnabled(hasActiveTask);
+ actionMap[MDI_TILE_VERTICAL]->setEnabled(hasActiveTask);
+ actionMap[CLOSE_WINDOW]->setEnabled(hasActiveTask);
+ actionMap[CLOSE_OTHER_WINDOWS]->setEnabled(hasActiveTask);
+ actionMap[CLOSE_ALL_WINDOWS]->setEnabled(hasActiveTask);
+ actionMap[RENAME_WINDOW]->setEnabled(hasActiveTask);
+ actionMap[RESTORE_WINDOW]->setEnabled(hasClosedWindowToRestore());
+}
+
+MdiArea *MainWindow::getMdiArea() const
+{
+ return dynamic_cast<MdiArea*>(ui->mdiArea);
+}
+
+DbTree *MainWindow::getDbTree() const
+{
+ return dbTree;
+}
+
+StatusField *MainWindow::getStatusField() const
+{
+ return statusField;
+}
+
+void MainWindow::closeEvent(QCloseEvent* event)
+{
+ if (SQLITESTUDIO->getImmediateQuit())
+ {
+ closingApp = true;
+ QMainWindow::closeEvent(event);
+ return;
+ }
+
+ if (!Committable::canQuit())
+ {
+ event->ignore();
+ return;
+ }
+
+ closingApp = true;
+ closeNonSessionWindows();
+ MdiWindow* currWindow = ui->mdiArea->getCurrentWindow();
+ hide();
+ saveSession(currWindow);
+ QMainWindow::closeEvent(event);
+}
+
+void MainWindow::createActions()
+{
+ createAction(OPEN_SQL_EDITOR, ICONS.OPEN_SQL_EDITOR, tr("Open SQL editor"), this, SLOT(openSqlEditorSlot()), ui->mainToolBar);
+ createAction(OPEN_DDL_HISTORY, ICONS.DDL_HISTORY, tr("Open DDL history"), this, SLOT(openDdlHistorySlot()), ui->mainToolBar);
+ createAction(OPEN_FUNCTION_EDITOR, ICONS.FUNCTION, tr("Open SQL functions editor"), this, SLOT(openFunctionEditorSlot()), ui->mainToolBar);
+ createAction(OPEN_COLLATION_EDITOR, ICONS.CONSTRAINT_COLLATION, tr("Open collations editor"), this, SLOT(openCollationEditorSlot()), ui->mainToolBar);
+ createAction(IMPORT, ICONS.IMPORT, tr("Import"), this, SLOT(importAnything()), ui->mainToolBar);
+ createAction(EXPORT, ICONS.EXPORT, tr("Export"), this, SLOT(exportAnything()), ui->mainToolBar);
+ ui->mainToolBar->addSeparator();
+ createAction(OPEN_CONFIG, ICONS.CONFIGURE, tr("Open configuration dialog"), this, SLOT(openConfig()), ui->mainToolBar);
+
+ createAction(MDI_TILE, ICONS.WIN_TILE, tr("Tile windows"), ui->mdiArea, SLOT(tileSubWindows()), ui->viewToolbar);
+ createAction(MDI_TILE_HORIZONTAL, ICONS.WIN_TILE_HORIZONTAL, tr("Tile windows horizontally"), ui->mdiArea, SLOT(tileHorizontally()), ui->viewToolbar);
+ createAction(MDI_TILE_VERTICAL, ICONS.WIN_TILE_VERTICAL, tr("Tile windows vertically"), ui->mdiArea, SLOT(tileVertically()), ui->viewToolbar);
+ createAction(MDI_CASCADE, ICONS.WIN_CASCADE, tr("Cascade windows"), ui->mdiArea, SLOT(cascadeSubWindows()), ui->viewToolbar);
+ createAction(NEXT_TASK, tr("Next window"), ui->taskBar, SLOT(nextTask()), this);
+ createAction(PREV_TASK, tr("Previous window"), ui->taskBar, SLOT(prevTask()), this);
+ createAction(HIDE_STATUS_FIELD, tr("Hide status field"), this, SLOT(hideStatusField()), this);
+
+ createAction(CLOSE_WINDOW, ICONS.WIN_CLOSE, tr("Close selected window"), this, SLOT(closeSelectedWindow()), this);
+ createAction(CLOSE_OTHER_WINDOWS, ICONS.WIN_CLOSE_OTHER, tr("Close all windows but selected"), this, SLOT(closeAllWindowsButSelected()), this);
+ createAction(CLOSE_ALL_WINDOWS, ICONS.WIN_CLOSE_ALL, tr("Close all windows"), this, SLOT(closeAllWindows()), this);
+ createAction(RESTORE_WINDOW, ICONS.WIN_RESTORE, tr("Restore recently closed window"), this, SLOT(restoreLastClosedWindow()), this);
+ createAction(RENAME_WINDOW, ICONS.WIN_RENAME, tr("Rename selected window"), this, SLOT(renameWindow()), this);
+
+ createAction(OPEN_DEBUG_CONSOLE, tr("Open Debug Console"), this, SLOT(openDebugConsole()), this);
+ createAction(REPORT_BUG, ICONS.BUG, tr("Report a bug"), this, SLOT(reportBug()), this);
+ createAction(FEATURE_REQUEST, ICONS.FEATURE_REQUEST, tr("Propose a new feature"), this, SLOT(requestFeature()), this);
+ createAction(ABOUT, ICONS.SQLITESTUDIO_APP16, tr("About"), this, SLOT(aboutSqlitestudio()), this);
+ createAction(LICENSES, ICONS.LICENSES, tr("Licenses"), this, SLOT(licenses()), this);
+ createAction(HOMEPAGE, ICONS.HOMEPAGE, tr("Open home page"), this, SLOT(homepage()), this);
+ createAction(FORUM, ICONS.OPEN_FORUM, tr("Open forum page"), this, SLOT(forum()), this);
+ createAction(USER_MANUAL, ICONS.USER_MANUAL, tr("User Manual"), this, SLOT(userManual()), this);
+ createAction(SQLITE_DOCS, ICONS.SQLITE_DOCS, tr("SQLite documentation"), this, SLOT(sqliteDocs()), this);
+ createAction(BUG_REPORT_HISTORY, ICONS.BUG_LIST, tr("Report history"), this, SLOT(reportHistory()), this);
+ createAction(CHECK_FOR_UPDATES, ICONS.GET_UPDATE, tr("Check for updates"), this, SLOT(checkForUpdates()), this);
+
+ actionMap[ABOUT]->setMenuRole(QAction::AboutRole);
+ actionMap[OPEN_CONFIG]->setMenuRole(QAction::PreferencesRole);
+
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::CONNECT_TO_DB));
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::DISCONNECT_FROM_DB));
+ ui->dbToolbar->addSeparator();
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::ADD_DB));
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::EDIT_DB));
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::DELETE_DB));
+ ui->dbToolbar->addSeparator();
+ ui->dbToolbar->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMA));
+
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_TABLE));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_TABLE));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_TABLE));
+ ui->structureToolbar->addSeparator();
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_INDEX));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_INDEX));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_INDEX));
+ ui->structureToolbar->addSeparator();
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_TRIGGER));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_TRIGGER));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_TRIGGER));
+ ui->structureToolbar->addSeparator();
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_VIEW));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_VIEW));
+ ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_VIEW));
+
+ ui->taskBar->initContextMenu(this);
+}
+
+void MainWindow::initMenuBar()
+{
+ // Database menu
+ dbMenu = new QMenu(this);
+ dbMenu->setTitle(tr("Database", "menubar"));
+ menuBar()->addMenu(dbMenu);
+
+ dbMenu->addAction(dbTree->getAction(DbTree::CONNECT_TO_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::DISCONNECT_FROM_DB));
+ dbMenu->addSeparator();
+ dbMenu->addAction(dbTree->getAction(DbTree::ADD_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::EDIT_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::DELETE_DB));
+ dbMenu->addSeparator();
+ dbMenu->addAction(dbTree->getAction(DbTree::EXPORT_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::CONVERT_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::VACUUM_DB));
+ dbMenu->addAction(dbTree->getAction(DbTree::INTEGRITY_CHECK));
+ dbMenu->addSeparator();
+ dbMenu->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMA));
+ dbMenu->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMAS));
+
+ // Structure menu
+ structMenu = new QMenu(this);
+ structMenu->setTitle(tr("Structure", "menubar"));
+ menuBar()->addMenu(structMenu);
+
+ structMenu->addAction(dbTree->getAction(DbTree::ADD_TABLE));
+ structMenu->addAction(dbTree->getAction(DbTree::EDIT_TABLE));
+ structMenu->addAction(dbTree->getAction(DbTree::DEL_TABLE));
+ structMenu->addSeparator();
+ structMenu->addAction(dbTree->getAction(DbTree::ADD_INDEX));
+ structMenu->addAction(dbTree->getAction(DbTree::EDIT_INDEX));
+ structMenu->addAction(dbTree->getAction(DbTree::DEL_INDEX));
+ structMenu->addSeparator();
+ structMenu->addAction(dbTree->getAction(DbTree::ADD_TRIGGER));
+ structMenu->addAction(dbTree->getAction(DbTree::EDIT_TRIGGER));
+ structMenu->addAction(dbTree->getAction(DbTree::DEL_TRIGGER));
+ structMenu->addSeparator();
+ structMenu->addAction(dbTree->getAction(DbTree::ADD_VIEW));
+ structMenu->addAction(dbTree->getAction(DbTree::EDIT_VIEW));
+ structMenu->addAction(dbTree->getAction(DbTree::DEL_VIEW));
+
+ // View menu
+ viewMenu = createPopupMenu();
+ viewMenu->setTitle(tr("View", "menubar"));
+ menuBar()->addMenu(viewMenu);
+
+ mdiMenu = new QMenu(viewMenu);
+ mdiMenu->setTitle(tr("Window list", "menubar view menu"));
+ connect(ui->mdiArea, &MdiArea::windowListChanged, this, &MainWindow::refreshMdiWindows);
+
+ viewMenu->addSeparator();
+ viewMenu->addAction(actionMap[MDI_TILE]);
+ viewMenu->addAction(actionMap[MDI_TILE_HORIZONTAL]);
+ viewMenu->addAction(actionMap[MDI_TILE_VERTICAL]);
+ viewMenu->addAction(actionMap[MDI_CASCADE]);
+ viewMenu->addSeparator();
+ viewMenu->addAction(actionMap[CLOSE_WINDOW]);
+ viewMenu->addAction(actionMap[CLOSE_OTHER_WINDOWS]);
+ viewMenu->addAction(actionMap[CLOSE_ALL_WINDOWS]);
+ viewMenu->addSeparator();
+ viewMenu->addAction(actionMap[RESTORE_WINDOW]);
+ viewMenu->addAction(actionMap[RENAME_WINDOW]);
+
+ viewMenu->addSeparator();
+ viewMenu->addMenu(mdiMenu);
+
+ // Tools menu
+ toolsMenu = new QMenu(this);
+ toolsMenu->setTitle(tr("Tools", "menubar"));
+ menuBar()->addMenu(toolsMenu);
+
+ toolsMenu->addAction(actionMap[OPEN_SQL_EDITOR]);
+ toolsMenu->addAction(actionMap[OPEN_DDL_HISTORY]);
+ toolsMenu->addAction(actionMap[OPEN_FUNCTION_EDITOR]);
+ toolsMenu->addAction(actionMap[OPEN_COLLATION_EDITOR]);
+ toolsMenu->addAction(actionMap[IMPORT]);
+ toolsMenu->addAction(actionMap[EXPORT]);
+ toolsMenu->addSeparator();
+ toolsMenu->addAction(actionMap[OPEN_CONFIG]);
+
+ // Help menu
+ sqlitestudioMenu = new QMenu(this);
+ sqlitestudioMenu->setTitle(tr("Help"));
+ menuBar()->addMenu(sqlitestudioMenu);
+ if (isDebugEnabled() && isDebugConsoleEnabled())
+ {
+ sqlitestudioMenu->addAction(actionMap[OPEN_DEBUG_CONSOLE]);
+ sqlitestudioMenu->addSeparator();
+ }
+ sqlitestudioMenu->addAction(actionMap[USER_MANUAL]);
+ sqlitestudioMenu->addAction(actionMap[SQLITE_DOCS]);
+ sqlitestudioMenu->addAction(actionMap[HOMEPAGE]);
+ sqlitestudioMenu->addAction(actionMap[FORUM]);
+ sqlitestudioMenu->addSeparator();
+ if (UPDATES->isPlatformEligibleForUpdate())
+ {
+ sqlitestudioMenu->addAction(actionMap[CHECK_FOR_UPDATES]);
+ sqlitestudioMenu->addSeparator();
+ }
+ sqlitestudioMenu->addAction(actionMap[REPORT_BUG]);
+ sqlitestudioMenu->addAction(actionMap[FEATURE_REQUEST]);
+ sqlitestudioMenu->addAction(actionMap[BUG_REPORT_HISTORY]);
+ sqlitestudioMenu->addSeparator();
+ sqlitestudioMenu->addAction(actionMap[LICENSES]);
+ sqlitestudioMenu->addAction(actionMap[ABOUT]);
+}
+
+void MainWindow::saveSession(MdiWindow* currWindow)
+{
+ /*
+ * The currWindow is passed as parameter to the method to let hide the main window before
+ * saving session, because saving might take a while.
+ */
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["state"] = saveState();
+ sessionValue["geometry"] = saveGeometry();
+
+ QList<QVariant> windowSessions;
+ foreach (MdiWindow* window, ui->mdiArea->getWindows())
+ if (window->restoreSessionNextTime())
+ windowSessions << window->saveSession();
+
+ sessionValue["windowSessions"] = windowSessions;
+
+ if (currWindow && currWindow->restoreSessionNextTime())
+ {
+ QString title = currWindow->windowTitle();
+ sessionValue["activeWindowTitle"] = title;
+ }
+
+ sessionValue["dbTree"] = dbTree->saveSession();
+ sessionValue["style"] = currentStyle();
+
+ CFG_UI.General.Session.set(sessionValue);
+}
+
+void MainWindow::restoreSession()
+{
+ QHash<QString,QVariant> sessionValue = CFG_UI.General.Session.get();
+ if (sessionValue.size() == 0)
+ return;
+
+ if (sessionValue.contains("style"))
+ setStyle(sessionValue["style"].toString());
+
+ if (sessionValue.contains("geometry"))
+ restoreGeometry(sessionValue["geometry"].toByteArray());
+
+ if (sessionValue.contains("state"))
+ restoreState(sessionValue["state"].toByteArray());
+
+ if (sessionValue.contains("dbTree"))
+ dbTree->restoreSession(sessionValue["dbTree"]);
+
+ if (sessionValue.contains("windowSessions"))
+ restoreWindowSessions(sessionValue["windowSessions"].toList());
+
+ if (sessionValue.contains("activeWindowTitle"))
+ {
+ QString title = sessionValue["activeWindowTitle"].toString();
+ MdiWindow* window = ui->mdiArea->getWindowByTitle(title);
+ if (window)
+ ui->mdiArea->setActiveSubWindow(window);
+ }
+
+ if (statusField->hasMessages())
+ statusField->setVisible(true);
+
+ updateWindowActions();
+}
+
+void MainWindow::restoreWindowSessions(const QList<QVariant>& windowSessions)
+{
+ if (windowSessions.size() == 0)
+ return;
+
+ foreach (const QVariant& winSession, windowSessions)
+ restoreWindowSession(winSession);
+}
+
+MdiWindow* MainWindow::restoreWindowSession(const QVariant &windowSessions)
+{
+ QHash<QString, QVariant> winSessionHash = windowSessions.toHash();
+ if (!winSessionHash.contains("class"))
+ return nullptr;
+
+ // Find out the type of stored session
+ QByteArray classBytes = winSessionHash["class"].toString().toLatin1();
+ char* className = classBytes.data();
+ int type = QMetaType::type(className);
+ if (type == QMetaType::UnknownType)
+ {
+ qWarning() << "Could not restore window session, because type" << className
+ << "is not known to Qt meta subsystem.";
+ return nullptr;
+ }
+
+ // Try to instantiate the object
+ void* object = QMetaType::create(type);
+ if (!object)
+ {
+ qWarning() << "Could not restore window session, because type" << className
+ << "could not be instantiated.";
+ return nullptr;
+ }
+
+ // Switch to session aware window, so we can use its session aware interface.
+ MdiChild* mdiChild = reinterpret_cast<MdiChild*>(object);
+ if (mdiChild->isInvalid())
+ {
+ delete mdiChild;
+ return nullptr;
+ }
+
+ // Add the window to MDI area and restore its session
+ MdiWindow* window = ui->mdiArea->addSubWindow(mdiChild);
+ if (!window->restoreSession(winSessionHash))
+ delete window;
+
+ return window;
+}
+
+void MainWindow::setStyle(const QString& styleName)
+{
+ QStyle* style = QStyleFactory::create(styleName);
+ if (!style)
+ {
+ notifyWarn(tr("Could not set style: %1", "main window").arg(styleName));
+ return;
+ }
+ QApplication::setStyle(style);
+}
+
+QString MainWindow::currentStyle() const
+{
+ return QApplication::style()->objectName();
+}
+
+void MainWindow::closeNonSessionWindows()
+{
+ foreach (MdiWindow* window, ui->mdiArea->getWindows())
+ if (!window->restoreSessionNextTime())
+ window->close();
+}
+FormManager* MainWindow::getFormManager() const
+{
+ return formManager;
+}
+
+void MainWindow::setupDefShortcuts()
+{
+ BIND_SHORTCUTS(MainWindow, Action);
+}
+
+void MainWindow::openSqlEditorSlot()
+{
+ openSqlEditor();
+}
+
+void MainWindow::refreshMdiWindows()
+{
+ mdiMenu->clear();
+
+ foreach (QAction* action, getMdiArea()->getTaskBar()->getTasks())
+ mdiMenu->addAction(action);
+
+ updateWindowActions();
+}
+
+void MainWindow::hideStatusField()
+{
+ statusField->close();
+}
+
+void MainWindow::openConfig()
+{
+ ConfigDialog config(this);
+ config.exec();
+}
+
+void MainWindow::openDdlHistorySlot()
+{
+ openDdlHistory();
+}
+
+void MainWindow::openFunctionEditorSlot()
+{
+ openFunctionEditor();
+}
+
+void MainWindow::openCollationEditorSlot()
+{
+ openCollationEditor();
+}
+
+void MainWindow::exportAnything()
+{
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.exec();
+}
+
+void MainWindow::importAnything()
+{
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ dialog.exec();
+}
+
+void MainWindow::closeAllWindows()
+{
+ ui->mdiArea->closeAllSubWindows();
+}
+
+void MainWindow::closeAllWindowsButSelected()
+{
+ ui->mdiArea->closeAllButActive();
+}
+
+void MainWindow::closeSelectedWindow()
+{
+ ui->mdiArea->closeActiveSubWindow();
+}
+
+void MainWindow::renameWindow()
+{
+ MdiWindow* win = ui->mdiArea->getActiveWindow();
+ if (!win)
+ return;
+
+ QString newTitle = QInputDialog::getText(this, tr("Rename window"), tr("Enter new name for the window:"), QLineEdit::Normal, win->windowTitle());
+ if (newTitle == win->windowTitle() || newTitle.isEmpty())
+ return;
+
+ win->rename(newTitle);
+
+}
+
+void MainWindow::openDebugConsole()
+{
+ showUiDebugConsole();
+}
+
+void MainWindow::reportBug()
+{
+ BugDialog dialog(this);
+ dialog.exec();
+}
+
+void MainWindow::requestFeature()
+{
+ BugDialog dialog(this);
+ dialog.setFeatureRequestMode(true);
+ dialog.exec();
+}
+
+void MainWindow::aboutSqlitestudio()
+{
+ AboutDialog dialog(AboutDialog::ABOUT, this);
+ dialog.exec();
+}
+
+void MainWindow::licenses()
+{
+ AboutDialog dialog(AboutDialog::LICENSES, this);
+ dialog.exec();
+}
+
+void MainWindow::homepage()
+{
+ QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getHomePage()));
+}
+
+void MainWindow::forum()
+{
+ QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getForumPage()));
+}
+
+void MainWindow::userManual()
+{
+ QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getUserManualPage()));
+}
+
+void MainWindow::sqliteDocs()
+{
+ QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getSqliteDocsPage()));
+}
+
+void MainWindow::reportHistory()
+{
+ openReportHistory();
+}
+
+void MainWindow::updatesAvailable(const QList<UpdateManager::UpdateEntry>& updates)
+{
+ manualUpdatesChecking = false;
+ newVersionDialog = new NewVersionDialog(this);
+ newVersionDialog->setUpdates(updates);
+ notifyInfo(tr("New updates are available. <a href=\"%1\">Click here for details</a>.").arg(openUpdatesUrl));
+}
+
+void MainWindow::noUpdatesAvailable()
+{
+ if (!manualUpdatesChecking)
+ return;
+
+ notifyInfo(tr("You're running the most recent version. No updates are available."));
+ manualUpdatesChecking = false;
+}
+
+void MainWindow::statusFieldLinkClicked(const QString& link)
+{
+ if (link == openUpdatesUrl && newVersionDialog)
+ {
+ newVersionDialog->exec();
+ return;
+ }
+}
+
+void MainWindow::checkForUpdates()
+{
+ manualUpdatesChecking = true;
+ UPDATES->checkForUpdates();
+}
+
+void MainWindow::handleUpdatingProgress(const QString& jobTitle, int jobPercent, int totalPercent)
+{
+ if (!widgetCover->isVisible())
+ widgetCover->show();
+
+ updatingLabel->setText(jobTitle);
+ updatingBusyBar->setValue(totalPercent);
+ updatingSubBar->setValue(jobPercent);
+}
+
+void MainWindow::handleUpdatingError()
+{
+ widgetCover->hide();
+}
+
+DdlHistoryWindow* MainWindow::openDdlHistory()
+{
+ return openMdiWindow<DdlHistoryWindow>();
+}
+
+FunctionsEditor* MainWindow::openFunctionEditor()
+{
+ return openMdiWindow<FunctionsEditor>();
+}
+
+CollationsEditor* MainWindow::openCollationEditor()
+{
+ return openMdiWindow<CollationsEditor>();
+}
+
+BugReportHistoryWindow* MainWindow::openReportHistory()
+{
+ return openMdiWindow<BugReportHistoryWindow>();
+}
+
+bool MainWindow::confirmQuit(const QList<Committable*>& instances)
+{
+ QuitConfirmDialog dialog(MAINWINDOW);
+
+ for (Committable* c : instances)
+ {
+ if (c->isUncommited())
+ dialog.addMessage(c->getQuitUncommitedConfirmMessage());
+ }
+
+ if (dialog.getMessageCount() == 0)
+ return true;
+
+ if (dialog.exec() == QDialog::Accepted)
+ return true;
+
+ return false;
+}
+
+bool MainWindow::isClosingApp() const
+{
+ return closingApp;
+}
+
+QToolBar* MainWindow::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_MAIN:
+ return ui->mainToolBar;
+ case TOOLBAR_DATABASE:
+ return ui->dbToolbar;
+ case TOOLBAR_STRUCTURE:
+ return ui->structureToolbar;
+ case TOOLBAR_VIEW:
+ return ui->viewToolbar;
+ }
+ return nullptr;
+}
+
+void MainWindow::openDb(const QString& path)
+{
+ QString name = DBLIST->quickAddDb(path, QHash<QString,QVariant>());
+ if (!name.isNull())
+ {
+ notifyInfo(tr("Database passed in command line parameters (%1) has been temporarily added to the list under name: %2").arg(path, name));
+ Db* db = DBLIST->getByName(name);
+ db->open();
+ }
+ else
+ notifyError(tr("Could not add database %1 to list.").arg(path));
+}
+
+QMenu* MainWindow::getDatabaseMenu() const
+{
+ return dbMenu;
+}
+
+QMenu* MainWindow::getStructureMenu() const
+{
+ return structMenu;
+}
+
+QMenu* MainWindow::getViewMenu() const
+{
+ return viewMenu;
+}
+
+QMenu* MainWindow::getToolsMenu() const
+{
+ return toolsMenu;
+}
+
+QMenu* MainWindow::getSQLiteStudioMenu() const
+{
+ return sqlitestudioMenu;
+}
+
+MainWindow *MainWindow::getInstance()
+{
+ if (!instance)
+ instance = new MainWindow();
+
+ return instance;
+}
+
+bool MainWindow::eventFilter(QObject* obj, QEvent* e)
+{
+ UNUSED(obj);
+ if (e->type() == QEvent::FileOpen)
+ {
+ QUrl url = dynamic_cast<QFileOpenEvent*>(e)->url();
+ if (!url.isLocalFile())
+ return false;
+
+ DbDialog dialog(DbDialog::ADD, this);
+ dialog.setPath(url.toLocalFile());
+ dialog.exec();
+ return true;
+ }
+ return false;
+}
+
+void MainWindow::pushClosedWindowSessionValue(const QVariant &value)
+{
+ closedWindowSessionValues.enqueue(value);
+
+ if (closedWindowSessionValues.size() > closedWindowsStackSize)
+ closedWindowSessionValues.dequeue();
+}
+
+void MainWindow::restoreLastClosedWindow()
+{
+ if (closedWindowSessionValues.size() == 0)
+ return;
+
+ QMdiSubWindow* activeWin = ui->mdiArea->activeSubWindow();
+ bool maximizedMode = activeWin && activeWin->isMaximized();
+
+ QVariant winSession = closedWindowSessionValues.takeLast();
+ if (maximizedMode)
+ {
+ QHash<QString, QVariant> winSessionHash = winSession.toHash();
+ winSessionHash.remove("geometry");
+ winSession = winSessionHash;
+ }
+
+ restoreWindowSession(winSession);
+}
+
+bool MainWindow::hasClosedWindowToRestore() const
+{
+ return closedWindowSessionValues.size() > 0;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.h b/SQLiteStudio3/guiSQLiteStudio/mainwindow.h
new file mode 100644
index 0000000..df12621
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.h
@@ -0,0 +1,234 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "common/extactioncontainer.h"
+#include "db/db.h"
+#include "ui_mainwindow.h"
+#include "mdiwindow.h"
+#include "services/updatemanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QMainWindow>
+#include <QHash>
+#include <QQueue>
+
+class QUiLoader;
+class DbTree;
+class StatusField;
+class EditorWindow;
+class MdiArea;
+class QActionGroup;
+class MdiWindow;
+class ViewWindow;
+class TableWindow;
+class FormManager;
+class DdlHistoryWindow;
+class FunctionsEditor;
+class CollationsEditor;
+class BugReportHistoryWindow;
+class NewVersionDialog;
+class Committable;
+class WidgetCover;
+class QProgressBar;
+class QLabel;
+
+#ifdef Q_OS_MACX
+#define PREV_TASK_KEY_SEQ Qt::CTRL + Qt::ALT + Qt::Key_Left
+#define NEXT_TASK_KEY_SEQ Qt::CTRL + Qt::ALT + Qt::Key_Right
+#else
+#define PREV_TASK_KEY_SEQ Qt::CTRL + Qt::Key_PageUp
+#define NEXT_TASK_KEY_SEQ Qt::CTRL + Qt::Key_PageDown
+#endif
+
+CFG_KEY_LIST(MainWindow, QObject::tr("Main window"),
+ CFG_KEY_ENTRY(OPEN_SQL_EDITOR, Qt::ALT + Qt::Key_E, QObject::tr("Open SQL editor"))
+ CFG_KEY_ENTRY(PREV_TASK, PREV_TASK_KEY_SEQ, QObject::tr("Previous window"))
+ CFG_KEY_ENTRY(NEXT_TASK, NEXT_TASK_KEY_SEQ, QObject::tr("Next window"))
+ CFG_KEY_ENTRY(HIDE_STATUS_FIELD, Qt::Key_Escape, QObject::tr("Hide status area"))
+ CFG_KEY_ENTRY(OPEN_CONFIG, Qt::Key_F2, QObject::tr("Open configuration dialog"))
+ CFG_KEY_ENTRY(OPEN_DEBUG_CONSOLE, Qt::Key_F12, QObject::tr("Open Debug Console"))
+)
+
+class GUI_API_EXPORT MainWindow : public QMainWindow, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ MDI_TILE,
+ MDI_CASCADE,
+ MDI_TILE_HORIZONTAL,
+ MDI_TILE_VERTICAL,
+ OPEN_SQL_EDITOR,
+ NEXT_TASK,
+ PREV_TASK,
+ HIDE_STATUS_FIELD,
+ OPEN_CONFIG,
+ OPEN_DDL_HISTORY,
+ OPEN_FUNCTION_EDITOR,
+ OPEN_COLLATION_EDITOR,
+ EXPORT,
+ IMPORT,
+ CLOSE_WINDOW,
+ CLOSE_ALL_WINDOWS,
+ CLOSE_OTHER_WINDOWS,
+ RESTORE_WINDOW,
+ RENAME_WINDOW,
+ OPEN_DEBUG_CONSOLE,
+ LICENSES,
+ HOMEPAGE,
+ FORUM,
+ USER_MANUAL,
+ SQLITE_DOCS,
+ REPORT_BUG,
+ FEATURE_REQUEST,
+ ABOUT,
+ BUG_REPORT_HISTORY,
+ CHECK_FOR_UPDATES
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_MAIN,
+ TOOLBAR_DATABASE,
+ TOOLBAR_STRUCTURE,
+ TOOLBAR_VIEW
+ };
+
+ static MainWindow* getInstance();
+
+ MdiArea* getMdiArea() const;
+ DbTree* getDbTree() const;
+ StatusField* getStatusField() const;
+ void restoreSession();
+ void setStyle(const QString& styleName);
+ FormManager* getFormManager() const;
+ bool eventFilter(QObject* obj, QEvent* e);
+ void pushClosedWindowSessionValue(const QVariant& value);
+ bool hasClosedWindowToRestore() const;
+ bool isClosingApp() const;
+ QToolBar* getToolBar(int toolbar) const;
+ void openDb(const QString& path);
+ QMenu* getDatabaseMenu() const;
+ QMenu* getStructureMenu() const;
+ QMenu* getViewMenu() const;
+ QMenu* getToolsMenu() const;
+ QMenu* getSQLiteStudioMenu() const;
+
+ protected:
+ void closeEvent(QCloseEvent *event);
+
+ private:
+ MainWindow();
+ ~MainWindow();
+
+ void init();
+ void createActions();
+ void setupDefShortcuts();
+ void initMenuBar();
+ void saveSession(MdiWindow* currWindow);
+ void restoreWindowSessions(const QList<QVariant>& windowSessions);
+ MdiWindow *restoreWindowSession(const QVariant& windowSessions);
+ QString currentStyle() const;
+ void closeNonSessionWindows();
+ DdlHistoryWindow* openDdlHistory();
+ FunctionsEditor* openFunctionEditor();
+ CollationsEditor* openCollationEditor();
+ BugReportHistoryWindow* openReportHistory();
+
+ template <class T>
+ T* openMdiWindow();
+
+ static bool confirmQuit(const QList<Committable*>& instances);
+
+ static MainWindow* instance;
+ static constexpr int closedWindowsStackSize = 20;
+ static_char* openUpdatesUrl = "open_updates://";
+
+ Ui::MainWindow *ui = nullptr;
+ DbTree* dbTree = nullptr;
+ StatusField* statusField = nullptr;
+ QMenu* mdiMenu = nullptr;
+ FormManager* formManager = nullptr;
+ QQueue<QVariant> closedWindowSessionValues;
+ bool closingApp = false;
+ QMenu* dbMenu = nullptr;
+ QMenu* structMenu = nullptr;
+ QMenu* viewMenu = nullptr;
+ QMenu* toolsMenu = nullptr;
+ QMenu* sqlitestudioMenu = nullptr;
+ QPointer<NewVersionDialog> newVersionDialog;
+ WidgetCover* widgetCover = nullptr;
+ QLabel* updatingLabel = nullptr;
+ QProgressBar* updatingBusyBar = nullptr;
+ QProgressBar* updatingSubBar = nullptr;
+ bool manualUpdatesChecking = false;
+
+ public slots:
+ EditorWindow* openSqlEditor();
+ void updateWindowActions();
+
+ private slots:
+ void cleanUp();
+ void openSqlEditorSlot();
+ void refreshMdiWindows();
+ void hideStatusField();
+ void openConfig();
+ void openDdlHistorySlot();
+ void openFunctionEditorSlot();
+ void openCollationEditorSlot();
+ void exportAnything();
+ void importAnything();
+ void closeAllWindows();
+ void closeAllWindowsButSelected();
+ void closeSelectedWindow();
+ void restoreLastClosedWindow();
+ void renameWindow();
+ void openDebugConsole();
+ void reportBug();
+ void requestFeature();
+ void aboutSqlitestudio();
+ void licenses();
+ void homepage();
+ void forum();
+ void userManual();
+ void sqliteDocs();
+ void reportHistory();
+ void updatesAvailable(const QList<UpdateManager::UpdateEntry>& updates);
+ void noUpdatesAvailable();
+ void statusFieldLinkClicked(const QString& link);
+ void checkForUpdates();
+ void handleUpdatingProgress(const QString& jobTitle, int jobPercent, int totalPercent);
+ void handleUpdatingError();
+};
+
+template <class T>
+T* MainWindow::openMdiWindow()
+{
+ T* win = nullptr;
+ foreach (MdiWindow* mdiWin, ui->mdiArea->getWindows())
+ {
+ win = dynamic_cast<T*>(mdiWin->getMdiChild());
+ if (win)
+ {
+ ui->mdiArea->setActiveSubWindow(mdiWin);
+ return win;
+ }
+ }
+
+ win = new T(ui->mdiArea);
+ if (win->isInvalid())
+ {
+ delete win;
+ return nullptr;
+ }
+
+ ui->mdiArea->addSubWindow(win);
+ return win;
+}
+
+
+#define MAINWINDOW MainWindow::getInstance()
+
+#endif // MAINWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui b/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui
new file mode 100644
index 0000000..0ae72c8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>964</width>
+ <height>626</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQLiteStudio (?.?.?)</string>
+ </property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="documentMode">
+ <bool>true</bool>
+ </property>
+ <property name="dockOptions">
+ <set>QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks</set>
+ </property>
+ <property name="unifiedTitleAndToolBarOnMac">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="MdiArea" name="mdiArea">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="background">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>138</red>
+ <green>138</green>
+ <blue>138</blue>
+ </color>
+ </brush>
+ </property>
+ <property name="documentMode">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>964</width>
+ <height>22</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QToolBar" name="dbToolbar">
+ <property name="windowTitle">
+ <string>Database toolbar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QToolBar" name="structureToolbar">
+ <property name="windowTitle">
+ <string>Structure toolbar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <property name="windowTitle">
+ <string>Tools</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="TaskBar" name="taskBar">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Window list</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QToolBar" name="viewToolbar">
+ <property name="windowTitle">
+ <string>View toolbar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+ <customwidget>
+ <class>MdiArea</class>
+ <extends>QMdiArea</extends>
+ <header>mdiarea.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>TaskBar</class>
+ <extends>QToolBar</extends>
+ <header>taskbar.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp b/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp
new file mode 100644
index 0000000..d53874c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp
@@ -0,0 +1,264 @@
+#include "mdiarea.h"
+#include "mainwindow.h"
+#include "iconmanager.h"
+#include "mdichild.h"
+#include "mdiwindow.h"
+#include "taskbar.h"
+#include <QMdiSubWindow>
+#include <QAction>
+#include <QActionGroup>
+#include <QDebug>
+
+MdiArea::MdiArea(QWidget *parent) :
+ QMdiArea(parent)
+{
+}
+
+MdiWindow *MdiArea::addSubWindow(MdiChild *mdiChild)
+{
+ MdiWindow* mdiWin = new MdiWindow(mdiChild, this);
+ QMdiArea::addSubWindow(mdiWin);
+ mdiWin->show();
+
+ if (taskBar)
+ {
+ QAction* action = taskBar->addTask(mdiWin->windowIcon(), mdiWin->windowTitle());
+ action->setCheckable(true);
+ action->setChecked(true);
+ actionToWinMap[action] = mdiWin;
+ winToActionMap[mdiWin] = action;
+
+ connect(action, &QAction::triggered, this, &MdiArea::taskActivated);
+ connect(mdiWin, &QMdiSubWindow::aboutToActivate, this, &MdiArea::windowActivated);
+ }
+
+ if (!mdiChild->handleInitialFocus())
+ mdiChild->setFocus();
+
+ emit windowListChanged();
+ return mdiWin;
+}
+
+MdiWindow *MdiArea::getActiveWindow()
+{
+ return dynamic_cast<MdiWindow*>(activeSubWindow());
+}
+
+void MdiArea::setTaskBar(TaskBar* value)
+{
+ taskBar = value;
+}
+
+TaskBar* MdiArea::getTaskBar() const
+{
+ return taskBar;
+}
+
+QAction* MdiArea::getTaskByWindow(MdiWindow* window)
+{
+ if (winToActionMap.contains(window))
+ return winToActionMap[window];
+
+ return nullptr;
+}
+
+QList<MdiWindow*> MdiArea::getWindows() const
+{
+ QList<MdiWindow*> windowList;
+ foreach(QAction* action, taskBar->getTasks())
+ windowList << actionToWinMap[action];
+
+ return windowList;
+}
+
+QList<MdiChild*> MdiArea::getMdiChilds() const
+{
+ QList<MdiChild*> childs;
+ for (MdiWindow* win : getWindows())
+ childs << win->getMdiChild();
+
+ return childs;
+}
+
+QList<MdiWindow*> MdiArea::getWindowsToTile() const
+{
+ QList<MdiWindow*> list;
+ foreach (MdiWindow *window, getWindows())
+ {
+ if (window->isMinimized())
+ continue;
+
+ list << window;
+ }
+ return list;
+}
+
+void MdiArea::taskActivated()
+{
+ QAction* action = dynamic_cast<QAction*>(sender());
+ if (!action)
+ {
+ qWarning() << "MdiArea::taskActivated() slot called by sender that is not QAction.";
+ return;
+ }
+
+ setActiveSubWindow(actionToWinMap[action]);
+}
+
+void MdiArea::windowDestroyed(MdiWindow* window)
+{
+ if (!taskBar)
+ return;
+
+ QAction* action = winToActionMap[window];
+ winToActionMap.remove(window);
+ actionToWinMap.remove(action);
+ taskBar->removeTask(action);
+ delete action;
+
+ emit windowListChanged();
+}
+
+void MdiArea::windowActivated()
+{
+ if (!taskBar)
+ return;
+
+ MdiWindow* subWin = dynamic_cast<MdiWindow*>(sender());
+ if (!subWin)
+ {
+ qWarning() << "MdiArea::windowActivated() slot called by sender that is not QMdiSubWindow.";
+ return;
+ }
+
+ QAction* action = winToActionMap[subWin];
+ action->setChecked(true);
+}
+
+void MdiArea::tileHorizontally()
+{
+ if (taskBar->isEmpty())
+ return;
+
+ bool gotFocus = false;
+ QPoint position(0, 0);
+ QList<MdiWindow*> windowsToTile = getWindowsToTile();
+ int winCnt = windowsToTile.count();
+ foreach (MdiWindow *window, windowsToTile)
+ {
+ if (window->isMaximized())
+ window->showNormal();
+
+ QRect rect(0, 0, width() / winCnt, height());
+ window->setGeometry(rect);
+ window->move(position);
+ position.setX(position.x() + window->width());
+
+ if (window->hasFocus())
+ gotFocus = true;
+ }
+
+ if (!gotFocus && windowsToTile.size() > 0)
+ windowsToTile.first()->setFocus();
+}
+
+void MdiArea::tileVertically()
+{
+ if (taskBar->isEmpty())
+ return;
+
+ bool gotFocus = false;
+ QPoint position(0, 0);
+ QList<MdiWindow*> windowsToTile = getWindowsToTile();
+ int winCnt = windowsToTile.count();
+ foreach (MdiWindow *window, windowsToTile)
+ {
+ if (window->isMaximized())
+ window->showNormal();
+
+ QRect rect(0, 0, width(), height() / winCnt);
+ window->setGeometry(rect);
+ window->move(position);
+ position.setY(position.y() + window->height());
+
+ if (window->hasFocus())
+ gotFocus = true;
+ }
+
+ if (!gotFocus && windowsToTile.size() > 0)
+ windowsToTile.first()->setFocus();
+}
+
+void MdiArea::closeAllButActive()
+{
+ QList<QMdiSubWindow*> allButActive = subWindowList();
+ allButActive.removeOne(activeSubWindow());
+
+ foreach (QMdiSubWindow *window, allButActive)
+ window->close();
+}
+
+MdiWindow* MdiArea::getWindowByChild(MdiChild *child)
+{
+ if (!child)
+ return nullptr;
+
+ foreach (QMdiSubWindow *window, subWindowList())
+ if (window->widget() == child)
+ return dynamic_cast<MdiWindow*>(window);
+
+ return nullptr;
+}
+
+MdiWindow* MdiArea::getCurrentWindow()
+{
+ QMdiSubWindow* subWin = activeSubWindow();
+ return dynamic_cast<MdiWindow*>(subWin);
+}
+
+bool MdiArea::isActiveSubWindow(MdiWindow *window)
+{
+ if (!window)
+ return false;
+
+ QMdiSubWindow* subWin = currentSubWindow();
+ if (!subWin)
+ return false;
+
+ return window == subWin;
+}
+
+bool MdiArea::isActiveSubWindow(MdiChild *child)
+{
+ if (!child)
+ return false;
+
+ QMdiSubWindow* subWin = currentSubWindow();
+ if (!subWin)
+ return false;
+
+ return child == subWin->widget();
+}
+
+QStringList MdiArea::getWindowTitles()
+{
+ QStringList titles;
+ foreach (QMdiSubWindow *subWin, subWindowList())
+ titles << subWin->windowTitle();
+
+ return titles;
+}
+
+MdiWindow *MdiArea::getWindowByTitle(const QString &title)
+{
+ foreach (QMdiSubWindow *subWin, subWindowList())
+ {
+ QString t = subWin->windowTitle();
+ if (subWin->windowTitle() == title)
+ {
+ return dynamic_cast<MdiWindow*>(subWin);
+ }
+ }
+
+ return nullptr;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiarea.h b/SQLiteStudio3/guiSQLiteStudio/mdiarea.h
new file mode 100644
index 0000000..f34434e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdiarea.h
@@ -0,0 +1,73 @@
+#ifndef MDIAREA_H
+#define MDIAREA_H
+
+#include "mdiwindow.h"
+#include "guiSQLiteStudio_global.h"
+#include <QMdiArea>
+
+class TaskBar;
+class QActionGroup;
+class MdiChild;
+
+class GUI_API_EXPORT MdiArea : public QMdiArea
+{
+ Q_OBJECT
+ public:
+ explicit MdiArea(QWidget *parent = 0);
+
+ MdiWindow* addSubWindow(MdiChild* mdiChild);
+ MdiWindow* getActiveWindow();
+ MdiWindow* getWindowByTitle(const QString& title);
+ MdiWindow* getWindowByChild(MdiChild* child);
+ MdiWindow* getCurrentWindow();
+ bool isActiveSubWindow(MdiWindow* window);
+ bool isActiveSubWindow(MdiChild* child);
+ QStringList getWindowTitles();
+ void setTaskBar(TaskBar *value);
+ TaskBar* getTaskBar() const;
+ QAction* getTaskByWindow(MdiWindow* window);
+ QList<MdiWindow*> getWindows() const;
+ QList<MdiChild*> getMdiChilds() const;
+
+ template<class T>
+ QList<T*> getMdiChilds() const;
+
+ private:
+ QList<MdiWindow*> getWindowsToTile() const;
+
+ TaskBar* taskBar = nullptr;
+ QHash<QAction*,MdiWindow*> actionToWinMap;
+ QHash<MdiWindow*,QAction*> winToActionMap;
+
+ signals:
+ void windowListChanged();
+
+ private slots:
+ void taskActivated();
+ void windowActivated();
+
+ public slots:
+ void windowDestroyed(MdiWindow* window);
+ void tileHorizontally();
+ void tileVertically();
+ void closeAllButActive();
+};
+
+template<class T>
+QList<T*> MdiArea::getMdiChilds() const
+{
+ QList<T*> childs;
+ T* child = nullptr;
+ for (MdiWindow* win : getWindows())
+ {
+ child = dynamic_cast<T*>(win->getMdiChild());
+ if (child)
+ childs << child;
+ }
+
+ return childs;
+}
+
+#define MDIAREA MainWindow::getInstance()->getMdiArea()
+
+#endif // MDIAREA_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp b/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp
new file mode 100644
index 0000000..da45b42
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp
@@ -0,0 +1,77 @@
+#include "mdichild.h"
+#include "mdiwindow.h"
+#include "iconmanager.h"
+#include "mainwindow.h"
+#include <QDebug>
+
+MdiChild::MdiChild(QWidget* parent) :
+ QWidget(parent)
+{
+}
+
+MdiChild::~MdiChild()
+{
+}
+
+QVariant MdiChild::getSessionValue()
+{
+ QVariant value = saveSession();
+ QHash<QString, QVariant> hash = value.toHash();
+ hash["class"] = QString(metaObject()->className());
+ return hash;
+}
+
+bool MdiChild::applySessionValue(const QVariant& sessionValue)
+{
+ bool result = restoreSession(sessionValue);
+ return result;
+}
+
+MdiWindow* MdiChild::getMdiWindow() const
+{
+ return mdiWindow;
+}
+
+void MdiChild::setMdiWindow(MdiWindow* value)
+{
+ mdiWindow = value;
+ if (mdiWindow)
+ {
+ mdiWindow->setWindowTitle(getTitleForMdiWindow());
+ mdiWindow->setWindowIcon(*getIconNameForMdiWindow());
+ }
+}
+
+bool MdiChild::isInvalid() const
+{
+ return invalid;
+}
+
+bool MdiChild::restoreSessionNextTime()
+{
+ return true;
+}
+
+void MdiChild::updateWindowTitle()
+{
+ if (mdiWindow)
+ {
+ QString newTitle = getTitleForMdiWindow();
+ if (mdiWindow->windowTitle() != newTitle)
+ mdiWindow->rename(newTitle);
+ }
+}
+
+bool MdiChild::handleInitialFocus()
+{
+ return false;
+}
+
+Db* MdiChild::getAssociatedDb() const
+{
+ return nullptr;
+}
+
+void MdiChild::dbClosedFinalCleanup()
+{
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdichild.h b/SQLiteStudio3/guiSQLiteStudio/mdichild.h
new file mode 100644
index 0000000..5ca20e5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdichild.h
@@ -0,0 +1,47 @@
+#ifndef MDICHILD_H
+#define MDICHILD_H
+
+#include "common/extactioncontainer.h"
+#include "committable.h"
+#include <QWidget>
+#include <QVariant>
+
+class MdiWindow;
+class Icon;
+class Db;
+
+class GUI_API_EXPORT MdiChild : public QWidget, public ExtActionContainer, public Committable
+{
+ Q_OBJECT
+ public:
+ explicit MdiChild(QWidget* parent = 0);
+ ~MdiChild();
+
+ QVariant getSessionValue();
+ bool applySessionValue(const QVariant& sessionValue);
+
+ MdiWindow* getMdiWindow() const;
+ void setMdiWindow(MdiWindow* value);
+ bool isInvalid() const;
+ void updateWindowTitle();
+ virtual bool restoreSessionNextTime();
+ virtual bool handleInitialFocus();
+ virtual Db* getAssociatedDb() const;
+ virtual void dbClosedFinalCleanup();
+
+ protected:
+
+ virtual QVariant saveSession() = 0;
+ virtual bool restoreSession(const QVariant& sessionValue) = 0;
+
+ virtual Icon* getIconNameForMdiWindow() = 0;
+ virtual QString getTitleForMdiWindow() = 0;
+
+ bool invalid = false;
+
+ private:
+ MdiWindow* mdiWindow = nullptr;
+};
+
+
+#endif // MDICHILD_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp
new file mode 100644
index 0000000..dafd108
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp
@@ -0,0 +1,201 @@
+#include "mdiwindow.h"
+#include "mdichild.h"
+#include "common/unused.h"
+#include "mdiarea.h"
+#include "mainwindow.h"
+#include "services/dbmanager.h"
+#include "db/db.h"
+#include <QDateTime>
+#include <QApplication>
+#include <QDebug>
+#include <QFocusEvent>
+#include <QAction>
+#include <QMessageBox>
+#include <QAbstractButton>
+
+MdiWindow::MdiWindow(MdiChild* mdiChild, MdiArea *mdiArea, Qt::WindowFlags flags) :
+ QMdiSubWindow(mdiArea->viewport(), flags), mdiArea(mdiArea)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ setWidget(mdiChild);
+
+ connect(DBLIST, SIGNAL(dbAboutToBeDisconnected(Db*,bool&)), this, SLOT(dbAboutToBeDisconnected(Db*,bool&)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+}
+
+MdiWindow::~MdiWindow()
+{
+ if (SQLITESTUDIO->getImmediateQuit())
+ return;
+
+ if (!MAINWINDOW->isClosingApp())
+ MAINWINDOW->pushClosedWindowSessionValue(saveSession());
+
+ mdiArea->windowDestroyed(this);
+}
+
+QVariant MdiWindow::saveSession()
+{
+ if (!widget())
+ return QVariant();
+
+ QHash<QString, QVariant> hash = getMdiChild()->getSessionValue().toHash();
+ hash["title"] = windowTitle();
+ hash["position"] = pos();
+ hash["geometry"] = saveGeometry();
+ return hash;
+}
+
+bool MdiWindow::restoreSession(const QVariant& sessionValue)
+{
+ if (!widget())
+ return true;
+
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ return true;
+
+ if (value.contains("geometry"))
+ restoreGeometry(value["geometry"].toByteArray());
+
+ if (value.contains("position"))
+ move(value["position"].toPoint());
+
+ if (value.contains("title"))
+ {
+ QString title = value["title"].toString();
+ rename(title);
+ }
+
+ return getMdiChild()->applySessionValue(sessionValue);
+}
+
+MdiChild* MdiWindow::getMdiChild() const
+{
+ if (!widget())
+ return nullptr;
+
+ return dynamic_cast<MdiChild*>(widget());
+}
+
+void MdiWindow::setWidget(MdiChild* value)
+{
+ QMdiSubWindow::setWidget(value);
+ if (value)
+ value->setMdiWindow(this);
+}
+
+bool MdiWindow::restoreSessionNextTime()
+{
+ return getMdiChild()->restoreSessionNextTime();
+}
+
+void MdiWindow::rename(const QString& title)
+{
+ setWindowTitle(title);
+
+ QAction* task = mdiArea->getTaskByWindow(this);
+ if (task)
+ task->setText(title);
+}
+
+void MdiWindow::changeEvent(QEvent* event)
+{
+ if (event->type() != QEvent::WindowStateChange)
+ {
+ QMdiSubWindow::changeEvent(event);
+ return;
+ }
+
+ QWindowStateChangeEvent *changeEvent = static_cast<QWindowStateChangeEvent *>(event);
+
+ bool wasActive = changeEvent->oldState().testFlag(Qt::WindowActive);
+ bool isActive = windowState().testFlag(Qt::WindowActive);
+
+ /*
+ * This is a hack for a bug in Qt: QTBUG-23515. The problem is that when QMdiSubWindow
+ * changes its state (because we used shortcut, or the TaskBar to activate another window,
+ * or window is maximized or minimized), then the code responsible for hiding old window
+ * gives focus to its previous or next child widget (depending on what is the state change),
+ * before the new window is shown. This seems to be happening in QWidgetPrivate::hide_helper().
+ *
+ */
+ if (wasActive && isActive)
+ {
+ // Handle problem with maximize/minimize
+ QWidget* w = focusWidget();
+ QMdiSubWindow::changeEvent(event);
+ if (w)
+ w->setFocus();
+ }
+ else if (wasActive && !isActive)
+ {
+ // Handle problem with switching between 2 MDI windows - part 1
+ lastFocusedWidget = focusWidget();
+ QMdiSubWindow::changeEvent(event);
+ }
+ else if (!wasActive && isActive)
+ {
+ // Handle problem with switching between 2 MDI windows - part 2
+ QMdiSubWindow::changeEvent(event);
+ if (!lastFocusedWidget.isNull() && (!focusWidget() || !isAncestorOf(focusWidget())))
+ lastFocusedWidget->setFocus();
+ }
+ else
+ QMdiSubWindow::changeEvent(event);
+}
+
+void MdiWindow::closeEvent(QCloseEvent* e)
+{
+ if (dbBeingClosed || MAINWINDOW->isClosingApp() || !getMdiChild()->isUncommited())
+ {
+ QMdiSubWindow::closeEvent(e);
+ return;
+ }
+
+ if (confirmClose())
+ QMdiSubWindow::closeEvent(e);
+ else
+ e->ignore();
+}
+
+void MdiWindow::dbAboutToBeDisconnected(Db* db, bool& deny)
+{
+ if (!db || getMdiChild()->getAssociatedDb() != db)
+ return;
+
+ if (MAINWINDOW->isClosingApp())
+ return;
+
+ if (getMdiChild()->isUncommited() && !confirmClose())
+ deny = true;
+ else
+ dbBeingClosed = true;
+}
+
+void MdiWindow::dbDisconnected(Db* db)
+{
+ if (!db || getMdiChild()->getAssociatedDb() != db)
+ return;
+
+ if (MAINWINDOW->isClosingApp())
+ return;
+
+ getMdiChild()->dbClosedFinalCleanup();
+ close();
+}
+
+bool MdiWindow::confirmClose()
+{
+ QMessageBox msgBox(QMessageBox::Question, tr("Uncommited changes"), getMdiChild()->getQuitUncommitedConfirmMessage(), QMessageBox::Yes|QMessageBox::No, this);
+ msgBox.setDefaultButton(QMessageBox::No);
+
+ QAbstractButton* closeBtn = msgBox.button(QMessageBox::Yes);
+ QAbstractButton* cancelBtn = msgBox.button(QMessageBox::No);
+ closeBtn->setText(tr("Close anyway"));
+ closeBtn->setIcon(ICONS.CLOSE);
+ cancelBtn->setText(tr("Don't close"));
+ cancelBtn->setIcon(ICONS.GO_BACK);
+
+ return (msgBox.exec() == QMessageBox::Yes);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h
new file mode 100644
index 0000000..fd6c4dc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h
@@ -0,0 +1,43 @@
+#ifndef MDIWINDOW_H
+#define MDIWINDOW_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QMdiSubWindow>
+#include <QPointer>
+
+class MdiChild;
+class MdiArea;
+class Db;
+
+class GUI_API_EXPORT MdiWindow : public QMdiSubWindow
+{
+ Q_OBJECT
+
+ public:
+ MdiWindow(MdiChild* mdiChild, MdiArea *mdiArea, Qt::WindowFlags flags = 0);
+ virtual ~MdiWindow();
+
+ virtual QVariant saveSession();
+ virtual bool restoreSession(const QVariant& sessionValue);
+
+ MdiChild* getMdiChild() const;
+ void setWidget(MdiChild* value);
+ bool restoreSessionNextTime();
+ void rename(const QString& title);
+
+ void changeEvent(QEvent *event);
+ void closeEvent(QCloseEvent* e);
+
+ private:
+ bool confirmClose();
+
+ QPointer<QWidget> lastFocusedWidget;
+ MdiArea* mdiArea = nullptr;
+ bool dbBeingClosed = false;
+
+ private slots:
+ void dbAboutToBeDisconnected(Db* db, bool& deny);
+ void dbDisconnected(Db* db);
+};
+
+#endif // MDIWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp
new file mode 100644
index 0000000..2bae2f9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp
@@ -0,0 +1,366 @@
+#include "multieditor.h"
+#include "multieditortext.h"
+#include "multieditornumeric.h"
+#include "multieditordatetime.h"
+#include "multieditordate.h"
+#include "multieditortime.h"
+#include "multieditorbool.h"
+#include "multieditorhex.h"
+#include "mainwindow.h"
+#include "common/unused.h"
+#include "services/notifymanager.h"
+#include "services/pluginmanager.h"
+#include "multieditorwidgetplugin.h"
+#include "uiconfig.h"
+#include "dialogs/configdialog.h"
+#include "formview.h"
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QTabWidget>
+#include <QTabBar>
+#include <QLabel>
+#include <QCheckBox>
+#include <QVariant>
+#include <QEvent>
+#include <QGraphicsColorizeEffect>
+#include <QToolButton>
+#include <QDebug>
+#include <QKeyEvent>
+
+static QHash<QString,bool> missingEditorPluginsAlreadyWarned;
+
+MultiEditor::MultiEditor(QWidget *parent) :
+ QWidget(parent)
+{
+ init();
+}
+
+void MultiEditor::init()
+{
+ QVBoxLayout* vbox = new QVBoxLayout();
+ vbox->setMargin(margins);
+ vbox->setSpacing(spacing);
+ setLayout(vbox);
+
+ QWidget* top = new QWidget();
+ layout()->addWidget(top);
+
+ QHBoxLayout* hbox = new QHBoxLayout();
+ hbox->setMargin(0);
+ hbox->setSpacing(0);
+ top->setLayout(hbox);
+
+ nullCheck = new QCheckBox(tr("Null value", "multieditor"));
+ hbox->addWidget(nullCheck);
+
+ hbox->addStretch();
+
+ stateLabel = new QLabel();
+ hbox->addWidget(stateLabel);
+
+ hbox->addSpacing(50);
+
+ tabs = new QTabWidget();
+ layout()->addWidget(tabs);
+ tabs->tabBar()->installEventFilter(this);
+
+ configBtn = new QToolButton();
+ configBtn->setToolTip(tr("Configure editors for this data type"));
+ configBtn->setIcon(ICONS.CONFIGURE);
+ configBtn->setFocusPolicy(Qt::NoFocus);
+ configBtn->setAutoRaise(true);
+ configBtn->setEnabled(false);
+ connect(configBtn, SIGNAL(clicked()), this, SLOT(configClicked()));
+ tabs->setCornerWidget(configBtn);
+
+ QGraphicsColorizeEffect* effect = new QGraphicsColorizeEffect();
+ effect->setColor(Qt::black);
+ effect->setStrength(0.5);
+ nullEffect = effect;
+ tabs->setGraphicsEffect(effect);
+
+ connect(tabs, &QTabWidget::currentChanged, this, &MultiEditor::tabChanged);
+ connect(nullCheck, &QCheckBox::stateChanged, this, &MultiEditor::nullStateChanged);
+ connect(this, SIGNAL(modified()), this, SLOT(setModified()));
+}
+
+void MultiEditor::tabChanged(int idx)
+{
+ int prevTab = currentTab;
+ currentTab = idx;
+
+ MultiEditorWidget* newEditor = editors[idx];
+ newEditor->setFocus();
+
+ if (prevTab < 0)
+ return;
+
+ if (newEditor->isUpToDate())
+ return;
+
+ MultiEditorWidget* prevEditor = editors[prevTab];
+ newEditor->setValue(prevEditor->getValue());
+ newEditor->setUpToDate(true);
+}
+
+void MultiEditor::nullStateChanged(int state)
+{
+ bool checked = (state == Qt::Checked);
+
+ if (checked)
+ valueBeforeNull = getValueOmmitNull();
+
+ updateNullEffect();
+ updateValue(checked ? QVariant() : valueBeforeNull);
+
+ if (!checked)
+ valueBeforeNull.clear();
+
+ tabs->setEnabled(!checked);
+ emit modified();
+}
+
+void MultiEditor::invalidateValue()
+{
+ if (invalidatingDisabled)
+ return;
+
+ QObject* obj = sender();
+ if (!obj)
+ {
+ qWarning() << "No sender object while invalidating MultiEditor value.";
+ return;
+ }
+
+ QWidget* editorWidget = nullptr;
+ for (int i = 0; i < tabs->count(); i++)
+ {
+ editorWidget = tabs->widget(i);
+ if (editorWidget == obj)
+ continue; // skip sender
+
+ dynamic_cast<MultiEditorWidget*>(editorWidget)->setUpToDate(false);
+ }
+
+ emit modified();
+}
+
+void MultiEditor::setModified()
+{
+ valueModified = true;
+}
+
+void MultiEditor::addEditor(MultiEditorWidget* editorWidget)
+{
+ editorWidget->setReadOnly(readOnly);
+ connect(editorWidget, SIGNAL(valueModified()), this, SLOT(invalidateValue()));
+ editors << editorWidget;
+ tabs->addTab(editorWidget, editorWidget->getTabLabel().replace("&", "&&"));
+ editorWidget->installEventFilter(this);
+}
+
+void MultiEditor::showTab(int idx)
+{
+ tabs->setCurrentIndex(idx);
+}
+
+void MultiEditor::setValue(const QVariant& value)
+{
+ nullCheck->setChecked(!value.isValid() || value.isNull());
+ valueBeforeNull = value;
+ updateVisibility();
+ updateValue(value);
+ valueModified = false;
+}
+
+QVariant MultiEditor::getValue() const
+{
+ if (nullCheck->isChecked())
+ return QVariant();
+
+ return getValueOmmitNull();
+}
+
+bool MultiEditor::isModified() const
+{
+ return valueModified;
+}
+
+bool MultiEditor::eventFilter(QObject* obj, QEvent* event)
+{
+ if (event->type() == QEvent::Wheel)
+ {
+ QWidget::event(event);
+ return true;
+ }
+
+ return QWidget::eventFilter(obj, event);
+}
+
+bool MultiEditor::getReadOnly() const
+{
+ return readOnly;
+}
+
+void MultiEditor::setReadOnly(bool value)
+{
+ readOnly = value;
+
+ for (int i = 0; i < tabs->count(); i++)
+ dynamic_cast<MultiEditorWidget*>(tabs->widget(i))->setReadOnly(value);
+
+ stateLabel->setVisible(readOnly);
+ nullCheck->setEnabled(!readOnly);
+ updateVisibility();
+ updateLabel();
+}
+
+void MultiEditor::setDeletedRow(bool value)
+{
+ deleted = value;
+ updateLabel();
+}
+
+void MultiEditor::setDataType(const DataType& dataType)
+{
+ this->dataType = dataType;
+
+ foreach (MultiEditorWidget* editorWidget, getEditorTypes(dataType))
+ addEditor(editorWidget);
+
+ showTab(0);
+ configBtn->setEnabled(true);
+}
+
+void MultiEditor::focusThisEditor()
+{
+ MultiEditorWidget* w = dynamic_cast<MultiEditorWidget*>(tabs->currentWidget());
+ if (!w)
+ return;
+
+ w->focusThisWidget();
+}
+
+void MultiEditor::loadBuiltInEditors()
+{
+ PLUGINS->loadBuiltInPlugin(new MultiEditorBoolPlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorDateTimePlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorDatePlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorHexPlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorTextPlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorTimePlugin);
+ PLUGINS->loadBuiltInPlugin(new MultiEditorNumericPlugin);
+}
+
+QList<MultiEditorWidget*> MultiEditor::getEditorTypes(const DataType& dataType)
+{
+ QList<MultiEditorWidget*> editors;
+
+ QString typeStr = dataType.toString().trimmed().toUpper();
+ QHash<QString,QVariant> editorsOrder = CFG_UI.General.DataEditorsOrder.get();
+ if (editorsOrder.contains(typeStr))
+ {
+ MultiEditorWidgetPlugin* plugin = nullptr;
+ for (const QString& editorPluginName : editorsOrder[typeStr].toStringList())
+ {
+ plugin = dynamic_cast<MultiEditorWidgetPlugin*>(PLUGINS->getLoadedPlugin(editorPluginName));
+ if (!plugin)
+ {
+ if (!missingEditorPluginsAlreadyWarned.contains(editorPluginName))
+ {
+ notifyWarn(tr("Data editor plugin '%1' not loaded, while it is defined for editing '%1' data type."));
+ missingEditorPluginsAlreadyWarned[editorPluginName] = true;
+ }
+ continue;
+ }
+
+ editors << plugin->getInstance();
+ }
+ }
+
+ if (editors.size() > 0)
+ return editors;
+
+ //
+ // Prepare default list of editors
+ //
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+
+ typedef QPair<int,MultiEditorWidget*> EditorWithPriority;
+
+ QList<EditorWithPriority> sortedEditors;
+ EditorWithPriority editorWithPrio;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ if (!plugin->validFor(dataType))
+ continue;
+
+ editorWithPrio.first = plugin->getPriority(dataType);
+ editorWithPrio.second = plugin->getInstance();
+ sortedEditors << editorWithPrio;
+ }
+
+ qSort(sortedEditors.begin(), sortedEditors.end(), [=](const EditorWithPriority& ed1, const EditorWithPriority& ed2) -> bool
+ {
+ return ed1.first < ed2.first;
+ });
+
+ for (const EditorWithPriority& e : sortedEditors)
+ editors << e.second;
+
+ return editors;
+}
+
+void MultiEditor::configClicked()
+{
+ ConfigDialog config(MAINWINDOW);
+ config.configureDataEditors(dataType.toString());
+ config.exec();
+}
+
+void MultiEditor::updateVisibility()
+{
+ tabs->setVisible(!readOnly || !nullCheck->isChecked());
+ nullCheck->setVisible(!readOnly || nullCheck->isChecked());
+ updateNullEffect();
+}
+
+void MultiEditor::updateNullEffect()
+{
+ nullEffect->setEnabled(tabs->isVisible() && nullCheck->isChecked());
+ if (tabs->isVisible())
+ {
+ for (int i = 0; i < tabs->count(); i++)
+ dynamic_cast<MultiEditorWidget*>(tabs->widget(i))->update();
+
+ nullEffect->update();
+ }
+}
+
+void MultiEditor::updateValue(const QVariant& newValue)
+{
+ invalidatingDisabled = true;
+ MultiEditorWidget* editorWidget = nullptr;
+ for (int i = 0; i < tabs->count(); i++)
+ {
+ editorWidget = dynamic_cast<MultiEditorWidget*>(tabs->widget(i));
+ editorWidget->setValue(newValue);
+ editorWidget->setUpToDate(true);
+ }
+ invalidatingDisabled = false;
+}
+
+void MultiEditor::updateLabel()
+{
+ if (deleted)
+ stateLabel->setText("<i>"+tr("Deleted", "multieditor")+"<i>");
+ else if (readOnly)
+ stateLabel->setText("<i>"+tr("Read only", "multieditor")+"<i>");
+ else
+ stateLabel->setText("");
+}
+
+QVariant MultiEditor::getValueOmmitNull() const
+{
+ return dynamic_cast<MultiEditorWidget*>(tabs->currentWidget())->getValue();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h
new file mode 100644
index 0000000..2576c97
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h
@@ -0,0 +1,96 @@
+#ifndef MULTIEDITOR_H
+#define MULTIEDITOR_H
+
+#include "guiSQLiteStudio_global.h"
+#include "datagrid/sqlquerymodelcolumn.h"
+#include <QWidget>
+#include <QVariant>
+
+class QCheckBox;
+class QTabWidget;
+class MultiEditorWidget;
+class QLabel;
+class MultiEditorWidgetPlugin;
+class QToolButton;
+
+class GUI_API_EXPORT MultiEditor : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVariant value READ getValue WRITE setValue)
+
+ public:
+ enum BuiltInEditor
+ {
+ TEXT,
+ NUMERIC,
+ BOOLEAN,
+ DATE,
+ TIME,
+ DATETIME,
+ HEX
+ };
+
+ explicit MultiEditor(QWidget *parent = 0);
+
+ void addEditor(MultiEditorWidget* editorWidget);
+ void showTab(int idx);
+
+ void setValue(const QVariant& value);
+ QVariant getValue() const;
+ bool isModified() const;
+ bool eventFilter(QObject* obj, QEvent* event);
+ bool getReadOnly() const;
+ void setReadOnly(bool value);
+ void setDeletedRow(bool value);
+ void setDataType(const DataType& dataType);
+ void focusThisEditor();
+
+ static void loadBuiltInEditors();
+
+ private:
+ void init();
+ void updateVisibility();
+ void updateNullEffect();
+ void updateValue(const QVariant& newValue);
+ void updateLabel();
+ QVariant getValueOmmitNull() const;
+
+ static QList<MultiEditorWidget*> getEditorTypes(const DataType& dataType);
+
+ static const int margins = 2;
+ static const int spacing = 2;
+
+ QCheckBox* nullCheck = nullptr;
+ QTabWidget* tabs = nullptr;
+ QList<MultiEditorWidget*> editors;
+ QLabel* stateLabel = nullptr;
+ bool readOnly = false;
+ bool deleted = false;
+ bool invalidatingDisabled = false;
+ QGraphicsEffect* nullEffect = nullptr;
+ bool valueModified = false;
+ QVariant valueBeforeNull;
+ QToolButton* configBtn = nullptr;
+ DataType dataType;
+
+ /**
+ * @brief currentTab
+ * Hold current tab index. It might seem as duplicate for tabs->currentIndex,
+ * but this is necessary when we want to know what was the previous tab,
+ * while being in tabChanged() slot.
+ */
+ int currentTab = -1;
+
+ private slots:
+ void configClicked();
+ void tabChanged(int idx);
+ void nullStateChanged(int state);
+ void invalidateValue();
+ void setModified();
+
+ signals:
+ void modified();
+};
+
+#endif // MULTIEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp
new file mode 100644
index 0000000..ed7c260
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp
@@ -0,0 +1,215 @@
+#include "multieditorbool.h"
+#include <QVBoxLayout>
+#include <QCheckBox>
+#include <QVariant>
+
+QStringList MultiEditorBool::validValues;
+
+MultiEditorBool::MultiEditorBool(QWidget* parent)
+ : MultiEditorWidget(parent)
+{
+ setLayout(new QVBoxLayout());
+ checkBox = new QCheckBox();
+ layout()->addWidget(checkBox);
+ connect(checkBox, &QCheckBox::stateChanged, this, &MultiEditorBool::stateChanged);
+}
+
+void MultiEditorBool::staticInit()
+{
+ validValues << "true" << "false"
+ << "yes" << "no"
+ << "on" << "off"
+ << "1" << "0";
+}
+
+void MultiEditorBool::setValue(const QVariant& value)
+{
+ switch (value.userType())
+ {
+ case QVariant::Bool:
+ case QVariant::Int:
+ case QVariant::LongLong:
+ case QVariant::UInt:
+ case QVariant::ULongLong:
+ boolValue = value.toBool();
+ upperCaseValue = false;
+ valueFormat = BOOL;
+ break;
+ default:
+ boolValue = valueFromString(value.toString());
+ break;
+ }
+
+ updateLabel();
+ checkBox->setChecked(boolValue);
+}
+
+bool MultiEditorBool::valueFromString(const QString& strValue)
+{
+ if (strValue.isEmpty())
+ {
+ upperCaseValue = false;
+ valueFormat = BOOL;
+ return false;
+ }
+
+ int idx = validValues.indexOf(strValue.toLower());
+ if (idx < 0)
+ {
+ upperCaseValue = false;
+ valueFormat = BOOL;
+ return true;
+ }
+
+ upperCaseValue = strValue[0].isUpper();
+ switch (idx)
+ {
+ case 0:
+ case 1:
+ valueFormat = TRUE_FALSE;
+ break;
+ case 2:
+ case 3:
+ valueFormat = YES_NO;
+ break;
+ case 4:
+ case 5:
+ valueFormat = ON_OFF;
+ break;
+ case 6:
+ case 7:
+ valueFormat = ONE_ZERO;
+ break;
+ }
+ return !(bool)(idx % 2);
+}
+
+QVariant MultiEditorBool::getValue()
+{
+ QString value;
+ switch (valueFormat)
+ {
+ case MultiEditorBool::TRUE_FALSE:
+ value = boolValue ? "true" : "false";
+ break;
+ case MultiEditorBool::ON_OFF:
+ value = boolValue ? "on" : "off";
+ break;
+ case MultiEditorBool::YES_NO:
+ value = boolValue ? "yes" : "no";
+ break;
+ case MultiEditorBool::ONE_ZERO:
+ case MultiEditorBool::BOOL:
+ value = boolValue ? "1" : "0";
+ break;
+ }
+
+ if (value.isNull())
+ value = boolValue ? "1" : "0";
+
+ if (upperCaseValue)
+ value = value.toUpper();
+
+ return value;
+}
+
+void MultiEditorBool::setReadOnly(bool value)
+{
+ readOnly = value;
+}
+
+QList<QWidget*> MultiEditorBool::getNoScrollWidgets()
+{
+ QList<QWidget*> list;
+ list << checkBox;
+ return list;
+}
+
+QString MultiEditorBool::getTabLabel()
+{
+ return tr("Boolean");
+}
+
+void MultiEditorBool::focusThisWidget()
+{
+ checkBox->setFocus();
+}
+
+void MultiEditorBool::updateLabel()
+{
+ checkBox->setText(getValue().toString());
+}
+
+void MultiEditorBool::stateChanged(int state)
+{
+ if (readOnly && ((bool)state) != boolValue)
+ {
+ checkBox->setChecked(boolValue);
+ return;
+ }
+
+ boolValue = checkBox->isChecked();
+ updateLabel();
+ emit valueModified();
+}
+
+MultiEditorWidget* MultiEditorBoolPlugin::getInstance()
+{
+ return new MultiEditorBool();
+}
+
+bool MultiEditorBoolPlugin::validFor(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BOOLEAN:
+ return true;
+ case DataType::BLOB:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ }
+ return false;
+}
+
+int MultiEditorBoolPlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BOOLEAN:
+ return 1;
+ case DataType::BLOB:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ }
+ return 100;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h
new file mode 100644
index 0000000..f328cf0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h
@@ -0,0 +1,68 @@
+#ifndef MULTIEDITORBOOL_H
+#define MULTIEDITORBOOL_H
+
+#include "guiSQLiteStudio_global.h"
+#include "multieditorwidget.h"
+#include "multieditorwidgetplugin.h"
+#include "plugins/builtinplugin.h"
+#include <QStringList>
+
+class QCheckBox;
+
+class GUI_API_EXPORT MultiEditorBool : public MultiEditorWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit MultiEditorBool(QWidget* parent = 0);
+
+ static void staticInit();
+
+ void setValue(const QVariant& boolValue);
+ QVariant getValue();
+ void setReadOnly(bool boolValue);
+ QList<QWidget*> getNoScrollWidgets();
+ QString getTabLabel();
+ void focusThisWidget();
+
+ private:
+ enum Format
+ {
+ TRUE_FALSE,
+ ON_OFF,
+ YES_NO,
+ ONE_ZERO,
+ BOOL
+ };
+
+ bool valueFromString(const QString& strValue);
+ void updateLabel();
+
+ static QStringList validValues;
+
+ QCheckBox* checkBox = nullptr;
+ Format valueFormat = ONE_ZERO;
+ bool upperCaseValue = false;
+ bool readOnly = false;
+ bool boolValue = false;
+
+ private slots:
+ void stateChanged(int state);
+};
+
+class GUI_API_EXPORT MultiEditorBoolPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Boolean data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Boolean")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORBOOL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp
new file mode 100644
index 0000000..44178f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp
@@ -0,0 +1,87 @@
+#include "multieditordate.h"
+#include <QDateEdit>
+
+QStringList MultiEditorDate::formats;
+
+MultiEditorDate::MultiEditorDate(QWidget* parent)
+ : MultiEditorDateTime(parent)
+{
+ setDisplayFormat(formats.first());
+}
+
+QString MultiEditorDate::getTabLabel()
+{
+ return tr("Date");
+}
+
+void MultiEditorDate::staticInit()
+{
+ formats << "yyyy-MM-dd";
+}
+
+QStringList MultiEditorDate::getParsingFormats()
+{
+ return MultiEditorDateTime::getParsingFormats();
+}
+
+
+MultiEditorWidget*MultiEditorDatePlugin::getInstance()
+{
+ return new MultiEditorDate();
+}
+
+bool MultiEditorDatePlugin::validFor(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ case DataType::DATE:
+ return true;
+ }
+ return false;
+}
+
+int MultiEditorDatePlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::TIME:
+ case DataType::DATETIME:
+ case DataType::unknown:
+ break;
+ case DataType::DATE:
+ return 1;
+ }
+ return 10;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h
new file mode 100644
index 0000000..b6f6d7c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h
@@ -0,0 +1,37 @@
+#ifndef MULTIEDITORDATE_H
+#define MULTIEDITORDATE_H
+
+#include "multieditordatetime.h"
+
+class GUI_API_EXPORT MultiEditorDate : public MultiEditorDateTime
+{
+ public:
+ explicit MultiEditorDate(QWidget *parent = 0);
+
+ QString getTabLabel();
+
+ static void staticInit();
+
+ protected:
+ QStringList getParsingFormats();
+
+ private:
+ static QStringList formats;
+};
+
+class GUI_API_EXPORT MultiEditorDatePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Date data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Date")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORDATE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp
new file mode 100644
index 0000000..bd1e244
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp
@@ -0,0 +1,275 @@
+#include "multieditordatetime.h"
+#include "common/utils.h"
+#include "common/unused.h"
+#include <QDateTimeEdit>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QVariant>
+#include <QString>
+#include <QDebug>
+#include <QCalendarWidget>
+#include <QWheelEvent>
+#include <QTableView>
+#include <QLabel>
+
+QStringList MultiEditorDateTime::formats;
+
+MultiEditorDateTime::MultiEditorDateTime(QWidget *parent) :
+ MultiEditorWidget(parent)
+{
+ QVBoxLayout* vbox = new QVBoxLayout();
+ setLayout(vbox);
+ dateTimeEdit = new QDateTimeEdit();
+ dateTimeLabel = new QLabel();
+ calendar = new QCalendarWidget();
+ // Extending width, becuase day labels are truncated on some systems.
+ calendar->setFixedSize(calendar->sizeHint() + QSize(80, 0));
+
+ vbox->addWidget(dateTimeEdit);
+ vbox->addWidget(dateTimeLabel);
+ vbox->addWidget(calendar);
+
+ setDisplayFormat(formats.first());
+
+ connect(calendar, &QCalendarWidget::selectionChanged, this, &MultiEditorDateTime::calendarDateChanged);
+ connect(dateTimeEdit, &QDateTimeEdit::dateChanged, this, &MultiEditorDateTime::dateChanged);
+ connect(dateTimeEdit, &QDateTimeEdit::timeChanged, this, &MultiEditorDateTime::timeChanged);
+
+ setFocusProxy(dateTimeEdit);
+ updateCalendarDisplay();
+}
+
+void MultiEditorDateTime::staticInit()
+{
+ formats << "yyyy-MM-dd hh:mm:ss"
+ << "yyyy-MM-dd hh:mm"
+ << "yyyy-MM-dd"
+ << "yyyy-MM-dd hh:mm:ss.z"
+ << "yyyy-MM-ddThh:mm"
+ << "yyyy-MM-ddThh:mm:ss"
+ << "yyyy-MM-ddThh:mm:ss.z";
+}
+
+void MultiEditorDateTime::setDisplayFormat(const QString& format)
+{
+ dateTimeEdit->setDisplayFormat(format);
+ dateTimeEdit->setMaximumWidth(dateTimeEdit->sizeHint().width());
+}
+
+void MultiEditorDateTime::setValue(const QVariant& value)
+{
+ switch (value.userType())
+ {
+ case QVariant::DateTime:
+ dateTimeEdit->setDateTime(value.toDateTime());
+ break;
+ case QVariant::Date:
+ dateTimeEdit->setDate(value.toDate());
+ break;
+ default:
+ {
+ dateTimeEdit->setDateTime(fromString(value.toString()));
+ break;
+ }
+ }
+ updateReadOnlyDisplay();
+}
+
+QVariant MultiEditorDateTime::getValue()
+{
+ if (formatType == STRING)
+ return dateTimeEdit->dateTime().toString(originalValueFormat);
+ else if (formatType == UNIXTIME)
+ return dateTimeEdit->dateTime().toTime_t();
+ else if (formatType == JULIAN_DAY)
+ return toJulian(dateTimeEdit->dateTime());
+ else
+ return dateTimeEdit->dateTime().toString(dateTimeEdit->displayFormat());
+}
+
+QList<QWidget*> MultiEditorDateTime::getNoScrollWidgets()
+{
+ QList<QWidget*> list;
+ list << dateTimeEdit << calendar;
+
+ QObject* obj = calendar->findChild<QTableView*>("qt_calendar_calendarview");
+ if (obj)
+ {
+ QTableView* view = dynamic_cast<QTableView*>(obj);
+ if (view)
+ list << view->viewport();
+ }
+
+ return list;
+}
+
+QDateTime MultiEditorDateTime::fromString(const QString& value)
+{
+ QDateTime dateTime;
+ foreach (const QString& format, getParsingFormats())
+ {
+ dateTime = QDateTime::fromString(value, format);
+ if (dateTime.isValid())
+ {
+ formatType = STRING;
+ originalValueFormat = format;
+ return dateTime;
+ }
+ }
+
+ // Try with unixtime
+ bool ok;
+ uint unixtime = value.toUInt(&ok);
+ if (ok)
+ {
+ dateTime = QDateTime::fromTime_t(unixtime);
+ formatType = UNIXTIME;
+ return dateTime;
+ }
+
+ // Try with Julian day
+ double jd = value.toDouble(&ok);
+ if (ok)
+ {
+ dateTime = toGregorian(jd);
+ formatType = JULIAN_DAY;
+ return dateTime;
+ }
+
+ formatType = OTHER;
+ return QDateTime();
+}
+
+void MultiEditorDateTime::calendarDateChanged()
+{
+ if (updatingCalendar)
+ return;
+
+ dateTimeEdit->setDate(calendar->selectedDate());
+ emit valueModified();
+}
+
+void MultiEditorDateTime::dateChanged(const QDate& date)
+{
+ updatingCalendar = true;
+ calendar->setSelectedDate(date);
+ updatingCalendar = false;
+ emit valueModified();
+}
+
+void MultiEditorDateTime::timeChanged(const QTime& time)
+{
+ UNUSED(time);
+ emit valueModified();
+}
+
+bool MultiEditorDateTime::getReadOnly() const
+{
+ return readOnly;
+}
+
+void MultiEditorDateTime::setReadOnly(bool value)
+{
+ readOnly = value;
+ dateTimeEdit->setVisible(!readOnly);
+ dateTimeLabel->setVisible(readOnly);
+ updateReadOnlyDisplay();
+}
+
+QString MultiEditorDateTime::getTabLabel()
+{
+ return tr("Date & time");
+}
+
+void MultiEditorDateTime::focusThisWidget()
+{
+ dateTimeEdit->setFocus();
+}
+
+QStringList MultiEditorDateTime::getParsingFormats()
+{
+ return formats;
+}
+
+void MultiEditorDateTime::updateReadOnlyDisplay()
+{
+ if (!readOnly)
+ return;
+
+ dateTimeLabel->setText(getValue().toString());
+ QDate date = dateTimeEdit->date();
+ calendar->setMinimumDate(date);
+ calendar->setMaximumDate(date);
+ calendar->setSelectedDate(date);
+}
+
+void MultiEditorDateTime::updateCalendarDisplay()
+{
+ if (!showCalendars)
+ {
+ calendar->setVisible(false);
+ return;
+ }
+}
+
+MultiEditorWidget*MultiEditorDateTimePlugin::getInstance()
+{
+ return new MultiEditorDateTime();
+}
+
+bool MultiEditorDateTimePlugin::validFor(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ case DataType::DATE:
+ case DataType::DATETIME:
+ return true;
+ }
+ return false;
+}
+
+int MultiEditorDateTimePlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ case DataType::DATE:
+ return 2;
+ case DataType::DATETIME:
+ return 1;
+ }
+ return 10;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h
new file mode 100644
index 0000000..59bd111
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h
@@ -0,0 +1,84 @@
+#ifndef MULTIEDITORDATETIME_H
+#define MULTIEDITORDATETIME_H
+
+#include "multieditorwidget.h"
+#include "multieditorwidgetplugin.h"
+#include "plugins/builtinplugin.h"
+#include <QStringList>
+#include <QDateTime>
+
+class QCalendarWidget;
+class QDateTimeEdit;
+class QLabel;
+
+class GUI_API_EXPORT MultiEditorDateTime : public MultiEditorWidget
+{
+ Q_OBJECT
+ public:
+ explicit MultiEditorDateTime(QWidget *parent = 0);
+
+ static void staticInit();
+
+ void setValue(const QVariant& value);
+ QVariant getValue();
+ bool needsValueUpdate();
+
+ QList<QWidget*> getNoScrollWidgets();
+
+ bool getReadOnly() const;
+ void setReadOnly(bool value);
+ QString getTabLabel();
+ void focusThisWidget();
+
+ protected:
+ void updateCalendarDisplay();
+ void setDisplayFormat(const QString& format);
+
+ virtual QStringList getParsingFormats();
+
+ QDateTimeEdit* dateTimeEdit = nullptr;
+ bool showCalendars = true;
+
+ private:
+ enum FormatType
+ {
+ STRING,
+ JULIAN_DAY,
+ UNIXTIME,
+ OTHER
+ };
+
+ void updateReadOnlyDisplay();
+ QDateTime fromString(const QString& value);
+
+ static QStringList formats;
+
+ QLabel* dateTimeLabel = nullptr;
+ QCalendarWidget* calendar = nullptr;
+ QString originalValueFormat;
+ FormatType formatType;
+ bool updatingCalendar = false;
+ bool readOnly = false;
+
+ private slots:
+ void calendarDateChanged();
+ void dateChanged(const QDate& date);
+ void timeChanged(const QTime& time);
+};
+
+class GUI_API_EXPORT MultiEditorDateTimePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Date and time data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Date and time")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORDATETIME_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp
new file mode 100644
index 0000000..5e3985c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp
@@ -0,0 +1,49 @@
+#include "multieditordialog.h"
+#include "multieditor.h"
+#include <QDialogButtonBox>
+#include <QVBoxLayout>
+
+MultiEditorDialog::MultiEditorDialog(QWidget *parent) :
+ QDialog(parent)
+{
+ multiEditor = new MultiEditor();
+
+ QVBoxLayout* vbox = new QVBoxLayout();
+ vbox->addWidget(multiEditor);
+ setLayout(vbox);
+
+ multiEditor->setReadOnly(false);
+
+ buttonBox = new QDialogButtonBox(Qt::Horizontal);
+ buttonBox->addButton(QDialogButtonBox::Ok);
+ buttonBox->addButton(QDialogButtonBox::Cancel);
+ vbox->addWidget(buttonBox);
+
+ connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+MultiEditorDialog::~MultiEditorDialog()
+{
+ delete multiEditor;
+}
+
+void MultiEditorDialog::setValue(const QVariant& value)
+{
+ multiEditor->setValue(value);
+}
+
+QVariant MultiEditorDialog::getValue()
+{
+ return multiEditor->getValue();
+}
+
+void MultiEditorDialog::setDataType(const DataType& dataType)
+{
+ multiEditor->setDataType(dataType);
+}
+
+void MultiEditorDialog::setReadOnly(bool readOnly)
+{
+ multiEditor->setReadOnly(readOnly);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h
new file mode 100644
index 0000000..ffbbd9c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h
@@ -0,0 +1,29 @@
+#ifndef MULTIEDITORDIALOG_H
+#define MULTIEDITORDIALOG_H
+
+#include "datagrid/sqlquerymodelcolumn.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class MultiEditor;
+class QDialogButtonBox;
+
+class GUI_API_EXPORT MultiEditorDialog : public QDialog
+{
+ Q_OBJECT
+ public:
+ explicit MultiEditorDialog(QWidget *parent = 0);
+ ~MultiEditorDialog();
+
+ void setValue(const QVariant& value);
+ QVariant getValue();
+
+ void setDataType(const DataType& dataType);
+ void setReadOnly(bool readOnly);
+
+ private:
+ MultiEditor* multiEditor = nullptr;
+ QDialogButtonBox* buttonBox = nullptr;
+};
+
+#endif // MULTIEDITORDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp
new file mode 100644
index 0000000..5a3cd28
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp
@@ -0,0 +1,94 @@
+#include "multieditorhex.h"
+#include "qhexedit2/qhexedit.h"
+#include "common/unused.h"
+#include <QVBoxLayout>
+
+MultiEditorHex::MultiEditorHex()
+{
+ setLayout(new QVBoxLayout());
+ hexEdit = new QHexEdit();
+ layout()->addWidget(hexEdit);
+
+ //hexEdit->setTabChangesFocus(true);
+
+ connect(hexEdit, SIGNAL(dataChanged()), this, SLOT(modificationChanged()));
+ setFocusProxy(hexEdit);
+}
+
+MultiEditorHex::~MultiEditorHex()
+{
+}
+
+void MultiEditorHex::setValue(const QVariant& value)
+{
+ hexEdit->setData(value.toByteArray());
+}
+
+QVariant MultiEditorHex::getValue()
+{
+ return hexEdit->data();
+}
+
+void MultiEditorHex::setReadOnly(bool value)
+{
+ hexEdit->setReadOnly(value);
+}
+
+QString MultiEditorHex::getTabLabel()
+{
+ return tr("Hex");
+}
+
+void MultiEditorHex::focusThisWidget()
+{
+ hexEdit->setFocus();
+}
+
+QList<QWidget*> MultiEditorHex::getNoScrollWidgets()
+{
+ return QList<QWidget*>();
+}
+
+void MultiEditorHex::modificationChanged()
+{
+ emit valueModified();
+}
+
+MultiEditorWidget*MultiEditorHexPlugin::getInstance()
+{
+ return new MultiEditorHex();
+}
+
+bool MultiEditorHexPlugin::validFor(const DataType& dataType)
+{
+ UNUSED(dataType);
+ return true;
+}
+
+int MultiEditorHexPlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ return 1;
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::BOOLEAN:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ }
+ return 100;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h
new file mode 100644
index 0000000..5fd32a0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h
@@ -0,0 +1,50 @@
+#ifndef MULTIEDITORHEX_H
+#define MULTIEDITORHEX_H
+
+#include "multieditorwidget.h"
+#include "multieditorwidgetplugin.h"
+#include "plugins/builtinplugin.h"
+#include <QVariant>
+#include <QSharedPointer>
+
+class QHexEdit;
+class QBuffer;
+
+class GUI_API_EXPORT MultiEditorHex : public MultiEditorWidget
+{
+ Q_OBJECT
+ public:
+ explicit MultiEditorHex();
+ ~MultiEditorHex();
+
+ void setValue(const QVariant& value);
+ QVariant getValue();
+ void setReadOnly(bool value);
+ QString getTabLabel();
+ void focusThisWidget();
+
+ QList<QWidget*> getNoScrollWidgets();
+
+ private:
+ QHexEdit* hexEdit = nullptr;
+
+ private slots:
+ void modificationChanged();
+};
+
+class GUI_API_EXPORT MultiEditorHexPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Hexadecimal data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Hexadecimal")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORHEX_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp
new file mode 100644
index 0000000..198f71b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp
@@ -0,0 +1,109 @@
+#include "multieditornumeric.h"
+#include "common/numericspinbox.h"
+#include <QVariant>
+#include <QVBoxLayout>
+
+MultiEditorNumeric::MultiEditorNumeric(QWidget* parent)
+ : MultiEditorWidget(parent)
+{
+ setLayout(new QVBoxLayout());
+ spinBox = new NumericSpinBox();
+ layout()->addWidget(spinBox);
+
+ connect(spinBox, SIGNAL(modified()), this, SIGNAL(valueModified()));
+
+ setFocusProxy(spinBox);
+}
+
+void MultiEditorNumeric::setValue(const QVariant& value)
+{
+ spinBox->setValue(value);
+}
+
+QVariant MultiEditorNumeric::getValue()
+{
+ return spinBox->getValue();
+}
+
+void MultiEditorNumeric::setReadOnly(bool value)
+{
+ spinBox->setReadOnly(value);
+}
+
+QString MultiEditorNumeric::getTabLabel()
+{
+ return tr("Number");
+}
+
+void MultiEditorNumeric::focusThisWidget()
+{
+ spinBox->setFocus();
+}
+
+QList<QWidget*> MultiEditorNumeric::getNoScrollWidgets()
+{
+ QList<QWidget*> list;
+ list << spinBox;
+ return list;
+}
+
+MultiEditorWidget*MultiEditorNumericPlugin::getInstance()
+{
+ return new MultiEditorNumeric();
+}
+
+bool MultiEditorNumericPlugin::validFor(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ return true;
+ case DataType::BOOLEAN:
+ case DataType::BLOB:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ }
+ return false;
+}
+
+int MultiEditorNumericPlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ return 1;
+ case DataType::BOOLEAN:
+ case DataType::BLOB:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ case DataType::unknown:
+ break;
+ }
+ return 10;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h
new file mode 100644
index 0000000..65d0409
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h
@@ -0,0 +1,42 @@
+#ifndef MULTIEDITORNUMERIC_H
+#define MULTIEDITORNUMERIC_H
+
+#include "multieditorwidget.h"
+#include "multieditorwidgetplugin.h"
+#include "plugins/builtinplugin.h"
+
+class NumericSpinBox;
+
+class GUI_API_EXPORT MultiEditorNumeric : public MultiEditorWidget
+{
+ public:
+ explicit MultiEditorNumeric(QWidget *parent = 0);
+
+ void setValue(const QVariant& value);
+ QVariant getValue();
+ void setReadOnly(bool value);
+ QString getTabLabel();
+ void focusThisWidget();
+
+ QList<QWidget*> getNoScrollWidgets();
+
+ private:
+ NumericSpinBox* spinBox = nullptr;
+};
+
+class GUI_API_EXPORT MultiEditorNumericPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Numeric data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Numeric types")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORNUMERIC_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp
new file mode 100644
index 0000000..05db8e0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp
@@ -0,0 +1,184 @@
+#include "multieditortext.h"
+#include "common/unused.h"
+#include <QPlainTextEdit>
+#include <QVariant>
+#include <QVBoxLayout>
+#include <QAction>
+#include <QMenu>
+
+CFG_KEYS_DEFINE(MultiEditorText)
+
+MultiEditorText::MultiEditorText(QWidget *parent) :
+ MultiEditorWidget(parent)
+{
+ setLayout(new QVBoxLayout());
+ textEdit = new QPlainTextEdit();
+ layout()->addWidget(textEdit);
+ initActions();
+ setupMenu();
+
+ setFocusProxy(textEdit);
+ textEdit->setContextMenuPolicy(Qt::CustomContextMenu);
+ textEdit->setTabChangesFocus(true);
+
+ connect(textEdit, &QPlainTextEdit::modificationChanged, this, &MultiEditorText::modificationChanged);
+ connect(textEdit, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showCustomMenu(QPoint)));
+}
+
+void MultiEditorText::setValue(const QVariant& value)
+{
+ textEdit->setPlainText(value.toString());
+}
+
+QVariant MultiEditorText::getValue()
+{
+ return textEdit->toPlainText();
+}
+
+void MultiEditorText::setReadOnly(bool value)
+{
+ textEdit->setReadOnly(value);
+}
+
+QString MultiEditorText::getTabLabel()
+{
+ return tr("Text");
+}
+
+QToolBar* MultiEditorText::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void MultiEditorText::focusThisWidget()
+{
+ textEdit->setFocus();
+}
+
+QList<QWidget*> MultiEditorText::getNoScrollWidgets()
+{
+ // We don't return text, we want it to be scrolled.
+ QList<QWidget*> list;
+ return list;
+}
+
+void MultiEditorText::modificationChanged(bool changed)
+{
+ if (changed)
+ emit valueModified();
+}
+
+void MultiEditorText::deleteSelected()
+{
+ textEdit->textCursor().removeSelectedText();
+}
+
+void MultiEditorText::showCustomMenu(const QPoint& point)
+{
+ contextMenu->popup(textEdit->mapToGlobal(point));
+}
+
+void MultiEditorText::updateUndoAction(bool enabled)
+{
+ actionMap[UNDO]->setEnabled(enabled);
+}
+
+void MultiEditorText::updateRedoAction(bool enabled)
+{
+ actionMap[REDO]->setEnabled(enabled);
+}
+
+void MultiEditorText::updateCopyAction(bool enabled)
+{
+ actionMap[CUT]->setEnabled(enabled);
+ actionMap[COPY]->setEnabled(enabled);
+ actionMap[DELETE]->setEnabled(enabled);
+}
+
+void MultiEditorText::toggleTabFocus()
+{
+ textEdit->setTabChangesFocus(actionMap[TAB_CHANGES_FOCUS]->isChecked());
+}
+
+void MultiEditorText::createActions()
+{
+ createAction(TAB_CHANGES_FOCUS, tr("Tab changes focus"), this, SLOT(toggleTabFocus()), this);
+ createAction(CUT, ICONS.ACT_CUT, tr("Cut"), textEdit, SLOT(cut()), this);
+ createAction(COPY, ICONS.ACT_COPY, tr("Copy"), textEdit, SLOT(copy()), this);
+ createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), textEdit, SLOT(paste()), this);
+ createAction(DELETE, ICONS.ACT_DELETE, tr("Delete"), this, SLOT(deleteSelected()), this);
+ createAction(UNDO, ICONS.ACT_UNDO, tr("Undo"), textEdit, SLOT(undo()), this);
+ createAction(REDO, ICONS.ACT_REDO, tr("Redo"), textEdit, SLOT(redo()), this);
+
+ actionMap[CUT]->setEnabled(false);
+ actionMap[COPY]->setEnabled(false);
+ actionMap[DELETE]->setEnabled(false);
+ actionMap[UNDO]->setEnabled(false);
+ actionMap[REDO]->setEnabled(false);
+
+ actionMap[TAB_CHANGES_FOCUS]->setCheckable(true);
+ actionMap[TAB_CHANGES_FOCUS]->setChecked(true);
+
+ connect(textEdit, &QPlainTextEdit::undoAvailable, this, &MultiEditorText::updateUndoAction);
+ connect(textEdit, &QPlainTextEdit::redoAvailable, this, &MultiEditorText::updateRedoAction);
+ connect(textEdit, &QPlainTextEdit::copyAvailable, this, &MultiEditorText::updateCopyAction);
+}
+
+void MultiEditorText::setupDefShortcuts()
+{
+ BIND_SHORTCUTS(MultiEditorText, Action);
+}
+
+void MultiEditorText::setupMenu()
+{
+ contextMenu = new QMenu(this);
+ contextMenu->addAction(actionMap[TAB_CHANGES_FOCUS]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[UNDO]);
+ contextMenu->addAction(actionMap[REDO]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[CUT]);
+ contextMenu->addAction(actionMap[COPY]);
+ contextMenu->addAction(actionMap[PASTE]);
+ contextMenu->addAction(actionMap[DELETE]);
+}
+
+MultiEditorWidget* MultiEditorTextPlugin::getInstance()
+{
+ return new MultiEditorText();
+}
+
+bool MultiEditorTextPlugin::validFor(const DataType& dataType)
+{
+ UNUSED(dataType);
+ return true;
+}
+
+int MultiEditorTextPlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::TIME:
+ return 10;
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::unknown:
+ break;
+ }
+ return 1;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h
new file mode 100644
index 0000000..bd814ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h
@@ -0,0 +1,87 @@
+#ifndef MULTIEDITORTEXT_H
+#define MULTIEDITORTEXT_H
+
+#include "multieditorwidget.h"
+#include "multieditorwidgetplugin.h"
+#include "common/extactioncontainer.h"
+#include "plugins/builtinplugin.h"
+
+class QPlainTextEdit;
+class QMenu;
+
+CFG_KEY_LIST(MultiEditorText, QObject::tr("Cell text value editor"),
+ CFG_KEY_ENTRY(CUT, QKeySequence::Cut, QObject::tr("Cut selected text"))
+ CFG_KEY_ENTRY(COPY, QKeySequence::Copy, QObject::tr("Copy selected text"))
+ CFG_KEY_ENTRY(PASTE, QKeySequence::Paste, QObject::tr("Paste from clipboard"))
+ CFG_KEY_ENTRY(DELETE, QKeySequence::Delete, QObject::tr("Delete selected text"))
+ CFG_KEY_ENTRY(UNDO, QKeySequence::Undo, QObject::tr("Undo"))
+ CFG_KEY_ENTRY(REDO, QKeySequence::Redo, QObject::tr("Redo"))
+)
+
+class GUI_API_EXPORT MultiEditorText : public MultiEditorWidget, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ TAB_CHANGES_FOCUS,
+ CUT,
+ COPY,
+ PASTE,
+ DELETE,
+ UNDO,
+ REDO
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit MultiEditorText(QWidget *parent = 0);
+
+ void setValue(const QVariant& value);
+ QVariant getValue();
+ void setReadOnly(bool value);
+ QString getTabLabel();
+ QToolBar* getToolBar(int toolbar) const;
+ void focusThisWidget();
+ QList<QWidget*> getNoScrollWidgets();
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void setupMenu();
+
+ QPlainTextEdit* textEdit = nullptr;
+ QMenu* contextMenu = nullptr;
+
+ private slots:
+ void modificationChanged(bool changed);
+ void deleteSelected();
+ void showCustomMenu(const QPoint& point);
+ void updateUndoAction(bool enabled);
+ void updateRedoAction(bool enabled);
+ void updateCopyAction(bool enabled);
+ void toggleTabFocus();
+};
+
+class GUI_API_EXPORT MultiEditorTextPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Standard text data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Text")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORTEXT_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp
new file mode 100644
index 0000000..8b49715
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp
@@ -0,0 +1,90 @@
+#include "multieditortime.h"
+#include <QTimeEdit>
+
+QStringList MultiEditorTime::formats;
+
+MultiEditorTime::MultiEditorTime(QWidget *parent)
+ : MultiEditorDateTime(parent)
+{
+ showCalendars = false;
+ updateCalendarDisplay();
+ setDisplayFormat(formats.first());
+}
+
+QString MultiEditorTime::getTabLabel()
+{
+ return tr("Time");
+}
+
+void MultiEditorTime::staticInit()
+{
+ formats << "hh:mm:ss"
+ << "hh:mm:ss.zzz"
+ << "hh:mm";
+}
+
+QStringList MultiEditorTime::getParsingFormats()
+{
+ return formats;
+}
+
+MultiEditorWidget*MultiEditorTimePlugin::getInstance()
+{
+ return new MultiEditorTime();
+}
+
+bool MultiEditorTimePlugin::validFor(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::unknown:
+ break;
+ case DataType::TIME:
+ return true;
+ }
+ return false;
+}
+
+int MultiEditorTimePlugin::getPriority(const DataType& dataType)
+{
+ switch (dataType.getType())
+ {
+ case DataType::BLOB:
+ case DataType::BOOLEAN:
+ case DataType::BIGINT:
+ case DataType::DECIMAL:
+ case DataType::DOUBLE:
+ case DataType::INTEGER:
+ case DataType::INT:
+ case DataType::NUMERIC:
+ case DataType::REAL:
+ case DataType::NONE:
+ case DataType::STRING:
+ case DataType::TEXT:
+ case DataType::CHAR:
+ case DataType::VARCHAR:
+ case DataType::DATE:
+ case DataType::DATETIME:
+ case DataType::unknown:
+ break;
+ case DataType::TIME:
+ return 1;
+ }
+ return 10;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h
new file mode 100644
index 0000000..56bf60e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h
@@ -0,0 +1,38 @@
+#ifndef MULTIEDITORTIME_H
+#define MULTIEDITORTIME_H
+
+#include "multieditordatetime.h"
+#include "guiSQLiteStudio_global.h"
+
+class GUI_API_EXPORT MultiEditorTime : public MultiEditorDateTime
+{
+ public:
+ explicit MultiEditorTime(QWidget *parent = 0);
+
+ QString getTabLabel();
+
+ static void staticInit();
+
+ protected:
+ QStringList getParsingFormats();
+
+ private:
+ static QStringList formats;
+};
+
+class GUI_API_EXPORT MultiEditorTimePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+ SQLITESTUDIO_PLUGIN_DESC("Time data editor.")
+ SQLITESTUDIO_PLUGIN_TITLE("Time")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+
+ public:
+ MultiEditorWidget* getInstance();
+ bool validFor(const DataType& dataType);
+ int getPriority(const DataType& dataType);
+};
+
+#endif // MULTIEDITORTIME_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp
new file mode 100644
index 0000000..caea9a5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp
@@ -0,0 +1,23 @@
+#include "multieditorwidget.h"
+
+MultiEditorWidget::MultiEditorWidget(QWidget *parent) :
+ QWidget(parent)
+{
+}
+
+void MultiEditorWidget::installEventFilter(QObject* filterObj)
+{
+ QObject::installEventFilter(filterObj);
+ foreach (QWidget* w, getNoScrollWidgets())
+ w->installEventFilter(filterObj);
+}
+
+bool MultiEditorWidget::isUpToDate() const
+{
+ return upToDate;
+}
+
+void MultiEditorWidget::setUpToDate(bool value)
+{
+ upToDate = value;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h
new file mode 100644
index 0000000..14bac26
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h
@@ -0,0 +1,33 @@
+#ifndef MULTIEDITORWIDGET_H
+#define MULTIEDITORWIDGET_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+class GUI_API_EXPORT MultiEditorWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit MultiEditorWidget(QWidget *parent = 0);
+
+ virtual void setValue(const QVariant& value) = 0;
+ virtual QVariant getValue() = 0;
+ virtual void setReadOnly(bool value) = 0;
+ virtual QList<QWidget*> getNoScrollWidgets() = 0;
+ virtual QString getTabLabel() = 0;
+ virtual void focusThisWidget() = 0;
+
+ void installEventFilter(QObject* filterObj);
+
+ bool isUpToDate() const;
+ void setUpToDate(bool value);
+
+ private:
+ bool upToDate = true;
+
+ signals:
+ void valueModified();
+};
+
+#endif // MULTIEDITORWIDGET_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h
new file mode 100644
index 0000000..011bde5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h
@@ -0,0 +1,17 @@
+#ifndef MULTIEDITORWIDGETPLUGIN_H
+#define MULTIEDITORWIDGETPLUGIN_H
+
+#include "plugins/plugin.h"
+#include "datagrid/sqlquerymodelcolumn.h"
+
+class MultiEditorWidget;
+
+class GUI_API_EXPORT MultiEditorWidgetPlugin : public virtual Plugin
+{
+ public:
+ virtual MultiEditorWidget* getInstance() = 0;
+ virtual bool validFor(const DataType& dataType) = 0;
+ virtual int getPriority(const DataType& dataType) = 0;
+};
+
+#endif // MULTIEDITORWIDGETPLUGIN_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp
new file mode 100644
index 0000000..303091d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp
@@ -0,0 +1,115 @@
+#include "commands.h"
+
+CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent)
+ : QUndoCommand(parent)
+{
+ _xData = xData;
+ _charPos = charPos;
+ _newChar = newChar;
+ _cmd = cmd;
+}
+
+bool CharCommand::mergeWith(const QUndoCommand *command)
+{
+ const CharCommand *nextCommand = static_cast<const CharCommand *>(command);
+ bool result = false;
+
+ if (_cmd != remove)
+ {
+ if (nextCommand->_cmd == replace)
+ if (nextCommand->_charPos == _charPos)
+ {
+ _newChar = nextCommand->_newChar;
+ result = true;
+ }
+ }
+ return result;
+}
+
+void CharCommand::undo()
+{
+ switch (_cmd)
+ {
+ case insert:
+ _xData->remove(_charPos, 1);
+ break;
+ case replace:
+ _xData->replace(_charPos, _oldChar);
+ _xData->setDataChanged(_charPos, _wasChanged);
+ break;
+ case remove:
+ _xData->insert(_charPos, _oldChar);
+ _xData->setDataChanged(_charPos, _wasChanged);
+ break;
+ }
+}
+
+void CharCommand::redo()
+{
+ switch (_cmd)
+ {
+ case insert:
+ _xData->insert(_charPos, _newChar);
+ break;
+ case replace:
+ _oldChar = _xData->data()[_charPos];
+ _wasChanged = _xData->dataChanged(_charPos);
+ _xData->replace(_charPos, _newChar);
+ break;
+ case remove:
+ _oldChar = _xData->data()[_charPos];
+ _wasChanged = _xData->dataChanged(_charPos);
+ _xData->remove(_charPos, 1);
+ break;
+ }
+}
+
+
+
+ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent)
+ : QUndoCommand(parent)
+{
+ _cmd = cmd;
+ _xData = xData;
+ _baPos = baPos;
+ _newBa = newBa;
+ _len = len;
+}
+
+void ArrayCommand::undo()
+{
+ switch (_cmd)
+ {
+ case insert:
+ _xData->remove(_baPos, _newBa.length());
+ break;
+ case replace:
+ _xData->replace(_baPos, _oldBa);
+ _xData->setDataChanged(_baPos, _wasChanged);
+ break;
+ case remove:
+ _xData->insert(_baPos, _oldBa);
+ _xData->setDataChanged(_baPos, _wasChanged);
+ break;
+ }
+}
+
+void ArrayCommand::redo()
+{
+ switch (_cmd)
+ {
+ case insert:
+ _xData->insert(_baPos, _newBa);
+ break;
+ case replace:
+ _oldBa = _xData->data().mid(_baPos, _len);
+ _wasChanged = _xData->dataChanged(_baPos, _len);
+ _xData->replace(_baPos, _newBa);
+ break;
+ case remove:
+ _oldBa = _xData->data().mid(_baPos, _len);
+ _wasChanged = _xData->dataChanged(_baPos, _len);
+ _xData->remove(_baPos, _len);
+ break;
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h
new file mode 100644
index 0000000..b7e4921
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h
@@ -0,0 +1,70 @@
+#ifndef COMMANDS_H
+#define COMMANDS_H
+
+/** \cond docNever */
+
+#include "guiSQLiteStudio_global.h"
+#include "xbytearray.h"
+#include <QUndoCommand>
+
+/*! CharCommand is a class to prived undo/redo functionality in QHexEdit.
+A QUndoCommand represents a single editing action on a document. CharCommand
+is responsable for manipulations on single chars. It can insert. replace and
+remove characters. A manipulation stores allways to actions
+1. redo (or do) action
+2. undo action.
+
+CharCommand also supports command compression via mergeWidht(). This allows
+the user to execute a undo command contation e.g. 3 steps in a single command.
+If you for example insert a new byt "34" this means for the editor doing 3
+steps: insert a "00", replace it with "03" and the replace it with "34". These
+3 steps are combined into a single step, insert a "34".
+*/
+class GUI_API_EXPORT CharCommand : public QUndoCommand
+{
+public:
+ enum { Id = 1234 };
+ enum Cmd {insert, remove, replace};
+
+ CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar,
+ QUndoCommand *parent=0);
+
+ void undo();
+ void redo();
+ bool mergeWith(const QUndoCommand *command);
+ int id() const { return Id; }
+
+private:
+ XByteArray * _xData;
+ int _charPos;
+ bool _wasChanged;
+ char _newChar;
+ char _oldChar;
+ Cmd _cmd;
+};
+
+/*! ArrayCommand provides undo/redo functionality for handling binary strings. It
+can undo/redo insert, replace and remove binary strins (QByteArrays).
+*/
+class GUI_API_EXPORT ArrayCommand : public QUndoCommand
+{
+public:
+ enum Cmd {insert, remove, replace};
+ ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0,
+ QUndoCommand *parent=0);
+ void undo();
+ void redo();
+
+private:
+ Cmd _cmd;
+ XByteArray * _xData;
+ int _baPos;
+ int _len;
+ QByteArray _wasChanged;
+ QByteArray _newBa;
+ QByteArray _oldBa;
+};
+
+/** \endcond docNever */
+
+#endif // COMMANDS_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp
new file mode 100644
index 0000000..b12624e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp
@@ -0,0 +1,180 @@
+#include <QtGui>
+
+#include "qhexedit.h"
+
+
+QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent)
+{
+ qHexEdit_p = new QHexEditPrivate(this);
+ setWidget(qHexEdit_p);
+ setWidgetResizable(true);
+
+ connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int)));
+ connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int)));
+ connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged()));
+ connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool)));
+ setFocusPolicy(Qt::NoFocus);
+}
+
+int QHexEdit::indexOf(const QByteArray & ba, int from) const
+{
+ return qHexEdit_p->indexOf(ba, from);
+}
+
+void QHexEdit::insert(int i, const QByteArray & ba)
+{
+ qHexEdit_p->insert(i, ba);
+}
+
+void QHexEdit::insert(int i, char ch)
+{
+ qHexEdit_p->insert(i, ch);
+}
+
+int QHexEdit::lastIndexOf(const QByteArray & ba, int from) const
+{
+ return qHexEdit_p->lastIndexOf(ba, from);
+}
+
+void QHexEdit::remove(int pos, int len)
+{
+ qHexEdit_p->remove(pos, len);
+}
+
+void QHexEdit::replace( int pos, int len, const QByteArray & after)
+{
+ qHexEdit_p->replace(pos, len, after);
+}
+
+QString QHexEdit::toReadableString()
+{
+ return qHexEdit_p->toRedableString();
+}
+
+QString QHexEdit::selectionToReadableString()
+{
+ return qHexEdit_p->selectionToReadableString();
+}
+
+void QHexEdit::setAddressArea(bool addressArea)
+{
+ qHexEdit_p->setAddressArea(addressArea);
+}
+
+void QHexEdit::redo()
+{
+ qHexEdit_p->redo();
+}
+
+void QHexEdit::undo()
+{
+ qHexEdit_p->undo();
+}
+
+void QHexEdit::setAddressWidth(int addressWidth)
+{
+ qHexEdit_p->setAddressWidth(addressWidth);
+}
+
+void QHexEdit::setAsciiArea(bool asciiArea)
+{
+ qHexEdit_p->setAsciiArea(asciiArea);
+}
+
+void QHexEdit::setHighlighting(bool mode)
+{
+ qHexEdit_p->setHighlighting(mode);
+}
+
+void QHexEdit::setAddressOffset(int offset)
+{
+ qHexEdit_p->setAddressOffset(offset);
+}
+
+int QHexEdit::addressOffset()
+{
+ return qHexEdit_p->addressOffset();
+}
+
+void QHexEdit::setCursorPosition(int cursorPos)
+{
+ // cursorPos in QHexEditPrivate is the position of the textcoursor without
+ // blanks, means bytePos*2
+ qHexEdit_p->setCursorPos(cursorPos*2);
+}
+
+int QHexEdit::cursorPosition()
+{
+ return qHexEdit_p->cursorPos() / 2;
+}
+
+
+void QHexEdit::setData(const QByteArray &data)
+{
+ qHexEdit_p->setData(data);
+}
+
+QByteArray QHexEdit::data()
+{
+ return qHexEdit_p->data();
+}
+
+void QHexEdit::setAddressAreaColor(const QColor &color)
+{
+ qHexEdit_p->setAddressAreaColor(color);
+}
+
+QColor QHexEdit::addressAreaColor()
+{
+ return qHexEdit_p->addressAreaColor();
+}
+
+void QHexEdit::setHighlightingColor(const QColor &color)
+{
+ qHexEdit_p->setHighlightingColor(color);
+}
+
+QColor QHexEdit::highlightingColor()
+{
+ return qHexEdit_p->highlightingColor();
+}
+
+void QHexEdit::setSelectionColor(const QColor &color)
+{
+ qHexEdit_p->setSelectionColor(color);
+}
+
+QColor QHexEdit::selectionColor()
+{
+ return qHexEdit_p->selectionColor();
+}
+
+void QHexEdit::setOverwriteMode(bool overwriteMode)
+{
+ qHexEdit_p->setOverwriteMode(overwriteMode);
+}
+
+bool QHexEdit::overwriteMode()
+{
+ return qHexEdit_p->overwriteMode();
+}
+
+void QHexEdit::setReadOnly(bool readOnly)
+{
+ qHexEdit_p->setReadOnly(readOnly);
+}
+
+bool QHexEdit::isReadOnly()
+{
+ return qHexEdit_p->isReadOnly();
+}
+
+void QHexEdit::setFont(const QFont &font)
+{
+ qHexEdit_p->setFont(font);
+}
+
+const QFont & QHexEdit::font() const
+{
+ return qHexEdit_p->font();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h
new file mode 100644
index 0000000..b2d707c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h
@@ -0,0 +1,230 @@
+#ifndef QHEXEDIT_H
+#define QHEXEDIT_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QtGui>
+#include <QHBoxLayout>
+#include "qhexedit_p.h"
+
+/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework.
+It is a simple editor for binary data, just like QPlainTextEdit is for text
+data. There are sip configuration files included, so it is easy to create
+bindings for PyQt and you can use this widget also in python.
+
+QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use
+the mouse or the keyboard to navigate inside the widget. If you hit the keys
+(0..9, a..f) you will change the data. Changed data is highlighted and can be
+accessed via data().
+
+Normaly QHexEdit works in the overwrite Mode. You can set overwriteMode(false)
+and insert data. In this case the size of data() increases. It is also possible
+to delete bytes (del or backspace), here the size of data decreases.
+
+You can select data with keyboard hits or mouse movements. The copy-key will
+copy the selected data into the clipboard. The cut-key copies also but delets
+it afterwards. In overwrite mode, the paste function overwrites the content of
+the (does not change the length) data. In insert mode, clipboard data will be
+inserted. The clipboard content is expected in ASCII Hex notation. Unknown
+characters will be ignored.
+
+QHexEdit comes with undo/redo functionality. All changes can be undone, by
+pressing the undo-key (usually ctr-z). They can also be redone afterwards.
+The undo/redo framework is cleared, when setData() sets up a new
+content for the editor. You can search data inside the content with indexOf()
+and lastIndexOf(). The replace() function is to change located subdata. This
+'replaced' data can also be undone by the undo/redo framework.
+
+This widget can only handle small amounts of data. The size has to be below 10
+megabytes, otherwise the scroll sliders ard not shown and you can't scroll any
+more.
+*/
+class GUI_API_EXPORT QHexEdit : public QScrollArea
+{
+ Q_OBJECT
+ /*! Property data holds the content of QHexEdit. Call setData() to set the
+ content of QHexEdit, data() returns the actual content.
+ */
+ Q_PROPERTY(QByteArray data READ data WRITE setData)
+
+ /*! Property addressOffset is added to the Numbers of the Address Area.
+ A offset in the address area (left side) is sometimes usefull, whe you show
+ only a segment of a complete memory picture. With setAddressOffset() you set
+ this property - with addressOffset() you get the actual value.
+ */
+ Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset)
+
+ /*! Property address area color sets (setAddressAreaColor()) the backgorund
+ color of address areas. You can also read the color (addressaAreaColor()).
+ */
+ Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor)
+
+ /*! Porperty cursorPosition sets or gets the position of the editor cursor
+ in QHexEdit.
+ */
+ Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition)
+
+ /*! Property highlighting color sets (setHighlightingColor()) the backgorund
+ color of highlighted text areas. You can also read the color
+ (highlightingColor()).
+ */
+ Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor)
+
+ /*! Property selection color sets (setSelectionColor()) the backgorund
+ color of selected text areas. You can also read the color
+ (selectionColor()).
+ */
+ Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor)
+
+ /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode
+ in which the editor works. In overwrite mode the user will overwrite existing data. The
+ size of data will be constant. In insert mode the size will grow, when inserting
+ new data.
+ */
+ Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode)
+
+ /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode
+ in which the editor works. In readonly mode the the user can only navigate
+ through the data and select data; modifying is not possible. This
+ property's default is false.
+ */
+ Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly)
+
+ /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/
+ Q_PROPERTY(QFont font READ font WRITE setFont)
+
+
+public:
+ /*! Creates an instance of QHexEdit.
+ \param parent Parent widget of QHexEdit.
+ */
+ QHexEdit(QWidget *parent = 0);
+
+ /*! Returns the index position of the first occurrence
+ of the byte array ba in this byte array, searching forward from index position
+ from. Returns -1 if ba could not be found. In addition to this functionality
+ of QByteArray the cursorposition is set to the end of found bytearray and
+ it will be selected.
+
+ */
+ int indexOf(const QByteArray & ba, int from = 0) const;
+
+ /*! Inserts a byte array.
+ \param i Index position, where to insert
+ \param ba byte array, which is to insert
+ In overwrite mode, the existing data will be overwritten, in insertmode ba will be
+ inserted and size of data grows.
+ */
+ void insert(int i, const QByteArray & ba);
+
+ /*! Inserts a char.
+ \param i Index position, where to insert
+ \param ch Char, which is to insert
+ In overwrite mode, the existing data will be overwritten, in insertmode ba will be
+ inserted and size of data grows.
+ */
+ void insert(int i, char ch);
+
+ /*! Returns the index position of the last occurrence
+ of the byte array ba in this byte array, searching backwards from index position
+ from. Returns -1 if ba could not be found. In addition to this functionality
+ of QByteArray the cursorposition is set to the beginning of found bytearray and
+ it will be selected.
+
+ */
+ int lastIndexOf(const QByteArray & ba, int from = 0) const;
+
+ /*! Removes len bytes from the content.
+ \param pos Index position, where to remove
+ \param len Amount of bytes to remove
+ In overwrite mode, the existing bytes will be overwriten with 0x00.
+ */
+ void remove(int pos, int len=1);
+
+ /*! Replaces len bytes from index position pos with the byte array after.
+ */
+ void replace( int pos, int len, const QByteArray & after);
+
+ /*! Gives back a formatted image of the content of QHexEdit
+ */
+ QString toReadableString();
+
+ /*! Gives back a formatted image of the selected content of QHexEdit
+ */
+ QString selectionToReadableString();
+
+ /*! \cond docNever */
+ void setAddressOffset(int offset);
+ int addressOffset();
+ void setCursorPosition(int cusorPos);
+ int cursorPosition();
+ void setData(QByteArray const &data);
+ QByteArray data();
+ void setAddressAreaColor(QColor const &color);
+ QColor addressAreaColor();
+ void setHighlightingColor(QColor const &color);
+ QColor highlightingColor();
+ void setSelectionColor(QColor const &color);
+ QColor selectionColor();
+ void setOverwriteMode(bool);
+ bool overwriteMode();
+ void setReadOnly(bool);
+ bool isReadOnly();
+ const QFont &font() const;
+ void setFont(const QFont &);
+ /*! \endcond docNever */
+
+public slots:
+ /*! Redoes the last operation. If there is no operation to redo, i.e.
+ there is no redo step in the undo/redo history, nothing happens.
+ */
+ void redo();
+
+ /*! Set the minimum width of the address area.
+ \param addressWidth Width in characters.
+ */
+ void setAddressWidth(int addressWidth);
+
+ /*! Switch the address area on or off.
+ \param addressArea true (show it), false (hide it).
+ */
+ void setAddressArea(bool addressArea);
+
+ /*! Switch the ascii area on or off.
+ \param asciiArea true (show it), false (hide it).
+ */
+ void setAsciiArea(bool asciiArea);
+
+ /*! Switch the highlighting feature on or of.
+ \param mode true (show it), false (hide it).
+ */
+ void setHighlighting(bool mode);
+
+ /*! Undoes the last operation. If there is no operation to undo, i.e.
+ there is no undo step in the undo/redo history, nothing happens.
+ */
+ void undo();
+
+signals:
+
+ /*! Contains the address, where the cursor is located. */
+ void currentAddressChanged(int address);
+
+ /*! Contains the size of the data to edit. */
+ void currentSizeChanged(int size);
+
+ /*! The signal is emited every time, the data is changed. */
+ void dataChanged();
+
+ /*! The signal is emited every time, the overwrite mode is changed. */
+ void overwriteModeChanged(bool state);
+
+private:
+ /*! \cond docNever */
+ QHexEditPrivate *qHexEdit_p;
+ QHBoxLayout *layout;
+ QScrollArea *scrollArea;
+ /*! \endcond docNever */
+};
+
+#endif
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp
new file mode 100644
index 0000000..3919c19
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp
@@ -0,0 +1,883 @@
+#include <QtGui>
+#include <QApplication>
+
+#include "qhexedit_p.h"
+#include "commands.h"
+
+const int HEXCHARS_IN_LINE = 47;
+const int GAP_ADR_HEX = 10;
+const int GAP_HEX_ASCII = 16;
+const int BYTES_PER_LINE = 16;
+
+QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent)
+{
+ _undoStack = new QUndoStack(this);
+
+ _scrollArea = parent;
+ setAddressWidth(4);
+ setAddressOffset(0);
+ setAddressArea(true);
+ setAsciiArea(true);
+ setHighlighting(true);
+ setOverwriteMode(true);
+ setReadOnly(false);
+ setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff));
+ setHighlightingColor(QColor(0xee, 0xee, 0x88, 0xff));
+ setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff));
+ setFont(QFont("Courier", 10));
+
+ _size = 0;
+ resetSelection(0);
+
+ setFocusPolicy(Qt::StrongFocus);
+
+ connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor()));
+ _cursorTimer.setInterval(500);
+ _cursorTimer.start();
+}
+
+void QHexEditPrivate::setAddressOffset(int offset)
+{
+ _xData.setAddressOffset(offset);
+ adjust();
+}
+
+int QHexEditPrivate::addressOffset()
+{
+ return _xData.addressOffset();
+}
+
+void QHexEditPrivate::setData(const QByteArray &data)
+{
+ _xData.setData(data);
+ _undoStack->clear();
+ adjust();
+ setCursorPos(0);
+}
+
+QByteArray QHexEditPrivate::data()
+{
+ return _xData.data();
+}
+
+void QHexEditPrivate::setAddressAreaColor(const QColor &color)
+{
+ _addressAreaColor = color;
+ update();
+}
+
+QColor QHexEditPrivate::addressAreaColor()
+{
+ return _addressAreaColor;
+}
+
+void QHexEditPrivate::setHighlightingColor(const QColor &color)
+{
+ _highlightingColor = color;
+ update();
+}
+
+QColor QHexEditPrivate::highlightingColor()
+{
+ return _highlightingColor;
+}
+
+void QHexEditPrivate::setSelectionColor(const QColor &color)
+{
+ _selectionColor = color;
+ update();
+}
+
+QColor QHexEditPrivate::selectionColor()
+{
+ return _selectionColor;
+}
+
+void QHexEditPrivate::setReadOnly(bool readOnly)
+{
+ _readOnly = readOnly;
+}
+
+bool QHexEditPrivate::isReadOnly()
+{
+ return _readOnly;
+}
+
+XByteArray & QHexEditPrivate::xData()
+{
+ return _xData;
+}
+
+int QHexEditPrivate::indexOf(const QByteArray & ba, int from)
+{
+ if (from > (_xData.data().length() - 1))
+ from = _xData.data().length() - 1;
+ int idx = _xData.data().indexOf(ba, from);
+ if (idx > -1)
+ {
+ int curPos = idx*2;
+ setCursorPos(curPos + ba.length()*2);
+ resetSelection(curPos);
+ setSelection(curPos + ba.length()*2);
+ ensureVisible();
+ }
+ return idx;
+}
+
+void QHexEditPrivate::insert(int index, const QByteArray & ba)
+{
+ if (ba.length() > 0)
+ {
+ if (_overwriteMode)
+ {
+ QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
+ _undoStack->push(arrayCommand);
+ emit dataChanged();
+ }
+ else
+ {
+ QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length());
+ _undoStack->push(arrayCommand);
+ emit dataChanged();
+ }
+ }
+}
+
+void QHexEditPrivate::insert(int index, char ch)
+{
+ QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch);
+ _undoStack->push(charCommand);
+ emit dataChanged();
+}
+
+int QHexEditPrivate::lastIndexOf(const QByteArray & ba, int from)
+{
+ from -= ba.length();
+ if (from < 0)
+ from = 0;
+ int idx = _xData.data().lastIndexOf(ba, from);
+ if (idx > -1)
+ {
+ int curPos = idx*2;
+ setCursorPos(curPos);
+ resetSelection(curPos);
+ setSelection(curPos + ba.length()*2);
+ ensureVisible();
+ }
+ return idx;
+}
+
+void QHexEditPrivate::remove(int index, int len)
+{
+ if (len > 0)
+ {
+ if (len == 1)
+ {
+ if (_overwriteMode)
+ {
+ QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0));
+ _undoStack->push(charCommand);
+ emit dataChanged();
+ }
+ else
+ {
+ QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0));
+ _undoStack->push(charCommand);
+ emit dataChanged();
+ }
+ }
+ else
+ {
+ QByteArray ba = QByteArray(len, char(0));
+ if (_overwriteMode)
+ {
+ QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
+ _undoStack->push(arrayCommand);
+ emit dataChanged();
+ }
+ else
+ {
+ QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len);
+ _undoStack->push(arrayCommand);
+ emit dataChanged();
+ }
+ }
+ }
+}
+
+void QHexEditPrivate::replace(int index, char ch)
+{
+ QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch);
+ _undoStack->push(charCommand);
+ resetSelection();
+ emit dataChanged();
+}
+
+void QHexEditPrivate::replace(int index, const QByteArray & ba)
+{
+ QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
+ _undoStack->push(arrayCommand);
+ resetSelection();
+ emit dataChanged();
+}
+
+void QHexEditPrivate::replace(int pos, int len, const QByteArray &after)
+{
+ QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, pos, after, len);
+ _undoStack->push(arrayCommand);
+ resetSelection();
+ emit dataChanged();
+}
+
+void QHexEditPrivate::setAddressArea(bool addressArea)
+{
+ _addressArea = addressArea;
+ adjust();
+
+ setCursorPos(_cursorPosition);
+}
+
+void QHexEditPrivate::setAddressWidth(int addressWidth)
+{
+ _xData.setAddressWidth(addressWidth);
+
+ setCursorPos(_cursorPosition);
+}
+
+void QHexEditPrivate::setAsciiArea(bool asciiArea)
+{
+ _asciiArea = asciiArea;
+ adjust();
+}
+
+void QHexEditPrivate::setFont(const QFont &font)
+{
+ QWidget::setFont(font);
+ adjust();
+}
+
+void QHexEditPrivate::setHighlighting(bool mode)
+{
+ _highlighting = mode;
+ update();
+}
+
+void QHexEditPrivate::setOverwriteMode(bool overwriteMode)
+{
+ _overwriteMode = overwriteMode;
+}
+
+bool QHexEditPrivate::overwriteMode()
+{
+ return _overwriteMode;
+}
+
+void QHexEditPrivate::redo()
+{
+ _undoStack->redo();
+ emit dataChanged();
+ setCursorPos(_cursorPosition);
+ update();
+}
+
+void QHexEditPrivate::undo()
+{
+ _undoStack->undo();
+ emit dataChanged();
+ setCursorPos(_cursorPosition);
+ update();
+}
+
+QString QHexEditPrivate::toRedableString()
+{
+ return _xData.toRedableString();
+}
+
+
+QString QHexEditPrivate::selectionToReadableString()
+{
+ return _xData.toRedableString(getSelectionBegin(), getSelectionEnd());
+}
+
+void QHexEditPrivate::keyPressEvent(QKeyEvent *event)
+{
+ int charX = (_cursorX - _xPosHex) / _charWidth;
+ int posX = (charX / 3) * 2 + (charX % 3);
+ int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2;
+
+
+/*****************************************************************************/
+/* Cursor movements */
+/*****************************************************************************/
+
+ if (event->matches(QKeySequence::MoveToNextChar))
+ {
+ setCursorPos(_cursorPosition + 1);
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToPreviousChar))
+ {
+ setCursorPos(_cursorPosition - 1);
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToEndOfLine))
+ {
+ setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1));
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToStartOfLine))
+ {
+ setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)));
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToPreviousLine))
+ {
+ setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE));
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToNextLine))
+ {
+ setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE));
+ resetSelection(_cursorPosition);
+ }
+
+ if (event->matches(QKeySequence::MoveToNextPage))
+ {
+ setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToPreviousPage))
+ {
+ setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToEndOfDocument))
+ {
+ setCursorPos(_xData.size() * 2);
+ resetSelection(_cursorPosition);
+ }
+ if (event->matches(QKeySequence::MoveToStartOfDocument))
+ {
+ setCursorPos(0);
+ resetSelection(_cursorPosition);
+ }
+
+/*****************************************************************************/
+/* Select commands */
+/*****************************************************************************/
+ if (event->matches(QKeySequence::SelectAll))
+ {
+ resetSelection(0);
+ setSelection(2*_xData.size() + 1);
+ }
+ if (event->matches(QKeySequence::SelectNextChar))
+ {
+ int pos = _cursorPosition + 1;
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectPreviousChar))
+ {
+ int pos = _cursorPosition - 1;
+ setSelection(pos);
+ setCursorPos(pos);
+ }
+ if (event->matches(QKeySequence::SelectEndOfLine))
+ {
+ int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectStartOfLine))
+ {
+ int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE));
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectPreviousLine))
+ {
+ int pos = _cursorPosition - (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectNextLine))
+ {
+ int pos = _cursorPosition + (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+
+ if (event->matches(QKeySequence::SelectNextPage))
+ {
+ int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectPreviousPage))
+ {
+ int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectEndOfDocument))
+ {
+ int pos = _xData.size() * 2;
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+ if (event->matches(QKeySequence::SelectStartOfDocument))
+ {
+ int pos = 0;
+ setCursorPos(pos);
+ setSelection(pos);
+ }
+
+/*****************************************************************************/
+/* Edit Commands */
+/*****************************************************************************/
+if (!_readOnly)
+{
+ /* Hex input */
+ int key = int(event->text()[0].toLatin1()); // changed from toAscii() to toLatin() for Qt5 needs
+ if ((key>='0' && key<='9') || (key>='a' && key <= 'f'))
+ {
+ if (getSelectionBegin() != getSelectionEnd())
+ {
+ posBa = getSelectionBegin();
+ remove(posBa, getSelectionEnd() - posBa);
+ setCursorPos(2*posBa);
+ resetSelection(2*posBa);
+ }
+
+ // If insert mode, then insert a byte
+ if (_overwriteMode == false)
+ if ((charX % 3) == 0)
+ {
+ insert(posBa, char(0));
+ }
+
+ // Change content
+ if (_xData.size() > 0)
+ {
+ QByteArray hexValue = _xData.data().mid(posBa, 1).toHex();
+ if ((charX % 3) == 0)
+ hexValue[0] = key;
+ else
+ hexValue[1] = key;
+
+ replace(posBa, QByteArray().fromHex(hexValue)[0]);
+
+ setCursorPos(_cursorPosition + 1);
+ resetSelection(_cursorPosition);
+ }
+ }
+
+ /* Cut & Paste */
+ if (event->matches(QKeySequence::Cut))
+ {
+ QString result = QString();
+ for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
+ {
+ result += _xData.data().mid(idx, 1).toHex() + " ";
+ if ((idx % 16) == 15)
+ result.append("\n");
+ }
+ remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin());
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(result);
+ setCursorPos(getSelectionBegin());
+ resetSelection(getSelectionBegin());
+ }
+
+ if (event->matches(QKeySequence::Paste))
+ {
+ QClipboard *clipboard = QApplication::clipboard();
+ QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1());
+ insert(_cursorPosition / 2, ba);
+ setCursorPos(_cursorPosition + 2 * ba.length());
+ resetSelection(getSelectionBegin());
+ }
+
+
+ /* Delete char */
+ if (event->matches(QKeySequence::Delete))
+ {
+ if (getSelectionBegin() != getSelectionEnd())
+ {
+ posBa = getSelectionBegin();
+ remove(posBa, getSelectionEnd() - posBa);
+ setCursorPos(2*posBa);
+ resetSelection(2*posBa);
+ }
+ else
+ {
+ if (_overwriteMode)
+ replace(posBa, char(0));
+ else
+ remove(posBa, 1);
+ }
+ }
+
+ /* Backspace */
+ if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier))
+ {
+ if (getSelectionBegin() != getSelectionEnd())
+ {
+ posBa = getSelectionBegin();
+ remove(posBa, getSelectionEnd() - posBa);
+ setCursorPos(2*posBa);
+ resetSelection(2*posBa);
+ }
+ else
+ {
+ if (posBa > 0)
+ {
+ if (_overwriteMode)
+ replace(posBa - 1, char(0));
+ else
+ remove(posBa - 1, 1);
+ setCursorPos(_cursorPosition - 2);
+ }
+ }
+ }
+
+ /* undo */
+ if (event->matches(QKeySequence::Undo))
+ {
+ undo();
+ }
+
+ /* redo */
+ if (event->matches(QKeySequence::Redo))
+ {
+ redo();
+ }
+
+ }
+
+ if (event->matches(QKeySequence::Copy))
+ {
+ QString result = QString();
+ for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
+ {
+ result += _xData.data().mid(idx, 1).toHex() + " ";
+ if ((idx % 16) == 15)
+ result.append('\n');
+ }
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(result);
+ }
+
+ // Switch between insert/overwrite mode
+ if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier))
+ {
+ _overwriteMode = !_overwriteMode;
+ setCursorPos(_cursorPosition);
+ overwriteModeChanged(_overwriteMode);
+ }
+
+ ensureVisible();
+ update();
+}
+
+void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event)
+{
+ _blink = false;
+ update();
+ int actPos = cursorPos(event->pos());
+ setCursorPos(actPos);
+ setSelection(actPos);
+}
+
+void QHexEditPrivate::mousePressEvent(QMouseEvent * event)
+{
+ _blink = false;
+ update();
+ int cPos = cursorPos(event->pos());
+ resetSelection(cPos);
+ setCursorPos(cPos);
+}
+
+void QHexEditPrivate::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+
+ // draw some patterns if needed
+ painter.fillRect(event->rect(), this->palette().color(QPalette::Base));
+ if (_addressArea)
+ painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor);
+ if (_asciiArea)
+ {
+ int linePos = _xPosAscii - (GAP_HEX_ASCII / 2);
+ painter.setPen(Qt::gray);
+ painter.drawLine(linePos, event->rect().top(), linePos, height());
+ }
+
+ painter.setPen(this->palette().color(QPalette::WindowText));
+
+ // calc position
+ int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE;
+ if (firstLineIdx < 0)
+ firstLineIdx = 0;
+ int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE;
+ if (lastLineIdx > _xData.size())
+ lastLineIdx = _xData.size();
+ int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight;
+
+ // paint address area
+ if (_addressArea)
+ {
+ for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
+ {
+ QString address = QString("%1")
+ .arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0'));
+ painter.drawText(_xPosAdr, yPos, address);
+ }
+ }
+
+ // paint hex area
+ QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex());
+ QBrush highLighted = QBrush(_highlightingColor);
+ QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText));
+ QBrush selected = QBrush(_selectionColor);
+ QPen colSelected = QPen(Qt::white);
+ QPen colStandard = QPen(this->palette().color(QPalette::WindowText));
+ painter.setBackgroundMode(Qt::TransparentMode);
+
+ for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
+ {
+ QByteArray hex;
+ int xPos = _xPosHex;
+ for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++)
+ {
+ int posBa = lineIdx + colIdx;
+ if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa))
+ {
+ painter.setBackground(selected);
+ painter.setBackgroundMode(Qt::OpaqueMode);
+ painter.setPen(colSelected);
+ }
+ else
+ {
+ if (_highlighting)
+ {
+ // hilight diff bytes
+ painter.setBackground(highLighted);
+ if (_xData.dataChanged(posBa))
+ {
+ painter.setPen(colHighlighted);
+ painter.setBackgroundMode(Qt::OpaqueMode);
+ }
+ else
+ {
+ painter.setPen(colStandard);
+ painter.setBackgroundMode(Qt::TransparentMode);
+ }
+ }
+ }
+
+ // render hex value
+ if (colIdx == 0)
+ {
+ hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2);
+ painter.drawText(xPos, yPos, hex);
+ xPos += 2 * _charWidth;
+ } else {
+ hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" ");
+ painter.drawText(xPos, yPos, hex);
+ xPos += 3 * _charWidth;
+ }
+
+ }
+ }
+ painter.setBackgroundMode(Qt::TransparentMode);
+ painter.setPen(this->palette().color(QPalette::WindowText));
+
+ // paint ascii area
+ if (_asciiArea)
+ {
+ painter.setBackground(highLighted);
+ for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
+ {
+ int xPosAscii = _xPosAscii;
+ for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++)
+ {
+ if (_cursorPosition/2 == (lineIdx + colIdx))
+ {
+ painter.setBackgroundMode(Qt::OpaqueMode);
+ painter.setPen(colHighlighted);
+ }
+ else
+ {
+ painter.setPen(colStandard);
+ painter.setBackgroundMode(Qt::TransparentMode);
+ }
+
+ painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx));
+ xPosAscii += _charWidth;
+ }
+ }
+ }
+ painter.setBackgroundMode(Qt::TransparentMode);
+ painter.setPen(this->palette().color(QPalette::WindowText));
+
+ // paint cursor
+ if (_blink && !_readOnly && hasFocus())
+ {
+ if (_overwriteMode)
+ painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText));
+ else
+ painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText));
+ }
+
+ if (_size != _xData.size())
+ {
+ _size = _xData.size();
+ emit currentSizeChanged(_size);
+ }
+}
+
+void QHexEditPrivate::setCursorPos(int position)
+{
+ // delete cursor
+ _blink = false;
+ update();
+
+ // cursor in range?
+ if (_overwriteMode)
+ {
+ if (position > (_xData.size() * 2 - 1))
+ position = _xData.size() * 2 - 1;
+ } else {
+ if (position > (_xData.size() * 2))
+ position = _xData.size() * 2;
+ }
+
+ if (position < 0)
+ position = 0;
+
+ // calc position
+ _cursorPosition = position;
+ _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4;
+ int x = (position % (2 * BYTES_PER_LINE));
+ _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex;
+
+ // immiadately draw cursor
+ _blink = true;
+ update();
+ emit currentAddressChanged(_cursorPosition/2);
+}
+
+int QHexEditPrivate::cursorPos(QPoint pos)
+{
+ int result = -1;
+ // find char under cursor
+ if ((pos.x() >= _xPosHex) and (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth)))
+ {
+ int x = (pos.x() - _xPosHex) / _charWidth;
+ if ((x % 3) == 0)
+ x = (x / 3) * 2;
+ else
+ x = ((x / 3) * 2) + 1;
+ int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE;
+ result = x + y;
+ }
+ return result;
+}
+
+int QHexEditPrivate::cursorPos()
+{
+ return _cursorPosition;
+}
+
+void QHexEditPrivate::resetSelection()
+{
+ _selectionBegin = _selectionInit;
+ _selectionEnd = _selectionInit;
+}
+
+void QHexEditPrivate::resetSelection(int pos)
+{
+ if (pos < 0)
+ pos = 0;
+ pos = pos / 2;
+ _selectionInit = pos;
+ _selectionBegin = pos;
+ _selectionEnd = pos;
+}
+
+void QHexEditPrivate::setSelection(int pos)
+{
+ if (pos < 0)
+ pos = 0;
+ pos = pos / 2;
+ if (pos >= _selectionInit)
+ {
+ _selectionEnd = pos;
+ _selectionBegin = _selectionInit;
+ }
+ else
+ {
+ _selectionBegin = pos;
+ _selectionEnd = _selectionInit;
+ }
+}
+
+int QHexEditPrivate::getSelectionBegin()
+{
+ return _selectionBegin;
+}
+
+int QHexEditPrivate::getSelectionEnd()
+{
+ return _selectionEnd;
+}
+
+QSize QHexEditPrivate::sizeHint() const
+{
+ int wd = _xPosAscii + BYTES_PER_LINE * _charWidth;
+ int hg = _charHeight * 8;
+ if (!_asciiArea)
+ wd = _xPosHex + HEXCHARS_IN_LINE * _charWidth;
+
+ return QSize(wd, hg);
+}
+
+
+void QHexEditPrivate::updateCursor()
+{
+ if (_blink)
+ _blink = false;
+ else
+ _blink = true;
+ update(_cursorX, _cursorY, _charWidth, _charHeight);
+}
+
+void QHexEditPrivate::adjust()
+{
+ _charWidth = fontMetrics().width(QLatin1Char('9'));
+ _charHeight = fontMetrics().height();
+
+ _xPosAdr = 0;
+ if (_addressArea)
+ _xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX;
+ else
+ _xPosHex = 0;
+ _xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII;
+
+ // tell QAbstractScollbar, how big we are
+ setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5);
+ if(_asciiArea)
+ setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth));
+ else
+ setMinimumWidth(_xPosHex + HEXCHARS_IN_LINE * _charWidth);
+
+ update();
+}
+
+void QHexEditPrivate::ensureVisible()
+{
+ // scrolls to cursorx, cusory (which are set by setCursorPos)
+ // x-margin is 3 pixels, y-margin is half of charHeight
+ _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h
new file mode 100644
index 0000000..b9c17d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h
@@ -0,0 +1,129 @@
+#ifndef QHEXEDIT_P_H
+#define QHEXEDIT_P_H
+
+/** \cond docNever */
+
+
+#include <QtGui>
+#include <QScrollArea>
+#include <QUndoStack>
+#include "xbytearray.h"
+
+class QHexEditPrivate : public QWidget
+{
+Q_OBJECT
+
+public:
+ explicit QHexEditPrivate(QScrollArea *parent);
+
+ void setAddressAreaColor(QColor const &color);
+ QColor addressAreaColor();
+
+ void setAddressOffset(int offset);
+ int addressOffset();
+
+ void setCursorPos(int position);
+ int cursorPos();
+
+ void setData(QByteArray const &data);
+ QByteArray data();
+
+ void setHighlightingColor(QColor const &color);
+ QColor highlightingColor();
+
+ void setOverwriteMode(bool overwriteMode);
+ bool overwriteMode();
+
+ void setReadOnly(bool readOnly);
+ bool isReadOnly();
+
+ void setSelectionColor(QColor const &color);
+ QColor selectionColor();
+
+ XByteArray & xData();
+
+ int indexOf(const QByteArray & ba, int from = 0);
+ void insert(int index, const QByteArray & ba);
+ void insert(int index, char ch);
+ int lastIndexOf(const QByteArray & ba, int from = 0);
+ void remove(int index, int len=1);
+ void replace(int index, char ch);
+ void replace(int index, const QByteArray & ba);
+ void replace(int pos, int len, const QByteArray & after);
+
+ void setAddressArea(bool addressArea);
+ void setAddressWidth(int addressWidth);
+ void setAsciiArea(bool asciiArea);
+ void setHighlighting(bool mode);
+ virtual void setFont(const QFont &font);
+
+ void undo();
+ void redo();
+
+ QString toRedableString();
+ QString selectionToReadableString();
+
+ QSize sizeHint() const;
+
+signals:
+ void currentAddressChanged(int address);
+ void currentSizeChanged(int size);
+ void dataChanged();
+ void overwriteModeChanged(bool state);
+
+protected:
+ void keyPressEvent(QKeyEvent * event);
+ void mouseMoveEvent(QMouseEvent * event);
+ void mousePressEvent(QMouseEvent * event);
+
+ void paintEvent(QPaintEvent *event);
+
+ int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION
+
+ void resetSelection(int pos); // set selectionStart and selectionEnd to pos
+ void resetSelection(); // set selectionEnd to selectionStart
+ void setSelection(int pos); // set min (if below init) or max (if greater init)
+ int getSelectionBegin();
+ int getSelectionEnd();
+
+
+private slots:
+ void updateCursor();
+
+private:
+ void adjust();
+ void ensureVisible();
+
+ QColor _addressAreaColor;
+ QColor _highlightingColor;
+ QColor _selectionColor;
+ QScrollArea *_scrollArea;
+ QTimer _cursorTimer;
+ QUndoStack *_undoStack;
+
+ XByteArray _xData; // Hält den Inhalt des Hex Editors
+
+ bool _blink; // true: then cursor blinks
+ bool _renderingRequired; // Flag to store that rendering is necessary
+ bool _addressArea; // left area of QHexEdit
+ bool _asciiArea; // medium area
+ bool _highlighting; // highlighting of changed bytes
+ bool _overwriteMode;
+ bool _readOnly; // true: the user can only look and navigate
+
+ int _charWidth, _charHeight; // char dimensions (dpendend on font)
+ int _cursorX, _cursorY; // graphics position of the cursor
+ int _cursorPosition; // character positioin in stream (on byte ends in to steps)
+ int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas
+
+ int _selectionBegin; // First selected char
+ int _selectionEnd; // Last selected char
+ int _selectionInit; // That's, where we pressed the mouse button
+
+ int _size;
+};
+
+/** \endcond docNever */
+
+#endif
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp
new file mode 100644
index 0000000..ec8bf3d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp
@@ -0,0 +1,167 @@
+#include "xbytearray.h"
+
+XByteArray::XByteArray()
+{
+ _oldSize = -99;
+ _addressNumbers = 4;
+ _addressOffset = 0;
+
+}
+
+int XByteArray::addressOffset()
+{
+ return _addressOffset;
+}
+
+void XByteArray::setAddressOffset(int offset)
+{
+ _addressOffset = offset;
+}
+
+int XByteArray::addressWidth()
+{
+ return _addressNumbers;
+}
+
+void XByteArray::setAddressWidth(int width)
+{
+ if ((width >= 0) and (width<=6))
+ {
+ _addressNumbers = width;
+ }
+}
+
+QByteArray & XByteArray::data()
+{
+ return _data;
+}
+
+void XByteArray::setData(QByteArray data)
+{
+ _data = data;
+ _changedData = QByteArray(data.length(), char(0));
+}
+
+bool XByteArray::dataChanged(int i)
+{
+ return bool(_changedData[i]);
+}
+
+QByteArray XByteArray::dataChanged(int i, int len)
+{
+ return _changedData.mid(i, len);
+}
+
+void XByteArray::setDataChanged(int i, bool state)
+{
+ _changedData[i] = char(state);
+}
+
+void XByteArray::setDataChanged(int i, const QByteArray & state)
+{
+ int length = state.length();
+ int len;
+ if ((i + length) > _changedData.length())
+ len = _changedData.length() - i;
+ else
+ len = length;
+ _changedData.replace(i, len, state);
+}
+
+int XByteArray::realAddressNumbers()
+{
+ if (_oldSize != _data.size())
+ {
+ // is addressNumbers wide enought?
+ QString test = QString("%1")
+ .arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0'));
+ _realAddressNumbers = test.size();
+ }
+ return _realAddressNumbers;
+}
+
+int XByteArray::size()
+{
+ return _data.size();
+}
+
+QByteArray & XByteArray::insert(int i, char ch)
+{
+ _data.insert(i, ch);
+ _changedData.insert(i, char(1));
+ return _data;
+}
+
+QByteArray & XByteArray::insert(int i, const QByteArray & ba)
+{
+ _data.insert(i, ba);
+ _changedData.insert(i, QByteArray(ba.length(), char(1)));
+ return _data;
+}
+
+QByteArray & XByteArray::remove(int i, int len)
+{
+ _data.remove(i, len);
+ _changedData.remove(i, len);
+ return _data;
+}
+
+QByteArray & XByteArray::replace(int index, char ch)
+{
+ _data[index] = ch;
+ _changedData[index] = char(1);
+ return _data;
+}
+
+QByteArray & XByteArray::replace(int index, const QByteArray & ba)
+{
+ int len = ba.length();
+ return replace(index, len, ba);
+}
+
+QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba)
+{
+ int len;
+ if ((index + length) > _data.length())
+ len = _data.length() - index;
+ else
+ len = length;
+ _data.replace(index, len, ba.mid(0, len));
+ _changedData.replace(index, len, QByteArray(len, char(1)));
+ return _data;
+}
+
+QChar XByteArray::asciiChar(int index)
+{
+ char ch = _data[index];
+ if ((ch < 0x20) or (ch > 0x7e))
+ ch = '.';
+ return QChar(ch);
+}
+
+QString XByteArray::toRedableString(int start, int end)
+{
+ int adrWidth = realAddressNumbers();
+ if (_addressNumbers > adrWidth)
+ adrWidth = _addressNumbers;
+ if (end < 0)
+ end = _data.size();
+
+ QString result;
+ for (int i=start; i < end; i += 16)
+ {
+ QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0'));
+ QString hexStr;
+ QString ascStr;
+ for (int j=0; j<16; j++)
+ {
+ if ((i + j) < _data.size())
+ {
+ hexStr.append(" ").append(_data.mid(i+j, 1).toHex());
+ ascStr.append(asciiChar(i+j));
+ }
+ }
+ result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n";
+ }
+ return result;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h
new file mode 100644
index 0000000..a5cdc11
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h
@@ -0,0 +1,67 @@
+#ifndef XBYTEARRAY_H
+#define XBYTEARRAY_H
+
+/** \cond docNever */
+
+#include "guiSQLiteStudio_global.h"
+#include <QtCore>
+
+/*! XByteArray represents the content of QHexEcit.
+XByteArray comprehend the data itself and informations to store if it was
+changed. The QHexEdit component uses these informations to perform nice
+rendering of the data
+
+XByteArray also provides some functionality to insert, replace and remove
+single chars and QByteArras. Additionally some functions support rendering
+and converting to readable strings.
+*/
+class GUI_API_EXPORT XByteArray
+{
+public:
+ explicit XByteArray();
+
+ int addressOffset();
+ void setAddressOffset(int offset);
+
+ int addressWidth();
+ void setAddressWidth(int width);
+
+ QByteArray & data();
+ void setData(QByteArray data);
+
+ bool dataChanged(int i);
+ QByteArray dataChanged(int i, int len);
+ void setDataChanged(int i, bool state);
+ void setDataChanged(int i, const QByteArray & state);
+
+ int realAddressNumbers();
+ int size();
+
+ QByteArray & insert(int i, char ch);
+ QByteArray & insert(int i, const QByteArray & ba);
+
+ QByteArray & remove(int pos, int len);
+
+ QByteArray & replace(int index, char ch);
+ QByteArray & replace(int index, const QByteArray & ba);
+ QByteArray & replace(int index, int length, const QByteArray & ba);
+
+ QChar asciiChar(int index);
+ QString toRedableString(int start=0, int end=-1);
+
+signals:
+
+public slots:
+
+private:
+ QByteArray _data;
+ QByteArray _changedData;
+
+ int _addressNumbers; // wanted width of address area
+ int _addressOffset; // will be added to the real addres inside bytearray
+ int _realAddressNumbers; // real width of address area (can be greater then wanted width)
+ int _oldSize; // size of data
+};
+
+/** \endcond docNever */
+#endif // XBYTEARRAY_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp
new file mode 100644
index 0000000..6ca5c59
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp
@@ -0,0 +1,359 @@
+#include "qtscriptsyntaxhighlighter.h"
+#include "uiconfig.h"
+
+#include <QPlainTextEdit>
+
+JavaScriptSyntaxHighlighter::JavaScriptSyntaxHighlighter(QTextDocument *parent)
+ : QSyntaxHighlighter(parent)
+ , m_markCaseSensitivity(Qt::CaseInsensitive)
+{
+ // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words
+ m_keywords << "break";
+ m_keywords << "case";
+ m_keywords << "catch";
+ m_keywords << "continue";
+ m_keywords << "default";
+ m_keywords << "delete";
+ m_keywords << "do";
+ m_keywords << "else";
+ m_keywords << "finally";
+ m_keywords << "for";
+ m_keywords << "function";
+ m_keywords << "if";
+ m_keywords << "in";
+ m_keywords << "instanceof";
+ m_keywords << "new";
+ m_keywords << "return";
+ m_keywords << "switch";
+ m_keywords << "this";
+ m_keywords << "throw";
+ m_keywords << "try";
+ m_keywords << "typeof";
+ m_keywords << "var";
+ m_keywords << "void";
+ m_keywords << "while";
+ m_keywords << "with";
+
+ m_keywords << "true";
+ m_keywords << "false";
+ m_keywords << "null";
+
+ // built-in and other popular objects + properties
+ m_knownIds << "Object";
+ m_knownIds << "prototype";
+ m_knownIds << "create";
+ m_knownIds << "defineProperty";
+ m_knownIds << "defineProperties";
+ m_knownIds << "getOwnPropertyDescriptor";
+ m_knownIds << "keys";
+ m_knownIds << "getOwnPropertyNames";
+ m_knownIds << "constructor";
+ m_knownIds << "__parent__";
+ m_knownIds << "__proto__";
+ m_knownIds << "__defineGetter__";
+ m_knownIds << "__defineSetter__";
+ m_knownIds << "eval";
+ m_knownIds << "hasOwnProperty";
+ m_knownIds << "isPrototypeOf";
+ m_knownIds << "__lookupGetter__";
+ m_knownIds << "__lookupSetter__";
+ m_knownIds << "__noSuchMethod__";
+ m_knownIds << "propertyIsEnumerable";
+ m_knownIds << "toSource";
+ m_knownIds << "toLocaleString";
+ m_knownIds << "toString";
+ m_knownIds << "unwatch";
+ m_knownIds << "valueOf";
+ m_knownIds << "watch";
+
+ m_knownIds << "Function";
+ m_knownIds << "arguments";
+ m_knownIds << "arity";
+ m_knownIds << "caller";
+ m_knownIds << "constructor";
+ m_knownIds << "length";
+ m_knownIds << "name";
+ m_knownIds << "apply";
+ m_knownIds << "bind";
+ m_knownIds << "call";
+
+ m_knownIds << "String";
+ m_knownIds << "fromCharCode";
+ m_knownIds << "length";
+ m_knownIds << "charAt";
+ m_knownIds << "charCodeAt";
+ m_knownIds << "concat";
+ m_knownIds << "indexOf";
+ m_knownIds << "lastIndexOf";
+ m_knownIds << "localCompare";
+ m_knownIds << "match";
+ m_knownIds << "quote";
+ m_knownIds << "replace";
+ m_knownIds << "search";
+ m_knownIds << "slice";
+ m_knownIds << "split";
+ m_knownIds << "substr";
+ m_knownIds << "substring";
+ m_knownIds << "toLocaleLowerCase";
+ m_knownIds << "toLocaleUpperCase";
+ m_knownIds << "toLowerCase";
+ m_knownIds << "toUpperCase";
+ m_knownIds << "trim";
+ m_knownIds << "trimLeft";
+ m_knownIds << "trimRight";
+
+ m_knownIds << "Array";
+ m_knownIds << "isArray";
+ m_knownIds << "index";
+ m_knownIds << "input";
+ m_knownIds << "pop";
+ m_knownIds << "push";
+ m_knownIds << "reverse";
+ m_knownIds << "shift";
+ m_knownIds << "sort";
+ m_knownIds << "splice";
+ m_knownIds << "unshift";
+ m_knownIds << "concat";
+ m_knownIds << "join";
+ m_knownIds << "filter";
+ m_knownIds << "forEach";
+ m_knownIds << "every";
+ m_knownIds << "map";
+ m_knownIds << "some";
+ m_knownIds << "reduce";
+ m_knownIds << "reduceRight";
+
+ m_knownIds << "RegExp";
+ m_knownIds << "global";
+ m_knownIds << "ignoreCase";
+ m_knownIds << "lastIndex";
+ m_knownIds << "multiline";
+ m_knownIds << "source";
+ m_knownIds << "exec";
+ m_knownIds << "test";
+
+ m_knownIds << "JSON";
+ m_knownIds << "parse";
+ m_knownIds << "stringify";
+
+ m_knownIds << "decodeURI";
+ m_knownIds << "decodeURIComponent";
+ m_knownIds << "encodeURI";
+ m_knownIds << "encodeURIComponent";
+ m_knownIds << "eval";
+ m_knownIds << "isFinite";
+ m_knownIds << "isNaN";
+ m_knownIds << "parseFloat";
+ m_knownIds << "parseInt";
+ m_knownIds << "Infinity";
+ m_knownIds << "NaN";
+ m_knownIds << "undefined";
+
+ m_knownIds << "Math";
+ m_knownIds << "E";
+ m_knownIds << "LN2";
+ m_knownIds << "LN10";
+ m_knownIds << "LOG2E";
+ m_knownIds << "LOG10E";
+ m_knownIds << "PI";
+ m_knownIds << "SQRT1_2";
+ m_knownIds << "SQRT2";
+ m_knownIds << "abs";
+ m_knownIds << "acos";
+ m_knownIds << "asin";
+ m_knownIds << "atan";
+ m_knownIds << "atan2";
+ m_knownIds << "ceil";
+ m_knownIds << "cos";
+ m_knownIds << "exp";
+ m_knownIds << "floor";
+ m_knownIds << "log";
+ m_knownIds << "max";
+ m_knownIds << "min";
+ m_knownIds << "pow";
+ m_knownIds << "random";
+ m_knownIds << "round";
+ m_knownIds << "sin";
+ m_knownIds << "sqrt";
+ m_knownIds << "tan";
+
+ m_knownIds << "document";
+ m_knownIds << "window";
+ m_knownIds << "navigator";
+ m_knownIds << "userAgent";
+
+ keywordsFormat.setFontWeight(QFont::Bold);
+}
+
+void JavaScriptSyntaxHighlighter::highlightBlock(const QString &text)
+{
+ // parsing state
+ enum {
+ Start = -1,
+ Number = 1,
+ Identifier = 2,
+ String = 3,
+ Comment = 4,
+ Regex = 5
+ };
+
+ int state = previousBlockState();
+ int start = 0;
+ int i = 0;
+ while (i <= text.length()) {
+ QChar ch = (i < text.length()) ? text.at(i) : QChar();
+ QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar();
+
+ switch (state) {
+
+ case Start:
+ start = i;
+ if (ch.isSpace()) {
+ ++i;
+ } else if (ch.isDigit()) {
+ ++i;
+ state = Number;
+ } else if (ch.isLetter() || ch == '_') {
+ ++i;
+ state = Identifier;
+ } else if (ch == '\'' || ch == '\"') {
+ ++i;
+ state = String;
+ } else if (ch == '/' && next == '*') {
+ ++i;
+ ++i;
+ state = Comment;
+ } else if (ch == '/' && next == '/') {
+ i = text.length();
+ setFormat(start, text.length(), CFG_UI.Colors.JavaScriptComment.get());
+ } else if (ch == '/' && next != '*') {
+ ++i;
+ state = Regex;
+ } else {
+ if (!QString("(){}[]").contains(ch))
+ setFormat(start, 1, CFG_UI.Colors.JavaScriptOperator.get());
+ ++i;
+ state = Start;
+ }
+ break;
+
+ case Number:
+ if (ch.isSpace() || !ch.isDigit()) {
+ setFormat(start, i - start, CFG_UI.Colors.JavaScriptNumber.get());
+ state = Start;
+ } else {
+ ++i;
+ }
+ break;
+
+ case Identifier:
+ if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) {
+ QString token = text.mid(start, i - start).trimmed();
+ if (m_keywords.contains(token))
+ {
+ keywordsFormat.setForeground(CFG_UI.Colors.JavaScriptKeyword.get());
+ setFormat(start, i - start, keywordsFormat);
+ }
+ else if (m_knownIds.contains(token))
+ setFormat(start, i - start, CFG_UI.Colors.JavaScriptBuiltIn.get());
+ state = Start;
+ } else {
+ ++i;
+ }
+ break;
+
+ case String:
+ if (ch == text.at(start)) {
+ QChar prev = (i > 0) ? text.at(i - 1) : QChar();
+ if (prev != '\\') {
+ ++i;
+ setFormat(start, i - start, CFG_UI.Colors.JavaScriptString.get());
+ state = Start;
+ } else {
+ ++i;
+ }
+ } else {
+ ++i;
+ }
+ break;
+
+ case Comment:
+ if (ch == '*' && next == '/') {
+ ++i;
+ ++i;
+ setFormat(start, i - start, CFG_UI.Colors.JavaScriptComment.get());
+ state = Start;
+ } else {
+ ++i;
+ }
+ break;
+
+ case Regex:
+ if (ch == '/') {
+ QChar prev = (i > 0) ? text.at(i - 1) : QChar();
+ if (prev != '\\') {
+ ++i;
+ setFormat(start, i - start, CFG_UI.Colors.JavaScriptString.get());
+ state = Start;
+ } else {
+ ++i;
+ }
+ } else {
+ ++i;
+ }
+ break;
+
+ default:
+ state = Start;
+ break;
+ }
+ }
+
+ if (state == Comment)
+ setFormat(start, text.length(), CFG_UI.Colors.JavaScriptComment.get());
+ else
+ state = Start;
+
+ if (!m_markString.isEmpty()) {
+ int pos = 0;
+ int len = m_markString.length();
+ QTextCharFormat markerFormat;
+ markerFormat.setBackground(CFG_UI.Colors.JavaScriptMarker.get());
+ markerFormat.setForeground(CFG_UI.Colors.JavaScriptFg.get());
+ for (;;) {
+ pos = text.indexOf(m_markString, pos, m_markCaseSensitivity);
+ if (pos < 0)
+ break;
+ setFormat(pos, len, markerFormat);
+ ++pos;
+ }
+ }
+
+ setCurrentBlockState(state);
+}
+
+void JavaScriptSyntaxHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
+{
+ m_markString = str;
+ m_markCaseSensitivity = caseSensitivity;
+ rehighlight();
+}
+
+
+QString JavaScriptHighlighterPlugin::getLanguageName() const
+{
+ return QStringLiteral("QtScript");
+}
+
+QSyntaxHighlighter* JavaScriptHighlighterPlugin::createSyntaxHighlighter(QWidget* textEdit) const
+{
+ QPlainTextEdit* plainEdit = dynamic_cast<QPlainTextEdit*>(textEdit);
+ if (plainEdit)
+ return new JavaScriptSyntaxHighlighter(plainEdit->document());
+
+ QTextEdit* edit = dynamic_cast<QTextEdit*>(textEdit);
+ if (edit)
+ return new JavaScriptSyntaxHighlighter(edit->document());
+
+ return nullptr;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h
new file mode 100644
index 0000000..bf978d2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h
@@ -0,0 +1,75 @@
+#ifndef JAVASCRIPTSYNTAXHIGHLIGHTER_H
+#define JAVASCRIPTSYNTAXHIGHLIGHTER_H
+
+/*
+ This file is part of the Ofi Labs X2 project.
+
+ Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "guiSQLiteStudio_global.h"
+#include "plugins/builtinplugin.h"
+#include "syntaxhighlighterplugin.h"
+#include <QSyntaxHighlighter>
+#include <QtGui>
+
+/**
+ * @brief The JavaScript (also QtScript) highlighter
+ *
+ * This class is mostly copied from Ofi Labs X2 project. It has been slightly modified for SQLiteStudio needs.
+ * See the source code for the full license disclaimer.
+ */
+class GUI_API_EXPORT JavaScriptSyntaxHighlighter : public QSyntaxHighlighter
+{
+ public:
+ explicit JavaScriptSyntaxHighlighter(QTextDocument *parent = 0);
+ void mark(const QString &str, Qt::CaseSensitivity caseSensitivity);
+
+ protected:
+ void highlightBlock(const QString &text);
+
+ private:
+ QSet<QString> m_keywords;
+ QSet<QString> m_knownIds;
+ QString m_markString;
+ Qt::CaseSensitivity m_markCaseSensitivity;
+ QTextCharFormat keywordsFormat;
+};
+
+class GUI_API_EXPORT JavaScriptHighlighterPlugin : public BuiltInPlugin, public SyntaxHighlighterPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_TITLE("QtScript highlighter")
+ SQLITESTUDIO_PLUGIN_DESC("QtScript (JavaScript) syntax highlighter.")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+
+ public:
+ QString getLanguageName() const;
+ QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const;
+};
+#endif // JAVASCRIPTSYNTAXHIGHLIGHTER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp
new file mode 100644
index 0000000..ebdf2c0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp
@@ -0,0 +1,204 @@
+#include "searchtextlocator.h"
+#include "common/unused.h"
+#include <QTextCursor>
+#include <QTextBlock>
+#include <QRegExp>
+#include <QDebug>
+
+SearchTextLocator::SearchTextLocator(QTextDocument* document, QObject* parent) :
+ QObject(parent), document(document)
+{
+}
+
+QString SearchTextLocator::getLookupString() const
+{
+ return lookupString;
+}
+
+void SearchTextLocator::setLookupString(const QString& value)
+{
+ lookupString = value;
+ lastMatchStart = -1;
+ lastMatchEnd = -1;
+}
+
+QString SearchTextLocator::getReplaceString() const
+{
+ return replaceString;
+}
+
+void SearchTextLocator::setReplaceString(const QString& value)
+{
+ replaceString = value;
+}
+
+bool SearchTextLocator::getCaseSensitive() const
+{
+ return caseSensitive;
+}
+
+void SearchTextLocator::setCaseSensitive(bool value)
+{
+ caseSensitive = value;
+}
+
+bool SearchTextLocator::getRegularExpression() const
+{
+ return regularExpression;
+}
+
+void SearchTextLocator::setRegularExpression(bool value)
+{
+ regularExpression = value;
+}
+
+bool SearchTextLocator::getSearchBackwards() const
+{
+ return searchBackwards;
+}
+
+void SearchTextLocator::setSearchBackwards(bool value)
+{
+ searchBackwards = value;
+}
+
+int SearchTextLocator::getStartPosition() const
+{
+ return startPosition;
+}
+
+void SearchTextLocator::setStartPosition(int value)
+{
+ startPosition = value;
+ initialStartPosition = value;
+ afterDocPositionSwitch = false;
+ lastMatchStart = -1;
+ lastMatchEnd = -1;
+ emit replaceAvailable(false);
+}
+
+QTextDocument::FindFlags SearchTextLocator::getFlags()
+{
+ QTextDocument::FindFlags flags;
+ if (caseSensitive)
+ flags |= QTextDocument::FindCaseSensitively;
+
+ if (searchBackwards)
+ flags |= QTextDocument::FindBackward;
+
+ return flags;
+}
+
+void SearchTextLocator::notFound()
+{
+ startPosition = initialStartPosition;
+ afterDocPositionSwitch = false;
+ emit reachedEnd();
+ emit replaceAvailable(false);
+}
+
+QTextCursor SearchTextLocator::findInWholeDoc(QTextDocument::FindFlags flags)
+{
+ // Simply find a match
+ QTextCursor cursor;
+ if (regularExpression)
+ cursor = document->find(QRegExp(lookupString), startPosition, flags);
+ else
+ cursor = document->find(lookupString, startPosition, flags);
+
+ // If not matched, see if we can find match at the other part of document (before/after init start position)
+ if (cursor.isNull() && !afterDocPositionSwitch)
+ {
+ afterDocPositionSwitch = true;
+ int start = 0;
+ if (flags.testFlag(QTextDocument::FindBackward))
+ start = document->lastBlock().position() + document->lastBlock().length();
+
+ if (regularExpression)
+ cursor = document->find(QRegExp(lookupString), start, flags);
+ else
+ cursor = document->find(lookupString, start, flags);
+ }
+
+ // If we found a match after/before start position, but it's already before/after start position, we cannot report it as a match.
+ if ((afterDocPositionSwitch && !cursor.isNull()) && // we have a match after switching doc position
+ ((!flags.testFlag(QTextDocument::FindBackward) && cursor.selectionStart() >= initialStartPosition) || // it's after init pos
+ (flags.testFlag(QTextDocument::FindBackward) && cursor.selectionEnd() <= initialStartPosition))) // it's before init pos
+ {
+ cursor = QTextCursor(); // exceeded search range
+ }
+
+ // If we do have a match, we need to remember its parameters for the next lookup.
+ if (!cursor.isNull())
+ {
+ if (flags.testFlag(QTextDocument::FindBackward))
+ startPosition = cursor.selectionStart();
+ else
+ startPosition = cursor.selectionEnd();
+
+ lastMatchStart = cursor.selectionStart();
+ lastMatchEnd = cursor.selectionEnd();
+ }
+
+ return cursor;
+}
+
+void SearchTextLocator::replaceCurrent()
+{
+ if (lastMatchStart == -1 || lastMatchEnd == -1)
+ return;
+
+ QTextCursor cursor(document);
+ cursor.setPosition(lastMatchStart);
+ cursor.setPosition(lastMatchEnd, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+ cursor.insertText(replaceString);
+}
+
+bool SearchTextLocator::find(QTextDocument::FindFlags flags)
+{
+ if (flags == 0)
+ flags = getFlags();
+
+ QTextCursor cursor = findInWholeDoc(flags);
+ if (cursor.isNull())
+ {
+ notFound();
+ return false;
+ }
+
+ emit found(cursor.selectionStart(), cursor.selectionEnd());
+ emit replaceAvailable(true);
+ return true;
+}
+
+void SearchTextLocator::findNext()
+{
+ QTextDocument::FindFlags flags = getFlags();
+ flags &= !QTextDocument::FindBackward;
+ find(flags);
+}
+
+void SearchTextLocator::findPrev()
+{
+ QTextDocument::FindFlags flags = getFlags();
+ flags |= QTextDocument::FindBackward;
+ find(flags);
+}
+
+bool SearchTextLocator::replaceAndFind()
+{
+ replaceCurrent();
+ return find();
+}
+
+void SearchTextLocator::replaceAll()
+{
+ while (replaceAndFind())
+ continue;
+}
+
+void SearchTextLocator::cursorMoved()
+{
+ emit replaceAvailable(false);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h
new file mode 100644
index 0000000..0cfdb44
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h
@@ -0,0 +1,75 @@
+#ifndef SEARCHTEXTLOCATOR_H
+#define SEARCHTEXTLOCATOR_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QObject>
+#include <QTextDocument>
+
+class GUI_API_EXPORT SearchTextLocator : public QObject
+{
+ Q_OBJECT
+
+ public:
+ struct GUI_API_EXPORT Occurrance
+ {
+ int start;
+ int end;
+ };
+
+ SearchTextLocator(QTextDocument* document, QObject* parent = nullptr);
+
+ QString getLookupString() const;
+ void setLookupString(const QString& value);
+
+ QString getReplaceString() const;
+ void setReplaceString(const QString& value);
+
+ bool getCaseSensitive() const;
+ void setCaseSensitive(bool value);
+
+ bool getRegularExpression() const;
+ void setRegularExpression(bool value);
+
+ bool getSearchBackwards() const;
+ void setSearchBackwards(bool value);
+
+ int getStartPosition() const;
+ void setStartPosition(int value);
+
+ void reset();
+
+ private:
+ QTextDocument::FindFlags getFlags();
+ void notFound();
+ QTextCursor findInWholeDoc(QTextDocument::FindFlags flags);
+ void replaceCurrent();
+
+ QTextDocument* document = nullptr;
+ int initialStartPosition;
+ int lastMatchStart = -1;
+ int lastMatchEnd = -1;
+ bool afterDocPositionSwitch = false;
+
+ // Config parameters
+ QString lookupString;
+ QString replaceString;
+ bool caseSensitive = false;
+ bool regularExpression = false;
+ bool searchBackwards = false;
+ int startPosition = 0;
+
+ public slots:
+ bool find(QTextDocument::FindFlags flags = 0);
+ void findNext();
+ void findPrev();
+ bool replaceAndFind();
+ void replaceAll();
+ void cursorMoved();
+
+ signals:
+ void found(int start, int end);
+ void reachedEnd();
+ void replaceAvailable(bool available);
+};
+
+#endif // SEARCHTEXTLOCATOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp
new file mode 100644
index 0000000..f19d52d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp
@@ -0,0 +1,109 @@
+#include "selectabledbmodel.h"
+#include "dbtree/dbtreeitem.h"
+#include "dbtree/dbtreemodel.h"
+
+SelectableDbModel::SelectableDbModel(QObject* parent) :
+ QSortFilterProxyModel(parent)
+{
+}
+
+QVariant SelectableDbModel::data(const QModelIndex& index, int role) const
+{
+ if (role != Qt::CheckStateRole)
+ return QSortFilterProxyModel::data(index, role);
+
+ DbTreeItem* item = getItemForProxyIndex(index);
+ if (!item)
+ return QSortFilterProxyModel::data(index, role);
+
+ DbTreeItem::Type type = item->getType();
+ if (type != DbTreeItem::Type::DB)
+ return QSortFilterProxyModel::data(index, role);
+
+ return checkedDatabases.contains(item->text(), Qt::CaseInsensitive) ? Qt::Checked : Qt::Unchecked;
+}
+
+bool SelectableDbModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ if (role != Qt::CheckStateRole)
+ return QSortFilterProxyModel::setData(index, value, role);
+
+ DbTreeItem* item = getItemForProxyIndex(index);
+ if (!item)
+ return QSortFilterProxyModel::setData(index, value, role);
+
+ DbTreeItem::Type type = item->getType();
+ if (type != DbTreeItem::Type::DB)
+ return QSortFilterProxyModel::setData(index, value, role);
+
+ if (value.toBool())
+ checkedDatabases << item->text();
+ else
+ checkedDatabases.removeOne(item->text());
+
+ emit dataChanged(index, index, {Qt::CheckStateRole});
+
+ return true;
+}
+
+Qt::ItemFlags SelectableDbModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags itemFlags = QSortFilterProxyModel::flags(index);
+
+ DbTreeItem* item = getItemForProxyIndex(index);
+ if (!item)
+ return itemFlags;
+
+ DbTreeItem::Type type = item->getType();
+ if (item->getDb() && item->getDb()->getVersion() == disabledVersion)
+ itemFlags ^= Qt::ItemIsEnabled;
+ else if (type == DbTreeItem::Type::DB)
+ itemFlags |= Qt::ItemIsUserCheckable;
+
+ return itemFlags;
+}
+
+void SelectableDbModel::setDatabases(const QStringList& databases)
+{
+ beginResetModel();
+ checkedDatabases = databases;
+ endResetModel();
+}
+
+QStringList SelectableDbModel::getDatabases() const
+{
+ return checkedDatabases;
+}
+
+bool SelectableDbModel::filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const
+{
+ QModelIndex idx = sourceModel()->index(srcRow, 0, srcParent);
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(dynamic_cast<DbTreeModel*>(sourceModel())->itemFromIndex(idx));
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::DB:
+ return true;
+ default:
+ return false;
+ }
+ return false;
+}
+
+DbTreeItem* SelectableDbModel::getItemForProxyIndex(const QModelIndex& index) const
+{
+ QModelIndex srcIdx = mapToSource(index);
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(dynamic_cast<DbTreeModel*>(sourceModel())->itemFromIndex(srcIdx));
+ return item;
+}
+int SelectableDbModel::getDisabledVersion() const
+{
+ return disabledVersion;
+}
+
+void SelectableDbModel::setDisabledVersion(int value)
+{
+ disabledVersion = value;
+}
+
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h
new file mode 100644
index 0000000..bbae582
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h
@@ -0,0 +1,36 @@
+#ifndef SELECTABLEDBMODEL_H
+#define SELECTABLEDBMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QModelIndex>
+#include <QSortFilterProxyModel>
+#include <QVariant>
+
+class DbTreeItem;
+
+class GUI_API_EXPORT SelectableDbModel : public QSortFilterProxyModel
+{
+ public:
+ explicit SelectableDbModel(QObject *parent = 0);
+
+ QVariant data(const QModelIndex& index, int role) const;
+ bool setData(const QModelIndex& index, const QVariant& value, int role);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+
+ void setDatabases(const QStringList& databases);
+ QStringList getDatabases() const;
+
+ int getDisabledVersion() const;
+ void setDisabledVersion(int value);
+
+ protected:
+ bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const;
+
+ private:
+ DbTreeItem* getItemForProxyIndex(const QModelIndex& index) const;
+
+ QStringList checkedDatabases;
+ int disabledVersion = -1;
+};
+
+#endif // SELECTABLEDBMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp
new file mode 100644
index 0000000..75579d5
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp
@@ -0,0 +1,244 @@
+#include "selectabledbobjmodel.h"
+#include "dbtree/dbtreeitem.h"
+#include "dbtree/dbtreemodel.h"
+#include <QDebug>
+#include <QTreeView>
+
+SelectableDbObjModel::SelectableDbObjModel(QObject *parent) :
+ QSortFilterProxyModel(parent)
+{
+}
+
+QVariant SelectableDbObjModel::data(const QModelIndex& index, int role) const
+{
+ if (role != Qt::CheckStateRole)
+ return QSortFilterProxyModel::data(index, role);
+
+ return getStateFromChilds(index);
+}
+
+bool SelectableDbObjModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ if (role != Qt::CheckStateRole)
+ return QSortFilterProxyModel::setData(index, value, role);
+
+ Qt::CheckState checked = static_cast<Qt::CheckState>(value.toInt());
+ setRecurrently(index, checked);
+ emit dataChanged(index, index, {Qt::CheckStateRole});
+
+ return true;
+}
+
+Qt::ItemFlags SelectableDbObjModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
+ DbTreeItem* item = getItemForProxyIndex(index);
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ {
+ flags |= Qt::ItemIsUserCheckable;
+ if (index.child(0, 0).isValid())
+ flags |= Qt::ItemIsTristate;
+
+ break;
+ }
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return flags;
+}
+
+QString SelectableDbObjModel::getDbName() const
+{
+ return dbName;
+}
+
+void SelectableDbObjModel::setDbName(const QString& value)
+{
+ beginResetModel();
+ dbName = value;
+ checkedObjects.clear();
+ endResetModel();
+}
+QStringList SelectableDbObjModel::getCheckedObjects() const
+{
+ return checkedObjects.toList();
+}
+
+void SelectableDbObjModel::setCheckedObjects(const QStringList& value)
+{
+ checkedObjects = value.toSet();
+}
+
+void SelectableDbObjModel::setRootChecked(bool checked)
+{
+ QModelIndex idx = index(0, 0);
+ if (!idx.isValid())
+ return;
+
+ setData(idx, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
+}
+
+DbTreeItem* SelectableDbObjModel::getItemForIndex(const QModelIndex& index) const
+{
+ return getItemForProxyIndex(index);
+}
+
+bool SelectableDbObjModel::filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const
+{
+ QModelIndex idx = sourceModel()->index(srcRow, 0, srcParent);
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(dynamic_cast<DbTreeModel*>(sourceModel())->itemFromIndex(idx));
+ DbTreeItem* dbItem = item->getPathToParentItem(DbTreeItem::Type::DB).last();
+
+ // These 3 conditions could be written as one OR-ed, but this is easier to debug which one fails this way.
+ if (!dbItem)
+ return false;
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ return checkRecurrentlyForDb(item);
+
+ if (!dbItem->getDb())
+ return false;
+
+ if (dbItem->getDb()->getName() != dbName)
+ return false;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::DB:
+ return true;
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ return item->rowCount() > 0;
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ return false;
+ }
+ return false;
+}
+
+DbTreeItem* SelectableDbObjModel::getItemForProxyIndex(const QModelIndex& index) const
+{
+ QModelIndex srcIdx = mapToSource(index);
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(dynamic_cast<DbTreeModel*>(sourceModel())->itemFromIndex(srcIdx));
+ return item;
+}
+
+Qt::CheckState SelectableDbObjModel::getStateFromChilds(const QModelIndex& index) const
+{
+ DbTreeItem* item = getItemForProxyIndex(index);
+ if (!item)
+ return Qt::Unchecked;
+
+ if (!index.child(0, 0).isValid())
+ {
+ if (isObject(item) && checkedObjects.contains(item->text()))
+ return Qt::Checked;
+ else
+ return Qt::Unchecked;
+ }
+
+ int total = 0;
+ int checked = 0;
+ int partial = 0;
+ Qt::CheckState state;
+ QModelIndex child;
+ for (int i = 0; (child = index.child(i, 0)).isValid(); i++)
+ {
+ if (!child.flags().testFlag(Qt::ItemIsUserCheckable))
+ continue;
+
+ total++;
+ state = static_cast<Qt::CheckState>(child.data(Qt::CheckStateRole).toInt());
+ if (state == Qt::Checked || state == Qt::PartiallyChecked)
+ {
+ checked++;
+ if (state == Qt::PartiallyChecked)
+ partial++;
+ }
+ }
+
+ if (total == checked)
+ {
+ if (partial > 0)
+ return Qt::PartiallyChecked;
+ else
+ return Qt::Checked;
+ }
+
+ if (checked == 0)
+ {
+ if (isObject(item) && checkedObjects.contains(item->text()))
+ return Qt::PartiallyChecked;
+ else
+ return Qt::Unchecked;
+ }
+
+ return Qt::PartiallyChecked;
+}
+
+void SelectableDbObjModel::setRecurrently(const QModelIndex& index, Qt::CheckState checked)
+{
+ DbTreeItem* item = getItemForProxyIndex(index);
+ if (!item)
+ return;
+
+ if (checked && isObject(item))
+ checkedObjects << item->text();
+ else
+ checkedObjects.remove(item->text());
+
+ if (!index.child(0, 0).isValid())
+ return;
+
+ // Limiting checked to 'checked/unchecked', cause recurrent marking cannot set partially checked, it makes no sense
+ checked = (checked > 0 ? Qt::Checked : Qt::Unchecked);
+
+ QModelIndex child;
+ for (int i = 0; (child = index.child(i, 0)).isValid(); i++)
+ setData(child, checked, Qt::CheckStateRole);
+}
+
+bool SelectableDbObjModel::isObject(DbTreeItem* item) const
+{
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool SelectableDbObjModel::checkRecurrentlyForDb(DbTreeItem* item) const
+{
+ return item->findItem(DbTreeItem::Type::DB, dbName) != nullptr;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h
new file mode 100644
index 0000000..2fca1b4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h
@@ -0,0 +1,44 @@
+#ifndef SELECTABLEDBOBJMODEL_H
+#define SELECTABLEDBOBJMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QSortFilterProxyModel>
+#include <QSet>
+
+class DbTreeItem;
+class QTreeView;
+
+class GUI_API_EXPORT SelectableDbObjModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+ public:
+ explicit SelectableDbObjModel(QObject *parent = 0);
+
+ QVariant data(const QModelIndex& index, int role) const;
+ bool setData(const QModelIndex& index, const QVariant& value, int role);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+
+ QString getDbName() const;
+ void setDbName(const QString& value);
+
+ QStringList getCheckedObjects() const;
+ void setCheckedObjects(const QStringList& value);
+
+ void setRootChecked(bool checked);
+ DbTreeItem* getItemForIndex(const QModelIndex& index) const;
+
+ protected:
+ bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const;
+
+ private:
+ DbTreeItem* getItemForProxyIndex(const QModelIndex& index) const;
+ Qt::CheckState getStateFromChilds(const QModelIndex& index) const;
+ void setRecurrently(const QModelIndex& index, Qt::CheckState checked);
+ bool isObject(DbTreeItem* item) const;
+ bool checkRecurrentlyForDb(DbTreeItem* item) const;
+
+ QSet<QString> checkedObjects;
+ QString dbName;
+};
+
+#endif // SELECTABLEDBOBJMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp
new file mode 100644
index 0000000..0b5b91d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp
@@ -0,0 +1,143 @@
+#include "sqlcompareview.h"
+#include "sqlview.h"
+#include "common/utils.h"
+#include "diff/diff_match_patch.h"
+#include "sqlitesyntaxhighlighter.h"
+#include <QHeaderView>
+#include <QDebug>
+
+SqlCompareView::SqlCompareView(QWidget *parent) :
+ QTableWidget(parent)
+{
+ setColumnCount(2);
+ setVerticalScrollMode(ScrollPerPixel);
+ horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+ horizontalHeader()->setVisible(false);
+// verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ diff = new diff_match_patch;
+}
+
+void SqlCompareView::setSides(const QList<QPair<QString, QString>>& data)
+{
+ setRowCount(data.size());
+
+ int row = 0;
+ SqlView* leftView = nullptr;
+ SqlView* rightView = nullptr;
+ for (const QPair<QString, QString>& rowData : data)
+ {
+ leftView = new SqlView();
+ leftView->setFrameStyle(QFrame::NoFrame);
+ leftView->setPlainText(rowData.first);
+ setCellWidget(row, 0, leftView);
+
+ rightView = new SqlView();
+ rightView->setFrameStyle(QFrame::NoFrame);
+ rightView->setPlainText(rowData.second);
+ setCellWidget(row, 1, rightView);
+
+ setupHighlighting(rowData.first, rowData.second, leftView, rightView);
+
+ row++;
+ }
+ updateLabels();
+ updateSizes();
+}
+
+void SqlCompareView::setLeftLabel(const QString& label)
+{
+ leftLabel = label;
+}
+
+void SqlCompareView::setRightLabel(const QString& label)
+{
+ rightLabel = label;
+}
+
+void SqlCompareView::updateSizes()
+{
+ if (rowCount() == 0 || !isVisible())
+ return;
+
+ SqlView* view = dynamic_cast<SqlView*>(cellWidget(0, 0));
+ if (!view)
+ {
+ qCritical() << "Not a SqlView in SqlCompareView::updateSizes():" << cellWidget(0, 0);
+ return;
+ }
+
+ QFont font = view->font();
+ QFontMetrics fm(font);
+
+ int leftWidth = horizontalHeader()->sectionSize(0);
+ int rightWidth = horizontalHeader()->sectionSize(1);
+
+ SqlView* leftView = nullptr;
+ SqlView* rightView = nullptr;
+ QSize leftSize;
+ QSize rightSize;
+ for (int row = 0, total = rowCount(); row < total; ++row)
+ {
+ leftView = dynamic_cast<SqlView*>(cellWidget(row, 0));
+ leftView->document()->setTextWidth(leftWidth);
+
+ rightView = dynamic_cast<SqlView*>(cellWidget(row, 1));
+ rightView->document()->setTextWidth(rightWidth);
+
+ leftSize = QSize(leftWidth, leftView->document()->size().toSize().height());
+ rightSize = QSize(rightWidth, rightView->document()->size().toSize().height());
+ if (leftSize.height() > rightSize.height())
+ rightSize.setHeight(leftSize.height());
+ else
+ leftSize.setHeight(rightSize.height());
+
+ leftView->setFixedSize(leftSize);
+ rightView->setFixedSize(rightSize);
+ }
+ verticalHeader()->resizeSections(QHeaderView::ResizeToContents);
+}
+
+void SqlCompareView::updateLabels()
+{
+ setHorizontalHeaderLabels({leftLabel, rightLabel});
+}
+
+void SqlCompareView::setupHighlighting(const QString& left, const QString& right, SqlView* leftView, SqlView* rightView)
+{
+ QList<Diff> diffs = diff->diff_main(left, right);
+ int leftPos = 0;
+ int rightPos = 0;
+ int lgt = 0;
+ for (const Diff& d : diffs)
+ {
+ lgt = d.text.length();
+ switch (d.operation)
+ {
+ case DELETE:
+ leftView->setTextBackgroundColor(leftPos, leftPos + lgt - 1, Qt::red);
+ leftPos += lgt;
+ break;
+ case EQUAL:
+ leftPos += lgt;
+ rightPos += lgt;
+ break;
+ case INSERT:
+ rightView->setTextBackgroundColor(leftPos, leftPos + lgt - 1, Qt::green);
+ rightPos += lgt;
+ break;
+ }
+ }
+}
+
+void SqlCompareView::resizeEvent(QResizeEvent* e)
+{
+ QTableWidget::resizeEvent(e);
+ updateSizes();
+}
+
+void SqlCompareView::showEvent(QShowEvent* e)
+{
+ QTableWidget::showEvent(e);
+ updateSizes();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h
new file mode 100644
index 0000000..175e35a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h
@@ -0,0 +1,34 @@
+#ifndef SQLCOMPAREVIEW_H
+#define SQLCOMPAREVIEW_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QTableWidget>
+
+class SqlView;
+class diff_match_patch;
+class SqliteSyntaxHighlighter;
+
+class GUI_API_EXPORT SqlCompareView : public QTableWidget
+{
+ public:
+ explicit SqlCompareView(QWidget* parent = 0);
+
+ void setSides(const QList<QPair<QString,QString>>& data);
+ void setLeftLabel(const QString& label);
+ void setRightLabel(const QString& label);
+ void updateSizes();
+
+ protected:
+ void resizeEvent(QResizeEvent* e);
+ void showEvent(QShowEvent* e);
+
+ private:
+ void updateLabels();
+ void setupHighlighting(const QString& left, const QString& right, SqlView* leftView, SqlView* rightView);
+
+ QString leftLabel;
+ QString rightLabel;
+ diff_match_patch* diff = nullptr;
+};
+
+#endif // SQLCOMPAREVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp
new file mode 100644
index 0000000..52e1676
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp
@@ -0,0 +1,1521 @@
+#include "sqleditor.h"
+#include "uiconfig.h"
+#include "uiutils.h"
+#include "services/config.h"
+#include "iconmanager.h"
+#include "completer/completerwindow.h"
+#include "completionhelper.h"
+#include "common/utils_sql.h"
+#include "parser/lexer.h"
+#include "parser/parser.h"
+#include "parser/parsererror.h"
+#include "common/unused.h"
+#include "services/notifymanager.h"
+#include "dialogs/searchtextdialog.h"
+#include "dbobjectdialogs.h"
+#include "searchtextlocator.h"
+#include "services/codeformatter.h"
+#include "sqlitestudio.h"
+#include "dbtree/dbtreeitem.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include <QAction>
+#include <QMenu>
+#include <QTimer>
+#include <QDebug>
+#include <QKeyEvent>
+#include <QPainter>
+#include <QTextBlock>
+#include <QScrollBar>
+#include <QFileDialog>
+
+CFG_KEYS_DEFINE(SqlEditor)
+
+SqlEditor::SqlEditor(QWidget *parent) :
+ QPlainTextEdit(parent)
+{
+ init();
+}
+
+SqlEditor::~SqlEditor()
+{
+ if (queryParser)
+ {
+ delete queryParser;
+ queryParser = nullptr;
+ }
+}
+
+void SqlEditor::init()
+{
+ highlighter = new SqliteSyntaxHighlighter(document());
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+ initActions();
+ setupMenu();
+
+ textLocator = new SearchTextLocator(document(), this);
+ connect(textLocator, SIGNAL(found(int,int)), this, SLOT(found(int,int)));
+ connect(textLocator, SIGNAL(reachedEnd()), this, SLOT(reachedEnd()));
+
+ lineNumberArea = new LineNumberArea(this);
+
+ connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth()));
+ connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
+ connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
+ connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorMoved()));
+
+ updateLineNumberAreaWidth();
+ highlightCurrentLine();
+
+ completer = new CompleterWindow(this);
+ connect(completer, SIGNAL(accepted()), this, SLOT(completeSelected()));
+ connect(completer, SIGNAL(textTyped(QString)), this, SLOT(completerTypedText(QString)));
+ connect(completer, SIGNAL(backspacePressed()), this, SLOT(completerBackspacePressed()));
+ connect(completer, SIGNAL(leftPressed()), this, SLOT(completerLeftPressed()));
+ connect(completer, SIGNAL(rightPressed()), this, SLOT(completerRightPressed()));
+
+ autoCompleteTimer = new QTimer(this);
+ autoCompleteTimer->setSingleShot(true);
+ autoCompleteTimer->setInterval(autoCompleterDelay);
+ connect(autoCompleteTimer, SIGNAL(timeout()), this, SLOT(checkForAutoCompletion()));
+
+ queryParserTimer = new QTimer(this);
+ queryParserTimer->setSingleShot(true);
+ queryParserTimer->setInterval(queryParserDelay);
+ connect(queryParserTimer, SIGNAL(timeout()), this, SLOT(parseContents()));
+ connect(this, SIGNAL(textChanged()), this, SLOT(scheduleQueryParser()));
+
+ queryParser = new Parser(Dialect::Sqlite3);
+
+ connect(this, &QWidget::customContextMenuRequested, this, &SqlEditor::customContextMenuRequested);
+ connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
+ connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(configModified()));
+}
+
+void SqlEditor::removeErrorMarkers()
+{
+ highlighter->clearErrors();
+}
+
+bool SqlEditor::haveErrors()
+{
+ return highlighter->haveErrors();
+}
+
+bool SqlEditor::isSyntaxChecked()
+{
+ return syntaxValidated;
+}
+
+void SqlEditor::markErrorAt(int start, int end, bool limitedDamage)
+{
+ highlighter->addError(start, end, limitedDamage);
+}
+
+void SqlEditor::createActions()
+{
+ createAction(CUT, ICONS.ACT_CUT, tr("Cut", "sql editor"), this, SLOT(cut()), this);
+ createAction(COPY, ICONS.ACT_COPY, tr("Copy", "sql editor"), this, SLOT(copy()), this);
+ createAction(PASTE, ICONS.ACT_PASTE, tr("Paste", "sql editor"), this, SLOT(paste()), this);
+ createAction(DELETE, ICONS.ACT_DELETE, tr("Delete", "sql editor"), this, SLOT(deleteSelected()), this);
+ createAction(SELECT_ALL, ICONS.ACT_SELECT_ALL, tr("Select all", "sql editor"), this, SLOT(selectAll()), this);
+ createAction(UNDO, ICONS.ACT_UNDO, tr("Undo", "sql editor"), this, SLOT(undo()), this);
+ createAction(REDO, ICONS.ACT_REDO, tr("Redo", "sql editor"), this, SLOT(redo()), this);
+ createAction(COMPLETE, ICONS.COMPLETE, tr("Complete", "sql editor"), this, SLOT(complete()), this);
+ createAction(FORMAT_SQL, ICONS.FORMAT_SQL, tr("Format SQL", "sql editor"), this, SLOT(formatSql()), this);
+ createAction(SAVE_SQL_FILE, ICONS.SAVE_SQL_FILE, tr("Save SQL to file", "sql editor"), this, SLOT(saveToFile()), this);
+ createAction(OPEN_SQL_FILE, ICONS.OPEN_SQL_FILE, tr("Load SQL from file", "sql editor"), this, SLOT(loadFromFile()), this);
+ createAction(DELETE_LINE, ICONS.ACT_DEL_LINE, tr("Delete line", "sql editor"), this, SLOT(deleteLine()), this);
+ createAction(MOVE_BLOCK_DOWN, tr("Move block down", "sql editor"), this, SLOT(moveBlockDown()), this);
+ createAction(MOVE_BLOCK_UP, tr("Move block up", "sql editor"), this, SLOT(moveBlockUp()), this);
+ createAction(COPY_BLOCK_DOWN, tr("Copy block down", "sql editor"), this, SLOT(copyBlockDown()), this);
+ createAction(COPY_BLOCK_UP, tr("Copy up down", "sql editor"), this, SLOT(copyBlockUp()), this);
+ createAction(FIND, ICONS.ACT_SEARCH, tr("Find", "sql editor"), this, SLOT(find()), this);
+ createAction(FIND_NEXT, tr("Find next", "sql editor"), this, SLOT(findNext()), this);
+ createAction(FIND_PREV, tr("Find previous", "sql editor"), this, SLOT(findPrevious()), this);
+ createAction(REPLACE, tr("Replace", "sql editor"), this, SLOT(replace()), this);
+
+ actionMap[CUT]->setEnabled(false);
+ actionMap[COPY]->setEnabled(false);
+ actionMap[UNDO]->setEnabled(false);
+ actionMap[REDO]->setEnabled(false);
+ actionMap[DELETE]->setEnabled(false);
+
+ connect(this, &QPlainTextEdit::undoAvailable, this, &SqlEditor::updateUndoAction);
+ connect(this, &QPlainTextEdit::redoAvailable, this, &SqlEditor::updateRedoAction);
+ connect(this, &QPlainTextEdit::copyAvailable, this, &SqlEditor::updateCopyAction);
+}
+
+void SqlEditor::setupDefShortcuts()
+{
+ setShortcutContext({CUT, COPY, PASTE, DELETE, SELECT_ALL, UNDO, REDO, COMPLETE, FORMAT_SQL, SAVE_SQL_FILE, OPEN_SQL_FILE,
+ DELETE_LINE}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(SqlEditor, Action);
+}
+
+void SqlEditor::setupMenu()
+{
+ contextMenu = new QMenu(this);
+ contextMenu->addAction(actionMap[FORMAT_SQL]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[SAVE_SQL_FILE]);
+ contextMenu->addAction(actionMap[OPEN_SQL_FILE]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[UNDO]);
+ contextMenu->addAction(actionMap[REDO]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[FIND]);
+ contextMenu->addAction(actionMap[CUT]);
+ contextMenu->addAction(actionMap[COPY]);
+ contextMenu->addAction(actionMap[PASTE]);
+ contextMenu->addAction(actionMap[DELETE]);
+ contextMenu->addSeparator();
+ contextMenu->addAction(actionMap[SELECT_ALL]);
+
+ validObjContextMenu = new QMenu(this);
+}
+
+Db* SqlEditor::getDb() const
+{
+ return db;
+}
+
+void SqlEditor::setDb(Db* value)
+{
+ db = value;
+ scheduleQueryParser(true);
+}
+
+void SqlEditor::setAutoCompletion(bool enabled)
+{
+ autoCompletion = enabled;
+}
+
+void SqlEditor::customContextMenuRequested(const QPoint &pos)
+{
+ if (objectLinksEnabled)
+ {
+ const DbObject* obj = getValidObjectForPosition(pos);
+ QString objName = toPlainText().mid(obj->from, (obj->to - obj->from + 1));
+
+ validObjContextMenu->clear();
+
+ DbTreeItem* item = nullptr;
+ for (DbTreeItem::Type type : {DbTreeItem::Type::TABLE, DbTreeItem::Type::INDEX, DbTreeItem::Type::TRIGGER, DbTreeItem::Type::VIEW})
+ {
+ item = DBTREE->getModel()->findItem(type, objName);
+ if (item)
+ break;
+ }
+
+ if (item)
+ {
+ DBTREE->setSelectedItem(item);
+ DBTREE->setupActionsForMenu(item, validObjContextMenu);
+ if (validObjContextMenu->actions().size() == 0)
+ return;
+
+ DBTREE->updateActionStates(item);
+ validObjContextMenu->popup(mapToGlobal(pos));
+ }
+ return;
+ }
+ contextMenu->popup(mapToGlobal(pos));
+}
+
+void SqlEditor::updateUndoAction(bool enabled)
+{
+ actionMap[UNDO]->setEnabled(enabled);
+}
+
+void SqlEditor::updateRedoAction(bool enabled)
+{
+ actionMap[REDO]->setEnabled(enabled);
+}
+
+void SqlEditor::updateCopyAction(bool enabled)
+{
+ actionMap[COPY]->setEnabled(enabled);
+ actionMap[CUT]->setEnabled(enabled);
+ actionMap[DELETE]->setEnabled(enabled);
+}
+
+void SqlEditor::deleteSelected()
+{
+ textCursor().removeSelectedText();
+}
+
+void SqlEditor::homePressed(Qt::KeyboardModifiers modifiers)
+{
+ QTextCursor cursor = textCursor();
+
+ bool shift = modifiers.testFlag(Qt::ShiftModifier);
+ QTextCursor::MoveMode mode = shift ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
+ if (modifiers.testFlag(Qt::ControlModifier))
+ {
+ cursor.setPosition(0, mode);
+ setTextCursor(cursor);
+ return;
+ }
+
+ int curPos = cursor.positionInBlock();
+ QString line = cursor.block().text();
+ int firstPrintable = line.indexOf(QRegExp("\\S"));
+
+ if (firstPrintable <= 0)
+ {
+ // If first printable character is the first character in line,
+ // or there's no printable characters at all, move to start of line.
+ cursor.movePosition(QTextCursor::StartOfLine, mode);
+ }
+ else if (curPos == 0 || curPos < firstPrintable)
+ {
+ // If cursor is at the line begining, or it's still before first printable character.
+ // Move to first printable character.
+ cursor.movePosition(QTextCursor::NextWord, mode);
+ }
+ else if (curPos == firstPrintable)
+ {
+ // If cursor is already at first printable character, now is the time to move it
+ // to start of the line.
+ cursor.movePosition(QTextCursor::StartOfLine, mode);
+ }
+ else
+ {
+ // Cursor is somewhere in the middle of printable text. Move it to the begining
+ // of line and then to first printable character.
+ cursor.movePosition(QTextCursor::StartOfLine, mode);
+ cursor.movePosition(QTextCursor::NextWord, mode);
+ }
+
+ setTextCursor(cursor);
+}
+
+void SqlEditor::tabPressed(bool shiftPressed)
+{
+ QTextCursor cursor = textCursor();
+ if (cursor.hasSelection())
+ {
+ indentSelected(shiftPressed);
+ return;
+ }
+
+ // Get current line, its first printable character
+ int curPos = cursor.positionInBlock();
+ QString line = cursor.block().text();
+ int firstPrintable = line.indexOf(QRegExp("\\S"));
+
+ // Handle shift+tab (unindent)
+ if (shiftPressed)
+ {
+ cursor.movePosition(QTextCursor::StartOfLine);
+
+ if (firstPrintable > 0)
+ cursor.movePosition(QTextCursor::NextWord);
+
+ setTextCursor(cursor);
+ backspacePressed();
+ return;
+ }
+
+ // If we're past any printable character (and there was any), insert a tab
+ if (curPos > firstPrintable && firstPrintable >= 0)
+ {
+ insertPlainText(" ");
+ return;
+ }
+
+ // If there is no previous block to refer to, insert a tab
+ QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1);
+ if (!previousBlock.isValid())
+ {
+ insertPlainText(" ");
+ return;
+ }
+
+ // If previous block has first pritable character further than current cursor position, insert spaces to meet above position
+ int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S"));
+ if (curPos < previousFirstPrintable)
+ {
+ insertPlainText(QString(" ").repeated(previousFirstPrintable - curPos));
+ return;
+ }
+
+ // At this point we know that previous block don't have first printable character further than the cursor. Insert tab.
+ insertPlainText(" ");
+}
+
+void SqlEditor::backspacePressed()
+{
+ // If we have any selection, delete it and that's all.
+ QTextCursor cursor = textCursor();
+ if (cursor.hasSelection())
+ {
+ deleteSelected();
+ return;
+ }
+
+ // No selection. Collect line, cursor position, first and last printable characters in line.
+ int curPos = cursor.positionInBlock();
+ QString line = cursor.block().text();
+ int firstPrintable = line.indexOf(QRegExp("\\S"));
+
+ // If there is any printable character (which means that line length is greater than 0) and cursor is after first character,
+ // or when cursor is at the begining of line, delete previous character, always.
+ if ((firstPrintable > -1 && curPos > firstPrintable) || curPos == 0)
+ {
+ cursor.deletePreviousChar();
+ return;
+ }
+
+ // Define number of spaces available for deletion.
+ int spaces = firstPrintable;
+ if (spaces < 0)
+ spaces = curPos;
+
+ // Get previous block. If there was none, then delete up to 4 previous spaces.
+ QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1);
+ if (!previousBlock.isValid())
+ {
+ doBackspace(spaces < 4 ? spaces : 4);
+ return;
+ }
+
+ // If first printable character in previous block is prior to the current cursor position (but not first in the line),
+ // delete as many spaces, as necessary to reach the same position, but never more than defined spaces number earlier.
+ int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S"));
+ if (curPos > previousFirstPrintable && previousFirstPrintable > 0)
+ {
+ int spacesToDelete = curPos - previousFirstPrintable;
+ doBackspace(spaces < spacesToDelete ? spaces : spacesToDelete);
+ return;
+ }
+
+ // There is no character to back off to, so we simply delete up to 4 previous spaces.
+ doBackspace(spaces < 4 ? spaces : 4);
+}
+
+void SqlEditor::complete()
+{
+ if (!db || !db->isValid())
+ {
+ notifyWarn(tr("Syntax completion can be used only when a valid database is set for the SQL editor."));
+ return;
+ }
+
+ QString sql = toPlainText();
+ int curPos = textCursor().position();
+
+ if (!virtualSqlExpression.isNull())
+ {
+ sql = virtualSqlExpression.arg(sql);
+ curPos += virtualSqlOffset;
+ }
+
+ CompletionHelper completionHelper(sql, curPos, db);
+ completionHelper.setCreateTriggerTable(createTriggerTable);
+ CompletionHelper::Results result = completionHelper.getExpectedTokens();
+ if (result.filtered().size() == 0)
+ return;
+
+ completer->setData(result);
+ completer->setDb(db);
+ if (completer->immediateResolution())
+ return;
+
+ updateCompleterPosition();
+ completer->show();
+}
+
+void SqlEditor::updateCompleterPosition()
+{
+ QPoint pos = cursorRect().bottomRight();
+ pos += QPoint(1, fontMetrics().descent());
+ completer->move(mapToGlobal(pos));
+}
+
+void SqlEditor::completeSelected()
+{
+ deletePreviousChars(completer->getNumberOfCharsToRemove());
+
+ ExpectedTokenPtr token = completer->getSelected();
+ QString value = token->value;
+ if (token->needsWrapping())
+ value = wrapObjIfNeeded(value, getDialect());
+
+ if (!token->prefix.isNull())
+ {
+ value.prepend(".");
+ value.prepend(wrapObjIfNeeded(token->prefix, getDialect()));
+ }
+
+ insertPlainText(value);
+}
+
+void SqlEditor::scheduleAutoCompletion()
+{
+ autoCompleteTimer->stop();
+
+ if (autoCompletion && !deletionKeyPressed)
+ autoCompleteTimer->start();
+}
+
+void SqlEditor::checkForAutoCompletion()
+{
+ if (!db || !autoCompletion || deletionKeyPressed)
+ return;
+
+ Lexer lexer(getDialect());
+ QString sql = toPlainText();
+ int curPos = textCursor().position();
+ TokenList tokens = lexer.tokenize(sql.left(curPos));
+
+ if (tokens.size() > 0 && tokens.last()->type == Token::OPERATOR && tokens.last()->value == ".")
+ complete();
+}
+
+void SqlEditor::deletePreviousChars(int length)
+{
+ QTextCursor cursor = textCursor();
+ for (int i = 0; i < length; i++)
+ cursor.deletePreviousChar();
+}
+
+void SqlEditor::refreshValidObjects()
+{
+ if (!db || !db->isValid())
+ return;
+
+ objectsInNamedDb.clear();
+
+ SchemaResolver resolver(db);
+ QSet<QString> databases = resolver.getDatabases();
+ databases << "main";
+ QStringList objects;
+ foreach (const QString& dbName, databases)
+ {
+ objects = resolver.getAllObjects();
+ objectsInNamedDb[dbName] << objects;
+ }
+}
+
+Dialect SqlEditor::getDialect()
+{
+ return !db ? Dialect::Sqlite3 : db->getDialect();
+}
+
+void SqlEditor::setObjectLinks(bool enabled)
+{
+ objectLinksEnabled = enabled;
+ setMouseTracking(enabled);
+ highlighter->setObjectLinksEnabled(enabled);
+ highlighter->rehighlight();
+
+ if (enabled)
+ handleValidObjectCursor(mapFromGlobal(QCursor::pos()));
+ else
+ viewport()->setCursor(Qt::IBeamCursor);
+}
+
+void SqlEditor::addDbObject(int from, int to, const QString& dbName)
+{
+ validDbObjects << DbObject(from, to, dbName);
+ highlighter->addDbObject(from, to);
+}
+
+void SqlEditor::clearDbObjects()
+{
+ validDbObjects.clear();
+ highlighter->clearDbObjects();
+}
+
+void SqlEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
+{
+ QPainter painter(lineNumberArea);
+ painter.fillRect(event->rect(), CFG_UI.Colors.SqlEditorLineNumAreaBg.get());
+ QTextBlock block = firstVisibleBlock();
+ int blockNumber = block.blockNumber();
+ int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
+ int bottom = top + (int) blockBoundingRect(block).height();
+ while (block.isValid() && top <= event->rect().bottom())
+ {
+ if (block.isVisible() && bottom >= event->rect().top())
+ {
+ QString number = QString::number(blockNumber + 1);
+ painter.setPen(Qt::black);
+ painter.drawText(0, top, lineNumberArea->width()-2, fontMetrics().height(), Qt::AlignRight, number);
+ }
+
+ block = block.next();
+ top = bottom;
+ bottom = top + (int) blockBoundingRect(block).height();
+ blockNumber++;
+ }
+}
+
+int SqlEditor::lineNumberAreaWidth()
+{
+ int digits = 1;
+ int max = qMax(1, document()->blockCount());
+ while (max >= 10)
+ {
+ max /= 10;
+ digits++;
+ }
+
+ int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
+ return space;
+}
+
+void SqlEditor::highlightParenthesis()
+{
+ // Clear extra selections
+ QList<QTextEdit::ExtraSelection> selections = extraSelections();
+
+ // Just keep "current line" highlighting
+ QMutableListIterator<QTextEdit::ExtraSelection> it(selections);
+ while (it.hasNext())
+ {
+ if (!it.next().format.property(QTextFormat::FullWidthSelection).toBool())
+ it.remove();
+ }
+ setExtraSelections(selections);
+
+ // Find out parenthesis under the cursor
+ int curPos = textCursor().position();
+ TextBlockData* data = dynamic_cast<TextBlockData*>(textCursor().block().userData());
+ if (!data)
+ return;
+
+ const TextBlockData::Parenthesis* parOnRight = data->parenthesisForPosision(curPos);
+ const TextBlockData::Parenthesis* parOnLeft = data->parenthesisForPosision(curPos - 1);
+ const TextBlockData::Parenthesis* thePar = parOnRight;
+ if (parOnLeft && !parOnRight) // go with parenthesis on left only when there's no parenthesis on right
+ thePar = parOnLeft;
+
+ if (!thePar)
+ return;
+
+ // Getting all parenthesis in the entire document
+ QList<const TextBlockData::Parenthesis*> allParenthesis;
+ for (QTextBlock block = document()->begin(); block.isValid(); block = block.next())
+ {
+ data = dynamic_cast<TextBlockData*>(block.userData());
+ if (!data)
+ continue;
+
+ allParenthesis += data->parentheses();
+ }
+
+ // Matching the parenthesis
+ const TextBlockData::Parenthesis* matchedPar = matchParenthesis(allParenthesis, thePar);
+ if (!matchedPar)
+ return;
+
+ // Mark new match
+ markMatchedParenthesis(thePar->position, matchedPar->position, selections);
+ setExtraSelections(selections);
+}
+
+void SqlEditor::markMatchedParenthesis(int pos1, int pos2, QList<QTextEdit::ExtraSelection>& selections)
+{
+ QTextEdit::ExtraSelection selection;
+
+ selection.format.setBackground(CFG_UI.Colors.SqlEditorParenthesisBg.get());
+
+ QTextCursor cursor = textCursor();
+
+ cursor.setPosition(pos1);
+ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+ selection.cursor = cursor;
+ selections.append(selection);
+
+ cursor.setPosition(pos2);
+ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+ selection.cursor = cursor;
+ selections.append(selection);
+}
+
+void SqlEditor::doBackspace(int repeats)
+{
+ QTextCursor cursor = textCursor();
+ for (int i = 0; i < repeats; i++)
+ cursor.deletePreviousChar();
+}
+
+void SqlEditor::indentSelected(bool shiftPressed)
+{
+ QTextCursor cursor = textCursor();
+ QTextDocument* doc = document();
+ QTextBlock startBlock = doc->findBlock(cursor.selectionStart());
+ QTextBlock endBlock = doc->findBlock(cursor.selectionEnd());
+ for (QTextBlock it = startBlock; it != endBlock; it = it.next())
+ {
+ if (shiftPressed)
+ unindentBlock(it);
+ else
+ indentBlock(it);
+ }
+}
+
+void SqlEditor::indentBlock(const QTextBlock& block)
+{
+ QTextCursor cursor = textCursor();
+ cursor.setPosition(block.position());
+ cursor.insertText(" ");
+}
+
+void SqlEditor::unindentBlock(const QTextBlock& block)
+{
+ QString str = block.text();
+ if (!str.startsWith(" "))
+ return;
+
+ int spaces = 0;
+ int firstPrintable = str.indexOf(QRegExp("\\S"));
+ if (firstPrintable == -1)
+ spaces = str.length();
+ else
+ spaces = firstPrintable;
+
+ QTextCursor cursor = textCursor();
+ cursor.setPosition(block.position());
+ for (int i = 0; i < 4 && i < spaces; i++)
+ cursor.deleteChar();
+}
+
+void SqlEditor::indentNewLine()
+{
+ QTextCursor cursor = textCursor();
+
+ // If there is no previous block to refer to, do nothing
+ QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1);
+ if (!previousBlock.isValid())
+ return;
+
+ // If previous block has first pritable character further than current cursor position, insert spaces to meet above position
+ int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S"));
+ if (previousFirstPrintable > 0)
+ {
+ insertPlainText(QString(" ").repeated(previousFirstPrintable));
+ return;
+ }
+
+}
+
+void SqlEditor::showSearchDialog()
+{
+ if (!searchDialog)
+ {
+ searchDialog = new SearchTextDialog(textLocator, this);
+ searchDialog->setWindowTitle(tr("Find or replace", "sql editor find/replace dialog"));
+ }
+
+ if (searchDialog->isVisible())
+ searchDialog->hide();
+
+ searchDialog->show();
+}
+
+const TextBlockData::Parenthesis* SqlEditor::matchParenthesis(QList<const TextBlockData::Parenthesis*> parList,
+ const TextBlockData::Parenthesis* thePar)
+{
+ bool matchLeftPar = (thePar->character == ')');
+ char parToMatch = matchLeftPar ? '(' : ')';
+ int parListSize = parList.size();
+ int theParIdx = parList.indexOf(thePar);
+ int counter = 0;
+ for (int i = theParIdx; (matchLeftPar ? i >= 0 : i < parListSize); (matchLeftPar ? i-- : i++))
+ {
+ if (parList[i]->character == parToMatch)
+ counter--;
+ else
+ counter++;
+
+ if (counter == 0)
+ return parList[i];
+ }
+ return nullptr;
+}
+
+void SqlEditor::completerTypedText(const QString& text)
+{
+ insertPlainText(text);
+ completer->extendFilterBy(text);
+ updateCompleterPosition();
+}
+
+void SqlEditor::completerBackspacePressed()
+{
+ deletionKeyPressed = true;
+ textCursor().deletePreviousChar();
+ completer->shringFilterBy(1);
+ updateCompleterPosition();
+ deletionKeyPressed = false;
+}
+
+void SqlEditor::completerLeftPressed()
+{
+ completer->shringFilterBy(1);
+ moveCursor(QTextCursor::Left);
+ updateCompleterPosition();
+}
+
+void SqlEditor::completerRightPressed()
+{
+ // Last character seems to be virtual in QPlainTextEdit, so that QTextCursor can be at its position
+ int charCnt = document()->characterCount() - 1;
+ int curPos = textCursor().position();
+
+ if (curPos >= charCnt)
+ {
+ completer->reject();
+ return;
+ }
+
+ QChar c = document()->characterAt(curPos);
+ if (!c.isNull())
+ completer->extendFilterBy(QString(c));
+
+ moveCursor(QTextCursor::Right);
+ updateCompleterPosition();
+}
+
+void SqlEditor::parseContents()
+{
+ if (document()->characterCount() > 100000)
+ {
+ if (richFeaturesEnabled)
+ notifyWarn(tr("Contents of the SQL editor are huge, so errors detecting and existing objects highlighting are temporarily disabled."));
+
+ richFeaturesEnabled = false;
+ return;
+ }
+ else if (!richFeaturesEnabled)
+ {
+ richFeaturesEnabled = true;
+ }
+
+ // Updating dialect according to current database (if any)
+ Dialect dialect = Dialect::Sqlite3;
+ if (db && db->isValid())
+ dialect = db->getDialect();
+
+ QString sql = toPlainText();
+ if (!virtualSqlExpression.isNull())
+ {
+ if (virtualSqlCompleteSemicolon && !sql.trimmed().endsWith(";"))
+ sql += ";";
+
+ sql = virtualSqlExpression.arg(sql);
+ }
+
+ queryParser->setDialect(dialect);
+ queryParser->parse(sql);
+
+ checkForValidObjects();
+ checkForSyntaxErrors();
+ highlighter->rehighlight();
+}
+
+void SqlEditor::checkForSyntaxErrors()
+{
+ syntaxValidated = true;
+
+ removeErrorMarkers();
+
+ // Marking invalid tokens, like in "SELECT * from test] t" - the "]" token is invalid.
+ // Such tokens don't cause parser to fail.
+ foreach (SqliteQueryPtr query, queryParser->getQueries())
+ {
+ foreach (TokenPtr token, query->tokens)
+ {
+ if (token->type == Token::INVALID)
+ markErrorAt(token->start, token->end, true);
+ }
+ }
+
+ if (queryParser->isSuccessful())
+ {
+ emit errorsChecked(false);
+ return;
+ }
+
+ // Setting new markers when errors were detected
+ foreach (ParserError* error, queryParser->getErrors())
+ markErrorAt(sqlIndex(error->getFrom()), sqlIndex(error->getTo()));
+
+ emit errorsChecked(true);
+}
+
+void SqlEditor::checkForValidObjects()
+{
+ clearDbObjects();
+ if (!db || !db->isValid())
+ return;
+
+ Dialect dialect = db->getDialect();
+ QList<SqliteStatement::FullObject> fullObjects;
+ QString dbName;
+ foreach (SqliteQueryPtr query, queryParser->getQueries())
+ {
+ fullObjects = query->getContextFullObjects();
+ foreach (const SqliteStatement::FullObject& fullObj, fullObjects)
+ {
+ dbName = fullObj.database ? stripObjName(fullObj.database->value, dialect) : "main";
+ if (!objectsInNamedDb.contains(dbName))
+ continue;
+
+ if (fullObj.type == SqliteStatement::FullObject::DATABASE)
+ {
+ // Valid db name
+ addDbObject(sqlIndex(fullObj.database->start), sqlIndex(fullObj.database->end), QString::null);
+ continue;
+ }
+
+ if (!objectsInNamedDb[dbName].contains(stripObjName(fullObj.object->value, dialect)))
+ continue;
+
+ // Valid object name
+ addDbObject(sqlIndex(fullObj.object->start), sqlIndex(fullObj.object->end), dbName);
+ }
+ }
+}
+
+void SqlEditor::scheduleQueryParser(bool force)
+{
+ if (!document()->isModified() && !force)
+ return;
+
+ syntaxValidated = false;
+
+ document()->setModified(false);
+ queryParserTimer->stop();
+ queryParserTimer->start();
+
+ scheduleAutoCompletion();
+}
+
+int SqlEditor::sqlIndex(int idx)
+{
+ if (virtualSqlExpression.isNull())
+ return idx;
+
+ if (idx < virtualSqlOffset)
+ return virtualSqlOffset;
+
+ idx -= virtualSqlOffset;
+
+ int lastIdx = toPlainText().length() - 1;
+ if (idx > lastIdx)
+ return lastIdx;
+
+ return idx;
+}
+
+void SqlEditor::updateLineNumberArea()
+{
+ updateLineNumberArea(viewport()->rect(), viewport()->y());
+}
+
+bool SqlEditor::hasSelection() const
+{
+ return textCursor().hasSelection();
+}
+
+void SqlEditor::replaceSelectedText(const QString &newText)
+{
+ textCursor().insertText(newText);
+}
+
+QString SqlEditor::getSelectedText() const
+{
+ QString txt = textCursor().selectedText();
+ fixTextCursorSelectedText(txt);
+ return txt;
+}
+
+void SqlEditor::openObject(const QString& database, const QString& name)
+{
+ DbObjectDialogs dialogs(db);
+ dialogs.editObject(database, name);
+}
+
+void SqlEditor::updateLineNumberAreaWidth()
+{
+ if (!showLineNumbers)
+ {
+ setViewportMargins(0, 0, 0, 0);
+ return;
+ }
+
+ setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
+}
+
+void SqlEditor::highlightCurrentLine()
+{
+ QList<QTextEdit::ExtraSelection> selections;
+
+ if (!isReadOnly() && isEnabled())
+ {
+ QTextEdit::ExtraSelection selection;
+
+ selection.format.setBackground(CFG_UI.Colors.SqlEditorCurrentLineBg.get());
+ selection.format.setProperty(QTextFormat::FullWidthSelection, true);
+ selection.cursor = textCursor();
+ selection.cursor.clearSelection();
+ selections.append(selection);
+ }
+
+ setExtraSelections(selections);
+}
+
+void SqlEditor::updateLineNumberArea(const QRect& rect, int dy)
+{
+ if (!showLineNumbers)
+ {
+ updateLineNumberAreaWidth();
+ return;
+ }
+
+ if (dy)
+ lineNumberArea->scroll(0, dy);
+ else
+ lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
+
+ if (rect.contains(viewport()->rect()))
+ updateLineNumberAreaWidth();
+}
+
+void SqlEditor::cursorMoved()
+{
+ highlightParenthesis();
+
+ if (!cursorMovingByLocator)
+ {
+ textLocator->setStartPosition(textCursor().position());
+ textLocator->cursorMoved();
+ }
+}
+
+void SqlEditor::formatSql()
+{
+ QString sql = hasSelection() ? getSelectedText() : toPlainText();
+ sql = SQLITESTUDIO->getCodeFormatter()->format("sql", sql, db);
+
+ if (!hasSelection())
+ selectAll();
+
+ replaceSelectedText(sql);
+}
+
+void SqlEditor::saveToFile()
+{
+ QString dir = getFileDialogInitPath();
+ QString fName = QFileDialog::getSaveFileName(this, tr("Save to file"), dir);
+ if (fName.isNull())
+ return;
+
+ setFileDialogInitPathByFile(fName);
+
+ QFile file(fName);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ notifyError(tr("Could not open file '%1'' for writing: %2").arg(fName).arg(file.errorString()));
+ return;
+ }
+
+ QTextStream stream(&file);
+ stream.setCodec("UTF-8");
+ stream << toPlainText();
+ stream.flush();
+ file.close();
+}
+
+void SqlEditor::loadFromFile()
+{
+ QString dir = getFileDialogInitPath();
+ QString filters = tr("SQL scripts (*.sql);;All files (*)");
+ QString fName = QFileDialog::getOpenFileName(this, tr("Open file"), dir, filters);
+ if (fName.isNull())
+ return;
+
+ setFileDialogInitPathByFile(fName);
+
+ QFile file(fName);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ notifyError(tr("Could not open file '%1'' for reading: %2").arg(fName).arg(file.errorString()));
+ return;
+ }
+
+ QTextStream stream(&file);
+ stream.setCodec("UTF-8");
+ QString sql = stream.readAll();
+ file.close();
+ setPlainText(sql);
+}
+
+void SqlEditor::deleteLine()
+{
+ QTextCursor cursor = textCursor();
+ if (cursor.hasSelection())
+ deleteSelectedLines();
+ else
+ deleteCurrentLine();
+}
+
+void SqlEditor::deleteCurrentLine()
+{
+ QTextCursor cursor = textCursor();
+ cursor.movePosition(QTextCursor::StartOfLine);
+ cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+
+ QTextDocument* doc = document();
+ QTextBlock block = doc->findBlock(cursor.position());
+ if (block.next().isValid())
+ cursor.deleteChar();
+ else
+ {
+ cursor.deletePreviousChar();
+ cursor.movePosition(QTextCursor::StartOfLine);
+ }
+ setTextCursor(cursor);
+}
+
+void SqlEditor::deleteSelectedLines()
+{
+ QTextCursor cursor = textCursor();
+ QTextDocument* doc = document();
+ QTextBlock startBlock = doc->findBlock(cursor.selectionStart());
+ QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1);
+ int idxMod = 0;
+ if (!endBlock.next().isValid()) // no newline at the end
+ idxMod = -1;
+
+ cursor.setPosition(startBlock.position());
+ cursor.setPosition(endBlock.position() + endBlock.length() + idxMod, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+}
+void SqlEditor::moveBlockDown(bool deleteOld)
+{
+ QTextCursor cursor = textCursor();
+ if (!cursor.hasSelection())
+ {
+ cursor.movePosition(QTextCursor::StartOfLine);
+ cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
+ }
+
+ QTextDocument* doc = document();
+ QTextBlock startBlock = doc->findBlock(cursor.selectionStart());
+ QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1);
+
+ QTextBlock nextBlock = endBlock.next();
+ QTextBlock blockBeforeNewText = endBlock;
+
+ // When moving text, we next block to be valid and operate on one after that
+ if (deleteOld)
+ {
+ if (!nextBlock.isValid())
+ return;
+
+ blockBeforeNewText = nextBlock;
+ nextBlock = nextBlock.next();
+ }
+
+ // If next block is invalid, we need to create it
+ bool removeLastNewLine = false;
+ if (!nextBlock.isValid())
+ {
+ cursor.setPosition(blockBeforeNewText.position());
+ cursor.movePosition(QTextCursor::EndOfLine);
+ cursor.insertBlock();
+ nextBlock = blockBeforeNewText.next();
+ removeLastNewLine = true;
+ }
+
+ int textLength = endBlock.position() + endBlock.length() - startBlock.position();
+
+ // Collecting text and removing text from old position (if not copying)
+ cursor.setPosition(startBlock.position());
+ cursor.setPosition(startBlock.position() + textLength, QTextCursor::KeepAnchor);
+ QString text = cursor.selectedText();
+ fixTextCursorSelectedText(text);
+ if (deleteOld) // this is false when just copying
+ cursor.removeSelectedText();
+
+ // Pasting text at new position and reselecting it
+ cursor.setPosition(nextBlock.position());
+ cursor.insertText(text);
+ cursor.setPosition(nextBlock.position() + textLength);
+ if (removeLastNewLine) // this is done when we moved to the last line, created block and copied another \n to it
+ cursor.deletePreviousChar();
+
+ cursor.setPosition(nextBlock.position(), QTextCursor::KeepAnchor);
+ setTextCursor(cursor);
+}
+
+void SqlEditor::moveBlockUp(bool deleteOld)
+{
+ QTextCursor cursor = textCursor();
+ if (!cursor.hasSelection())
+ {
+ cursor.movePosition(QTextCursor::StartOfLine);
+ cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
+ }
+
+ QTextDocument* doc = document();
+ QTextBlock startBlock = doc->findBlock(cursor.selectionStart());
+ QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1);
+ bool hasNewLineChar = endBlock.next().isValid();
+
+ QTextBlock insertingBlock = startBlock;
+ if (deleteOld)
+ {
+ insertingBlock = startBlock.previous();
+ if (!insertingBlock.isValid())
+ return;
+ }
+
+ // We will operate on full line length, unless next block was invalid, thus at the end there's no new line.
+ int textLength = endBlock.position() + endBlock.length() - startBlock.position();
+ if (!hasNewLineChar)
+ textLength--;
+
+ // Collecting text and removing text from old position (if not copying)
+ cursor.setPosition(startBlock.position());
+ cursor.setPosition(startBlock.position() + textLength, QTextCursor::KeepAnchor);
+ QString text = cursor.selectedText();
+ fixTextCursorSelectedText(text);
+ if (deleteOld) // this is false when just copying
+ cursor.removeSelectedText();
+
+ // Pasting text at new position
+ cursor.setPosition(insertingBlock.position());
+ cursor.insertText(text);
+ if (!hasNewLineChar)
+ {
+ cursor.insertBlock();
+ cursor.setPosition(insertingBlock.next().next().position());
+ cursor.deletePreviousChar();
+ textLength++; // we will need to include "new line" when reselecting text
+ }
+
+ // Reselecting new text
+ cursor.setPosition(insertingBlock.position() + textLength);
+ cursor.setPosition(insertingBlock.position(), QTextCursor::KeepAnchor);
+ setTextCursor(cursor);
+}
+
+void SqlEditor::copyBlockDown()
+{
+ moveBlockDown(false);
+}
+
+void SqlEditor::copyBlockUp()
+{
+ moveBlockUp(false);
+}
+
+void SqlEditor::find()
+{
+ textLocator->setStartPosition(textCursor().position());
+ showSearchDialog();
+}
+
+void SqlEditor::findNext()
+{
+ textLocator->findNext();
+}
+
+void SqlEditor::findPrevious()
+{
+ textLocator->findPrev();
+}
+
+void SqlEditor::replace()
+{
+ showSearchDialog();
+}
+
+void SqlEditor::found(int start, int end)
+{
+ QTextCursor cursor = textCursor();
+ cursor.setPosition(end);
+ cursor.setPosition(start, QTextCursor::KeepAnchor);
+ cursorMovingByLocator = true;
+ setTextCursor(cursor);
+ cursorMovingByLocator = false;
+ ensureCursorVisible();
+}
+
+void SqlEditor::reachedEnd()
+{
+ notifyInfo(tr("Reached the end of document. Hit the find again to restart the search."));
+}
+
+void SqlEditor::changeFont(const QVariant& font)
+{
+ setFont(font.value<QFont>());
+}
+
+void SqlEditor::configModified()
+{
+ highlighter->rehighlight();
+}
+
+void SqlEditor::keyPressEvent(QKeyEvent* e)
+{
+ switch (e->key())
+ {
+ case Qt::Key_Backspace:
+ {
+ deletionKeyPressed = true;
+ if (e->modifiers().testFlag(Qt::NoModifier))
+ backspacePressed();
+ else
+ QPlainTextEdit::keyPressEvent(e);
+ deletionKeyPressed = false;
+ return;
+ }
+ case Qt::Key_Delete:
+ {
+ deletionKeyPressed = true;
+ QPlainTextEdit::keyPressEvent(e);
+ deletionKeyPressed = false;
+ return;
+ }
+ case Qt::Key_Home:
+ {
+ homePressed(e->modifiers());
+ return;
+ }
+ case Qt::Key_Tab:
+ {
+ tabPressed(e->modifiers().testFlag(Qt::ShiftModifier));
+ return;
+ }
+ case Qt::Key_Backtab:
+ {
+ tabPressed(true);
+ return;
+ }
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ {
+ QPlainTextEdit::keyPressEvent(e);
+ indentNewLine();
+ return;
+ }
+ case Qt::Key_Control:
+ setObjectLinks(true);
+ break;
+ default:
+ break;
+ }
+ QPlainTextEdit::keyPressEvent(e);
+}
+
+void SqlEditor::keyReleaseEvent(QKeyEvent* e)
+{
+ if (e->key() == Qt::Key_Control)
+ setObjectLinks(false);
+
+ QPlainTextEdit::keyReleaseEvent(e);
+}
+
+void SqlEditor::focusOutEvent(QFocusEvent* e)
+{
+ UNUSED(e);
+ setObjectLinks(false);
+ QPlainTextEdit::focusOutEvent(e);
+}
+
+void SqlEditor::focusInEvent(QFocusEvent* e)
+{
+ if (completer->isVisible())
+ {
+ // Sometimes, when switching to other application window and then getting back to SQLiteStudio,
+ // the completer loses focus, but doesn't close. In that case, the SqlEditor gets focused,
+ // while completer still exists. Here we fix this case.
+ completer->reject();
+ return;
+ }
+
+ QPlainTextEdit::focusInEvent(e);
+}
+
+void SqlEditor::mouseMoveEvent(QMouseEvent* e)
+{
+ handleValidObjectCursor(e->pos());
+ QPlainTextEdit::mouseMoveEvent(e);
+}
+
+void SqlEditor::mousePressEvent(QMouseEvent* e)
+{
+ if (objectLinksEnabled)
+ {
+ const DbObject* obj = getValidObjectForPosition(e->pos());
+ if (obj && e->button() == Qt::LeftButton)
+ {
+ QString objName = toPlainText().mid(obj->from, (obj->to - obj->from + 1));
+ openObject(obj->dbName, objName);
+ }
+ }
+
+ QPlainTextEdit::mousePressEvent(e);
+}
+
+void SqlEditor::resizeEvent(QResizeEvent* e)
+{
+ QPlainTextEdit::resizeEvent(e);
+ QRect cr = contentsRect();
+ lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
+}
+
+void SqlEditor::handleValidObjectCursor(const QPoint& point)
+{
+ if (!objectLinksEnabled)
+ return;
+
+ QTextCursor cursor = cursorForPosition(point);
+ int position = cursor.position();
+ QRect curRect = cursorRect(cursor);
+ bool isValid = false;
+ if (point.y() >= curRect.top() && point.y() <= curRect.bottom())
+ {
+ // Mouse pointer is at the same line as cursor, so cursor was returned for actual character under mouse
+ // and not just first/last character of the line, because mouse was out of text.
+ bool movedLeft = (curRect.x() - point.x()) < 0;
+ isValid = (getValidObjectForPosition(position, movedLeft) != nullptr);
+ }
+ viewport()->setCursor(isValid ? Qt::PointingHandCursor : Qt::IBeamCursor);
+}
+bool SqlEditor::getVirtualSqlCompleteSemicolon() const
+{
+ return virtualSqlCompleteSemicolon;
+}
+
+void SqlEditor::setVirtualSqlCompleteSemicolon(bool value)
+{
+ virtualSqlCompleteSemicolon = value;
+}
+
+bool SqlEditor::getShowLineNumbers() const
+{
+ return showLineNumbers;
+}
+
+void SqlEditor::setShowLineNumbers(bool value)
+{
+ showLineNumbers = value;
+ lineNumberArea->setVisible(value);
+ updateLineNumberArea();
+}
+
+void SqlEditor::checkSyntaxNow()
+{
+ queryParserTimer->stop();
+ parseContents();
+}
+
+void SqlEditor::saveSelection()
+{
+ QTextCursor cur = textCursor();
+ storedSelectionStart = cur.selectionStart();
+ storedSelectionEnd = cur.selectionEnd();
+}
+
+void SqlEditor::restoreSelection()
+{
+ QTextCursor cur = textCursor();
+ cur.setPosition(storedSelectionStart);
+ cur.setPosition(storedSelectionEnd, QTextCursor::KeepAnchor);
+}
+
+QToolBar* SqlEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+QString SqlEditor::getVirtualSqlExpression() const
+{
+ return virtualSqlExpression;
+}
+
+void SqlEditor::setVirtualSqlExpression(const QString& value)
+{
+ virtualSqlExpression = value;
+
+ virtualSqlOffset = virtualSqlExpression.indexOf("%1");
+ if (virtualSqlOffset == -1)
+ {
+ virtualSqlOffset = 0;
+ virtualSqlExpression = QString::null;
+ qWarning() << "Tried to set invalid virtualSqlExpression for SqlEditor. Ignored.";
+ return;
+ }
+
+ virtualSqlRightOffset = virtualSqlExpression.length() - virtualSqlOffset - 2;
+}
+
+void SqlEditor::setTriggerContext(const QString& table)
+{
+ createTriggerTable = table;
+ highlighter->setCreateTriggerContext(!table.isEmpty());
+}
+
+const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(const QPoint& point)
+{
+ QTextCursor cursor = cursorForPosition(point);
+ int position = cursor.position();
+ bool movedLeft = (cursorRect(cursor).x() - point.x()) < 0;
+ return getValidObjectForPosition(position, movedLeft);
+}
+
+const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(int position, bool movedLeft)
+{
+ foreach (const DbObject& obj, validDbObjects)
+ {
+ if ((!movedLeft && position > obj.from && position-1 <= obj.to) ||
+ (movedLeft && position >= obj.from && position <= obj.to))
+ {
+ return &obj;
+ }
+ }
+ return nullptr;
+}
+
+SqlEditor::DbObject::DbObject(int from, int to, const QString& dbName) :
+ from(from), to(to), dbName(dbName)
+{
+
+}
+
+SqlEditor::LineNumberArea::LineNumberArea(SqlEditor* editor) :
+ QWidget(editor), codeEditor(editor)
+{
+}
+
+QSize SqlEditor::LineNumberArea::sizeHint() const
+{
+ return QSize(codeEditor->lineNumberAreaWidth(), 0);
+}
+
+void SqlEditor::LineNumberArea::paintEvent(QPaintEvent* event)
+{
+ if (codeEditor->getShowLineNumbers())
+ codeEditor->lineNumberAreaPaintEvent(event);
+}
+
+
+void SqlEditor::changeEvent(QEvent* e)
+{
+ if (e->type() == QEvent::EnabledChange)
+ highlightCurrentLine();
+
+ QPlainTextEdit::changeEvent(e);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.h b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h
new file mode 100644
index 0000000..d525e20
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h
@@ -0,0 +1,280 @@
+#ifndef SQLEDITOR_H
+#define SQLEDITOR_H
+
+#include "guiSQLiteStudio_global.h"
+#include "common/extactioncontainer.h"
+#include "db/db.h"
+#include "sqlitesyntaxhighlighter.h"
+#include <QPlainTextEdit>
+#include <QTextEdit>
+#include <QFont>
+#include <QHash>
+
+class CompleterWindow;
+class QTimer;
+class Parser;
+class SqlEditor;
+class SearchTextDialog;
+class SearchTextLocator;
+
+CFG_KEY_LIST(SqlEditor, QObject::tr("SQL editor input field"),
+ CFG_KEY_ENTRY(CUT, QKeySequence::Cut, QObject::tr("Cut selected text"))
+ CFG_KEY_ENTRY(COPY, QKeySequence::Copy, QObject::tr("Copy selected text"))
+ CFG_KEY_ENTRY(PASTE, QKeySequence::Paste, QObject::tr("Paste from clipboard"))
+ CFG_KEY_ENTRY(DELETE, QKeySequence::Delete, QObject::tr("Delete selected text"))
+ CFG_KEY_ENTRY(SELECT_ALL, QKeySequence::SelectAll, QObject::tr("Select whole editor contents"))
+ CFG_KEY_ENTRY(UNDO, QKeySequence::Undo, QObject::tr("Undo"))
+ CFG_KEY_ENTRY(REDO, QKeySequence::Redo, QObject::tr("Redo"))
+ CFG_KEY_ENTRY(SAVE_SQL_FILE, QKeySequence::Save, QObject::tr("Save contents into a file"))
+ CFG_KEY_ENTRY(OPEN_SQL_FILE, QKeySequence::Open, QObject::tr("Load contents from a file"))
+ CFG_KEY_ENTRY(FIND, QKeySequence::Find, QObject::tr("Find in text"))
+ CFG_KEY_ENTRY(FIND_NEXT, QKeySequence::FindNext, QObject::tr("Find next"))
+ CFG_KEY_ENTRY(FIND_PREV, QKeySequence::FindPrevious, QObject::tr("Find previous"))
+ CFG_KEY_ENTRY(REPLACE, QKeySequence::Replace, QObject::tr("Replace in text"))
+ CFG_KEY_ENTRY(DELETE_LINE, Qt::CTRL + Qt::Key_D, QObject::tr("Delete current line"))
+ CFG_KEY_ENTRY(COMPLETE, Qt::CTRL + Qt::Key_Space, QObject::tr("Request code assistant"))
+ CFG_KEY_ENTRY(FORMAT_SQL, Qt::CTRL + Qt::Key_T, QObject::tr("Format contents"))
+ CFG_KEY_ENTRY(MOVE_BLOCK_DOWN, Qt::ALT + Qt::Key_Down, QObject::tr("Move selected block of text one line down"))
+ CFG_KEY_ENTRY(MOVE_BLOCK_UP, Qt::ALT + Qt::Key_Up, QObject::tr("Move selected block of text one line up"))
+ CFG_KEY_ENTRY(COPY_BLOCK_DOWN, Qt::ALT + Qt::CTRL + Qt::Key_Down, QObject::tr("Copy selected block of text and paste it a line below"))
+ CFG_KEY_ENTRY(COPY_BLOCK_UP, Qt::ALT + Qt::CTRL + Qt::Key_Up, QObject::tr("Copy selected block of text and paste it a line above"))
+)
+
+class GUI_API_EXPORT SqlEditor : public QPlainTextEdit, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ COPY,
+ PASTE,
+ CUT,
+ UNDO,
+ REDO,
+ DELETE,
+ SELECT_ALL,
+ FORMAT_SQL,
+ OPEN_SQL_FILE,
+ SAVE_SQL_FILE,
+ DELETE_LINE,
+ COMPLETE,
+ MOVE_BLOCK_DOWN,
+ MOVE_BLOCK_UP,
+ COPY_BLOCK_DOWN,
+ COPY_BLOCK_UP,
+ FIND,
+ FIND_NEXT,
+ FIND_PREV,
+ REPLACE
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit SqlEditor(QWidget *parent = 0);
+ ~SqlEditor();
+
+ Db* getDb() const;
+ void setDb(Db* value);
+ void setAutoCompletion(bool enabled);
+ QString getVirtualSqlExpression() const;
+ void setVirtualSqlExpression(const QString& value);
+ void setTriggerContext(const QString& table);
+ bool haveErrors();
+ bool isSyntaxChecked();
+ bool getShowLineNumbers() const;
+ void setShowLineNumbers(bool value);
+ void checkSyntaxNow();
+ void saveSelection();
+ void restoreSelection();
+ QToolBar* getToolBar(int toolbar) const;
+
+ bool getVirtualSqlCompleteSemicolon() const;
+ void setVirtualSqlCompleteSemicolon(bool value);
+
+ protected:
+ void setupDefShortcuts();
+ void createActions();
+ void keyPressEvent(QKeyEvent* e);
+ void keyReleaseEvent(QKeyEvent* e);
+ void focusOutEvent(QFocusEvent* e);
+ void focusInEvent(QFocusEvent* e);
+ void mouseMoveEvent(QMouseEvent* e);
+ void mousePressEvent(QMouseEvent* e);
+ void resizeEvent(QResizeEvent *e);
+ void changeEvent(QEvent*e);
+
+ private:
+ class LineNumberArea : public QWidget
+ {
+ public:
+ explicit LineNumberArea(SqlEditor *editor);
+ QSize sizeHint() const;
+
+ protected:
+ void paintEvent(QPaintEvent *event);
+
+ private:
+ SqlEditor *codeEditor = nullptr;
+ };
+
+ struct DbObject
+ {
+ DbObject(int from, int to, const QString& dbName);
+
+ int from;
+ int to;
+
+ /**
+ * @brief dbName
+ * Attach name for the db that object belongs to.
+ * If the object is database itself, then this variable is null.
+ */
+ QString dbName;
+ };
+
+ void setupMenu();
+ void updateCompleterPosition();
+ void init();
+ void removeErrorMarkers();
+ void deleteCurrentLine();
+ void deleteSelectedLines();
+
+ /**
+ * @brief markErrorAt Mark error range.
+ * @param start Start index of error.
+ * @param end End index of error.
+ * @param limitedDamage true if error is just invalid token, that didn't cause parser to fail.
+ */
+ void markErrorAt(int start, int end, bool limitedDamage = false);
+ void deletePreviousChars(int length = 1);
+ void refreshValidObjects();
+ void checkForSyntaxErrors();
+ void checkForValidObjects();
+ Dialect getDialect();
+ void setObjectLinks(bool enabled);
+ void addDbObject(int from, int to, const QString& dbName);
+ void clearDbObjects();
+ void lineNumberAreaPaintEvent(QPaintEvent* event);
+ int lineNumberAreaWidth();
+ void highlightParenthesis();
+ const TextBlockData::Parenthesis* matchParenthesis(QList<const TextBlockData::Parenthesis*> parList, const TextBlockData::Parenthesis* thePar);
+ void markMatchedParenthesis(int pos1, int pos2, QList<QTextEdit::ExtraSelection>& selections);
+ void doBackspace(int repeats = 1);
+ void indentSelected(bool shiftPressed);
+ void indentBlock(const QTextBlock& block);
+ void unindentBlock(const QTextBlock& block);
+ void indentNewLine();
+ void showSearchDialog();
+ int sqlIndex(int idx);
+ void updateLineNumberArea();
+ bool hasSelection() const;
+ void replaceSelectedText(const QString& newText);
+ QString getSelectedText() const;
+ void openObject(const QString& database, const QString& name);
+
+ /**
+ * @brief getValidObjectForPosition
+ * @param position Cursor text position determinated by Qt mouse event.
+ * @param movedLeft true if Qt moved cursor left from click point, which means that user clicked closer to left border of character. Otherwise cursor was moved towards right.
+ * @return Object identified under given text position, or null if there was no valid object under that position.
+ */
+ const DbObject* getValidObjectForPosition(int position, bool movedLeft);
+ const DbObject* getValidObjectForPosition(const QPoint& point);
+ void handleValidObjectCursor(const QPoint& point);
+
+ SqliteSyntaxHighlighter* highlighter = nullptr;
+ QMenu* contextMenu = nullptr;
+ QMenu* validObjContextMenu = nullptr;
+ Db* db = nullptr;
+ CompleterWindow* completer = nullptr;
+ QTimer* autoCompleteTimer = nullptr;
+ bool autoCompletion = true;
+ bool deletionKeyPressed = false;
+ QTimer* queryParserTimer = nullptr;
+ Parser* queryParser = nullptr;
+ QHash<QString,QStringList> objectsInNamedDb;
+ bool objectLinksEnabled = false;
+ QList<DbObject> validDbObjects;
+ QWidget* lineNumberArea = nullptr;
+ SearchTextDialog* searchDialog = nullptr;
+ SearchTextLocator* textLocator = nullptr;
+ bool cursorMovingByLocator = false;
+ bool syntaxValidated = false;
+ bool showLineNumbers = true;
+ int storedSelectionStart = 0;
+ int storedSelectionEnd = 0;
+ bool richFeaturesEnabled = true;
+
+ /**
+ * @brief virtualSqlExpression
+ * It has to be an SQL string containing exactly one argument %1 (as Qt string arguments).
+ * It will be used in every syntax completion request as a template, as if user
+ * wrote this entire SQL statement, plus his own code in place of %1 and then the completer is invoked.
+ * User never sees this SQL expression, it's hidden from him.
+ * The expression will also be used for syntax error checking the same was as for completer.
+ *
+ * This is useful for example when we want to have a context for completion in CHECK() constraint,
+ * but user has only input edit for the CHECK expression itself, so for completer to work correctly
+ * it needs to be lied that there is entire "CREATE TABLE...CHECK(" before the users code. In that
+ * case you would set this variable to something like this: "CREATE TABLE x (c CHECK(%1))".
+ * The SqlEditor is smart enough to do all the magic given the above expression.
+ */
+ QString virtualSqlExpression;
+ int virtualSqlOffset = 0;
+ int virtualSqlRightOffset = 0;
+ bool virtualSqlCompleteSemicolon = false;
+ QString createTriggerTable;
+
+ static const int autoCompleterDelay = 300;
+ static const int queryParserDelay = 500;
+
+ private slots:
+ void customContextMenuRequested(const QPoint& pos);
+ void updateUndoAction(bool enabled);
+ void updateRedoAction(bool enabled);
+ void updateCopyAction(bool enabled);
+ void deleteSelected();
+ void homePressed(Qt::KeyboardModifiers modifiers);
+ void tabPressed(bool shiftPressed);
+ void backspacePressed();
+ void complete();
+ void completeSelected();
+ void scheduleAutoCompletion();
+ void checkForAutoCompletion();
+ void completerTypedText(const QString& text);
+ void completerBackspacePressed();
+ void completerLeftPressed();
+ void completerRightPressed();
+ void parseContents();
+ void scheduleQueryParser(bool force = false);
+ void updateLineNumberAreaWidth();
+ void highlightCurrentLine();
+ void updateLineNumberArea(const QRect&rect, int dy);
+ void cursorMoved();
+ void formatSql();
+ void saveToFile();
+ void loadFromFile();
+ void deleteLine();
+ void moveBlockDown(bool deleteOld = true);
+ void moveBlockUp(bool deleteOld = true);
+ void copyBlockDown();
+ void copyBlockUp();
+ void find();
+ void findNext();
+ void findPrevious();
+ void replace();
+ void found(int start, int end);
+ void reachedEnd();
+ void changeFont(const QVariant& font);
+ void configModified();
+
+ signals:
+ void errorsChecked(bool haveErrors);
+};
+
+
+#endif // SQLEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp
new file mode 100644
index 0000000..e3e1950
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp
@@ -0,0 +1,447 @@
+#include "sqlitesyntaxhighlighter.h"
+#include "parser/lexer.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include <QTextDocument>
+#include <QDebug>
+#include <QPlainTextEdit>
+
+SqliteSyntaxHighlighter::SqliteSyntaxHighlighter(QTextDocument *parent) :
+ QSyntaxHighlighter(parent)
+{
+ setupFormats();
+ setupMapping();
+ setCurrentBlockState(regulartTextBlockState);
+ connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(setupFormats()));
+}
+
+void SqliteSyntaxHighlighter::setSqliteVersion(int version)
+{
+ this->sqliteVersion = version;
+ rehighlight();
+}
+
+void SqliteSyntaxHighlighter::setFormat(SqliteSyntaxHighlighter::State state, QTextCharFormat format)
+{
+ formats[state] = format;
+}
+
+QTextCharFormat SqliteSyntaxHighlighter::getFormat(SqliteSyntaxHighlighter::State state) const
+{
+ return formats[state];
+}
+
+void SqliteSyntaxHighlighter::setupFormats()
+{
+ QTextCharFormat format;
+
+ // Standard
+ format.setForeground(CFG_UI.Colors.SqlEditorForeground.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(false);
+ formats[State::STANDARD] = format;
+
+ // Parenthesis
+ formats[State::PARENTHESIS] = format;
+
+ // String
+ format.setForeground(CFG_UI.Colors.SqlEditorStringFg.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(false);
+ formats[State::STRING] = format;
+
+ // Keyword
+ format.setForeground(CFG_UI.Colors.SqlEditorKeywordFg.get());
+ format.setFontWeight(QFont::Bold);
+ format.setFontItalic(false);
+ formats[State::KEYWORD] = format;
+
+ // BindParam
+ format.setForeground(CFG_UI.Colors.SqlEditorBindParamFg.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(false);
+ formats[State::BIND_PARAM] = format;
+
+ // Blob
+ format.setForeground(CFG_UI.Colors.SqlEditorBlobFg.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(false);
+ formats[State::BLOB] = format;
+
+ // Comment
+ format.setForeground(CFG_UI.Colors.SqlEditorCommentFg.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(true);
+ formats[State::COMMENT] = format;
+
+ // Number
+ format.setForeground(CFG_UI.Colors.SqlEditorNumberFg.get());
+ format.setFontWeight(QFont::Normal);
+ format.setFontItalic(false);
+ formats[State::NUMBER] = format;
+}
+
+void SqliteSyntaxHighlighter::setupMapping()
+{
+ tokenTypeMapping[Token::STRING] = State::STRING;
+ tokenTypeMapping[Token::COMMENT] = State::COMMENT;
+ tokenTypeMapping[Token::FLOAT] = State::NUMBER;
+ tokenTypeMapping[Token::INTEGER] = State::NUMBER;
+ tokenTypeMapping[Token::BIND_PARAM] = State::BIND_PARAM;
+ tokenTypeMapping[Token::PAR_LEFT] = State::PARENTHESIS;
+ tokenTypeMapping[Token::PAR_RIGHT] = State::PARENTHESIS;
+ tokenTypeMapping[Token::BLOB] = State::BLOB;
+ tokenTypeMapping[Token::KEYWORD] = State::KEYWORD;
+}
+
+QString SqliteSyntaxHighlighter::getPreviousStatePrefix(TextBlockState textBlockState)
+{
+ QString prefix = "";
+ switch (textBlockState)
+ {
+ case SqliteSyntaxHighlighter::TextBlockState::REGULAR:
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::BLOB:
+ prefix = "x'";
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::STRING:
+ prefix = "'";
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::COMMENT:
+ prefix = "/*";
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::ID_1:
+ prefix = "[";
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::ID_2:
+ prefix = "\"";
+ break;
+ case SqliteSyntaxHighlighter::TextBlockState::ID_3:
+ prefix = "`";
+ break;
+ }
+ return prefix;
+}
+
+void SqliteSyntaxHighlighter::highlightBlock(const QString &text)
+{
+ if (text.length() <= 0)
+ return;
+
+ // Reset to default
+ QSyntaxHighlighter::setFormat(0, text.length(), formats[State::STANDARD]);
+
+ qint32 idxModifier = 0;
+ QString statePrefix = "";
+ if (previousBlockState() != regulartTextBlockState)
+ {
+ statePrefix = getPreviousStatePrefix(static_cast<TextBlockState>(previousBlockState()));
+ idxModifier += statePrefix.size();
+ }
+
+ Lexer lexer(sqliteVersion == 2 ? Dialect::Sqlite2 : Dialect::Sqlite3);
+ lexer.setTolerantMode(true);
+ lexer.prepare(statePrefix+text);
+
+ // Previous error state.
+ // Empty lines have no userData, so we will look for any previous paragraph that is
+ // valid and has a data, so it has any logical meaning to highlighter.
+ QTextBlock prevBlock = currentBlock().previous();
+ while ((!prevBlock.isValid() || !prevBlock.userData() || prevBlock.text().isEmpty()) && prevBlock.position() > 0)
+ prevBlock = prevBlock.previous();
+
+ TextBlockData* prevData = nullptr;
+ if (prevBlock.isValid())
+ prevData = dynamic_cast<TextBlockData*>(prevBlock.userData());
+
+ TextBlockData* data = new TextBlockData();
+ int errorStart = -1;
+ TokenPtr token = lexer.getToken();
+ while (token)
+ {
+ if (handleToken(token, idxModifier, errorStart, data, prevData))
+ errorStart = token->start + currentBlock().position();
+
+ if (data->getEndsWithQuerySeparator())
+ errorStart = -1;
+
+ handleParenthesis(token, data);
+ token = lexer.getToken();
+ }
+
+ setCurrentBlockUserData(data);
+}
+
+bool SqliteSyntaxHighlighter::handleToken(TokenPtr token, qint32 idxModifier, int errorStart, TextBlockData* currBlockData,
+ TextBlockData* previousBlockData)
+{
+ qint64 start = token->start - idxModifier;
+ qint64 lgt = token->end - token->start + 1;
+ if (start < 0)
+ {
+ lgt += start; // cut length by num of chars before 0 (after idxModifier applied)
+ start = 0;
+ }
+
+ if (createTriggerContext && token->type == Token::OTHER && (token->value.toLower() == "old" || token->value.toLower() == "new"))
+ token->type = Token::KEYWORD;
+
+ bool limitedDamage = false;
+ bool querySeparator = (token->type == Token::Type::OPERATOR && token->value == ";");
+ bool error = isError(start, lgt, &limitedDamage);
+ bool valid = isValid(start, lgt);
+ bool wasError = (
+ (errorStart > -1) &&
+ (start + currentBlock().position() + lgt >= errorStart) &&
+ !currBlockData->getEndsWithQuerySeparator() // if it was set for previous token in the same block
+ ) ||
+ (
+ token->start == 0 &&
+ previousBlockData &&
+ previousBlockData->getEndsWithError() &&
+ !previousBlockData->getEndsWithQuerySeparator()
+ );
+ bool fatalError = (error && !limitedDamage) || wasError;
+
+ QTextCharFormat format;
+
+ // Applying valid object format.
+ applyValidObjectFormat(format, valid, error, wasError);
+
+ // Get format for token type (if any)
+ if (tokenTypeMapping.contains(token->type))
+ format = formats[tokenTypeMapping[token->type]];
+
+ // Merge with error format (if this is an error).
+ applyErrorFormat(format, error, wasError, token->type);
+
+ // Apply format
+ QSyntaxHighlighter::setFormat(start, lgt, format);
+
+ // Save block state
+ TolerantTokenPtr tolerantToken = token.dynamicCast<TolerantToken>();
+ if (tolerantToken->invalid)
+ setStateForUnfinishedToken(tolerantToken);
+ else
+ setCurrentBlockState(regulartTextBlockState);
+
+ currBlockData->setEndsWithError(fatalError);
+ currBlockData->setEndsWithQuerySeparator(querySeparator);
+
+ return fatalError;
+}
+
+void SqliteSyntaxHighlighter::applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType)
+{
+ if ((!isError && !wasError) || tokenType == Token::Type::COMMENT)
+ return;
+
+ format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
+ format.setUnderlineColor(QColor(Qt::red));
+}
+
+void SqliteSyntaxHighlighter::applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError)
+{
+ if (isError || wasError || !isValid)
+ return;
+
+ format.setForeground(CFG_UI.Colors.SqlEditorValidObject.get());
+ if (objectLinksEnabled)
+ format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+}
+
+void SqliteSyntaxHighlighter::handleParenthesis(TokenPtr token, TextBlockData* data)
+{
+ if (token->type == Token::PAR_LEFT || token->type == Token::PAR_RIGHT)
+ data->insertParenthesis(currentBlock().position() + token->start, token->value[0].toLatin1());
+}
+bool SqliteSyntaxHighlighter::getCreateTriggerContext() const
+{
+ return createTriggerContext;
+}
+
+void SqliteSyntaxHighlighter::setCreateTriggerContext(bool value)
+{
+ createTriggerContext = value;
+}
+
+
+bool SqliteSyntaxHighlighter::getObjectLinksEnabled() const
+{
+ return objectLinksEnabled;
+}
+
+void SqliteSyntaxHighlighter::setObjectLinksEnabled(bool value)
+{
+ objectLinksEnabled = value;
+}
+
+bool SqliteSyntaxHighlighter::isError(int start, int lgt, bool* limitedDamage)
+{
+ start += currentBlock().position();
+ int end = start + lgt - 1;
+ foreach (const Error& error, errors)
+ {
+ if (error.from <= start && error.to >= end)
+ {
+ *limitedDamage = error.limitedDamage;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SqliteSyntaxHighlighter::isValid(int start, int lgt)
+{
+ start += currentBlock().position();
+ int end = start + lgt - 1;
+ foreach (const DbObject& obj, dbObjects)
+ {
+ if (obj.from <= start && obj.to >= end)
+ return true;
+ }
+ return false;
+}
+
+void SqliteSyntaxHighlighter::setStateForUnfinishedToken(TolerantTokenPtr tolerantToken)
+{
+ switch (tolerantToken->type)
+ {
+ case Token::OTHER:
+ {
+ switch (tolerantToken->value.at(0).toLatin1())
+ {
+ case '[':
+ setCurrentBlockState(static_cast<int>(TextBlockState::ID_1));
+ break;
+ case '"':
+ setCurrentBlockState(static_cast<int>(TextBlockState::ID_2));
+ break;
+ case '`':
+ setCurrentBlockState(static_cast<int>(TextBlockState::ID_3));
+ break;
+ }
+ break;
+ }
+ case Token::STRING:
+ setCurrentBlockState(static_cast<int>(TextBlockState::STRING));
+ break;
+ case Token::COMMENT:
+ setCurrentBlockState(static_cast<int>(TextBlockState::COMMENT));
+ break;
+ case Token::BLOB:
+ setCurrentBlockState(static_cast<int>(TextBlockState::BLOB));
+ break;
+ default:
+ break;
+ }
+}
+void SqliteSyntaxHighlighter::clearErrors()
+{
+ errors.clear();
+}
+
+bool SqliteSyntaxHighlighter::haveErrors()
+{
+ return errors.count() > 0;
+}
+
+void SqliteSyntaxHighlighter::addDbObject(int from, int to)
+{
+ dbObjects << DbObject(from, to);
+}
+
+void SqliteSyntaxHighlighter::clearDbObjects()
+{
+ dbObjects.clear();
+}
+
+void SqliteSyntaxHighlighter::addError(int from, int to, bool limitedDamage)
+{
+ errors << Error(from, to, limitedDamage);
+}
+
+SqliteSyntaxHighlighter::Error::Error(int from, int to, bool limitedDamage) :
+ from(from), to(to), limitedDamage(limitedDamage)
+{
+}
+
+int qHash(SqliteSyntaxHighlighter::State state)
+{
+ return static_cast<int>(state);
+}
+
+
+SqliteSyntaxHighlighter::DbObject::DbObject(int from, int to) :
+ from(from), to(to)
+{
+}
+
+QList<const TextBlockData::Parenthesis*> TextBlockData::parentheses()
+{
+ QList<const TextBlockData::Parenthesis*> list;
+ foreach (const TextBlockData::Parenthesis& par, parData)
+ list << &par;
+
+ return list;
+}
+
+void TextBlockData::insertParenthesis(int pos, char c)
+{
+ Parenthesis par;
+ par.character = c;
+ par.position = pos;
+ parData << par;
+}
+
+const TextBlockData::Parenthesis* TextBlockData::parenthesisForPosision(int pos)
+{
+ foreach (const Parenthesis& par, parData)
+ {
+ if (par.position == pos)
+ return &par;
+ }
+ return nullptr;
+}
+bool TextBlockData::getEndsWithError() const
+{
+ return endsWithError;
+}
+
+void TextBlockData::setEndsWithError(bool value)
+{
+ endsWithError = value;
+}
+bool TextBlockData::getEndsWithQuerySeparator() const
+{
+ return endsWithQuerySeparator;
+}
+
+void TextBlockData::setEndsWithQuerySeparator(bool value)
+{
+ endsWithQuerySeparator = value;
+}
+
+
+int TextBlockData::Parenthesis::operator==(const TextBlockData::Parenthesis& other)
+{
+ return other.position == position && other.character == character;
+}
+
+QString SqliteHighlighterPlugin::getLanguageName() const
+{
+ return "SQL";
+}
+
+QSyntaxHighlighter* SqliteHighlighterPlugin::createSyntaxHighlighter(QWidget* textEdit) const
+{
+ QPlainTextEdit* plainEdit = dynamic_cast<QPlainTextEdit*>(textEdit);
+ if (plainEdit)
+ return new SqliteSyntaxHighlighter(plainEdit->document());
+
+ QTextEdit* edit = dynamic_cast<QTextEdit*>(textEdit);
+ if (edit)
+ return new SqliteSyntaxHighlighter(edit->document());
+
+ return nullptr;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h
new file mode 100644
index 0000000..0696d02
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h
@@ -0,0 +1,185 @@
+#ifndef SQLITESYNTAXHIGHLIGHTER_H
+#define SQLITESYNTAXHIGHLIGHTER_H
+
+#include "parser/token.h"
+#include "syntaxhighlighterplugin.h"
+#include "plugins/builtinplugin.h"
+#include "guiSQLiteStudio_global.h"
+#include <QSyntaxHighlighter>
+#include <QRegularExpression>
+
+class QWidget;
+
+class GUI_API_EXPORT TextBlockData : public QTextBlockUserData
+{
+ public:
+ struct GUI_API_EXPORT Parenthesis
+ {
+ char character;
+ int position;
+ int operator==(const Parenthesis& other);
+ };
+
+ QList<const Parenthesis*> parentheses();
+ void insertParenthesis(int pos, char c);
+ const Parenthesis* parenthesisForPosision(int pos);
+
+ bool getEndsWithError() const;
+ void setEndsWithError(bool value);
+
+ bool getEndsWithQuerySeparator() const;
+ void setEndsWithQuerySeparator(bool value);
+
+ private:
+ QList<Parenthesis> parData;
+ bool endsWithError = false;
+ bool endsWithQuerySeparator = false;
+};
+
+class GUI_API_EXPORT SqliteSyntaxHighlighter : public QSyntaxHighlighter
+{
+ Q_OBJECT
+ public:
+ enum class State
+ {
+ STANDARD,
+ PARENTHESIS,
+ STRING,
+ KEYWORD,
+ BIND_PARAM,
+ BLOB,
+ COMMENT,
+ NUMBER
+ };
+
+ explicit SqliteSyntaxHighlighter(QTextDocument *parent);
+
+ void setSqliteVersion(int version);
+ void setFormat(State state, QTextCharFormat format);
+ QTextCharFormat getFormat(State state) const;
+
+ void addError(int from, int to, bool limitedDamage = false);
+ void clearErrors();
+ bool haveErrors();
+
+ void addDbObject(int from, int to);
+ void clearDbObjects();
+
+ bool getObjectLinksEnabled() const;
+ void setObjectLinksEnabled(bool value);
+
+ void addCustomBgColor(int from, int to, const QColor& color);
+ void clearCustomBgColors();
+
+ bool getCreateTriggerContext() const;
+ void setCreateTriggerContext(bool value);
+
+ protected:
+ void highlightBlock(const QString &text);
+
+ private:
+ enum class TextBlockState
+ {
+ REGULAR = -1, // default, the -1 is default of QSyntaxHighlighter
+ BLOB, // x'blob'
+ STRING, // 'string'
+ COMMENT, // /* comment */
+ ID_1, // [id]
+ ID_2, // "id"
+ ID_3 // `id`
+ };
+
+ struct Error
+ {
+ Error(int from, int to, bool limitedDamage = false);
+
+ int from;
+ int to;
+ bool limitedDamage = false; // if it's just an invalid token, but parser dealt with it, mark only this token
+ };
+
+ struct DbObject
+ {
+ DbObject(int from, int to);
+
+ int from;
+ int to;
+ };
+
+ void setupMapping();
+
+ /**
+ * @brief getPreviousStatePrefix Provides prefix for previous block's state.
+ * @param textBlockState Previous text block's state.
+ * @return Prefix string (if any) for lexer to provide proper tokens according to previous state.
+ */
+ QString getPreviousStatePrefix(TextBlockState textBlockState);
+
+ /**
+ * @brief handleToken Highlights token.
+ * @param token Token to handle.
+ * @param idxModifier Modifier for text highlighting in case of previous state defined by multi-character token. See getPreviousStatePrefix() for details.
+ * @return true if the token is being marked as invalid (syntax error).
+ */
+ bool handleToken(TokenPtr token, qint32 idxModifier, int errorStart, TextBlockData* currBlockData, TextBlockData* previousBlockData);
+
+ bool isError(int start, int lgt, bool* limitedDamage);
+ bool isValid(int start, int lgt);
+
+ /**
+ * @brief markUncheckedErrors Marks text as being uncheck for possible errors.
+ * @param errorStart Start index of unchecked text.
+ * Unchecked text is all text after first error, becuase it could not be parser, therefore could not be checked.
+ */
+ void markUncheckedErrors(int errorStart, int length);
+ void setStateForUnfinishedToken(TolerantTokenPtr tolerantToken);
+
+ /**
+ * @brief applyErrorFormat Applies error format properties to given format.
+ * @param format Format to apply properties to.
+ * @param isError true if error was detected and error format needs to be applied.
+ * @param wasError true if there was an error already in previous token.
+ */
+ void applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType);
+
+ /**
+ * @brief applyValidObjectFormat Applies valid database object format properties to given format.
+ * @param format Format to apply properties to.
+ * @param isValid true if we're about to mark valid database object
+ * @param qtextdisError true if error was detected and error format needs to be applied.
+ * @param wasError true if there was an error already in previous token.
+ */
+ void applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError);
+
+ void handleParenthesis(TokenPtr token, TextBlockData* data);
+
+ static const int regulartTextBlockState = static_cast<int>(TextBlockState::REGULAR);
+ int sqliteVersion = 3;
+ QHash<State,QTextCharFormat> formats;
+ QHash<Token::Type,State> tokenTypeMapping;
+ QList<Error> errors;
+ QList<DbObject> dbObjects;
+ bool objectLinksEnabled = false;
+ bool createTriggerContext = false;
+
+ private slots:
+ void setupFormats();
+};
+
+class GUI_API_EXPORT SqliteHighlighterPlugin : public BuiltInPlugin, public SyntaxHighlighterPlugin
+{
+ Q_OBJECT
+
+ SQLITESTUDIO_PLUGIN_TITLE("SQL highlighter")
+ SQLITESTUDIO_PLUGIN_DESC("SQL (SQLite) syntax highlighter.")
+ SQLITESTUDIO_PLUGIN_VERSION(10000)
+ SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")
+
+ public:
+ QString getLanguageName() const;
+ QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const;
+};
+
+GUI_API_EXPORT int qHash(SqliteSyntaxHighlighter::State state);
+
+#endif // SQLITESYNTAXHIGHLIGHTER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp
new file mode 100644
index 0000000..e65a60f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp
@@ -0,0 +1,43 @@
+#include "sqlview.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "uiconfig.h"
+
+SqlView::SqlView(QWidget *parent) :
+ QTextEdit(parent)
+{
+ highlighter = new SqliteSyntaxHighlighter(this->document());
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+ connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
+ setReadOnly(true);
+}
+
+void SqlView::setSqliteVersion(int version)
+{
+ highlighter->setSqliteVersion(version);
+}
+
+void SqlView::setTextBackgroundColor(int from, int to, const QColor& color)
+{
+ bool wasRo = false;
+ if (isReadOnly())
+ {
+ wasRo = true;
+ setReadOnly(false);
+ }
+
+ QTextCharFormat format;
+ format.setBackground(color);
+
+ QTextCursor cur(document());
+ cur.setPosition(from);
+ cur.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, to - from + 1);
+ cur.mergeCharFormat(format);
+
+ if (wasRo)
+ setReadOnly(true);
+}
+
+void SqlView::changeFont(const QVariant &font)
+{
+ setFont(font.value<QFont>());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlview.h b/SQLiteStudio3/guiSQLiteStudio/sqlview.h
new file mode 100644
index 0000000..7358a43
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/sqlview.h
@@ -0,0 +1,25 @@
+#ifndef SQLVIEW_H
+#define SQLVIEW_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QTextEdit>
+
+class SqliteSyntaxHighlighter;
+
+class GUI_API_EXPORT SqlView : public QTextEdit
+{
+ Q_OBJECT
+ public:
+ explicit SqlView(QWidget *parent = 0);
+
+ void setSqliteVersion(int version);
+ void setTextBackgroundColor(int from, int to, const QColor& color);
+
+ private:
+ SqliteSyntaxHighlighter* highlighter = nullptr;
+
+ private slots:
+ void changeFont(const QVariant& font);
+};
+
+#endif // SQLVIEW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp b/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp
new file mode 100644
index 0000000..5de85d7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp
@@ -0,0 +1,218 @@
+#include "statusfield.h"
+#include "ui_statusfield.h"
+#include "mainwindow.h"
+#include "uiconfig.h"
+#include "iconmanager.h"
+#include "common/tablewidget.h"
+#include "services/notifymanager.h"
+#include <QMenu>
+#include <QAction>
+#include <QDateTime>
+#include <QLabel>
+#include <QVariantAnimation>
+#include <QDebug>
+
+StatusField::StatusField(QWidget *parent) :
+ QDockWidget(parent),
+ ui(new Ui::StatusField)
+{
+ ui->setupUi(this);
+ setupMenu();
+ ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ ui->tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+
+ NotifyManager* nm = NotifyManager::getInstance();
+ connect(nm, SIGNAL(notifyInfo(QString)), this, SLOT(info(QString)));
+ connect(nm, SIGNAL(notifyError(QString)), this, SLOT(error(QString)));
+ connect(nm, SIGNAL(notifyWarning(QString)), this, SLOT(warn(QString)));
+ connect(CFG_UI.Fonts.StatusField, SIGNAL(changed(QVariant)), this, SLOT(fontChanged(QVariant)));
+
+ readRecentMessages();
+}
+
+bool StatusField::hasMessages() const
+{
+ return ui->tableWidget->rowCount() > 0;
+}
+
+StatusField::~StatusField()
+{
+ delete ui;
+}
+
+void StatusField::changeEvent(QEvent *e)
+{
+ QDockWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void StatusField::info(const QString &text)
+{
+ addEntry(ICONS.STATUS_INFO, text, CFG_UI.Colors.StatusFieldInfoFg.get());
+}
+
+void StatusField::warn(const QString &text)
+{
+ addEntry(ICONS.STATUS_WARNING, text, CFG_UI.Colors.StatusFieldWarnFg.get());
+}
+
+void StatusField::error(const QString &text)
+{
+ addEntry(ICONS.STATUS_ERROR, text, CFG_UI.Colors.StatusFieldErrorFg.get());
+}
+
+void StatusField::addEntry(const QIcon &icon, const QString &text, const QColor& color)
+{
+ int row = ui->tableWidget->rowCount();
+ ui->tableWidget->setRowCount(row+1);
+
+ if (row > itemCountLimit)
+ {
+ ui->tableWidget->removeRow(0);
+ row--;
+ }
+
+ QList<QTableWidgetItem*> itemsCreated;
+ QTableWidgetItem* item = nullptr;
+
+ item = new QTableWidgetItem();
+ item->setIcon(icon);
+ ui->tableWidget->setItem(row, 0, item);
+ itemsCreated << item;
+
+ QFont font = CFG_UI.Fonts.StatusField.get();
+
+ QString timeStr = "[" + QDateTime::currentDateTime().toString(timeStampFormat) + "]";
+ item = new QTableWidgetItem(timeStr);
+ item->setForeground(QBrush(color));
+ item->setFont(font);
+ ui->tableWidget->setItem(row, 1, item);
+ itemsCreated << item;
+
+ item = new QTableWidgetItem();
+ item->setForeground(QBrush(color));
+ item->setFont(font);
+ ui->tableWidget->setItem(row, 2, item);
+ itemsCreated << item;
+
+ static_qstring(colorTpl, "QLabel {color: %1}");
+ // While QLabel does detect if the text is rich automatically, we don't want to use qlabel for plain text,
+ // because it's not wrapped correctly if the text is longer.
+ if (text.contains("<"))
+ {
+ QLabel* label = new QLabel(text);
+ QMargins margin = label->contentsMargins();
+ margin.setLeft(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
+ label->setContentsMargins(margin);
+ label->setFont(font);
+ label->setStyleSheet(colorTpl.arg(color.name()));
+ connect(label, SIGNAL(linkActivated(QString)), this, SIGNAL(linkActivated(QString)));
+ ui->tableWidget->setCellWidget(row, 2, label);
+ }
+ else
+ {
+ item->setText(text);
+ }
+
+ setVisible(true);
+
+ ui->tableWidget->scrollToBottom();
+
+ if (!noFlashing)
+ flashItems(itemsCreated, color);
+}
+
+void StatusField::flashItems(const QList<QTableWidgetItem*>& items, const QColor& color)
+{
+ QColor alphaColor = color;
+ alphaColor.setAlpha(0);
+
+ QColor finalColor = color;
+ finalColor.setAlpha(150);
+
+ QVariantAnimation* anim = new QVariantAnimation();
+ anim->setDuration(500);
+ anim->setEasingCurve(QEasingCurve::OutQuad);
+ anim->setStartValue(finalColor);
+ anim->setEndValue(alphaColor);
+
+ itemAnimations << anim;
+ connect(anim, &QObject::destroyed, [this, anim]() {itemAnimations.removeOne(anim);});
+
+ connect(anim, &QVariantAnimation::valueChanged, [items](const QVariant& value)
+ {
+ for (QTableWidgetItem* item : items)
+ item->setBackground(value.value<QColor>());
+ });
+
+ anim->start(QAbstractAnimation::DeleteWhenStopped);
+}
+
+void StatusField::setupMenu()
+{
+ menu = new QMenu(this);
+
+ copyAction = new QAction(ICONS.ACT_COPY, tr("Copy"), ui->tableWidget);
+ copyAction->setShortcut(QKeySequence::Copy);
+ connect(copyAction, &QAction::triggered, ui->tableWidget, &TableWidget::copy);
+ menu->addAction(copyAction);
+
+ menu->addSeparator();
+
+ clearAction = new QAction(ICONS.ACT_CLEAR, tr("Clear"), ui->tableWidget);
+ connect(clearAction, &QAction::triggered, this, &StatusField::reset);
+ menu->addAction(clearAction);
+
+ connect(ui->tableWidget, &QWidget::customContextMenuRequested, this, &StatusField::customContextMenuRequested);
+}
+
+void StatusField::readRecentMessages()
+{
+ noFlashing = true;
+ foreach (const QString& msg, NotifyManager::getInstance()->getRecentInfos())
+ info(msg);
+
+ foreach (const QString& msg, NotifyManager::getInstance()->getRecentWarnings())
+ warn(msg);
+
+ foreach (const QString& msg, NotifyManager::getInstance()->getRecentErrors())
+ error(msg);
+
+ noFlashing = false;
+}
+
+void StatusField::customContextMenuRequested(const QPoint &pos)
+{
+ copyAction->setEnabled(ui->tableWidget->selectionModel()->selectedRows().size() > 0);
+
+ menu->popup(ui->tableWidget->mapToGlobal(pos));
+}
+
+void StatusField::reset()
+{
+ for (QAbstractAnimation* anim : itemAnimations)
+ anim->stop();
+
+ itemAnimations.clear();
+ ui->tableWidget->clear();
+ ui->tableWidget->setRowCount(0);
+}
+
+void StatusField::fontChanged(const QVariant& variant)
+{
+ QFont newFont = variant.value<QFont>();
+ QFont font;
+ for (int row = 0; row < ui->tableWidget->rowCount(); row++)
+ {
+ font = ui->tableWidget->item(row, 1)->font();
+ font = newFont.resolve(font);
+ for (int col = 1; col <= 2; col++)
+ ui->tableWidget->item(row, col)->setFont(font);
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.h b/SQLiteStudio3/guiSQLiteStudio/statusfield.h
new file mode 100644
index 0000000..ac07f51
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.h
@@ -0,0 +1,58 @@
+#ifndef STATUSFIELD_H
+#define STATUSFIELD_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QVariant>
+#include <QDockWidget>
+
+class QMenu;
+class QAbstractAnimation;
+class QTableWidgetItem;
+
+namespace Ui {
+ class StatusField;
+}
+
+class GUI_API_EXPORT StatusField : public QDockWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit StatusField(QWidget *parent = 0);
+ ~StatusField();
+
+ bool hasMessages() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void addEntry(const QIcon& icon, const QString& text, const QColor &color);
+ void flashItems(const QList<QTableWidgetItem*>& items, const QColor& color);
+ void setupMenu();
+ void readRecentMessages();
+
+ Ui::StatusField *ui = nullptr;
+ QMenu* menu = nullptr;
+ QAction* copyAction = nullptr;
+ QAction* clearAction = nullptr;
+ QList<QAbstractAnimation*> itemAnimations;
+ bool noFlashing = false;
+
+ static const int timeStampColumnWidth = 70;
+ static const int itemCountLimit = 30;
+ static constexpr const char* timeStampFormat = "hh:mm:ss";
+
+ private slots:
+ void customContextMenuRequested(const QPoint& pos);
+ void info(const QString& text);
+ void warn(const QString& text);
+ void error(const QString& text);
+ void reset();
+ void fontChanged(const QVariant& variant);
+
+ signals:
+ void linkActivated(const QString& link);
+};
+
+#endif // STATUSFIELD_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.ui b/SQLiteStudio3/guiSQLiteStudio/statusfield.ui
new file mode 100644
index 0000000..c36828c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StatusField</class>
+ <widget class="QDockWidget" name="StatusField">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>708</width>
+ <height>106</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Status</string>
+ </property>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="TableWidget" name="tableWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Ignored">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>60</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="gridStyle">
+ <enum>Qt::NoPen</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>3</number>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="horizontalHeaderDefaultSectionSize">
+ <number>24</number>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderDefaultSectionSize">
+ <number>18</number>
+ </attribute>
+ <column/>
+ <column/>
+ <column/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>TableWidget</class>
+ <extends>QTableWidget</extends>
+ <header>common/tablewidget.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h b/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h
new file mode 100644
index 0000000..15b6c2c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h
@@ -0,0 +1,17 @@
+#ifndef SYNTAXHIGHLIGHTERPLUGIN_H
+#define SYNTAXHIGHLIGHTERPLUGIN_H
+
+#include "guiSQLiteStudio_global.h"
+#include "plugins/plugin.h"
+
+class QWidget;
+class QSyntaxHighlighter;
+
+class GUI_API_EXPORT SyntaxHighlighterPlugin : virtual public Plugin
+{
+ public:
+ virtual QString getLanguageName() const = 0;
+ virtual QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const = 0;
+};
+
+#endif // SYNTAXHIGHLIGHTERPLUGIN_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp
new file mode 100644
index 0000000..915ca9a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp
@@ -0,0 +1,299 @@
+#include "taskbar.h"
+#include "mainwindow.h"
+#include <QMouseEvent>
+#include <QMimeData>
+#include <QDataStream>
+#include <QDrag>
+#include <QToolButton>
+#include <QCursor>
+#include <QAction>
+#include <QStyle>
+#include <QRubberBand>
+#include <QApplication>
+#include <QDebug>
+#include <QMenu>
+
+TaskBar::TaskBar(const QString& title, QWidget *parent) :
+ QToolBar(title, parent), taskGroup(this)
+{
+ init();
+}
+
+TaskBar::TaskBar(QWidget* parent) :
+ QToolBar(parent), taskGroup(this)
+{
+ init();
+}
+
+QAction* TaskBar::addTask(const QIcon& icon, const QString& text)
+{
+ // A workaround for QAction button (or QToolBar itself) that takes over (and doesn't propagate) mousePressEvent.
+ QAction* action = QToolBar::addAction(icon, text);
+ tasks << action;
+ QToolButton* btn = getToolButton(action);
+ btn->setMaximumWidth(400);
+ if (!btn)
+ return action;
+
+ taskGroup.addAction(action);
+ connect(btn, SIGNAL(pressed()), this, SLOT(mousePressed()));
+ return action;
+}
+
+void TaskBar::removeTask(QAction* action)
+{
+ tasks.removeOne(action);
+ taskGroup.removeAction(action);
+ removeAction(action);
+}
+
+QList<QAction*> TaskBar::getTasks() const
+{
+ return tasks;
+}
+
+void TaskBar::init()
+{
+ setAcceptDrops(true);
+}
+
+void TaskBar::mousePressed()
+{
+ dragStartPosition = mapFromGlobal(QCursor::pos());
+ dragStartTask = actionAt(dragStartPosition);
+ if (dragStartTask)
+ dragStartTask->trigger();
+}
+
+int TaskBar::getActiveTaskIdx()
+{
+ QAction* checked = taskGroup.checkedAction();
+ if (!checked)
+ {
+ // Looks like no tasks yet.
+ return -1;
+ }
+
+ return tasks.indexOf(checked);
+}
+
+void TaskBar::nextTask()
+{
+ int idx = getActiveTaskIdx() + 1;
+ if (tasks.size() <= idx)
+ return;
+
+ tasks[idx]->trigger();
+}
+
+void TaskBar::prevTask()
+{
+ int idx = getActiveTaskIdx() - 1;
+ if (idx < 0)
+ return;
+
+ tasks[idx]->trigger();
+}
+
+void TaskBar::initContextMenu(ExtActionContainer* mainWin)
+{
+ // MainWindow is passed as argument to this function, so it's not referenced with MAINWINDOW macro,
+ // because that macro causes MainWindow initialization and this caused endless loop.
+ taskMenu = new QMenu(this);
+ taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_WINDOW));
+ taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_OTHER_WINDOWS));
+ taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_ALL_WINDOWS));
+ taskMenu->addSeparator();
+ taskMenu->addAction(mainWin->getAction(MainWindow::RESTORE_WINDOW));
+ taskMenu->addAction(mainWin->getAction(MainWindow::RENAME_WINDOW));
+
+ connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(taskBarMenuRequested(QPoint)));
+}
+
+void TaskBar::taskBarMenuRequested(const QPoint &p)
+{
+
+ QAction* task = actionAt(p);
+ bool taskClicked = (task != nullptr);
+ if (taskClicked)
+ task->trigger();
+
+ MAINWINDOW->updateWindowActions();
+ taskMenu->popup(mapToGlobal(p));
+}
+
+QToolButton* TaskBar::getToolButton(QAction* action)
+{
+ return dynamic_cast<QToolButton*>(widgetForAction(action));
+}
+
+QAction* TaskBar::getNextClosestAction(const QPoint& position)
+{
+ QToolButton* btn = nullptr;
+ if (orientation() == Qt::Horizontal)
+ {
+ foreach (QAction* action, tasks)
+ {
+ btn = getToolButton(action);
+ if (btn && btn->x() >= position.x())
+ return action;
+ }
+ }
+ else
+ {
+ foreach (QAction* action, tasks)
+ {
+ btn = getToolButton(action);
+ if (btn && btn->y() >= position.y())
+ return action;
+ }
+ }
+ return nullptr;
+}
+
+void TaskBar::mousePressEvent(QMouseEvent* event)
+{
+ QToolBar::mousePressEvent(event);
+ dragStartTask = nullptr;
+}
+
+void TaskBar::mouseMoveEvent(QMouseEvent *event)
+{
+ if (!handleMouseMoveEvent(event))
+ QToolBar::mouseMoveEvent(event);
+}
+
+bool TaskBar::handleMouseMoveEvent(QMouseEvent* event)
+{
+ if (!(event->buttons() & Qt::LeftButton))
+ return false;
+
+ if (!dragStartTask)
+ return false;
+
+ if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance())
+ return false;
+
+ QDrag *drag = new QDrag(this);
+ drag->setMimeData(generateMimeData());
+
+ dragStartIndex = tasks.indexOf(dragStartTask);
+ return true;
+}
+
+void TaskBar::dragEnterEvent(QDragEnterEvent *event)
+{
+ if (!event->mimeData()->hasFormat(mimeDataId))
+ return;
+
+ dragTaskTo(dragStartTask, event->pos());
+ event->acceptProposedAction();
+}
+
+void TaskBar::dragMoveEvent(QDragMoveEvent* event)
+{
+ if (!event->mimeData()->hasFormat(mimeDataId))
+ return;
+
+ dragTaskTo(dragStartTask, event->pos());
+ event->acceptProposedAction();
+}
+
+void TaskBar::dropEvent(QDropEvent *event)
+{
+ event->acceptProposedAction();
+}
+
+void TaskBar::dragTaskTo(QAction* task, const QPoint& position)
+{
+ int idx = getDropPositionIndex(task, position);
+ if (idx < 0)
+ return;
+
+ dragTaskTo(task, idx);
+}
+
+void TaskBar::dragTaskTo(QAction* task, int positionIndex)
+{
+ if (positionIndex < 0)
+ return;
+
+ removeAction(task);
+
+ if (positionIndex >= tasks.size())
+ addAction(task);
+ else
+ insertAction(tasks.at(positionIndex), task);
+
+ connect(getToolButton(task), SIGNAL(pressed()), this, SLOT(mousePressed()));
+ dragCurrentIndex = positionIndex;
+}
+
+QMimeData* TaskBar::generateMimeData()
+{
+ QMimeData *mimeData = new QMimeData();
+ mimeData->setData(mimeDataId, QByteArray());
+ return mimeData;
+}
+
+int TaskBar::getDropPositionIndex(QAction* task, const QPoint& position)
+{
+ QAction* action = actionAt(position);
+ if (!action)
+ action = getNextClosestAction(position);
+
+ if (!action)
+ return tasks.size(); // We moved completly out of actions range, report last possible position.
+
+ if (action == task)
+ return -1;
+
+ int newIdx = tasks.indexOf(action);
+
+ QToolButton* btn = getToolButton(action);
+ int actionBeginPos;
+ int actionEndPos;
+ int newPos;
+ if (orientation() == Qt::Horizontal)
+ {
+ actionBeginPos = btn->x();
+ actionEndPos = btn->x() + btn->width();
+ newPos = position.x();
+ }
+ else
+ {
+ actionBeginPos = btn->y();
+ actionEndPos = btn->y() + btn->height();
+ newPos = position.y();
+ }
+
+ if (dragCurrentIndex <= newIdx)
+ {
+ // D&D from left to right
+ if (newPos >= actionBeginPos)
+ return newIdx + 1;
+ else
+ return newIdx;
+
+ }
+ else
+ {
+ // D&D from right to left
+ if (newPos <= actionEndPos)
+ return newIdx;
+ else
+ return newIdx + 1;
+ }
+
+ return -1; // This also should never happen. All cases should be covered above. But just in case.
+}
+
+bool TaskBar::isEmpty()
+{
+ return tasks.isEmpty();
+}
+
+int TaskBar::count()
+{
+ return tasks.count();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/taskbar.h b/SQLiteStudio3/guiSQLiteStudio/taskbar.h
new file mode 100644
index 0000000..e858f7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/taskbar.h
@@ -0,0 +1,73 @@
+#ifndef TASKBAR_H
+#define TASKBAR_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QToolBar>
+#include <QActionGroup>
+
+class QMimeData;
+class QToolButton;
+class QRubberBand;
+class ExtActionContainer;
+
+// TODO enclose task<->mdiWindow relation inside a task class and make it managed by taskbar, not by mdiarea
+class GUI_API_EXPORT TaskBar : public QToolBar
+{
+ Q_OBJECT
+ public:
+ TaskBar(const QString& title, QWidget *parent = 0);
+ explicit TaskBar(QWidget *parent = 0);
+
+ QAction* addTask(const QIcon& icon, const QString& text);
+ void removeTask(QAction* action);
+ QList<QAction*> getTasks() const;
+ bool isEmpty();
+ int count();
+
+ protected:
+ void mousePressEvent(QMouseEvent* event);
+ void mouseMoveEvent(QMouseEvent* event);
+ void dragEnterEvent(QDragEnterEvent* event);
+ void dragMoveEvent(QDragMoveEvent* event);
+ void dropEvent(QDropEvent* event);
+
+ private:
+ void init();
+ bool handleMouseMoveEvent(QMouseEvent* event);
+ QToolButton* getToolButton(QAction* action);
+ QAction* getNextClosestAction(const QPoint& position);
+ void dragTaskTo(QAction* task, const QPoint& position);
+ void dragTaskTo(QAction* task, int positionIndex);
+ QAction* getDragTask(const QMimeData* data);
+ QMimeData* generateMimeData();
+ int getActiveTaskIdx();
+
+ constexpr static const char* mimeDataId = "application/x-sqlitestudio-taskbar-task";
+
+ /**
+ * @brief getDropPositionIndex
+ * @param task
+ * @param position
+ * @return Index of action in actions() that drag should be inserting dropped item just before, or -1 to indicate "at the end".
+ */
+ int getDropPositionIndex(QAction* task, const QPoint& position);
+
+ QActionGroup taskGroup;
+ QList<QAction*> tasks;
+ QAction* dragStartTask = nullptr;
+ QPoint dragStartPosition;
+ int dragStartIndex;
+ int dragCurrentIndex;
+ QMenu* taskMenu = nullptr;
+
+ public slots:
+ void nextTask();
+ void prevTask();
+ void initContextMenu(ExtActionContainer *mainWin);
+
+ private slots:
+ void taskBarMenuRequested(const QPoint& p);
+ void mousePressed();
+};
+
+#endif // TASKBAR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp b/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp
new file mode 100644
index 0000000..c2dfa8f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp
@@ -0,0 +1,68 @@
+#include "uiconfig.h"
+#include <QApplication>
+#include <QPlainTextEdit>
+#include <QStyle>
+#include <QStandardItem>
+#include <QDir>
+
+namespace Cfg
+{
+ QVariant getStyleDefaultValue()
+ {
+ return QApplication::style()->objectName();
+ }
+
+ QVariant getDefaultTextEditorFont()
+ {
+ QPlainTextEdit monoEdit;
+ QFont font = monoEdit.document()->defaultFont();
+#ifdef Q_OS_MACX
+ font.setFamily("Courier New");
+#elif defined(Q_OS_WIN32)
+ font.setFamily("Consolas");
+#else
+ font.setFamily("DejaVu Sans Mono");
+#endif
+ return QVariant::fromValue<QFont>(font);
+ }
+
+ QVariant getDefaultItemViewFont()
+ {
+ QStandardItem it;
+ return it.font();
+ }
+
+ QVariant getDefaultDbTreeLabelFont()
+ {
+ QFont font = getDefaultItemViewFont().value<QFont>();
+#ifdef Q_OS_WIN32
+ font.setPointSize(font.pointSize() - 1);
+#else
+ font.setPointSize(font.pointSize() - 2);
+#endif
+ return font;
+ }
+
+}
+
+CFG_DEFINE(Ui)
+
+void setFileDialogInitPathByFile(const QString& filePath)
+{
+ if (filePath.isNull())
+ return;
+
+ QDir newDir(filePath);
+ newDir.cdUp();
+ setFileDialogInitPath(newDir.absolutePath());
+}
+
+void setFileDialogInitPath(const QString& path)
+{
+ CFG_UI.General.FileDialogLastPath.set(path);
+}
+
+QString getFileDialogInitPath()
+{
+ return CFG_UI.General.FileDialogLastPath.get();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiconfig.h b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h
new file mode 100644
index 0000000..7b645e1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h
@@ -0,0 +1,90 @@
+#ifndef UICONFIG_H
+#define UICONFIG_H
+
+#include "guiSQLiteStudio_global.h"
+#include "config_builder.h"
+#include <QFont>
+#include <QHash>
+#include <QColor>
+
+namespace Cfg
+{
+ GUI_API_EXPORT QVariant getStyleDefaultValue();
+ GUI_API_EXPORT QVariant getDefaultTextEditorFont();
+ GUI_API_EXPORT QVariant getDefaultItemViewFont();
+ GUI_API_EXPORT QVariant getDefaultDbTreeLabelFont();
+ typedef QHash<QString,QVariant> Session;
+ typedef QHash<QString,QVariant> DataEditorsOrder;
+}
+
+CFG_CATEGORIES(Ui,
+ CFG_CATEGORY(Fonts,
+ CFG_ENTRY(QFont, SqlEditor, &Cfg::getDefaultTextEditorFont)
+ CFG_ENTRY(QFont, DataView, &Cfg::getDefaultItemViewFont)
+ CFG_ENTRY(QFont, DbTree, &Cfg::getDefaultItemViewFont)
+ CFG_ENTRY(QFont, DbTreeLabel, &Cfg::getDefaultDbTreeLabelFont)
+ CFG_ENTRY(QFont, StatusField, &Cfg::getDefaultItemViewFont)
+ )
+
+ CFG_CATEGORY(Colors,
+ CFG_ENTRY(QColor, SqlEditorParenthesisBg, Qt::green)
+ CFG_ENTRY(QColor, SqlEditorCurrentLineBg, QColor(Qt::cyan).lighter(190))
+ CFG_ENTRY(QColor, SqlEditorLineNumAreaBg, QColor(Qt::lightGray).lighter(120))
+ CFG_ENTRY(QColor, SqlEditorValidObject, Qt::blue)
+ CFG_ENTRY(QColor, SqlEditorForeground, Qt::black)
+ CFG_ENTRY(QColor, SqlEditorStringFg, Qt::darkGreen)
+ CFG_ENTRY(QColor, SqlEditorKeywordFg, Qt::black)
+ CFG_ENTRY(QColor, SqlEditorBindParamFg, Qt::darkMagenta)
+ CFG_ENTRY(QColor, SqlEditorBlobFg, Qt::darkCyan)
+ CFG_ENTRY(QColor, SqlEditorCommentFg, Qt::darkGray)
+ CFG_ENTRY(QColor, SqlEditorNumberFg, Qt::darkBlue)
+ CFG_ENTRY(QColor, DataUncommitedError, Qt::red)
+ CFG_ENTRY(QColor, DataUncommited, Qt::blue)
+ CFG_ENTRY(QColor, DataNullFg, Qt::gray)
+ CFG_ENTRY(QColor, DataDeletedBg, Qt::gray)
+ CFG_ENTRY(QColor, DbTreeLabelsFg, Qt::blue)
+ CFG_ENTRY(QColor, StatusFieldInfoFg, Qt::darkBlue)
+ CFG_ENTRY(QColor, StatusFieldWarnFg, Qt::black)
+ CFG_ENTRY(QColor, StatusFieldErrorFg, Qt::red)
+ CFG_ENTRY(QColor, JavaScriptFg, "#000000")
+ CFG_ENTRY(QColor, JavaScriptComment, "#808080")
+ CFG_ENTRY(QColor, JavaScriptNumber, "#008000")
+ CFG_ENTRY(QColor, JavaScriptString, "#800000")
+ CFG_ENTRY(QColor, JavaScriptOperator, "#808000")
+ CFG_ENTRY(QColor, JavaScriptIdentifier, "#000020")
+ CFG_ENTRY(QColor, JavaScriptKeyword, "#000080")
+ CFG_ENTRY(QColor, JavaScriptBuiltIn, "#008080")
+ CFG_ENTRY(QColor, JavaScriptMarker, "#ffff00")
+ )
+
+ CFG_CATEGORY(General,
+ CFG_ENTRY(QString, DataViewTabs, QString())
+ CFG_ENTRY(QString, SqlEditorTabs, QString())
+ CFG_ENTRY(QString, SqlEditorDbListOrder, "LikeDbTree")
+ CFG_ENTRY(bool, ExpandTables, true)
+ CFG_ENTRY(bool, ExpandViews, true)
+ CFG_ENTRY(bool, SortObjects, true)
+ CFG_ENTRY(bool, SortColumns, false)
+ CFG_ENTRY(bool, ExecuteCurrentQueryOnly, true)
+ CFG_ENTRY(bool, ShowSystemObjects, false)
+ CFG_ENTRY(bool, ShowDbTreeLabels, true) // any labels at all
+ CFG_ENTRY(bool, ShowRegularTableLabels, false)
+ CFG_ENTRY(bool, ShowVirtualTableLabels, true)
+ CFG_ENTRY(int, NumberOfRowsPerPage, 1000)
+ CFG_ENTRY(QString, Style, &Cfg::getStyleDefaultValue)
+ CFG_ENTRY(Cfg::Session, Session, Cfg::Session())
+ CFG_ENTRY(bool, DontShowDdlPreview, false)
+ CFG_ENTRY(bool, OpenTablesOnData, false)
+ CFG_ENTRY(bool, OpenViewsOnData, false)
+ CFG_ENTRY(Cfg::DataEditorsOrder, DataEditorsOrder, Cfg::DataEditorsOrder())
+ CFG_ENTRY(QString, FileDialogLastPath, QString())
+ )
+)
+
+QString getFileDialogInitPath();
+void setFileDialogInitPath(const QString& path);
+void setFileDialogInitPathByFile(const QString& filePath);
+
+#define CFG_UI CFG_INSTANCE(Ui)
+
+#endif // UICONFIG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp
new file mode 100644
index 0000000..f72eb35
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp
@@ -0,0 +1,30 @@
+#include "uicustomicon.h"
+#include "iconmanager.h"
+#include <QLabel>
+#include <QAbstractButton>
+
+#define TRY_ICON_WITH(Type, Widget, Method, Icon) \
+ if (dynamic_cast<Type*>(Widget))\
+ {\
+ dynamic_cast<Type*>(Widget)->Method(Icon);\
+ return;\
+ }
+
+UiCustomIcon::UiCustomIcon()
+{
+}
+
+const char* UiCustomIcon::getPropertyName() const
+{
+ return "customIcon";
+}
+
+void UiCustomIcon::handle(QWidget* widget, const QVariant& value)
+{
+ QString iconName = value.toString();
+ QIcon* icon = ICONMANAGER->getIcon(iconName);
+ if (!icon)
+ return;
+
+ TRY_ICON_WITH(QAbstractButton, widget, setIcon, *icon);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h
new file mode 100644
index 0000000..8332970
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h
@@ -0,0 +1,16 @@
+#ifndef UICUSTOMICON_H
+#define UICUSTOMICON_H
+
+#include "guiSQLiteStudio_global.h"
+#include "uiloaderpropertyhandler.h"
+
+class GUI_API_EXPORT UiCustomIcon : public UiLoaderPropertyHandler
+{
+ public:
+ UiCustomIcon();
+
+ const char* getPropertyName() const;
+ void handle(QWidget* widget, const QVariant& value);
+};
+
+#endif // UICUSTOMICON_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp
new file mode 100644
index 0000000..9504bda
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp
@@ -0,0 +1,116 @@
+#include "uidebug.h"
+#include "common/unused.h"
+#include "qio.h"
+#include "debugconsole.h"
+#include "common/global.h"
+#include <QTime>
+
+DebugConsole* sqliteStudioUiDebugConsole = nullptr;
+MsgHandlerThreadProxy* msgHandlerThreadProxy = nullptr;
+bool UI_DEBUG_ENABLED = false;
+bool UI_DEBUG_CONSOLE = true;
+
+void uiMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ if (!UI_DEBUG_ENABLED)
+ return;
+
+ UNUSED(context);
+
+ static const QString dbgMsg = QStringLiteral("[%1] DEBUG: %2");
+ static const QString wrnMsg = QStringLiteral("[%1] WARNING: %2");
+ static const QString criMsg = QStringLiteral("[%1] CRITICAL: %2");
+ static const QString fatMsg = QStringLiteral("[%1] FATAL: %2");
+
+ QString time = QTime::currentTime().toString("HH:mm:ss.zzz");
+ switch (type) {
+ case QtDebugMsg:
+ msgHandlerThreadProxy->debug(dbgMsg.arg(time, msg));
+ break;
+ case QtWarningMsg:
+ msgHandlerThreadProxy->warn(wrnMsg.arg(time, msg));
+ break;
+ case QtCriticalMsg:
+ msgHandlerThreadProxy->critical(criMsg.arg(time, msg));
+ break;
+ case QtFatalMsg:
+ msgHandlerThreadProxy->fatal(fatMsg.arg(time, msg));
+ abort();
+ }
+}
+
+void setUiDebug(bool enabled, bool useUiConsole)
+{
+ UI_DEBUG_ENABLED = enabled;
+ UI_DEBUG_CONSOLE = useUiConsole;
+ safe_delete(msgHandlerThreadProxy);
+ safe_delete(sqliteStudioUiDebugConsole);
+ if (enabled)
+ {
+ if (useUiConsole)
+ sqliteStudioUiDebugConsole = new DebugConsole();
+
+ msgHandlerThreadProxy = new MsgHandlerThreadProxy();
+ }
+}
+
+void showUiDebugConsole()
+{
+ if (sqliteStudioUiDebugConsole)
+ sqliteStudioUiDebugConsole->show();
+}
+
+bool isDebugEnabled()
+{
+ return UI_DEBUG_ENABLED;
+}
+
+bool isDebugConsoleEnabled()
+{
+ return UI_DEBUG_CONSOLE;
+}
+
+MsgHandlerThreadProxy::MsgHandlerThreadProxy(QObject *parent) :
+ QObject(parent)
+{
+ if (sqliteStudioUiDebugConsole)
+ {
+ connect(this, SIGNAL(debugRequested(QString)), sqliteStudioUiDebugConsole, SLOT(debug(QString)));
+ connect(this, SIGNAL(warnRequested(QString)), sqliteStudioUiDebugConsole, SLOT(warning(QString)));
+ connect(this, SIGNAL(criticalRequested(QString)), sqliteStudioUiDebugConsole, SLOT(critical(QString)));
+ connect(this, SIGNAL(fatalRequested(QString)), sqliteStudioUiDebugConsole, SLOT(fatal(QString)));
+ }
+ else
+ {
+ connect(this, SIGNAL(debugRequested(QString)), this, SLOT(print(QString)));
+ connect(this, SIGNAL(warnRequested(QString)), this, SLOT(print(QString)));
+ connect(this, SIGNAL(criticalRequested(QString)), this, SLOT(print(QString)));
+ connect(this, SIGNAL(fatalRequested(QString)), this, SLOT(print(QString)));
+ }
+}
+
+void MsgHandlerThreadProxy::debug(const QString &msg)
+{
+ emit debugRequested(msg);
+}
+
+void MsgHandlerThreadProxy::warn(const QString &msg)
+{
+ emit warnRequested(msg);
+}
+
+void MsgHandlerThreadProxy::critical(const QString &msg)
+{
+ emit criticalRequested(msg);
+}
+
+void MsgHandlerThreadProxy::fatal(const QString &msg)
+{
+ emit fatalRequested(msg);
+}
+
+void MsgHandlerThreadProxy::print(const QString& txt)
+{
+ qOut << txt << "\n";
+ qOut.flush();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.h b/SQLiteStudio3/guiSQLiteStudio/uidebug.h
new file mode 100644
index 0000000..d1b04b7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.h
@@ -0,0 +1,36 @@
+#ifndef UIDEBUG_H
+#define UIDEBUG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QtDebug>
+
+class GUI_API_EXPORT MsgHandlerThreadProxy : public QObject
+{
+ Q_OBJECT
+
+ public:
+ explicit MsgHandlerThreadProxy(QObject* parent = 0);
+
+ public slots:
+ void debug(const QString& msg);
+ void warn(const QString& msg);
+ void critical(const QString& msg);
+ void fatal(const QString& msg);
+
+ signals:
+ void debugRequested(const QString& msg);
+ void warnRequested(const QString& msg);
+ void criticalRequested(const QString& msg);
+ void fatalRequested(const QString& msg);
+
+ private slots:
+ void print(const QString& txt);
+};
+
+GUI_API_EXPORT void uiMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+GUI_API_EXPORT void setUiDebug(bool enabled, bool useUiConsole = true);
+GUI_API_EXPORT void showUiDebugConsole();
+GUI_API_EXPORT bool isDebugEnabled();
+GUI_API_EXPORT bool isDebugConsoleEnabled();
+
+#endif // UIDEBUG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp b/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp
new file mode 100644
index 0000000..cc02b16
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp
@@ -0,0 +1,95 @@
+#include "uiloader.h"
+#include "common/unused.h"
+#include "uiloaderpropertyhandler.h"
+#include "uiscriptingcombo.h"
+#include "uiscriptingedit.h"
+#include "uicustomicon.h"
+#include "uiurlbutton.h"
+#include "sqlview.h"
+#include "common/configradiobutton.h"
+#include "common/configcombobox.h"
+#include "common/fileedit.h"
+#include "common/colorbutton.h"
+#include <QComboBox>
+#include <QDebug>
+#include <QMetaProperty>
+#include <QXmlSimpleReader>
+
+#define REGISTER_WIDGET(Class) \
+ registerWidgetClass(#Class, [](QWidget* parent, const QString& name) -> QWidget*\
+ {\
+ Class* w = new Class(parent);\
+ w->setObjectName(name);\
+ return w;\
+ })
+
+UiLoader::UiLoader(QObject *parent) :
+ QUiLoader(parent)
+{
+ registerPropertyHandler(new UiScriptingCombo());
+ registerPropertyHandler(new UiScriptingEdit());
+ registerPropertyHandler(new UiCustomIcon());
+ registerPropertyHandler(new UiUrlButton());
+
+ REGISTER_WIDGET(ConfigRadioButton);
+ REGISTER_WIDGET(ConfigComboBox);
+ REGISTER_WIDGET(FileEdit);
+ REGISTER_WIDGET(ColorButton);
+ REGISTER_WIDGET(SqlView);
+}
+
+QWidget* UiLoader::createWidget(const QString& className, QWidget* parent, const QString& name)
+{
+ QWidget* w = nullptr;
+ if (registeredClasses.contains(className))
+ w = registeredClasses[className](parent, name);
+ else
+ w = QUiLoader::createWidget(className, parent, name);
+
+ return w;
+}
+
+void UiLoader::registerWidgetClass(const QString& className, FactoryFunction factoryFunction)
+{
+ registeredClasses[className] = factoryFunction;
+}
+
+void UiLoader::handlePropertiesRecursively(QWidget* widget)
+{
+ if (widget->dynamicPropertyNames().size() > 0)
+ handleProperties(widget);
+
+ for (QWidget* w : widget->findChildren<QWidget*>())
+ handleProperties(w);
+}
+
+void UiLoader::handleProperties(QWidget* widget)
+{
+ QVariant propValue;
+ for (UiLoaderPropertyHandler* handler : propertyHandlers)
+ {
+ propValue = widget->property(handler->getPropertyName());
+ if (propValue.isValid())
+ handler->handle(widget, propValue);
+ }
+}
+
+QWidget* UiLoader::load(const QString& path)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ qCritical() << "FormManager was unable to open ui file:" << path;
+ return nullptr;
+ }
+
+ QWidget* w = QUiLoader::load(&file, nullptr);
+ handlePropertiesRecursively(w);
+ return w;
+}
+
+void UiLoader::registerPropertyHandler(UiLoaderPropertyHandler* handler)
+{
+ propertyHandlers << handler;
+}
+
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloader.h b/SQLiteStudio3/guiSQLiteStudio/uiloader.h
new file mode 100644
index 0000000..5d16bcb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiloader.h
@@ -0,0 +1,33 @@
+#ifndef UILOADER_H
+#define UILOADER_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QUiLoader>
+#include <QHash>
+#include <QStack>
+#include <QXmlDefaultHandler>
+
+class UiLoaderPropertyHandler;
+
+class GUI_API_EXPORT UiLoader : public QUiLoader
+{
+ Q_OBJECT
+ public:
+ typedef std::function<QWidget*(QWidget*, const QString&)> FactoryFunction;
+
+ explicit UiLoader(QObject *parent = 0);
+
+ QWidget* createWidget(const QString & className, QWidget * parent = 0, const QString & name = QString());
+ void registerWidgetClass(const QString& className, FactoryFunction factoryFunction);
+ void registerPropertyHandler(UiLoaderPropertyHandler* handler);
+ QWidget* load(const QString& path);
+
+ private:
+ void handlePropertiesRecursively(QWidget* widget);
+ void handleProperties(QWidget* widget);
+
+ QHash<QString, FactoryFunction> registeredClasses;
+ QList<UiLoaderPropertyHandler*> propertyHandlers;
+};
+
+#endif // UILOADER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h b/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h
new file mode 100644
index 0000000..506d588
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h
@@ -0,0 +1,16 @@
+#ifndef UILOADERPROPERTYHANDLER_H
+#define UILOADERPROPERTYHANDLER_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QVariant>
+
+class QWidget;
+
+class GUI_API_EXPORT UiLoaderPropertyHandler
+{
+ public:
+ virtual const char* getPropertyName() const = 0;
+ virtual void handle(QWidget* widget, const QVariant& value) = 0;
+};
+
+#endif // UILOADERPROPERTYHANDLER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp
new file mode 100644
index 0000000..b203873
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp
@@ -0,0 +1,26 @@
+#include "uiscriptingcombo.h"
+#include "services/pluginmanager.h"
+#include "plugins/scriptingplugin.h"
+#include <QComboBox>
+
+UiScriptingCombo::UiScriptingCombo()
+{
+}
+
+const char* UiScriptingCombo::getPropertyName() const
+{
+ return "ScriptingLangCombo";
+}
+
+void UiScriptingCombo::handle(QWidget* widget, const QVariant& value)
+{
+ QComboBox* cb = dynamic_cast<QComboBox*>(widget);
+ if (!cb)
+ return;
+
+ if (!value.toBool())
+ return;
+
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ cb->addItem(plugin->getLanguage());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h
new file mode 100644
index 0000000..ad9430f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h
@@ -0,0 +1,16 @@
+#ifndef UISCRIPTINGCOMBO_H
+#define UISCRIPTINGCOMBO_H
+
+#include "guiSQLiteStudio_global.h"
+#include "uiloaderpropertyhandler.h"
+
+class GUI_API_EXPORT UiScriptingCombo : public UiLoaderPropertyHandler
+{
+ public:
+ UiScriptingCombo();
+
+ const char* getPropertyName() const;
+ void handle(QWidget* widget, const QVariant& value);
+};
+
+#endif // UISCRIPTINGCOMBO_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp
new file mode 100644
index 0000000..329af53
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp
@@ -0,0 +1,75 @@
+#include "uiscriptingedit.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "syntaxhighlighterplugin.h"
+#include "pluginservicebase.h"
+#include <QPlainTextEdit>
+#include <QTextEdit>
+#include <QCoreApplication>
+#include <QSyntaxHighlighter>
+
+UiScriptingEdit::UiScriptingEdit()
+{
+}
+
+const char* UiScriptingEdit::getPropertyName() const
+{
+ return "scriptingEdit";
+}
+
+void UiScriptingEdit::handle(QWidget* widget, const QVariant& value)
+{
+ if (!value.toBool())
+ return;
+
+ new EditUpdater(widget); // widget becomes its parent and owns it
+}
+
+UiScriptingEdit::EditUpdater::EditUpdater(QWidget* widget) :
+ QObject(widget), watchedWidget(widget)
+{
+ widget->installEventFilter(this);
+}
+
+bool UiScriptingEdit::EditUpdater::eventFilter(QObject* obj, QEvent* e)
+{
+ UNUSED(obj);
+ if (changingHighlighter)
+ return false;
+
+ if (e->type() != QEvent::DynamicPropertyChange)
+ return false;
+
+ if (dynamic_cast<QDynamicPropertyChangeEvent*>(e)->propertyName() != PluginServiceBase::LANG_PROPERTY_NAME)
+ return false;
+
+ QVariant prop = watchedWidget->property(PluginServiceBase::LANG_PROPERTY_NAME);
+ installNewHighlighter(prop);
+
+ return false;
+}
+
+void UiScriptingEdit::EditUpdater::installNewHighlighter(const QVariant& prop)
+{
+ QString lang = prop.toString();
+ if (lang == currentLang)
+ return;
+
+ // When highlighter is deleted, it causes textChanged() signal and so this method is called recurrently.
+ // To avoid inifinite recursion, the changingHighlighter is used to ignore property changes during deletion
+ // of the highlighter.
+ changingHighlighter = true;
+ safe_delete(currentHighlighter);
+ currentLang = QString();
+ changingHighlighter = false;
+
+ for (SyntaxHighlighterPlugin* plugin : PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ {
+ if (plugin->getLanguageName() != lang)
+ continue;
+
+ currentHighlighter = plugin->createSyntaxHighlighter(watchedWidget);
+ currentLang = lang;
+ break;
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h
new file mode 100644
index 0000000..fdd2df8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h
@@ -0,0 +1,37 @@
+#ifndef UISCRIPTINGEDIT_H
+#define UISCRIPTINGEDIT_H
+
+#include "uiloaderpropertyhandler.h"
+#include "common/global.h"
+#include "guiSQLiteStudio_global.h"
+#include <QObject>
+
+class QSyntaxHighlighter;
+
+class GUI_API_EXPORT UiScriptingEdit : public UiLoaderPropertyHandler
+{
+ public:
+ UiScriptingEdit();
+
+ const char* getPropertyName() const;
+ void handle(QWidget* widget, const QVariant& value);
+
+ private:
+ class EditUpdater : public QObject
+ {
+ public:
+ EditUpdater(QWidget* widget);
+
+ bool eventFilter(QObject* obj, QEvent* e);
+
+ private:
+ void installNewHighlighter(const QVariant& prop);
+
+ QWidget* watchedWidget = nullptr;
+ QString currentLang;
+ QSyntaxHighlighter* currentHighlighter = nullptr;
+ bool changingHighlighter = false;
+ };
+};
+
+#endif // UISCRIPTINGEDIT_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp
new file mode 100644
index 0000000..dcc3dc8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp
@@ -0,0 +1,27 @@
+#include "uiurlbutton.h"
+#include <QAbstractButton>
+#include <QDesktopServices>
+#include <QUrl>
+
+UiUrlButton::UiUrlButton()
+{
+}
+
+
+const char* UiUrlButton::getPropertyName() const
+{
+ return "openUrl";
+}
+
+void UiUrlButton::handle(QWidget* widget, const QVariant& value)
+{
+ QAbstractButton* btn = dynamic_cast<QAbstractButton*>(widget);
+ QString url = value.toString();
+ if (btn)
+ {
+ QObject::connect(btn, &QAbstractButton::clicked, [url](bool)
+ {
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+ });
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h
new file mode 100644
index 0000000..72438ad
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h
@@ -0,0 +1,16 @@
+#ifndef UIURLBUTTON_H
+#define UIURLBUTTON_H
+
+#include "guiSQLiteStudio_global.h"
+#include "uiloaderpropertyhandler.h"
+
+class GUI_API_EXPORT UiUrlButton : public UiLoaderPropertyHandler
+{
+ public:
+ UiUrlButton();
+
+ const char* getPropertyName() const;
+ void handle(QWidget* widget, const QVariant& value);
+};
+
+#endif // UIURLBUTTON_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp
new file mode 100644
index 0000000..182cdb2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp
@@ -0,0 +1,123 @@
+#include "uiutils.h"
+#include "services/config.h"
+#include "common/widgetstateindicator.h"
+#include "common/utils.h"
+#include <QObject>
+#include <QCheckBox>
+#include <QSpinBox>
+#include <QLineEdit>
+#include <QDateTimeEdit>
+#include <QFileDialog>
+#include <QStringList>
+#include <QSet>
+#include <QDebug>
+#include <QPainter>
+#include <QDesktopWidget>
+#include <QApplication>
+
+const QStringList pageSizes = {
+ "A4", "B5", "Letter", "Legal", "Executive", "A0", "A1", "A2", "A3", "A5", "A6", "A7", "A8", "A9", "B0", "B1",
+ "B10", "B2", "B3", "B4", "B6", "B7", "B8", "B9", "C5E", "Comm10E", "DLE", "Folio", "Ledger", "Tabloid", "Custom"
+};
+
+const QStringList pageSizesWithDimensions;
+
+QString getDbPath(const QString &startWith)
+{
+ QString dir = startWith;
+ if (dir.isNull())
+ dir = CFG->get("dialogCache", "lastDbDir").toString();
+
+ QStringList filters;
+ filters += QObject::tr("All SQLite databases")+" (*.db *.sdb *.sqlite *.db3 *.s3db *.sqlite3 *.sl3 *.db2 *.s2db *.sqlite2 *.sl2)";
+ filters += "SQLite3 (*.db3 *.s3db *.sqlite3 *.sl3)";
+ filters += "SQLite2 (*.db2 *.s2db *.sqlite2 *.sl2)";
+ filters += QObject::tr("All files")+" (*)";
+ QString filter = filters.join(";;");
+
+ QString path = QFileDialog::getSaveFileName(0, QObject::tr("Database file"), dir, filter, &filters[0], QFileDialog::DontConfirmOverwrite);
+ return path;
+}
+
+void setValidState(QWidget *widget, bool valid, const QString& message)
+{
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::ERROR);
+ INDICATOR(widget)->setVisible(!valid, valid ? QString() : message);
+}
+
+void setValidStateWihtTooltip(QWidget* widget, const QString& tooltip, bool valid, const QString& message)
+{
+ if (!valid)
+ {
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::ERROR);
+ INDICATOR(widget)->setVisible(true, message);
+ }
+ else
+ {
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::HINT);
+ INDICATOR(widget)->setVisible(widget->isEnabled(), tooltip);
+ }
+}
+
+void setValidStateWarning(QWidget* widget, const QString& warning)
+{
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::WARNING);
+ INDICATOR(widget)->setVisible(widget->isEnabled(), warning);
+}
+
+void setValidStateInfo(QWidget* widget, const QString& info)
+{
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::INFO);
+ INDICATOR(widget)->setVisible(widget->isEnabled(), info);
+}
+
+void setValidStateTooltip(QWidget* widget, const QString& tip)
+{
+ INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::HINT);
+ INDICATOR(widget)->setVisible(widget->isEnabled(), tip);
+}
+
+QString convertPageSize(QPagedPaintDevice::PageSize size)
+{
+ const int pageSizesSize = pageSizes.size();
+
+ int idx = static_cast<int>(size);
+ if (idx < 0 || idx >= pageSizesSize)
+ {
+ qDebug() << "Asked to convertPageSize() with page side enum value out of range:" << idx;
+ return QString::null;
+ }
+
+ return pageSizes[idx];
+}
+
+QPagedPaintDevice::PageSize convertPageSize(const QString& size)
+{
+ return static_cast<QPagedPaintDevice::PageSize>(indexOf(pageSizes, size, Qt::CaseInsensitive));
+}
+
+const QStringList& getAllPageSizes()
+{
+ return pageSizes;
+}
+
+QPixmap addOpacity(const QPixmap& input, float opacity)
+{
+ QPixmap output(input.size());
+ output.fill(Qt::transparent);
+ QPainter p(&output);
+ p.setOpacity(opacity);
+ p.drawPixmap(0, 0, input);
+ p.end();
+ return output;
+}
+
+void limitDialogWidth(QDialog* dialog)
+{
+ dialog->setMaximumWidth(QApplication::desktop()->availableGeometry().width());
+}
+
+void fixTextCursorSelectedText(QString& text)
+{
+ text.replace("\u2029", "\n");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.h b/SQLiteStudio3/guiSQLiteStudio/uiutils.h
new file mode 100644
index 0000000..b1c78f3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.h
@@ -0,0 +1,23 @@
+#ifndef UIUTILS_H
+#define UIUTILS_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QVariant>
+#include <QPagedPaintDevice>
+
+class QWidget;
+
+GUI_API_EXPORT QString getDbPath(const QString& startWith = QString::null);
+GUI_API_EXPORT void setValidState(QWidget* widget, bool valid, const QString& message = QString());
+GUI_API_EXPORT void setValidStateWihtTooltip(QWidget* widget, const QString& tooltip, bool valid, const QString& message = QString());
+GUI_API_EXPORT void setValidStateWarning(QWidget* widget, const QString& warning);
+GUI_API_EXPORT void setValidStateInfo(QWidget* widget, const QString& info);
+GUI_API_EXPORT void setValidStateTooltip(QWidget* widget, const QString& tip);
+GUI_API_EXPORT const QStringList& getAllPageSizes();
+GUI_API_EXPORT QString convertPageSize(QPagedPaintDevice::PageSize size);
+GUI_API_EXPORT QPagedPaintDevice::PageSize convertPageSize(const QString& size);
+GUI_API_EXPORT QPixmap addOpacity(const QPixmap& input, float opacity);
+GUI_API_EXPORT void limitDialogWidth(QDialog* dialog);
+GUI_API_EXPORT void fixTextCursorSelectedText(QString& text);
+
+#endif // UIUTILS_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp
new file mode 100644
index 0000000..4602050
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp
@@ -0,0 +1,137 @@
+#include "widgetresizer.h"
+#include "common/unused.h"
+#include <QMouseEvent>
+#include <QDebug>
+#include <QLayout>
+
+WidgetResizer::WidgetResizer(QWidget *parent) :
+ QWidget(parent)
+{
+ init();
+}
+
+WidgetResizer::~WidgetResizer()
+{
+}
+
+WidgetResizer::WidgetResizer(const Qt::Orientation& orientation, QWidget* parent) :
+ QWidget(parent), orientation(orientation)
+{
+ init();
+}
+
+void WidgetResizer::init()
+{
+ updateCursor();
+ updateWidth();
+ widgetMinimumSize = QSize(20, 20);
+}
+
+Qt::Orientation WidgetResizer::getOrientation() const
+{
+ return orientation;
+}
+
+void WidgetResizer::setOrientation(const Qt::Orientation& value)
+{
+ orientation = value;
+ updateCursor();
+}
+
+void WidgetResizer::updateCursor()
+{
+ switch (orientation)
+ {
+ case Qt::Horizontal:
+ setCursor(Qt::SplitHCursor);
+ break;
+ case Qt::Vertical:
+ setCursor(Qt::SplitVCursor);
+ break;
+ }
+}
+
+void WidgetResizer::updateWidth()
+{
+ setMinimumSize(width, width);
+}
+
+void WidgetResizer::mousePressEvent(QMouseEvent* event)
+{
+ UNUSED(event);
+ if (!widget)
+ return;
+
+ dragStartPosition = QCursor::pos();
+ dragStartSize = widget->size();
+}
+
+void WidgetResizer::mouseMoveEvent(QMouseEvent* event)
+{
+ UNUSED(event);
+ if (!widget)
+ return;
+
+ switch (orientation)
+ {
+ case Qt::Horizontal:
+ handleHorizontalMove(QCursor::pos().x());
+ break;
+ case Qt::Vertical:
+ handleVerticalMove(QCursor::pos().y());
+ break;
+ }
+}
+
+void WidgetResizer::handleHorizontalMove(int position)
+{
+ int newWidth = dragStartSize.width() + position - dragStartPosition.y();
+ if (newWidth < widgetMinimumSize.width())
+ return;
+
+ widget->setFixedWidth(newWidth);
+}
+
+void WidgetResizer::handleVerticalMove(int position)
+{
+ int newHeight = dragStartSize.height() + position - dragStartPosition.y();
+ if (newHeight < widgetMinimumSize.height())
+ return;
+
+ widget->setFixedHeight(newHeight);
+}
+
+int WidgetResizer::getWidth() const
+{
+ return width;
+}
+
+void WidgetResizer::setWidth(int value)
+{
+ width = value;
+}
+
+QWidget* WidgetResizer::getWidget() const
+{
+ return widget;
+}
+
+void WidgetResizer::setWidget(QWidget* value)
+{
+ widget = value;
+}
+
+QSize WidgetResizer::getWidgetMinimumSize() const
+{
+ return widgetMinimumSize;
+}
+
+void WidgetResizer::setWidgetMinimumSize(const QSize& value)
+{
+ widgetMinimumSize = value;
+}
+
+void WidgetResizer::setWidgetMinimumSize(int width, int height)
+{
+ widgetMinimumSize = QSize(width, height);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h
new file mode 100644
index 0000000..29e380b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h
@@ -0,0 +1,47 @@
+#ifndef WIDGETRESIZER_H
+#define WIDGETRESIZER_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+class GUI_API_EXPORT WidgetResizer : public QWidget
+{
+ Q_OBJECT
+ public:
+ explicit WidgetResizer(const Qt::Orientation& orientation, QWidget *parent = 0);
+ explicit WidgetResizer(QWidget *parent = 0);
+ ~WidgetResizer();
+
+ Qt::Orientation getOrientation() const;
+ void setOrientation(const Qt::Orientation& value);
+
+ int getWidth() const;
+ void setWidth(int value);
+
+ QWidget* getWidget() const;
+ void setWidget(QWidget* value);
+
+ QSize getWidgetMinimumSize() const;
+ void setWidgetMinimumSize(const QSize& value);
+ void setWidgetMinimumSize(int width, int height);
+
+ protected:
+ void mouseMoveEvent(QMouseEvent* event);
+ void mousePressEvent(QMouseEvent* event);
+
+ private:
+ void init();
+ void updateCursor();
+ void updateWidth();
+ void handleHorizontalMove(int position);
+ void handleVerticalMove(int position);
+
+ Qt::Orientation orientation = Qt::Vertical;
+ int width = 4;
+ QWidget* widget = nullptr;
+ QPoint dragStartPosition;
+ QSize dragStartSize;
+ QSize widgetMinimumSize;
+};
+
+#endif // WIDGETRESIZER_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
new file mode 100644
index 0000000..c92f6f4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
@@ -0,0 +1,155 @@
+#include "bugreporthistorywindow.h"
+#include "ui_bugreporthistorywindow.h"
+#include "common/unused.h"
+#include "services/config.h"
+#include <QDebug>
+#include <QLabel>
+
+CFG_KEYS_DEFINE(BugReportHistoryWindow)
+
+BugReportHistoryWindow::BugReportHistoryWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::BugReportHistoryWindow)
+{
+ init();
+}
+
+BugReportHistoryWindow::~BugReportHistoryWindow()
+{
+ delete ui;
+}
+
+bool BugReportHistoryWindow::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant BugReportHistoryWindow::saveSession()
+{
+ return QVariant();
+}
+
+bool BugReportHistoryWindow::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return false;
+}
+
+Icon* BugReportHistoryWindow::getIconNameForMdiWindow()
+{
+ return ICONS.BUG_LIST;
+}
+
+QString BugReportHistoryWindow::getTitleForMdiWindow()
+{
+ return tr("Reports history");
+}
+
+void BugReportHistoryWindow::createActions()
+{
+ createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear reports history"), this, SLOT(clearHistory()), ui->toolBar);
+ createAction(DELETE_SELECTED, ICONS.DELETE_ROW, tr("Delete selected entry"), this, SLOT(deleteSelected()), ui->toolBar);
+}
+
+void BugReportHistoryWindow::setupDefShortcuts()
+{
+ setShortcutContext({
+ DELETE_SELECTED
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(BugReportHistoryWindow, Action);
+}
+
+QToolBar* BugReportHistoryWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+void BugReportHistoryWindow::init()
+{
+ ui->setupUi(this);
+ initActions();
+
+ reload();
+ connect(ui->reportsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(CFG, SIGNAL(reportsHistoryRefreshNeeded()), this, SLOT(reload()));
+
+ updateState();
+}
+
+void BugReportHistoryWindow::updateState()
+{
+ actionMap[DELETE_SELECTED]->setEnabled(ui->reportsList->selectedItems().size() > 0);
+}
+
+void BugReportHistoryWindow::reload()
+{
+ static_qstring(urlTpl, "<a href=\"%1\">%2</a>");
+ QString invalidUrlTpl = tr("Invalid response from server.");
+
+ QList<Config::ReportHistoryEntryPtr> entries = CFG->getReportHistory();
+ ui->reportsList->clear();
+ ui->reportsList->setRowCount(entries.size());
+
+ QTableWidgetItem* item = nullptr;
+ QLabel* urlLabel = nullptr;
+ int row = 0;
+ for (const Config::ReportHistoryEntryPtr& entry : entries)
+ {
+ item = new QTableWidgetItem((entry->isFeatureRequest ? ICONS.FEATURE_REQUEST : ICONS.BUG), entry->title);
+ item->setData(ENTRY_ID, entry->id);
+ ui->reportsList->setItem(row, 0, item);
+
+ item = new QTableWidgetItem(QDateTime::fromTime_t(entry->timestamp).toString("yyyy-MM-dd HH:mm:ss"));
+ ui->reportsList->setItem(row, 1, item);
+
+ if (entry->url.startsWith("http://"))
+ urlLabel = new QLabel(urlTpl.arg(entry->url, entry->url));
+ else
+ urlLabel = new QLabel(invalidUrlTpl);
+
+ urlLabel->setOpenExternalLinks(true);
+ ui->reportsList->setCellWidget(row, 2, urlLabel);
+
+ row++;
+ }
+
+ ui->reportsList->setHorizontalHeaderLabels({tr("Title"), tr("Reported at"), tr("URL")});
+ ui->reportsList->resizeColumnsToContents();
+}
+
+void BugReportHistoryWindow::clearHistory()
+{
+ CFG->clearReportHistory();
+}
+
+void BugReportHistoryWindow::deleteSelected()
+{
+ QList<QTableWidgetItem*> items = ui->reportsList->selectedItems();
+ if (items.size() == 0)
+ {
+ qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no row selected.";
+ return;
+ }
+
+ int id = items.first()->data(ENTRY_ID).toInt();
+ if (id == 0)
+ {
+ qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no ID in selected row.";
+ return;
+ }
+
+ CFG->deleteReport(id);
+}
+
+bool BugReportHistoryWindow::isUncommited() const
+{
+ return false;
+}
+
+QString BugReportHistoryWindow::getQuitUncommitedConfirmMessage() const
+{
+ return QString();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
new file mode 100644
index 0000000..e582a48
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
@@ -0,0 +1,65 @@
+#ifndef BUGREPORTHISTORYWINDOW_H
+#define BUGREPORTHISTORYWINDOW_H
+
+#include "mdichild.h"
+#include <QWidget>
+
+namespace Ui {
+ class BugReportHistoryWindow;
+}
+
+CFG_KEY_LIST(BugReportHistoryWindow, QObject::tr("Reports history window"),
+ CFG_KEY_ENTRY(DELETE_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected entry"))
+)
+
+class GUI_API_EXPORT BugReportHistoryWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ DELETE_SELECTED,
+ CLEAR_HISTORY
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit BugReportHistoryWindow(QWidget *parent = 0);
+ ~BugReportHistoryWindow();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ enum UserRole
+ {
+ ENTRY_ID = Qt::UserRole + 1
+ };
+
+ void init();
+
+ Ui::BugReportHistoryWindow *ui = nullptr;
+
+ private slots:
+ void updateState();
+ void reload();
+ void clearHistory();
+ void deleteSelected();
+};
+
+#endif // BUGREPORTHISTORYWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
new file mode 100644
index 0000000..3218822
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugReportHistoryWindow</class>
+ <widget class="QWidget" name="BugReportHistoryWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="reportsList">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Title</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Reported at</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>URL</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
new file mode 100644
index 0000000..1d0594d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
@@ -0,0 +1,389 @@
+#include "collationseditor.h"
+#include "ui_collationseditor.h"
+#include "common/unused.h"
+#include "selectabledbmodel.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "collationseditormodel.h"
+#include "common/utils.h"
+#include "uiutils.h"
+#include "services/pluginmanager.h"
+#include "syntaxhighlighterplugin.h"
+#include "plugins/scriptingplugin.h"
+#include "uiconfig.h"
+#include <QDesktopServices>
+#include <QSyntaxHighlighter>
+
+CollationsEditor::CollationsEditor(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::CollationsEditor)
+{
+ init();
+}
+
+CollationsEditor::~CollationsEditor()
+{
+ delete ui;
+}
+
+bool CollationsEditor::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant CollationsEditor::saveSession()
+{
+ return QVariant();
+}
+
+bool CollationsEditor::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* CollationsEditor::getIconNameForMdiWindow()
+{
+ return ICONS.CONSTRAINT_COLLATION;
+}
+
+QString CollationsEditor::getTitleForMdiWindow()
+{
+ return tr("Collations editor");
+}
+
+void CollationsEditor::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit all collation changes"), this, SLOT(commit()), ui->toolbar);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all collation changes"), this, SLOT(rollback()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(ADD, ICONS.NEW_COLLATION, tr("Create new collation"), this, SLOT(newCollation()), ui->toolbar);
+ createAction(DELETE, ICONS.DELETE_COLLATION, tr("Delete selected collation"), this, SLOT(deleteCollation()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(HELP, ICONS.HELP, tr("Editing collations manual"), this, SLOT(help()), ui->toolbar);
+}
+
+void CollationsEditor::setupDefShortcuts()
+{
+
+}
+
+QToolBar* CollationsEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolbar;
+}
+
+void CollationsEditor::init()
+{
+ ui->setupUi(this);
+ initActions();
+
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+
+ model = new CollationsEditorModel(this);
+ collationFilterModel = new QSortFilterProxyModel(this);
+ collationFilterModel->setSourceModel(model);
+ ui->collationList->setModel(collationFilterModel);
+
+ dbListModel = new SelectableDbModel(this);
+ dbListModel->setDisabledVersion(2);
+ dbListModel->setSourceModel(DBTREE->getModel());
+ ui->databaseList->setModel(dbListModel);
+ ui->databaseList->expandAll();
+
+ model->setData(COLLATIONS->getAllCollations());
+
+ connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(collationSelected(QItemSelection,QItemSelection)));
+ connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->selectedDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified()));
+
+ connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+ connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
+
+ // Language plugins
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ ui->langCombo->addItem(plugin->getLanguage());
+
+ // Syntax highlighting plugins
+ foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ highlighterPlugins[plugin->getLanguageName()] = plugin;
+
+ updateState();
+}
+
+int CollationsEditor::getCurrentCollationRow() const
+{
+ QModelIndexList idxList = ui->collationList->selectionModel()->selectedIndexes();
+ if (idxList.size() == 0)
+ return -1;
+
+ return idxList.first().row();
+}
+
+void CollationsEditor::collationDeselected(int row)
+{
+ model->setName(row, ui->nameEdit->text());
+ model->setLang(row, ui->langCombo->currentText());
+ model->setAllDatabases(row, ui->allDatabasesRadio->isChecked());
+ model->setCode(row, ui->codeEdit->toPlainText());
+ model->setModified(row, currentModified);
+
+ if (ui->selectedDatabasesRadio->isChecked())
+ model->setDatabases(row, getCurrentDatabases());
+
+ model->validateNames();
+}
+
+void CollationsEditor::collationSelected(int row)
+{
+ updatesForSelection = true;
+ ui->nameEdit->setText(model->getName(row));
+ ui->codeEdit->setPlainText(model->getCode(row));
+ ui->langCombo->setCurrentText(model->getLang(row));
+
+ // Databases
+ dbListModel->setDatabases(model->getDatabases(row));
+ ui->databaseList->expandAll();
+
+ if (model->getAllDatabases(row))
+ ui->allDatabasesRadio->setChecked(true);
+ else
+ ui->selectedDatabasesRadio->setChecked(true);
+
+ updatesForSelection = false;
+ currentModified = false;
+
+ updateCurrentCollationState();
+}
+
+void CollationsEditor::clearEdits()
+{
+ ui->nameEdit->setText(QString::null);
+ ui->codeEdit->setPlainText(QString::null);
+ ui->langCombo->setCurrentText(QString::null);
+ ui->allDatabasesRadio->setChecked(true);
+ ui->langCombo->setCurrentIndex(-1);
+}
+
+void CollationsEditor::selectCollation(int row)
+{
+ if (!model->isValidRowIndex(row))
+ return;
+
+ ui->collationList->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+QStringList CollationsEditor::getCurrentDatabases() const
+{
+ return dbListModel->getDatabases();
+}
+
+void CollationsEditor::setFont(const QFont& font)
+{
+ ui->codeEdit->setFont(font);
+}
+
+void CollationsEditor::help()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void CollationsEditor::commit()
+{
+ int row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ collationDeselected(row);
+
+ QList<CollationManager::CollationPtr> collations = model->getCollations();
+
+ COLLATIONS->setCollations(collations);
+ model->clearModified();
+ currentModified = false;
+
+ if (model->isValidRowIndex(row))
+ selectCollation(row);
+
+ updateState();
+}
+
+void CollationsEditor::rollback()
+{
+ int selectedBefore = getCurrentCollationRow();
+
+ model->setData(COLLATIONS->getAllCollations());
+ currentModified = false;
+ clearEdits();
+
+ if (model->isValidRowIndex(selectedBefore))
+ selectCollation(selectedBefore);
+
+ updateState();
+}
+
+void CollationsEditor::newCollation()
+{
+ if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0)
+ ui->langCombo->setCurrentIndex(0);
+
+ CollationManager::CollationPtr coll = CollationManager::CollationPtr::create();
+ coll->name = generateUniqueName("collation", model->getCollationNames());
+
+ if (ui->langCombo->currentIndex() > -1)
+ coll->lang = ui->langCombo->currentText();
+
+ model->addCollation(coll);
+
+ selectCollation(model->rowCount() - 1);
+}
+
+void CollationsEditor::deleteCollation()
+{
+ int row = getCurrentCollationRow();
+ model->deleteCollation(row);
+ clearEdits();
+
+ row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ collationSelected(row);
+
+ updateState();
+}
+
+void CollationsEditor::updateState()
+{
+ bool modified = model->isModified() || currentModified;
+ bool valid = model->isValid();
+
+ actionMap[COMMIT]->setEnabled(modified && valid);
+ actionMap[ROLLBACK]->setEnabled(modified);
+ actionMap[DELETE]->setEnabled(ui->collationList->selectionModel()->selectedIndexes().size() > 0);
+}
+
+void CollationsEditor::updateCurrentCollationState()
+{
+ int row = getCurrentCollationRow();
+ bool validRow = model->isValidRowIndex(row);
+ ui->rightWidget->setEnabled(validRow);
+ if (!validRow)
+ {
+ setValidState(ui->langCombo, true);
+ setValidState(ui->nameEdit, true);
+ setValidState(ui->codeEdit, true);
+ return;
+ }
+
+ QString name = ui->nameEdit->text();
+ bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty();
+ setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the collation."));
+
+ bool langOk = ui->langCombo->currentIndex() >= 0;
+ ui->codeGroup->setEnabled(langOk);
+ ui->databasesGroup->setEnabled(langOk);
+ ui->nameEdit->setEnabled(langOk);
+ ui->nameLabel->setEnabled(langOk);
+ ui->databaseList->setEnabled(ui->selectedDatabasesRadio->isChecked());
+ setValidState(ui->langCombo, langOk, tr("Pick the implementation language."));
+
+ bool codeOk = !ui->codeEdit->toPlainText().trimmed().isEmpty();
+ setValidState(ui->codeEdit, codeOk, tr("Enter a non-empty implementation code."));
+
+ // Syntax highlighter
+ QString lang = ui->langCombo->currentText();
+ if (lang != currentHighlighterLang)
+ {
+ QSyntaxHighlighter* highlighter = nullptr;
+ if (currentHighlighter)
+ {
+ // A pointers swap with local var - this is necessary, cause deleting highlighter
+ // triggers textChanged on QPlainTextEdit, which then calls this method,
+ // so it becomes an infinite recursion with deleting the same pointer.
+ // We set the pointer to null first, then delete it. That way it's safe.
+ highlighter = currentHighlighter;
+ currentHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (langOk && highlighterPlugins.contains(lang))
+ {
+ currentHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->codeEdit);
+ }
+
+ currentHighlighterLang = lang;
+ }
+ model->setValid(row, langOk && codeOk && nameOk);
+ updateState();
+}
+
+void CollationsEditor::collationSelected(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ int deselCnt = deselected.indexes().size();
+ int selCnt = selected.indexes().size();
+
+ if (deselCnt > 0)
+ collationDeselected(deselected.indexes().first().row());
+
+ if (selCnt > 0)
+ collationSelected(selected.indexes().first().row());
+
+ if (deselCnt > 0 && selCnt == 0)
+ {
+ currentModified = false;
+ clearEdits();
+ }
+}
+
+void CollationsEditor::updateModified()
+{
+ if (updatesForSelection)
+ return;
+
+ int row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ {
+ bool nameDiff = model->getName(row) != ui->nameEdit->text();
+ bool codeDiff = model->getCode(row) != ui->codeEdit->toPlainText();
+ bool langDiff = model->getLang(row) != ui->langCombo->currentText();
+ bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked();
+ bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order
+
+ currentModified = (nameDiff || codeDiff || langDiff || allDatabasesDiff || dbDiff);
+ }
+
+ updateCurrentCollationState();
+}
+
+void CollationsEditor::applyFilter(const QString& value)
+{
+ //
+ // See FunctionsEditor::applyFilter() for details why we remember current selection and restore it at the end.
+ //
+
+ int row = getCurrentCollationRow();
+ ui->collationList->selectionModel()->clearSelection();
+
+ collationFilterModel->setFilterFixedString(value);
+
+ selectCollation(row);
+}
+
+void CollationsEditor::changeFont(const QVariant& font)
+{
+ setFont(font.value<QFont>());
+}
+
+
+bool CollationsEditor::isUncommited() const
+{
+ return model->isModified();
+}
+
+QString CollationsEditor::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Collations editor window has uncommited modifications.");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
new file mode 100644
index 0000000..62cb281
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
@@ -0,0 +1,89 @@
+#ifndef COLLATIONSEDITOR_H
+#define COLLATIONSEDITOR_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include <QItemSelection>
+#include <QModelIndex>
+#include <QWidget>
+
+namespace Ui {
+ class CollationsEditor;
+}
+
+class SyntaxHighlighterPlugin;
+class SelectableDbModel;
+class CollationsEditorModel;
+class QSortFilterProxyModel;
+class QSyntaxHighlighter;
+
+class GUI_API_EXPORT CollationsEditor : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ ADD,
+ DELETE,
+ HELP
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit CollationsEditor(QWidget *parent = 0);
+ ~CollationsEditor();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ int getCurrentCollationRow() const;
+ void collationDeselected(int row);
+ void collationSelected(int row);
+ void clearEdits();
+ void selectCollation(int row);
+ QStringList getCurrentDatabases() const;
+ void setFont(const QFont& font);
+
+ Ui::CollationsEditor *ui = nullptr;
+ CollationsEditorModel* model = nullptr;
+ QSortFilterProxyModel* collationFilterModel = nullptr;
+ SelectableDbModel* dbListModel = nullptr;
+ QHash<QString,SyntaxHighlighterPlugin*> highlighterPlugins;
+ QSyntaxHighlighter* currentHighlighter = nullptr;
+ QString currentHighlighterLang;
+ bool currentModified = false;
+ bool updatesForSelection = false;
+
+ private slots:
+ void help();
+ void commit();
+ void rollback();
+ void newCollation();
+ void deleteCollation();
+ void updateState();
+ void updateCurrentCollationState();
+ void collationSelected(const QItemSelection& selected, const QItemSelection& deselected);
+ void updateModified();
+ void applyFilter(const QString& value);
+ void changeFont(const QVariant& font);
+};
+
+#endif // COLLATIONSEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
new file mode 100644
index 0000000..635ae59
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CollationsEditor</class>
+ <widget class="QWidget" name="CollationsEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>765</width>
+ <height>529</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolbar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="mainWidgt" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="leftWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="placeholderText">
+ <string>Filter collations</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="collationList"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="rightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Collation name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="langCombo"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="langLabel">
+ <property name="text">
+ <string>Implementation language:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QWidget" name="rightBottomWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QGroupBox" name="databasesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Databases</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="allDatabasesRadio">
+ <property name="text">
+ <string>Register in all databases</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selectedDatabasesRadio">
+ <property name="text">
+ <string>Register in following databases:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="databaseList">
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="codeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPlainTextEdit" name="codeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
new file mode 100644
index 0000000..05ca4e1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
@@ -0,0 +1,287 @@
+#include "collationseditormodel.h"
+#include "common/unused.h"
+#include "common/strhash.h"
+#include "services/pluginmanager.h"
+#include "plugins/scriptingplugin.h"
+#include "icon.h"
+
+#define SETTER(X, Y) \
+ if (!isValidRowIndex(row) || X == Y) \
+ return; \
+ \
+ X = Y; \
+ emitDataChanged(row);
+
+#define GETTER(X, Y) \
+ if (!isValidRowIndex(row)) \
+ return Y; \
+ \
+ return X;
+
+CollationsEditorModel::CollationsEditorModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+ init();
+}
+
+void CollationsEditorModel::clearModified()
+{
+ beginResetModel();
+ foreach (Collation* coll, collationList)
+ coll->modified = false;
+
+ listModified = false;
+ originalCollationList = collationList;
+
+ endResetModel();
+}
+
+bool CollationsEditorModel::isModified() const
+{
+ if (collationList != originalCollationList)
+ return true;
+
+ foreach (Collation* coll, collationList)
+ {
+ if (coll->modified)
+ return true;
+ }
+ return false;
+}
+
+bool CollationsEditorModel::isModified(int row) const
+{
+ GETTER(collationList[row]->modified, false);
+}
+
+void CollationsEditorModel::setModified(int row, bool modified)
+{
+ SETTER(collationList[row]->modified, modified);
+}
+
+void CollationsEditorModel::setName(int row, const QString& name)
+{
+ SETTER(collationList[row]->data->name, name);
+}
+
+QString CollationsEditorModel::getName(int row) const
+{
+ GETTER(collationList[row]->data->name, QString());
+}
+
+void CollationsEditorModel::setLang(int row, const QString& lang)
+{
+ SETTER(collationList[row]->data->lang, lang);
+}
+
+QString CollationsEditorModel::getLang(int row) const
+{
+ GETTER(collationList[row]->data->lang, QString());
+}
+
+void CollationsEditorModel::setAllDatabases(int row, bool allDatabases)
+{
+ SETTER(collationList[row]->data->allDatabases, allDatabases);
+}
+
+bool CollationsEditorModel::getAllDatabases(int row) const
+{
+ GETTER(collationList[row]->data->allDatabases, true);
+}
+
+void CollationsEditorModel::setCode(int row, const QString& code)
+{
+ SETTER(collationList[row]->data->code, code);
+}
+
+QString CollationsEditorModel::getCode(int row) const
+{
+ GETTER(collationList[row]->data->code, QString());
+}
+
+void CollationsEditorModel::setDatabases(int row, const QStringList& databases)
+{
+ SETTER(collationList[row]->data->databases, databases);
+}
+
+QStringList CollationsEditorModel::getDatabases(int row)
+{
+ GETTER(collationList[row]->data->databases, QStringList());
+}
+
+bool CollationsEditorModel::isValid(int row) const
+{
+ GETTER(collationList[row]->valid, false);
+}
+
+void CollationsEditorModel::setValid(int row, bool valid)
+{
+ SETTER(collationList[row]->valid, valid);
+}
+
+bool CollationsEditorModel::isValid() const
+{
+ foreach (Collation* coll, collationList)
+ {
+ if (!coll->valid)
+ return false;
+ }
+ return true;
+}
+
+void CollationsEditorModel::setData(const QList<CollationManager::CollationPtr>& collations)
+{
+ beginResetModel();
+
+ Collation* collationPtr = nullptr;
+ foreach (collationPtr, collationList)
+ delete collationPtr;
+
+ collationList.clear();
+
+ foreach (const CollationManager::CollationPtr& coll, collations)
+ collationList << new Collation(coll);
+
+ listModified = false;
+ originalCollationList = collationList;
+
+ endResetModel();
+}
+
+void CollationsEditorModel::addCollation(const CollationManager::CollationPtr& collation)
+{
+ int row = collationList.size();
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ collationList << new Collation(collation);
+ listModified = true;
+
+ endInsertRows();
+}
+
+void CollationsEditorModel::deleteCollation(int row)
+{
+ if (!isValidRowIndex(row))
+ return;
+
+ beginRemoveRows(QModelIndex(), row, row);
+
+ delete collationList[row];
+ collationList.removeAt(row);
+
+ listModified = true;
+
+ endRemoveRows();
+}
+
+QList<CollationManager::CollationPtr> CollationsEditorModel::getCollations() const
+{
+ QList<CollationManager::CollationPtr> results;
+
+ foreach (Collation* coll, collationList)
+ results << coll->data;
+
+ return results;
+}
+
+QStringList CollationsEditorModel::getCollationNames() const
+{
+ QStringList names;
+ foreach (Collation* coll, collationList)
+ names << coll->data->name;
+
+ return names;
+}
+
+void CollationsEditorModel::validateNames()
+{
+ StrHash<QList<int>> counter;
+
+ int row = 0;
+ foreach (Collation* coll, collationList)
+ {
+ coll->valid &= true;
+ counter[coll->data->name] << row++;
+ }
+
+ QHashIterator<QString,QList<int>> cntIt = counter.iterator();
+ while (cntIt.hasNext())
+ {
+ cntIt.next();
+ if (cntIt.value().size() > 1)
+ {
+ foreach (int cntRow, cntIt.value())
+ setValid(cntRow, false);
+ }
+ }
+
+ QModelIndex idx;
+ for (int i = 0; i < collationList.size(); i++)
+ {
+ idx = index(i);
+ emit dataChanged(idx, idx);
+ }
+}
+
+bool CollationsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate)
+{
+ QStringList names = getCollationNames();
+ names.removeAt(rowToSkip);
+ return !names.contains(nameToValidate, Qt::CaseInsensitive);
+}
+
+bool CollationsEditorModel::isValidRowIndex(int row) const
+{
+ return (row >= 0 && row < collationList.size());
+}
+
+int CollationsEditorModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return collationList.size();
+}
+
+QVariant CollationsEditorModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || !isValidRowIndex(index.row()))
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ return collationList[index.row()]->data->name;
+
+ if (role == Qt::DecorationRole && langToIcon.contains(collationList[index.row()]->data->lang))
+ {
+ QIcon icon = langToIcon[collationList[index.row()]->data->lang];
+ if (!collationList[index.row()]->valid)
+ icon = Icon::merge(icon, Icon::ERROR);
+
+ return icon;
+ }
+
+ return QVariant();
+
+}
+
+void CollationsEditorModel::init()
+{
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
+}
+
+void CollationsEditorModel::emitDataChanged(int row)
+{
+ QModelIndex idx = index(row);
+ emit dataChanged(idx, idx);
+}
+
+CollationsEditorModel::Collation::Collation()
+{
+ data = CollationManager::CollationPtr::create();
+}
+
+CollationsEditorModel::Collation::Collation(const CollationManager::CollationPtr& other)
+{
+ data = CollationManager::CollationPtr::create(*other);
+ originalName = data->name;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
new file mode 100644
index 0000000..0c17c5b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
@@ -0,0 +1,77 @@
+#ifndef COLLATIONSEDITORMODEL_H
+#define COLLATIONSEDITORMODEL_H
+
+#include "services/collationmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QIcon>
+#include <QHash>
+#include <QAbstractListModel>
+
+class GUI_API_EXPORT CollationsEditorModel : public QAbstractListModel
+{
+ Q_OBJECT
+ public:
+ using QAbstractItemModel::setData;
+
+ explicit CollationsEditorModel(QObject *parent = 0);
+
+ void clearModified();
+ bool isModified() const;
+ bool isModified(int row) const;
+ void setModified(int row, bool modified);
+ void setName(int row, const QString& name);
+ QString getName(int row) const;
+ void setLang(int row, const QString& lang);
+ QString getLang(int row) const;
+ void setAllDatabases(int row, bool allDatabases);
+ bool getAllDatabases(int row) const;
+ void setCode(int row, const QString& code);
+ QString getCode(int row) const;
+ void setDatabases(int row, const QStringList& databases);
+ QStringList getDatabases(int row);
+ bool isValid(int row) const;
+ void setValid(int row, bool valid);
+ bool isValid() const;
+ void setData(const QList<CollationManager::CollationPtr>& collations);
+ void addCollation(const CollationManager::CollationPtr& collation);
+ void deleteCollation(int row);
+ QList<CollationManager::CollationPtr> getCollations() const;
+ QStringList getCollationNames() const;
+ void validateNames();
+ bool isAllowedName(int rowToSkip, const QString& nameToValidate);
+ bool isValidRowIndex(int row) const;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ private:
+ struct Collation
+ {
+ Collation();
+ Collation(const CollationManager::CollationPtr& other);
+
+ CollationManager::CollationPtr data;
+ bool modified = false;
+ bool valid = true;
+ QString originalName;
+ };
+
+ void init();
+ void emitDataChanged(int row);
+
+ QList<Collation*> collationList;
+
+ /**
+ * @brief List of collation pointers before modifications.
+ *
+ * This list is kept to check for modifications in the overall list of collations.
+ * Pointers on this list may be already deleted, so don't use them!
+ * It's only used to compare list of pointers to collationList, so it can tell you
+ * if the list was modified in regards of adding or deleting collations.
+ */
+ QList<Collation*> originalCollationList;
+ QHash<QString,QIcon> langToIcon;
+ bool listModified = false;
+};
+
+#endif // COLLATIONSEDITORMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
new file mode 100644
index 0000000..2d8897b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
@@ -0,0 +1,395 @@
+#include "constrainttabmodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+#include "common/utils_sql.h"
+#include <QDebug>
+
+ConstraintTabModel::ConstraintTabModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int ConstraintTabModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ int cnt = 0;
+ foreach (SqliteCreateTable::Column* col, createTable->columns)
+ cnt += col->constraints.size();
+
+ cnt += createTable->constraints.size();
+ return cnt;
+}
+
+int ConstraintTabModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 4;
+}
+
+QVariant ConstraintTabModel::data(const QModelIndex& index, int role) const
+{
+ if (createTable.isNull())
+ return QVariant();
+
+ int constrIdx = index.row();
+ int currIdx = -1;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ {
+ foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints)
+ {
+ currIdx++;
+
+ if (currIdx == constrIdx)
+ return data(constr, index.column(), role);
+ }
+ }
+
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ currIdx++;
+
+ if (currIdx == constrIdx)
+ return data(constr, index.column(), role);
+ }
+
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::data(SqliteCreateTable::Constraint* constr, int column, int role) const
+{
+ switch (getColumn(column))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ {
+ if (role == Qt::DisplayRole)
+ return tr("Table", "table constraints");
+
+ break;
+ }
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const
+{
+ switch (getColumn(column))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ {
+ if (role == Qt::DisplayRole)
+ {
+ QString colName = dynamic_cast<SqliteCreateTable::Column*>(constr->parentStatement())->name;
+ return tr("Column (%1)", "table constraints").arg(colName);
+ }
+
+ break;
+ }
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ switch (getColumn(section))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ return tr("Scope", "table constraints");
+ case Columns::TYPE:
+ return tr("Type", "table constraints");
+ case Columns::DETAILS:
+ return tr("Details", "table constraints");
+ case Columns::NAME:
+ return tr("Name", "table constraints");
+ }
+ return QVariant();
+}
+
+void ConstraintTabModel::setCreateTable(const QPointer<SqliteCreateTable>& value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+}
+
+ConstraintTabModel::Columns ConstraintTabModel::getColumn(int idx) const
+{
+ return static_cast<Columns>(idx);
+}
+
+QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return "NOT NULL";
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return "DEFAULT";
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return "COLLATE";
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QIcon();
+ }
+ return QIcon();
+}
+
+QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ICONS.CONSTRAINT_NOT_NULL;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ICONS.CONSTRAINT_DEFAULT;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ICONS.CONSTRAINT_COLLATION;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QIcon();
+}
+
+QString ConstraintTabModel::getDetails(SqliteCreateTable::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return getNotNullDetails(constr);
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return getDefaultDetails(constr);
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return getCollateDetails(constr);
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "REFERENCES", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ConstraintTabModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const
+{
+ return getConstrDetails(constr->tokens, tokenOffset);
+}
+
+QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const
+{
+ return getConstrDetails(constr->tokens, tokenOffset);
+}
+
+QString ConstraintTabModel::getConstrDetails(const TokenList& constrTokens, int tokenOffset) const
+{
+ TokenList tokens = constrTokens.mid(tokenOffset);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+void ConstraintTabModel::updateModel()
+{
+ beginResetModel();
+ endResetModel();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h
new file mode 100644
index 0000000..f93415b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h
@@ -0,0 +1,70 @@
+#ifndef CONSTRAINTTABMODEL_H
+#define CONSTRAINTTABMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT ConstraintTabModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit ConstraintTabModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant data(SqliteCreateTable::Constraint* constr, int column, int role) const;
+ QVariant data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+ void setCreateTable(const QPointer<SqliteCreateTable>& value);
+
+ private:
+ enum class Columns
+ {
+ SCOPE,
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Columns getColumn(int idx) const;
+
+ QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const;
+ QString getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const;
+
+ QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const;
+ QIcon getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const;
+
+ QString getDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getDetails(SqliteCreateTable::Column::Constraint* constr) const;
+
+ QString getPkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Constraint* constr) const;
+
+ QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const;
+
+ QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const;
+ QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const;
+ QString getConstrDetails(const TokenList& constrTokens, int tokenOffset) const;
+
+ QPointer<SqliteCreateTable> createTable;
+
+ signals:
+
+ public slots:
+ void updateModel();
+
+};
+
+#endif // CONSTRAINTTABMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
new file mode 100644
index 0000000..3aeccfb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
@@ -0,0 +1,150 @@
+#include "ddlhistorywindow.h"
+#include "ui_ddlhistorywindow.h"
+#include "services/config.h"
+#include "common/userinputfilter.h"
+#include "common/extlineedit.h"
+#include "dblistmodel.h"
+#include "ddlhistorymodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+#include <QDate>
+#include <QLineEdit>
+#include <QStringListModel>
+
+DdlHistoryWindow::DdlHistoryWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::DdlHistoryWindow)
+{
+ init();
+}
+
+DdlHistoryWindow::~DdlHistoryWindow()
+{
+ delete ui;
+}
+
+void DdlHistoryWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DdlHistoryWindow::init()
+{
+ ui->setupUi(this);
+
+ dataModel = CFG->getDdlHistoryModel();
+
+ dbListModel = new QStringListModel(this);
+ QStringList dbList = dataModel->getDbNames();
+ dbList.prepend("");
+ dbListModel->setStringList(dbList);
+ ui->comboBox->setModel(dbListModel);
+ ui->comboBox->setCurrentIndex(-1);
+ connect(ui->comboBox, SIGNAL(currentTextChanged(QString)), this, SLOT(applyFilter(QString)));
+ connect(dataModel, SIGNAL(refreshed()), this, SLOT(refreshDbList()));
+
+ ui->tableView->setModel(dataModel);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
+
+ connect(ui->tableView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(activated(QModelIndex,QModelIndex)));
+}
+
+void DdlHistoryWindow::activated(const QModelIndex& current, const QModelIndex& previous)
+{
+ UNUSED(previous);
+
+ int row = current.row();
+ QString dbName = dataModel->data(dataModel->index(row, 0)).toString();
+ QString dbFile = dataModel->data(dataModel->index(row, 1)).toString();
+ QString dateString = dataModel->data(dataModel->index(row, 2)).toString();
+ QDate date = QDate::fromString(dateString, "yyyy-MM-dd");
+
+ static const QString templ = tr("-- Queries executed on database %1 (%2)\n"
+ "-- Date and time of execution: %3\n"
+ "%4");
+
+ QStringList contentEntries;
+ QList<Config::DdlHistoryEntryPtr> entries = CFG->getDdlHistoryFor(dbName, dbFile, date);
+ foreach (Config::DdlHistoryEntryPtr entry, entries)
+ {
+ contentEntries << templ.arg(entry->dbName).arg(entry->dbFile)
+ .arg(entry->timestamp.toString("yyyy-MM-dd HH:mm:ss"))
+ .arg(entry->queries);
+ }
+
+ ui->ddlEdit->setPlainText(contentEntries.join("\n\n"));
+}
+
+void DdlHistoryWindow::applyFilter(const QString& filterValue)
+{
+ dataModel->setDbNameForFilter(filterValue);
+}
+
+void DdlHistoryWindow::refreshDbList()
+{
+ QStringList dbList = dataModel->getDbNames();
+ dbList.prepend("");
+ dbListModel->setStringList(dbList);
+}
+
+bool DdlHistoryWindow::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant DdlHistoryWindow::saveSession()
+{
+ return QVariant();
+}
+
+bool DdlHistoryWindow::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* DdlHistoryWindow::getIconNameForMdiWindow()
+{
+ return ICONS.DDL_HISTORY;
+}
+
+QString DdlHistoryWindow::getTitleForMdiWindow()
+{
+ return tr("DDL history");
+}
+
+void DdlHistoryWindow::createActions()
+{
+}
+
+void DdlHistoryWindow::setupDefShortcuts()
+{
+}
+
+QToolBar* DdlHistoryWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+
+bool DdlHistoryWindow::isUncommited() const
+{
+ return false;
+}
+
+QString DdlHistoryWindow::getQuitUncommitedConfirmMessage() const
+{
+ return QString();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h
new file mode 100644
index 0000000..16a07ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h
@@ -0,0 +1,54 @@
+#ifndef DDLHISTORYWINDOW_H
+#define DDLHISTORYWINDOW_H
+
+#include "mdichild.h"
+
+namespace Ui {
+ class DdlHistoryWindow;
+}
+
+class QStringListModel;
+class UserInputFilter;
+class DdlHistoryModel;
+
+class GUI_API_EXPORT DdlHistoryWindow : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum ToolBar
+ {
+ };
+
+ explicit DdlHistoryWindow(QWidget *parent = 0);
+ ~DdlHistoryWindow();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+
+ Ui::DdlHistoryWindow *ui = nullptr;
+ QStringListModel* dbListModel = nullptr;
+ DdlHistoryModel* dataModel = nullptr;
+ UserInputFilter* filter = nullptr;
+
+ private slots:
+ void activated(const QModelIndex& current, const QModelIndex& previous);
+ void applyFilter(const QString& filterValue);
+ void refreshDbList();
+};
+
+#endif // DDLHISTORYWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui
new file mode 100644
index 0000000..6591198
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DdlHistoryWindow</class>
+ <widget class="QWidget" name="DdlHistoryWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>749</width>
+ <height>599</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Filter by database:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="editable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QTableView" name="tableView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
new file mode 100644
index 0000000..7856a5e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
@@ -0,0 +1,652 @@
+#include "editorwindow.h"
+#include "ui_editorwindow.h"
+#include "uiutils.h"
+#include "datagrid/sqlquerymodel.h"
+#include "iconmanager.h"
+#include "dblistmodel.h"
+#include "services/notifymanager.h"
+#include "dbtree/dbtree.h"
+#include "datagrid/sqlqueryitem.h"
+#include "datagrid/sqlqueryview.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "common/unused.h"
+#include "common/extaction.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "parser/lexer.h"
+#include "common/utils_sql.h"
+#include "parser/parser.h"
+#include <QComboBox>
+#include <QDebug>
+#include <QStringListModel>
+#include <QActionGroup>
+#include <QMessageBox>
+#include <dbobjectdialogs.h>
+#include <dialogs/exportdialog.h>
+
+CFG_KEYS_DEFINE(EditorWindow)
+EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode;
+QHash<EditorWindow::Action,QAction*> EditorWindow::staticActions;
+QHash<EditorWindow::ActionGroup,QActionGroup*> EditorWindow::staticActionGroups;
+
+EditorWindow::EditorWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::EditorWindow)
+{
+ ui->setupUi(this);
+ init();
+}
+
+EditorWindow::EditorWindow(const EditorWindow& editor) :
+ MdiChild(editor.parentWidget()),
+ ui(new Ui::EditorWindow)
+{
+ ui->setupUi(this);
+ init();
+ ui->sqlEdit->setAutoCompletion(false);
+ ui->sqlEdit->setPlainText(editor.ui->sqlEdit->toPlainText());
+ ui->sqlEdit->setAutoCompletion(true);
+}
+
+EditorWindow::~EditorWindow()
+{
+ delete ui;
+}
+
+void EditorWindow::staticInit()
+{
+ qRegisterMetaType<EditorWindow>("EditorWindow");
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+ loadTabsMode();
+ createStaticActions();
+}
+
+void EditorWindow::insertAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<EditorWindow>(action, toolbar);
+}
+
+void EditorWindow::insertActionBefore(ExtActionPrototype* action, EditorWindow::Action beforeAction, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<EditorWindow>(action, beforeAction, toolbar);
+}
+
+void EditorWindow::insertActionAfter(ExtActionPrototype* action, EditorWindow::Action afterAction, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<EditorWindow>(action, afterAction, toolbar);
+}
+
+void EditorWindow::removeAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<EditorWindow>(action, toolbar);
+}
+
+void EditorWindow::init()
+{
+ setFocusProxy(ui->sqlEdit);
+ updateResultsDisplayMode();
+
+ resultsModel = new SqlQueryModel(this);
+ ui->dataView->init(resultsModel);
+
+ createDbCombo();
+ initActions();
+ updateShortcutTips();
+
+ Db* currentDb = getCurrentDb();
+ resultsModel->setDb(currentDb);
+ ui->sqlEdit->setDb(currentDb);
+
+ connect(resultsModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(resultsModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(resultsModel, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable()));
+
+ // SQL history list
+ ui->historyList->setModel(CFG->getSqlHistoryModel());
+ ui->historyList->resizeColumnToContents(1);
+ connect(ui->historyList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(historyEntrySelected(QModelIndex,QModelIndex)));
+ connect(ui->historyList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(historyEntryActivated(QModelIndex)));
+
+ updateState();
+}
+
+void EditorWindow::loadTabsMode()
+{
+ QString tabsString = CFG_UI.General.SqlEditorTabs.get();
+ if (tabsString == "SEPARATE_TAB")
+ resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB;
+ else if (tabsString == "BELOW_QUERY")
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+}
+
+void EditorWindow::createStaticActions()
+{
+ staticActions[RESULTS_IN_TAB] = new ExtAction(ICONS.RESULTS_IN_TAB, tr("Results in the separate tab"), MainWindow::getInstance());
+ staticActions[RESULTS_BELOW] = new ExtAction(ICONS.RESULTS_BELOW, tr("Results below the query"), MainWindow::getInstance());
+
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING] = new QActionGroup(MainWindow::getInstance());
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_IN_TAB]);
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_BELOW]);
+
+ connect(staticActions[RESULTS_BELOW], &QAction::triggered, [=]()
+ {
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+ CFG_UI.General.SqlEditorTabs.set("BELOW_QUERY");
+ });
+ connect(staticActions[RESULTS_IN_TAB], &QAction::triggered, [=]()
+ {
+ resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB;
+ CFG_UI.General.SqlEditorTabs.set("SEPARATE_TAB");
+ });
+
+ staticActions[RESULTS_BELOW]->setCheckable(true);
+ staticActions[RESULTS_IN_TAB]->setCheckable(true);
+ if (resultsDisplayMode == ResultsDisplayMode::BELOW_QUERY)
+ staticActions[RESULTS_BELOW]->setChecked(true);
+ else
+ staticActions[RESULTS_IN_TAB]->setChecked(true);
+}
+
+Icon* EditorWindow::getIconNameForMdiWindow()
+{
+ return ICONS.OPEN_SQL_EDITOR;
+}
+
+QString EditorWindow::getTitleForMdiWindow()
+{
+ QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
+ QString title = tr("SQL editor %1").arg(sqlEditorNum++);
+ while (existingNames.contains(title))
+ title = tr("SQL editor %1").arg(sqlEditorNum++);
+
+ return title;
+}
+
+QSize EditorWindow::sizeHint() const
+{
+ return QSize(500, 400);
+}
+
+QAction* EditorWindow::getAction(EditorWindow::Action action)
+{
+ switch (action)
+ {
+ case RESULTS_BELOW:
+ case RESULTS_IN_TAB:
+ {
+ if (!staticActions.contains(action))
+ return nullptr;
+
+ return staticActions.value(action);
+ }
+ default:
+ break;
+ }
+
+ return ExtActionContainer::getAction(action);
+}
+
+QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery)
+{
+ QString sql;
+ if (ui->sqlEdit->textCursor().hasSelection())
+ {
+ sql = ui->sqlEdit->textCursor().selectedText();
+ fixTextCursorSelectedText(sql);
+ }
+ else if (CFG_UI.General.ExecuteCurrentQueryOnly.get())
+ {
+ ui->sqlEdit->saveSelection();
+ selectCurrentQuery(true);
+ sql = ui->sqlEdit->textCursor().selectedText();
+ fixTextCursorSelectedText(sql);
+ if (!doSelectCurrentQuery)
+ ui->sqlEdit->restoreSelection();
+ }
+ else
+ {
+ sql = ui->sqlEdit->toPlainText();
+ }
+ return sql;
+}
+
+bool EditorWindow::setCurrentDb(Db *db)
+{
+ if (dbCombo->findText(db->getName()) == -1)
+ return false;
+
+ dbCombo->setCurrentText(db->getName());
+ return true;
+}
+
+void EditorWindow::setContents(const QString &sql)
+{
+ ui->sqlEdit->setPlainText(sql);
+}
+
+QString EditorWindow::getContents() const
+{
+ return ui->sqlEdit->toPlainText();
+}
+
+void EditorWindow::execute()
+{
+ execQuery();
+}
+
+QToolBar* EditorWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+SqlEditor* EditorWindow::getEditor() const
+{
+ return ui->sqlEdit;
+}
+
+QVariant EditorWindow::saveSession()
+{
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["query"] = ui->sqlEdit->toPlainText();
+ sessionValue["curPos"] = ui->sqlEdit->textCursor().position();
+
+ Db* db = getCurrentDb();
+ if (db)
+ sessionValue["db"] = db->getName();
+
+ return sessionValue;
+}
+
+bool EditorWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ return true;
+
+ if (value.contains("query"))
+ {
+ ui->sqlEdit->setAutoCompletion(false);
+ ui->sqlEdit->setPlainText(value["query"].toString());
+ ui->sqlEdit->setAutoCompletion(true);
+ }
+
+ if (value.contains("curPos"))
+ {
+ QTextCursor cursor = ui->sqlEdit->textCursor();
+ cursor.setPosition(value["curPos"].toInt());
+ ui->sqlEdit->setTextCursor(cursor);
+ }
+
+ if (value.contains("db"))
+ {
+ dbCombo->setCurrentText(value["db"].toString());
+ if (dbCombo->currentText().isEmpty() && dbCombo->count() > 0)
+ dbCombo->setCurrentIndex(0);
+ }
+ return true;
+}
+
+void EditorWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+Db* EditorWindow::getCurrentDb()
+{
+ return dbComboModel->getDb(dbCombo->currentIndex());
+}
+
+void EditorWindow::updateResultsDisplayMode()
+{
+ switch (resultsDisplayMode)
+ {
+ case EditorWindow::ResultsDisplayMode::SEPARATE_TAB:
+ {
+ // Remove old view
+ ui->resultsContainer->hide();
+ ui->resultsContainer->layout()->removeWidget(ui->resultsFrame);
+
+ // Add new view
+ ui->tabWidget->insertTab(1, ui->results, tr("Results"));
+ ui->resultsFrame->setParent(ui->results);
+ ui->results->layout()->addWidget(ui->resultsFrame);
+ break;
+ }
+ case EditorWindow::ResultsDisplayMode::BELOW_QUERY:
+ {
+ int currIdx = ui->tabWidget->currentIndex();
+
+ // Remove old view
+ ui->tabWidget->removeTab(1);
+ ui->results->layout()->removeWidget(ui->resultsFrame);
+
+ // Add new view
+ ui->resultsContainer->show();
+ ui->resultsFrame->setParent(ui->resultsContainer);
+ ui->resultsContainer->layout()->addWidget(ui->resultsFrame);
+
+ // If results tab was selected before, switch to first tab
+ if (currIdx == 1)
+ {
+ ui->tabWidget->setCurrentIndex(0);
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+ }
+ break;
+ }
+ }
+}
+
+void EditorWindow::createActions()
+{
+ // SQL editor toolbar
+ createAction(EXEC_QUERY, ICONS.EXEC_QUERY, tr("Execute query"), this, SLOT(execQuery()), ui->toolBar, ui->sqlEdit);
+ createAction(EXPLAIN_QUERY, ICONS.EXPLAIN_QUERY, tr("Explain query"), this, SLOT(explainQuery()), ui->toolBar, ui->sqlEdit);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::FORMAT_SQL));
+ createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear execution history", "sql editor"), this, SLOT(clearHistory()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(EXPORT_RESULTS, ICONS.TABLE_EXPORT, tr("Export results", "sql editor"), this, SLOT(exportResults()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(CREATE_VIEW_FROM_QUERY, ICONS.VIEW_ADD, tr("Create view from query", "sql editor"), this, SLOT(createViewFromQuery()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::SAVE_SQL_FILE));
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::OPEN_SQL_FILE));
+ ui->toolBar->addSeparator();
+ actionMap[CURRENT_DB] = ui->toolBar->addWidget(dbCombo);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(staticActions[RESULTS_IN_TAB]);
+ ui->toolBar->addAction(staticActions[RESULTS_BELOW]);
+ createAction(PREV_DB, tr("Previous database"), this, SLOT(prevDb()), this);
+ createAction(NEXT_DB, tr("Next database"), this, SLOT(nextDb()), this);
+
+ // Other actions
+ createAction(SHOW_NEXT_TAB, tr("Show next tab", "sql editor"), this, SLOT(showNextTab()), this);
+ createAction(SHOW_PREV_TAB, tr("Show previous tab", "sql editor"), this, SLOT(showPrevTab()), this);
+ createAction(FOCUS_RESULTS_BELOW, tr("Focus results below", "sql editor"), this, SLOT(focusResultsBelow()), this);
+ createAction(FOCUS_EDITOR_ABOVE, tr("Focus SQL editor above", "sql editor"), this, SLOT(focusEditorAbove()), this);
+
+ // Static action triggers
+ connect(staticActions[RESULTS_IN_TAB], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode()));
+ connect(staticActions[RESULTS_BELOW], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode()));
+}
+
+void EditorWindow::createDbCombo()
+{
+ dbCombo = new QComboBox(this);
+ dbComboModel = new DbListModel(this);
+ dbComboModel->setCombo(dbCombo);
+ dbCombo->setModel(dbComboModel);
+ dbCombo->setEditable(false);
+ dbCombo->setFixedWidth(100);
+ connect(dbCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(dbChanged()));
+}
+
+void EditorWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({EXEC_QUERY, EXEC_QUERY, SHOW_NEXT_TAB, SHOW_PREV_TAB, FOCUS_RESULTS_BELOW,
+ FOCUS_EDITOR_ABOVE}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(EditorWindow, Action);
+}
+
+void EditorWindow::selectCurrentQuery(bool fallBackToPreviousIfNecessary)
+{
+ Dialect dialect = Dialect::Sqlite3;
+ Db* db = getCurrentDb();
+ if (db && db->isValid())
+ dialect = db->getDialect();
+
+ QTextCursor cursor = ui->sqlEdit->textCursor();
+ int pos = cursor.position();
+ int queryStartPos;
+ QString contents = ui->sqlEdit->toPlainText();
+ QString query = getQueryWithPosition(contents, pos, dialect, &queryStartPos);
+ TokenList tokens = Lexer::tokenize(query, dialect);
+ tokens.trim();
+ tokens.trimRight(Token::OPERATOR, ";");
+
+ if (tokens.size() == 0 && fallBackToPreviousIfNecessary)
+ {
+ // Fallback
+ pos = contents.lastIndexOf(";", pos - 1);
+ if (pos > -1)
+ {
+ query = getQueryWithPosition(contents, pos, dialect, &queryStartPos);
+ tokens = Lexer::tokenize(query, dialect);
+ tokens.trim();
+ tokens.trimRight(Token::OPERATOR, ";");
+ }
+ }
+
+ if (tokens.size() == 0)
+ {
+ qWarning() << "No tokens to select in EditorWindow::selectCurrentQuery().";
+ return;
+ }
+
+ cursor.clearSelection();
+ cursor.setPosition(tokens.first()->start + queryStartPos);
+ cursor.setPosition(tokens.last()->end + 1 + queryStartPos, QTextCursor::KeepAnchor);
+ ui->sqlEdit->setTextCursor(cursor);
+}
+
+void EditorWindow::updateShortcutTips()
+{
+ if (actionMap.contains(PREV_DB) && actionMap.contains(NEXT_DB))
+ {
+ QString prevDbKey = actionMap[PREV_DB]->shortcut().toString(QKeySequence::NativeText);
+ QString nextDbKey = actionMap[NEXT_DB]->shortcut().toString(QKeySequence::NativeText);
+ dbCombo->setToolTip(tr("Active database (%1/%2)").arg(prevDbKey).arg(nextDbKey));
+ }
+}
+
+void EditorWindow::execQuery(bool explain)
+{
+ QString sql = getQueryToExecute(true);
+ resultsModel->setDb(getCurrentDb());
+ resultsModel->setExplainMode(explain);
+ resultsModel->setQuery(sql);
+ ui->dataView->refreshData();
+ updateState();
+
+ if (resultsDisplayMode == ResultsDisplayMode::SEPARATE_TAB)
+ {
+ ui->tabWidget->setCurrentIndex(1);
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+ }
+}
+
+void EditorWindow::explainQuery()
+{
+ execQuery(true);
+}
+
+void EditorWindow::dbChanged()
+{
+ Db* currentDb = getCurrentDb();
+ ui->sqlEdit->setDb(currentDb);
+}
+
+void EditorWindow::executionSuccessful()
+{
+ double secs = ((double)resultsModel->getExecutionTime()) / 1000;
+ QString time = QString::number(secs, 'f', 3);
+ notifyInfo(tr("Query finished in %2 second(s).").arg(time));
+
+ lastQueryHistoryId = CFG->addSqlHistory(resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), 0);
+
+ // If we added first history entry - resize dates column.
+ if (ui->historyList->model()->rowCount() == 1)
+ ui->historyList->resizeColumnToContents(1);
+
+ Db* currentDb = getCurrentDb();
+ if (currentDb && resultsModel->wasSchemaModified())
+ DBTREE->refreshSchema(currentDb);
+
+ lastSuccessfulQuery = resultsModel->getQuery();
+
+ updateState();
+}
+
+void EditorWindow::executionFailed(const QString &errorText)
+{
+ notifyError(errorText);
+ updateState();
+}
+
+void EditorWindow::totalRowsAndPagesAvailable()
+{
+ qint64 rowsReturned = resultsModel->getTotalRowsReturned();
+ qint64 rowsAffected = resultsModel->getTotalRowsAffected();
+ qint64 rows;
+ if (rowsReturned > 0)
+ rows = rowsReturned;
+ else
+ rows = rowsAffected;
+
+ CFG->updateSqlHistory(lastQueryHistoryId, resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), rows);
+}
+
+void EditorWindow::prevDb()
+{
+ int idx = dbCombo->currentIndex() - 1;
+ if (idx < 0)
+ return;
+
+ dbCombo->setCurrentIndex(idx);
+}
+
+void EditorWindow::nextDb()
+{
+ int idx = dbCombo->currentIndex() + 1;
+ if (idx >= dbCombo->count())
+ return;
+
+ dbCombo->setCurrentIndex(idx);
+}
+
+void EditorWindow::showNextTab()
+{
+ int tabIdx = ui->tabWidget->currentIndex();
+ tabIdx++;
+ ui->tabWidget->setCurrentIndex(tabIdx);
+}
+
+void EditorWindow::showPrevTab()
+{
+ int tabIdx = ui->tabWidget->currentIndex();
+ tabIdx--;
+ ui->tabWidget->setCurrentIndex(tabIdx);
+}
+
+void EditorWindow::focusResultsBelow()
+{
+ if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY)
+ return;
+
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+}
+
+void EditorWindow::focusEditorAbove()
+{
+ if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY)
+ return;
+
+ ui->sqlEdit->setFocus();
+}
+
+void EditorWindow::historyEntrySelected(const QModelIndex& current, const QModelIndex& previous)
+{
+ UNUSED(previous);
+ QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ ui->historyContents->setPlainText(sql);
+}
+
+void EditorWindow::historyEntryActivated(const QModelIndex& current)
+{
+ QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ ui->sqlEdit->setPlainText(sql);
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void EditorWindow::clearHistory()
+{
+ QMessageBox::StandardButton res = QMessageBox::question(this, tr("Clear execution history"), tr("Are you sure you want to erase the entire SQL execution history? "
+ "This cannot be undone."));
+ if (res != QMessageBox::Yes)
+ return;
+
+ CFG->clearSqlHistory();
+}
+
+void EditorWindow::exportResults()
+{
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ QString query = lastSuccessfulQuery.isEmpty() ? getQueryToExecute() : lastSuccessfulQuery;
+ QStringList queries = splitQueries(query, getCurrentDb()->getDialect(), false);
+ if (queries.size() == 0)
+ {
+ qWarning() << "No queries after split in EditorWindow::exportResults()";
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setQueryMode(getCurrentDb(), queries.last().trimmed());
+ dialog.exec();
+}
+
+void EditorWindow::createViewFromQuery()
+{
+ if (!getCurrentDb())
+ {
+ notifyError(tr("No database selected in the SQL editor. Cannot create a view for unknown database."));
+ return;
+ }
+
+ QString sql = getQueryToExecute(true);
+ DbObjectDialogs dialogs(getCurrentDb());
+ dialogs.addView(sql);
+}
+
+void EditorWindow::updateState()
+{
+ bool executionInProgress = resultsModel->isExecutionInProgress();
+ actionMap[CURRENT_DB]->setEnabled(!executionInProgress);
+ actionMap[EXEC_QUERY]->setEnabled(!executionInProgress);
+ actionMap[EXPLAIN_QUERY]->setEnabled(!executionInProgress);
+}
+
+int qHash(EditorWindow::ActionGroup actionGroup)
+{
+ return static_cast<int>(actionGroup);
+}
+
+
+bool EditorWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited();
+}
+
+QString EditorWindow::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Editor window \"%1\" has uncommited data.").arg(getMdiWindow()->windowTitle());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
new file mode 100644
index 0000000..0052a74
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
@@ -0,0 +1,155 @@
+#ifndef EDITOR_H
+#define EDITOR_H
+
+#include "db/db.h"
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class EditorWindow;
+}
+
+class SqlQueryModel;
+class QComboBox;
+class QActionGroup;
+class DbListModel;
+class QLabel;
+class QLineEdit;
+class ExtLineEdit;
+class IntValidator;
+class FormView;
+class SqlQueryItem;
+class SqlEditor;
+
+CFG_KEY_LIST(EditorWindow, QObject::tr("SQL editor window"),
+ CFG_KEY_ENTRY(EXEC_QUERY, Qt::Key_F9, QObject::tr("Execute query"))
+ CFG_KEY_ENTRY(EXPLAIN_QUERY, Qt::Key_F8, QObject::tr("Execute \"EXPLAIN\" query"))
+ CFG_KEY_ENTRY(PREV_DB, Qt::CTRL + Qt::Key_Up, QObject::tr("Switch current working database to previous on the list"))
+ CFG_KEY_ENTRY(NEXT_DB, Qt::CTRL + Qt::Key_Down, QObject::tr("Switch current working database to next on the list"))
+ CFG_KEY_ENTRY(SHOW_NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next editor tab"))
+ CFG_KEY_ENTRY(SHOW_PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous editor tab"))
+ CFG_KEY_ENTRY(FOCUS_RESULTS_BELOW, Qt::ALT + Qt::Key_PageDown, QObject::tr("Move keyboard input focus to the results view below"))
+ CFG_KEY_ENTRY(FOCUS_EDITOR_ABOVE, Qt::ALT + Qt::Key_PageUp, QObject::tr("Move keyboard input focus to the SQL editor above"))
+)
+
+class GUI_API_EXPORT EditorWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum class ResultsDisplayMode
+ {
+ SEPARATE_TAB = 0,
+ BELOW_QUERY = 1
+ };
+
+ enum Action
+ {
+ EXEC_QUERY,
+ EXPLAIN_QUERY,
+ RESULTS_IN_TAB,
+ RESULTS_BELOW,
+ CURRENT_DB,
+ NEXT_DB,
+ PREV_DB,
+ SHOW_NEXT_TAB,
+ SHOW_PREV_TAB,
+ FOCUS_RESULTS_BELOW,
+ FOCUS_EDITOR_ABOVE,
+ CLEAR_HISTORY,
+ EXPORT_RESULTS,
+ CREATE_VIEW_FROM_QUERY
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_MAIN
+ };
+
+ enum class ActionGroup
+ {
+ RESULTS_POSITIONING
+ };
+
+ explicit EditorWindow(QWidget *parent = 0);
+ EditorWindow(const EditorWindow& editor);
+ ~EditorWindow();
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_MAIN);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_MAIN);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN);
+
+ QSize sizeHint() const;
+ QAction* getAction(Action action);
+ QString getQueryToExecute(bool doSelectCurrentQuery = false);
+ bool setCurrentDb(Db* db);
+ void setContents(const QString& sql);
+ QString getContents() const;
+ void execute();
+ QToolBar* getToolBar(int toolbar) const;
+ SqlEditor* getEditor() const;
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ Db* getCurrentDb();
+
+ private:
+ static void createStaticActions();
+ static void loadTabsMode();
+
+ void init();
+ void createActions();
+ void createDbCombo();
+ void setupDefShortcuts();
+ void selectCurrentQuery(bool fallBackToPreviousIfNecessary = false);
+ void updateShortcutTips();
+
+ static ResultsDisplayMode resultsDisplayMode;
+ static QHash<Action,QAction*> staticActions;
+ static QHash<ActionGroup,QActionGroup*> staticActionGroups;
+
+ Ui::EditorWindow *ui = nullptr;
+ SqlQueryModel* resultsModel = nullptr;
+ QHash<ActionGroup,QActionGroup*> actionGroups;
+ QComboBox* dbCombo = nullptr;
+ DbListModel* dbComboModel = nullptr;
+ int sqlEditorNum = 1;
+ qint64 lastQueryHistoryId = 0;
+ QString lastSuccessfulQuery;
+
+ private slots:
+ void execQuery(bool explain = false);
+ void explainQuery();
+ void dbChanged();
+ void executionSuccessful();
+ void executionFailed(const QString& errorText);
+ void totalRowsAndPagesAvailable();
+ void updateResultsDisplayMode();
+ void prevDb();
+ void nextDb();
+ void showNextTab();
+ void showPrevTab();
+ void focusResultsBelow();
+ void focusEditorAbove();
+ void historyEntrySelected(const QModelIndex& current, const QModelIndex& previous);
+ void historyEntryActivated(const QModelIndex& current);
+ void clearHistory();
+ void exportResults();
+ void createViewFromQuery();
+ void updateState();
+};
+
+GUI_API_EXPORT int qHash(EditorWindow::ActionGroup action);
+
+#endif // EDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
new file mode 100644
index 0000000..b0d598b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditorWindow</class>
+ <widget class="QWidget" name="EditorWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>502</width>
+ <height>325</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQL editor</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="query">
+ <attribute name="title">
+ <string>Query</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="SqlEditor" name="sqlEdit">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ </widget>
+ <widget class="QWidget" name="resultsContainer" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="results">
+ <attribute name="title">
+ <string notr="true">Results</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QWidget" name="resultsFrame" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="DataView" name="dataView">
+ <property name="tabPosition">
+ <enum>QTabWidget::South</enum>
+ </property>
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="history">
+ <attribute name="title">
+ <string>History</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QTableView" name="historyList">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ <widget class="SqlView" name="historyContents">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
new file mode 100644
index 0000000..29163e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
@@ -0,0 +1,632 @@
+#include "functionseditor.h"
+#include "ui_functionseditor.h"
+#include "common/unused.h"
+#include "common/utils.h"
+#include "uiutils.h"
+#include "functionseditormodel.h"
+#include "services/pluginmanager.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "dbtree/dbtreeitem.h"
+#include "iconmanager.h"
+#include "syntaxhighlighterplugin.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "plugins/scriptingplugin.h"
+#include "common/userinputfilter.h"
+#include "selectabledbmodel.h"
+#include "uiconfig.h"
+#include <QDebug>
+#include <QDesktopServices>
+#include <QStyleFactory>
+
+// TODO handle plugin loading/unloading to update editor state
+
+FunctionsEditor::FunctionsEditor(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::FunctionsEditor)
+{
+ init();
+}
+
+FunctionsEditor::~FunctionsEditor()
+{
+ delete ui;
+}
+
+bool FunctionsEditor::restoreSessionNextTime()
+{
+ return false;
+}
+
+bool FunctionsEditor::restoreSession(const QVariant &sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* FunctionsEditor::getIconNameForMdiWindow()
+{
+ return ICONS.FUNCTION;
+}
+
+QString FunctionsEditor::getTitleForMdiWindow()
+{
+ return tr("SQL function editor");
+}
+
+void FunctionsEditor::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit all function changes"), this, SLOT(commit()), ui->toolBar);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all function changes"), this, SLOT(rollback()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(ADD, ICONS.NEW_FUNCTION, tr("Create new function"), this, SLOT(newFunction()), ui->toolBar);
+ createAction(DELETE, ICONS.DELETE_FUNCTION, tr("Delete selected function"), this, SLOT(deleteFunction()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(HELP, ICONS.HELP, tr("Custom SQL functions manual"), this, SLOT(help()), ui->toolBar);
+
+ // Args toolbar
+ createAction(ARG_ADD, ICONS.INSERT_FN_ARG, tr("Add function argument"), this, SLOT(addFunctionArg()), ui->argsToolBar);
+ createAction(ARG_EDIT, ICONS.RENAME_FN_ARG, tr("Rename function argument"), this, SLOT(editFunctionArg()), ui->argsToolBar);
+ createAction(ARG_DEL, ICONS.DELETE_FN_ARG, tr("Delete function argument"), this, SLOT(delFunctionArg()), ui->argsToolBar);
+ ui->argsToolBar->addSeparator();
+ createAction(ARG_MOVE_UP, ICONS.MOVE_UP, tr("Move function argument up"), this, SLOT(moveFunctionArgUp()), ui->argsToolBar);
+ createAction(ARG_MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move function argument down"), this, SLOT(moveFunctionArgDown()), ui->argsToolBar);
+
+#ifdef Q_OS_MACX
+ QStyle *fusion = QStyleFactory::create("Fusion");
+ ui->toolBar->setStyle(fusion);
+ ui->argsToolBar->setStyle(fusion);
+#endif
+}
+
+void FunctionsEditor::setupDefShortcuts()
+{
+}
+
+QToolBar* FunctionsEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+void FunctionsEditor::init()
+{
+ ui->setupUi(this);
+ clearEdits();
+ ui->initCodeGroup->setVisible(false);
+ ui->finalCodeGroup->setVisible(false);
+
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+
+ model = new FunctionsEditorModel(this);
+ functionFilterModel = new QSortFilterProxyModel(this);
+ functionFilterModel->setSourceModel(model);
+ ui->list->setModel(functionFilterModel);
+
+ dbListModel = new SelectableDbModel(this);
+ dbListModel->setSourceModel(DBTREE->getModel());
+ ui->databasesList->setModel(dbListModel);
+ ui->databasesList->expandAll();
+
+ ui->typeCombo->addItem(tr("Scalar"), FunctionManager::ScriptFunction::SCALAR);
+ ui->typeCombo->addItem(tr("Aggregate"), FunctionManager::ScriptFunction::AGGREGATE);
+
+ new UserInputFilter(ui->functionFilterEdit, this, SLOT(applyFilter(QString)));
+ functionFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+
+ initActions();
+
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(functionSelected(QItemSelection,QItemSelection)));
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(ui->initCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->mainCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->finalCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->undefArgsCheck, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->selDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified()));
+
+ connect(ui->argsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateArgsState()));
+ connect(ui->argsList->model(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateModified()));
+
+ connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+ connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
+
+ model->setData(FUNCTIONS->getAllScriptFunctions());
+
+ // Language plugins
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ scriptingPlugins[plugin->getLanguage()] = plugin;
+
+ ui->langCombo->addItems(scriptingPlugins.keys());
+
+ // Syntax highlighting plugins
+ foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ highlighterPlugins[plugin->getLanguageName()] = plugin;
+
+ updateState();
+}
+
+int FunctionsEditor::getCurrentFunctionRow() const
+{
+ QModelIndexList idxList = ui->list->selectionModel()->selectedIndexes();
+ if (idxList.size() == 0)
+ return -1;
+
+ return idxList.first().row();
+}
+
+void FunctionsEditor::functionDeselected(int row)
+{
+ model->setName(row, ui->nameEdit->text());
+ model->setLang(row, ui->langCombo->currentText());
+ model->setType(row, getCurrentFunctionType());
+ model->setUndefinedArgs(row, ui->undefArgsCheck->isChecked());
+ model->setAllDatabases(row, ui->allDatabasesRadio->isChecked());
+ model->setCode(row, ui->mainCodeEdit->toPlainText());
+ model->setModified(row, currentModified);
+
+ if (model->isAggregate(row))
+ {
+ model->setInitCode(row, ui->initCodeEdit->toPlainText());
+ model->setFinalCode(row, ui->finalCodeEdit->toPlainText());
+ }
+ else
+ {
+ model->setInitCode(row, QString::null);
+ model->setFinalCode(row, QString::null);
+ }
+
+ if (!ui->undefArgsCheck->isChecked())
+ model->setArguments(row, getCurrentArgList());
+
+ if (ui->selDatabasesRadio->isChecked())
+ model->setDatabases(row, getCurrentDatabases());
+
+ model->validateNames();
+}
+
+void FunctionsEditor::functionSelected(int row)
+{
+ updatesForSelection = true;
+ ui->nameEdit->setText(model->getName(row));
+ ui->initCodeEdit->setPlainText(model->getInitCode(row));
+ ui->mainCodeEdit->setPlainText(model->getCode(row));
+ ui->finalCodeEdit->setPlainText(model->getFinalCode(row));
+ ui->undefArgsCheck->setChecked(model->getUndefinedArgs(row));
+ ui->langCombo->setCurrentText(model->getLang(row));
+
+ // Arguments
+ ui->argsList->clear();
+ QListWidgetItem* item = nullptr;
+ foreach (const QString& arg, model->getArguments(row))
+ {
+ item = new QListWidgetItem(arg);
+ item->setFlags(item->flags() | Qt::ItemIsEditable);
+ ui->argsList->addItem(item);
+ }
+
+ // Databases
+ dbListModel->setDatabases(model->getDatabases(row));
+ ui->databasesList->expandAll();
+
+ if (model->getAllDatabases(row))
+ ui->allDatabasesRadio->setChecked(true);
+ else
+ ui->selDatabasesRadio->setChecked(true);
+
+ // Type
+ FunctionManager::ScriptFunction::Type type = model->getType(row);
+ for (int i = 0; i < ui->typeCombo->count(); i++)
+ {
+ if (ui->typeCombo->itemData(i).toInt() == type)
+ {
+ ui->typeCombo->setCurrentIndex(i);
+ break;
+ }
+ }
+
+ updatesForSelection = false;
+ currentModified = false;
+
+ updateCurrentFunctionState();
+}
+
+void FunctionsEditor::clearEdits()
+{
+ ui->nameEdit->setText(QString::null);
+ ui->mainCodeEdit->setPlainText(QString::null);
+ ui->langCombo->setCurrentText(QString::null);
+ ui->undefArgsCheck->setChecked(true);
+ ui->argsList->clear();
+ ui->allDatabasesRadio->setChecked(true);
+ ui->typeCombo->setCurrentIndex(0);
+ ui->langCombo->setCurrentIndex(-1);
+}
+
+void FunctionsEditor::selectFunction(int row)
+{
+ if (!model->isValidRowIndex(row))
+ return;
+
+ ui->list->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::setFont(const QFont& font)
+{
+ ui->initCodeEdit->setFont(font);
+ ui->mainCodeEdit->setFont(font);
+ ui->finalCodeEdit->setFont(font);
+}
+
+QModelIndex FunctionsEditor::getSelectedArg() const
+{
+ QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes();
+ if (indexes.size() == 0 || !indexes.first().isValid())
+ return QModelIndex();
+
+ return indexes.first();
+
+}
+
+QStringList FunctionsEditor::getCurrentArgList() const
+{
+ QStringList currArgList;
+ for (int row = 0; row < ui->argsList->model()->rowCount(); row++)
+ currArgList << ui->argsList->item(row)->text();
+
+ return currArgList;
+}
+
+QStringList FunctionsEditor::getCurrentDatabases() const
+{
+ return dbListModel->getDatabases();
+}
+
+FunctionManager::ScriptFunction::Type FunctionsEditor::getCurrentFunctionType() const
+{
+ int intValue = ui->typeCombo->itemData(ui->typeCombo->currentIndex()).toInt();
+ return static_cast<FunctionManager::ScriptFunction::Type>(intValue);
+}
+
+void FunctionsEditor::commit()
+{
+ int row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ functionDeselected(row);
+
+ QList<FunctionManager::ScriptFunction*> functions = model->generateFunctions();
+
+ FUNCTIONS->setScriptFunctions(functions);
+ model->clearModified();
+ currentModified = false;
+
+ if (model->isValidRowIndex(row))
+ selectFunction(row);
+
+ updateState();
+}
+
+void FunctionsEditor::rollback()
+{
+ int selectedBefore = getCurrentFunctionRow();
+
+ model->setData(FUNCTIONS->getAllScriptFunctions());
+ currentModified = false;
+ clearEdits();
+
+ if (model->isValidRowIndex(selectedBefore))
+ selectFunction(selectedBefore);
+
+ updateState();
+}
+
+void FunctionsEditor::newFunction()
+{
+ if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0)
+ ui->langCombo->setCurrentIndex(0);
+
+ FunctionManager::ScriptFunction* func = new FunctionManager::ScriptFunction();
+ func->name = generateUniqueName("function", model->getFunctionNames());
+
+ if (ui->langCombo->currentIndex() > -1)
+ func->lang = ui->langCombo->currentText();
+
+ model->addFunction(func);
+
+ selectFunction(model->rowCount() - 1);
+}
+
+void FunctionsEditor::deleteFunction()
+{
+ int row = getCurrentFunctionRow();
+ model->deleteFunction(row);
+ clearEdits();
+
+ row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ functionSelected(row);
+
+ updateState();
+}
+
+void FunctionsEditor::updateModified()
+{
+ if (updatesForSelection)
+ return;
+
+ int row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ {
+ bool nameDiff = model->getName(row) != ui->nameEdit->text();
+ bool codeDiff = model->getCode(row) != ui->mainCodeEdit->toPlainText();
+ bool initCodeDiff = model->getInitCode(row) != ui->initCodeEdit->toPlainText();
+ bool finalCodeDiff = model->getFinalCode(row) != ui->finalCodeEdit->toPlainText();
+ bool langDiff = model->getLang(row) != ui->langCombo->currentText();
+ bool undefArgsDiff = model->getUndefinedArgs(row) != ui->undefArgsCheck->isChecked();
+ bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked();
+ bool argDiff = getCurrentArgList() != model->getArguments(row);
+ bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order
+ bool typeDiff = model->getType(row) != getCurrentFunctionType();
+
+ currentModified = (nameDiff || codeDiff || typeDiff || langDiff || undefArgsDiff || allDatabasesDiff || argDiff || dbDiff ||
+ initCodeDiff || finalCodeDiff);
+ }
+
+ updateCurrentFunctionState();
+}
+
+void FunctionsEditor::updateState()
+{
+ bool modified = model->isModified() || currentModified;
+ bool valid = model->isValid();
+
+ actionMap[COMMIT]->setEnabled(modified && valid);
+ actionMap[ROLLBACK]->setEnabled(modified);
+ actionMap[DELETE]->setEnabled(ui->list->selectionModel()->selectedIndexes().size() > 0);
+}
+
+void FunctionsEditor::updateCurrentFunctionState()
+{
+ int row = getCurrentFunctionRow();
+ bool validRow = model->isValidRowIndex(row);
+ ui->rightWidget->setEnabled(validRow);
+ if (!validRow)
+ {
+ setValidState(ui->langCombo, true);
+ setValidState(ui->nameEdit, true);
+ setValidState(ui->mainCodeEdit, true);
+ setValidState(ui->finalCodeEdit, true);
+ return;
+ }
+
+ QString name = ui->nameEdit->text();
+ bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty();
+ setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the function."));
+
+ bool langOk = ui->langCombo->currentIndex() >= 0;
+ ui->initCodeGroup->setEnabled(langOk);
+ ui->mainCodeGroup->setEnabled(langOk);
+ ui->finalCodeGroup->setEnabled(langOk);
+ ui->argsGroup->setEnabled(langOk);
+ ui->databasesGroup->setEnabled(langOk);
+ ui->nameEdit->setEnabled(langOk);
+ ui->nameLabel->setEnabled(langOk);
+ ui->typeCombo->setEnabled(langOk);
+ ui->typeLabel->setEnabled(langOk);
+ setValidState(ui->langCombo, langOk, tr("Pick the implementation language."));
+
+ bool aggregate = getCurrentFunctionType() == FunctionManager::ScriptFunction::AGGREGATE;
+ ui->initCodeGroup->setVisible(aggregate);
+ ui->mainCodeGroup->setTitle(aggregate ? tr("Per step code:") : tr("Function implementation code:"));
+ ui->finalCodeGroup->setVisible(aggregate);
+
+ ui->databasesList->setEnabled(ui->selDatabasesRadio->isChecked());
+
+ bool codeOk = !ui->mainCodeEdit->toPlainText().trimmed().isEmpty();
+ setValidState(ui->mainCodeEdit, codeOk, tr("Enter a non-empty implementation code."));
+
+ bool finalCodeOk = true;
+ if (aggregate)
+ finalCodeOk = !ui->finalCodeEdit->toPlainText().trimmed().isEmpty();
+
+ setValidState(ui->finalCodeEdit, finalCodeOk);
+
+ // Syntax highlighter
+ QString lang = ui->langCombo->currentText();
+ if (lang != currentHighlighterLang)
+ {
+ QSyntaxHighlighter* highlighter = nullptr;
+ if (currentMainHighlighter)
+ {
+ // A pointers swap with local var - this is necessary, cause deleting highlighter
+ // triggers textChanged on QPlainTextEdit, which then calls this method,
+ // so it becomes an infinite recursion with deleting the same pointer.
+ // We set the pointer to null first, then delete it. That way it's safe.
+ highlighter = currentMainHighlighter;
+ currentMainHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (currentFinalHighlighter)
+ {
+ highlighter = currentFinalHighlighter;
+ currentFinalHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (currentInitHighlighter)
+ {
+ highlighter = currentInitHighlighter;
+ currentInitHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (langOk && highlighterPlugins.contains(lang))
+ {
+ currentInitHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->initCodeEdit);
+ currentMainHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->mainCodeEdit);
+ currentFinalHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->finalCodeEdit);
+ }
+
+ currentHighlighterLang = lang;
+ }
+
+ updateArgsState();
+ model->setValid(row, langOk && codeOk && finalCodeOk && nameOk);
+ updateState();
+}
+
+void FunctionsEditor::functionSelected(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ int deselCnt = deselected.indexes().size();
+ int selCnt = selected.indexes().size();
+
+ if (deselCnt > 0)
+ functionDeselected(deselected.indexes().first().row());
+
+ if (selCnt > 0)
+ functionSelected(selected.indexes().first().row());
+
+ if (deselCnt > 0 && selCnt == 0)
+ {
+ currentModified = false;
+ clearEdits();
+ }
+}
+
+void FunctionsEditor::addFunctionArg()
+{
+ QListWidgetItem* item = new QListWidgetItem(tr("argument", "new function argument name in function editor window"));
+ item->setFlags(item->flags () | Qt::ItemIsEditable);
+ ui->argsList->addItem(item);
+
+ QModelIndex idx = ui->argsList->model()->index(ui->argsList->model()->rowCount() - 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+
+ ui->argsList->editItem(item);
+}
+
+void FunctionsEditor::editFunctionArg()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ QListWidgetItem* item = ui->argsList->item(row);
+ ui->argsList->editItem(item);
+}
+
+void FunctionsEditor::delFunctionArg()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ delete ui->argsList->takeItem(row);
+}
+
+void FunctionsEditor::moveFunctionArgUp()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ if (row <= 0)
+ return;
+
+ ui->argsList->insertItem(row - 1, ui->argsList->takeItem(row));
+
+ QModelIndex idx = ui->argsList->model()->index(row - 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::moveFunctionArgDown()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ if (row >= ui->argsList->model()->rowCount() - 1)
+ return;
+
+ ui->argsList->insertItem(row + 1, ui->argsList->takeItem(row));
+
+ QModelIndex idx = ui->argsList->model()->index(row + 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::updateArgsState()
+{
+ bool argsEnabled = !ui->undefArgsCheck->isChecked();
+ QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes();
+ bool argSelected = indexes.size() > 0;
+
+ bool canMoveUp = false;
+ bool canMoveDown = false;
+ if (argSelected)
+ {
+ canMoveUp = indexes.first().row() > 0;
+ canMoveDown = (indexes.first().row() + 1) < ui->argsList->count();
+ }
+
+ actionMap[ARG_ADD]->setEnabled(argsEnabled);
+ actionMap[ARG_EDIT]->setEnabled(argsEnabled && argSelected);
+ actionMap[ARG_DEL]->setEnabled(argsEnabled && argSelected);
+ actionMap[ARG_MOVE_UP]->setEnabled(argsEnabled && canMoveUp);
+ actionMap[ARG_MOVE_DOWN]->setEnabled(argsEnabled && canMoveDown);
+ ui->argsList->setEnabled(argsEnabled);
+}
+
+void FunctionsEditor::applyFilter(const QString& value)
+{
+ // Remembering old selection, clearing it and restoring afterwards is a workaround for a problem,
+ // which causees application to crash, when the item was selected, but after applying filter string,
+ // item was about to disappear.
+ // This must have something to do with the underlying model (FunctionsEditorModel) implementation,
+ // but for now I don't really know what is that.
+ // I have tested simple Qt application with the same routine, but the underlying model was QStandardItemModel
+ // and everything worked fine.
+ int row = getCurrentFunctionRow();
+ ui->list->selectionModel()->clearSelection();
+
+ functionFilterModel->setFilterFixedString(value);
+
+ selectFunction(row);
+}
+
+void FunctionsEditor::help()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void FunctionsEditor::changeFont(const QVariant& font)
+{
+ setFont(font.value<QFont>());
+}
+
+QVariant FunctionsEditor::saveSession()
+{
+ return QVariant();
+}
+
+
+bool FunctionsEditor::isUncommited() const
+{
+ return model->isModified();
+}
+
+QString FunctionsEditor::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Functions editor window has uncommited modifications.");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h
new file mode 100644
index 0000000..0fa496e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h
@@ -0,0 +1,109 @@
+#ifndef FUNCTIONSEDITOR_H
+#define FUNCTIONSEDITOR_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "services/config.h"
+#include "services/functionmanager.h"
+#include <QItemSelection>
+#include <QSortFilterProxyModel>
+
+namespace Ui {
+class FunctionsEditor;
+}
+
+class FunctionsEditorModel;
+class ScriptingPlugin;
+class SyntaxHighlighterPlugin;
+class DbTreeItem;
+class QTreeWidgetItem;
+class QSyntaxHighlighter;
+class SelectableDbModel;
+
+class GUI_API_EXPORT FunctionsEditor : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ ADD,
+ DELETE,
+ ARG_ADD,
+ ARG_EDIT,
+ ARG_DEL,
+ ARG_MOVE_UP,
+ ARG_MOVE_DOWN,
+ HELP
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit FunctionsEditor(QWidget *parent = 0);
+ ~FunctionsEditor();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ int getCurrentFunctionRow() const;
+ void functionDeselected(int row);
+ void functionSelected(int row);
+ void clearEdits();
+ void selectFunction(int row);
+ void setFont(const QFont& font);
+ QModelIndex getSelectedArg() const;
+ QStringList getCurrentArgList() const;
+ QStringList getCurrentDatabases() const;
+ FunctionManager::ScriptFunction::Type getCurrentFunctionType() const;
+
+ Ui::FunctionsEditor *ui = nullptr;
+ FunctionsEditorModel* model = nullptr;
+ QSortFilterProxyModel* functionFilterModel = nullptr;
+ bool currentModified = false;
+ QHash<QString,ScriptingPlugin*> scriptingPlugins;
+ QHash<QString,SyntaxHighlighterPlugin*> highlighterPlugins;
+ SelectableDbModel* dbListModel = nullptr;
+ QString currentHighlighterLang;
+ QSyntaxHighlighter* currentMainHighlighter = nullptr;
+ QSyntaxHighlighter* currentFinalHighlighter = nullptr;
+ QSyntaxHighlighter* currentInitHighlighter = nullptr;
+ bool updatesForSelection = false;
+
+ private slots:
+ void commit();
+ void rollback();
+ void newFunction();
+ void deleteFunction();
+ void updateModified();
+ void updateState();
+ void updateCurrentFunctionState();
+ void functionSelected(const QItemSelection& selected, const QItemSelection& deselected);
+ void addFunctionArg();
+ void editFunctionArg();
+ void delFunctionArg();
+ void moveFunctionArgUp();
+ void moveFunctionArgDown();
+ void updateArgsState();
+ void applyFilter(const QString& value);
+ void help();
+ void changeFont(const QVariant& font);
+};
+
+#endif // FUNCTIONSEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
new file mode 100644
index 0000000..d5d5015
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FunctionsEditor</class>
+ <widget class="QWidget" name="FunctionsEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>816</width>
+ <height>621</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="mainWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="leftWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="functionFilterEdit">
+ <property name="minimumSize">
+ <size>
+ <width>150</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="placeholderText">
+ <string>Filter funtions</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="list">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="rightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QWidget" name="topWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Function name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QComboBox" name="typeCombo"/>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="langLabel">
+ <property name="text">
+ <string>Implementation language:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QComboBox" name="langCombo"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QGroupBox" name="argsGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Input arguments</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="undefArgsCheck">
+ <property name="text">
+ <string>Undefined</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" rowspan="5" colspan="3">
+ <widget class="QListWidget" name="argsList">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolBar" name="argsToolBar"/>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="3">
+ <widget class="QGroupBox" name="databasesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Databases</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="allDatabasesRadio">
+ <property name="text">
+ <string>Register in all databases</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selDatabasesRadio">
+ <property name="text">
+ <string>Register in following databases:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="databasesList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerDefaultSectionSize">
+ <number>0</number>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="initCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Initialization code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPlainTextEdit" name="initCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="mainCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Function implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QPlainTextEdit" name="mainCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="finalCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Final step implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QPlainTextEdit" name="finalCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>functionFilterEdit</tabstop>
+ <tabstop>list</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>langCombo</tabstop>
+ <tabstop>undefArgsCheck</tabstop>
+ <tabstop>argsList</tabstop>
+ <tabstop>allDatabasesRadio</tabstop>
+ <tabstop>selDatabasesRadio</tabstop>
+ <tabstop>databasesList</tabstop>
+ <tabstop>initCodeEdit</tabstop>
+ <tabstop>mainCodeEdit</tabstop>
+ <tabstop>finalCodeEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
new file mode 100644
index 0000000..cf7efdf
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
@@ -0,0 +1,348 @@
+#include "functionseditormodel.h"
+#include "common/strhash.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "plugins/scriptingplugin.h"
+#include "icon.h"
+#include <QDebug>
+
+#define SETTER(X, Y) \
+ if (!isValidRowIndex(row) || X == Y) \
+ return; \
+ \
+ X = Y; \
+ emitDataChanged(row);
+
+#define GETTER(X, Y) \
+ if (!isValidRowIndex(row)) \
+ return Y; \
+ \
+ return X;
+
+FunctionsEditorModel::FunctionsEditorModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+ init();
+}
+
+void FunctionsEditorModel::clearModified()
+{
+ beginResetModel();
+ foreach (Function* func, functionList)
+ func->modified = false;
+
+ listModified = false;
+ originalFunctionList = functionList;
+
+ endResetModel();
+}
+
+bool FunctionsEditorModel::isModified() const
+{
+ if (functionList != originalFunctionList)
+ return true;
+
+ foreach (Function* func, functionList)
+ {
+ if (func->modified)
+ return true;
+ }
+ return false;
+}
+
+bool FunctionsEditorModel::isModified(int row) const
+{
+ GETTER(functionList[row]->modified, false);
+}
+
+void FunctionsEditorModel::setModified(int row, bool modified)
+{
+ SETTER(functionList[row]->modified, modified);
+}
+
+bool FunctionsEditorModel::isValid() const
+{
+ foreach (Function* func, functionList)
+ {
+ if (!func->valid)
+ return false;
+ }
+ return true;
+}
+
+bool FunctionsEditorModel::isValid(int row) const
+{
+ GETTER(functionList[row]->valid, false);
+}
+
+void FunctionsEditorModel::setValid(int row, bool valid)
+{
+ SETTER(functionList[row]->valid, valid);
+}
+
+void FunctionsEditorModel::setCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.code, code);
+}
+
+QString FunctionsEditorModel::getCode(int row) const
+{
+ GETTER(functionList[row]->data.code, QString::null);
+}
+
+void FunctionsEditorModel::setFinalCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.finalCode, code);
+}
+
+QString FunctionsEditorModel::getFinalCode(int row) const
+{
+ GETTER(functionList[row]->data.finalCode, QString::null);
+}
+
+void FunctionsEditorModel::setInitCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.initCode, code);
+}
+
+QString FunctionsEditorModel::getInitCode(int row) const
+{
+ GETTER(functionList[row]->data.initCode, QString::null);
+}
+
+void FunctionsEditorModel::setName(int row, const QString& newName)
+{
+ SETTER(functionList[row]->data.name, newName);
+}
+
+QString FunctionsEditorModel::getName(int row) const
+{
+ GETTER(functionList[row]->data.name, QString::null);
+}
+
+void FunctionsEditorModel::setLang(int row, const QString& lang)
+{
+ SETTER(functionList[row]->data.lang, lang);
+}
+
+QString FunctionsEditorModel::getLang(int row) const
+{
+ GETTER(functionList[row]->data.lang, QString::null);
+}
+
+bool FunctionsEditorModel::getUndefinedArgs(int row) const
+{
+ GETTER(functionList[row]->data.undefinedArgs, true);
+}
+
+void FunctionsEditorModel::setUndefinedArgs(int row, bool value)
+{
+ SETTER(functionList[row]->data.undefinedArgs, value);
+}
+
+bool FunctionsEditorModel::getAllDatabases(int row) const
+{
+ GETTER(functionList[row]->data.allDatabases, true);
+}
+
+void FunctionsEditorModel::setAllDatabases(int row, bool value)
+{
+ SETTER(functionList[row]->data.allDatabases, value);
+}
+
+FunctionManager::ScriptFunction::Type FunctionsEditorModel::getType(int row) const
+{
+ GETTER(functionList[row]->data.type, FunctionManager::ScriptFunction::SCALAR);
+}
+
+void FunctionsEditorModel::setType(int row, FunctionManager::ScriptFunction::Type type)
+{
+ SETTER(functionList[row]->data.type, type);
+}
+
+bool FunctionsEditorModel::isAggregate(int row) const
+{
+ GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::AGGREGATE, false);
+}
+
+bool FunctionsEditorModel::isScalar(int row) const
+{
+ GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::SCALAR, false);
+}
+
+QStringList FunctionsEditorModel::getArguments(int row) const
+{
+ GETTER(functionList[row]->data.arguments, QStringList());
+}
+
+void FunctionsEditorModel::setArguments(int row, const QStringList& value)
+{
+ SETTER(functionList[row]->data.arguments, value);
+}
+
+QStringList FunctionsEditorModel::getDatabases(int row) const
+{
+ GETTER(functionList[row]->data.databases, QStringList());
+}
+
+void FunctionsEditorModel::setDatabases(int row, const QStringList& value)
+{
+ SETTER(functionList[row]->data.databases, value);
+}
+
+void FunctionsEditorModel::setData(const QList<FunctionManager::ScriptFunction*>& functions)
+{
+ beginResetModel();
+
+ for (Function* functionPtr : functionList)
+ delete functionPtr;
+
+ functionList.clear();
+
+ foreach (FunctionManager::ScriptFunction* func, functions)
+ functionList << new Function(func);
+
+ listModified = false;
+ originalFunctionList = functionList;
+
+ endResetModel();
+}
+
+void FunctionsEditorModel::addFunction(FunctionManager::ScriptFunction* function)
+{
+ int row = functionList.size();
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ functionList << new Function(function);
+ listModified = true;
+
+ endInsertRows();
+}
+
+void FunctionsEditorModel::deleteFunction(int row)
+{
+ if (!isValidRowIndex(row))
+ return;
+
+ beginRemoveRows(QModelIndex(), row, row);
+
+ delete functionList[row];
+ functionList.removeAt(row);
+
+ listModified = true;
+
+ endRemoveRows();
+}
+
+QList<FunctionManager::ScriptFunction*> FunctionsEditorModel::generateFunctions() const
+{
+ QList<FunctionManager::ScriptFunction*> results;
+
+ foreach (Function* func, functionList)
+ results << new FunctionManager::ScriptFunction(func->data);
+
+ return results;
+}
+
+QStringList FunctionsEditorModel::getFunctionNames() const
+{
+ QStringList names;
+ foreach (Function* func, functionList)
+ names << func->data.name;
+
+ return names;
+}
+
+void FunctionsEditorModel::validateNames()
+{
+ StrHash<QList<int>> counter;
+
+ int row = 0;
+ foreach (Function* func, functionList)
+ {
+ func->valid &= true;
+ counter[func->data.name] << row++;
+ }
+
+ QHashIterator<QString,QList<int>> cntIt = counter.iterator();
+ while (cntIt.hasNext())
+ {
+ cntIt.next();
+ if (cntIt.value().size() > 1)
+ {
+ foreach (int cntRow, cntIt.value())
+ setValid(cntRow, false);
+ }
+ }
+
+ QModelIndex idx;
+ for (int i = 0; i < functionList.size(); i++)
+ {
+ idx = index(i);
+ emit dataChanged(idx, idx);
+ }
+}
+
+bool FunctionsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate)
+{
+ QStringList names = getFunctionNames();
+ names.removeAt(rowToSkip);
+ return !names.contains(nameToValidate, Qt::CaseInsensitive);
+}
+
+int FunctionsEditorModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return functionList.size();
+}
+
+QVariant FunctionsEditorModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || !isValidRowIndex(index.row()))
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ Function* fn = functionList[index.row()];
+ return fn->data.toString();
+ }
+
+ if (role == Qt::DecorationRole && langToIcon.contains(functionList[index.row()]->data.lang))
+ {
+ QIcon icon = langToIcon[functionList[index.row()]->data.lang];
+ if (!functionList[index.row()]->valid)
+ icon = Icon::merge(icon, Icon::ERROR);
+
+ return icon;
+ }
+
+ return QVariant();
+}
+
+void FunctionsEditorModel::init()
+{
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
+}
+
+bool FunctionsEditorModel::isValidRowIndex(int row) const
+{
+ return (row >= 0 && row < functionList.size());
+}
+
+void FunctionsEditorModel::emitDataChanged(int row)
+{
+ QModelIndex idx = index(row);
+ emit dataChanged(idx, idx);
+}
+
+FunctionsEditorModel::Function::Function()
+{
+}
+
+FunctionsEditorModel::Function::Function(FunctionManager::ScriptFunction* other)
+{
+ data = FunctionManager::ScriptFunction(*other);
+ originalName = data.name;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h
new file mode 100644
index 0000000..79f073f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h
@@ -0,0 +1,98 @@
+#ifndef FUNCTIONSEDITORMODEL_H
+#define FUNCTIONSEDITORMODEL_H
+
+#include "services/config.h"
+#include "services/functionmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QIcon>
+#include <QAbstractListModel>
+
+class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ public:
+ using QAbstractItemModel::setData;
+
+ enum Role
+ {
+ CODE = 1000,
+ MODIFIED = 1001,
+ VALID = 1002,
+ TYPE = 1003
+ };
+
+ explicit FunctionsEditorModel(QObject *parent = 0);
+
+ void clearModified();
+ bool isModified() const;
+ bool isModified(int row) const;
+ void setModified(int row, bool modified);
+ bool isValid() const;
+ bool isValid(int row) const;
+ void setValid(int row, bool valid);
+ void setCode(int row, const QString& code);
+ QString getCode(int row) const;
+ void setFinalCode(int row, const QString& code);
+ QString getFinalCode(int row) const;
+ void setInitCode(int row, const QString& code);
+ QString getInitCode(int row) const;
+ void setName(int row, const QString& newName);
+ QString getName(int row) const;
+ void setLang(int row, const QString& lang);
+ QString getLang(int row) const;
+ QStringList getDatabases(int row) const;
+ void setDatabases(int row, const QStringList& value);
+ QStringList getArguments(int row) const;
+ void setArguments(int row, const QStringList& value);
+ FunctionManager::ScriptFunction::Type getType(int row) const;
+ void setType(int row, FunctionManager::ScriptFunction::Type type);
+ bool isAggregate(int row) const;
+ bool isScalar(int row) const;
+ bool getUndefinedArgs(int row) const;
+ void setUndefinedArgs(int row, bool value);
+ bool getAllDatabases(int row) const;
+ void setAllDatabases(int row, bool value);
+ void setData(const QList<FunctionManager::ScriptFunction*>& functions);
+ void addFunction(FunctionManager::ScriptFunction* function);
+ void deleteFunction(int row);
+ QList<FunctionManager::ScriptFunction*> generateFunctions() const;
+ QStringList getFunctionNames() const;
+ void validateNames();
+ bool isAllowedName(int rowToSkip, const QString& nameToValidate);
+ bool isValidRowIndex(int row) const;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ private:
+ struct Function
+ {
+ Function();
+ Function(FunctionManager::ScriptFunction* other);
+
+ FunctionManager::ScriptFunction data;
+ bool modified = false;
+ bool valid = true;
+ QString originalName;
+ };
+
+ void init();
+ void emitDataChanged(int row);
+
+ QList<Function*> functionList;
+
+ /**
+ * @brief List of function pointers before modifications.
+ *
+ * This list is kept to check for modifications in the overall list of functions.
+ * Pointers on this list may be already deleted, so don't use them!
+ * It's only used to compare list of pointers to functionList, so it can tell you
+ * if the list was modified in regards of adding or deleting functions.
+ */
+ QList<Function*> originalFunctionList;
+ QHash<QString,QIcon> langToIcon;
+ bool listModified = false;
+};
+
+#endif // FUNCTIONSEDITORMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
new file mode 100644
index 0000000..850d8a7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
@@ -0,0 +1,482 @@
+#include "tableconstraintsmodel.h"
+#include "iconmanager.h"
+#include "common/utils_sql.h"
+#include "common/unused.h"
+#include <QDebug>
+#include <QMimeData>
+
+TableConstraintsModel::TableConstraintsModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int TableConstraintsModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ return createTable->constraints.size();
+}
+
+int TableConstraintsModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 3;
+}
+
+QVariant TableConstraintsModel::data(const QModelIndex& index, int role) const
+{
+ if (createTable.isNull())
+ return QVariant();
+
+ SqliteCreateTable::Constraint* constr = createTable->constraints[index.row()];
+ switch (getColumn(index.column()))
+ {
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant TableConstraintsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ switch (getColumn(section))
+ {
+ case TableConstraintsModel::Columns::TYPE:
+ return tr("Type", "table constraints");
+ case TableConstraintsModel::Columns::DETAILS:
+ return tr("Details", "table constraints");
+ case TableConstraintsModel::Columns::NAME:
+ return tr("Name", "table constraints");
+ }
+ return QVariant();
+}
+
+Qt::DropActions TableConstraintsModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+Qt::DropActions TableConstraintsModel::supportedDragActions() const
+{
+ return Qt::CopyAction|Qt::MoveAction;
+}
+
+QStringList TableConstraintsModel::mimeTypes() const
+{
+ return {mimeType};
+}
+
+QMimeData* TableConstraintsModel::mimeData(const QModelIndexList& indexes) const
+{
+ if (indexes.size() < 1)
+ return nullptr;
+
+ QModelIndex idx = indexes.first();
+
+ QMimeData *data = new QMimeData();
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ stream << idx.row();
+ data->setData(mimeType, output);
+
+ return data;
+}
+
+bool TableConstraintsModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ UNUSED(action);
+ UNUSED(row);
+ UNUSED(column);
+ UNUSED(parent);
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ return true;
+}
+
+bool TableConstraintsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(column);
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ if (action != Qt::MoveAction)
+ return false;
+
+ if (row < 0)
+ {
+ if (!parent.isValid() && !createTable.isNull())
+ row = createTable->constraints.size();
+ else
+ row = parent.row();
+ }
+
+ if (row < 0)
+ return false;
+
+ QByteArray byteData = data->data(mimeType);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+ int oldRow;
+ stream >> oldRow;
+
+ moveConstraintColumnTo(oldRow, row);
+ return true;
+}
+
+Qt::ItemFlags TableConstraintsModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defFlags = QAbstractItemModel::flags(index);
+ if (!index.isValid())
+ return defFlags|Qt::ItemIsDropEnabled;
+
+ return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled;
+}
+
+bool TableConstraintsModel::isModified() const
+{
+ return modified;
+}
+
+void TableConstraintsModel::setCreateTable(SqliteCreateTable* value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+ modified = false;
+ emit modifiyStateChanged();
+}
+
+SqliteCreateTable::Constraint* TableConstraintsModel::getConstraint(int constrIdx) const
+{
+ if (createTable.isNull())
+ return nullptr;
+
+ return createTable->constraints[constrIdx];
+}
+
+void TableConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ delete createTable->constraints[constrIdx];
+ createTable->constraints[constrIdx] = constr;
+ constr->setParent(createTable);
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1));
+}
+
+void TableConstraintsModel::constraintModified(int constrIdx)
+{
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1));
+}
+
+void TableConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), constrIdx, constrIdx);
+ createTable->constraints.insert(constrIdx, constr);
+ constr->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::appendConstraint(SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ createTable->constraints.append(constr);
+ constr->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::delConstraint(int constrIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ beginRemoveRows(QModelIndex(), constrIdx, constrIdx);
+ delete createTable->constraints[constrIdx];
+ createTable->constraints.removeAt(constrIdx);
+ endRemoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::moveConstraintUp(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx-1);
+}
+
+void TableConstraintsModel::moveConstraintDown(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx+1);
+}
+
+void TableConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ if (newIdx == constrIdx)
+ return;
+
+ if (newIdx == constrIdx+1)
+ {
+ // See TableStructureModel::moveColumnTo() for details above code below.
+ int tmpIdx = newIdx;
+ newIdx = constrIdx;
+ constrIdx = tmpIdx;
+ }
+
+ beginMoveRows(QModelIndex(), constrIdx, constrIdx, QModelIndex(), newIdx);
+ if (newIdx >= createTable->constraints.size())
+ {
+ SqliteCreateTable::Constraint* constr = createTable->constraints.takeAt(constrIdx);
+ createTable->constraints.append(constr);
+ }
+ else
+ createTable->constraints.move(constrIdx, newIdx);
+
+ endMoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit constraintOrderChanged();
+}
+
+TableConstraintsModel::Columns TableConstraintsModel::getColumn(int idx) const
+{
+ return static_cast<Columns>(idx);
+}
+
+QString TableConstraintsModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QIcon TableConstraintsModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QIcon();
+ }
+ return QIcon();
+}
+
+QString TableConstraintsModel::getDetails(SqliteCreateTable::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString TableConstraintsModel::getPkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getFkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const
+{
+ TokenList tokens = constr->tokens.mid(tokenOffset);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+void TableConstraintsModel::columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn)
+{
+ foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ {
+ if (idxCol->name.compare(oldColumn, Qt::CaseInsensitive) == 0)
+ {
+ idxCol->name = newColumn;
+ modified = true;
+ }
+ }
+
+ emit modifiyStateChanged();
+}
+
+bool TableConstraintsModel::handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column)
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ case SqliteCreateTable::Constraint::UNIQUE:
+ {
+ QMutableListIterator<SqliteIndexedColumn*> it(constr->indexedColumns);
+ SqliteIndexedColumn* idxCol = nullptr;
+ while (it.hasNext())
+ {
+ idxCol = it.next();
+ if (idxCol->name.compare(column, Qt::CaseInsensitive) == 0)
+ {
+ it.remove();
+ delete idxCol;
+ modified = true;
+ }
+ }
+
+ emit modifiyStateChanged();
+ return constr->indexedColumns.count() > 0;
+ }
+ case SqliteCreateTable::Constraint::CHECK:
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ break;
+ }
+ return true;
+}
+
+void TableConstraintsModel::columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn)
+{
+ if (newColumn->name == oldColumn)
+ return;
+
+ int idx = 0;
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ if (constr->doesAffectColumn(oldColumn))
+ {
+ columnRenamed(constr, oldColumn, newColumn->name);
+ constr->rebuildTokens();
+ emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1));
+ }
+
+ idx++;
+ }
+}
+
+void TableConstraintsModel::columnDeleted(const QString& column)
+{
+ QList<int> toDelete;
+ int idx = 0;
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ if (constr->doesAffectColumn(column))
+ {
+ if (handleColumnDeleted(constr, column))
+ {
+ constr->rebuildTokens();
+ emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1));
+ }
+ else
+ toDelete << idx;
+ }
+
+ idx++;
+ }
+
+ foreach (int idx, toDelete)
+ delConstraint(idx);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h
new file mode 100644
index 0000000..e3ee9e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h
@@ -0,0 +1,72 @@
+#ifndef TABLECONSTRAINTSMODEL_H
+#define TABLECONSTRAINTSMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT TableConstraintsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit TableConstraintsModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ Qt::DropActions supportedDropActions() const;
+ Qt::DropActions supportedDragActions() const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList& indexes) const;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+ bool isModified() const;
+ void setCreateTable(SqliteCreateTable* value);
+ SqliteCreateTable::Constraint* getConstraint(int constrIdx) const;
+ void replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr);
+ void constraintModified(int constrIdx);
+ void insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr);
+ void appendConstraint(SqliteCreateTable::Constraint* constr);
+ void delConstraint(int constrIdx);
+ void moveConstraintUp(int constrIdx);
+ void moveConstraintDown(int constrIdx);
+ void moveConstraintColumnTo(int constrIdx, int newIdx);
+
+ private:
+ enum class Columns
+ {
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Columns getColumn(int idx) const;
+ QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const;
+ QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const;
+ QString getDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getPkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const;
+ void columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn);
+ bool handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column);
+
+ static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructureconstraintmodel-row-index";
+
+ QPointer<SqliteCreateTable> createTable;
+ bool modified = false;
+
+ public slots:
+ void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn);
+ void columnDeleted(const QString& column);
+
+ signals:
+ void modifiyStateChanged();
+ void constraintOrderChanged();
+};
+
+#endif // TABLECONSTRAINTSMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
new file mode 100644
index 0000000..80c4567
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
@@ -0,0 +1,604 @@
+#include "tablestructuremodel.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include "uiconfig.h"
+#include <QFont>
+#include <QDebug>
+#include <QMimeData>
+
+TableStructureModel::TableStructureModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int TableStructureModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ return createTable->columns.size();
+}
+
+int TableStructureModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ switch (createTable->dialect)
+ {
+ case Dialect::Sqlite3:
+ return 9;
+ case Dialect::Sqlite2:
+ return 7;
+ }
+ return 0;
+}
+
+QVariant TableStructureModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (createTable.isNull())
+ return QVariant();
+
+ if (!isValidColumnIdx(index.column()))
+ return QVariant();
+
+ int row = index.row();
+ if (createTable->columns.size() <= row)
+ return QVariant();
+
+ switch (getHeaderColumn(index.column()))
+ {
+ case TableStructureModel::Columns::NAME:
+ {
+ if (role != Qt::DisplayRole)
+ break;
+
+ return getColumnName(row);
+ }
+ case TableStructureModel::Columns::TYPE:
+ {
+ if (role != Qt::DisplayRole)
+ break;
+
+ return getColumnType(row);
+ }
+ case TableStructureModel::Columns::PK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnPk(row);
+ }
+ case TableStructureModel::Columns::FK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnFk(row);
+ }
+ case TableStructureModel::Columns::UNIQUE:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnUnique(row);
+ }
+ case TableStructureModel::Columns::CHECK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnCheck(row);
+ }
+ case TableStructureModel::Columns::NOTNULL:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnNotNull(row);
+ }
+ case TableStructureModel::Columns::COLLATE:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnCollate(row);
+ }
+ case TableStructureModel::Columns::DEFAULT:
+ {
+ if (role == Qt::FontRole)
+ return getColumnDefaultFont(row);
+
+ if (role == Qt::ForegroundRole)
+ return getColumnDefaultColor(row);
+
+ if (role == Qt::DisplayRole)
+ return getColumnDefaultValue(row);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant TableStructureModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ // Now it's horizontal orientation with DisplayRole
+ return columnLabel(section);
+}
+
+TableStructureModel::Columns TableStructureModel::getHeaderColumn(int colIdx) const
+{
+ if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2)
+ {
+ if (colIdx >= 3)
+ colIdx++; // skip FK
+
+ if (colIdx >= 7)
+ colIdx++; // skip COLLATE
+ }
+ return static_cast<Columns>(colIdx);
+}
+
+bool TableStructureModel::isValidColumnIdx(int colIdx) const
+{
+ if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2)
+ return colIdx >= 0 && colIdx < 7;
+
+ return colIdx >= 0 && colIdx < 9;
+}
+
+SqliteCreateTable::Column* TableStructureModel::getColumn(int colIdx) const
+{
+ if (createTable.isNull())
+ return nullptr;
+
+ return createTable->columns[colIdx];
+}
+
+void TableStructureModel::replaceColumn(int colIdx, SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ SqliteCreateTable::Column* oldColumn = createTable->columns[colIdx];
+ QString oldColumnName = oldColumn->name;
+
+ delete oldColumn;
+ createTable->columns[colIdx] = column;
+ column->setParent(createTable);
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(colIdx, 0), createIndex(colIdx, columnCount()-1));
+ emit columnModified(oldColumnName, column);
+}
+
+void TableStructureModel::insertColumn(int colIdx, SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), colIdx, colIdx);
+ createTable->columns.insert(colIdx, column);
+ column->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableStructureModel::appendColumn(SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ createTable->columns.append(column);
+ column->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableStructureModel::delColumn(int colIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ QString name = createTable->columns[colIdx]->name;
+
+ beginRemoveRows(QModelIndex(), colIdx, colIdx);
+ delete createTable->columns[colIdx];
+ createTable->columns.removeAt(colIdx);
+ endRemoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit columnDeleted(name);
+}
+
+void TableStructureModel::moveColumnUp(int colIdx)
+{
+ moveColumnTo(colIdx, colIdx-1);
+}
+
+void TableStructureModel::moveColumnDown(int colIdx)
+{
+ moveColumnTo(colIdx, colIdx+1);
+}
+
+void TableStructureModel::moveColumnTo(int colIdx, int newIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ if (newIdx == colIdx)
+ return;
+
+ int totalCols = createTable->columns.size();
+ if (colIdx + 1 == totalCols && newIdx == totalCols) // Moving last column out of range? Nothing to do.
+ return;
+
+ if (newIdx == colIdx+1)
+ {
+ // From Qt docs: "you must ensure that the destinationChild is not within the range of sourceFirst and sourceLast + 1".
+ // So in this case - which is easy to handle - we will invert operation. We will move target index one level up,
+ // instead of moving source index down.
+ int tmpIdx = newIdx;
+ newIdx = colIdx;
+ colIdx = tmpIdx;
+ }
+
+ beginMoveRows(QModelIndex(), colIdx, colIdx, QModelIndex(), newIdx);
+ if (newIdx >= totalCols)
+ {
+ SqliteCreateTable::Column* col = createTable->columns.takeAt(colIdx);
+ createTable->columns.append(col);
+ }
+ else
+ createTable->columns.move(colIdx, newIdx);
+
+ endMoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit columnsOrderChanged();
+}
+
+QModelIndex TableStructureModel::findColumn(const QString& columnName, Qt::CaseSensitivity cs) const
+{
+ int row = 0;
+ foreach (SqliteCreateTable::Column* col, createTable->columns)
+ {
+ if (col->name.compare(columnName, cs) == 0)
+ return createIndex(row, 0);
+
+ row++;
+ }
+ return QModelIndex();
+}
+
+QString TableStructureModel::columnLabel(int column) const
+{
+ switch (getHeaderColumn(column))
+ {
+ case Columns::NAME:
+ return tr("Name", "table structure columns");
+ case Columns::TYPE:
+ return tr("Data type", "table structure columns");
+ case Columns::PK:
+ return "P";
+ case Columns::FK:
+ return "F";
+ case Columns::UNIQUE:
+ return "U";
+ case Columns::CHECK:
+ return "H";
+ case Columns::NOTNULL:
+ return "N";
+ case Columns::COLLATE:
+ return "C";
+ case Columns::DEFAULT:
+ return tr("Default value", "table structure columns");
+ }
+ return QString::null;
+}
+
+QVariant TableStructureModel::getColumnName(int row) const
+{
+ return getColumn(row)->name;
+}
+
+QVariant TableStructureModel::getColumnType(int row) const
+{
+ SqliteColumnType* type = getColumn(row)->type;
+ return type ? type->detokenize() : "";
+}
+
+QVariant TableStructureModel::getColumnPk(int row) const
+{
+ if (isColumnPk(getColumn(row)))
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnFk(int row) const
+{
+ if (isColumnFk(getColumn(row)))
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnUnique(int row) const
+{
+ if (isColumnUnique(getColumn(row)))
+ return ICONS.CONSTRAINT_UNIQUE;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnCheck(int row) const
+{
+ if (isColumnCheck(getColumn(row)))
+ return ICONS.CONSTRAINT_CHECK;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnNotNull(int row) const
+{
+ if (isColumnNotNull(getColumn(row)))
+ return ICONS.CONSTRAINT_NOT_NULL;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnCollate(int row) const
+{
+ if (isColumnCollate(getColumn(row)))
+ return ICONS.CONSTRAINT_COLLATION;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefaultValue(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ return "NULL";
+
+ return value;
+}
+
+QVariant TableStructureModel::getColumnDefaultFont(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ {
+ QFont font;
+ font.setItalic(true);
+ return font;
+ }
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefaultColor(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ return QColor(CFG_UI.Colors.DataNullFg);
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefault(int row) const
+{
+ SqliteCreateTable::Column::Constraint* constr = getColumn(row)->getConstraint(SqliteCreateTable::Column::Constraint::DEFAULT);
+ if (!constr)
+ return QVariant();
+
+ if (!constr->id.isNull())
+ return constr->id;
+ else if (!constr->literalValue.isNull())
+ return constr->literalValue;
+ else if (!constr->ctime.isNull())
+ return constr->ctime;
+ else if (constr->expr)
+ return constr->expr->detokenize();
+ else
+ return QVariant();
+}
+
+bool TableStructureModel::isColumnPk(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnFk(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::FOREIGN_KEY);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnUnique(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::UNIQUE))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::UNIQUE);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnCheck(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::CHECK))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::CHECK);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->expr->getContextColumns(false).contains(column->name, Qt::CaseInsensitive))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnNotNull(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnCollate(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::COLLATE))
+ return true;
+
+ return false;
+}
+
+void TableStructureModel::setCreateTable(SqliteCreateTable* value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+
+ modified = false;
+ emit modifiyStateChanged();
+}
+
+bool TableStructureModel::isModified() const
+{
+ return modified;
+}
+
+Qt::DropActions TableStructureModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+Qt::DropActions TableStructureModel::supportedDragActions() const
+{
+ return Qt::CopyAction|Qt::MoveAction;
+}
+
+
+QStringList TableStructureModel::mimeTypes() const
+{
+ return {mimeType};
+}
+
+QMimeData* TableStructureModel::mimeData(const QModelIndexList& indexes) const
+{
+ if (indexes.size() < 1)
+ return nullptr;
+
+ QModelIndex idx = indexes.first();
+
+ QMimeData *data = new QMimeData();
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ stream << idx.row();
+ data->setData(mimeType, output);
+
+ return data;
+}
+
+
+bool TableStructureModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ UNUSED(action);
+ UNUSED(row);
+ UNUSED(column);
+ UNUSED(parent);
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ return true;
+}
+
+bool TableStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(column);
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ if (action != Qt::MoveAction)
+ return false;
+
+ if (row < 0)
+ {
+ if (!parent.isValid() && !createTable.isNull())
+ row = createTable->columns.size();
+ else
+ row = parent.row();
+ }
+
+ if (row < 0)
+ return false;
+
+ QByteArray byteData = data->data(mimeType);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+ int oldRow;
+ stream >> oldRow;
+
+ moveColumnTo(oldRow, row);
+ return true;
+}
+
+Qt::ItemFlags TableStructureModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defFlags = QAbstractItemModel::flags(index);
+ if (!index.isValid())
+ return defFlags|Qt::ItemIsDropEnabled;
+
+ return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h
new file mode 100644
index 0000000..e1cfa4e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h
@@ -0,0 +1,89 @@
+#ifndef TABLESTRUCTUREMODEL_H
+#define TABLESTRUCTUREMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT TableStructureModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+ public:
+ explicit TableStructureModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ Qt::DropActions supportedDropActions() const;
+ Qt::DropActions supportedDragActions() const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList& indexes) const;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+ bool isModified() const;
+ void setCreateTable(SqliteCreateTable* value);
+ SqliteCreateTable::Column* getColumn(int colIdx) const;
+ void replaceColumn(int colIdx, SqliteCreateTable::Column* column);
+ void insertColumn(int colIdx, SqliteCreateTable::Column* column);
+ void appendColumn(SqliteCreateTable::Column* column);
+ void delColumn(int colIdx);
+ void moveColumnUp(int colIdx);
+ void moveColumnDown(int colIdx);
+ void moveColumnTo(int colIdx, int newIdx);
+ QModelIndex findColumn(const QString& columnName, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
+
+ private:
+ enum class Columns
+ {
+ NAME,
+ TYPE,
+ PK,
+ FK,
+ UNIQUE,
+ CHECK,
+ NOTNULL,
+ COLLATE,
+ DEFAULT
+ };
+
+ Columns getHeaderColumn(int colIdx) const;
+ bool isValidColumnIdx(int colIdx) const;
+ bool doesColumnHasConstraint(SqliteCreateTable::Column* column, SqliteCreateTable::Column::Constraint::Type type);
+ QString columnLabel(int column) const;
+ QString columnLabelForSqlite2(int column) const;
+ QVariant getColumnName(int row) const;
+ QVariant getColumnType(int row) const;
+ QVariant getColumnPk(int row) const;
+ QVariant getColumnFk(int row) const;
+ QVariant getColumnUnique(int row) const;
+ QVariant getColumnCheck(int row) const;
+ QVariant getColumnNotNull(int row) const;
+ QVariant getColumnCollate(int row) const;
+ QVariant getColumnDefaultValue(int row) const;
+ QVariant getColumnDefaultFont(int row) const;
+ QVariant getColumnDefaultColor(int row) const;
+ QVariant getColumnDefault(int row) const;
+ bool isColumnPk(SqliteCreateTable::Column* column) const;
+ bool isColumnFk(SqliteCreateTable::Column* column) const;
+ bool isColumnUnique(SqliteCreateTable::Column* column) const;
+ bool isColumnCheck(SqliteCreateTable::Column* column) const;
+ bool isColumnNotNull(SqliteCreateTable::Column* column) const;
+ bool isColumnCollate(SqliteCreateTable::Column* column) const;
+
+ static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructuremodel-row-index";
+
+ QPointer<SqliteCreateTable> createTable;
+ bool modified = false;
+
+ signals:
+ void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn);
+ void columnDeleted(const QString& column);
+ void modifiyStateChanged();
+ void columnsOrderChanged();
+};
+
+#endif // TABLESTRUCTUREMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
new file mode 100644
index 0000000..56accd0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
@@ -0,0 +1,1508 @@
+#include "tablewindow.h"
+#include "ui_tablewindow.h"
+#include "services/dbmanager.h"
+#include "services/notifymanager.h"
+#include "sqlitestudio.h"
+#include "common/unused.h"
+#include "schemaresolver.h"
+#include "iconmanager.h"
+#include "common/intvalidator.h"
+#include "common/extlineedit.h"
+#include "datagrid/sqltablemodel.h"
+#include "common/extaction.h"
+#include "mainwindow.h"
+#include "tablestructuremodel.h"
+#include "tableconstraintsmodel.h"
+#include "dialogs/columndialog.h"
+#include "dialogs/constraintdialog.h"
+#include "mdiarea.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "dialogs/newconstraintdialog.h"
+#include "db/chainexecutor.h"
+#include "common/widgetcover.h"
+#include "mdiwindow.h"
+#include "dbtree/dbtree.h"
+#include "constrainttabmodel.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "dialogs/messagelistdialog.h"
+#include "services/codeformatter.h"
+#include "uiconfig.h"
+#include "dialogs/ddlpreviewdialog.h"
+#include "services/config.h"
+#include "services/importmanager.h"
+#include "dbobjectdialogs.h"
+#include "dialogs/exportdialog.h"
+#include <QMenu>
+#include <QToolButton>
+#include <QLabel>
+#include <QDebug>
+#include <QMessageBox>
+#include <tablemodifier.h>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QDebug>
+#include <QStyleFactory>
+#include <dialogs/importdialog.h>
+#include <dialogs/populatedialog.h>
+
+// TODO extend QTableView for columns and constraints, so they show full-row-width drop indicator,
+// instead of single column drop indicator.
+
+CFG_KEYS_DEFINE(TableWindow)
+
+TableWindow::TableWindow(QWidget* parent) :
+ MdiChild(parent),
+ ui(new Ui::TableWindow)
+{
+ init();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(Db* db, QWidget* parent) :
+ MdiChild(parent),
+ db(db),
+ ui(new Ui::TableWindow)
+{
+ newTable();
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(const TableWindow& win) :
+ MdiChild(win.parentWidget()),
+ db(win.db),
+ database(win.database),
+ table(win.table),
+ ui(new Ui::TableWindow)
+{
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table) :
+ MdiChild(parent),
+ db(db),
+ database(database),
+ table(table),
+ ui(new Ui::TableWindow)
+{
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::~TableWindow()
+{
+ delete ui;
+
+ if (tableModifier)
+ {
+ delete tableModifier;
+ tableModifier = nullptr;
+ }
+}
+
+void TableWindow::staticInit()
+{
+ qRegisterMetaType<TableWindow>("TableWindow");
+}
+
+void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<TableWindow>(action, toolbar);
+}
+
+void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<TableWindow>(action, beforeAction, toolbar);
+}
+
+void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<TableWindow>(action, afterAction, toolbar);
+}
+
+void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<TableWindow>(action, toolbar);
+}
+
+void TableWindow::newTable()
+{
+ existingTable = false;
+ table = "";
+}
+
+void TableWindow::init()
+{
+ ui->setupUi(this);
+ ui->structureSplitter->setStretchFactor(0, 2);
+
+#ifdef Q_OS_MACX
+ QStyle *fusion = QStyleFactory::create("Fusion");
+ ui->structureToolBar->setStyle(fusion);
+ ui->structureTab->layout()->setSpacing(0);
+ ui->tableConstraintsToolbar->setStyle(fusion);
+ ui->constraintsWidget->layout()->setSpacing(0);
+ ui->indexToolBar->setStyle(fusion);
+ ui->indexesTab->layout()->setSpacing(0);
+ ui->triggerToolBar->setStyle(fusion);
+ ui->triggersTab->layout()->setSpacing(0);
+#endif
+
+ dataModel = new SqlTableModel(this);
+ ui->dataView->init(dataModel);
+
+ initActions();
+
+ connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+ connect(this, SIGNAL(modifyStatusChanged()), this, SLOT(updateStructureCommitState()));
+ connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(modifyStatusChanged()));
+ connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged()));
+ connect(ui->indexList, SIGNAL(itemSelectionChanged()), this, SLOT(updateIndexesState()));
+ connect(ui->triggerList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
+
+ structureExecutor = new ChainExecutor(this);
+ connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited()));
+ connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
+
+ setupCoverWidget();
+ updateAfterInit();
+}
+
+void TableWindow::createActions()
+{
+ createAction(EXPORT, ICONS.TABLE_EXPORT, tr("Export table", "table window"), this, SLOT(exportTable()), this);
+ createAction(IMPORT, ICONS.TABLE_IMPORT, tr("Import data to table", "table window"), this, SLOT(importTable()), this);
+ createAction(POPULATE, ICONS.TABLE_POPULATE, tr("Populate table", "table window"), this, SLOT(populateTable()), this);
+
+ createStructureActions();
+ createDataGridActions();
+ createDataFormActions();
+ createIndexActions();
+ createTriggerActions();
+
+ createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
+ createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
+}
+
+void TableWindow::createStructureActions()
+{
+ createAction(REFRESH_STRUCTURE, ICONS.RELOAD, tr("Refresh structure", "table window"), this, SLOT(refreshStructure()), ui->structureToolBar);
+ ui->structureToolBar->addSeparator();
+ createAction(COMMIT_STRUCTURE, ICONS.COMMIT, tr("Commit structure changes", "table window"), this, SLOT(commitStructure()), ui->structureToolBar);
+ createAction(ROLLBACK_STRUCTURE, ICONS.ROLLBACK, tr("Rollback structure changes", "table window"), this, SLOT(rollbackStructure()), ui->structureToolBar);
+ createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "table window"), this, SLOT(addColumn()), ui->structureToolBar, ui->structureView);
+ createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "table window"), this, SLOT(editColumn()), ui->structureToolBar, ui->structureView);
+ createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "table window"), this, SLOT(delColumn()), ui->structureToolBar, ui->structureView);
+ createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "table window"), this, SLOT(moveColumnUp()), ui->structureToolBar, ui->structureView);
+ createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "table window"), this, SLOT(moveColumnDown()), ui->structureToolBar, ui->structureView);
+ ui->structureToolBar->addSeparator();
+ ui->structureToolBar->addAction(actionMap[IMPORT]);
+ ui->structureToolBar->addAction(actionMap[EXPORT]);
+ ui->structureToolBar->addAction(actionMap[POPULATE]);
+ ui->structureToolBar->addSeparator();
+ createAction(CREATE_SIMILAR, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table", "table window"), this, SLOT(createSimilarTable()), ui->structureToolBar);
+
+ // Table constraints
+ createAction(ADD_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_ADD, tr("Add table constraint", "table window"), this, SLOT(addConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(EDIT_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_EDIT, tr("Edit table constraint", "table window"), this, SLOT(editConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(DEL_TABLE_CONSTRAINT, ICONS.TABLE_COLUMN_DELETE, tr("Delete table constraint", "table window"), this, SLOT(delConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move table constraint up", "table window"), this, SLOT(moveConstraintUp()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move table constraint down", "table window"), this, SLOT(moveConstraintDown()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ ui->tableConstraintsToolbar->addSeparator();
+ createAction(ADD_TABLE_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add table primary key", "table window"), this, SLOT(addPk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add table foreign key", "table window"), this, SLOT(addFk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add table unique constraint", "table window"), this, SLOT(addUnique()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add table check constraint", "table window"), this, SLOT(addCheck()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+}
+
+void TableWindow::createDataGridActions()
+{
+ QAction* before = ui->dataView->getAction(DataView::FILTER_VALUE);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[IMPORT]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[EXPORT]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[POPULATE]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertSeparator(before);
+}
+
+void TableWindow::createDataFormActions()
+{
+}
+
+void TableWindow::createIndexActions()
+{
+ createAction(REFRESH_INDEXES, ICONS.RELOAD, tr("Refresh index list", "table window"), this, SLOT(updateIndexes()), ui->indexToolBar, ui->indexList);
+ ui->indexToolBar->addSeparator();
+ createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->indexToolBar, ui->indexList);
+ createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit index", "table window"), this, SLOT(editIndex()), ui->indexToolBar, ui->indexList);
+ createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Delete index", "table window"), this, SLOT(delIndex()), ui->indexToolBar, ui->indexList);
+ connect(ui->indexList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editIndex()));
+}
+
+void TableWindow::createTriggerActions()
+{
+ createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "table window"), this, SLOT(updateTriggers()), ui->triggerToolBar, ui->triggerList);
+ ui->triggerToolBar->addSeparator();
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->triggerToolBar, ui->triggerList);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit trigger", "table window"), this, SLOT(editTrigger()), ui->triggerToolBar, ui->triggerList);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete trigger", "table window"), this, SLOT(delTrigger()), ui->triggerToolBar, ui->triggerList);
+ connect(ui->triggerList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editTrigger()));
+}
+
+void TableWindow::editColumn(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::editColumn() with invalid index.";
+ return;
+ }
+
+ SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
+ ColumnDialog columnDialog(db, this);
+ columnDialog.setColumn(column);
+ if (columnDialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
+ structureModel->replaceColumn(idx.row(), modifiedColumn);
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::delColumn(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::delColumn() with invalid index.";
+ return;
+ }
+
+ SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
+
+ QString msg = tr("Are you sure you want to delete column '%1'?", "table window").arg(column->name);
+ int btn = QMessageBox::question(this, tr("Delete column", "table window"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ structureModel->delColumn(idx.row());
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::executeStructureChanges()
+{
+ QStringList sqls;
+
+ createTable->rebuildTokens();
+ if (!existingTable)
+ {
+ sqls << createTable->detokenize();
+ }
+ else
+ {
+ if (tableModifier)
+ delete tableModifier;
+
+ tableModifier = new TableModifier(db, database, table);
+ tableModifier->alterTable(createTable);
+
+ if (tableModifier->hasMessages())
+ {
+ MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n"
+ "Would you like to proceed?", "table window"));
+ dialog.setWindowTitle(tr("Table modification", "table window"));
+ foreach (const QString& error, tableModifier->getErrors())
+ dialog.addError(error);
+
+ foreach (const QString& warn, tableModifier->getWarnings())
+ dialog.addWarning(warn);
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ sqls = tableModifier->generateSqls();
+ }
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ modifyingThisTable = true;
+ structureExecutor->setDb(db);
+ structureExecutor->setQueries(sqls);
+ widgetCover->show();
+ structureExecutor->exec();
+}
+
+void TableWindow::updateAfterInit()
+{
+ updateStructureCommitState();
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+ updateNewTableState();
+ updateIndexesState();
+ updateTriggersState();
+}
+
+QModelIndex TableWindow::structureCurrentIndex() const
+{
+ return ui->structureView->selectionModel()->currentIndex();
+}
+
+void TableWindow::updateStructureToolbarState()
+{
+ QItemSelectionModel *selModel = ui->structureView->selectionModel();
+ bool validIdx = false;
+ bool isFirst = false;
+ bool isLast = false;
+ if (selModel)
+ {
+ QModelIndex currIdx = selModel->currentIndex();
+ if (currIdx.isValid())
+ {
+ validIdx = true;
+ if (currIdx.row() == 0)
+ isFirst = true;
+
+ if (currIdx.row() == (structureModel->rowCount() - 1))
+ isLast = true;
+ }
+ }
+
+ actionMap[EDIT_COLUMN]->setEnabled(validIdx);
+ actionMap[DEL_COLUMN]->setEnabled(validIdx);
+ actionMap[MOVE_COLUMN_UP]->setEnabled(validIdx && !isFirst);
+ actionMap[MOVE_COLUMN_DOWN]->setEnabled(validIdx && !isLast);
+}
+
+void TableWindow::updateStructureCommitState()
+{
+ bool modified = isModified();
+ actionMap[COMMIT_STRUCTURE]->setEnabled(modified);
+ actionMap[ROLLBACK_STRUCTURE]->setEnabled(modified && existingTable);
+}
+
+void TableWindow::updateTableConstraintsToolbarState()
+{
+ QItemSelectionModel *selModel = ui->tableConstraintsView->selectionModel();
+ bool anyColumn = structureModel && structureModel->rowCount() > 0;
+ bool validIdx = false;
+ bool isFirst = false;
+ bool isLast = false;
+ if (selModel)
+ {
+ QModelIndex currIdx = selModel->currentIndex();
+ if (currIdx.isValid())
+ {
+ validIdx = true;
+ if (currIdx.row() == 0)
+ isFirst = true;
+
+ if (currIdx.row() == (structureConstraintsModel->rowCount() - 1))
+ isLast = true;
+ }
+ }
+
+ actionMap[EDIT_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
+ actionMap[DEL_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
+ actionMap[MOVE_CONSTRAINT_UP]->setEnabled(anyColumn && validIdx && !isFirst);
+ actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(anyColumn && validIdx && !isLast);
+}
+
+void TableWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({
+ REFRESH_STRUCTURE,
+ REFRESH_INDEXES,
+ REFRESH_TRIGGERS,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ ADD_TABLE_CONSTRAINT,
+ EDIT_TABLE_CONSTRAINT,
+ DEL_TABLE_CONSTRAINT,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(TableWindow, Action);
+}
+
+void TableWindow::executionSuccessful()
+{
+ modifyingThisTable = false;
+ dataLoaded = true;
+}
+
+void TableWindow::executionFailed(const QString& errorText)
+{
+ modifyingThisTable = false;
+ notifyError(tr("Could not load data for table %1. Error details: %2").arg(table).arg(errorText));
+}
+
+void TableWindow::initDbAndTable()
+{
+ if (db->getVersion() == 2)
+ {
+ ui->withoutRowIdCheck->setVisible(false);
+ }
+
+ if (existingTable)
+ {
+ dataModel->setDb(db);
+ dataModel->setDatabaseAndTable(database, table);
+ }
+
+ ui->tableNameEdit->setText(table); // TODO no attached/temp db name support here
+
+ if (structureModel)
+ {
+ delete structureModel;
+ structureModel = nullptr;
+ }
+
+ if (structureConstraintsModel)
+ {
+ delete structureConstraintsModel;
+ structureConstraintsModel = nullptr;
+ }
+
+ if (constraintTabModel)
+ {
+ delete constraintTabModel;
+ constraintTabModel = nullptr;
+ }
+
+ structureModel = new TableStructureModel(this);
+ structureConstraintsModel = new TableConstraintsModel(this);
+ constraintTabModel = new ConstraintTabModel(this);
+
+ // Columns model signals
+ connect(structureModel, SIGNAL(columnModified(QString,SqliteCreateTable::Column*)),
+ structureConstraintsModel, SLOT(columnModified(QString,SqliteCreateTable::Column*)));
+ connect(structureModel, SIGNAL(columnDeleted(QString)),
+ structureConstraintsModel, SLOT(columnDeleted(QString)));
+ connect(structureModel, SIGNAL(columnsOrderChanged()), this, SLOT(updateStructureToolbarState()));
+
+ connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
+
+ ui->structureView->setModel(structureModel);
+ ui->structureView->verticalHeader()->setDefaultSectionSize(ui->structureView->fontMetrics().height() + 8);
+
+ // Constraints model signals
+ connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
+
+ connect(structureConstraintsModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
+ connect(structureConstraintsModel, SIGNAL(constraintOrderChanged()), this, SLOT(updateTableConstraintsToolbarState()));
+
+ ui->tableConstraintsView->setModel(structureConstraintsModel);
+ ui->tableConstraintsView->verticalHeader()->setDefaultSectionSize(ui->tableConstraintsView->fontMetrics().height() + 8);
+
+ // Constraint tab model signals
+ connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
+
+ ui->constraintsView->setModel(constraintTabModel);
+
+ connect(ui->withoutRowIdCheck, SIGNAL(clicked()), this, SLOT(withOutRowIdChanged()));
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+ parseDdl();
+ updateIndexes();
+ updateTriggers();
+
+ // (Re)connect to DB signals
+ connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));
+
+ // Selection model is recreated when setModel() is called on the view
+ connect(ui->structureView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(updateStructureToolbarState()));
+ connect(ui->tableConstraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(updateTableConstraintsToolbarState()));
+}
+
+void TableWindow::setupCoverWidget()
+{
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer();
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
+}
+
+void TableWindow::parseDdl()
+{
+ if (existingTable)
+ {
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(database, table, SchemaResolver::TABLE);
+ if (!parsedObject.dynamicCast<SqliteCreateTable>())
+ {
+ notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table));
+ invalid = true;
+ return;
+ }
+
+ createTable = parsedObject.dynamicCast<SqliteCreateTable>();
+ }
+ else
+ {
+ createTable = SqliteCreateTablePtr::create();
+ createTable->table = table;
+ createTable->dialect = db->getDialect();
+ }
+ originalCreateTable = SqliteCreateTablePtr::create(*createTable);
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ constraintTabModel->setCreateTable(createTable.data());
+ ui->withoutRowIdCheck->setChecked(!createTable->withOutRowId.isNull());
+ ui->tableConstraintsView->resizeColumnsToContents();
+ ui->structureView->resizeColumnsToContents();
+ ui->constraintsView->resizeColumnsToContents();
+
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+ updateDdlTab();
+}
+
+void TableWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QVariant TableWindow::saveSession()
+{
+ if (!db || DBLIST->isTemporary(db))
+ return QVariant();
+
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["table"] = table;
+ sessionValue["db"] = db->getName();
+ return sessionValue;
+}
+
+bool TableWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ {
+ notifyWarn("Could not restore window, because no database or table was stored in session for this window.");
+ return false;
+ }
+
+ if (!value.contains("db") || !value.contains("table"))
+ {
+ notifyWarn("Could not restore window, because no database or table was stored in session for this window.");
+ return false;
+ }
+
+ db = DBLIST->getByName(value["db"].toString());
+ if (!db || !db->isValid() || (!db->isOpen() && !db->open()))
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString()));
+ return false;
+ }
+
+ table = value["table"].toString();
+ database = value["database"].toString();
+ SchemaResolver resolver(db);
+ if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive))
+ {
+ notifyWarn(tr("Could not restore window, because the table %1 doesn't exist in the database %2.").arg(table).arg(db->getName()));
+ return false;
+ }
+
+ initDbAndTable();
+ applyInitialTab();
+ return true;
+}
+
+Icon* TableWindow::getIconNameForMdiWindow()
+{
+ return ICONS.TABLE;
+}
+
+QString TableWindow::getTitleForMdiWindow()
+{
+ QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
+ if (existingTable)
+ return table + dbSuffix;
+
+ QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
+ if (existingNames.contains(windowTitle()))
+ return windowTitle();
+
+ // Generate new name
+ QString title = tr("New table %1").arg(newTableWindowNum++);
+ while (existingNames.contains(title))
+ title = tr("New table %1").arg(newTableWindowNum++);
+
+ title += dbSuffix;
+ return title;
+}
+
+Db* TableWindow::getDb() const
+{
+ return db;
+}
+
+QString TableWindow::getTable() const
+{
+ return table;
+}
+
+void TableWindow::dbClosedFinalCleanup()
+{
+ db = nullptr;
+ dataModel->setDb(nullptr);
+ structureExecutor->setDb(nullptr);
+}
+
+void TableWindow::checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type)
+{
+ UNUSED(database);
+
+ // TODO uncomment below when dbnames are supported
+// if (this->database != database)
+// return;
+
+ switch (type)
+ {
+ case DbObjectType::TABLE:
+ break;
+ case DbObjectType::INDEX:
+ checkIfIndexDeleted(object);
+ return;
+ case DbObjectType::TRIGGER:
+ checkIfTriggerDeleted(object);
+ return;
+ case DbObjectType::VIEW:
+ return;
+ }
+
+ if (modifyingThisTable)
+ return;
+
+ if (object.compare(table, Qt::CaseInsensitive) == 0)
+ {
+ dbClosedFinalCleanup();
+ getMdiWindow()->close();
+ }
+}
+
+void TableWindow::checkIfIndexDeleted(const QString& object)
+{
+ for (int i = 0, total = ui->indexList->rowCount(); i < total; ++i)
+ {
+ if (ui->indexList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->indexList->removeRow(i);
+ return;
+ }
+ }
+}
+
+void TableWindow::checkIfTriggerDeleted(const QString& object)
+{
+ for (int i = 0, total = ui->triggerList->rowCount(); i < total; ++i)
+ {
+ if (ui->triggerList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->triggerList->removeRow(i);
+ return;
+ }
+ }
+}
+
+void TableWindow::refreshStructure()
+{
+ parseDdl();
+ updateIndexes();
+ updateTriggers();
+}
+
+void TableWindow::commitStructure(bool skipWarning)
+{
+ if (!isModified())
+ {
+ qWarning() << "Called TableWindow::commitStructure(), but isModified() returned false.";
+ updateStructureCommitState();
+ return;
+ }
+
+ if (!validate(skipWarning))
+ return;
+
+ executeStructureChanges();
+}
+
+void TableWindow::changesSuccessfullyCommited()
+{
+ QStringList sqls = structureExecutor->getQueries();
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ widgetCover->hide();
+
+ originalCreateTable = createTable;
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ dataLoaded = false;
+
+ QString oldTable = table;
+ database = createTable->database;
+ table = createTable->table;
+ existingTable = true;
+ initDbAndTable();
+ updateStructureCommitState();
+ updateNewTableState();
+ updateWindowTitle();
+
+ DBTREE->refreshSchema(db);
+
+ if (tableModifier)
+ {
+ QList<QStringList> modifiedObjects = {
+ tableModifier->getModifiedTables(),
+ tableModifier->getModifiedIndexes(),
+ tableModifier->getModifiedTriggers(),
+ tableModifier->getModifiedViews()
+ };
+ NotifyManager* notifyManager = NotifyManager::getInstance();
+ foreach (const QStringList& objList, modifiedObjects)
+ {
+ foreach (const QString& obj, objList)
+ {
+ if (obj.compare(oldTable, Qt::CaseInsensitive) == 0)
+ continue;
+
+ notifyManager->modified(db, database, obj);
+ }
+ }
+ }
+}
+
+void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText)
+{
+ qDebug() << "TableWindow::changesFailedToCommit:" << errorCode << errorText;
+
+ widgetCover->hide();
+ notifyError(tr("Could not commit table structure. Error message: %1", "table window").arg(errorText));
+}
+
+void TableWindow::rollbackStructure()
+{
+ createTable = SqliteCreateTablePtr::create(*originalCreateTable.data());
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ constraintTabModel->setCreateTable(createTable.data());
+ ui->tableNameEdit->setText(createTable->table);
+
+ updateStructureCommitState();
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+}
+
+void TableWindow::addColumn()
+{
+ SqliteCreateTable::Column column;
+ column.setParent(createTable.data());
+
+ ColumnDialog columnDialog(db, this);
+ columnDialog.setColumn(&column);
+ if (columnDialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
+ structureModel->appendColumn(modifiedColumn);
+ ui->structureView->resizeColumnToContents(0);
+
+ ui->structureView->setCurrentIndex(structureModel->index(structureModel->rowCount()-1, 0));
+}
+
+void TableWindow::editColumn()
+{
+ editColumn(structureCurrentIndex());
+}
+
+void TableWindow::delColumn()
+{
+ QModelIndex idx = structureCurrentIndex();
+ delColumn(idx);
+}
+
+void TableWindow::moveColumnUp()
+{
+ QModelIndex idx = structureCurrentIndex();
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::moveColumnUp() with invalid index.";
+ return;
+ }
+
+ structureModel->moveColumnUp(idx.row());
+}
+
+void TableWindow::moveColumnDown()
+{
+ QModelIndex idx = structureCurrentIndex();
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::moveColumnDown() with invalid index.";
+ return;
+ }
+
+ structureModel->moveColumnDown(idx.row());
+}
+
+
+void TableWindow::addConstraint(ConstraintDialog::Constraint mode)
+{
+ NewConstraintDialog dialog(mode, createTable.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteStatement* constrStmt = dialog.getConstraint();
+ SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(constrStmt);
+ if (!tableConstr)
+ {
+ qCritical() << "Constraint returned from ConstraintDialog was not of table type, while we're trying to add table constraint.";
+ return;
+ }
+
+ structureConstraintsModel->appendConstraint(tableConstr);
+ ui->tableConstraintsView->resizeColumnToContents(0);
+ ui->tableConstraintsView->resizeColumnToContents(1);
+}
+
+bool TableWindow::validate(bool skipWarning)
+{
+ if (!existingTable && !skipWarning && ui->tableNameEdit->text().isEmpty())
+ {
+ int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the table is allowed in SQLite, but it is not recommended.\n"
+ "Are you sure you want to create a table with blank name?"), QMessageBox::Yes, QMessageBox::No);
+
+ if (res != QMessageBox::Yes)
+ return false;
+ }
+
+ if (structureModel->rowCount() == 0)
+ {
+ notifyError(tr("Cannot create a table without at least one column."));
+ return false;
+ }
+
+ if (ui->withoutRowIdCheck->isChecked())
+ {
+ bool hasPk = false;
+ bool isPkAutoIncr = false;
+
+ if (createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY).size() > 0)
+ hasPk = true;
+
+ SqliteCreateTable::Column::Constraint* colConstraint = nullptr;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ {
+ colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
+ if (colConstraint)
+ {
+ hasPk = true;
+ if (colConstraint->autoincrKw)
+ isPkAutoIncr = true;
+ }
+ }
+
+ if (!hasPk)
+ {
+ notifyError(tr("Cannot create table without ROWID, if it has no PRIMARY KEY defined."
+ " Either uncheck the WITHOUT ROWID, or define a PRIMARY KEY."));
+ return false;
+ }
+
+ if (isPkAutoIncr)
+ {
+ notifyError(tr("Cannot use AUTOINCREMENT for PRIMARY KEY when WITHOUT ROWID clause is used."
+ " Either uncheck the WITHOUT ROWID, or the AUTOINCREMENT in a PRIMARY KEY."));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TableWindow::isModified() const
+{
+ return (structureModel && structureModel->isModified()) ||
+ (structureConstraintsModel && structureConstraintsModel->isModified()) ||
+ (originalCreateTable &&
+ (originalCreateTable->table != ui->tableNameEdit->text() ||
+ originalCreateTable->withOutRowId != createTable->withOutRowId)
+ ) ||
+ !existingTable;
+}
+
+TokenList TableWindow::indexColumnTokens(SqliteCreateIndexPtr index)
+{
+ if (index->indexedColumns.size() == 0)
+ return TokenList();
+
+ SqliteIndexedColumn* firstCol = index->indexedColumns.first();
+ SqliteIndexedColumn* lastCol = index->indexedColumns.last();
+ if (firstCol->tokens.size() == 0)
+ return TokenList();
+
+ if (lastCol->tokens.size() == 0)
+ return TokenList();
+
+ int firstIdx = index->tokens.indexOf(firstCol->tokens.first());
+ int lastIdx = index->tokens.indexOf(lastCol->tokens.last());
+
+ return index->tokens.mid(firstIdx, lastIdx-firstIdx+1);
+}
+
+QString TableWindow::getCurrentIndex() const
+{
+ int row = ui->indexList->currentRow();
+ QTableWidgetItem* item = ui->indexList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString TableWindow::getCurrentTrigger() const
+{
+ int row = ui->triggerList->currentRow();
+ QTableWidgetItem* item = ui->triggerList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+void TableWindow::applyInitialTab()
+{
+ if (existingTable && !table.isNull() && CFG_UI.General.OpenTablesOnData.get())
+ ui->tabWidget->setCurrentIndex(1);
+ else
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void TableWindow::updateDdlTab()
+{
+ CodeFormatter* formatter = SQLITESTUDIO->getCodeFormatter();
+ createTable->rebuildTokens();
+ ui->ddlEdit->setPlainText(formatter->format("sql", createTable->detokenize(), db));
+}
+
+void TableWindow::updateNewTableState()
+{
+ for (int i = 1; i < 5; i++)
+ ui->tabWidget->setTabEnabled(i, existingTable);
+
+ actionMap[EXPORT]->setEnabled(existingTable);
+ actionMap[IMPORT]->setEnabled(existingTable);
+ actionMap[POPULATE]->setEnabled(existingTable);
+ actionMap[CREATE_SIMILAR]->setEnabled(existingTable);
+ actionMap[REFRESH_STRUCTURE]->setEnabled(existingTable);
+}
+
+void TableWindow::addConstraint()
+{
+ addConstraint(ConstraintDialog::UNKNOWN);
+}
+
+void TableWindow::editConstraint()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ editConstraint(idx);
+}
+
+void TableWindow::delConstraint()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ delConstraint(idx);
+}
+
+void TableWindow::editConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
+ ConstraintDialog dialog(ConstraintDialog::EDIT, constr, createTable.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ structureConstraintsModel->constraintModified(idx.row());
+ ui->tableConstraintsView->resizeColumnToContents(0);
+ ui->tableConstraintsView->resizeColumnToContents(1);
+}
+
+void TableWindow::delConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
+
+ QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
+ QString msg = tr("Are you sure you want to delete table constraint '%1'?", "table window").arg(arg);
+ int btn = QMessageBox::question(this, tr("Delete constraint", "table window"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ structureConstraintsModel->delConstraint(idx.row());
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::moveConstraintUp()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ structureConstraintsModel->moveConstraintUp(idx.row());
+ updateTableConstraintsToolbarState();
+ updateStructureCommitState();
+}
+
+void TableWindow::moveConstraintDown()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ structureConstraintsModel->moveConstraintDown(idx.row());
+ updateTableConstraintsToolbarState();
+ updateStructureCommitState();
+}
+
+void TableWindow::addPk()
+{
+ addConstraint(ConstraintDialog::PK);
+}
+
+void TableWindow::addFk()
+{
+ addConstraint(ConstraintDialog::FK);
+}
+
+void TableWindow::addUnique()
+{
+ addConstraint(ConstraintDialog::UNIQUE);
+}
+
+void TableWindow::addCheck()
+{
+ addConstraint(ConstraintDialog::CHECK);
+}
+
+void TableWindow::exportTable()
+{
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setTableMode(db, table);
+ dialog.exec();
+}
+
+void TableWindow::importTable()
+{
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ if (dialog.exec() == QDialog::Accepted && dataLoaded)
+ ui->dataView->refreshData();
+}
+
+void TableWindow::populateTable()
+{
+ PopulateDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ if (dialog.exec() == QDialog::Accepted && dataLoaded)
+ ui->dataView->refreshData();
+}
+
+void TableWindow::createSimilarTable()
+{
+ DbObjectDialogs dialog(db);
+ dialog.addTableSimilarTo(QString(), table);
+}
+
+void TableWindow::tabChanged(int newTab)
+{
+ switch (newTab)
+ {
+ case 1:
+ {
+ if (isModified())
+ {
+ int res = QMessageBox::question(this, tr("Uncommited changes"),
+ tr("There are uncommited structure modifications. You cannot browse or edit data until you have "
+ "table structure settled.\n"
+ "Do you want to commit the structure, or do you want to go back to the structure tab?"),
+ tr("Go back to structure tab"), tr("Commit modifications and browse data."));
+
+ ui->tabWidget->setCurrentIndex(0);
+ if (res == 1)
+ commitStructure(true);
+
+ break;
+ }
+
+ if (!dataLoaded)
+ ui->dataView->refreshData();
+
+ break;
+ }
+ }
+}
+
+void TableWindow::on_structureView_doubleClicked(const QModelIndex &index)
+{
+ editColumn(index);
+}
+
+void TableWindow::on_tableConstraintsView_doubleClicked(const QModelIndex &index)
+{
+ editConstraint(index);
+}
+
+void TableWindow::nameChanged()
+{
+ if (!createTable)
+ return;
+
+ createTable->table = ui->tableNameEdit->text();
+ updateDdlTab();
+}
+
+void TableWindow::withOutRowIdChanged()
+{
+ if (!createTable)
+ return;
+
+ createTable->withOutRowId = ui->withoutRowIdCheck->isChecked() ? QStringLiteral("ROWID") : QString::null;
+ updateDdlTab();
+ emit modifyStatusChanged();
+}
+
+void TableWindow::addIndex()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addIndex(table);
+ updateIndexes();
+}
+
+void TableWindow::editIndex()
+{
+ QString index = getCurrentIndex();
+ if (index.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editIndex(index);
+ updateIndexes();
+}
+
+void TableWindow::delIndex()
+{
+ QString index = getCurrentIndex();
+ if (index.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(index);
+ updateIndexes();
+}
+
+void TableWindow::addTrigger()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addTriggerOnTable(table);
+ updateTriggers();
+}
+
+void TableWindow::editTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editTrigger(trigger);
+ updateTriggers();
+}
+
+void TableWindow::delTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(trigger);
+ updateTriggers();
+}
+
+void TableWindow::updateIndexesState()
+{
+ bool editDel = ui->indexList->currentItem() != nullptr;
+ actionMap[REFRESH_INDEXES]->setEnabled(existingTable);
+ actionMap[ADD_INDEX]->setEnabled(existingTable);
+ actionMap[EDIT_INDEX]->setEnabled(editDel);
+ actionMap[DEL_INDEX]->setEnabled(editDel);
+}
+
+void TableWindow::updateTriggersState()
+{
+ bool editDel = ui->triggerList->currentItem() != nullptr;
+ actionMap[REFRESH_TRIGGERS]->setEnabled(existingTable);
+ actionMap[ADD_TRIGGER]->setEnabled(existingTable);
+ actionMap[EDIT_TRIGGER]->setEnabled(editDel);
+ actionMap[DEL_TRIGGER]->setEnabled(editDel);
+}
+
+void TableWindow::nextTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx++;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void TableWindow::prevTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx--;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void TableWindow::updateIndexes()
+{
+ ui->indexList->clear();
+
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(true);
+ QList<SqliteCreateIndexPtr> indexes = resolver.getParsedIndexesForTable(database, table);
+
+ ui->indexList->setColumnCount(4);
+ ui->indexList->setRowCount(indexes.size());
+ ui->indexList->setHorizontalHeaderLabels({
+ tr("Name", "table window indexes"),
+ tr("Unique", "table window indexes"),
+ tr("Columns", "table window indexes"),
+ tr("Partial index condition", "table window indexes"),
+ });
+
+ Dialect dialect= db->getDialect();
+ if (dialect == Dialect::Sqlite2)
+ ui->indexList->setColumnCount(3);
+
+ ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
+
+ QTableWidgetItem* item = nullptr;
+ int row = 0;
+ foreach (SqliteCreateIndexPtr index, indexes)
+ {
+ item = new QTableWidgetItem(index->index);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 0, item);
+
+ // TODO a delegate to make the checkbox in the center, or use setCellWidget()
+ item = new QTableWidgetItem();
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ item->setCheckState(index->uniqueKw ? Qt::Checked : Qt::Unchecked);
+ ui->indexList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(indexColumnTokens(index).detokenize());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 2, item);
+
+ if (dialect == Dialect::Sqlite3)
+ {
+ item = new QTableWidgetItem(index->where ? index->where->detokenize() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 3, item);
+ }
+
+ row++;
+ }
+
+ ui->indexList->resizeColumnsToContents();
+ updateIndexesState();
+}
+
+void TableWindow::updateTriggers()
+{
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForTable(database, table);
+
+ ui->triggerList->setColumnCount(4);
+ ui->triggerList->setRowCount(triggers.size());
+ ui->triggerList->horizontalHeader()->setMaximumSectionSize(200);
+ ui->triggerList->setHorizontalHeaderLabels({
+ tr("Name", "table window triggers"),
+ tr("Event", "table window triggers"),
+ tr("Condition", "table window triggers"),
+ tr("Details", "table window triggers")
+ });
+
+ QTableWidgetItem* item = nullptr;
+ QString timeAndEvent;
+ int row = 0;
+ foreach (SqliteCreateTriggerPtr trig, triggers)
+ {
+ item = new QTableWidgetItem(trig->trigger);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 0, item);
+
+ timeAndEvent = trig->tokensMap["trigger_time"].detokenize() + trig->tokensMap["trigger_event"].detokenize();
+ item = new QTableWidgetItem(timeAndEvent);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 2, item);
+
+ item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 3, item);
+
+ row++;
+ }
+
+ ui->triggerList->resizeColumnsToContents();
+ updateTriggersState();
+}
+
+void TableWindow::editColumn(const QString& columnName)
+{
+ QModelIndex colIdx = structureModel->findColumn(columnName);
+ if (!colIdx.isValid())
+ return;
+
+ editColumn(colIdx);
+}
+
+void TableWindow::delColumn(const QString& columnName)
+{
+ QModelIndex colIdx = structureModel->findColumn(columnName);
+ if (!colIdx.isValid())
+ return;
+
+ delColumn(colIdx);
+}
+
+bool TableWindow::restoreSessionNextTime()
+{
+ return existingTable && db && !DBLIST->isTemporary(db);
+}
+
+QToolBar* TableWindow::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_STRUCTURE:
+ return ui->structureToolBar;
+ case TOOLBAR_INDEXES:
+ return ui->indexToolBar;
+ case TOOLBAR_TRIGGERS:
+ return ui->triggerToolBar;
+ }
+ return nullptr;
+}
+
+bool TableWindow::handleInitialFocus()
+{
+ if (!existingTable)
+ {
+ ui->tableNameEdit->setFocus();
+ return true;
+ }
+ return false;
+}
+
+bool TableWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited() || isModified();
+}
+
+QString TableWindow::getQuitUncommitedConfirmMessage() const
+{
+ QString title = getMdiWindow()->windowTitle();
+ if (ui->dataView->isUncommited() && isModified())
+ return tr("Table window \"%1\" has uncommited structure modifications and data.").arg(title);
+ else if (ui->dataView->isUncommited())
+ return tr("Table window \"%1\" has uncommited data.").arg(title);
+ else if (isModified())
+ return tr("Table window \"%1\" has uncommited structure modifications.").arg(title);
+ else
+ {
+ qCritical() << "Unhandled message case in TableWindow::getQuitUncommitedConfirmMessage().";
+ return QString();
+ }
+}
+
+void TableWindow::useCurrentTableAsBaseForNew()
+{
+ newTable();
+ ui->tableNameEdit->clear();
+ updateWindowTitle();
+ ui->tableNameEdit->setFocus();
+ updateAfterInit();
+}
+
+Db* TableWindow::getAssociatedDb() const
+{
+ return db;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h
new file mode 100644
index 0000000..b0ee1e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h
@@ -0,0 +1,241 @@
+#ifndef TABLEWINDOW_H
+#define TABLEWINDOW_H
+
+#include "db/db.h"
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include "dialogs/constraintdialog.h"
+#include "db/chainexecutor.h"
+#include "guiSQLiteStudio_global.h"
+#include <QPointer>
+
+class TableModifier;
+class SqlTableModel;
+class ExtLineEdit;
+class IntValidator;
+class QLabel;
+class TableStructureModel;
+class TableConstraintsModel;
+class QProgressBar;
+class WidgetCover;
+class SqliteSyntaxHighlighter;
+class ConstraintTabModel;
+
+namespace Ui {
+ class TableWindow;
+}
+
+CFG_KEY_LIST(TableWindow, QObject::tr("Table window"),
+ CFG_KEY_ENTRY(REFRESH_STRUCTURE, Qt::Key_F5, QObject::tr("Refresh table structure"))
+ CFG_KEY_ENTRY(ADD_COLUMN, Qt::Key_Insert, QObject::tr("Add new column"))
+ CFG_KEY_ENTRY(EDIT_COLUMN, Qt::Key_Return, QObject::tr("Edit selected column"))
+ CFG_KEY_ENTRY(DEL_COLUMN, Qt::Key_Delete, QObject::tr("Delete selected column"))
+ CFG_KEY_ENTRY(EXPORT, Qt::CTRL + Qt::Key_E, QObject::tr("Export table data"))
+ CFG_KEY_ENTRY(IMPORT, Qt::CTRL + Qt::Key_I, QObject::tr("Import data to the table"))
+ CFG_KEY_ENTRY(ADD_TABLE_CONSTRAINT, Qt::Key_Insert, QObject::tr("Add new table constraint"))
+ CFG_KEY_ENTRY(EDIT_TABLE_CONSTRAINT, Qt::Key_Return, QObject::tr("Edit selected table constraint"))
+ CFG_KEY_ENTRY(DEL_TABLE_CONSTRAINT, Qt::Key_Delete, QObject::tr("Delete selected table constraint"))
+ CFG_KEY_ENTRY(REFRESH_INDEXES, Qt::Key_F5, QObject::tr("Refresh table index list"))
+ CFG_KEY_ENTRY(ADD_INDEX, Qt::Key_Insert, QObject::tr("Add new index"))
+ CFG_KEY_ENTRY(EDIT_INDEX, Qt::Key_Return, QObject::tr("Edit selected index"))
+ CFG_KEY_ENTRY(DEL_INDEX, Qt::Key_Delete, QObject::tr("Delete selected index"))
+ CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh table trigger list"))
+ CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger"))
+ CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger"))
+ CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger"))
+ CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab"))
+ CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab"))
+)
+
+class GUI_API_EXPORT TableWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ // Structure tab
+ REFRESH_STRUCTURE,
+ COMMIT_STRUCTURE,
+ ROLLBACK_STRUCTURE,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ MOVE_COLUMN_UP,
+ MOVE_COLUMN_DOWN,
+ ADD_TABLE_CONSTRAINT,
+ EDIT_TABLE_CONSTRAINT,
+ DEL_TABLE_CONSTRAINT,
+ ADD_TABLE_PK,
+ ADD_TABLE_FK,
+ ADD_TABLE_UNIQUE,
+ ADD_TABLE_CHECK,
+ MOVE_CONSTRAINT_UP,
+ MOVE_CONSTRAINT_DOWN,
+ EXPORT,
+ IMPORT,
+ POPULATE,
+ CREATE_SIMILAR,
+ // Indexes tab
+ REFRESH_INDEXES,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ // Triggers tab
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ // All tabs
+ NEXT_TAB,
+ PREV_TAB
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_STRUCTURE,
+ TOOLBAR_INDEXES,
+ TOOLBAR_TRIGGERS
+ };
+
+ explicit TableWindow(QWidget *parent = 0);
+ TableWindow(Db* db, QWidget *parent = 0);
+ TableWindow(const TableWindow& win);
+ TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table);
+ ~TableWindow();
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE);
+
+ QString getTable() const;
+ Db* getDb() const;
+ bool handleInitialFocus();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+ void useCurrentTableAsBaseForNew();
+ Db* getAssociatedDb() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ bool restoreSessionNextTime();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ void newTable();
+ void parseDdl();
+ void initDbAndTable();
+ void setupCoverWidget();
+ void createStructureActions();
+ void createDataGridActions();
+ void createDataFormActions();
+ void createIndexActions();
+ void createTriggerActions();
+ void editColumn(const QModelIndex& idx);
+ void delColumn(const QModelIndex& idx);
+ void editConstraint(const QModelIndex& idx);
+ void delConstraint(const QModelIndex& idx);
+ void executeStructureChanges();
+ void updateAfterInit();
+ QModelIndex structureCurrentIndex() const;
+ void addConstraint(ConstraintDialog::Constraint mode);
+ bool validate(bool skipWarning = false);
+ bool isModified() const;
+ TokenList indexColumnTokens(SqliteCreateIndexPtr index);
+ QString getCurrentIndex() const;
+ QString getCurrentTrigger() const;
+ void applyInitialTab();
+
+ int newTableWindowNum = 1;
+
+ Db* db = nullptr;
+ QString database;
+ QString table;
+ Ui::TableWindow *ui = nullptr;
+ SqlTableModel* dataModel = nullptr;
+ bool dataLoaded = false;
+ bool existingTable = true;
+ SqliteCreateTablePtr createTable;
+ SqliteCreateTablePtr originalCreateTable;
+ TableStructureModel* structureModel = nullptr;
+ TableConstraintsModel* structureConstraintsModel = nullptr;
+ ConstraintTabModel* constraintTabModel = nullptr;
+ WidgetCover* widgetCover = nullptr;
+ ChainExecutor* structureExecutor = nullptr;
+ TableModifier* tableModifier = nullptr;
+ bool modifyingThisTable = false;
+
+ private slots:
+ void executionSuccessful();
+ void executionFailed(const QString& errorText);
+ void dbClosedFinalCleanup();
+ void checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type);
+ void checkIfIndexDeleted(const QString& object);
+ void checkIfTriggerDeleted(const QString& object);
+ void refreshStructure();
+ void commitStructure(bool skipWarning = false);
+ void changesSuccessfullyCommited();
+ void changesFailedToCommit(int errorCode, const QString& errorText);
+ void rollbackStructure();
+ void editColumn();
+ void delColumn();
+ void moveColumnUp();
+ void moveColumnDown();
+ void addConstraint();
+ void editConstraint();
+ void delConstraint();
+ void moveConstraintUp();
+ void moveConstraintDown();
+ void addPk();
+ void addFk();
+ void addUnique();
+ void addCheck();
+ void exportTable();
+ void importTable();
+ void populateTable();
+ void createSimilarTable();
+ void tabChanged(int newTab);
+ void updateStructureToolbarState();
+ void updateStructureCommitState();
+ void updateTableConstraintsToolbarState();
+ void updateDdlTab();
+ void updateNewTableState();
+ void on_structureView_doubleClicked(const QModelIndex &index);
+ void on_tableConstraintsView_doubleClicked(const QModelIndex &index);
+ void nameChanged();
+ void withOutRowIdChanged();
+ void addIndex();
+ void editIndex();
+ void delIndex();
+ void addTrigger();
+ void editTrigger();
+ void delTrigger();
+ void updateIndexesState();
+ void updateTriggersState();
+ void nextTab();
+ void prevTab();
+
+ public slots:
+ void updateIndexes();
+ void updateTriggers();
+ void addColumn();
+ void editColumn(const QString& columnName);
+ void delColumn(const QString& columnName);
+
+ signals:
+ void modifyStatusChanged();
+};
+
+#endif // TABLEWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui
new file mode 100644
index 0000000..8c46443
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui
@@ -0,0 +1,307 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TableWindow</class>
+ <widget class="QWidget" name="TableWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>609</width>
+ <height>415</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="structureTab">
+ <attribute name="title">
+ <string>Structure</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="structureTabLayout">
+ <item>
+ <widget class="QToolBar" name="structureToolBar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="structureTopBar" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="tableNameLabel">
+ <property name="text">
+ <string>Table name:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="tableNameEdit">
+ <property name="maximumSize">
+ <size>
+ <width>200</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="withoutRowIdCheck">
+ <property name="text">
+ <string>WITHOUT ROWID</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSplitter" name="structureSplitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
+ <widget class="QTableView" name="structureView">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropOverwriteMode">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="constraintsWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="tableConstraintsToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableView" name="tableConstraintsView">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropOverwriteMode">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataTab">
+ <attribute name="title">
+ <string>Data</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="DataView" name="dataView">
+ <property name="tabPosition">
+ <enum>QTabWidget::South</enum>
+ </property>
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="constraintsTab">
+ <attribute name="title">
+ <string>Constraints</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QTableView" name="constraintsView">
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="indexesTab">
+ <attribute name="title">
+ <string>Indexes</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QToolBar" name="indexToolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="indexList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="triggersTab">
+ <attribute name="title">
+ <string>Triggers</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QToolBar" name="triggerToolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="triggerList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddlTab">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
new file mode 100644
index 0000000..d54a359
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
@@ -0,0 +1,760 @@
+#include "viewwindow.h"
+#include "ui_viewwindow.h"
+#include "common/unused.h"
+#include "schemaresolver.h"
+#include "services/notifymanager.h"
+#include "services/dbmanager.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "datagrid/sqlquerymodel.h"
+#include "common/utils_sql.h"
+#include "viewmodifier.h"
+#include "common/widgetcover.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "dialogs/messagelistdialog.h"
+#include "dbobjectdialogs.h"
+#include "dialogs/ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include <QPushButton>
+#include <QProgressBar>
+#include <QDebug>
+#include <QMessageBox>
+
+CFG_KEYS_DEFINE(ViewWindow)
+
+ViewWindow::ViewWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(Db* db, QWidget* parent) :
+ MdiChild(parent),
+ db(db),
+ ui(new Ui::ViewWindow)
+{
+ newView();
+ init();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(const ViewWindow& win) :
+ MdiChild(win.parentWidget()),
+ db(win.db),
+ database(win.database),
+ view(win.view),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ initView();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(QWidget* parent, Db* db, const QString& database, const QString& view) :
+ MdiChild(parent),
+ db(db),
+ database(database),
+ view(view),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ initView();
+ applyInitialTab();
+}
+
+ViewWindow::~ViewWindow()
+{
+ delete ui;
+}
+
+void ViewWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QVariant ViewWindow::saveSession()
+{
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["view"] = view;
+ sessionValue["db"] = db->getName();
+ return sessionValue;
+}
+
+bool ViewWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ {
+ notifyWarn("Could not restore window, because no database or view was stored in session for this window.");
+ return false;
+ }
+
+ if (!value.contains("db") || !value.contains("view"))
+ {
+ notifyWarn("Could not restore window, because no database or view was stored in session for this window.");
+ return false;
+ }
+
+ db = DBLIST->getByName(value["db"].toString());
+ if (!db)
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString()));
+ return false;
+ }
+
+ if (!db->isOpen() && !db->open())
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be open.").arg(value["db"].toString()));
+ return false;
+ }
+
+ view = value["view"].toString();
+ database = value["database"].toString();
+ SchemaResolver resolver(db);
+ if (!resolver.getViews(database).contains(view, Qt::CaseInsensitive))
+ {
+ notifyWarn(tr("Could not restore window, because the view %1 doesn't exist in the database %2.").arg(view).arg(db->getName()));
+ return false;
+ }
+
+ initView();
+ applyInitialTab();
+ return true;
+}
+
+Icon* ViewWindow::getIconNameForMdiWindow()
+{
+ return ICONS.VIEW;
+}
+
+QString ViewWindow::getTitleForMdiWindow()
+{
+ QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
+ if (existingView)
+ return view + dbSuffix;
+
+ QStringList existingNames = MDIAREA->getWindowTitles();
+ if (existingNames.contains(windowTitle()))
+ return windowTitle();
+
+ // Generate new name
+ QString title = tr("New view %1").arg(newViewWindowNum++);
+ while (existingNames.contains(title))
+ title = tr("New view %1").arg(newViewWindowNum++);
+
+ title += dbSuffix;
+ return title;
+}
+
+void ViewWindow::createActions()
+{
+ createQueryTabActions();
+ createTriggersTabActions();
+
+ createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
+ createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
+}
+
+void ViewWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(ViewWindow, Action);
+}
+
+bool ViewWindow::restoreSessionNextTime()
+{
+ return existingView;
+}
+
+QToolBar* ViewWindow::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_QUERY:
+ return ui->queryToolbar;
+ case TOOLBAR_TRIGGERS:
+ return ui->triggersToolbar;
+ }
+ return nullptr;
+}
+
+void ViewWindow::init()
+{
+ ui->setupUi(this);
+
+ dataModel = new SqlQueryModel(this);
+ ui->dataView->init(dataModel);
+
+ ui->queryEdit->setVirtualSqlExpression("CREATE VIEW name AS %1");
+ ui->queryEdit->setDb(db);
+
+ connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->queryEdit, SIGNAL(textChanged()), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->triggersList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
+
+ structureExecutor = new ChainExecutor(this);
+ connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited()));
+ connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
+
+ setupCoverWidget();
+
+ initActions();
+
+ refreshTriggers();
+ updateQueryToolbarStatus();
+ updateTriggersState();
+}
+
+void ViewWindow::newView()
+{
+ existingView = false;
+ view = "";
+}
+
+void ViewWindow::initView()
+{
+ ui->nameEdit->setText(view);
+
+ parseDdl();
+
+ if (!createView)
+ return; // error occured while parsing ddl, window will be closed
+
+ if (existingView)
+ {
+ dataModel->setDb(db);
+ dataModel->setQuery(originalCreateView->select->detokenize());
+ }
+
+ ui->queryEdit->setDb(db);
+ ui->queryEdit->setPlainText(createView->select->detokenize());
+ updateDdlTab();
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+
+ refreshTriggers();
+
+ connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType)));
+}
+
+void ViewWindow::setupCoverWidget()
+{
+ widgetCover = new WidgetCover(this);
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
+}
+
+void ViewWindow::createQueryTabActions()
+{
+ createAction(REFRESH_QUERY, ICONS.RELOAD, tr("Refresh the view", "view window"), this, SLOT(refreshView()), ui->queryToolbar);
+ ui->queryToolbar->addSeparator();
+ createAction(COMMIT_QUERY, ICONS.COMMIT, tr("Commit the view changes", "view window"), this, SLOT(commitView()), ui->queryToolbar);
+ createAction(ROLLBACK_QUERY, ICONS.ROLLBACK, tr("Rollback the view changes", "view window"), this, SLOT(rollbackView()), ui->queryToolbar);
+ ui->queryToolbar->addSeparator();
+ ui->queryToolbar->addAction(ui->queryEdit->getAction(SqlEditor::FORMAT_SQL));
+}
+
+void ViewWindow::createTriggersTabActions()
+{
+ createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "view window"), this, SLOT(refreshTriggers()), ui->triggersToolbar, ui->triggersList);
+ ui->triggersToolbar->addSeparator();
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create new triger", "view window"), this, SLOT(addTrigger()), ui->triggersToolbar, ui->triggersList);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit selected triger", "view window"), this, SLOT(editTrigger()), ui->triggersToolbar, ui->triggersList);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete selected triger", "view window"), this, SLOT(deleteTrigger()), ui->triggersToolbar, ui->triggersList);
+}
+QString ViewWindow::getView() const
+{
+ return view;
+}
+
+void ViewWindow::setSelect(const QString &selectSql)
+{
+ ui->queryEdit->setPlainText(selectSql);
+}
+
+bool ViewWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited() || isModified();
+}
+
+QString ViewWindow::getQuitUncommitedConfirmMessage() const
+{
+ QString title = getMdiWindow()->windowTitle();
+ if (ui->dataView->isUncommited() && isModified())
+ return tr("View window \"%1\" has uncommited structure modifications and data.").arg(title);
+ else if (ui->dataView->isUncommited())
+ return tr("View window \"%1\" has uncommited data.").arg(title);
+ else if (isModified())
+ return tr("View window \"%1\" has uncommited structure modifications.").arg(title);
+ else
+ {
+ qCritical() << "Unhandled message case in ViewWindow::getQuitUncommitedConfirmMessage().";
+ return QString();
+ }
+}
+
+Db* ViewWindow::getAssociatedDb() const
+{
+ return db;
+}
+
+void ViewWindow::staticInit()
+{
+ qRegisterMetaType<ViewWindow>("ViewWindow");
+}
+
+void ViewWindow::insertAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<ViewWindow>(action, toolbar);
+}
+
+void ViewWindow::insertActionBefore(ExtActionPrototype* action, ViewWindow::Action beforeAction, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<ViewWindow>(action, beforeAction, toolbar);
+}
+
+void ViewWindow::insertActionAfter(ExtActionPrototype* action, ViewWindow::Action afterAction, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<ViewWindow>(action, afterAction, toolbar);
+}
+
+void ViewWindow::removeAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<ViewWindow>(action, toolbar);
+}
+
+QString ViewWindow::getDatabase() const
+{
+ return database;
+}
+
+Db* ViewWindow::getDb() const
+{
+ return db;
+}
+
+void ViewWindow::refreshView()
+{
+ initView();
+ updateTriggersState();
+}
+
+void ViewWindow::commitView(bool skipWarnings)
+{
+ if (!isModified())
+ {
+ qWarning() << "Called ViewWindow::commitView(), but isModified() returned false.";
+ updateQueryToolbarStatus();
+ return;
+ }
+
+ if (!validate(skipWarnings))
+ return;
+
+ executeStructureChanges();
+}
+
+void ViewWindow::rollbackView()
+{
+ createView = SqliteCreateViewPtr::create(*originalCreateView.data());
+ ui->nameEdit->setText(createView->view);
+ ui->queryEdit->setPlainText(createView->select->detokenize());
+
+ updateQueryToolbarStatus();
+ updateDdlTab();
+}
+
+QString ViewWindow::getCurrentTrigger() const
+{
+ int row = ui->triggersList->currentRow();
+ QTableWidgetItem* item = ui->triggersList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+void ViewWindow::applyInitialTab()
+{
+ if (existingView && !view.isNull() && CFG_UI.General.OpenViewsOnData.get())
+ ui->tabWidget->setCurrentIndex(1);
+ else
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void ViewWindow::addTrigger()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addTriggerOnView(view);
+ refreshTriggers();
+}
+
+void ViewWindow::editTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editTrigger(trigger);
+ refreshTriggers();
+}
+
+void ViewWindow::deleteTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(trigger);
+ refreshTriggers();
+}
+
+void ViewWindow::executionSuccessful()
+{
+ modifyingThisView = false;
+ dataLoaded = true;
+}
+
+void ViewWindow::executionFailed(const QString& errorMessage)
+{
+ modifyingThisView = false;
+ notifyError(tr("Could not load data for view %1. Error details: %2").arg(view).arg(errorMessage));
+}
+
+void ViewWindow::tabChanged(int tabIdx)
+{
+ switch (tabIdx)
+ {
+ case 1:
+ {
+ if (isModified())
+ {
+ int res = QMessageBox::question(this, tr("Uncommited changes"),
+ tr("There are uncommited structure modifications. You cannot browse or edit data until you have "
+ "the view structure settled.\n"
+ "Do you want to commit the structure, or do you want to go back to the structure tab?"),
+ tr("Go back to structure tab"), tr("Commit modifications and browse data."));
+
+ ui->tabWidget->setCurrentIndex(0);
+ if (res == 1)
+ commitView(true);
+
+ break;
+ }
+
+ if (!dataLoaded)
+ ui->dataView->refreshData();
+
+ break;
+ }
+ case 3:
+ {
+ updateDdlTab();
+ break;
+ }
+ }
+}
+
+void ViewWindow::updateQueryToolbarStatus()
+{
+ bool modified = isModified();
+ bool queryOk = ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors();
+ actionMap[COMMIT_QUERY]->setEnabled(modified && queryOk);
+ actionMap[ROLLBACK_QUERY]->setEnabled(modified && existingView);
+ actionMap[REFRESH_QUERY]->setEnabled(existingView);
+}
+
+void ViewWindow::changesSuccessfullyCommited()
+{
+ QStringList sqls = structureExecutor->getQueries();
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ widgetCover->hide();
+
+ originalCreateView = createView;
+ dataLoaded = false;
+
+ //QString oldView = view; // uncomment when implementing notify manager call
+ database = createView->database;
+ view = createView->view;
+ existingView = true;
+ initView();
+ updateQueryToolbarStatus();
+ updateWindowTitle();
+
+ DBTREE->refreshSchema(db);
+}
+
+void ViewWindow::changesFailedToCommit(int errorCode, const QString& errorText)
+{
+ qDebug() << "ViewWindow::changesFailedToCommit:" << errorCode << errorText;
+
+ widgetCover->hide();
+
+ NotifyManager::getInstance()->error(tr("Could not commit view changes. Error message: %1", "view window").arg(errorText));
+}
+
+void ViewWindow::updateTriggersState()
+{
+ bool editDel = ui->triggersList->currentItem() != nullptr;
+ actionMap[REFRESH_TRIGGERS]->setEnabled(existingView);
+ actionMap[ADD_TRIGGER]->setEnabled(existingView);
+ actionMap[EDIT_TRIGGER]->setEnabled(editDel);
+ actionMap[DEL_TRIGGER]->setEnabled(editDel);
+}
+
+void ViewWindow::nextTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx++;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void ViewWindow::prevTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx--;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void ViewWindow::dbClosedFinalCleanup()
+{
+ dataModel->setDb(nullptr);
+ ui->queryEdit->setDb(nullptr);
+ structureExecutor->setDb(nullptr);
+}
+
+void ViewWindow::checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type)
+{
+ UNUSED(database);
+
+ if (type == DbObjectType::TRIGGER)
+ {
+ for (int i = 0, total = ui->triggersList->rowCount(); i < total; ++i)
+ {
+ if (ui->triggersList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->triggersList->removeRow(i);
+ return;
+ }
+ }
+ }
+
+ if (type != DbObjectType::VIEW)
+ return;
+
+ if (modifyingThisView)
+ return;
+
+ // TODO uncomment below when dbnames are supported
+// if (this->database != database)
+// return;
+
+ if (object.compare(view, Qt::CaseInsensitive) == 0)
+ {
+ dbClosedFinalCleanup();
+ getMdiWindow()->close();
+ }
+}
+
+void ViewWindow::refreshTriggers()
+{
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForView(database, view);
+
+ ui->triggersList->setColumnCount(4);
+ ui->triggersList->setRowCount(triggers.size());
+ ui->triggersList->horizontalHeader()->setMaximumSectionSize(200);
+ ui->triggersList->setHorizontalHeaderLabels({
+ tr("Name", "view window triggers"),
+ tr("Instead of", "view window triggers"),
+ tr("Condition", "view window triggers"),
+ tr("Details", "table window triggers")
+ });
+
+ QTableWidgetItem* item = nullptr;
+ QString event;
+ int row = 0;
+ foreach (SqliteCreateTriggerPtr trig, triggers)
+ {
+ item = new QTableWidgetItem(trig->trigger);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 0, item);
+
+ event = trig->tokensMap["trigger_event"].detokenize();
+ item = new QTableWidgetItem(event);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 2, item);
+
+ item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 3, item);
+
+ row++;
+ }
+
+ ui->triggersList->resizeColumnsToContents();
+ updateTriggersState();
+}
+
+void ViewWindow::parseDdl()
+{
+ if (existingView)
+ {
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(database, view, SchemaResolver::VIEW);
+ if (!parsedObject.dynamicCast<SqliteCreateView>())
+ {
+ notifyError(tr("Could not process the %1 view correctly. Unable to open a view window.").arg(view));
+ invalid = true;
+ return;
+ }
+
+ createView = parsedObject.dynamicCast<SqliteCreateView>();
+ }
+ else
+ {
+ createView = SqliteCreateViewPtr::create();
+ createView->view = view;
+ createView->dialect = db->getDialect();
+ }
+ originalCreateView = SqliteCreateViewPtr::create(*createView);
+ originalQuery = originalCreateView->select->detokenize();
+}
+
+void ViewWindow::updateDdlTab()
+{
+ QString ddl = "CREATE VIEW %1 AS %2";
+ ui->ddlEdit->setPlainText(ddl.arg(wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect())).arg(ui->queryEdit->toPlainText()));
+}
+
+bool ViewWindow::isModified() const
+{
+ return (originalCreateView && originalCreateView->view != ui->nameEdit->text()) ||
+ ui->queryEdit->toPlainText() != originalQuery ||
+ !existingView;
+}
+
+bool ViewWindow::validate(bool skipWarnings)
+{
+ if (!existingView && !skipWarnings && ui->nameEdit->text().isEmpty())
+ {
+ int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the view is allowed in SQLite, but it is not recommended.\n"
+ "Are you sure you want to create a view with blank name?"), QMessageBox::Yes, QMessageBox::No);
+
+ if (res != QMessageBox::Yes)
+ return false;
+ }
+
+ // Rebuilding createView statement and validating it on the fly.
+ QString ddl = "CREATE VIEW %1 AS %2";
+ QString viewName = wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect());
+ QString select = ui->queryEdit->toPlainText();
+
+ Parser parser(db->getDialect());
+ if (!parser.parse(ddl.arg(viewName).arg(select)) || parser.getQueries().size() < 1)
+ {
+ notifyError(tr("The SELECT statement could not be parsed. Please correct the query and retry.\nDetails: %1").arg(parser.getErrorString()));
+ return false;
+ }
+
+ SqliteQueryPtr query = parser.getQueries().first();
+ SqliteCreateViewPtr viewStmt = query.dynamicCast<SqliteCreateView>();
+ if (!viewStmt)
+ {
+ notifyError(tr("The view could not be modified due to internal SQLiteStudio error. Please report this!"));
+ qCritical() << "Could not parse new view, because parsed object is of different type. The type is"
+ << sqliteQueryTypeToString(query->queryType) << "for following query:" << ddl;
+ return false;
+ }
+
+ createView = viewStmt;
+ return true;
+}
+
+void ViewWindow::executeStructureChanges()
+{
+ QStringList sqls;
+ QList<bool> sqlMandatoryFlags;
+
+ createView->rebuildTokens();
+ if (!existingView)
+ {
+ sqls << createView->detokenize();
+ }
+ else
+ {
+ if (viewModifier)
+ delete viewModifier;
+
+ viewModifier = new ViewModifier(db, database, view);
+ viewModifier->alterView(createView);
+
+ if (viewModifier->hasMessages())
+ {
+ MessageListDialog dialog(tr("Following problems will take place while modifying the view.\n"
+ "Would you like to proceed?", "view window"));
+ dialog.setWindowTitle(tr("View modification", "view window"));
+ foreach (const QString& error, viewModifier->getErrors())
+ dialog.addError(error);
+
+ foreach (const QString& warn, viewModifier->getWarnings())
+ dialog.addWarning(warn);
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ sqls = viewModifier->generateSqls();
+ sqlMandatoryFlags = viewModifier->getMandatoryFlags();
+ }
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ modifyingThisView = true;
+ structureExecutor->setDb(db);
+ structureExecutor->setQueries(sqls);
+ structureExecutor->setMandatoryQueries(sqlMandatoryFlags);
+ structureExecutor->exec();
+ widgetCover->show();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
new file mode 100644
index 0000000..6b50135
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
@@ -0,0 +1,146 @@
+#ifndef VIEWWINDOW_H
+#define VIEWWINDOW_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "db/db.h"
+#include "parser/ast/sqlitecreateview.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class ViewWindow;
+}
+
+class SqliteSyntaxHighlighter;
+class SqlQueryModel;
+class WidgetCover;
+class QPushButton;
+class QProgressBar;
+class ChainExecutor;
+class ViewModifier;
+
+CFG_KEY_LIST(ViewWindow, QObject::tr("A view window"),
+ CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh view trigger list"))
+ CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger"))
+ CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger"))
+ CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger"))
+ CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab"))
+ CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab"))
+)
+
+class GUI_API_EXPORT ViewWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ // Structure tab
+ REFRESH_QUERY,
+ COMMIT_QUERY,
+ ROLLBACK_QUERY,
+ // Triggers tab
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ // All tabs
+ NEXT_TAB,
+ PREV_TAB
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_QUERY,
+ TOOLBAR_TRIGGERS
+ };
+
+ explicit ViewWindow(QWidget *parent = 0);
+ ViewWindow(Db* db, QWidget *parent = 0);
+ ViewWindow(const ViewWindow& win);
+ ViewWindow(QWidget *parent, Db* db, const QString& database, const QString& view);
+ ~ViewWindow();
+
+ Db* getDb() const;
+ QString getDatabase() const;
+ QString getView() const;
+ void setSelect(const QString& selectSql);
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+ Db* getAssociatedDb() const;
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_QUERY);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_QUERY);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY);
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ bool restoreSessionNextTime();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ void newView();
+ void initView();
+ void setupCoverWidget();
+ void createQueryTabActions();
+ void createTriggersTabActions();
+ void parseDdl();
+ void updateDdlTab();
+ bool isModified() const;
+ bool validate(bool skipWarnings = false);
+ void executeStructureChanges();
+ QString getCurrentTrigger() const;
+ void applyInitialTab();
+
+ Db* db = nullptr;
+ QString database;
+ QString view;
+ bool existingView = true;
+ bool dataLoaded = false;
+ int newViewWindowNum = 1;
+ bool modified = false;
+ SqliteCreateViewPtr originalCreateView;
+ SqliteCreateViewPtr createView;
+ SqlQueryModel* dataModel = nullptr;
+ QString originalQuery;
+ WidgetCover* widgetCover = nullptr;
+ ChainExecutor* structureExecutor = nullptr;
+ ViewModifier* viewModifier = nullptr;
+ Ui::ViewWindow *ui = nullptr;
+ bool modifyingThisView = false;
+
+ private slots:
+ void refreshView();
+ void commitView(bool skipWarnings = false);
+ void rollbackView();
+ void addTrigger();
+ void editTrigger();
+ void deleteTrigger();
+ void executionSuccessful();
+ void executionFailed(const QString& errorMessage);
+ void tabChanged(int tabIdx);
+ void updateQueryToolbarStatus();
+ void changesSuccessfullyCommited();
+ void changesFailedToCommit(int errorCode, const QString& errorText);
+ void updateTriggersState();
+ void nextTab();
+ void prevTab();
+ void dbClosedFinalCleanup();
+ void checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type);
+
+ public slots:
+ void refreshTriggers();
+};
+
+#endif // VIEWWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
new file mode 100644
index 0000000..734a265
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ViewWindow</class>
+ <widget class="QWidget" name="ViewWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>621</width>
+ <height>468</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="queryTab">
+ <attribute name="title">
+ <string>Query</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QToolBar" name="queryToolbar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="nameWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>View name:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="SqlEditor" name="queryEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataTab">
+ <attribute name="title">
+ <string>Data</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="DataView" name="dataView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="triggersTab">
+ <attribute name="title">
+ <string>Triggers</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QToolBar" name="triggersToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="triggersList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddl">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>