From 7167ce41b61d2ba2cdb526777a4233eb84a3b66a Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Sat, 6 Dec 2014 17:33:25 -0500 Subject: Imported Upstream version 2.99.6 --- SQLiteStudio3/guiSQLiteStudio/actionentry.cpp | 42 + SQLiteStudio3/guiSQLiteStudio/actionentry.h | 30 + .../guiSQLiteStudio/common/colorbutton.cpp | 39 + SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h | 30 + .../guiSQLiteStudio/common/configcombobox.cpp | 16 + .../guiSQLiteStudio/common/configcombobox.h | 34 + .../guiSQLiteStudio/common/configradiobutton.cpp | 36 + .../guiSQLiteStudio/common/configradiobutton.h | 46 + .../guiSQLiteStudio/common/datawidgetmapper.cpp | 138 ++ .../guiSQLiteStudio/common/datawidgetmapper.h | 54 + SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp | 30 + SQLiteStudio3/guiSQLiteStudio/common/extaction.h | 20 + .../guiSQLiteStudio/common/extactioncontainer.cpp | 260 +++ .../guiSQLiteStudio/common/extactioncontainer.h | 254 +++ .../common/extactionmanagementnotifier.cpp | 19 + .../common/extactionmanagementnotifier.h | 30 + .../guiSQLiteStudio/common/extactionprototype.cpp | 65 + .../guiSQLiteStudio/common/extactionprototype.h | 44 + .../guiSQLiteStudio/common/extlineedit.cpp | 118 ++ SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h | 45 + SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp | 98 + SQLiteStudio3/guiSQLiteStudio/common/fileedit.h | 52 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp | 68 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.h | 43 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui | 35 + .../guiSQLiteStudio/common/intvalidator.cpp | 38 + .../guiSQLiteStudio/common/intvalidator.h | 25 + .../guiSQLiteStudio/common/numericspinbox.cpp | 152 ++ .../guiSQLiteStudio/common/numericspinbox.h | 57 + .../guiSQLiteStudio/common/tablewidget.cpp | 49 + SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h | 23 + .../guiSQLiteStudio/common/userinputfilter.cpp | 33 + .../guiSQLiteStudio/common/userinputfilter.h | 31 + .../common/verifiablewizardpage.cpp | 20 + .../guiSQLiteStudio/common/verifiablewizardpage.h | 22 + .../guiSQLiteStudio/common/widgetcover.cpp | 209 +++ SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h | 72 + .../common/widgetstateindicator.cpp | 412 +++++ .../guiSQLiteStudio/common/widgetstateindicator.h | 99 + .../completer/completeritemdelegate.cpp | 133 ++ .../completer/completeritemdelegate.h | 31 + .../guiSQLiteStudio/completer/completermodel.cpp | 170 ++ .../guiSQLiteStudio/completer/completermodel.h | 50 + .../guiSQLiteStudio/completer/completerview.cpp | 70 + .../guiSQLiteStudio/completer/completerview.h | 30 + .../guiSQLiteStudio/completer/completerwindow.cpp | 229 +++ .../guiSQLiteStudio/completer/completerwindow.h | 64 + .../guiSQLiteStudio/completer/completerwindow.ui | 47 + SQLiteStudio3/guiSQLiteStudio/configmapper.cpp | 652 +++++++ SQLiteStudio3/guiSQLiteStudio/configmapper.h | 121 ++ SQLiteStudio3/guiSQLiteStudio/configuiplugin.h | 24 + .../configwidgets/combodatawidget.cpp | 63 + .../configwidgets/combodatawidget.h | 35 + .../configwidgets/listtostringlisthash.cpp | 67 + .../configwidgets/listtostringlisthash.h | 22 + .../configwidgets/styleconfigwidget.cpp | 54 + .../configwidgets/styleconfigwidget.h | 20 + .../constraints/columncheckpanel.cpp | 56 + .../guiSQLiteStudio/constraints/columncheckpanel.h | 27 + .../constraints/columncollatepanel.cpp | 107 ++ .../constraints/columncollatepanel.h | 40 + .../constraints/columncollatepanel.ui | 69 + .../constraints/columndefaultpanel.cpp | 204 +++ .../constraints/columndefaultpanel.h | 41 + .../constraints/columndefaultpanel.ui | 56 + .../constraints/columnforeignkeypanel.cpp | 266 +++ .../constraints/columnforeignkeypanel.h | 48 + .../constraints/columnforeignkeypanel.ui | 147 ++ .../constraints/columnnotnullpanel.cpp | 13 + .../constraints/columnnotnullpanel.h | 17 + .../constraints/columnprimarykeypanel.cpp | 127 ++ .../constraints/columnprimarykeypanel.h | 36 + .../constraints/columnprimarykeypanel.ui | 105 ++ .../constraints/columnuniqueandnotnullpanel.cpp | 99 + .../constraints/columnuniqueandnotnullpanel.h | 37 + .../constraints/columnuniqueandnotnullpanel.ui | 72 + .../constraints/columnuniquepanel.cpp | 13 + .../constraints/columnuniquepanel.h | 22 + .../constraints/constraintcheckpanel.cpp | 175 ++ .../constraints/constraintcheckpanel.h | 49 + .../constraints/constraintcheckpanel.ui | 72 + .../constraints/constraintpanel.cpp | 96 + .../guiSQLiteStudio/constraints/constraintpanel.h | 79 + .../constraints/tablecheckpanel.cpp | 56 + .../guiSQLiteStudio/constraints/tablecheckpanel.h | 27 + .../constraints/tableforeignkeypanel.cpp | 418 +++++ .../constraints/tableforeignkeypanel.h | 57 + .../constraints/tableforeignkeypanel.ui | 215 +++ .../constraints/tablepkanduniquepanel.cpp | 299 +++ .../constraints/tablepkanduniquepanel.h | 63 + .../constraints/tablepkanduniquepanel.ui | 228 +++ .../constraints/tableprimarykeypanel.cpp | 83 + .../constraints/tableprimarykeypanel.h | 23 + .../constraints/tableprimarykeypanel.ui | 102 ++ .../constraints/tableuniquepanel.cpp | 21 + .../guiSQLiteStudio/constraints/tableuniquepanel.h | 16 + .../guiSQLiteStudio/customconfigwidgetplugin.h | 21 + .../guiSQLiteStudio/datagrid/sqlqueryitem.cpp | 460 +++++ .../guiSQLiteStudio/datagrid/sqlqueryitem.h | 96 + .../datagrid/sqlqueryitemdelegate.cpp | 70 + .../datagrid/sqlqueryitemdelegate.h | 23 + .../guiSQLiteStudio/datagrid/sqlquerymodel.cpp | 1519 ++++++++++++++++ .../guiSQLiteStudio/datagrid/sqlquerymodel.h | 444 +++++ .../datagrid/sqlquerymodelcolumn.cpp | 465 +++++ .../guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h | 177 ++ .../datagrid/sqlqueryrownummodel.cpp | 63 + .../guiSQLiteStudio/datagrid/sqlqueryrownummodel.h | 27 + .../guiSQLiteStudio/datagrid/sqlqueryview.cpp | 454 +++++ .../guiSQLiteStudio/datagrid/sqlqueryview.h | 130 ++ .../guiSQLiteStudio/datagrid/sqltablemodel.cpp | 332 ++++ .../guiSQLiteStudio/datagrid/sqltablemodel.h | 50 + SQLiteStudio3/guiSQLiteStudio/dataview.cpp | 836 +++++++++ SQLiteStudio3/guiSQLiteStudio/dataview.h | 199 ++ SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp | 184 ++ SQLiteStudio3/guiSQLiteStudio/dblistmodel.h | 66 + SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp | 309 ++++ SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h | 67 + SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp | 120 ++ SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h | 59 + SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp | 1557 ++++++++++++++++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h | 205 +++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui | 90 + .../guiSQLiteStudio/dbtree/dbtreeitem.cpp | 332 ++++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h | 112 ++ .../guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp | 164 ++ .../guiSQLiteStudio/dbtree/dbtreeitemdelegate.h | 28 + .../guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp | 74 + .../guiSQLiteStudio/dbtree/dbtreeitemfactory.h | 26 + .../guiSQLiteStudio/dbtree/dbtreemodel.cpp | 1222 +++++++++++++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h | 135 ++ .../guiSQLiteStudio/dbtree/dbtreeview.cpp | 255 +++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h | 57 + SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp | 79 + SQLiteStudio3/guiSQLiteStudio/debugconsole.h | 44 + SQLiteStudio3/guiSQLiteStudio/debugconsole.ui | 73 + .../guiSQLiteStudio/dialogs/aboutdialog.cpp | 94 + .../guiSQLiteStudio/dialogs/aboutdialog.h | 37 + .../guiSQLiteStudio/dialogs/aboutdialog.ui | 109 ++ .../guiSQLiteStudio/dialogs/bugdialog.cpp | 220 +++ SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h | 42 + SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui | 208 +++ .../dialogs/bugreportlogindialog.cpp | 94 + .../guiSQLiteStudio/dialogs/bugreportlogindialog.h | 40 + .../dialogs/bugreportlogindialog.ui | 132 ++ .../guiSQLiteStudio/dialogs/columndialog.cpp | 616 +++++++ .../guiSQLiteStudio/dialogs/columndialog.h | 113 ++ .../guiSQLiteStudio/dialogs/columndialog.ui | 348 ++++ .../dialogs/columndialogconstraintsmodel.cpp | 335 ++++ .../dialogs/columndialogconstraintsmodel.h | 58 + .../guiSQLiteStudio/dialogs/configdialog.cpp | 1529 ++++++++++++++++ .../guiSQLiteStudio/dialogs/configdialog.h | 141 ++ .../guiSQLiteStudio/dialogs/configdialog.ui | 1923 ++++++++++++++++++++ .../guiSQLiteStudio/dialogs/constraintdialog.cpp | 213 +++ .../guiSQLiteStudio/dialogs/constraintdialog.h | 79 + .../guiSQLiteStudio/dialogs/constraintdialog.ui | 113 ++ .../guiSQLiteStudio/dialogs/dbconverterdialog.cpp | 219 +++ .../guiSQLiteStudio/dialogs/dbconverterdialog.h | 52 + .../guiSQLiteStudio/dialogs/dbconverterdialog.ui | 144 ++ SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp | 590 ++++++ SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h | 89 + SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui | 236 +++ .../guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp | 58 + .../guiSQLiteStudio/dialogs/ddlpreviewdialog.h | 35 + .../guiSQLiteStudio/dialogs/ddlpreviewdialog.ui | 106 ++ .../dialogs/errorsconfirmdialog.cpp | 47 + .../guiSQLiteStudio/dialogs/errorsconfirmdialog.h | 28 + .../guiSQLiteStudio/dialogs/errorsconfirmdialog.ui | 85 + .../guiSQLiteStudio/dialogs/exportdialog.cpp | 737 ++++++++ .../guiSQLiteStudio/dialogs/exportdialog.h | 105 ++ .../guiSQLiteStudio/dialogs/exportdialog.ui | 438 +++++ .../guiSQLiteStudio/dialogs/importdialog.cpp | 376 ++++ .../guiSQLiteStudio/dialogs/importdialog.h | 69 + .../guiSQLiteStudio/dialogs/importdialog.ui | 230 +++ .../guiSQLiteStudio/dialogs/indexdialog.cpp | 468 +++++ .../guiSQLiteStudio/dialogs/indexdialog.h | 72 + .../guiSQLiteStudio/dialogs/indexdialog.ui | 195 ++ .../guiSQLiteStudio/dialogs/messagelistdialog.cpp | 97 + .../guiSQLiteStudio/dialogs/messagelistdialog.h | 37 + .../guiSQLiteStudio/dialogs/messagelistdialog.ui | 84 + .../dialogs/newconstraintdialog.cpp | 275 +++ .../guiSQLiteStudio/dialogs/newconstraintdialog.h | 65 + .../guiSQLiteStudio/dialogs/newconstraintdialog.ui | 75 + .../guiSQLiteStudio/dialogs/newversiondialog.cpp | 68 + .../guiSQLiteStudio/dialogs/newversiondialog.h | 34 + .../guiSQLiteStudio/dialogs/newversiondialog.ui | 144 ++ .../dialogs/populateconfigdialog.cpp | 119 ++ .../guiSQLiteStudio/dialogs/populateconfigdialog.h | 47 + .../dialogs/populateconfigdialog.ui | 99 + .../guiSQLiteStudio/dialogs/populatedialog.cpp | 332 ++++ .../guiSQLiteStudio/dialogs/populatedialog.h | 76 + .../guiSQLiteStudio/dialogs/populatedialog.ui | 158 ++ .../guiSQLiteStudio/dialogs/quitconfirmdialog.cpp | 42 + .../guiSQLiteStudio/dialogs/quitconfirmdialog.h | 28 + .../guiSQLiteStudio/dialogs/quitconfirmdialog.ui | 83 + .../guiSQLiteStudio/dialogs/searchtextdialog.cpp | 75 + .../guiSQLiteStudio/dialogs/searchtextdialog.h | 39 + .../guiSQLiteStudio/dialogs/searchtextdialog.ui | 153 ++ .../guiSQLiteStudio/dialogs/sortdialog.cpp | 248 +++ SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h | 60 + .../guiSQLiteStudio/dialogs/sortdialog.ui | 112 ++ .../dialogs/triggercolumnsdialog.cpp | 52 + .../guiSQLiteStudio/dialogs/triggercolumnsdialog.h | 33 + .../dialogs/triggercolumnsdialog.ui | 117 ++ .../guiSQLiteStudio/dialogs/triggerdialog.cpp | 413 +++++ .../guiSQLiteStudio/dialogs/triggerdialog.h | 62 + .../guiSQLiteStudio/dialogs/triggerdialog.ui | 215 +++ .../dialogs/versionconvertsummarydialog.cpp | 31 + .../dialogs/versionconvertsummarydialog.h | 28 + .../dialogs/versionconvertsummarydialog.ui | 91 + SQLiteStudio3/guiSQLiteStudio/formmanager.cpp | 185 ++ SQLiteStudio3/guiSQLiteStudio/formmanager.h | 43 + .../guiSQLiteStudio/forms/sqlformatterplugin.ui | 46 + SQLiteStudio3/guiSQLiteStudio/formview.cpp | 274 +++ SQLiteStudio3/guiSQLiteStudio/formview.h | 114 ++ SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro | 364 ++++ .../guiSQLiteStudio/guiSQLiteStudio_global.h | 12 + SQLiteStudio3/guiSQLiteStudio/icon.cpp | 385 ++++ SQLiteStudio3/guiSQLiteStudio/icon.h | 104 ++ SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp | 164 ++ SQLiteStudio3/guiSQLiteStudio/iconmanager.h | 275 +++ SQLiteStudio3/guiSQLiteStudio/icons.qrc | 194 ++ SQLiteStudio3/guiSQLiteStudio/img/abort24.png | Bin 0 -> 1081 bytes SQLiteStudio3/guiSQLiteStudio/img/act_abort.png | Bin 0 -> 544 bytes SQLiteStudio3/guiSQLiteStudio/img/act_clear.png | Bin 0 -> 784 bytes SQLiteStudio3/guiSQLiteStudio/img/act_copy.png | Bin 0 -> 606 bytes SQLiteStudio3/guiSQLiteStudio/img/act_cut.png | Bin 0 -> 690 bytes SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png | Bin 0 -> 529 bytes SQLiteStudio3/guiSQLiteStudio/img/act_delete.png | Bin 0 -> 663 bytes SQLiteStudio3/guiSQLiteStudio/img/act_paste.png | Bin 0 -> 685 bytes SQLiteStudio3/guiSQLiteStudio/img/act_redo.png | Bin 0 -> 613 bytes SQLiteStudio3/guiSQLiteStudio/img/act_search.png | Bin 0 -> 781 bytes .../guiSQLiteStudio/img/act_select_all.png | Bin 0 -> 327 bytes SQLiteStudio3/guiSQLiteStudio/img/act_undo.png | Bin 0 -> 632 bytes SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png | Bin 0 -> 591 bytes .../guiSQLiteStudio/img/apply_filter_re.png | Bin 0 -> 589 bytes .../guiSQLiteStudio/img/apply_filter_sql.png | Bin 0 -> 601 bytes .../guiSQLiteStudio/img/apply_filter_txt.png | Bin 0 -> 566 bytes SQLiteStudio3/guiSQLiteStudio/img/bug.png | Bin 0 -> 704 bytes SQLiteStudio3/guiSQLiteStudio/img/bug_list.png | Bin 0 -> 793 bytes SQLiteStudio3/guiSQLiteStudio/img/check.png | Bin 0 -> 650 bytes .../guiSQLiteStudio/img/clear_history.png | Bin 0 -> 658 bytes .../guiSQLiteStudio/img/clear_lineedit.png | Bin 0 -> 493 bytes SQLiteStudio3/guiSQLiteStudio/img/close.png | Bin 0 -> 544 bytes SQLiteStudio3/guiSQLiteStudio/img/collation.png | Bin 0 -> 599 bytes SQLiteStudio3/guiSQLiteStudio/img/column.png | Bin 0 -> 580 bytes .../guiSQLiteStudio/img/column_constraint.png | Bin 0 -> 814 bytes SQLiteStudio3/guiSQLiteStudio/img/columns.png | Bin 0 -> 707 bytes SQLiteStudio3/guiSQLiteStudio/img/commit.png | Bin 0 -> 605 bytes SQLiteStudio3/guiSQLiteStudio/img/complete.png | Bin 0 -> 671 bytes .../guiSQLiteStudio/img/completer_blob.png | Bin 0 -> 668 bytes .../guiSQLiteStudio/img/completer_no_value.png | Bin 0 -> 404 bytes .../guiSQLiteStudio/img/completer_number.png | Bin 0 -> 276 bytes .../guiSQLiteStudio/img/completer_operator.png | Bin 0 -> 308 bytes .../guiSQLiteStudio/img/completer_other.png | Bin 0 -> 415 bytes .../guiSQLiteStudio/img/completer_pragma.png | Bin 0 -> 845 bytes .../guiSQLiteStudio/img/completer_string.png | Bin 0 -> 524 bytes .../guiSQLiteStudio/img/config_colors.png | Bin 0 -> 865 bytes .../guiSQLiteStudio/img/config_data_editors.png | Bin 0 -> 359 bytes SQLiteStudio3/guiSQLiteStudio/img/config_font.png | Bin 0 -> 459 bytes .../guiSQLiteStudio/img/config_general.png | Bin 0 -> 435 bytes .../guiSQLiteStudio/img/config_look_and_feel.png | Bin 0 -> 538 bytes SQLiteStudio3/guiSQLiteStudio/img/config_style.png | Bin 0 -> 927 bytes SQLiteStudio3/guiSQLiteStudio/img/configure.png | Bin 0 -> 699 bytes .../guiSQLiteStudio/img/configure_constraint.png | Bin 0 -> 297 bytes SQLiteStudio3/guiSQLiteStudio/img/convert_db.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/database.png | Bin 0 -> 569 bytes .../guiSQLiteStudio/img/database_connect.png | Bin 0 -> 1370 bytes .../guiSQLiteStudio/img/database_connected.png | Bin 0 -> 725 bytes .../guiSQLiteStudio/img/database_disconnect.png | Bin 0 -> 1520 bytes .../guiSQLiteStudio/img/database_export.png | Bin 0 -> 941 bytes .../guiSQLiteStudio/img/database_export_wizard.svg | 284 +++ .../guiSQLiteStudio/img/database_file.png | Bin 0 -> 701 bytes .../guiSQLiteStudio/img/database_import_wizard.svg | 284 +++ .../guiSQLiteStudio/img/database_invalid.png | Bin 0 -> 692 bytes .../guiSQLiteStudio/img/database_network.png | Bin 0 -> 619 bytes .../guiSQLiteStudio/img/database_offline.png | Bin 0 -> 495 bytes .../guiSQLiteStudio/img/database_online.png | Bin 0 -> 569 bytes .../guiSQLiteStudio/img/database_reload.png | Bin 0 -> 697 bytes SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png | Bin 0 -> 845 bytes SQLiteStudio3/guiSQLiteStudio/img/default.png | Bin 0 -> 591 bytes SQLiteStudio3/guiSQLiteStudio/img/delete_row.png | Bin 0 -> 479 bytes .../guiSQLiteStudio/img/delete_selected.png | Bin 0 -> 512 bytes SQLiteStudio3/guiSQLiteStudio/img/delete_small.png | Bin 0 -> 348 bytes SQLiteStudio3/guiSQLiteStudio/img/denied_small.png | Bin 0 -> 338 bytes SQLiteStudio3/guiSQLiteStudio/img/directory.png | Bin 0 -> 401 bytes .../guiSQLiteStudio/img/directory_open.png | Bin 0 -> 511 bytes .../guiSQLiteStudio/img/directory_open_with_db.png | Bin 0 -> 746 bytes .../guiSQLiteStudio/img/directory_with_db.png | Bin 0 -> 663 bytes SQLiteStudio3/guiSQLiteStudio/img/edit_small.png | Bin 0 -> 314 bytes SQLiteStudio3/guiSQLiteStudio/img/erase.png | Bin 0 -> 680 bytes SQLiteStudio3/guiSQLiteStudio/img/error_small.png | Bin 0 -> 337 bytes SQLiteStudio3/guiSQLiteStudio/img/exec_query.png | Bin 0 -> 470 bytes .../guiSQLiteStudio/img/explain_query.png | Bin 0 -> 524 bytes SQLiteStudio3/guiSQLiteStudio/img/export.png | Bin 0 -> 813 bytes .../guiSQLiteStudio/img/export_file_browse.png | Bin 0 -> 507 bytes .../guiSQLiteStudio/img/feature_request.png | Bin 0 -> 818 bytes SQLiteStudio3/guiSQLiteStudio/img/fk.png | Bin 0 -> 673 bytes SQLiteStudio3/guiSQLiteStudio/img/font_browse.png | Bin 0 -> 340 bytes SQLiteStudio3/guiSQLiteStudio/img/format_sql.png | Bin 0 -> 445 bytes SQLiteStudio3/guiSQLiteStudio/img/function.png | Bin 0 -> 372 bytes SQLiteStudio3/guiSQLiteStudio/img/get_update.png | Bin 0 -> 1153 bytes SQLiteStudio3/guiSQLiteStudio/img/go_back.png | Bin 0 -> 631 bytes SQLiteStudio3/guiSQLiteStudio/img/help.png | Bin 0 -> 621 bytes SQLiteStudio3/guiSQLiteStudio/img/homepage.png | Bin 0 -> 961 bytes SQLiteStudio3/guiSQLiteStudio/img/import.png | Bin 0 -> 771 bytes SQLiteStudio3/guiSQLiteStudio/img/index.png | Bin 0 -> 749 bytes SQLiteStudio3/guiSQLiteStudio/img/indexes.png | Bin 0 -> 759 bytes .../guiSQLiteStudio/img/indicator_error.png | Bin 0 -> 346 bytes .../guiSQLiteStudio/img/indicator_hint.png | Bin 0 -> 453 bytes .../guiSQLiteStudio/img/indicator_info.png | Bin 0 -> 370 bytes .../guiSQLiteStudio/img/indicator_warn.png | Bin 0 -> 429 bytes SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png | Bin 0 -> 745 bytes SQLiteStudio3/guiSQLiteStudio/img/info_small.png | Bin 0 -> 357 bytes SQLiteStudio3/guiSQLiteStudio/img/insert_row.png | Bin 0 -> 583 bytes SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png | Bin 0 -> 565 bytes .../guiSQLiteStudio/img/integrity_check.png | Bin 0 -> 845 bytes SQLiteStudio3/guiSQLiteStudio/img/keyboard.png | Bin 0 -> 1485 bytes SQLiteStudio3/guiSQLiteStudio/img/keyword.png | Bin 0 -> 603 bytes SQLiteStudio3/guiSQLiteStudio/img/licenses.png | Bin 0 -> 707 bytes SQLiteStudio3/guiSQLiteStudio/img/loading.gif | Bin 0 -> 1849 bytes SQLiteStudio3/guiSQLiteStudio/img/minus_small.png | Bin 0 -> 351 bytes SQLiteStudio3/guiSQLiteStudio/img/move_down.png | Bin 0 -> 659 bytes SQLiteStudio3/guiSQLiteStudio/img/move_up.png | Bin 0 -> 651 bytes SQLiteStudio3/guiSQLiteStudio/img/not_null.png | Bin 0 -> 753 bytes SQLiteStudio3/guiSQLiteStudio/img/open_forum.png | Bin 0 -> 1510 bytes .../guiSQLiteStudio/img/open_sql_editor.png | Bin 0 -> 648 bytes .../guiSQLiteStudio/img/open_sql_file.png | Bin 0 -> 511 bytes .../guiSQLiteStudio/img/open_value_editor.png | Bin 0 -> 514 bytes SQLiteStudio3/guiSQLiteStudio/img/page_first.png | Bin 0 -> 684 bytes SQLiteStudio3/guiSQLiteStudio/img/page_last.png | Bin 0 -> 659 bytes SQLiteStudio3/guiSQLiteStudio/img/page_next.png | Bin 0 -> 658 bytes SQLiteStudio3/guiSQLiteStudio/img/page_prev.png | Bin 0 -> 658 bytes SQLiteStudio3/guiSQLiteStudio/img/pk.png | Bin 0 -> 689 bytes SQLiteStudio3/guiSQLiteStudio/img/plugin.png | Bin 0 -> 768 bytes SQLiteStudio3/guiSQLiteStudio/img/plus_small.png | Bin 0 -> 358 bytes .../guiSQLiteStudio/img/question_small.png | Bin 0 -> 365 bytes SQLiteStudio3/guiSQLiteStudio/img/reload.png | Bin 0 -> 659 bytes .../guiSQLiteStudio/img/rename_fn_arg.png | Bin 0 -> 639 bytes .../guiSQLiteStudio/img/results_below.png | Bin 0 -> 361 bytes .../guiSQLiteStudio/img/results_in_tab.png | Bin 0 -> 343 bytes SQLiteStudio3/guiSQLiteStudio/img/rollback.png | Bin 0 -> 588 bytes .../guiSQLiteStudio/img/save_sql_file.png | Bin 0 -> 507 bytes SQLiteStudio3/guiSQLiteStudio/img/set_null.png | Bin 0 -> 667 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png | Bin 0 -> 429 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png | Bin 0 -> 446 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png | Bin 0 -> 446 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png | Bin 0 -> 444 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png | Bin 0 -> 432 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png | Bin 0 -> 442 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png | Bin 0 -> 456 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png | Bin 0 -> 453 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png | Bin 0 -> 474 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png | Bin 0 -> 473 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png | Bin 0 -> 467 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png | Bin 0 -> 463 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png | Bin 0 -> 468 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png | Bin 0 -> 465 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png | Bin 0 -> 472 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png | Bin 0 -> 466 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png | Bin 0 -> 472 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png | Bin 0 -> 497 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png | Bin 0 -> 1489 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png | Bin 0 -> 376 bytes .../guiSQLiteStudio/img/sort_ind_desc.png | Bin 0 -> 383 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png | Bin 0 -> 681 bytes SQLiteStudio3/guiSQLiteStudio/img/sql.png | Bin 0 -> 376 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png | Bin 0 -> 670 bytes .../guiSQLiteStudio/img/sqlitestudio.icns | Bin 0 -> 287952 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico | Bin 0 -> 297966 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg | 384 ++++ .../guiSQLiteStudio/img/sqlitestudio_16.png | Bin 0 -> 1381 bytes SQLiteStudio3/guiSQLiteStudio/img/status_error.png | Bin 0 -> 696 bytes SQLiteStudio3/guiSQLiteStudio/img/status_info.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/status_warn.png | Bin 0 -> 722 bytes SQLiteStudio3/guiSQLiteStudio/img/table.png | Bin 0 -> 563 bytes .../guiSQLiteStudio/img/table_column_add.png | Bin 0 -> 636 bytes .../guiSQLiteStudio/img/table_column_delete.png | Bin 0 -> 660 bytes .../guiSQLiteStudio/img/table_column_edit.png | Bin 0 -> 783 bytes .../guiSQLiteStudio/img/table_constraint.png | Bin 0 -> 802 bytes .../guiSQLiteStudio/img/table_create_similar.png | Bin 0 -> 574 bytes SQLiteStudio3/guiSQLiteStudio/img/table_export.png | Bin 0 -> 930 bytes SQLiteStudio3/guiSQLiteStudio/img/table_import.png | Bin 0 -> 975 bytes .../guiSQLiteStudio/img/table_populate.png | Bin 0 -> 718 bytes SQLiteStudio3/guiSQLiteStudio/img/tables.png | Bin 0 -> 654 bytes .../guiSQLiteStudio/img/tabs_at_bottom.png | Bin 0 -> 364 bytes SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png | Bin 0 -> 352 bytes .../guiSQLiteStudio/img/test_conn_error.png | Bin 0 -> 874 bytes SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png | Bin 0 -> 905 bytes SQLiteStudio3/guiSQLiteStudio/img/tip.png | Bin 0 -> 743 bytes SQLiteStudio3/guiSQLiteStudio/img/trigger.png | Bin 0 -> 736 bytes .../guiSQLiteStudio/img/trigger_columns.png | Bin 0 -> 300 bytes SQLiteStudio3/guiSQLiteStudio/img/triggers.png | Bin 0 -> 680 bytes SQLiteStudio3/guiSQLiteStudio/img/unique.png | Bin 0 -> 630 bytes SQLiteStudio3/guiSQLiteStudio/img/user.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/user_manual.png | Bin 0 -> 628 bytes SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png | Bin 0 -> 614 bytes SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png | Bin 0 -> 933 bytes SQLiteStudio3/guiSQLiteStudio/img/view.png | Bin 0 -> 813 bytes SQLiteStudio3/guiSQLiteStudio/img/views.png | Bin 0 -> 1283 bytes .../guiSQLiteStudio/img/virtual_table.png | Bin 0 -> 671 bytes SQLiteStudio3/guiSQLiteStudio/img/warn_small.png | Bin 0 -> 424 bytes SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png | Bin 0 -> 452 bytes SQLiteStudio3/guiSQLiteStudio/img/win_tile.png | Bin 0 -> 408 bytes .../guiSQLiteStudio/img/win_tile_horizontal.png | Bin 0 -> 434 bytes .../guiSQLiteStudio/img/win_tile_vertical.png | Bin 0 -> 324 bytes SQLiteStudio3/guiSQLiteStudio/img/window_close.png | Bin 0 -> 386 bytes .../guiSQLiteStudio/img/window_close_all.png | Bin 0 -> 551 bytes .../guiSQLiteStudio/img/window_close_other.png | Bin 0 -> 698 bytes .../guiSQLiteStudio/img/window_rename.png | Bin 0 -> 586 bytes .../guiSQLiteStudio/img/window_restore.png | Bin 0 -> 479 bytes SQLiteStudio3/guiSQLiteStudio/license.txt | 502 +++++ SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp | 909 +++++++++ SQLiteStudio3/guiSQLiteStudio/mainwindow.h | 234 +++ SQLiteStudio3/guiSQLiteStudio/mainwindow.ui | 142 ++ SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp | 264 +++ SQLiteStudio3/guiSQLiteStudio/mdiarea.h | 73 + SQLiteStudio3/guiSQLiteStudio/mdichild.cpp | 77 + SQLiteStudio3/guiSQLiteStudio/mdichild.h | 47 + SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp | 201 ++ SQLiteStudio3/guiSQLiteStudio/mdiwindow.h | 43 + .../guiSQLiteStudio/multieditor/multieditor.cpp | 366 ++++ .../guiSQLiteStudio/multieditor/multieditor.h | 96 + .../multieditor/multieditorbool.cpp | 215 +++ .../guiSQLiteStudio/multieditor/multieditorbool.h | 68 + .../multieditor/multieditordate.cpp | 87 + .../guiSQLiteStudio/multieditor/multieditordate.h | 37 + .../multieditor/multieditordatetime.cpp | 275 +++ .../multieditor/multieditordatetime.h | 84 + .../multieditor/multieditordialog.cpp | 49 + .../multieditor/multieditordialog.h | 29 + .../guiSQLiteStudio/multieditor/multieditorhex.cpp | 94 + .../guiSQLiteStudio/multieditor/multieditorhex.h | 50 + .../multieditor/multieditornumeric.cpp | 109 ++ .../multieditor/multieditornumeric.h | 42 + .../multieditor/multieditortext.cpp | 184 ++ .../guiSQLiteStudio/multieditor/multieditortext.h | 87 + .../multieditor/multieditortime.cpp | 90 + .../guiSQLiteStudio/multieditor/multieditortime.h | 38 + .../multieditor/multieditorwidget.cpp | 23 + .../multieditor/multieditorwidget.h | 33 + .../multieditor/multieditorwidgetplugin.h | 17 + .../guiSQLiteStudio/qhexedit2/commands.cpp | 115 ++ SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h | 70 + .../guiSQLiteStudio/qhexedit2/qhexedit.cpp | 180 ++ SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h | 230 +++ .../guiSQLiteStudio/qhexedit2/qhexedit_p.cpp | 883 +++++++++ .../guiSQLiteStudio/qhexedit2/qhexedit_p.h | 129 ++ .../guiSQLiteStudio/qhexedit2/xbytearray.cpp | 167 ++ .../guiSQLiteStudio/qhexedit2/xbytearray.h | 67 + .../guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp | 359 ++++ .../guiSQLiteStudio/qtscriptsyntaxhighlighter.h | 75 + .../guiSQLiteStudio/searchtextlocator.cpp | 204 +++ SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h | 75 + .../guiSQLiteStudio/selectabledbmodel.cpp | 109 ++ SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h | 36 + .../guiSQLiteStudio/selectabledbobjmodel.cpp | 244 +++ .../guiSQLiteStudio/selectabledbobjmodel.h | 44 + SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp | 143 ++ SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h | 34 + SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp | 1521 ++++++++++++++++ SQLiteStudio3/guiSQLiteStudio/sqleditor.h | 280 +++ .../guiSQLiteStudio/sqlitesyntaxhighlighter.cpp | 447 +++++ .../guiSQLiteStudio/sqlitesyntaxhighlighter.h | 185 ++ SQLiteStudio3/guiSQLiteStudio/sqlview.cpp | 43 + SQLiteStudio3/guiSQLiteStudio/sqlview.h | 25 + SQLiteStudio3/guiSQLiteStudio/statusfield.cpp | 218 +++ SQLiteStudio3/guiSQLiteStudio/statusfield.h | 58 + SQLiteStudio3/guiSQLiteStudio/statusfield.ui | 97 + .../guiSQLiteStudio/syntaxhighlighterplugin.h | 17 + SQLiteStudio3/guiSQLiteStudio/taskbar.cpp | 299 +++ SQLiteStudio3/guiSQLiteStudio/taskbar.h | 73 + SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp | 68 + SQLiteStudio3/guiSQLiteStudio/uiconfig.h | 90 + SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp | 30 + SQLiteStudio3/guiSQLiteStudio/uicustomicon.h | 16 + SQLiteStudio3/guiSQLiteStudio/uidebug.cpp | 116 ++ SQLiteStudio3/guiSQLiteStudio/uidebug.h | 36 + SQLiteStudio3/guiSQLiteStudio/uiloader.cpp | 95 + SQLiteStudio3/guiSQLiteStudio/uiloader.h | 33 + .../guiSQLiteStudio/uiloaderpropertyhandler.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp | 26 + SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp | 75 + SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h | 37 + SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp | 27 + SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiutils.cpp | 123 ++ SQLiteStudio3/guiSQLiteStudio/uiutils.h | 23 + SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp | 137 ++ SQLiteStudio3/guiSQLiteStudio/widgetresizer.h | 47 + .../windows/bugreporthistorywindow.cpp | 155 ++ .../windows/bugreporthistorywindow.h | 65 + .../windows/bugreporthistorywindow.ui | 55 + .../guiSQLiteStudio/windows/collationseditor.cpp | 389 ++++ .../guiSQLiteStudio/windows/collationseditor.h | 89 + .../guiSQLiteStudio/windows/collationseditor.ui | 210 +++ .../windows/collationseditormodel.cpp | 287 +++ .../windows/collationseditormodel.h | 77 + .../guiSQLiteStudio/windows/constrainttabmodel.cpp | 395 ++++ .../guiSQLiteStudio/windows/constrainttabmodel.h | 70 + .../guiSQLiteStudio/windows/ddlhistorywindow.cpp | 150 ++ .../guiSQLiteStudio/windows/ddlhistorywindow.h | 54 + .../guiSQLiteStudio/windows/ddlhistorywindow.ui | 126 ++ .../guiSQLiteStudio/windows/editorwindow.cpp | 652 +++++++ .../guiSQLiteStudio/windows/editorwindow.h | 155 ++ .../guiSQLiteStudio/windows/editorwindow.ui | 137 ++ .../guiSQLiteStudio/windows/functionseditor.cpp | 632 +++++++ .../guiSQLiteStudio/windows/functionseditor.h | 109 ++ .../guiSQLiteStudio/windows/functionseditor.ui | 346 ++++ .../windows/functionseditormodel.cpp | 348 ++++ .../guiSQLiteStudio/windows/functionseditormodel.h | 98 + .../windows/tableconstraintsmodel.cpp | 482 +++++ .../windows/tableconstraintsmodel.h | 72 + .../windows/tablestructuremodel.cpp | 604 ++++++ .../guiSQLiteStudio/windows/tablestructuremodel.h | 89 + .../guiSQLiteStudio/windows/tablewindow.cpp | 1508 +++++++++++++++ .../guiSQLiteStudio/windows/tablewindow.h | 241 +++ .../guiSQLiteStudio/windows/tablewindow.ui | 307 ++++ .../guiSQLiteStudio/windows/viewwindow.cpp | 760 ++++++++ SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h | 146 ++ .../guiSQLiteStudio/windows/viewwindow.ui | 137 ++ 524 files changed, 57566 insertions(+) create mode 100644 SQLiteStudio3/guiSQLiteStudio/actionentry.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/actionentry.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extaction.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fileedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/configmapper.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configmapper.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configuiplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dataview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dataview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dblistmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/formmanager.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/formmanager.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/formview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/formview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro create mode 100644 SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/icon.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/icon.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/iconmanager.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/icons.qrc create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/abort24.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_abort.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_clear.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_copy.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_cut.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_delete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_paste.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_redo.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_search.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_undo.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/bug.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/bug_list.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/check.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/clear_history.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/close.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/collation.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/column.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/commit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/complete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_number.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_other.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_string.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_colors.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_font.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_general.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_style.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/configure.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/convert_db.png create mode 100755 SQLiteStudio3/guiSQLiteStudio/img/database.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_connect.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_connected.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_network.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_offline.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_online.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_reload.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/default.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_row.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/denied_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_open.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/edit_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/erase.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/error_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/exec_query.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/explain_query.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/feature_request.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/fk.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/font_browse.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/format_sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/function.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/get_update.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/go_back.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/help.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/homepage.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/import.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/index.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indexes.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/info_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/insert_row.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/keyboard.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/keyword.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/licenses.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/loading.gif create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/minus_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/move_down.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/move_up.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/not_null.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_forum.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_first.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_last.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_next.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_prev.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/pk.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/plugin.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/plus_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/question_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/reload.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/results_below.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/rollback.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/set_null.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_info.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_warn.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_import.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_populate.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tables.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tip.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/trigger.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/triggers.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/unique.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user_manual.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/view.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/views.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/warn_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_rename.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_restore.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/license.txt create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiarea.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdichild.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdichild.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqleditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/taskbar.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/taskbar.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiconfig.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uicustomicon.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uidebug.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uidebug.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloader.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloader.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiutils.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiutils.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/widgetresizer.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui (limited to 'SQLiteStudio3/guiSQLiteStudio') 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 +#include + +struct GUI_API_EXPORT ActionEntry +{ + enum class Type + { + SINGLE, + SUB_MENU + }; + + QString subMenuLabel; + QIcon subMenuIcon; + QList 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 +#include + +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 +#include + +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 + +/** + * @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 +#include + +/** + * @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 +#include + +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 +#include + +class QAbstractItemModel; + +class DataWidgetMapper : public QObject +{ + Q_OBJECT + public: + typedef std::function 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 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 +#include + +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 + +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 +#include +#include +#include +#include + +ExtActionContainer::ClassNameToToolBarAndAction ExtActionContainer::extraActions; +QList 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(&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& 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(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 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(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 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 +#include +#include +#include +#include +#include + +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 ExtraActions; + typedef QHash ToolBarToAction; + typedef QHash ClassNameToToolBarAndAction; + + public: + ExtActionContainer(); + virtual ~ExtActionContainer(); + + QAction* getAction(int action); + virtual const QMetaObject* metaObject() const = 0; + + template + static void insertAction(ExtActionPrototype* action, int toolbar = -1); + + template + static void insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar = -1); + + template + static void insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar = -1); + + template + static void removeAction(ExtActionPrototype* action, int toolbar = -1); + + protected: + QHash actionMap; + QHash shortcuts; + QSet 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 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 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 + static QList getInstances(); + + template + static void insertAction(ExtActionPrototype* action, int pos, bool after, int toolbar); + + static ClassNameToToolBarAndAction extraActions; + static QList instances; + + QSignalMapper* actionIdMapper = nullptr; + QHash extraActionToToolbarAndProto; + QHash toolbarAndProtoToAction; +}; + +template +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()) + instance->handleActionInsert(toolbar, dets); +} + +template +void ExtActionContainer::insertAction(ExtActionPrototype* action, int toolbar) +{ + insertAction(action, -1, false, toolbar); +} + +template +void ExtActionContainer::insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar) +{ + insertAction(action, afterAction, true, toolbar); +} + +template +void ExtActionContainer::insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar) +{ + insertAction(action, beforeAction, false, toolbar); +} + +template +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()) + instance->handleActionRemoval(toolbar, dets); + + extraActions[clsName][toolbar].removeOne(dets); + delete dets; +} + +template +QList ExtActionContainer::getInstances() +{ + QList typedInstances; + T* typedInstance = nullptr; + for (ExtActionContainer* instance : instances) + { + typedInstance = dynamic_cast(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 + +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 +#include + +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 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 +#include + +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 +#include +#include + +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 +#include +#include + +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(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 + +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 +#include +#include +#include + +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 + +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 +#include + +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 + +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 @@ + + + FontEdit + + + + 0 + 0 + 451 + 35 + + + + Form + + + + + + TextLabel + + + + + + + ... + + + + + + + + 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 +#include + +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 +#include +#include + +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 + +/** + * @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 +#include +#include +#include + +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(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 + +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 +#include + +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 + +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 + +class GUI_API_EXPORT VerifiableWizardPage : public QWizardPage +{ + Q_OBJECT + public: + typedef std::function 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 +#include +#include +#include +#include +#include +#include +#include + +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()); + 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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QHash 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(widget)) + positionMode = PositionMode::GROUP_BOX; + else if (dynamic_cast(widget)) + positionMode = PositionMode::LABEL; + else if (dynamic_cast(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("

%1

"); + if (msg.startsWith("

") && msg.endsWith("

")) + 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(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(w) && !dynamic_cast(w)) + w = w->parentWidget(); + + if (dynamic_cast(w)) + return dynamic_cast(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 + +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 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 +#include +#include +#include +#include + +/* + * 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(); + 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 + +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 +#include + +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& 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 +#include + +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& 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 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 + +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 + +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 +#include +#include + +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 +#include + +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 @@ + + + CompleterWindow + + + + 0 + 0 + 309 + 184 + + + + SQLiteStudio completer + + + true + + + + 0 + + + 0 + + + + + true + + + + + + + + + + + CompleterView + QListView +
completer/completerview.h
+
+
+ + +
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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLY_CFG(Widget, Value, WidgetType, Method, DataType) \ + APPLY_CFG_VARIANT(Widget, Value.value(), WidgetType, Method) + +#define APPLY_CFG_COND(Widget, Value, WidgetType, Method, DataType, ExtraConditionMethod) \ + if (qobject_cast(Widget) && qobject_cast(Widget)->ExtraConditionMethod())\ + {\ + qobject_cast(Widget)->Method(Value.value());\ + return;\ + } + +#define APPLY_CFG_VARIANT(Widget, Value, WidgetType, Method) \ + if (qobject_cast(Widget))\ + {\ + qobject_cast(Widget)->Method(Value);\ + return;\ + } + +#define APPLY_NOTIFIER(Widget, Key, WidgetType, Notifier) \ + if (qobject_cast(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(Widget) && qobject_cast(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(Widget))\ + return qobject_cast(Widget)->Method(); + +#define GET_CFG_VALUE_COND(Widget, Key, WidgetType, Method, ExtraConditionMethod) \ + if (qobject_cast(Widget) && qobject_cast(Widget)->ExtraConditionMethod())\ + return qobject_cast(Widget)->Method(); + +ConfigMapper::ConfigMapper(CfgMain* cfgMain) +{ + this->cfgMainList << cfgMain; +} + +ConfigMapper::ConfigMapper(const QList 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(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 handlers; + handlers += internalCustomConfigWidgets; + handlers += PLUGINS->getLoadedPlugins(); + + 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 allConfigEntries = getAllConfigEntries(); + QList allConfigWidgets = getAllConfigWidgets(topLevelWidget) + extraWidgets; + QHash 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 allConfigEntries = getAllConfigEntries(); + QList 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 &allConfigEntries, const QHash& 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& allConfigEntries) +{ + handleConfigComboBox(widget, allConfigEntries); +} + +void ConfigMapper::handleConfigComboBox(QWidget* widget, const QHash& allConfigEntries) +{ + ConfigComboBox* ccb = dynamic_cast(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 handlers; + handlers += internalCustomConfigWidgets; + handlers += PLUGINS->getLoadedPlugins(); + + 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 handlers; + handlers += internalCustomConfigWidgets; + handlers += PLUGINS->getLoadedPlugins(); + + 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 &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 handlers; + handlers += internalCustomConfigWidgets; + handlers += PLUGINS->getLoadedPlugins(); + + 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& allConfigEntries) +{ + return getEntryForProperty(widget, CFG_MODEL_PROPERTY, allConfigEntries); +} + +CfgEntry* ConfigMapper::getEntryForProperty(QWidget* widget, const char* propertyName, const QHash& 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 ConfigMapper::getAllConfigEntries() +{ + if (allEntries.isEmpty()) + { + QString key; + for (CfgMain* cfgMain : cfgMainList) + { + QHashIterator catIt(cfgMain->getCategories()); + while (catIt.hasNext()) + { + catIt.next(); + QHashIterator 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 ConfigMapper::getAllConfigWidgets(QWidget *parent) +{ + QList results; + QWidget* widget = nullptr; + foreach (QObject* obj, parent->children()) + { + widget = qobject_cast(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 ConfigMapper::getExtraWidgets() const +{ + return extraWidgets; +} + +void ConfigMapper::setExtraWidgets(const QList &value) +{ + extraWidgets = value; +} + +void ConfigMapper::addExtraWidget(QWidget *w) +{ + extraWidgets << w; +} + +void ConfigMapper::addExtraWidgets(const QList &list) +{ + extraWidgets += list; +} + +void ConfigMapper::clearExtraWidgets() +{ + extraWidgets.clear(); +} + +void ConfigMapper::handleModified() +{ + if (realTimeUpdates && !updatingEntry) + { + QWidget* widget = dynamic_cast(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(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(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(sender()); + if (!specialConfigEntryToWidgets.contains(key)) + return; + + QWidget* w = specialConfigEntryToWidgets[key]; + ConfigComboBox* ccb = dynamic_cast(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(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& 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 + +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 cfgMainList); + + void loadToWidget(QWidget* topLevelWidget); + void loadToWidget(CfgEntry* config, QWidget* widget); + void saveFromWidget(QWidget* widget, bool noTransaction = false); + void setInternalCustomConfigWidgets(const QList& 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 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 &value); + void addExtraWidget(QWidget* w); + void addExtraWidgets(const QList& list); + void clearExtraWidgets(); + + private: + void applyConfigToWidget(QWidget *widget, const QHash& allConfigEntries, const QHash &config); + void applyConfigToWidget(QWidget *widget, CfgEntry* cfgEntry, const QVariant& configValue); + void handleSpecialWidgets(QWidget *widget, const QHash& allConfigEntries); + void handleConfigComboBox(QWidget *widget, const QHash& 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& 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& allConfigEntries); + CfgEntry* getEntryForProperty(QWidget* widget, const char* propertyName, const QHash& allConfigEntries); + QHash getAllConfigEntries(); + QList getAllConfigWidgets(QWidget* parent); + bool isPersistant() const; + + QList cfgMainList; + QList internalCustomConfigWidgets; + bool realTimeUpdates = false; + QHash widgetToConfigEntry; + QHash configEntryToWidgets; + QHash specialConfigEntryToWidgets; + bool updatingEntry = false; + QList extraWidgets; + QHash 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 + +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 +#include + +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(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(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 + +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(widget); + QHash itemsByName; + for (int i = 0; i < list->count(); i++) + itemsByName[list->item(i)->text()] = list->item(i); + + QHash 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(widget); + if (!list) + { + ok = false; + return QVariant(); + } + + QHash 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(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 +#include + +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(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(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 + +ColumnCheckPanel::ColumnCheckPanel(QWidget *parent) : + ConstraintCheckPanel(parent) +{ +} + +SqliteExpr* ColumnCheckPanel::readExpr() +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + return constr->expr; +} + +QString ColumnCheckPanel::readName() +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + return constr->name; +} + +void ColumnCheckPanel::storeType() +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + constr->type = SqliteCreateTable::Column::Constraint::CHECK; +} + +SqliteConflictAlgo ColumnCheckPanel::readConflictAlgo() +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + return constr->onConflict; +} + +void ColumnCheckPanel::storeExpr(SqliteExpr* expr) +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + constr->expr = expr; +} + +void ColumnCheckPanel::storeName(const QString& name) +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + constr->name = name; +} + +void ColumnCheckPanel::storeConflictAlgo(SqliteConflictAlgo algo) +{ + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constraint.data()); + constr->onConflict = algo; +} + +SqliteCreateTable* ColumnCheckPanel::getCreateTable() +{ + return dynamic_cast(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 + +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 + +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(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(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 @@ + + + ColumnCollatePanel + + + + 0 + 0 + 400 + 79 + + + + Form + + + + + + + 0 + 30 + + + + + 0 + + + + + Collation name: + + + + + + + true + + + + + + + + + + + 0 + + + + + Named constraint: + + + + + + + + + + + + + + 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 + +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(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(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(constraint->parentStatement()); + SqliteCreateTable* createTable = dynamic_cast(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 queries = parser.getQueries(); + if (queries.size() == 0) + return SqliteExprPtr(); + + SqliteQueryPtr first = queries.first(); + if (first->queryType != SqliteQueryType::Select) + return SqliteExprPtr(); + + SqliteSelectPtr select = first.dynamicCast(); + 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(); +} + +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 + +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 @@ + + + ColumnDefaultPanel + + + + 0 + 0 + 400 + 169 + + + + Form + + + + + + Default value: + + + + + + + + + + + + + + + Named constraint: + + + + + + + + + + + + + + SqlEditor + QPlainTextEdit +
sqleditor.h
+
+
+ + +
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 +#include + +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(constraint->parent()); + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(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(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(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(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 +#include + +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 @@ + + + ColumnForeignKeyPanel + + + + 0 + 0 + 400 + 320 + + + + + 0 + 320 + + + + Form + + + + + + + + + Foreign table: + + + + + + + + + + + + + + + + Foreign column: + + + + + + + + + + + + + Reactions + + + + + + ON UPDATE + + + + + + + + + + ON DELETE + + + + + + + MATCH + + + + + + + + + + + + + + + + Deferred foreign key + + + + + + + + + + + + + + + + + + Named constraint + + + + + + + Constraint name + + + + + + + + + + fkTableCombo + fkColumnCombo + onUpdateCheckBox + onUpdateCombo + onDeleteCheckBox + onDeleteCombo + matchCheckBox + matchCombo + deferrableCombo + initiallyCombo + namedCheckBox + nameEdit + + + + 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(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(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(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(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 @@ + + + ColumnPrimaryKeyPanel + + + + 0 + 0 + 379 + 110 + + + + + 0 + 110 + + + + Form + + + + + + Autoincrement + + + + + + + + 0 + + + + + Sort order: + + + + + + + + 80 + 16777215 + + + + + + + + + + + + 0 + + + + + Named constraint: + + + + + + + + + + + + + + 0 + + + + + On conflict: + + + + + + + + 120 + 16777215 + + + + + + + + + + + + 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(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(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 @@ + + + ColumnUniqueAndNotNullPanel + + + + 0 + 0 + 400 + 82 + + + + + 0 + 80 + + + + Form + + + + + + + 0 + + + + + Named constraint: + + + + + + + + + + + + + + 0 + + + + + On conflict: + + + + + + + + 120 + 16777215 + + + + + + + + + + + + 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(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 + +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 + +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 @@ + + + ConstraintCheckPanel + + + + 0 + 0 + 400 + 197 + + + + Form + + + + + + The expression + + + + + + + + + + + + + + + Named constraint: + + + + + + + + + + + + + + + + On conflict + + + + + + + + + + + + + + SqlEditor + QPlainTextEdit +
sqleditor.h
+
+
+ + +
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 + +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 +#include + +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 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 + +TableCheckPanel::TableCheckPanel(QWidget *parent) : + ConstraintCheckPanel(parent) +{ +} + +SqliteExpr* TableCheckPanel::readExpr() +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + return constr->expr; +} + +QString TableCheckPanel::readName() +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + return constr->name; +} + +void TableCheckPanel::storeType() +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + constr->type = SqliteCreateTable::Constraint::CHECK; +} + +SqliteConflictAlgo TableCheckPanel::readConflictAlgo() +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + return constr->onConflict; +} + +void TableCheckPanel::storeExpr(SqliteExpr* expr) +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + constr->expr = expr; +} + +void TableCheckPanel::storeName(const QString& name) +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + constr->name = name; +} + +void TableCheckPanel::storeConflictAlgo(SqliteConflictAlgo algo) +{ + SqliteCreateTable::Constraint* constr = dynamic_cast(constraint.data()); + constr->onConflict = algo; +} + +SqliteCreateTable* TableCheckPanel::getCreateTable() +{ + return dynamic_cast(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 + +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 +#include + +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(columnsLayout->itemAtPosition(i, 0)->widget()); + combo = qobject_cast(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(columnsLayout->itemAtPosition(rowIdx, 0)->widget()); + bool wasEnabled = check->isEnabled(); + check->setEnabled(tableSelected); + + QComboBox* combo = qobject_cast(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(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(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(columnsLayout->itemAtPosition(idx, 0)->widget()); + check->setChecked(true); + + combo = dynamic_cast(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(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(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(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(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(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(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(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 +#include + +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 @@ + + + TableForeignKeyPanel + + + + 0 + 0 + 400 + 400 + + + + + 0 + 400 + + + + Form + + + + + + + + + Foreign table: + + + + + + + + + + + + + + 8 + true + + + + SQLite 2 does not support foreign keys officially, +but it's okay to use them anyway. + + + Qt::AlignCenter + + + 5 + + + + + + + Columns + + + + + + + + + + 75 + true + + + + Local column + + + + + + + + 75 + true + + + + Foreign column + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 362 + 25 + + + + + + + + + + + + Reactions + + + + + + ON UPDATE + + + + + + + + + + ON DELETE + + + + + + + MATCH + + + + + + + + + + + + + + + + Deferred foreign key + + + + + + + + + + + + + + + + + + Named constraint + + + + + + + Constraint name + + + + + + + + + + fkTableCombo + columnsArea + onUpdateCheckBox + onUpdateCombo + onDeleteCheckBox + onDeleteCombo + matchCheckBox + matchCombo + deferrableCombo + initiallyCombo + namedCheckBox + nameEdit + + + + 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 +#include +#include +#include +#include +#include +#include +#include + +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(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(item); + bool enable = cb->isChecked(); + + item = columnsLayout->itemAtPosition(colIdx, 1)->widget(); + qobject_cast(item)->setEnabled(enable); + + if (!constraint.isNull() && constraint->dialect == Dialect::Sqlite3) + { + item = columnsLayout->itemAtPosition(colIdx, 2)->widget(); + qobject_cast(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(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(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(columnsLayout->itemAtPosition(i, 0)->widget()); + if (!check->isChecked()) + continue; + + name = check->text(); + + if (constr->dialect == Dialect::Sqlite3) + { + combo = dynamic_cast(columnsLayout->itemAtPosition(i, 1)->widget()); + collate = combo->currentText(); + if (collate.isEmpty()) + collate = QString::null; + + combo = dynamic_cast(columnsLayout->itemAtPosition(i, 2)->widget()); + sortOrder = sqliteSortOrder(combo->currentText()); + } + else + { + combo = dynamic_cast(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(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(columnsLayout->itemAtPosition(idx, 0)->widget()); + check->setChecked(true); + + if (constr->dialect == Dialect::Sqlite3) + { + combo = dynamic_cast(columnsLayout->itemAtPosition(idx, 1)->widget()); + combo->setCurrentText(idxCol->collate); + + combo = dynamic_cast(columnsLayout->itemAtPosition(idx, 2)->widget()); + combo->setCurrentText(sqliteSortOrder(idxCol->sortOrder)); + } + else + { + combo = dynamic_cast(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(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 + +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 @@ + + + TablePrimaryKeyAndUniquePanel + + + + 0 + 0 + 386 + 269 + + + + PrimaryKeyOrUniquePanel + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Columns + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Column + + + + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + 75 + true + + + + Collation + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + + 75 + true + + + + Sort + + + + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 364 + 132 + + + + + + + + + + + + Valid only for a single column with INTEGER data type + + + Autoincrement + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Named constraint + + + + + + + Constraint name + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + On conflict + + + + + + + + 140 + 16777215 + + + + + + + + + + + scrollArea + autoIncrCheckBox + namedCheckBox + namedLineEdit + conflictCheckBox + conflictComboBox + + + + 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 + +TablePrimaryKeyPanel::TablePrimaryKeyPanel(QWidget *parent) : + TablePrimaryKeyAndUniquePanel(parent) +{ +} + +void TablePrimaryKeyPanel::storeConfiguration() +{ + TablePrimaryKeyAndUniquePanel::storeConfiguration(); + + if (constraint.isNull()) + return; + + // Type + SqliteCreateTable::Constraint* constr = dynamic_cast(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(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(item); + if (cb->isChecked()) + columns << cb->text(); + } + + if (columns.size() != 1) + { + ui->autoIncrCheckBox->setChecked(false); + ui->autoIncrCheckBox->setEnabled(false); + return; + } + + SqliteCreateTable* createTable = dynamic_cast(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 @@ + + + PrimaryKeyPanel + + + + 0 + 0 + 386 + 269 + + + + Form + + + + + + Column + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 364 + 147 + + + + + + + + + + + + Autoincrement + + + + + + + + 0 + + + + + Named constraint + + + + + + + + + + + + + + 0 + + + + + On conflict + + + + + + + + 140 + 16777215 + + + + + + + + + + + + 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(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 + +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 +#include + +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 = "%1
"; + static const QString rowTmp = "%1%2"; + static const QString hdrRowTmp = "%2 %3"; + static const QString constrRowTmp = "%2%3"; + static const QString emptyRow = ""; + + 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 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(); +} + +void SqlQueryItem::setColumn(SqlQueryModelColumn* column) +{ + QStandardItem::setData(QVariant::fromValue(column), DataRole::COLUMN); +} + +SqlQueryModel *SqlQueryItem::getModel() const +{ + if (!model()) + return nullptr; + + return dynamic_cast(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 + +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 +#include +#include + +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(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(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 + +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 +#include +#include +#include +#include +#include +#include + +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 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 SqlQueryModel::getColumns() +{ + return columns; +} + +SqlQueryItem *SqlQueryModel::itemFromIndex(const QModelIndex &index) const +{ + return dynamic_cast(QStandardItemModel::itemFromIndex(index)); +} + +SqlQueryItem*SqlQueryModel::itemFromIndex(int row, int column) const +{ + return dynamic_cast(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 SqlQueryModel::findItems(int role, const QVariant& value, int hits) const +{ + return toItemList(findIndexes(role, value, hits)); +} + +QList 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 SqlQueryModel::getUncommitedItems() const +{ + return findItems(SqlQueryItem::DataRole::UNCOMMITED, true); +} + +QList > SqlQueryModel::groupItemsByRows(const QList& items) +{ + QMap> itemsByRow; + for (SqlQueryItem* item : items) + itemsByRow[item->row()] << item; + + return itemsByRow.values(); +} + +QHash> SqlQueryModel::groupItemsByTable(const QList& items) +{ + QHash> 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 SqlQueryModel::filterOutCommitedItems(const QList& 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 newList; + foreach (SqlQueryItem* item, items) + if (item->isUncommited()) + newList << item; + + return newList; +} + +QList SqlQueryModel::getRow(int row) +{ + QList items; + for (int i = 0; i < columnCount(); i++) + items << itemFromIndex(row, i); + + return items; +} + +SqlQueryModel::Features SqlQueryModel::features() const +{ + return Features(); +} + +QList SqlQueryModel::toItemList(const QModelIndexList& indexes) const +{ + QList list; + foreach (const QModelIndex& idx, indexes) + list << itemFromIndex(idx); + + return list; +} + +void SqlQueryModel::commit() +{ + QList items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + commitInternal(items); +} + +void SqlQueryModel::commit(const QList& items) +{ + commitInternal(filterOutCommitedItems(items)); +} + +bool SqlQueryModel::commitRow(const QList& 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& 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 items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + rollbackInternal(items); +} + +void SqlQueryModel::rollback(const QList& items) +{ + rollbackInternal(filterOutCommitedItems(items)); +} + +void SqlQueryModel::commitInternal(const QList& 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 > groupedItems = groupItemsByRows(items); + bool ok = true; + foreach (const QList& itemsInRow, groupedItems) + { + if (!commitRow(itemsInRow)) + { + ok = false; + break; + } + } + + // Getting current uncommited list (after rows deletion it may be different) + QList itemsLeft = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + + // Getting common elements of initial and current item list, because of a possibility of the selective commit + QMutableListIterator 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& items) +{ + QList > groupedItems = groupItemsByRows(items); + foreach (const QList& 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& itemsInRow) +{ + UNUSED(itemsInRow); + return false; +} + +bool SqlQueryModel::commitEditedRow(const QList& itemsInRow) +{ + if (itemsInRow.size() == 0) + { + qWarning() << "SqlQueryModel::commitEditedRow() called with no items in the list."; + return true; + } + + Dialect dialect = db->getDialect(); + + QHash> itemsByTable = groupItemsByTable(itemsInRow); + + // Values + QString query; + SqlQueryModelColumn* col = nullptr; + QHash queryArgs; + QStringList assignmentArgs; + RowId rowId; + RowId newRowId; + CommitUpdateQueryBuilder queryBuilder; + QHashIterator> it(itemsByTable); + QList 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& 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& 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& itemsInRow) +{ + foreach (SqlQueryItem* item, itemsInRow) + item->rollback(); +} + +void SqlQueryModel::rollbackDeletedRow(const QList& 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 SqlQueryModel::getTableColumnModels(const QString& database, const QString& table) +{ + QList 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 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 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 SqlQueryModel::loadRow(SqlResultsRowPtr row) +{ + QList 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 rowIdColumns = tableToRowIdColumn[table]; + QHashIterator 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 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 editionForbiddenGlobalReasons; + foreach (QueryExecutor::EditionForbiddenReason reason, queryExecutor->getEditionForbiddenGlobalReasons()) + editionForbiddenGlobalReasons << SqlQueryModelColumn::convert(reason); + + // Reading all the details from query executor source tables + QHash 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 SqlQueryModel::readTableDetails() +{ + QHash 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()) + { + qWarning() << "Could not get parsed table while reading table details in SqlQueryModel. Queried table was:" + << database + "." + srcTable->table; + continue; + } + createTable = query.dynamicCast(); + + // Table details + TableDetails tableDetails; + table = {database, srcTable->table}; + + // Table constraints + foreach (SqliteCreateTable::Constraint* tableConstr, createTable->constraints) + tableDetails.constraints << tableConstr->detach(); + + // 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(); + else + columnDetails.type = SqliteColumnTypePtr(); + + // Column constraints + foreach (SqliteCreateTable::Column::Constraint* columnConstr, columnStmt->constraints) + columnDetails.constraints << columnConstr->detach(); + + tableDetails.columns[columnName] = columnDetails; + } + + results[table] = tableDetails; + } + + return results; + +} + +QList SqlQueryModel::getTablesForColumns() +{ + QList
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 SqlQueryModel::getColumnEditionEnabledList() +{ + QList 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 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 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 selectedItems = view->getSelectedItems(); + QSet rows; + foreach (SqlQueryItem* item, selectedItems) + rows << item->index().row(); + + QList rowList = rows.toList(); + qSort(rowList); + + QList 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 +#include + +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 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 findItems(int role, const QVariant &value, int hits = -1) const; + QList findItems(const QModelIndex &start, const QModelIndex& end, int role, const QVariant &value, int hits = -1) const; + QList getUncommitedItems() const; + QList 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> groupItemsByRows(const QList& items); + static QHash > groupItemsByTable(const QList& 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& 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. + * Important thing to pay attention to is that the item list passed in arguments contains only modified items. + */ + virtual bool commitEditedRow(const QList& 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& 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& itemsInRow); + + /** + * @brief rollbackEditedRow + * @param itemsInRow All cells for the deleted row. + * Restores original values in items. + */ + virtual void rollbackEditedRow(const QList& 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& itemsInRow); + + SqlQueryModelColumnPtr getColumnModel(const QString& database, const QString& table, const QString& column); + SqlQueryModelColumnPtr getColumnModel(const QString& table, const QString& column); + QList getTableColumnModels(const QString& database, const QString& table); + QList getTableColumnModels(const QString& table); + void updateItem(SqlQueryItem* item, const QVariant& value, int columnIndex, const RowId& rowId); + RowId getNewRowId(const RowId& currentRowId, const QList items); + void updateRowIdForAllItems(const Table& table, const RowId& rowId, const RowId& newRowId); + + QueryExecutor* queryExecutor = nullptr; + Db* db = nullptr; + QList 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 constraints; + }; + + QHash columns; + QList constraints; + }; + + void loadData(SqlQueryPtr results); + QList loadRow(SqlResultsRowPtr row); + RowId getRowIdValue(SqlResultsRowPtr row, int columnIdx); + void readColumns(); + void readColumnDetails(); + void updateColumnsHeader(); + void updateColumnHeaderLabels(); + void executeQueryInternal(); + void internalExecutionStopped(); + QHash readTableDetails(); + QList
getTablesForColumns(); + QList getColumnEditionEnabledList(); + QList toItemList(const QModelIndexList& indexes) const; + bool commitRow(const QList& itemsInRow); + void rollbackRow(const QList& itemsInRow); + void storeStep1NumbersFromExecution(); + void storeStep2NumbersFromExecution(); + void restoreNumbersToQueryExecutor(); + QList filterOutCommitedItems(const QList& items); + void commitInternal(const QList& items); + void rollbackInternal(const QList& 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 columnMap; + QHash> 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
tablesForColumns; + + /** + * @brief columnEditionStatus + * List of column edition capabilities, in the same order as \link #columns. + */ + QList 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& items); + void rollback(const QList& 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 + +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*"); + qRegisterMetaTypeStreamOperators("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(-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(-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 list = editionForbiddenReason.toList(); + qSort(list); + return resolveMessage(list[0]); +} + +bool SqlQueryModelColumn::isPk() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isRowIdPk() const +{ + if (dataType.getType() != DataType::INTEGER) + return false; + + foreach (ConstraintPk* pk, getConstraints()) + if (pk->scope == Constraint::Scope::COLUMN) + return true; + + return false; +} + +bool SqlQueryModelColumn::isAutoIncr() const +{ + foreach (ConstraintPk* pk, getConstraints()) + if (pk->autoIncrement) + return true; + + return false; +} + +bool SqlQueryModelColumn::isNotNull() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isUnique() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isFk() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isDefault() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isCollate() const +{ + return getConstraints().size() > 0; +} + +QList SqlQueryModelColumn::getFkConstraints() const +{ + return getConstraints(); +} + +SqlQueryModelColumn::ConstraintDefault* SqlQueryModelColumn::getDefaultConstraint() const +{ + QList list = getConstraints(); + if (list.size() == 0) + return nullptr; + + return list[0]; +} + +int qHash(SqlQueryModelColumn::EditionForbiddenReason reason) +{ + return static_cast(reason); +} + +QDataStream&operator <<(QDataStream& out, const SqlQueryModelColumn* col) +{ + out << reinterpret_cast(col); + return out; +} + +QDataStream&operator >>(QDataStream& in, SqlQueryModelColumn*& col) +{ + quint64 ptr; + in >> ptr; + col = reinterpret_cast(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 +QList SqlQueryModelColumn::getConstraints() const +{ + QList results; + foreach (Constraint* constr, constraints) + if (dynamic_cast(constr)) + results << dynamic_cast(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 getFkConstraints() const; + ConstraintDefault* getDefaultConstraint() const; + + QString displayName; + QString column; + QString table; + QString database; + DataType dataType; + QSet editionForbiddenReason; + QList constraints; + + private: + template + QList getConstraints() const; +}; + +typedef QSharedPointer 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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(SqlQueryView) + +SqlQueryView::SqlQueryView(QWidget *parent) : + QTableView(parent) +{ + init(); +} + +SqlQueryView::~SqlQueryView() +{ + delete itemDelegate; +} + +QList SqlQueryView::getSelectedItems() +{ + QList 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(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(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(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& selectedItems) +{ + UNUSED(currentItem); + + // Selected items count + int selCount = selectedItems.size(); + + // Uncommited items count + QList 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 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 selectedItems = getSelectedItems(); + QList > groupedItems = SqlQueryModel::groupItemsByRows(selectedItems); + + QStringList cells; + QList rows; + + foreach (const QList& 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 deserializedRows = TsvSerializer::deserialize(qApp->clipboard()->text()); + + QList 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(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 + +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 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& 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 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& 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 +#include +#include + +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& itemsInRow) +{ + QList 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 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& 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 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& itemsInRow, const QList& modelColumns, RowId rowId) +{ + // Update cells with data just like it was entered. Only DEFAULT and PRIMARY KEY AUTOINCREMENT will have special values. + QList 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& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& 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& itemsInRow, const QList& modelColumns, + QStringList& colNameList, QStringList& sqlValues, QList& 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& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& 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& itemsInRow); + bool commitDeletedRow(const QList& itemsInRow); + + private: + class CommitDeleteQueryBuilder : public CommitUpdateQueryBuilder + { + public: + QString build(); + }; + + + void updateColumnsAndValuesWithDefaultValues(const QList& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& args); + void updateColumnsAndValues(const QList& itemsInRow, const QList& modelColumns, + QStringList& colNameList, QStringList& sqlValues, QList& args); + QString getInsertSql(const QList& modelColumns, QStringList& colNameList, QStringList& sqlValues, + QList& args); + void updateRowAfterInsert(const QList& itemsInRow, const QList& 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 +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(DataView) +DataView::FilterMode DataView::filterMode; +DataView::TabsPosition DataView::tabsPosition; +QHash DataView::staticActions; +QHash 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(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 selectedItems = gridView->getSelectedItems(); + model->commit(selectedItems); +} + +void DataView::selectiveRollbackGrid() +{ + QList 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)) + { + 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(action, toolbar); +} + +void DataView::insertActionBefore(ExtActionPrototype* action, DataView::Action beforeAction, DataView::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void DataView::insertActionAfter(ExtActionPrototype* action, DataView::Action afterAction, DataView::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void DataView::removeAction(ExtActionPrototype* action, DataView::ToolBar toolbar) +{ + ExtActionContainer::removeAction(action, toolbar); +} + +SqlQueryView* DataView::getGridView() const +{ + return gridView; +} + +int qHash(DataView::ActionGroup action) +{ + return static_cast(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 +#include + +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 staticActions; + static QHash 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 + +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 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 + +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 unsortedList; + QList 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 +#include + +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(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(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(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 +#include + +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 +#include + +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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(DbTree) +QHash> DbTree::allowedTypesInside; +QSet 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 enabled; + const DbTreeItem* dbTreeItem = dynamic_cast(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 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 srcItems, const DbTreeItem* dstItem) +{ + QSet srcDbs; + QList 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& 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& items) +{ + QMutableListIterator 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& items) +{ + QMutableListIterator 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(currItem)->getType() == DbTreeItem::Type::DIR) + { + existingItems = dynamic_cast(currItem)->childNames(); + break; + } + else + { + itemToMove = dynamic_cast(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(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 ¤t, const QModelIndex &previous) +{ + UNUSED(previous); + updateActionStates(treeModel->itemFromIndex(current)); +} + +void DbTree::deleteSelected() +{ + QModelIndexList idxList = ui->treeView->getSelectedIndexes(); + QList items; + foreach (const QModelIndex& idx, idxList) + items << dynamic_cast(treeModel->itemFromIndex(idx)); + + deleteItems(items); +} + +void DbTree::deleteItems(const QList& itemsToDelete) +{ + QList items = itemsToDelete; + + filterUndeletableItems(items); + filterItemsWithParentInList(items); + + // Warning user about items to be deleted + static const QString itemTmp = " %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

Are you sure you want to continue?").arg(actions.join("

")); + + QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete objects"), msg); + if (result != QMessageBox::Yes) + return; + + // Deleting items + QSet 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(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 + +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& items); + void filterItemsWithParentInList(QList& items); + void deleteItem(DbTreeItem* item); + static bool areDbTreeItemsValidForItem(QList srcItems, const DbTreeItem* dstItem); + static bool areUrlsValidForItem(const QList& srcUrls, const DbTreeItem* dstItem); + + static void initDndTypes(); + + Ui::DbTree *ui = nullptr; + DbTreeModel* treeModel = nullptr; + WidgetCover* widgetCover = nullptr; + + static QHash> allowedTypesInside; + static QSet 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& 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 @@ + + + DbTree + + + + 0 + 0 + 200 + 618 + + + + false + + + QDockWidget::AllDockWidgetFeatures + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Databases + + + + + + + + 0 + 0 + + + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::CopyAction + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter by name + + + + + + + + + + + + DbTreeView + QTreeView +
dbtree/dbtreeview.h
+
+
+ + nameFilter + treeView + + + +
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 +#include + +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*"); + qRegisterMetaTypeStreamOperators("DbTreeItem*"); +} + +DbTreeItem::Type DbTreeItem::getType() const +{ + return static_cast(type()); +} + +void DbTreeItem::setType(Type type) +{ + setData(static_cast(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 DbTreeItem::childs() const +{ + QList 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(parentItem); +} + +QList DbTreeItem::getPathToRoot() +{ + QList path; + getPathToRoot(path); + return path; +} + +QList DbTreeItem::getPathToParentItem(DbTreeItem::Type type) +{ + QList path; + getPathToParentItem(path, type); + return path; +} + +QList DbTreeItem::getPathToParentItem(DbTreeItem::Type type, const QString& name) +{ + QList 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 &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& path, DbTreeItem::Type type) +{ + path << this; + if (getType() == type) + return; + + if (parentDbTreeItem()) + parentDbTreeItem()->getPathToParentItem(path, type); +} + +void DbTreeItem::getPathToParentItem(QList& 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(); +} + +void DbTreeItem::setHidden(bool hidden) +{ + setData(hidden, DataRole::HIDDEN); + dynamic_cast(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(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 +#include + +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 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 getPathToRoot(); + QList getPathToParentItem(Type type); + QList 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& path); + void getPathToParentItem(QList& path, Type type); + void getPathToParentItem(QList& 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 +#include + +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(index.model()); + DbTreeItem* item = dynamic_cast(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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +const QString DbTreeModel::toolTipTableTmp = "
%1
"; +const QString DbTreeModel::toolTipHdrRowTmp = "%2"; +const QString DbTreeModel::toolTipRowTmp = "%1%2"; +const QString DbTreeModel::toolTipIconRowTmp = "%2%3"; + +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(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(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(groupItem)->parentItem(); + if (!parentItem) + parentItem = root(); + + foreach (QStandardItem* child, dynamic_cast(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(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(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 groups = childsToConfig(invisibleRootItem()); + CFG->storeGroups(groups); +} + +void DbTreeModel::readGroups(QList dbList) +{ + QList 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 DbTreeModel::childsToConfig(QStandardItem *item) +{ + QList groups; + Config::DbGroupPtr group; + DbTreeItem* dbTreeItem = nullptr; + for (int i = 0; i < item->rowCount(); i++) + { + dbTreeItem = dynamic_cast(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* 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(item)->getType() == DbTreeItem::Type::DIR) + itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN); +} + +void DbTreeModel::collapsed(const QModelIndex &index) +{ + QStandardItem* item = itemFromIndex(index); + if (dynamic_cast(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(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 DbTreeModel::getAllItemsAsFlatList() const +{ + return getChildsAsFlatList(root()); +} + +QList DbTreeModel::getChildsAsFlatList(QStandardItem* item) const +{ + QList items; + QStandardItem* child = nullptr; + for (int i = 0; i < item->rowCount(); i++) + { + child = item->child(i); + items << dynamic_cast(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(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(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 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 tableItems = refreshSchemaTables(tables, virtualTables, sort); + StrHash> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns()); + StrHash> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort); + StrHash> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort); + QList viewItems = refreshSchemaViews(resolver.getViews(), sort); + refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns); + populateChildItemsWithDb(item, db); + restoreExpandedState(expandedState, item); +} + +void DbTreeModel::collectExpandedState(QHash &state, QStandardItem *parentItem) +{ + if (!parentItem) + parentItem = root(); + + DbTreeItem* dbTreeItem = dynamic_cast(parentItem); + if (dbTreeItem) + state[dbTreeItem->signature()] = treeView->isExpanded(dbTreeItem->index()); + + for (int i = 0; i < parentItem->rowCount(); i++) + collectExpandedState(state, parentItem->child(i)); +} + +QList DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort) +{ + QStringList sortedTables = tables; + if (sort) + qSort(sortedTables); + + QList items; + foreach (const QString& table, sortedTables) + { + if (virtualTables.contains(table)) + items += DbTreeItemFactory::createVirtualTable(table, this); + else + items += DbTreeItemFactory::createTable(table, this); + } + + return items; +} + +StrHash> DbTreeModel::refreshSchemaTableColumns(const StrHash &columns) +{ + QStringList sortedColumns; + bool sort = CFG_UI.General.SortColumns.get(); + StrHash> 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 > DbTreeModel::refreshSchemaIndexes(const StrHash &indexes, bool sort) +{ + StrHash > 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> DbTreeModel::refreshSchemaTriggers(const StrHash &triggers, bool sort) +{ + StrHash> 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 DbTreeModel::refreshSchemaViews(const QStringList &views, bool sort) +{ + QStringList sortedViews = views; + if (sort) + qSort(sortedViews); + + QList 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(childItem)->setDb(db); + populateChildItemsWithDb(childItem, db); + } +} + +void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem, + QList tables, + StrHash > indexes, + StrHash > triggers, + QList views, + StrHash > 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& expandedState, QStandardItem* parentItem) +{ + DbTreeItem* parentDbTreeItem = dynamic_cast(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(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(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 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(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 DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem::Type type) +{ + QList items; + DbTreeItem* item = nullptr; + for (int i = 0; i < parentItem->rowCount(); i++) + { + item = dynamic_cast(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 urlList; + QStringList textList; + + DbTreeItem* item = nullptr; + stream << reinterpret_cast(indexes.size()); + for (const QModelIndex& idx : indexes) + { + item = dynamic_cast(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(itemFromIndex(idx)); + else // drop on top of the parent + dstItem = dynamic_cast(itemFromIndex(parent)); + } + else + { + dstItem = dynamic_cast(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 DbTreeModel::getDragItems(const QMimeData* data) +{ + QList 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 DbTreeModel::getItemsForIndexes(const QModelIndexList& indexes) const +{ + QList items; + for (const QModelIndex& idx : indexes) + { + if (idx.isValid()) + items << dynamic_cast(itemFromIndex(idx)); + } + + return items; +} + +void DbTreeModel::staticInit() +{ +} + +bool DbTreeModel::dropDbTreeItem(const QList& 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& 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 = 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& 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& 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 >& diffs) +{ + VersionConvertSummaryDialog dialog(MAINWINDOW); + dialog.setWindowTitle(tr("SQL statements conversion")); + dialog.setSides(diffs); + return dialog.exec() == QDialog::Accepted; +} + +bool DbTreeModel::confirmConversionErrors(const QHash>& 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 +#include + +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 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 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 getDragItems(const QMimeData* data); + QList 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 findItems(QStandardItem* parentItem, DbTreeItem::Type type); + static void staticInit(); + + static const constexpr char* MIMETYPE = "application/x-sqlitestudio-dbtreeitem"; + + private: + void readGroups(QList dbList); + QList childsToConfig(QStandardItem* item); + void restoreGroup(const Config::DbGroupPtr& group, QList* dbList = nullptr, QStandardItem *parent = nullptr); + bool applyFilter(QStandardItem* parentItem, const QString& filter); + void refreshSchema(Db* db, QStandardItem* item); + void collectExpandedState(QHash& state, QStandardItem* parentItem = nullptr); + QStandardItem* refreshSchemaDb(Db* db); + QList refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort); + StrHash > refreshSchemaTableColumns(const StrHash& columns); + StrHash > refreshSchemaIndexes(const StrHash& indexes, bool sort); + StrHash > refreshSchemaTriggers(const StrHash& triggers, bool sort); + QList refreshSchemaViews(const QStringList &views, bool sort); + void populateChildItemsWithDb(QStandardItem* parentItem, Db* db); + void refreshSchemaBuild(QStandardItem* dbItem, QList tables, StrHash > indexes, + StrHash > triggers, QList views, StrHash > allTableColumns); + void restoreExpandedState(const QHash& expandedState, QStandardItem* parentItem); + QString getToolTip(DbTreeItem *item) const; + QString getDbToolTip(DbTreeItem *item) const; + QString getTableToolTip(DbTreeItem *item) const; + QList getChildsAsFlatList(QStandardItem* item) const; + bool dropDbTreeItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool &invokeStdDropAction); + bool dropDbObjectItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction); + QCheckBox* createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label); + bool dropUrls(const QList& urls); + void moveOrCopyDbObjects(const QList& 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>& diffs); + static bool confirmConversionErrors(const QHash >& 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 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 +#include +#include +#include +#include + +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(model()->itemFromIndex(currentIndex())); +} + +DbTreeItem *DbTreeView::itemAt(const QPoint &pos) +{ + return dynamic_cast(model()->itemFromIndex(indexAt(pos))); +} + +QList DbTreeView::selectionItems() +{ + QList items; + QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); + foreach (QModelIndex modIdx, selectedIndexes) + items += dynamic_cast(model()->itemFromIndex(modIdx)); + + return items; +} + +DbTreeModel *DbTreeView::model() const +{ + return dynamic_cast(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(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 +#include +#include + +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 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 + +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 +#include + +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 @@ + + + DebugConsole + + + + 0 + 0 + 745 + 344 + + + + SQLiteStudio Debug Console + + + + + + + Courier + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + DebugConsole + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DebugConsole + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include + +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 licenses = SQLITESTUDIO->getExtraLicenseManager()->getLicenses(); + QHashIterator 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("
  • %1
  • "); + QStringList entries; + for (const QString& idx : indexContents) + entries += entryTpl.arg(idx); + + licenseContents.prepend("

    Table of contents:

      " + entries.join("") + "
    "); +} + +void AboutDialog::readLicense(int row, const QString& title, const QString& path) +{ + QString rowNum = QString::number(row); + QString contents = readFile(path); + licenseContents += "

    " + rowNum + ". " + title + "

    "; + licenseContents += "
    " + contents + "
    "; + 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 +#include + +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 @@ + + + AboutDialog + + + + 0 + 0 + 741 + 447 + + + + About SQLiteStudio and licenses + + + + + + 1 + + + + About + + + + + + <html><head/><body><p align="center"><span style=" font-size:11pt; font-weight:600;">SQLiteStudio v%1</span></p><p align="center">Free, open-source, cross-platform SQLite database manager.<br/><a href="http://sqlitestudio.pl"><span style=" text-decoration: underline; color:#0000ff;">http://sqlitestudio.pl</span></a><br/></p><p align="center">%2<br/></p><p align="center">Author and active maintainer:<br/>SalSoft (<a href="http://salsoft.com.pl"><span style=" text-decoration: underline; color:#0000ff;">http://salsoft.com.pl</span></a>)<br/></p></body></html> + + + true + + + + + + + + Licenses + + + + + + true + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include + +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 + +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 @@ + + + BugDialog + + + + 0 + 0 + 516 + 421 + + + + Dialog + + + + + + Reporter + + + + + + + + + :/icons/img/user_unknown.png + + + + + + + E-mail address + + + + + + + Log in + + + + :/icons/img/user.png:/icons/img/user.png + + + + + + + ... + + + + :/icons/img/help.png:/icons/img/help.png + + + + + + + + + + Short description + + + + + + + + + + + + Detailed description + + + + + + + + + + + + Show more details + + + true + + + false + + + + + + true + + + + + 0 + 0 + 462 + 209 + + + + + + + SQLiteStudio version + + + + + + + + + + + + Operating system + + + + + + + + + + + + Loaded plugins + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + BugDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BugDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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 + +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 @@ + + + BugReportLoginDialog + + + + 0 + 0 + 343 + 197 + + + + Log in + + + + + + Credentials + + + + + + Login: + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + Validation + + + + + + Validate + + + + :/icons/img/test_conn_error.png:/icons/img/test_conn_error.png + + + Qt::ToolButtonTextBesideIcon + + + + + + + Validation result message + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + BugReportLoginDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BugReportLoginDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include +#include +#include + +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(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 +#include + +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 @@ + + + ColumnDialog + + + + 0 + 0 + 424 + 360 + + + + Column + + + true + + + + + + Name and type + + + + + + + 50 + 0 + + + + + + + + , + + + + + + + + 50 + 0 + + + + + + + + Data type: + + + + + + + Column name: + + + + + + + + + + Size: + + + + + + + + 120 + 0 + + + + true + + + + + + + + + + Constraints + + + + + + 0 + + + + + + + Unique + + + + + + + Configure + + + + + + + Foreign Key + + + + + + + Configure + + + + + + + Collate + + + + + + + Not NULL + + + + + + + Check condition + + + + + + + Primary Key + + + + + + + Default + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Advanced mode + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + NumericSpinBox + QSpinBox +
    common/numericspinbox.h
    +
    +
    + + name + typeCombo + scale + precision + pkCheck + pkButton + fkCheck + fkButton + uniqueCheck + uniqueButton + checkCheck + checkButton + notNullCheck + notNullButton + collateCheck + collateButton + defaultCheck + defaultButton + advancedCheck + buttonBox + constraintsView + + + + + buttonBox + accepted() + ColumnDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ColumnDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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(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 +#include + +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_FILTER_STRING(Widget, WidgetType, Method) \ + if (qobject_cast(Widget))\ + return qobject_cast(Widget)->Method() + " " + Widget->toolTip();\ + +#define GET_FILTER_STRING2(Widget, WidgetType) \ + WidgetType* w##WidgetType = qobject_cast(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(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 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 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 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(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 widgets = ui->stackedWidget->findChildren(); + QList matchedWidgets; + foreach (QWidget* widget, widgets) + { + if (getFilterString(widget).contains(filter, Qt::CaseInsensitive)) + matchedWidgets << widget; + } + + QHash pageToCategoryItem = buildPageToCategoryItemMap(); + QSet 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 ConfigDialog::buildPageToCategoryItemMap() const +{ + QHash pageNameToCategoryItem; + foreach (QTreeWidgetItem* item, getAllCategoryItems()) + pageNameToCategoryItem[item->statusTip(0)] = item; + + QWidget* page = nullptr; + QHash pageToCategoryItem; + for (int i = 0; i < ui->stackedWidget->count(); i++) + { + page = ui->stackedWidget->widget(i); + pageToCategoryItem[page] = pageNameToCategoryItem[page->objectName()]; + } + return pageToCategoryItem; +} + +QList ConfigDialog::getAllCategoryItems() const +{ + return ui->categoriesTree->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); +} + +QList ConfigDialog::getDefaultEditorsForType(DataType::Enum dataType) +{ + QList plugins = PLUGINS->getLoadedPlugins(); + DataType modelDataType; + modelDataType.setType(dataType); + + typedef QPair PluginWithPriority; + QList 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 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 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 ConfigDialog::updateCustomDataTypeEditors(const QStringList& editorsOrder) +{ + // Building plugins list + QList plugins = PLUGINS->getLoadedPlugins(); + QList 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 ConfigDialog::updateDefaultDataTypeEditors(DataType::Enum typeEnum) +{ + // Building plugins list + QList plugins = PLUGINS->getLoadedPlugins(); + QList 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(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 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(PLUGINS->getLoadedPlugin(pluginName)); + if (!plugin) + { + qCritical() << "Could not find plugin for lang " << lang << " in updateActiveFormatterState()"; + button->setEnabled(false); + continue; + } + + button->setEnabled(dynamic_cast(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( + "" + "" + "" + "" + "" + "%2" + "
    %1
    "); + static const QString row = QStringLiteral("%1%2"); + static const QString hline = QStringLiteral("
    "); + + 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(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()) + 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(plugin); + if (notifiablePlugin) + notifiablePlugins << notifiablePlugin; +} + +void ConfigDialog::pluginUnloaded(const QString& pluginName, PluginType* type) +{ + UNUSED(pluginName); + + // Update formatters page + if (type->isForPluginType()) + 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 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(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 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 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 activeFormatters = CFG_CORE.General.ActiveCodeFormatter.get(); + + QList plugins = PLUGINS->getLoadedPlugins(); + QHash> 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> 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(child)) + continue; + + applyStyle(qobject_cast(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 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 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("%2 ").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(plugin)) + return false; + + UiConfiguredPlugin* cfgPlugin = dynamic_cast(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(plugin)) + { + UiConfiguredPlugin* cfgPlugin = dynamic_cast(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 editorsOrder = CFG_UI.General.DataEditorsOrder.get(); + QSet 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 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 + +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 buildPageToCategoryItemMap() const; + QList getAllCategoryItems() const; + QList getDefaultEditorsForType(DataType::Enum dataType); + QList updateCustomDataTypeEditors(const QStringList& editorsOrder); + QList 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 nameToPage; + BiHash itemToPluginNameMap; + QHash pluginTypeToItemMap; + QHash pluginToItemMap; + QHash formatterLangToPluginComboMap; + QHash formatterLangToConfigButtonMap; + ConfigMapper* configMapper = nullptr; + QHash pluginConfigMappers; + QAction* dataEditRenameAction = nullptr; + QAction* dataEditDeleteAction = nullptr; + bool updatingDataEditorItem = false; + bool modifiedFlag = false; + QList 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 @@ + + + ConfigDialog + + + + 0 + 0 + 770 + 539 + + + + Configuration + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::PreventContextMenu + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Search + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + QAbstractItemView::ScrollPerPixel + + + false + + + + 1 + + + + + General + + + generalPage + + + + :/icons/img/config_general.png:/icons/img/config_general.png + + + + + Keyboard shortcuts + + + shortcutsPage + + + + :/icons/img/keyboard.png:/icons/img/keyboard.png + + + + + Look & feel + + + lookAndFeelPage + + + + :/icons/img/config_look_and_feel.png:/icons/img/config_look_and_feel.png + + + + Style + + + stylePage + + + + :/icons/img/config_style.png:/icons/img/config_style.png + + + + + Fonts + + + fontsPage + + + + :/icons/img/config_font.png:/icons/img/config_font.png + + + + + Colors + + + colorsPage + + + + :/icons/img/config_colors.png:/icons/img/config_colors.png + + + + + + Plugins + + + pluginsPage + + + + :/icons/img/plugin.png:/icons/img/plugin.png + + + + Code formatters + + + formatterPluginsPage + + + + + + Data browsing + + + dataBrowsingPage + + + + :/icons/img/table.png:/icons/img/table.png + + + + Data editors + + + dataEditorsPage + + + + :/icons/img/config_data_editors.png:/icons/img/config_data_editors.png + + + + + + + + + + + 5 + 0 + + + + 2 + + + + + + + Data browsing and editing + + + + + + Number of data rows per page: + + + + + + + + 150 + 16777215 + + + + 1 + + + 99999 + + + General.NumberOfRowsPerPage + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Data types + + + + + + + + + General.DataEditorsOrder + + + + + + + + + 3 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available editors: + + + + + + true + + + + + + + + + + Editors selected for this data type: + + + + + + true + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Schema editing + + + + + + + 150 + 16777215 + + + + Number of DDL changes kept in history. + + + 9999999 + + + General.DdlHistorySize + + + + + + + DDL history size: + + + + + + + Don't show DDL preview dialog when commiting schema changes + + + General.DontShowDdlPreview + + + + + + + + + + SQL queries + + + + + + + 150 + 16777215 + + + + Number of queries kept in the history. + + + 999999 + + + General.SqlHistorySize + + + + + + + Number of queries kept in the history. + + + History size: + + + + + + + <p>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.</p> + + + Execute only the query under the cursor + + + General.ExecuteCurrentQueryOnly + + + + + + + + + + Updates + + + + + + Automatically check for updates at startup + + + General.CheckUpdatesOnStartup + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter shortcuts by name or key combination + + + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::SingleSelection + + + 0 + + + false + + + true + + + false + + + 150 + + + 16 + + + false + + + + Action + + + + + Key combination + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Database list + + + + + + If switched off, then columns will be sorted in the order they are typed in CREATE TABLE statement. + + + Sort table columns alphabetically + + + General.SortColumns + + + + + + + Expand tables node when connected to a database + + + General.ExpandTables + + + + + + + <p>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.<p> + + + Display additional labels on the list + + + true + + + false + + + General.ShowDbTreeLabels + + + + + + For regular tables labels will show number of columns, indexes and triggers for each of tables. + + + Display labels for regular tables + + + General.ShowRegularTableLabels + + + + + + + Virtual tables will be marked with a 'virtual' label. + + + Display labels for virtual tables + + + General.ShowVirtualTableLabels + + + + + + + + + + Expand views node when connected to a database + + + General.ExpandViews + + + + + + + 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) + + + Sort objects (tables, indexes, triggers and views) alphabetically + + + General.SortObjects + + + + + + + Display system tables and indexes on the list + + + General.ShowSystemObjects + + + + + + + + + + Table windows + + + + + + When enabled, Table Windows will show up with the data tab, instead of the structure tab. + + + Open Table Windows with the data tab for start + + + General.OpenTablesOnData + + + + + + + + + + View windows + + + + + + When enabled, View Windows will show up with the data tab, instead of the structure tab. + + + Open View Windows with the data tab for start + + + General.OpenViewsOnData + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + false + + + false + + + 2 + + + false + + + false + + + + 1 + + + + + 2 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Hide built-in plugins + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Current style: + + + + + + + General.Style + + + + + + + + + + Preview + + + + + + 0 + + + + Enabled + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 24 + + + + + + + CheckBox + + + + + + + 50 + + + Qt::Vertical + + + + + + + Qt::Vertical + + + + + + + PushButton + + + + + + + + + + 0 + + + 0 + + + Qt::AlignCenter + + + + + + + + Column + + + + + 123 + + + + 11111 + + + + + 22222 + + + + + 33333 + + + + + + 456 + + + + 44444 + + + + + 55555 + + + + + 66666 + + + + + + + + + ... + + + + + + + + + + RadioButton + + + + + + + + ABC + + + + + XYZ + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Abcdefgh</span></p></body></html> + + + + + + + + + + + Disabled + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + false + + + + Language + + + + + Active formatter plugin + + + + + Configuration + + + + + + + + + + + + QFrame::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 258 + 286 + + + + + + + SQL editor font + + + + + + Fonts.SqlEditor + + + + + + + + + + Database list font + + + + + + Fonts.DbTree + + + + + + + + + + Database list additional label font + + + + + + Fonts.DbTreeLabel + + + + + + + + + + Data view font + + + + + + Fonts.DataView + + + + + + + + + + Status field font + + + + + + Fonts.StatusField + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + QFrame::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 307 + 666 + + + + + + + SQL editor colors + + + + + + Current line background + + + + + + + <p>SQL strings are enclosed with single quote characters.</p> + + + String foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorKeywordFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorStringFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorCommentFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorForeground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorCurrentLineBg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorLineNumAreaBg + + + + + + + <p>Bind parameters are placeholders for values yet to be provided by the user. They have one of the forms:</p><ul><li>:param_name</li><li>$param_name</li><li>@param_name</li><li>?</li></ul> + + + Bind parameter foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorParenthesisBg + + + + + + + Highlighted parenthesis background + + + + + + + <p>BLOB values are binary values represented as hexadecimal numbers, like:</p><ul><li>X'12B4'</li><li>x'46A2F4'</li></ul> + + + BLOB value foreground + + + + + + + Regular foreground + + + + + + + Line numbers area background + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorBlobFg + + + + + + + Keyword foreground + + + + + + + Number foreground + + + + + + + Comment foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorNumberFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorBindParamFg + + + + + + + <p>Valid objects are name of tables, indexes, triggers, or views that exist in the SQLite database.</p> + + + Valid objects foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorValidObject + + + + + + + + + + Data view colors + + + + + + <p>Any data changes will be outlined with this color, until they're commited to the database.</p> + + + Uncommited data outline color + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataUncommited + + + + + + + <p>In case of error while commiting data changes, the problematic cell will be outlined with this color.</p> + + + Commit error outline color + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataUncommitedError + + + + + + + NULL value foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataNullFg + + + + + + + Deleted row background + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataDeletedBg + + + + + + + + + + Database list colors + + + + + + <p>Additional labels are those which tell you SQLite version, number of objects deeper in the tree, etc.</p> + + + Additional labels foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.DbTreeLabelsFg + + + + + + + + + + Status field colors + + + + + + Information message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldInfoFg + + + + + + + Warning message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldWarnFg + + + + + + + Error message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldErrorFg + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 100 + 30 + + + + + + + + ColorButton + QPushButton +
    common/colorbutton.h
    +
    + + FontEdit + QWidget +
    common/fontedit.h
    + 1 +
    +
    + + categoriesFilter + categoriesTree + pluginsList + buttonBox + expandViewsCheck + sortObjects + sortColumns + ddlHistorySizeSpin + dontShowDdlPreview + queryHistorySizeSpin + checkBox + expandTablesCheck + activeStyleCombo + previewTabs + previewCheckBox + previewVerticalSlider + previewPushButton + previewSpinBox + previewTreeWidget + previewToolButton + previewLineEdit + previewRadioButton + previewComboBox + previewTextEdit + fontsScrollArea + scrollArea + sqlEditorKeywordFgButton + sqlEditorStringFgButton + sqlEditorCommentFgButton + sqlEditorRegularFgButton + sqlEditorCurrLineBgButton + sqlEditorLineNumAreaBgButton + sqlEditorParBgButton + sqlEditorBlobFgButton + sqlEditorNumberFgButton + sqlEditorBindParamFgButton + sqlEditorValidObjectsButton + dataViewUncommitedButton + dataViewErrorButton + dataViewNullFgButton + dataViewDeletedRowBgButton + dbTreeLabelsButton + statusFieldInfoButton + statusFieldWarnButton + statusFieldErrorButton + + + + + + + buttonBox + accepted() + ConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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 +#include + +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(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(constrStatement)); + case ConstraintDialog::COLUMN: + return getSelectedConstraint(dynamic_cast(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(constrStatement)); + else + return ConstraintPanel::produce(dynamic_cast(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 +#include + +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 createTable; + QPointer columnStmt; + QHash 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 @@ + + + ConstraintDialog + + + + 0 + 0 + 400 + 300 + + + + + 400 + 0 + + + + Dialog + + + + + + + + + + 18 + 16777215 + + + + + + + + + + + + 75 + true + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConstraintDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConstraintDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include + +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 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 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 >& diffs) +{ + VersionConvertSummaryDialog dialog(MAINWINDOW); + dialog.setWindowTitle(tr("SQL statements conversion")); + dialog.setSides(diffs); + return dialog.exec() == QDialog::Accepted; +} + +bool DbConverterDialog::confirmConversionErrors(const QSet& 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 + +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 >& diffs); + static bool confirmConversionErrors(const QSet& 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 @@ + + + DbConverterDialog + + + + 0 + 0 + 400 + 251 + + + + Dialog + + + + + + Source database + + + + + + + + + Source database version: + + + + + + + false + + + + + + + + + + Target database + + + + + + Target version: + + + + + + + This is the file that will be created as a result of the conversion. + + + + + + + Target file: + + + + + + + Name of the new database: + + + + + + + + + + + + + + + + + This is the name that the converted database will be added to SQLiteStudio with. + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DbConverterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DbConverterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include +#include +#include +#include + +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 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(); + 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 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(editor); + connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::PASSWORD: + { + editor = new QLineEdit(this); + le = dynamic_cast(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(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(editor)->text(); + break; + case DbPluginOption::INT: + value = dynamic_cast(editor)->value(); + break; + case DbPluginOption::BOOL: + value = dynamic_cast(editor)->isChecked(); + break; + case DbPluginOption::DOUBLE: + value = dynamic_cast(editor)->value(); + break; + case DbPluginOption::CHOICE: + value = dynamic_cast(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(editor)->setText(value.toString()); + break; + case DbPluginOption::INT: + dynamic_cast(editor)->setValue(value.toInt()); + break; + case DbPluginOption::BOOL: + dynamic_cast(editor)->setChecked(value.toBool()); + break; + case DbPluginOption::DOUBLE: + dynamic_cast(editor)->setValue(value.toDouble()); + break; + case DbPluginOption::CHOICE: + dynamic_cast(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 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 DbDialog::collectOptions() +{ + QHash 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(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 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 +#include +#include +#include + +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 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 dbPlugins; + QList optionWidgets; + QHash optionKeyToWidget; + QHash optionKeyToType; + QHash 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 @@ + + + DbDialog + + + + 0 + 0 + 455 + 200 + + + + + 450 + 0 + + + + Database + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Database driver + + + + + + + + + true + + + + + + + Name + + + + + + + Type + + + + + + + + + + + + Browse for database file on local computer + + + + + + + + + + + + File + + + + + + + Generate name basing on file path + + + + + + true + + + + + + + Permanent + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <p>Enable this if you want the database to be stored in configuration file and restored every time SQLiteStudio is started.</p> + + + + + + true + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Test database connection + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + + fileEdit + browseButton + nameEdit + generateCheckBox + typeCombo + permamentCheckBox + + + + + buttonBox + accepted() + DbDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DbDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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 @@ + + + DdlPreviewDialog + + + + 0 + 0 + 527 + 351 + + + + Queries to be executed + + + + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Don't show again + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + + + buttonBox + accepted() + DdlPreviewDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DdlPreviewDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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>& 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& 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 + +namespace Ui { + class ErrorsConfirmDialog; +} + +class GUI_API_EXPORT ErrorsConfirmDialog : public QDialog +{ + Q_OBJECT + + public: + explicit ErrorsConfirmDialog(QWidget *parent = 0); + ~ErrorsConfirmDialog(); + + void setErrors(const QHash >& errors); + void setErrors(const QSet& 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 @@ + + + ErrorsConfirmDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + Following errors occured: + + + + + + + true + + + + + + + Would you like to proceed? + + + + + + + Qt::Horizontal + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + + buttonBox + accepted() + ErrorsConfirmDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ErrorsConfirmDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include +#include +#include +#include +#include + +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 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(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 + +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> 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 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 @@ + + + ExportDialog + + + + 0 + 0 + 515 + 414 + + + + Export dialog + + + QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton + + + + What do you want to export? + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + A database + + + true + + + + + + + A single table + + + + + + + Query results + + + + + + + + + + + + Table to export + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Database + + + + + + + Table + + + + + + + . + + + + + + + + + + Options + + + + + + When this option is unchecked, then only table DDL (CREATE TABLE statement) is exported. + + + Export table data + + + true + + + + + + + Export table indexes + + + true + + + + + + + Export table triggers + + + true + + + + + + + Qt::Horizontal + + + + + + + + true + + + + Note, that exporting table indexes and triggers may be unsupported by some output formats. + + + true + + + + + + + + + + + Select database objects to export + + + + + + false + + + + + + + Export data from tables + + + true + + + + + + + Select all + + + + + + + Deselect all + + + + + + + + + + Database: + + + + + + + + Query to export results for + + + + + + + + + + + + Database: + + + + + + + Query to be executed for results: + + + + + + + + Export format and options + + + + + + #formatScrollArea { background: transparent; } + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 298 + 288 + + + + #formatScrollAreaContents { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Export format + + + + + + + + + + + + Output + + + + + + Exported file path + + + + + + + Clipboard + + + + + + + ... + + + + + + + File + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Exported text encoding: + + + + + + + + + + + + + + + + Export format options + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + VerifiableWizardPage + QWizardPage +
    common/verifiablewizardpage.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + +
    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 +#include +#include + +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(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 + +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 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 @@ + + + ImportDialog + + + + 0 + 0 + 511 + 406 + + + + Import data + + + QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton + + + + Table to import to + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Table + + + + + + + Database + + + + + + + . + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + Data source to import from + + + + + + #scrollArea { background: transparent; } + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 269 + 280 + + + + #scrollAreaWidgetContents { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Data source type + + + + + + + + + + + + Options + + + + + + Input file: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + ... + + + + + + + + + + Text encoding: + + + + + + + + + + + + + Data source options + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + VerifiableWizardPage + QWizardPage +
    common/verifiablewizardpage.h
    + 1 +
    +
    + + +
    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 +#include +#include +#include +#include +#include + +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()) + { + notifyError(tr("Could not process index %1 correctly. Unable to open an index dialog.").arg(index)); + reject(); + return; + } + + createIndex = parsedObject.dynamicCast(); +} + +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 +#include + +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 columnCheckBoxes; + QList sortComboBoxes; + QList 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 @@ + + + IndexDialog + + + + 0 + 0 + 491 + 410 + + + + + 400 + 300 + + + + Index dialog + + + + + + 0 + + + + Index + + + + + + On table: + + + + + + + Index name: + + + + + + + Partial index condition + + + + + + + + 0 + 1 + + + + + + + + Unique index + + + + + + + + 0 + 2 + + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + + Column + + + + + Collation + + + + + Sort + + + + + + + + + + + + + + + DDL + + + + + + true + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + tabWidget + partialIndexCheck + partialIndexEdit + buttonBox + ddlEdit + + + + + buttonBox + accepted() + IndexDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + IndexDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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 +#include + +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 + +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 @@ + + + MessageListDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + + + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + MessageListDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MessageListDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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(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(constrStatement), + createTable.data(), db, parentWidget()); + break; + case ConstraintDialog::COLUMN: + constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast(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 +#include + +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 createTable; + QPointer 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 @@ + + + NewConstraintDialog + + + + 0 + 0 + 400 + 300 + + + + + 300 + 0 + + + + New constraint + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + buttonBox + accepted() + NewConstraintDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewConstraintDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +NewVersionDialog::NewVersionDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewVersionDialog) +{ + init(); +} + +NewVersionDialog::~NewVersionDialog() +{ + delete ui; +} + +void NewVersionDialog::setUpdates(const QList& 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 + +namespace Ui { + class NewVersionDialog; +} + +class GUI_API_EXPORT NewVersionDialog : public QDialog +{ + Q_OBJECT + + public: + explicit NewVersionDialog(QWidget *parent = 0); + ~NewVersionDialog(); + + void setUpdates(const QList& 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 @@ + + + NewVersionDialog + + + + 0 + 0 + 400 + 307 + + + + SQLiteStudio updates + + + + + + + 75 + true + + + + New updates are available! + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + false + + + true + + + false + + + 20 + + + 20 + + + + Component + + + + + Current version + + + + + Update version + + + + + + + + + + + Check for updates on startup + + + + + + + + + + Update to new version! + + + + :/icons/img/get_update.png:/icons/img/get_update.png + + + + 24 + 24 + + + + The update will be automatically downloaded and installed. This will also restart application at the end. + + + + + + + Not now. + + + + :/icons/img/abort24.png:/icons/img/abort24.png + + + + 24 + 24 + + + + Don't install the update and close this window. + + + + + + + + + + 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 +#include +#include + +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 %1 for column %2").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 + +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 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 @@ + + + PopulateConfigDialog + + + + 0 + 0 + 400 + 300 + + + + Populating configuration + + + + + + + + + Qt::RichText + + + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PopulateConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PopulateConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include +#include +#include +#include + +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(); + 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& 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(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 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 + +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& engines); + void rebuildEngines(); + + Ui::PopulateDialog *ui = nullptr; + QGridLayout* columnsGrid = nullptr; + DbListModel* dbListModel = nullptr; + DbObjListModel* tablesModel = nullptr; + Db* db = nullptr; + QStringList pluginTitles; + QList plugins; + QList columnEntries; + QSignalMapper* checkMapper = nullptr; + QSignalMapper* buttonMapper = nullptr; + QHash 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 @@ + + + PopulateDialog + + + + 0 + 0 + 447 + 358 + + + + Populate table + + + + + + + 0 + 0 + + + + Database + + + + + + + + + + + + + 0 + 0 + + + + Table + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + Columns + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 409 + 144 + + + + + + + + + + + + + Number of rows to populate: + + + + + + 1 + + + 999999999 + + + 100 + + + + + + + + + + + + buttonBox + accepted() + PopulateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PopulateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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 @@ + + + QuitConfirmDialog + + + + 0 + 0 + 489 + 186 + + + + Uncommited changes + + + + + + Are you sure you want to quit the application? + +Following items are pending: + + + + + + + Qt::Horizontal + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + + + + + + + + + + + + buttonBox + accepted() + QuitConfirmDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QuitConfirmDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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 @@ + + + SearchTextDialog + + + + 0 + 0 + 403 + 184 + + + + Dialog + + + + + + + + + Find: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Case sensitive + + + + + + + Search backwards + + + + + + + Regular expression matching + + + + + + + + + + Replace && +find next + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + Replace with: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Replace all + + + + + + + Find + + + true + + + + + + + findEdit + replaceEdit + caseSensitiveCheck + regExpCheck + backwardsCheck + findButton + replaceButton + replaceAllButton + buttonBox + + + + + buttonBox + accepted() + SearchTextDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SearchTextDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include + +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(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 checkedColumns; + QList 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(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 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(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 + +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 @@ + + + SortDialog + + + + 0 + 0 + 457 + 357 + + + + Sort by columns + + + + + + + + + + 2 + 0 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + false + + + + Column + + + + + Order + + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + SortDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SortDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 + +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 + +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 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 @@ + + + TriggerColumnsDialog + + + + 0 + 0 + 334 + 300 + + + + Dialog + + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 75 + true + + + + Triggering columns: + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 320 + 239 + + + + + 0 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + TriggerColumnsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TriggerColumnsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 +#include +#include + +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()) + { + notifyError(tr("Could not process trigger %1 correctly. Unable to open a trigger dialog.").arg(trigger)); + reject(); + return; + } + + createTrigger = parsedObject.dynamicCast(); + 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 + +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 @@ + + + TriggerDialog + + + + 0 + 0 + 490 + 480 + + + + Trigger dialog + + + + + + 0 + + + + Trigger + + + + + + On table: + + + + + + + + + + Action: + + + + + + + + + + + + + + + + <p>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.</p> + + + Pre-condition: + + + + + + + The scope is still not fully supported by the SQLite database. + + + + + + + Trigger name: + + + + + + + When: + + + + + + + List of columns for UPDATE OF action. + + + ... + + + + + + + Scope: + + + + + + + Code: + + + + + + + Trigger statements to be executed. + + + + + + + + 16777215 + 80 + + + + <p>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.</p> + + + + + + + + DDL + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + tabWidget + nameEdit + whenCombo + actionCombo + actionColumns + onCombo + scopeCombo + preconditionCheck + preconditionEdit + codeEdit + buttonBox + ddlEdit + + + + + buttonBox + accepted() + TriggerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TriggerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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 >& 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 + +namespace Ui { + class VersionConvertSummaryDialog; +} + +class GUI_API_EXPORT VersionConvertSummaryDialog : public QDialog +{ + Q_OBJECT + + public: + explicit VersionConvertSummaryDialog(QWidget *parent = 0); + ~VersionConvertSummaryDialog(); + + void setSides(const QList>& 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 @@ + + + VersionConvertSummaryDialog + + + + 0 + 0 + 600 + 497 + + + + Database version convert + + + + + + Following changes to the SQL statements will be made: + + + + + + + QAbstractItemView::NoSelection + + + false + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlCompareView + QTableWidget +
    sqlcompareview.h
    +
    +
    + + + + buttonBox + accepted() + VersionConvertSummaryDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + VersionConvertSummaryDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    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 +#include +#include +#include +#include + +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"()"); + + 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 +#include + +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 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 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Active SQL formatter plugin + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 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 +#include +#include +#include + +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(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 +#include +#include +#include + +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 gridView; + QPointer model; + QWidget* contents = nullptr; + QList widgets; + QList editors; + QList 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 + +#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 +#include +#include +#include + +QHash 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(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(); + qRegisterMetaTypeStreamOperators(); +} + +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(icon); + return out; +} + +QDataStream& operator>>(QDataStream& in, const Icon*& icon) +{ + qint64 ptr; + in >> ptr; + icon = reinterpret_cast(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 +#include +#include + +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 dynamicallyAttributed; + + static QHash 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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include + +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 icons; + QHash movies; + QHash 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 @@ + + + img/act_abort.png + img/act_clear.png + img/act_copy.png + img/act_cut.png + img/act_del_line.png + img/act_delete.png + img/act_paste.png + img/act_redo.png + img/act_search.png + img/act_select_all.png + img/act_undo.png + img/apply_filter_re.png + img/apply_filter_sql.png + img/apply_filter_txt.png + img/apply_filter.png + img/check.png + img/clear_history.png + img/clear_lineedit.png + img/collation.png + img/column_constraint.png + img/column.png + img/columns.png + img/commit.png + img/complete.png + img/completer_blob.png + img/completer_no_value.png + img/completer_number.png + img/completer_operator.png + img/completer_other.png + img/completer_pragma.png + img/completer_string.png + img/configure_constraint.png + img/configure.png + img/database_connect.png + img/database_connected.png + img/database_disconnect.png + img/database_export.png + img/database_file.png + img/database_invalid.png + img/database_network.png + img/database_reload.png + img/database.png + img/ddl_history.png + img/default.png + img/delete_row.png + img/delete_selected.png + img/delete_small.png + img/denied_small.png + img/directory_open_with_db.png + img/directory_open.png + img/directory_with_db.png + img/directory.png + img/edit_small.png + img/erase.png + img/error_small.png + img/exec_query.png + img/explain_query.png + img/export_file_browse.png + img/export.png + img/fk.png + img/font_browse.png + img/format_sql.png + img/function.png + img/help.png + img/import.png + img/index.png + img/indexes.png + img/indicator_error.png + img/indicator_hint.png + img/indicator_info.png + img/indicator_warn.png + img/info_balloon.png + img/info_small.png + img/insert_row.png + img/insert_rows.png + img/keyword.png + img/loading.gif + img/minus_small.png + img/move_down.png + img/move_up.png + img/not_null.png + img/open_sql_editor.png + img/open_sql_file.png + img/open_value_editor.png + img/page_first.png + img/page_last.png + img/page_next.png + img/page_prev.png + img/pk.png + img/plus_small.png + img/question_small.png + img/reload.png + img/rename_fn_arg.png + img/results_below.png + img/results_in_tab.png + img/rollback.png + img/save_sql_file.png + img/set_null.png + img/sort_cnt_01.png + img/sort_cnt_02.png + img/sort_cnt_03.png + img/sort_cnt_04.png + img/sort_cnt_05.png + img/sort_cnt_06.png + img/sort_cnt_07.png + img/sort_cnt_08.png + img/sort_cnt_09.png + img/sort_cnt_10.png + img/sort_cnt_11.png + img/sort_cnt_12.png + img/sort_cnt_13.png + img/sort_cnt_14.png + img/sort_cnt_15.png + img/sort_cnt_16.png + img/sort_cnt_17.png + img/sort_cnt_18.png + img/sort_cnt_19.png + img/sort_cnt_20.png + img/sort_cnt_20p.png + img/sort_columns.png + img/sort_ind_asc.png + img/sort_ind_desc.png + img/sort_reset.png + img/sql.png + img/status_error.png + img/status_info.png + img/status_warn.png + img/table_column_add.png + img/table_column_delete.png + img/table_column_edit.png + img/table_constraint.png + img/table_create_similar.png + img/table_export.png + img/table_import.png + img/table_populate.png + img/table.png + img/tables.png + img/tabs_at_bottom.png + img/tabs_on_top.png + img/test_conn_error.png + img/test_conn_ok.png + img/trigger_columns.png + img/trigger.png + img/triggers.png + img/unique.png + img/view.png + img/views.png + img/virtual_table.png + img/warn_small.png + img/win_cascade.png + img/win_tile_horizontal.png + img/win_tile_vertical.png + img/win_tile.png + img/convert_db.png + img/window_rename.png + img/window_close.png + img/window_close_other.png + img/window_close_all.png + img/window_restore.png + img/integrity_check.png + img/vacuum_db.png + img/bug.png + img/feature_request.png + img/user_unknown.png + img/user.png + img/open_forum.png + img/user_manual.png + img/sqlite_docs.png + img/tip.png + img/licenses.png + img/homepage.png + img/bug_list.png + img/get_update.png + img/sqlitestudio.svg + img/sqlitestudio_16.png + img/keyboard.png + img/config_general.png + img/config_colors.png + img/config_font.png + img/config_look_and_feel.png + img/config_style.png + img/plugin.png + img/config_data_editors.png + img/database_export_wizard.svg + img/database_import_wizard.svg + img/database_offline.png + img/database_online.png + img/abort24.png + img/close.png + img/go_back.png + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/abort24.png b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png new file mode 100644 index 0000000..87b6373 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png new file mode 100644 index 0000000..6b9fa6d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png new file mode 100644 index 0000000..2c6152e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png new file mode 100644 index 0000000..3836257 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png new file mode 100644 index 0000000..85f80b5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png new file mode 100644 index 0000000..70b59dc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png new file mode 100644 index 0000000..0cf8887 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png new file mode 100644 index 0000000..56776f5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_search.png b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png new file mode 100644 index 0000000..7d57954 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png new file mode 100644 index 0000000..aa4dd4e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png new file mode 100644 index 0000000..1f69604 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug.png b/SQLiteStudio3/guiSQLiteStudio/img/bug.png new file mode 100644 index 0000000..242d539 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/bug.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png new file mode 100644 index 0000000..bbf004c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/check.png b/SQLiteStudio3/guiSQLiteStudio/img/check.png new file mode 100644 index 0000000..0023b3a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/check.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png new file mode 100644 index 0000000..0cade7e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png new file mode 100644 index 0000000..4671248 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/close.png b/SQLiteStudio3/guiSQLiteStudio/img/close.png new file mode 100644 index 0000000..6b9fa6d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/close.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/collation.png b/SQLiteStudio3/guiSQLiteStudio/img/collation.png new file mode 100644 index 0000000..94fa577 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/collation.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column.png b/SQLiteStudio3/guiSQLiteStudio/img/column.png new file mode 100644 index 0000000..7d10ac9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/column.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png new file mode 100644 index 0000000..4270875 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/columns.png b/SQLiteStudio3/guiSQLiteStudio/img/columns.png new file mode 100644 index 0000000..4cbaa8a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/columns.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/commit.png b/SQLiteStudio3/guiSQLiteStudio/img/commit.png new file mode 100644 index 0000000..3b0e3fc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/commit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/complete.png b/SQLiteStudio3/guiSQLiteStudio/img/complete.png new file mode 100644 index 0000000..358d9aa Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/complete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png new file mode 100644 index 0000000..739efb5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png new file mode 100644 index 0000000..e914d5c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png new file mode 100644 index 0000000..29bac66 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png new file mode 100644 index 0000000..b723492 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png new file mode 100644 index 0000000..2fc5ccc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png new file mode 100644 index 0000000..df7ccc1 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_font.png b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png new file mode 100644 index 0000000..b6911ca Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_general.png b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png new file mode 100644 index 0000000..07f3522 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_style.png b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png new file mode 100644 index 0000000..99a3802 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure.png b/SQLiteStudio3/guiSQLiteStudio/img/configure.png new file mode 100644 index 0000000..89ab15f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/configure.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png new file mode 100644 index 0000000..40a7bf9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png new file mode 100644 index 0000000..15b5b1a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database.png b/SQLiteStudio3/guiSQLiteStudio/img/database.png new file mode 100755 index 0000000..d588f42 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png new file mode 100644 index 0000000..7af930e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png new file mode 100644 index 0000000..71bc905 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png new file mode 100644 index 0000000..abc3282 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_export.png b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png new file mode 100644 index 0000000..417a4a2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_file.png b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png new file mode 100644 index 0000000..a36f74b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png new file mode 100644 index 0000000..7ccd0a3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_network.png b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png new file mode 100644 index 0000000..03f6f90 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png new file mode 100644 index 0000000..6888e5e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_online.png b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png new file mode 100644 index 0000000..d588f42 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png new file mode 100644 index 0000000..007b6d4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/default.png b/SQLiteStudio3/guiSQLiteStudio/img/default.png new file mode 100644 index 0000000..f231987 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/default.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png new file mode 100644 index 0000000..6dc019a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png new file mode 100644 index 0000000..ee473b9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png new file mode 100644 index 0000000..de13448 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png new file mode 100644 index 0000000..c9fb206 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory.png b/SQLiteStudio3/guiSQLiteStudio/img/directory.png new file mode 100644 index 0000000..260b415 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png new file mode 100644 index 0000000..dbaa6ee Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png new file mode 100644 index 0000000..8b941e3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/erase.png b/SQLiteStudio3/guiSQLiteStudio/img/erase.png new file mode 100644 index 0000000..bc6a3fa Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/erase.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/error_small.png b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png new file mode 100644 index 0000000..2a8f0f4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png new file mode 100644 index 0000000..2dfaef5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png new file mode 100644 index 0000000..298343e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/export.png b/SQLiteStudio3/guiSQLiteStudio/img/export.png new file mode 100644 index 0000000..b8df9d6 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/export.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png new file mode 100644 index 0000000..49f5acc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/fk.png b/SQLiteStudio3/guiSQLiteStudio/img/fk.png new file mode 100644 index 0000000..968bcb9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/fk.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png new file mode 100644 index 0000000..d5ab25d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png new file mode 100644 index 0000000..6db18b4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/function.png b/SQLiteStudio3/guiSQLiteStudio/img/function.png new file mode 100644 index 0000000..18fa585 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/function.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/get_update.png b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png new file mode 100644 index 0000000..bc7f13f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/go_back.png b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png new file mode 100644 index 0000000..2a361a0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/help.png b/SQLiteStudio3/guiSQLiteStudio/img/help.png new file mode 100644 index 0000000..4ecaf37 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/help.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/homepage.png b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png new file mode 100644 index 0000000..73377f9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/import.png b/SQLiteStudio3/guiSQLiteStudio/img/import.png new file mode 100644 index 0000000..4cab099 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/import.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/index.png b/SQLiteStudio3/guiSQLiteStudio/img/index.png new file mode 100644 index 0000000..7ac2093 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/index.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indexes.png b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png new file mode 100644 index 0000000..90ddfab Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png new file mode 100644 index 0000000..bb51f6f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png new file mode 100644 index 0000000..0df7fb3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png new file mode 100644 index 0000000..d98378b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png new file mode 100644 index 0000000..62e4690 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png new file mode 100644 index 0000000..f14f2a3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_small.png b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png new file mode 100644 index 0000000..0e0db2a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png new file mode 100644 index 0000000..10d1f60 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png new file mode 100644 index 0000000..55a2a7c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png new file mode 100644 index 0000000..985b883 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyword.png b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png new file mode 100644 index 0000000..1ca13e8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/licenses.png b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png new file mode 100644 index 0000000..8918410 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/loading.gif b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif new file mode 100644 index 0000000..5b33f7e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png new file mode 100644 index 0000000..46d56ae Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_down.png b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png new file mode 100644 index 0000000..9c53794 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_up.png b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png new file mode 100644 index 0000000..a76885b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/not_null.png b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png new file mode 100644 index 0000000..94af62e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png new file mode 100644 index 0000000..6aa2603 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_first.png b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png new file mode 100644 index 0000000..37a6036 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_last.png b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png new file mode 100644 index 0000000..16d38fc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_next.png b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png new file mode 100644 index 0000000..5102071 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png new file mode 100644 index 0000000..058be1a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/pk.png b/SQLiteStudio3/guiSQLiteStudio/img/pk.png new file mode 100644 index 0000000..8284636 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/pk.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plugin.png b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png new file mode 100644 index 0000000..172881f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png new file mode 100644 index 0000000..0f8a230 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/question_small.png b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png new file mode 100644 index 0000000..b6862db Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/reload.png b/SQLiteStudio3/guiSQLiteStudio/img/reload.png new file mode 100644 index 0000000..0a85ddc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/reload.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/results_below.png b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png new file mode 100644 index 0000000..50e735d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/rollback.png b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png new file mode 100644 index 0000000..933272b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/set_null.png b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png new file mode 100644 index 0000000..1c28f6c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png new file mode 100644 index 0000000..aa79bc1 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png new file mode 100644 index 0000000..5717d70 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sql.png b/SQLiteStudio3/guiSQLiteStudio/img/sql.png new file mode 100644 index 0000000..ea232a7 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sql.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png new file mode 100644 index 0000000..32df661 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns new file mode 100644 index 0000000..70ac0cd Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico new file mode 100644 index 0000000..0aef62d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png new file mode 100644 index 0000000..b319f7b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_error.png b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png new file mode 100644 index 0000000..5177258 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_info.png b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png new file mode 100644 index 0000000..fa9a60b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png new file mode 100644 index 0000000..567704b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table.png b/SQLiteStudio3/guiSQLiteStudio/img/table.png new file mode 100644 index 0000000..b0cd69f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png new file mode 100644 index 0000000..710190d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_export.png b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png new file mode 100644 index 0000000..e783a13 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_import.png b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png new file mode 100644 index 0000000..d753ad4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png new file mode 100644 index 0000000..0626d0a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tables.png b/SQLiteStudio3/guiSQLiteStudio/img/tables.png new file mode 100644 index 0000000..4ac0456 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tables.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tip.png b/SQLiteStudio3/guiSQLiteStudio/img/tip.png new file mode 100644 index 0000000..845e110 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tip.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png new file mode 100644 index 0000000..efc599d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png new file mode 100644 index 0000000..2ad2087 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/triggers.png b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png new file mode 100644 index 0000000..284c03f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/unique.png b/SQLiteStudio3/guiSQLiteStudio/img/unique.png new file mode 100644 index 0000000..ed7ec0e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/unique.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user.png b/SQLiteStudio3/guiSQLiteStudio/img/user.png new file mode 100644 index 0000000..2d44726 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png new file mode 100644 index 0000000..069fae7 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png new file mode 100644 index 0000000..09f73c2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png new file mode 100644 index 0000000..463222b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/view.png b/SQLiteStudio3/guiSQLiteStudio/img/view.png new file mode 100644 index 0000000..8b4b28f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/view.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/views.png b/SQLiteStudio3/guiSQLiteStudio/img/views.png new file mode 100644 index 0000000..c64d4d9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/views.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png new file mode 100644 index 0000000..fe899d8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png new file mode 100644 index 0000000..f108a40 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png new file mode 100644 index 0000000..ab3e4b2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png new file mode 100644 index 0000000..d12ce37 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png new file mode 100644 index 0000000..923f0f5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png 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 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png new file mode 100644 index 0000000..1faecb8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png new file mode 100644 index 0000000..80cfa3c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png 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. + + + Copyright (C) + + 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. + + , 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 +#include +#include +#include +#include +#include +#include + +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(tr("Configuration widgets")); + PLUGINS->registerPluginType(tr("Syntax highlighting engines")); + PLUGINS->registerPluginType(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)), this, SLOT(updatesAvailable(QList))); + 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(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 sessionValue; + sessionValue["state"] = saveState(); + sessionValue["geometry"] = saveGeometry(); + + QList 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 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& windowSessions) +{ + if (windowSessions.size() == 0) + return; + + foreach (const QVariant& winSession, windowSessions) + restoreWindowSession(winSession); +} + +MdiWindow* MainWindow::restoreWindowSession(const QVariant &windowSessions) +{ + QHash 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(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& updates) +{ + manualUpdatesChecking = false; + newVersionDialog = new NewVersionDialog(this); + newVersionDialog->setUpdates(updates); + notifyInfo(tr("New updates are available. Click here for details.").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(); +} + +FunctionsEditor* MainWindow::openFunctionEditor() +{ + return openMdiWindow(); +} + +CollationsEditor* MainWindow::openCollationEditor() +{ + return openMdiWindow(); +} + +BugReportHistoryWindow* MainWindow::openReportHistory() +{ + return openMdiWindow(); +} + +bool MainWindow::confirmQuit(const QList& 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)) + { + 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()); + 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(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 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 +#include +#include + +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& windowSessions); + MdiWindow *restoreWindowSession(const QVariant& windowSessions); + QString currentStyle() const; + void closeNonSessionWindows(); + DdlHistoryWindow* openDdlHistory(); + FunctionsEditor* openFunctionEditor(); + CollationsEditor* openCollationEditor(); + BugReportHistoryWindow* openReportHistory(); + + template + T* openMdiWindow(); + + static bool confirmQuit(const QList& 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 closedWindowSessionValues; + bool closingApp = false; + QMenu* dbMenu = nullptr; + QMenu* structMenu = nullptr; + QMenu* viewMenu = nullptr; + QMenu* toolsMenu = nullptr; + QMenu* sqlitestudioMenu = nullptr; + QPointer 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& updates); + void noUpdatesAvailable(); + void statusFieldLinkClicked(const QString& link); + void checkForUpdates(); + void handleUpdatingProgress(const QString& jobTitle, int jobPercent, int totalPercent); + void handleUpdatingError(); +}; + +template +T* MainWindow::openMdiWindow() +{ + T* win = nullptr; + foreach (MdiWindow* mdiWin, ui->mdiArea->getWindows()) + { + win = dynamic_cast(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 @@ + + + MainWindow + + + + 0 + 0 + 964 + 626 + + + + SQLiteStudio (?.?.?) + + + + + + true + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks + + + true + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + + + 138 + 138 + 138 + + + + + true + + + + + + + + + 0 + 0 + 964 + 22 + + + + + + Database toolbar + + + TopToolBarArea + + + false + + + + + Structure toolbar + + + TopToolBarArea + + + false + + + + + Tools + + + TopToolBarArea + + + false + + + + + Qt::CustomContextMenu + + + Window list + + + Qt::ToolButtonTextBesideIcon + + + TopToolBarArea + + + false + + + + + View toolbar + + + TopToolBarArea + + + false + + + + + + + MdiArea + QMdiArea +
    mdiarea.h
    + 1 +
    + + TaskBar + QToolBar +
    taskbar.h
    +
    +
    + + +
    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 +#include +#include +#include + +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(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 MdiArea::getWindows() const +{ + QList windowList; + foreach(QAction* action, taskBar->getTasks()) + windowList << actionToWinMap[action]; + + return windowList; +} + +QList MdiArea::getMdiChilds() const +{ + QList childs; + for (MdiWindow* win : getWindows()) + childs << win->getMdiChild(); + + return childs; +} + +QList MdiArea::getWindowsToTile() const +{ + QList list; + foreach (MdiWindow *window, getWindows()) + { + if (window->isMinimized()) + continue; + + list << window; + } + return list; +} + +void MdiArea::taskActivated() +{ + QAction* action = dynamic_cast(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(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 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 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 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(window); + + return nullptr; +} + +MdiWindow* MdiArea::getCurrentWindow() +{ + QMdiSubWindow* subWin = activeSubWindow(); + return dynamic_cast(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(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 + +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 getWindows() const; + QList getMdiChilds() const; + + template + QList getMdiChilds() const; + + private: + QList getWindowsToTile() const; + + TaskBar* taskBar = nullptr; + QHash actionToWinMap; + QHash winToActionMap; + + signals: + void windowListChanged(); + + private slots: + void taskActivated(); + void windowActivated(); + + public slots: + void windowDestroyed(MdiWindow* window); + void tileHorizontally(); + void tileVertically(); + void closeAllButActive(); +}; + +template +QList MdiArea::getMdiChilds() const +{ + QList childs; + T* child = nullptr; + for (MdiWindow* win : getWindows()) + { + child = dynamic_cast(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 + +MdiChild::MdiChild(QWidget* parent) : + QWidget(parent) +{ +} + +MdiChild::~MdiChild() +{ +} + +QVariant MdiChild::getSessionValue() +{ + QVariant value = saveSession(); + QHash 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 +#include + +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 +#include +#include +#include +#include +#include +#include + +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 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 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(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(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 +#include + +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QHash 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(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(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(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 MultiEditor::getEditorTypes(const DataType& dataType) +{ + QList editors; + + QString typeStr = dataType.toString().trimmed().toUpper(); + QHash editorsOrder = CFG_UI.General.DataEditorsOrder.get(); + if (editorsOrder.contains(typeStr)) + { + MultiEditorWidgetPlugin* plugin = nullptr; + for (const QString& editorPluginName : editorsOrder[typeStr].toStringList()) + { + plugin = dynamic_cast(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 plugins = PLUGINS->getLoadedPlugins(); + + typedef QPair EditorWithPriority; + + QList 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(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(tabs->widget(i)); + editorWidget->setValue(newValue); + editorWidget->setUpToDate(true); + } + invalidatingDisabled = false; +} + +void MultiEditor::updateLabel() +{ + if (deleted) + stateLabel->setText(""+tr("Deleted", "multieditor")+""); + else if (readOnly) + stateLabel->setText(""+tr("Read only", "multieditor")+""); + else + stateLabel->setText(""); +} + +QVariant MultiEditor::getValueOmmitNull() const +{ + return dynamic_cast(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 +#include + +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 getEditorTypes(const DataType& dataType); + + static const int margins = 2; + static const int spacing = 2; + + QCheckBox* nullCheck = nullptr; + QTabWidget* tabs = nullptr; + QList 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 +#include +#include + +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 MultiEditorBool::getNoScrollWidgets() +{ + QList 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 + +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 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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 MultiEditorDateTime::getNoScrollWidgets() +{ + QList list; + list << dateTimeEdit << calendar; + + QObject* obj = calendar->findChild("qt_calendar_calendarview"); + if (obj) + { + QTableView* view = dynamic_cast(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 +#include + +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 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 +#include + +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 + +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 + +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 MultiEditorHex::getNoScrollWidgets() +{ + return QList(); +} + +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 +#include + +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 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 +#include + +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 MultiEditorNumeric::getNoScrollWidgets() +{ + QList 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 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 +#include +#include +#include +#include + +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 MultiEditorText::getNoScrollWidgets() +{ + // We don't return text, we want it to be scrolled. + QList 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 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 + +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 + +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 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(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 + +/*! 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 + +#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 +#include +#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 +#include + +#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 +#include +#include +#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 + +/*! 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 + +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(textEdit); + if (plainEdit) + return new JavaScriptSyntaxHighlighter(plainEdit->document()); + + QTextEdit* edit = dynamic_cast(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 + + 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 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 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 +#include + +/** + * @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 m_keywords; + QSet 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 +#include +#include +#include + +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 +#include + +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(dynamic_cast(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(dynamic_cast(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 +#include +#include + +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 +#include + +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(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(dynamic_cast(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(dynamic_cast(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(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 +#include + +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 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 +#include + +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>& data) +{ + setRowCount(data.size()); + + int row = 0; + SqlView* leftView = nullptr; + SqlView* rightView = nullptr; + for (const QPair& 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(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(cellWidget(row, 0)); + leftView->document()->setTextWidth(leftWidth); + + rightView = dynamic_cast(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 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 + +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>& 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 selections = extraSelections(); + + // Just keep "current line" highlighting + QMutableListIterator 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(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 allParenthesis; + for (QTextBlock block = document()->begin(); block.isValid(); block = block.next()) + { + data = dynamic_cast(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& 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 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 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 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()); +} + +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 +#include +#include +#include + +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 parList, const TextBlockData::Parenthesis* thePar); + void markMatchedParenthesis(int pos1, int pos2, QList& 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 objectsInNamedDb; + bool objectLinksEnabled = false; + QList 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 +#include +#include + +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(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(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(); + 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(TextBlockState::ID_1)); + break; + case '"': + setCurrentBlockState(static_cast(TextBlockState::ID_2)); + break; + case '`': + setCurrentBlockState(static_cast(TextBlockState::ID_3)); + break; + } + break; + } + case Token::STRING: + setCurrentBlockState(static_cast(TextBlockState::STRING)); + break; + case Token::COMMENT: + setCurrentBlockState(static_cast(TextBlockState::COMMENT)); + break; + case Token::BLOB: + setCurrentBlockState(static_cast(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(state); +} + + +SqliteSyntaxHighlighter::DbObject::DbObject(int from, int to) : + from(from), to(to) +{ +} + +QList TextBlockData::parentheses() +{ + QList list; + foreach (const TextBlockData::Parenthesis& par, parData) + list << ∥ + + 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 ∥ + } + 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(textEdit); + if (plainEdit) + return new SqliteSyntaxHighlighter(plainEdit->document()); + + QTextEdit* edit = dynamic_cast(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 +#include + +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 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 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(TextBlockState::REGULAR); + int sqliteVersion = 3; + QHash formats; + QHash tokenTypeMapping; + QList errors; + QList 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()); +} 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 + +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 +#include +#include +#include +#include +#include + +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 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& 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()); + }); + + 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 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 +#include + +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& items, const QColor& color); + void setupMenu(); + void readRecentMessages(); + + Ui::StatusField *ui = nullptr; + QMenu* menu = nullptr; + QAction* copyAction = nullptr; + QAction* clearAction = nullptr; + QList 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 @@ + + + StatusField + + + + 0 + 0 + 708 + 106 + + + + Status + + + + + + + + 0 + 0 + + + + + 0 + 60 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + Qt::NoPen + + + true + + + 3 + + + false + + + 24 + + + true + + + false + + + 18 + + + + + + + + + + + + TableWidget + QTableWidget +
    common/tablewidget.h
    +
    +
    + + +
    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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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(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 +#include + +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 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 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 +#include +#include +#include +#include + +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(font); + } + + QVariant getDefaultItemViewFont() + { + QStandardItem it; + return it.font(); + } + + QVariant getDefaultDbTreeLabelFont() + { + QFont font = getDefaultItemViewFont().value(); +#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 +#include +#include + +namespace Cfg +{ + GUI_API_EXPORT QVariant getStyleDefaultValue(); + GUI_API_EXPORT QVariant getDefaultTextEditorFont(); + GUI_API_EXPORT QVariant getDefaultItemViewFont(); + GUI_API_EXPORT QVariant getDefaultDbTreeLabelFont(); + typedef QHash Session; + typedef QHash 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 +#include + +#define TRY_ICON_WITH(Type, Widget, Method, Icon) \ + if (dynamic_cast(Widget))\ + {\ + dynamic_cast(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 + +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 + +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 +#include +#include +#include + +#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()) + 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 +#include +#include +#include + +class UiLoaderPropertyHandler; + +class GUI_API_EXPORT UiLoader : public QUiLoader +{ + Q_OBJECT + public: + typedef std::function 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 registeredClasses; + QList 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 + +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 + +UiScriptingCombo::UiScriptingCombo() +{ +} + +const char* UiScriptingCombo::getPropertyName() const +{ + return "ScriptingLangCombo"; +} + +void UiScriptingCombo::handle(QWidget* widget, const QVariant& value) +{ + QComboBox* cb = dynamic_cast(widget); + if (!cb) + return; + + if (!value.toBool()) + return; + + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins()) + 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 +#include +#include +#include + +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(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()) + { + 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 + +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 +#include +#include + +UiUrlButton::UiUrlButton() +{ +} + + +const char* UiUrlButton::getPropertyName() const +{ + return "openUrl"; +} + +void UiUrlButton::handle(QWidget* widget, const QVariant& value) +{ + QAbstractButton* btn = dynamic_cast(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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 +#include + +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 +#include +#include + +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 + +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 +#include + +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, "%2"); + QString invalidUrlTpl = tr("Invalid response from server."); + + QList 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 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 + +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 @@ + + + BugReportHistoryWindow + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + Title + + + + + Reported at + + + + + URL + + + + + + + + + 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 +#include + +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()) + ui->langCombo->addItem(plugin->getLanguage()); + + // Syntax highlighting plugins + foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins()) + 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 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()); +} + + +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 +#include +#include + +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 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 @@ + + + CollationsEditor + + + + 0 + 0 + 765 + 529 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter collations + + + + + + + + + + + + 4 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Collation name: + + + + + + + + + + Implementation language: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + Databases + + + + + + Register in all databases + + + + + + + Register in following databases: + + + + + + + false + + + + + + + + + 0 + 2 + + + + Implementation code: + + + + + + + + + + + + + + + + + + + + + + + + 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& 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 CollationsEditorModel::getCollations() const +{ + QList 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> counter; + + int row = 0; + foreach (Collation* coll, collationList) + { + coll->valid &= true; + counter[coll->data->name] << row++; + } + + QHashIterator> 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()) + 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 +#include +#include + +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& collations); + void addCollation(const CollationManager::CollationPtr& collation); + void deleteCollation(int row); + QList 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 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 originalCollationList; + QHash 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 + +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(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& value) +{ + beginResetModel(); + createTable = value; + endResetModel(); +} + +ConstraintTabModel::Columns ConstraintTabModel::getColumn(int idx) const +{ + return static_cast(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 +#include + +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& 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 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 +#include +#include + +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 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 @@ + + + DdlHistoryWindow + + + + 0 + 0 + 749 + 599 + + + + Form + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter by database: + + + + + + + + 200 + 0 + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + + + + 0 + 2 + + + + true + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    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 +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(EditorWindow) +EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode; +QHash EditorWindow::staticActions; +QHash 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"); + resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; + loadTabsMode(); + createStaticActions(); +} + +void EditorWindow::insertAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void EditorWindow::insertActionBefore(ExtActionPrototype* action, EditorWindow::Action beforeAction, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void EditorWindow::insertActionAfter(ExtActionPrototype* action, EditorWindow::Action afterAction, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void EditorWindow::removeAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(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 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 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(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 + +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 staticActions; + static QHash staticActionGroups; + + Ui::EditorWindow *ui = nullptr; + SqlQueryModel* resultsModel = nullptr; + QHash 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 @@ + + + EditorWindow + + + + 0 + 0 + 502 + 325 + + + + SQL editor + + + + 0 + + + + + + + + 0 + + + + Query + + + + + + Qt::Vertical + + + + Qt::CustomContextMenu + + + + + + 0 + + + + + + + + + + Results + + + + + + + 0 + + + + + QTabWidget::South + + + -1 + + + + + + + + + + + History + + + + + + Qt::Vertical + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + + + true + + + + + + + + + + + + + DataView + QTabWidget +
    dataview.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    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 +#include +#include + +// 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()) + scriptingPlugins[plugin->getLanguage()] = plugin; + + ui->langCombo->addItems(scriptingPlugins.keys()); + + // Syntax highlighting plugins + foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins()) + 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(intValue); +} + +void FunctionsEditor::commit() +{ + int row = getCurrentFunctionRow(); + if (model->isValidRowIndex(row)) + functionDeselected(row); + + QList 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()); +} + +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 +#include + +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 scriptingPlugins; + QHash 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 @@ + + + FunctionsEditor + + + + 0 + 0 + 816 + 621 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 150 + 0 + + + + Filter funtions + + + true + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ScrollPerPixel + + + + + + + + + 4 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Function name: + + + + + + + + + + Implementation language: + + + + + + + + + + Type: + + + + + + + + + + + 4 + 0 + + + + Input arguments + + + + + + Undefined + + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 1 + 0 + + + + Databases + + + + + + Register in all databases + + + + + + + Register in following databases: + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + 0 + + + + + + + + + + + + 0 + 2 + + + + Initialization code: + + + + + + + + + + + 0 + 2 + + + + Function implementation code: + + + + + + + + + + + 0 + 2 + + + + Final step implementation code: + + + + + + + + + + + + + + + + + + + + functionFilterEdit + list + nameEdit + typeCombo + langCombo + undefArgsCheck + argsList + allDatabasesRadio + selDatabasesRadio + databasesList + initCodeEdit + mainCodeEdit + finalCodeEdit + + + + 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 + +#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& 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 FunctionsEditorModel::generateFunctions() const +{ + QList 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> counter; + + int row = 0; + foreach (Function* func, functionList) + { + func->valid &= true; + counter[func->data.name] << row++; + } + + QHashIterator> 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()) + 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 +#include + +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& functions); + void addFunction(FunctionManager::ScriptFunction* function); + void deleteFunction(int row); + QList 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 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 originalFunctionList; + QHash 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 +#include + +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(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 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 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 +#include + +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 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 +#include +#include + +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(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 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 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 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 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 +#include + +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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"); +} + +void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(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()) + { + notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table)); + invalid = true; + return; + } + + createTable = parsedObject.dynamicCast(); + } + 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 sessionValue; + sessionValue["table"] = table; + sessionValue["db"] = db->getName(); + return sessionValue; +} + +bool TableWindow::restoreSession(const QVariant& sessionValue) +{ + QHash 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 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(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 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 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)) + { + 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 + +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 @@ + + + TableWindow + + + + 0 + 0 + 609 + 415 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Structure + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Table name: + + + + + + + + 200 + 16777215 + + + + + + + + WITHOUT ROWID + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + false + + + + true + + + false + + + QAbstractItemView::InternalMove + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + true + + + false + + + QAbstractItemView::InternalMove + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + + + + Data + + + + + + QTabWidget::South + + + -1 + + + + + + + + Constraints + + + + + + true + + + + + + + + Indexes + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + + + + + + Triggers + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + DDL + + + + + + true + + + + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + DataView + QTabWidget +
    dataview.h
    + 1 +
    +
    + + +
    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 +#include +#include +#include + +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 sessionValue; + sessionValue["view"] = view; + sessionValue["db"] = db->getName(); + return sessionValue; +} + +bool ViewWindow::restoreSession(const QVariant& sessionValue) +{ + QHash 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)) + { + 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"); +} + +void ViewWindow::insertAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void ViewWindow::insertActionBefore(ExtActionPrototype* action, ViewWindow::Action beforeAction, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void ViewWindow::insertActionAfter(ExtActionPrototype* action, ViewWindow::Action afterAction, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void ViewWindow::removeAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(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 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()) + { + notifyError(tr("Could not process the %1 view correctly. Unable to open a view window.").arg(view)); + invalid = true; + return; + } + + createView = parsedObject.dynamicCast(); + } + 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(); + 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 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 + +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 @@ + + + ViewWindow + + + + 0 + 0 + 621 + 468 + + + + Form + + + + 0 + + + + + 0 + + + + Query + + + + + + + + + + 0 + + + + + View name: + + + + + + + + + + + + + + + + + Data + + + + + + + + + + Triggers + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + DDL + + + + + + true + + + + + + + + + + + + DataView + QTabWidget +
    dataview.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    -- cgit v1.2.3