From 38a1e5b3c3760794d40b8e2d8cbd1b565ff42714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20V=C4=83n=20Ph=C3=BAc?= Date: Mon, 28 Aug 2023 23:24:42 +0700 Subject: [PATCH 1/4] Try appy Obsserver pattern and manual DI to avoid viewmodels hierarchy (#53) * remove method_v2 * port table to DI * port schemasidebar to DI * port table structure * port view data to DI --- res/gtk/schema-main.blp | 2 - res/gtk/schema-sidebar.blp | 10 +- res/gtk/schema-view.blp | 13 +- res/gtk/table-structure-view.blp | 6 +- res/gtk/view-structure-view.blp | 4 +- res/gtk/window.blp | 7 +- src/application.vala | 49 +++++- src/meson.build | 6 +- src/models/Query.vala | 22 ++- src/models/Relation.vala | 1 + src/services/ConnectionService.vala | 8 + src/services/Container.vala | 30 ++++ src/services/NavigationService.vala | 27 ++++ .../{QueryService.vala => SQLService.vala} | 55 ++----- src/services/SchemaService.vala | 26 ++-- src/ui/Window.vala | 48 ++---- src/ui/connection/ConnectionView.vala | 5 +- src/ui/schema/SchemaSidebar.vala | 57 +++---- src/ui/schema/SchemaView.vala | 31 +--- src/ui/schema/TableColumnInfo.vala | 3 +- src/ui/schema/TableDataView.vala | 4 + src/ui/schema/TableForeignKeyInfo.vala | 2 +- src/ui/schema/TableIndexInfo.vala | 2 +- src/ui/schema/TableStructureView.vala | 32 +++- src/ui/schema/ViewDataView.vala | 4 + src/ui/schema/ViewStructureView.vala | 24 +++ src/utils/Event.vala | 52 +++++++ src/viewmodels/BaseViewModel.vala | 12 +- src/viewmodels/QueryHistoryViewModel.vala | 2 +- src/viewmodels/SchemaViewModel.vala | 27 +++- src/viewmodels/TableDataViewModel.vala | 14 +- src/viewmodels/TableStructureViewModel.vala | 140 ++++++++++++++++-- src/viewmodels/TableViewModel.vala | 63 ++++++-- src/viewmodels/ViewDataViewModel.vala | 20 ++- src/viewmodels/ViewStructureViewModel.vala | 107 +++++++++++-- src/viewmodels/ViewViewModel.vala | 66 +++++++-- 36 files changed, 722 insertions(+), 259 deletions(-) create mode 100644 src/services/ConnectionService.vala create mode 100644 src/services/Container.vala create mode 100644 src/services/NavigationService.vala rename src/services/{QueryService.vala => SQLService.vala} (79%) create mode 100644 src/utils/Event.vala diff --git a/res/gtk/schema-main.blp b/res/gtk/schema-main.blp index cc06438..82cabde 100644 --- a/res/gtk/schema-main.blp +++ b/res/gtk/schema-main.blp @@ -36,8 +36,6 @@ template $PsequelSchemaMain : Gtk.Box { StackPage { name: "table"; child: $PsequelTableStructureView { - // selected-table: bind template.selected-table; - tablestructure-viewmodel: bind template.table-viewmodel as <$PsequelTableViewModel>.tablestructure-viewmodel; }; } diff --git a/res/gtk/schema-sidebar.blp b/res/gtk/schema-sidebar.blp index 9160705..75f798a 100644 --- a/res/gtk/schema-sidebar.blp +++ b/res/gtk/schema-sidebar.blp @@ -20,7 +20,7 @@ template $PsequelSchemaSidebar : Gtk.Box { } DropDown dropdown { hexpand: true; - model: bind template.schemas; + model: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.schemas; } } @@ -96,9 +96,9 @@ template $PsequelSchemaSidebar : Gtk.Box { ListView table_list { styles ["navigation-sidebar"] model: SingleSelection table_selection { - model: FilterListModel { + model: FilterListModel table_model { incremental: true; - model: bind template.tables; + model: bind template.table-viewmodel as <$PsequelTableViewModel>.tables; filter: StringFilter table_filter { }; @@ -169,9 +169,9 @@ template $PsequelSchemaSidebar : Gtk.Box { ListView views_list { styles ["navigation-sidebar"] model: SingleSelection view_selection { - model: FilterListModel { + model: FilterListModel view_model { incremental: true; - model: bind template.views; + model: bind template.view-viewmodel as <$PsequelViewViewModel>.views; filter: StringFilter view_filter { }; diff --git a/res/gtk/schema-view.blp b/res/gtk/schema-view.blp index e11b66e..f90d5f2 100644 --- a/res/gtk/schema-view.blp +++ b/res/gtk/schema-view.blp @@ -13,18 +13,7 @@ template $PsequelSchemaView : Adw.Bin { shrink-start-child: false; $PsequelSchemaSidebar sidebar { - width-request: 320; - - schemas: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.schemas; - // template->viewmodel->table-viewmodel->tables - tables: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.table-viewmodel as <$PsequelTableViewModel>.tables; - // template->viewmodel->view-viewmodel->views - views: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.view-viewmodel as <$PsequelViewViewModel>.views; - request-load-schema => $request_load_schema (); - table-selected-changed => $table_selected_changed (); - view-selected-changed => $view_selected_changed (); - request-logout => $request_logout_cb (); } $PsequelSchemaMain { @@ -34,7 +23,7 @@ template $PsequelSchemaView : Adw.Bin { menu: primary_menu; view-mode: bind sidebar.view-mode; // selected-table: bind sidebar.selected-table; - selected-view: bind sidebar.selected-view; + // selected-view: bind sidebar.selected-view; table-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.table-viewmodel; view-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.view-viewmodel; query-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.query-viewmodel; diff --git a/res/gtk/table-structure-view.blp b/res/gtk/table-structure-view.blp index e6bba93..1d4411e 100644 --- a/res/gtk/table-structure-view.blp +++ b/res/gtk/table-structure-view.blp @@ -19,7 +19,7 @@ template $PsequelTableStructureView : Gtk.Box { } $PsequelTableColInfo columns { - columns: bind template.tablestructure-viewmodel as <$PsequelTableStructureViewModel>.columns; + columns: bind template.columns; } @@ -30,7 +30,7 @@ template $PsequelTableStructureView : Gtk.Box { } $PsequelTableIndexInfo indexes { - indexes: bind template.tablestructure-viewmodel as <$PsequelTableStructureViewModel>.indexes; + indexes: bind template.indexes; } Label { @@ -40,6 +40,6 @@ template $PsequelTableStructureView : Gtk.Box { } $PsequelTableFKInfo foreign_keys { - fks: bind template.tablestructure-viewmodel as <$PsequelTableStructureViewModel>.foreign_keys; + fks: bind template.fks; } } diff --git a/res/gtk/view-structure-view.blp b/res/gtk/view-structure-view.blp index 88ae84c..ae1dd18 100644 --- a/res/gtk/view-structure-view.blp +++ b/res/gtk/view-structure-view.blp @@ -18,7 +18,7 @@ template $PsequelViewStructureView : Box { } $PsequelTableColInfo columns { - columns: bind template.viewstructure-viewmodel as <$PsequelViewStructureViewModel>.columns; + columns: bind template.columns; } @@ -29,6 +29,6 @@ template $PsequelViewStructureView : Box { } $PsequelTableIndexInfo indexes { - indexes: bind template.viewstructure-viewmodel as <$PsequelViewStructureViewModel>.indexes; + indexes: bind template.indexes; } } \ No newline at end of file diff --git a/res/gtk/window.blp b/res/gtk/window.blp index 5592e24..10f68d1 100644 --- a/res/gtk/window.blp +++ b/res/gtk/window.blp @@ -13,12 +13,11 @@ template $PsequelWindow : Adw.ApplicationWindow { Adw.ToastOverlay overlay { Stack stack { transition-type: slide_up_down; + visible-child-name: bind template.navigation as <$PsequelNavigationService>.current-view; StackPage { name: "connection-view"; child: $PsequelConnectionView { - viewmodel: bind template.connection-viewmodel; - request-database => $on_connect_db (); }; } @@ -26,8 +25,8 @@ template $PsequelWindow : Adw.ApplicationWindow { StackPage query-view { name: "query-view"; child: $PsequelSchemaView { - schema-viewmodel: bind template.schema-viewmodel; - request_logout => $on_request_logout (); + // schema-viewmodel: bind template.schema-viewmodel; + // request_logout => $on_request_logout (); }; } } diff --git a/src/application.vala b/src/application.vala index c062d3e..cdebfb7 100644 --- a/src/application.vala +++ b/src/application.vala @@ -172,17 +172,56 @@ namespace Psequel { * This result to another event to notify window is ready and widget should setup signals */ private Window new_window () { + + // give temp access because window is not created yet + Window.temp = create_viewmodels (); + var window = new Window (this, Window.temp); + // Window.temp = null; + return window; + } + + private Container create_viewmodels () { var sql_service = new SQLService (Application.background); var repository = new ConnectionRepository (Application.settings); + + // connection and schemas var conn_vm = new ConnectionViewModel (repository); var sche_vm = new SchemaViewModel (sql_service); - - - var window = new Window (this, conn_vm, sche_vm); - - return window; + var table_vm = new TableViewModel (sql_service); + var view_vm = new ViewViewModel (sql_service); + var table_structure_vm = new TableStructureViewModel (sql_service); + var view_structure_vm = new ViewStructureViewModel (sql_service); + var table_data_vm = new TableDataViewModel (sql_service); + var view_data_vm = new ViewDataViewModel (sql_service); + + var navigation = new NavigationService (); + + var container = new Container (); + container.register (sql_service); + container.register (conn_vm); + container.register (sche_vm); + container.register (table_vm); + container.register (view_vm); + container.register (table_structure_vm); + container.register (view_structure_vm); + container.register (table_data_vm); + container.register (view_data_vm); + container.register (navigation); + + sche_vm.subcribe (Event.SCHEMA_CHANGED, table_vm); + sche_vm.subcribe (Event.SCHEMA_CHANGED, view_vm); + sche_vm.subcribe (Event.SCHEMA_CHANGED, table_structure_vm); + sche_vm.subcribe (Event.SCHEMA_CHANGED, view_structure_vm); + + table_vm.subcribe (Event.SELECTED_TABLE_CHANGED, table_structure_vm); + table_vm.subcribe (Event.SELECTED_TABLE_CHANGED, table_data_vm); + view_vm.subcribe (Event.SELECTED_VIEW_CHANGED, view_structure_vm); + view_vm.subcribe (Event.SELECTED_VIEW_CHANGED, view_data_vm); + + + return container; } } } diff --git a/src/meson.build b/src/meson.build index f840edb..123a0f0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -33,7 +33,10 @@ psequel_sources = [ 'models/Query.vala', # 'models/utils.vala', - 'services/QueryService.vala', + 'services/SQLService.vala', + 'services/NavigationService.vala', + 'services/ConnectionService.vala', + 'services/Container.vala', 'services/ResourceManager.vala', 'services/SchemaService.vala', 'services/SQLCompletionProvider.vala', @@ -56,6 +59,7 @@ psequel_sources = [ 'utils/ObservableList.vala', 'utils/ValueConverter.vala', + 'utils/Event.vala', 'utils/helpers.vala', 'utils/logging.vala', 'utils/errors.vala', diff --git a/src/models/Query.vala b/src/models/Query.vala index e01a4d5..b2386ef 100644 --- a/src/models/Query.vala +++ b/src/models/Query.vala @@ -1,8 +1,26 @@ namespace Psequel { public class Query : Object, Json.Serializable { - public string sql { get; construct; } + public string sql { get; private set; } + public Variant[] params {get; private set;} public Query (string sql) { - Object (sql: sql); + base(); + this.sql = sql; + } + + public Query.with_params (string sql, Variant[] params) { + this(sql); + this.params = params; + } + + public void set_limit (int limit) { + if (!is_select ()) { + return; + } + sql += @" LIMIT $limit"; + } + + private inline bool is_select () { + return sql.up (6) == "SELECT"; } public Query clone () { diff --git a/src/models/Relation.vala b/src/models/Relation.vala index 34d101e..5b2e5e4 100644 --- a/src/models/Relation.vala +++ b/src/models/Relation.vala @@ -117,6 +117,7 @@ namespace Psequel { return data.nth_data ((uint) index); } + public class Iterator { private Relation relation; diff --git a/src/services/ConnectionService.vala b/src/services/ConnectionService.vala new file mode 100644 index 0000000..e41f225 --- /dev/null +++ b/src/services/ConnectionService.vala @@ -0,0 +1,8 @@ +namespace Psequel { + public class ConnectionService : Object { + + public ConnectionService() { + + } + } +} \ No newline at end of file diff --git a/src/services/Container.vala b/src/services/Container.vala new file mode 100644 index 0000000..22854a2 --- /dev/null +++ b/src/services/Container.vala @@ -0,0 +1,30 @@ +namespace Psequel { + public class Container : Object { + + private HashTable dependencies; + + + /** Manual dependency injection map */ + public Container() { + Object (); + dependencies = new HashTable (direct_hash, direct_equal); + } + + public void register (Object obj) { + var not_found = dependencies.replace (obj.get_type (), obj); + + if (!not_found) { + warning ("Register type is already in the map"); + } + } + + public Object find_type (GLib.Type type) { + if (!dependencies.contains (type)) { + warning ("Type %s not found in the map", type.name ()); + assert_not_reached (); + } + + return dependencies.lookup (type); + } + } +} \ No newline at end of file diff --git a/src/services/NavigationService.vala b/src/services/NavigationService.vala new file mode 100644 index 0000000..ee7e899 --- /dev/null +++ b/src/services/NavigationService.vala @@ -0,0 +1,27 @@ +namespace Psequel { + public class NavigationService : Object { + + public const string CONNECTION_VIEW = "connection-view"; + public const string QUERY_VIEW = "query-view"; + public const string[] VIEW_NAMES = {CONNECTION_VIEW, QUERY_VIEW}; + + public string current_view { get; set; default = CONNECTION_VIEW;} + + public NavigationService() { + } + + public void navigate (string view_name) { + if (view_name == current_view) { + return; + } + + for (int i = 0; i < VIEW_NAMES.length; i++) { + if (VIEW_NAMES[i] == view_name) { + debug ("Navigating to " + view_name); + current_view = view_name; + return; + } + } + } + } +} \ No newline at end of file diff --git a/src/services/QueryService.vala b/src/services/SQLService.vala similarity index 79% rename from src/services/QueryService.vala rename to src/services/SQLService.vala index acbf7d5..74b6ead 100644 --- a/src/services/QueryService.vala +++ b/src/services/SQLService.vala @@ -17,31 +17,14 @@ namespace Psequel { } /** Select info from a table. */ - public async Relation select_v2 (BaseTable table, int page) throws PsequelError { + public async Relation select (BaseTable table, int page) throws PsequelError { + string schema_name = active_db.escape_identifier (table.schema.name); string escape_tbname = active_db.escape_identifier (table.name); int offset = page * query_limit; - string stmt = @"SELECT * FROM $escape_tbname LIMIT $query_limit OFFSET $offset"; - return yield exec_query (stmt); - } - - public async Relation select (string schema, string table_name, int offset = 0, int limit = 500, string where_clause = "") throws PsequelError { - - string escape_tbname = active_db.escape_identifier (table_name); - - string stmt = @"SELECT * FROM $escape_tbname $where_clause LIMIT $limit OFFSET $offset"; - return yield exec_query (stmt); - } - - /** Get database version. */ - public async string db_version () throws PsequelError { - - string stmt = "SELECT version ();"; - var table = yield exec_query (stmt); - - string version = table[0][0]; - - return version; + string stmt = @"SELECT * FROM $schema_name.$escape_tbname LIMIT $query_limit OFFSET $offset"; + var query = new Query (stmt); + return yield exec_query (query); } /** Make a connection to database and active connection. */ @@ -68,12 +51,11 @@ namespace Psequel { } } - public async Relation exec_query_v2 (Query query) throws PsequelError { + public async Relation exec_query (Query query) throws PsequelError { - var limit_query = add_limit (query); int64 begin = GLib.get_real_time (); - var result = yield exec_query_internal (limit_query.sql); + var result = yield exec_query_internal (query.sql); check_query_status (result); @@ -87,27 +69,10 @@ namespace Psequel { return new Relation ((owned) res); } - public async Relation exec_query (string query, out int64 exec_us = null) throws PsequelError { - - int64 begin = GLib.get_real_time (); - var result = yield exec_query_internal (query); - - // check query status - check_query_status (result); - - int64 end = GLib.get_real_time (); - exec_us = (end - begin); - - var table = new Relation.with_fetch_time ((owned) result, exec_us); - - return table; - } - - public async Relation exec_query_params (string query, Variant[] params) throws PsequelError { - - - var result = yield exec_query_params_internal (query, params); + public async Relation exec_query_params (Query query) throws PsequelError { + assert (query.params != null); + var result = yield exec_query_params_internal (query.sql, query.params); // check query status check_query_status (result); diff --git a/src/services/SchemaService.vala b/src/services/SchemaService.vala index 0077ee8..d85885a 100644 --- a/src/services/SchemaService.vala +++ b/src/services/SchemaService.vala @@ -39,9 +39,9 @@ namespace Psequel { public const string SCHEMA_LIST_SQL = """ SELECT schema_name - FROM information_schema.schemata - WHERE schema_name NOT LIKE 'pg_%' AND schema_name NOT LIKE 'information_schema'; + FROM information_schema.schemata; """; + // WHERE schema_name NOT LIKE 'pg_%' AND schema_name NOT LIKE 'information_schema' private SQLService sql_service; @@ -51,7 +51,9 @@ namespace Psequel { /* Get the schema list as string */ public async string[] schema_list () throws PsequelError { - var relation = yield sql_service.exec_query (SCHEMA_LIST_SQL); + + var query = new Query (SCHEMA_LIST_SQL); + var relation = yield sql_service.exec_query (query); var _schema_list = new string[relation.rows]; @@ -70,7 +72,8 @@ namespace Psequel { public async List get_schemas () { var list = new List (); try { - var relation = yield sql_service.exec_query (SCHEMA_LIST_SQL); + var query = new Query (SCHEMA_LIST_SQL); + var relation = yield sql_service.exec_query (query); for (int i = 0; i < relation.rows; i++) { var s = new Schema (relation[i][0]); @@ -172,7 +175,8 @@ namespace Psequel { var list = new List (); try { - var relation = yield sql_service.exec_query_params (TB_SQL, { new Variant.string (schema.name) }); + var query = new Query.with_params (TB_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); foreach (var row in relation) { list.append (row[0]); @@ -188,7 +192,8 @@ namespace Psequel { var list = new List (); try { - var relation = yield sql_service.exec_query_params (VIEW_SQL, { new Variant.string (schema.name) }); + var query = new Query.with_params (VIEW_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); foreach (var row in relation) { list.append (row[0]); @@ -205,7 +210,8 @@ namespace Psequel { var list = new List (); try { - var relation = yield sql_service.exec_query_params (COLUMN_SQL, { new Variant.string (schema.name) }); + var query = new Query.with_params (COLUMN_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); foreach (var row in relation) { var col = new Column (); @@ -230,7 +236,8 @@ namespace Psequel { var list = new List (); try { - var relation = yield sql_service.exec_query_params (INDEX_SQL, { new Variant.string (schema.name) }); + var query = new Query.with_params (INDEX_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); foreach (var row in relation) { var index = new Index (); @@ -254,7 +261,8 @@ namespace Psequel { var list = new List (); try { - var relation = yield sql_service.exec_query_params (FK_SQL, { new Variant.string (schema.name) }); + var query = new Query.with_params (FK_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); foreach (var row in relation) { var fk = new ForeignKey (); diff --git a/src/ui/Window.vala b/src/ui/Window.vala index a976988..5fd26c4 100644 --- a/src/ui/Window.vala +++ b/src/ui/Window.vala @@ -25,6 +25,9 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/window.ui")] public class Window : Adw.ApplicationWindow { + public static Container? temp; + public Container containter {get; construct;} + const ActionEntry[] ACTIONS = { { "import", import_connection }, { "export", export_connection }, @@ -32,50 +35,29 @@ namespace Psequel { { "run-query", run_query }, }; - + public NavigationService navigation { get; private set; } public ConnectionViewModel connection_viewmodel { get; construct; } - public QueryViewModel query_viewmodel { get; construct; } - public QueryViewModel query_history_viewmodel { get; construct; } public SchemaViewModel schema_viewmodel { get; construct; } - public Window (Application app, - ConnectionViewModel conn_vm, - SchemaViewModel schema_vm) { + public Window (Application app, Container container) { Object ( application: app, - connection_viewmodel: conn_vm, - schema_viewmodel: schema_vm + containter: container ); } construct { + this.navigation = containter.find_type (typeof (NavigationService)) as NavigationService; + this.connection_viewmodel = containter.find_type (typeof (ConnectionViewModel)) as ConnectionViewModel; + this.schema_viewmodel = containter.find_type (typeof (SchemaViewModel)) as SchemaViewModel; + debug ("[CONTRUCT] %s", this.name); - Application.settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT); Application.settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT); - - navigate_to (BaseViewModel.CONNECTION_VIEW); - connection_viewmodel.navigate_to.connect (navigate_to); - this.add_action_entries (ACTIONS, this); } - /** - * Navigate to the stack view. - */ - public void navigate_to (string view_name) { - - var child = stack.get_child_by_name (view_name); - - if (child == null) { - warning ("No such view: %s", view_name); - } else { - debug ("navigate_to %s", view_name); - stack.visible_child = child; - } - } - public void add_toast (Adw.Toast toast) { overlay.add_toast (toast); } @@ -87,7 +69,7 @@ namespace Psequel { try { yield schema_viewmodel.connect_db (conn); - navigate_to (BaseViewModel.QUERY_VIEW); + navigation.navigate (NavigationService.QUERY_VIEW); } catch (PsequelError err) { create_dialog ("Connection Error", err.message).present (); } @@ -95,11 +77,6 @@ namespace Psequel { connection_viewmodel.is_connectting = false; } - [GtkCallback] - public void on_request_logout () { - navigate_to (BaseViewModel.CONNECTION_VIEW); - } - // Actions: public void run_query () { if (schema_viewmodel.query_viewmodel == null) { @@ -201,9 +178,6 @@ namespace Psequel { } } - [GtkChild] - private unowned Gtk.Stack stack; - [GtkChild] private unowned Adw.ToastOverlay overlay; } diff --git a/src/ui/connection/ConnectionView.vala b/src/ui/connection/ConnectionView.vala index 04dd632..26c67dc 100644 --- a/src/ui/connection/ConnectionView.vala +++ b/src/ui/connection/ConnectionView.vala @@ -17,8 +17,11 @@ namespace Psequel { // Connect event. construct { debug ("[CONTRUCT] %s", this.name); - setup_paned (paned); + + debug ("%s", Window.temp.get_type ().name ()); + var container = Window.temp as Psequel.Container; + viewmodel = container.find_type (typeof (ConnectionViewModel)) as ConnectionViewModel; } [GtkCallback] diff --git a/src/ui/schema/SchemaSidebar.vala b/src/ui/schema/SchemaSidebar.vala index 77d3d8f..24859ac 100644 --- a/src/ui/schema/SchemaSidebar.vala +++ b/src/ui/schema/SchemaSidebar.vala @@ -2,56 +2,37 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/schema-sidebar.ui")] public class SchemaSidebar : Gtk.Box { - public ObservableList schemas { get; set; } - public Schema? current_schema { get; set; } - public ObservableList tables {get; set;} - public Table? selected_table {get; set;} - public ObservableList views {get; set;} - public View? selected_view {get; set;} + public NavigationService navigation_service { get; set; } + public SchemaViewModel schema_viewmodel { get; set; } + public TableViewModel table_viewmodel {get; set;} + public ViewViewModel view_viewmodel {get; set;} public string view_mode {get; set;} - public signal void request_load_schema (Schema current_schema); - public signal void request_logout (); - public signal void table_selected_changed (Table table); - public signal void view_selected_changed (View view); - public SchemaSidebar () { Object (); } construct { - - this.notify["selected-table"].connect (() => { - debug ("selected table changed"); - table_selected_changed (selected_table); - }); - - this.notify["selected-view"].connect (() => { - debug ("selected view changed"); - view_selected_changed (selected_view); - }); - - this.notify["current-schema"].connect (() => { - debug ("current schema changed"); - request_load_schema (current_schema); - }); + this.table_viewmodel = (TableViewModel)Window.temp.find_type (typeof (TableViewModel)); + this.view_viewmodel = (ViewViewModel)Window.temp.find_type (typeof (ViewViewModel)); + this.schema_viewmodel = (SchemaViewModel)Window.temp.find_type (typeof (SchemaViewModel)); + this.navigation_service = (NavigationService)Window.temp.find_type (typeof (NavigationService)); sql_views.bind_property ("visible-child-name", this, "view-mode", DEFAULT); dropdown.notify["selected"].connect (() => { - debug ("selected schema changed"); - current_schema = (Schema)schemas.get_item (dropdown.selected); + schema_viewmodel.select_index ((int)dropdown.selected); }); table_selection.notify["selected"].connect (() => { - debug ("selected table changed"); - selected_table = (Table)tables.get_item (table_selection.selected); + var table = table_model.get_item ((int)table_selection.selected); + table_viewmodel.select_table ((Table)table); }); view_selection.notify["selected"].connect (() => { - debug ("selected view changed"); - selected_view = (View)views.get_item (view_selection.selected); + var view = view_model.get_item ((int)view_selection.selected); + view_viewmodel.select_view ((View)view); }); dropdown.expression = new Gtk.PropertyExpression (typeof (Schema), null, "name"); @@ -81,14 +62,12 @@ namespace Psequel { [GtkCallback] private void reload_btn_clicked (Gtk.Button btn) { - debug ("clicked"); - request_load_schema (current_schema); + schema_viewmodel.reload.begin (); } [GtkCallback] private void logout_btn_clicked (Gtk.Button btn) { - debug ("clicked"); - request_logout (); + navigation_service.navigate (NavigationService.CONNECTION_VIEW); } [GtkChild] @@ -103,6 +82,12 @@ namespace Psequel { [GtkChild] private unowned Gtk.StringFilter table_filter; + [GtkChild] + private unowned Gtk.FilterListModel table_model; + + [GtkChild] + private unowned Gtk.FilterListModel view_model; + [GtkChild] private unowned Gtk.StringFilter view_filter; diff --git a/src/ui/schema/SchemaView.vala b/src/ui/schema/SchemaView.vala index f239876..bde62ad 100644 --- a/src/ui/schema/SchemaView.vala +++ b/src/ui/schema/SchemaView.vala @@ -13,35 +13,8 @@ namespace Psequel { construct { setup_paned (paned); - } - - - [GtkCallback] - public void request_load_schema (Schema? schema) { - if (schema == null) { - debug ("schema is null"); - return ; - } - - schema_viewmodel.load_schema.begin (schema); - } - - [GtkCallback] - public void request_logout_cb () { - request_logout (); - } - - [GtkCallback] - public void table_selected_changed (Table table) { - debug ("table selected changed"); - - - schema_viewmodel.table_viewmodel.current_table = table; - } - - [GtkCallback] - public void view_selected_changed (View view) { - schema_viewmodel.view_viewmodel.current_view = view; + var container = Window.temp; + schema_viewmodel = container.find_type (typeof (SchemaViewModel)) as SchemaViewModel; } diff --git a/src/ui/schema/TableColumnInfo.vala b/src/ui/schema/TableColumnInfo.vala index 9c2f274..893ecdc 100644 --- a/src/ui/schema/TableColumnInfo.vala +++ b/src/ui/schema/TableColumnInfo.vala @@ -5,7 +5,8 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/table-cols.ui")] public class TableColInfo : Adw.Bin { - public ObservableList columns {get; set;} + // public ObservableList columns {get; set;} + public GLib.ListModel columns {get; set;} public TableColInfo () { Object (); diff --git a/src/ui/schema/TableDataView.vala b/src/ui/schema/TableDataView.vala index 18229f7..21763d9 100644 --- a/src/ui/schema/TableDataView.vala +++ b/src/ui/schema/TableDataView.vala @@ -9,6 +9,10 @@ namespace Psequel { Object (); } + construct { + tabledata_viewmodel = Window.temp.find_type (typeof (TableDataViewModel)) as TableDataViewModel; + } + [GtkCallback] private async void reload_data () { yield tabledata_viewmodel.reload_data (); diff --git a/src/ui/schema/TableForeignKeyInfo.vala b/src/ui/schema/TableForeignKeyInfo.vala index 44ce0ab..0030fab 100644 --- a/src/ui/schema/TableForeignKeyInfo.vala +++ b/src/ui/schema/TableForeignKeyInfo.vala @@ -5,7 +5,7 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/table-fk.ui")] public class TableFKInfo : Adw.Bin { - public ObservableList fks { get; set; } + public GLib.ListModel fks { get; set; } public TableFKInfo () { diff --git a/src/ui/schema/TableIndexInfo.vala b/src/ui/schema/TableIndexInfo.vala index fa43c00..56f9fc6 100644 --- a/src/ui/schema/TableIndexInfo.vala +++ b/src/ui/schema/TableIndexInfo.vala @@ -5,7 +5,7 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/table-index.ui")] public class TableIndexInfo : Adw.Bin { - public ObservableList indexes {get; set;} + public GLib.ListModel indexes {get; set;} public TableIndexInfo () { diff --git a/src/ui/schema/TableStructureView.vala b/src/ui/schema/TableStructureView.vala index 4f4eb6b..3f370d1 100644 --- a/src/ui/schema/TableStructureView.vala +++ b/src/ui/schema/TableStructureView.vala @@ -2,6 +2,36 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/table-structure-view.ui")] public class TableStructureView : Gtk.Box { - public TableStructureViewModel tablestructure_viewmodel { get; set; } + public TableStructureViewModel tablestructure_viewmodel { get; private set; } + + public Gtk.FilterListModel columns { get; private set; } + public Gtk.FilterListModel indexes { get; private set; } + public Gtk.FilterListModel fks { get; private set; } + public Gtk.StringFilter filter { get; private set; } + + public TableStructureView() { + Object(); + } + + construct { + this.tablestructure_viewmodel = Window.temp.find_type (typeof (TableStructureViewModel)) as TableStructureViewModel; + + this.filter = new Gtk.StringFilter (null); + filter.expression = new Gtk.PropertyExpression (typeof (BaseType), null, "table"); + filter.match_mode = Gtk.StringFilterMatchMode.EXACT; + this.columns = new Gtk.FilterListModel (this.tablestructure_viewmodel.columns, filter); + this.indexes = new Gtk.FilterListModel (this.tablestructure_viewmodel.indexes, filter); + this.fks = new Gtk.FilterListModel (this.tablestructure_viewmodel.foreign_keys, filter); + filter.search = ""; + + tablestructure_viewmodel.notify["selected-table"].connect (() => { + var table = tablestructure_viewmodel.selected_table; + filter.search = table.name; + + debug ("Notify Table: %s", table.name); + debug ("Filter: %s", filter.search); + debug ("columns: %u", columns.get_n_items ()); + }); + } } } \ No newline at end of file diff --git a/src/ui/schema/ViewDataView.vala b/src/ui/schema/ViewDataView.vala index b12fd27..a9327ac 100644 --- a/src/ui/schema/ViewDataView.vala +++ b/src/ui/schema/ViewDataView.vala @@ -9,6 +9,10 @@ namespace Psequel { Object (); } + construct { + viewdata_viewmodel = Window.temp.find_type (typeof (ViewDataViewModel)) as ViewDataViewModel; + } + [GtkCallback] private async void reload_data () { yield viewdata_viewmodel.reload_data (); diff --git a/src/ui/schema/ViewStructureView.vala b/src/ui/schema/ViewStructureView.vala index c8dd291..479a382 100644 --- a/src/ui/schema/ViewStructureView.vala +++ b/src/ui/schema/ViewStructureView.vala @@ -4,9 +4,33 @@ namespace Psequel { public class ViewStructureView : Gtk.Box { public ViewStructureViewModel viewstructure_viewmodel {get; set;} + public Gtk.FilterListModel columns {get; set;} + public Gtk.FilterListModel indexes {get; set;} + public Gtk.StringFilter filter {get; set;} public ViewStructureView () { Object (); } + + construct { + this.viewstructure_viewmodel = Window.temp.find_type (typeof (ViewStructureViewModel)) as ViewStructureViewModel; + + var expresion = new Gtk.PropertyExpression (typeof(BaseType), null, "table"); + this.filter = new Gtk.StringFilter (expresion); + this.filter.match_mode = Gtk.StringFilterMatchMode.EXACT; + + columns = new Gtk.FilterListModel (viewstructure_viewmodel.columns, filter); + indexes = new Gtk.FilterListModel (viewstructure_viewmodel.indexes, filter); + filter.search = ""; + + viewstructure_viewmodel.notify["selected-view"].connect (() => { + var view = viewstructure_viewmodel.selected_view; + filter.search = view.name; + + debug ("Notify View: %s", view.name); + debug ("Filter: %s", filter.search); + debug ("columns: %u", columns.get_n_items ()); + }); + } } } \ No newline at end of file diff --git a/src/utils/Event.vala b/src/utils/Event.vala new file mode 100644 index 0000000..22d58d5 --- /dev/null +++ b/src/utils/Event.vala @@ -0,0 +1,52 @@ +namespace Psequel { + + public class EventManager : Object { + private List targets; + + private class EventTarget { + public string event_type; + public Observer observer; + } + + public new void notify (string event_type, Object data) { + foreach (EventTarget target in targets) { + if (target.event_type == event_type) { + Event event = new Event (event_type, data); + target.observer.update (event); + } + } + } + + public EventManager () { + Object(); + targets = new List(); + } + + + public void subcribe (string event_type, Observer observer) { + EventTarget target = new EventTarget(); + target.event_type = event_type; + target.observer = observer; + + targets.append (target); + } + } + + public class Event : Object { + public const string SCHEMA_CHANGED = "schema-changed"; + public const string SELECTED_TABLE_CHANGED = "selected-table-changed"; + public const string SELECTED_VIEW_CHANGED = "selected-view-changed"; + public string type; + public Object data; + + public Event (string type, Object data) { + base(); + this.type = type; + this.data = data; + } + } + + public interface Observer : Object { + public abstract void update(Event event); + } +} \ No newline at end of file diff --git a/src/viewmodels/BaseViewModel.vala b/src/viewmodels/BaseViewModel.vala index 3250212..1d83dfc 100644 --- a/src/viewmodels/BaseViewModel.vala +++ b/src/viewmodels/BaseViewModel.vala @@ -6,9 +6,19 @@ namespace Psequel { public signal void navigate_to (string view); + protected EventManager event_manager; protected BaseViewModel () { - Object (); + event_manager = new EventManager (); + debug ("BaseViewModel created"); + } + + public void subcribe (string event_type, Observer observer) { + event_manager.subcribe (event_type, observer); + } + + protected void emit_event(string event_type, Object data) { + event_manager.notify (event_type, data); } } } \ No newline at end of file diff --git a/src/viewmodels/QueryHistoryViewModel.vala b/src/viewmodels/QueryHistoryViewModel.vala index 1ac4e06..7882716 100644 --- a/src/viewmodels/QueryHistoryViewModel.vala +++ b/src/viewmodels/QueryHistoryViewModel.vala @@ -69,7 +69,7 @@ namespace Psequel { is_loading = true; try { - current_relation = yield sql_service.exec_query_v2 (query); + current_relation = yield sql_service.exec_query (query); debug ("Rows: %d", current_relation.rows); is_loading = false; diff --git a/src/viewmodels/SchemaViewModel.vala b/src/viewmodels/SchemaViewModel.vala index ad4267b..5ad3a19 100644 --- a/src/viewmodels/SchemaViewModel.vala +++ b/src/viewmodels/SchemaViewModel.vala @@ -14,16 +14,16 @@ namespace Psequel { public SchemaRepository repository; // Services - public SQLService sql_service { get; construct; } + public SQLService sql_service { get; private set; } public SchemaService schema_service { get; private set; } public SchemaViewModel (SQLService service) { - Object (sql_service: service); - + base(); + this.sql_service = service; this.notify["current-schema"].connect (() => { - debug ("Schema changed"); - table_viewmodel = new TableViewModel (current_schema, sql_service); - view_viewmodel = new ViewViewModel (current_schema, sql_service); + this.emit_event (Event.SCHEMA_CHANGED, current_schema); + // table_viewmodel = new TableViewModel (current_schema, sql_service); + // view_viewmodel = new ViewViewModel (current_schema, sql_service); query_viewmodel = new QueryViewModel (current_schema, sql_service); }); } @@ -44,6 +44,13 @@ namespace Psequel { current_schema = schema; } + public async void reload () throws PsequelError { + if (current_schema == null) { + return; + } + yield load_schema (current_schema); + } + public async void list_schemas () throws PsequelError { schema_service = new SchemaService (sql_service); @@ -52,5 +59,13 @@ namespace Psequel { schemas.append_all (unload_schemas); } + + public void select_index (int index) { + if (index < 0 || index >= schemas.size) { + return; + } + debug ("Selecting schema: %s", schemas[index].name); + current_schema = schemas[index]; + } } } \ No newline at end of file diff --git a/src/viewmodels/TableDataViewModel.vala b/src/viewmodels/TableDataViewModel.vala index cdebc7f..4d067f5 100644 --- a/src/viewmodels/TableDataViewModel.vala +++ b/src/viewmodels/TableDataViewModel.vala @@ -1,6 +1,6 @@ namespace Psequel { - public class TableDataViewModel : BaseViewModel { + public class TableDataViewModel : BaseViewModel, Observer { public Table? selected_table { get; set; } // public View? current_view {get; set;} @@ -20,7 +20,7 @@ namespace Psequel { - public TableDataViewModel (Table table, SQLService service) { + public TableDataViewModel (SQLService service) { Object (sql_service: service); this.notify["current-page"].connect (() => { @@ -50,8 +50,14 @@ namespace Psequel { current_page = 0; reload_data.begin (); }); + } - selected_table = table; + public void update (Event event) { + switch (event.type) { + case Event.SELECTED_TABLE_CHANGED: + selected_table = event.data as Table; + break; + } } public async void reload_data () { @@ -72,7 +78,7 @@ namespace Psequel { try { is_loading = true; - current_relation = yield sql_service.select_v2 (table, page); + current_relation = yield sql_service.select (table, page); is_loading = false; debug ("Rows: %d", current_relation.rows); diff --git a/src/viewmodels/TableStructureViewModel.vala b/src/viewmodels/TableStructureViewModel.vala index a21f953..ff2bd3c 100644 --- a/src/viewmodels/TableStructureViewModel.vala +++ b/src/viewmodels/TableStructureViewModel.vala @@ -1,6 +1,7 @@ namespace Psequel { - public class TableStructureViewModel : Object { + public class TableStructureViewModel : Observer, Object { + public SQLService sql_service { get; set; } public Table selected_table { get; set; } public ObservableList columns { get; set; default = new ObservableList (); } @@ -8,26 +9,141 @@ namespace Psequel { public ObservableList foreign_keys { get; set; default = new ObservableList (); } - public TableStructureViewModel (Table table) { + public TableStructureViewModel (SQLService sql_service) { + base (); debug ("TableStructureViewModel created "); - Object (); + this.sql_service = sql_service; - this.notify["selected-table"].connect (() => { - debug ("Selected tabled changed to %s", selected_table?.name); - load_data (); - }); + // selected_table = table; + } - selected_table = table; + public void update (Event event) { + switch (event.type) { + case Event.SCHEMA_CHANGED: + var schema = event.data as Schema; + load_data.begin (schema); + break; + case Event.SELECTED_TABLE_CHANGED: + var table = event.data as Table; + selected_table = table; + break; + default: + break; + } } - private void load_data () { + private async void load_data (Schema schema) { columns.clear (); indexes.clear (); foreign_keys.clear (); - columns.append_all (selected_table.columns); - indexes.append_all (selected_table.indexes); - foreign_keys.append_all (selected_table.foreign_keys); + columns.append_all (yield _get_columns (schema)); + indexes.append_all (yield _get_indexes (schema)); + foreign_keys.append_all (yield _get_fks (schema)); + + debug ("cols: %d indx: %d fks: %d", columns.size, indexes.size, foreign_keys.size); + } + + private async List _get_columns (Schema schema) { + + var list = new List (); + + try { + var query = new Query.with_params (COLUMN_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var row in relation) { + var col = new Column (); + col.schemaname = schema.name; + col.name = row[0]; + col.table = row[1]; + col.column_type = row[2]; + col.nullable = row[3] == "YES" ? true : false; + col.default_val = row[4]; + + list.append (col); + } + } catch (PsequelError err) { + debug (err.message); + } + + return list; + } + + private async List _get_indexes (Schema schema) { + + var list = new List (); + + try { + var query = new Query.with_params (INDEX_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var row in relation) { + var index = new Index (); + index.schemaname = schema.name; + index.name = row[0]; + index.table = row[1]; + index.size = row[2]; + index.indexdef = row[3]; + + list.append (index); + } + } catch (PsequelError err) { + debug (err.message); + } + + return list; + } + + private async List _get_fks (Schema schema) { + + var list = new List (); + + try { + var query = new Query.with_params (FK_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var row in relation) { + var fk = new ForeignKey (); + fk.schemaname = schema.name; + fk.name = row[0]; + fk.table = row[1]; + fk.fk_def = row[2]; + + list.append (fk); + } + } catch (PsequelError err) { + debug (err.message); + } + + return list; } + + public const string COLUMN_SQL = """ + SELECT column_name, table_name, + case + when domain_name is not null then domain_name + when data_type='character varying' THEN 'varchar('||character_maximum_length||')' + when data_type='numeric' THEN 'numeric('||numeric_precision||','||numeric_scale||')' + else data_type + end as data_type, + is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = $1; + """; + public const string INDEX_SQL = """ + SELECT indexname, tablename, pg_size_pretty(pg_relation_size(indexname::regclass)) as size, indexdef + FROM pg_indexes + WHERE schemaname = $1; + """; + public const string FK_SQL = """ + SELECT con.conname, rel.relname, pg_catalog.pg_get_constraintdef(con.oid, true) as condef + FROM pg_catalog.pg_constraint con + INNER JOIN pg_catalog.pg_class rel + ON rel.oid = con.conrelid + INNER JOIN pg_catalog.pg_namespace nsp + ON nsp.oid = connamespace + WHERE con.contype = 'f' AND nsp.nspname = $1; + """; } } \ No newline at end of file diff --git a/src/viewmodels/TableViewModel.vala b/src/viewmodels/TableViewModel.vala index db01aec..935ba57 100644 --- a/src/viewmodels/TableViewModel.vala +++ b/src/viewmodels/TableViewModel.vala @@ -1,23 +1,64 @@ namespace Psequel { - public class TableViewModel : BaseViewModel { + public class TableViewModel : BaseViewModel, Observer { + + public Schema schema { get; set; } public ObservableList
tables { get; set; default = new ObservableList
(); } - public Table? current_table { get; set; } + public Table? selected_table { get; set; } public TableDataViewModel tabledata_viewmodel { get; set; } public TableStructureViewModel tablestructure_viewmodel {get; set;} - public TableViewModel (Schema schema, SQLService sql_service) { - Object (); - tables.append_all (schema.tables); - debug ("table view model created"); - debug ("tables: %d", tables.size); - this.notify["current-table"].connect (() => { - debug ("current table changed to " + current_table?.name); - tablestructure_viewmodel = new TableStructureViewModel (current_table); - tabledata_viewmodel = new TableDataViewModel (current_table, sql_service); + public SQLService sql_service {get; private set;} + + public TableViewModel (SQLService sql_service) { + base (); + this.sql_service = sql_service; + this.notify["selected-table"].connect (() => { + this.emit_event (Event.SELECTED_TABLE_CHANGED, selected_table); }); + } + public void update (Event event) { + if (event.type == Event.SCHEMA_CHANGED) { + schema = (Schema) event.data; + tables.clear (); + load_tables.begin (schema); + } + } + + public void select_table (Table? table) { + if (table == null) { + return; + } + debug ("selecting table %s", table.name); + selected_table = table; + } + public void select_index (int index) { + if (tables[index] == null) { + return; + } + debug ("selecting table %s", tables[index].name); + selected_table = tables[index]; } + + + private async void load_tables (Schema schema) throws PsequelError { + debug ("loading tables"); + var query = new Query.with_params (TABLE_LIST, { schema.name }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var item in relation) { + var table = new Table (schema); + table.name = item[0]; + tables.append (table); + } + + debug ("%d tables loaded", tables.size); + } + + public const string TABLE_LIST = """ + SELECT tablename FROM pg_tables WHERE schemaname=$1; + """; } } \ No newline at end of file diff --git a/src/viewmodels/ViewDataViewModel.vala b/src/viewmodels/ViewDataViewModel.vala index 118bf2e..525c6c8 100644 --- a/src/viewmodels/ViewDataViewModel.vala +++ b/src/viewmodels/ViewDataViewModel.vala @@ -1,5 +1,5 @@ namespace Psequel { - public class ViewDataViewModel : Object { + public class ViewDataViewModel : Object, Observer { public View? selected_view { get; set; } // public View? current_view {get; set;} @@ -15,12 +15,12 @@ namespace Psequel { public Relation current_relation { get; set; } public Relation.Row? selected_row { get; set; } - public SQLService sql_service { get; construct; } + public SQLService sql_service { get; private set; } - - public ViewDataViewModel (View view, SQLService service) { - Object (sql_service: service); + public ViewDataViewModel (SQLService service) { + base (); + this.sql_service = service; this.notify["selected-view"].connect (() => { current_page = 0; @@ -48,8 +48,14 @@ namespace Psequel { has_next_page = true; } }); + } - selected_view = view; + public void update (Event event) { + switch (event.type) { + case Event.SELECTED_VIEW_CHANGED: + selected_view = event.data as View; + break; + } } public async void reload_data () { @@ -70,7 +76,7 @@ namespace Psequel { try { is_loading = true; - current_relation = yield sql_service.select_v2 (view, page); + current_relation = yield sql_service.select (view, page); is_loading = false; debug ("Rows: %d", current_relation.rows); diff --git a/src/viewmodels/ViewStructureViewModel.vala b/src/viewmodels/ViewStructureViewModel.vala index 4ee7367..9f2a19e 100644 --- a/src/viewmodels/ViewStructureViewModel.vala +++ b/src/viewmodels/ViewStructureViewModel.vala @@ -1,26 +1,111 @@ namespace Psequel { - public class ViewStructureViewModel : Object { + public class ViewStructureViewModel : Object, Observer { + + public SQLService sql_service {get; private set;} + public View selected_view { get; set; } public ObservableList columns { get; set; default = new ObservableList (); } public ObservableList indexes { get; set; default = new ObservableList (); } - public ViewStructureViewModel (View view) { - Object (); - - this.notify["selected-view"].connect (() => { - load_data (); - }); + public ViewStructureViewModel (SQLService sql_service) { + base(); + this.sql_service = sql_service; + } - selected_view = view; + public void update (Event event) { + switch (event.type) { + case Event.SCHEMA_CHANGED: + var schema = event.data as Schema; + load_data.begin (schema); + break; + case Event.SELECTED_VIEW_CHANGED: + var view = event.data as View; + selected_view = view; + break; + default: + break; + } } - private void load_data () { + private async void load_data (Schema schema) { columns.clear (); indexes.clear (); - columns.append_all (selected_view.columns); - indexes.append_all (selected_view.indexes); + columns.append_all (yield _get_columns (schema)); + indexes.append_all (yield _get_indexes (schema)); + + debug ("cols: %d indx: %d", columns.size, indexes.size); } + + + private async List _get_columns (Schema schema) { + + var list = new List (); + + try { + var query = new Query.with_params (COLUMN_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var row in relation) { + var col = new Column (); + col.schemaname = schema.name; + col.name = row[0]; + col.table = row[1]; + col.column_type = row[2]; + col.nullable = row[3] == "YES" ? true : false; + col.default_val = row[4]; + + list.append (col); + } + } catch (PsequelError err) { + debug (err.message); + } + + return list; + } + + private async List _get_indexes (Schema schema) { + + var list = new List (); + + try { + var query = new Query.with_params (INDEX_SQL, { new Variant.string (schema.name) }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var row in relation) { + var index = new Index (); + index.schemaname = schema.name; + index.name = row[0]; + index.table = row[1]; + index.size = row[2]; + index.indexdef = row[3]; + + list.append (index); + } + } catch (PsequelError err) { + debug (err.message); + } + + return list; + } + + public const string COLUMN_SQL = """ + SELECT column_name, table_name, + case + when domain_name is not null then domain_name + when data_type='character varying' THEN 'varchar('||character_maximum_length||')' + when data_type='numeric' THEN 'numeric('||numeric_precision||','||numeric_scale||')' + else data_type + end as data_type, + is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = $1; + """; + public const string INDEX_SQL = """ + SELECT indexname, tablename, pg_size_pretty(pg_relation_size(indexname::regclass)) as size, indexdef + FROM pg_indexes + WHERE schemaname = $1; + """; } } \ No newline at end of file diff --git a/src/viewmodels/ViewViewModel.vala b/src/viewmodels/ViewViewModel.vala index 05b2af1..5347cb4 100644 --- a/src/viewmodels/ViewViewModel.vala +++ b/src/viewmodels/ViewViewModel.vala @@ -1,22 +1,70 @@ namespace Psequel { /* View here is database view (virtual tables), not UI */ - public class ViewViewModel : BaseViewModel { + public class ViewViewModel : BaseViewModel, Observer { public ObservableList views { get; set; default = new ObservableList (); } - public View? current_view { get; set; } + public View? selected_view { get; set; } + + + public Schema schema { get; private set; } + public SQLService sql_service { get; private set; } public ViewStructureViewModel viewstructure_viewmodel {get; set;} public ViewDataViewModel viewdata_viewmodel {get; set;} - public ViewViewModel (Schema schema, SQLService service) { - Object (); - views.append_all (schema.views); - - this.notify["current-view"].connect (() => { - viewstructure_viewmodel = new ViewStructureViewModel (current_view); - viewdata_viewmodel = new ViewDataViewModel (current_view, service); + public ViewViewModel (SQLService service) { + base(); + this.sql_service = service; + this.notify["selected-view"].connect (() => { + this.emit_event (Event.SELECTED_VIEW_CHANGED, selected_view); }); } + + public void update (Event event) { + if (event.type == Event.SCHEMA_CHANGED) { + schema = (Schema) event.data; + views.clear (); + load_views.begin (schema); + } + } + + public void select_view (View view) { + if (view == null) { + return; + } + + + debug ("selecting view %s", view.name); + selected_view = view; + } + + public void select_index (int index) { + if (index < 0 || index >= views.size) { + return; + } + + debug ("selecting view %s", views[index].name); + selected_view = views[index]; + } + + + private async void load_views (Schema schema) throws PsequelError { + debug ("loading views"); + var query = new Query.with_params (VIEW_LIST, { schema.name }); + var relation = yield sql_service.exec_query_params (query); + + foreach (var item in relation) { + var view = new View (schema); + view.name = item[0]; + views.append (view); + } + + debug ("%d views loaded", views.size); + } + + public const string VIEW_LIST = """ + SELECT table_name FROM INFORMATION_SCHEMA.views WHERE table_schema = $1; + """; } } \ No newline at end of file From 7b1870cc81caa310034b97957bf87d9d06dff821 Mon Sep 17 00:00:00 2001 From: ppvan Date: Tue, 29 Aug 2023 18:23:09 +0700 Subject: [PATCH 2/4] remove all parrent-child viewmodels --- res/gtk/schema-main.blp | 2 -- res/gtk/schema-view.blp | 5 --- res/gtk/window.blp | 1 - src/application.vala | 24 ++++++++++----- src/services/SQLCompletionProvider.vala | 39 +++++++++++++++--------- src/ui/Window.vala | 25 +++------------ src/ui/connection/ConnectionSidebar.vala | 5 ++- src/ui/connection/ConnectionView.vala | 11 ++----- src/ui/schema/QueryEditor.vala | 8 ++--- src/utils/Event.vala | 1 + src/viewmodels/ConnectionViewModel.vala | 31 ++++++++++++++----- src/viewmodels/QueryViewModel.vala | 5 ++- src/viewmodels/SchemaViewModel.vala | 28 ++++++----------- 13 files changed, 90 insertions(+), 95 deletions(-) diff --git a/res/gtk/schema-main.blp b/res/gtk/schema-main.blp index 82cabde..8baa97e 100644 --- a/res/gtk/schema-main.blp +++ b/res/gtk/schema-main.blp @@ -80,8 +80,6 @@ template $PsequelSchemaMain : Gtk.Box { icon-name: "terminal-symbolic"; title: "Query"; child: $PsequelQueryEditor { - query-viewmodel: bind template.query-viewmodel; - query-history-viewmodel: bind template.query-viewmodel as <$PsequelQueryViewModel>.query-history-viewmodel; }; } } diff --git a/res/gtk/schema-view.blp b/res/gtk/schema-view.blp index f90d5f2..e41073b 100644 --- a/res/gtk/schema-view.blp +++ b/res/gtk/schema-view.blp @@ -22,11 +22,6 @@ template $PsequelSchemaView : Adw.Bin { menu: primary_menu; view-mode: bind sidebar.view-mode; - // selected-table: bind sidebar.selected-table; - // selected-view: bind sidebar.selected-view; - table-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.table-viewmodel; - view-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.view-viewmodel; - query-viewmodel: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.query-viewmodel; } } } diff --git a/res/gtk/window.blp b/res/gtk/window.blp index 10f68d1..6cfca66 100644 --- a/res/gtk/window.blp +++ b/res/gtk/window.blp @@ -18,7 +18,6 @@ template $PsequelWindow : Adw.ApplicationWindow { StackPage { name: "connection-view"; child: $PsequelConnectionView { - request-database => $on_connect_db (); }; } diff --git a/src/application.vala b/src/application.vala index cdebfb7..150733c 100644 --- a/src/application.vala +++ b/src/application.vala @@ -182,12 +182,20 @@ namespace Psequel { } private Container create_viewmodels () { + var container = new Container (); var sql_service = new SQLService (Application.background); + var schema_service = new SchemaService (sql_service); var repository = new ConnectionRepository (Application.settings); + var navigation = new NavigationService (); + + container.register (sql_service); + container.register (schema_service); + container.register (repository); + container.register (navigation); // connection and schemas - var conn_vm = new ConnectionViewModel (repository); - var sche_vm = new SchemaViewModel (sql_service); + var conn_vm = new ConnectionViewModel (repository, sql_service, navigation); + var sche_vm = new SchemaViewModel (schema_service); var table_vm = new TableViewModel (sql_service); var view_vm = new ViewViewModel (sql_service); @@ -195,11 +203,9 @@ namespace Psequel { var view_structure_vm = new ViewStructureViewModel (sql_service); var table_data_vm = new TableDataViewModel (sql_service); var view_data_vm = new ViewDataViewModel (sql_service); + var query_history_vm = new QueryHistoryViewModel (sql_service); + var query_vm = new QueryViewModel (query_history_vm); - var navigation = new NavigationService (); - - var container = new Container (); - container.register (sql_service); container.register (conn_vm); container.register (sche_vm); container.register (table_vm); @@ -208,7 +214,10 @@ namespace Psequel { container.register (view_structure_vm); container.register (table_data_vm); container.register (view_data_vm); - container.register (navigation); + container.register (query_history_vm); + container.register (query_vm); + + conn_vm.subcribe (Event.ACTIVE_CONNECTION, sche_vm); sche_vm.subcribe (Event.SCHEMA_CHANGED, table_vm); sche_vm.subcribe (Event.SCHEMA_CHANGED, view_vm); @@ -217,6 +226,7 @@ namespace Psequel { table_vm.subcribe (Event.SELECTED_TABLE_CHANGED, table_structure_vm); table_vm.subcribe (Event.SELECTED_TABLE_CHANGED, table_data_vm); + view_vm.subcribe (Event.SELECTED_VIEW_CHANGED, view_structure_vm); view_vm.subcribe (Event.SELECTED_VIEW_CHANGED, view_data_vm); diff --git a/src/services/SQLCompletionProvider.vala b/src/services/SQLCompletionProvider.vala index fd1d1ff..db02bc9 100644 --- a/src/services/SQLCompletionProvider.vala +++ b/src/services/SQLCompletionProvider.vala @@ -8,11 +8,12 @@ namespace Psequel { private Gtk.FilterListModel model; private Gtk.StringFilter filter; - public QueryViewModel query_viewmodel { get; set; } + private SchemaViewModel schema_viewmodel; public SQLCompletionProvider () { base (); debug ("SQLCompletionProvider"); + this.schema_viewmodel = Window.temp.find_type (typeof (SchemaViewModel)) as SchemaViewModel; static_candidates = new List (); for (int i = 0; i < PGListerals.KEYWORDS.length; i++) { @@ -31,21 +32,19 @@ namespace Psequel { static_candidates.append (new Model (PGListerals.RESERVED[i], "RESERVED")); } - /* - Query viewmodel is not set until the query view is created. - */ - dynamic_candidates = new List (); - this.notify["query-viewmodel"].connect (() => { - dynamic_candidates = new List (); - query_viewmodel.current_schema.tables.foreach ((table) => { - dynamic_candidates.append (new Model (table.name, "TABLE")); - }); - query_viewmodel.current_schema.views.foreach ((view) => { - dynamic_candidates.append (new Model (view.name, "VIEW")); - }); - }); + // this.notify["query-viewmodel"].connect (() => { + + // dynamic_candidates = new List (); + // query_viewmodel.current_schema.tables.foreach ((table) => { + // dynamic_candidates.append (new Model (table.name, "TABLE")); + // }); + + // query_viewmodel.current_schema.views.foreach ((view) => { + // dynamic_candidates.append (new Model (view.name, "VIEW")); + // }); + // }); var expression = new Gtk.PropertyExpression (typeof (Model), null, "value"); filter = new Gtk.StringFilter (expression); @@ -102,6 +101,18 @@ namespace Psequel { public async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) { + /* + Query viewmodel is not set until the query view is created. + */ + dynamic_candidates = new List (); + schema_viewmodel.current_schema.tables.foreach ((table) => { + dynamic_candidates.append (new Model (table.name, "TABLE")); + }); + + schema_viewmodel.current_schema.views.foreach ((view) => { + dynamic_candidates.append (new Model (view.name, "VIEW")); + }); + var candidates = new ObservableList (); candidates.append_all (static_candidates); candidates.append_all (dynamic_candidates); diff --git a/src/ui/Window.vala b/src/ui/Window.vala index 5fd26c4..ce4d2c2 100644 --- a/src/ui/Window.vala +++ b/src/ui/Window.vala @@ -37,7 +37,7 @@ namespace Psequel { public NavigationService navigation { get; private set; } public ConnectionViewModel connection_viewmodel { get; construct; } - public SchemaViewModel schema_viewmodel { get; construct; } + public QueryViewModel query_viewmodel { get; private set; } public Window (Application app, Container container) { @@ -50,8 +50,8 @@ namespace Psequel { construct { this.navigation = containter.find_type (typeof (NavigationService)) as NavigationService; this.connection_viewmodel = containter.find_type (typeof (ConnectionViewModel)) as ConnectionViewModel; - this.schema_viewmodel = containter.find_type (typeof (SchemaViewModel)) as SchemaViewModel; - + this.query_viewmodel = containter.find_type (typeof (QueryViewModel)) as QueryViewModel; + debug ("[CONTRUCT] %s", this.name); Application.settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT); Application.settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT); @@ -62,28 +62,13 @@ namespace Psequel { overlay.add_toast (toast); } - [GtkCallback] - public async void on_connect_db (Connection conn) { - debug ("Window connect"); - connection_viewmodel.is_connectting = true; - try { - yield schema_viewmodel.connect_db (conn); - - navigation.navigate (NavigationService.QUERY_VIEW); - } catch (PsequelError err) { - create_dialog ("Connection Error", err.message).present (); - } - - connection_viewmodel.is_connectting = false; - } - // Actions: public void run_query () { - if (schema_viewmodel.query_viewmodel == null) { + if (query_viewmodel == null) { return; } - schema_viewmodel.query_viewmodel.run_selected_query.begin (); + query_viewmodel.run_selected_query.begin (); } public void import_connection () { diff --git a/src/ui/connection/ConnectionSidebar.vala b/src/ui/connection/ConnectionSidebar.vala index b9899a6..6a2c0ae 100644 --- a/src/ui/connection/ConnectionSidebar.vala +++ b/src/ui/connection/ConnectionSidebar.vala @@ -33,7 +33,6 @@ namespace Psequel { selection_model.bind_property ("selected", this, "selected-connection", DEFAULT | BIDIRECTIONAL, from_selected, to_selected); - } // On add, create new connection and select it. @@ -90,8 +89,8 @@ namespace Psequel { [GtkChild] private unowned Gtk.SingleSelection selection_model; - // [GtkChild] - // private unowned Gtk.ListView listview; + // [GtkChild] + // private unowned Gtk.ListView listview; } [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-row.ui")] diff --git a/src/ui/connection/ConnectionView.vala b/src/ui/connection/ConnectionView.vala index 26c67dc..9c072d0 100644 --- a/src/ui/connection/ConnectionView.vala +++ b/src/ui/connection/ConnectionView.vala @@ -4,11 +4,7 @@ namespace Psequel { [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-view.ui")] public class ConnectionView : Adw.Bin { - - - public ConnectionViewModel viewmodel { get; set; } - - public signal void request_database (Connection conn); + public ConnectionViewModel viewmodel { get; private set; } public ConnectionView (Application app) { Object (); @@ -18,8 +14,6 @@ namespace Psequel { construct { debug ("[CONTRUCT] %s", this.name); setup_paned (paned); - - debug ("%s", Window.temp.get_type ().name ()); var container = Window.temp as Psequel.Container; viewmodel = container.find_type (typeof (ConnectionViewModel)) as ConnectionViewModel; } @@ -36,8 +30,7 @@ namespace Psequel { [GtkCallback] public void active_connection (Connection conn) { - viewmodel.is_connectting = true; - request_database (conn); + viewmodel.active_connection.begin (conn); } [GtkCallback] diff --git a/src/ui/schema/QueryEditor.vala b/src/ui/schema/QueryEditor.vala index e8e0de7..22f1717 100644 --- a/src/ui/schema/QueryEditor.vala +++ b/src/ui/schema/QueryEditor.vala @@ -31,18 +31,16 @@ namespace Psequel { construct { debug ("[CONTRUCT] %s", this.name); - default_setttings (); + this.query_viewmodel = Window.temp.find_type (typeof (QueryViewModel)) as QueryViewModel; + this.query_history_viewmodel = Window.temp.find_type (typeof (QueryHistoryViewModel)) as QueryHistoryViewModel; + default_setttings (); selection_model.bind_property ("selected", this, "selected-query", BindingFlags.BIDIRECTIONAL, from_selected, to_selected); spinner.bind_property ("spinning", run_query_btn, "sensitive", BindingFlags.INVERT_BOOLEAN); buffer.changed.connect (highlight_current_query); buffer.cursor_moved.connect (highlight_current_query); - this.notify["query-viewmodel"].connect (() => { - this.provider.query_viewmodel = query_viewmodel; - }); - create_action_group (); setup_paned (paned); } diff --git a/src/utils/Event.vala b/src/utils/Event.vala index 22d58d5..f55abc4 100644 --- a/src/utils/Event.vala +++ b/src/utils/Event.vala @@ -36,6 +36,7 @@ namespace Psequel { public const string SCHEMA_CHANGED = "schema-changed"; public const string SELECTED_TABLE_CHANGED = "selected-table-changed"; public const string SELECTED_VIEW_CHANGED = "selected-view-changed"; + public const string ACTIVE_CONNECTION = "active-connection"; public string type; public Object data; diff --git a/src/viewmodels/ConnectionViewModel.vala b/src/viewmodels/ConnectionViewModel.vala index 9f421ea..b12703c 100644 --- a/src/viewmodels/ConnectionViewModel.vala +++ b/src/viewmodels/ConnectionViewModel.vala @@ -1,7 +1,9 @@ namespace Psequel { public class ConnectionViewModel : BaseViewModel { uint timeout_id = 0; - public ConnectionRepository repository { get; construct; } + public ConnectionRepository repository { get; private set; } + public SQLService sql_service { get; private set; } + public NavigationService navigation_service { get; private set; } public ObservableList connections { get; private set; default = new ObservableList (); } public Connection? selected_connection { get; set; } @@ -9,24 +11,23 @@ namespace Psequel { /** True when trying to establish a connection util know results. */ public bool is_connectting { get; set; default = false; } + public ConnectionViewModel (ConnectionRepository repository, SQLService sql_service, NavigationService navigation_service) { + base (); + this.repository = repository; + this.sql_service = sql_service; + this.navigation_service = navigation_service; - - public ConnectionViewModel (ConnectionRepository repository) { - Object (repository: repository); - } - - construct { unowned var loaded_conn = repository.get_connections (); connections.extend (loaded_conn); if (connections.empty ()) { new_connection (); } - // selected_connection = (Connection)connections.get_item (2); // Auto save data each 10 secs in case app crash. Timeout.add_seconds (10, () => { repository.save (); + return Source.CONTINUE; }, Priority.LOW); } @@ -62,6 +63,20 @@ namespace Psequel { this.connections.append_all (connections); } + public async void active_connection (Connection connection) { + this.is_connectting = true; + try { + yield sql_service.connect_db (connection); + this.emit_event (Event.ACTIVE_CONNECTION, connection); + this.navigation_service.navigate (NavigationService.QUERY_VIEW); + + } catch (PsequelError err) { + debug ("Error: %s", err.message); + create_dialog ("Connection Error", err.message.dup ()).present (); + } + this.is_connectting = false; + } + public unowned List export_connections () { return repository.get_connections (); } diff --git a/src/viewmodels/QueryViewModel.vala b/src/viewmodels/QueryViewModel.vala index ad52759..ca749e9 100644 --- a/src/viewmodels/QueryViewModel.vala +++ b/src/viewmodels/QueryViewModel.vala @@ -10,9 +10,8 @@ namespace Psequel { public Schema? current_schema { get; construct; } public SQLService sql_service { get; construct; } - public QueryViewModel (Schema? current_schema, SQLService sql_service) { - Object (current_schema: current_schema,sql_service: sql_service); - query_history_viewmodel = new QueryHistoryViewModel (sql_service); + public QueryViewModel (QueryHistoryViewModel query_history_viewmodel) { + Object (query_history_viewmodel: query_history_viewmodel); } public async void run_selected_query () { diff --git a/src/viewmodels/SchemaViewModel.vala b/src/viewmodels/SchemaViewModel.vala index 5ad3a19..7032b5f 100644 --- a/src/viewmodels/SchemaViewModel.vala +++ b/src/viewmodels/SchemaViewModel.vala @@ -1,39 +1,34 @@ namespace Psequel { - public class SchemaViewModel : BaseViewModel { + public class SchemaViewModel : BaseViewModel, Observer { const string DEFAULT = "public"; public ObservableList schemas { get; set; default = new ObservableList (); } public Schema? current_schema { get; set; } - // Child viewmodel - public TableViewModel table_viewmodel { get; set; } - public ViewViewModel view_viewmodel { get; set; } - public QueryViewModel query_viewmodel { get; set; } - public SchemaRepository repository; // Services - public SQLService sql_service { get; private set; } + public SchemaService schema_service { get; private set; } - public SchemaViewModel (SQLService service) { + public SchemaViewModel (SchemaService service) { base(); - this.sql_service = service; + this.schema_service = service; this.notify["current-schema"].connect (() => { this.emit_event (Event.SCHEMA_CHANGED, current_schema); - // table_viewmodel = new TableViewModel (current_schema, sql_service); - // view_viewmodel = new ViewViewModel (current_schema, sql_service); - query_viewmodel = new QueryViewModel (current_schema, sql_service); }); } - public async void connect_db (Connection conn) throws PsequelError { - yield sql_service.connect_db (conn); + public void update (Event event) { + if (event.type == Event.ACTIVE_CONNECTION) { + database_connected.begin (); + } + } + public async void database_connected () throws PsequelError { // auto load schema list. yield list_schemas (); - yield load_schema (schemas.find (s => s.name == DEFAULT)); } @@ -52,9 +47,6 @@ namespace Psequel { } public async void list_schemas () throws PsequelError { - - schema_service = new SchemaService (sql_service); - var unload_schemas = yield schema_service.get_schemas (); schemas.append_all (unload_schemas); From bc90c4885c255d49946b15b7a993db31d04a8def Mon Sep 17 00:00:00 2001 From: ppvan Date: Tue, 29 Aug 2023 19:19:53 +0700 Subject: [PATCH 3/4] add autowire --- src/application.vala | 15 +- src/services/SQLCompletionProvider.vala | 2 +- src/ui/Window.vala | 260 ++++++++++++------------ src/ui/connection/ConnectionView.vala | 3 +- src/ui/schema/QueryEditor.vala | 4 +- src/ui/schema/SchemaSidebar.vala | 8 +- src/ui/schema/SchemaView.vala | 3 +- src/ui/schema/TableDataView.vala | 2 +- src/ui/schema/TableStructureView.vala | 2 +- src/ui/schema/ViewDataView.vala | 2 +- src/ui/schema/ViewStructureView.vala | 2 +- src/utils/helpers.vala | 5 + 12 files changed, 155 insertions(+), 153 deletions(-) diff --git a/src/application.vala b/src/application.vala index 150733c..4cf9f9e 100644 --- a/src/application.vala +++ b/src/application.vala @@ -183,20 +183,16 @@ namespace Psequel { private Container create_viewmodels () { var container = new Container (); + + // services var sql_service = new SQLService (Application.background); var schema_service = new SchemaService (sql_service); var repository = new ConnectionRepository (Application.settings); var navigation = new NavigationService (); - container.register (sql_service); - container.register (schema_service); - container.register (repository); - container.register (navigation); - - // connection and schemas + // viewmodels var conn_vm = new ConnectionViewModel (repository, sql_service, navigation); var sche_vm = new SchemaViewModel (schema_service); - var table_vm = new TableViewModel (sql_service); var view_vm = new ViewViewModel (sql_service); var table_structure_vm = new TableStructureViewModel (sql_service); @@ -206,6 +202,10 @@ namespace Psequel { var query_history_vm = new QueryHistoryViewModel (sql_service); var query_vm = new QueryViewModel (query_history_vm); + container.register (sql_service); + container.register (schema_service); + container.register (repository); + container.register (navigation); container.register (conn_vm); container.register (sche_vm); container.register (table_vm); @@ -217,6 +217,7 @@ namespace Psequel { container.register (query_history_vm); container.register (query_vm); + // events conn_vm.subcribe (Event.ACTIVE_CONNECTION, sche_vm); sche_vm.subcribe (Event.SCHEMA_CHANGED, table_vm); diff --git a/src/services/SQLCompletionProvider.vala b/src/services/SQLCompletionProvider.vala index db02bc9..9de9dd1 100644 --- a/src/services/SQLCompletionProvider.vala +++ b/src/services/SQLCompletionProvider.vala @@ -13,7 +13,7 @@ namespace Psequel { public SQLCompletionProvider () { base (); debug ("SQLCompletionProvider"); - this.schema_viewmodel = Window.temp.find_type (typeof (SchemaViewModel)) as SchemaViewModel; + this.schema_viewmodel = autowire (); static_candidates = new List (); for (int i = 0; i < PGListerals.KEYWORDS.length; i++) { diff --git a/src/ui/Window.vala b/src/ui/Window.vala index db02bc9..68d2311 100644 --- a/src/ui/Window.vala +++ b/src/ui/Window.vala @@ -1,171 +1,169 @@ -namespace Psequel { - public class SQLCompletionProvider : Object, GtkSource.CompletionProvider { - - const string[] KEYWORDS = { "A", "ABORT", "ABS", "ABSENT", "ABSOLUTE", "ACCESS", "ACCORDING", "ACOS", "ACTION", "ADA", "ADD", "ADMIN", "AFTER", "AGGREGATE", "ALL", "ALLOCATE", "ALSO", "ALTER", "ALWAYS", "ANALYSE", "ANALYZE", "AND", "ANY", "ARE", "ARRAY", "ARRAY_AGG", "ARRAY_MAX_CARDINALITY", "AS", "ASC", "ASENSITIVE", "ASIN", "ASSERTION", "ASSIGNMENT", "ASYMMETRIC", "AT", "ATAN", "ATOMIC", "ATTACH", "ATTRIBUTE", "ATTRIBUTES", "AUTHORIZATION", "AVG", "BACKWARD", "BASE64", "BEFORE", "BEGIN", "BEGIN_FRAME", "BEGIN_PARTITION", "BERNOULLI", "BETWEEN", "BIGINT", "BINARY", "BIT", "BIT_LENGTH", "BLOB", "BLOCKED", "BOM", "BOOLEAN", "BOTH", "BREADTH", "BY", "C", "CACHE", "CALL", "CALLED", "CARDINALITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CATALOG_NAME", "CEIL", "CEILING", "CHAIN", "CHAINING", "CHAR", "CHARACTER", "CHARACTERISTICS", "CHARACTERS", "CHARACTER_LENGTH", "CHARACTER_SET_CATALOG", "CHARACTER_SET_NAME", "CHARACTER_SET_SCHEMA", "CHAR_LENGTH", "CHECK", "CHECKPOINT", "CLASS", "CLASSIFIER", "CLASS_ORIGIN", "CLOB", "CLOSE", "CLUSTER", "COALESCE", "COBOL", "COLLATE", "COLLATION", "COLLATION_CATALOG", "COLLATION_NAME", "COLLATION_SCHEMA", "COLLECT", "COLUMN", "COLUMNS", "COLUMN_NAME", "COMMAND_FUNCTION", "COMMAND_FUNCTION_CODE", "COMMENT", "COMMENTS", "COMMIT", "COMMITTED", "COMPRESSION", "CONCURRENTLY", "CONDITION", "CONDITIONAL", "CONDITION_NUMBER", "CONFIGURATION", "CONFLICT", "CONNECT", "CONNECTION", "CONNECTION_NAME", "CONSTRAINT", "CONSTRAINTS", "CONSTRAINT_CATALOG", "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CONSTRUCTOR", "CONTAINS", "CONTENT", "CONTINUE", "CONTROL", "CONVERSION", "CONVERT", "COPY", "CORR", "CORRESPONDING", "COS", "COSH", "COST", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CSV", "CUBE", "CUME_DIST", "CURRENT", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_ROW", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CURSOR_NAME", "CYCLE", "DATA", "DATABASE", "DATALINK", "DATE", "DATETIME_INTERVAL_CODE", "DATETIME_INTERVAL_PRECISION", "DAY", "DB", "DEALLOCATE", "DEC", "DECFLOAT", "DECIMAL", "DECLARE", "DEFAULT", "DEFAULTS", "DEFERRABLE", "DEFERRED", "DEFINE", "DEFINED", "DEFINER", "DEGREE", "DELETE", "DELIMITER", "DELIMITERS", "DENSE_RANK", "DEPENDS", "DEPTH", "DEREF", "DERIVED", "DESC", "DESCRIBE", "DESCRIPTOR", "DETACH", "DETERMINISTIC", "DIAGNOSTICS", "DICTIONARY", "DISABLE", "DISCARD", "DISCONNECT", "DISPATCH", "DISTINCT", "DLNEWCOPY", "DLPREVIOUSCOPY", "DLURLCOMPLETE", "DLURLCOMPLETEONLY", "DLURLCOMPLETEWRITE", "DLURLPATH", "DLURLPATHONLY", "DLURLPATHWRITE", "DLURLSCHEME", "DLURLSERVER", "DLVALUE", "DO", "DOCUMENT", "DOMAIN", "DOUBLE", "DROP", "DYNAMIC", "DYNAMIC_FUNCTION", "DYNAMIC_FUNCTION_CODE", "EACH", "ELEMENT", "ELSE", "EMPTY", "ENABLE", "ENCODING", "ENCRYPTED", "END", "END-EXEC", "END_FRAME", "END_PARTITION", "ENFORCED", "ENUM", "EQUALS", "ERROR", "ESCAPE", "EVENT", "EVERY", "EXCEPT", "EXCEPTION", "EXCLUDE", "EXCLUDING", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXP", "EXPLAIN", "EXPRESSION", "EXTENSION", "EXTERNAL", "EXTRACT", "FALSE", "FAMILY", "FETCH", "FILE", "FILTER", "FINAL", "FINALIZE", "FINISH", "FIRST", "FIRST_VALUE", "FLAG", "FLOAT", "FLOOR", "FOLLOWING", "FOR", "FORCE", "FOREIGN", "FORMAT", "FORTRAN", "FORWARD", "FOUND", "FRAME_ROW", "FREE", "FREEZE", "FROM", "FS", "FULFILL", "FULL", "FUNCTION", "FUNCTIONS", "FUSION", "G", "GENERAL", "GENERATED", "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GRANTED", "GREATEST", "GROUP", "GROUPING", "GROUPS", "HANDLER", "HAVING", "HEADER", "HEX", "HIERARCHY", "HOLD", "HOUR", "ID", "IDENTITY", "IF", "IGNORE", "ILIKE", "IMMEDIATE", "IMMEDIATELY", "IMMUTABLE", "IMPLEMENTATION", "IMPLICIT", "IMPORT", "IN", "INCLUDE", "INCLUDING", "INCREMENT", "INDENT", "INDEX", "INDEXES", "INDICATOR", "INHERIT", "INHERITS", "INITIAL", "INITIALLY", "INLINE", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTANCE", "INSTANTIABLE", "INSTEAD", "INT", "INTEGER", "INTEGRITY", "INTERSECT", "INTERSECTION", "INTERVAL", "INTO", "INVOKER", "IS", "ISNULL", "ISOLATION", "JOIN", "JSON_ARRAY", "JSON_ARRAYAGG", "JSON_EXISTS", "JSON_OBJECT", "JSON_OBJECTAGG", "JSON_QUERY", "JSON_TABLE", "JSON_TABLE_PRIMITIVE", "JSON_VALUE", "K", "KEEP", "KEY", "KEYS", "KEY_MEMBER", "KEY_TYPE", "LABEL", "LAG", "LANGUAGE", "LARGE", "LAST", "LAST_VALUE", "LATERAL", "LEAD", "LEADING", "LEAKPROOF", "LEAST", "LEFT", "LENGTH", "LEVEL", "LIBRARY", "LIKE", "LIKE_REGEX", "LIMIT", "LINK", "LISTAGG", "LISTEN", "LN", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCATOR", "LOCK", "LOCKED", "LOG", "LOG10", "LOGGED", "LOWER", "M", "MAP", "MAPPING", "MATCH", "MATCHED", "MATCHES", "MATCH_NUMBER", "MATCH_RECOGNIZE", "MATERIALIZED", "MAX", "MAXVALUE", "MEASURES", "MEMBER", "MERGE", "MESSAGE_LENGTH", "MESSAGE_OCTET_LENGTH", "MESSAGE_TEXT", "METHOD", "MIN", "MINUTE", "MINVALUE", "MOD", "MODE", "MODIFIES", "MODULE", "MONTH", "MORE", "MOVE", "MULTISET", "MUMPS", "NAME", "NAMES", "NAMESPACE", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NESTED", "NESTING", "NEW", "NEXT", "NFC", "NFD", "NFKC", "NFKD", "NIL", "NO", "NONE", "NORMALIZE", "NORMALIZED", "NOT", "NOTHING", "NOTIFY", "NOTNULL", "NOWAIT", "NTH_VALUE", "NTILE", "NULL", "NULLABLE", "NULLIF", "NULLS", "NULL_ORDERING", "NUMBER", "NUMERIC", "OBJECT", "OCCURRENCE", "OCCURRENCES_REGEX", "OCTETS", "OCTET_LENGTH", "OF", "OFF", "OFFSET", "OIDS", "OLD", "OMIT", "ON", "ONE", "ONLY", "OPEN", "OPERATOR", "OPTION", "OPTIONS", "OR", "ORDER", "ORDERING", "ORDINALITY", "OTHERS", "OUT", "OUTER", "OUTPUT", "OVER", "OVERFLOW", "OVERLAPS", "OVERLAY", "OVERRIDING", "OWNED", "OWNER", "P", "PAD", "PARALLEL", "PARAMETER", "PARAMETER_MODE", "PARAMETER_NAME", "PARAMETER_ORDINAL_POSITION", "PARAMETER_SPECIFIC_CATALOG", "PARAMETER_SPECIFIC_NAME", "PARAMETER_SPECIFIC_SCHEMA", "PARSER", "PARTIAL", "PARTITION", "PASCAL", "PASS", "PASSING", "PASSTHROUGH", "PASSWORD", "PAST", "PATH", "PATTERN", "PER", "PERCENT", "PERCENTILE_CONT", "PERCENTILE_DISC", "PERCENT_RANK", "PERIOD", "PERMISSION", "PERMUTE", "PIPE", "PLACING", "PLAN", "PLANS", "PLI", "POLICY", "PORTION", "POSITION", "POSITION_REGEX", "POWER", "PRECEDES", "PRECEDING", "PRECISION", "PREPARE", "PREPARED", "PRESERVE", "PREV", "PRIMARY", "PRIOR", "PRIVATE", "PRIVILEGES", "PROCEDURAL", "PROCEDURE", "PROCEDURES", "PROGRAM", "PRUNE", "PTF", "PUBLIC", "PUBLICATION", "QUOTE", "QUOTES", "RANGE", "RANK", "READ", "READS", "REAL", "REASSIGN", "RECHECK", "RECOVERY", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REFRESH", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", "REINDEX", "RELATIVE", "RELEASE", "RENAME", "REPEATABLE", "REPLACE", "REPLICA", "REQUIRING", "RESET", "RESPECT", "RESTART", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNED_CARDINALITY", "RETURNED_LENGTH", "RETURNED_OCTET_LENGTH", "RETURNED_SQLSTATE", "RETURNING", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROUTINES", "ROUTINE_CATALOG", "ROUTINE_NAME", "ROUTINE_SCHEMA", "ROW", "ROWS", "ROW_COUNT", "ROW_NUMBER", "RULE", "RUNNING", "SAVEPOINT", "SCALAR", "SCALE", "SCHEMA", "SCHEMAS", "SCHEMA_NAME", "SCOPE", "SCOPE_CATALOG", "SCOPE_NAME", "SCOPE_SCHEMA", "SCROLL", "SEARCH", "SECOND", "SECTION", "SECURITY", "SEEK", "SELECT", "SELECTIVE", "SELF", "SEMANTICS", "SENSITIVE", "SEQUENCE", "SEQUENCES", "SERIALIZABLE", "SERVER", "SERVER_NAME", "SESSION", "SESSION_USER", "SET", "SETOF", "SETS", "SHARE", "SHOW", "SIMILAR", "SIMPLE", "SIN", "SINH", "SIZE", "SKIP", "SMALLINT", "SNAPSHOT", "SOME", "SORT_DIRECTION", "SOURCE", "SPACE", "SPECIFIC", "SPECIFICTYPE", "SPECIFIC_NAME", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQRT", "STABLE", "STANDALONE", "START", "STATE", "STATEMENT", "STATIC", "STATISTICS", "STDDEV_POP", "STDDEV_SAMP", "STDIN", "STDOUT", "STORAGE", "STORED", "STRICT", "STRING", "STRIP", "STRUCTURE", "STYLE", "SUBCLASS_ORIGIN", "SUBMULTISET", "SUBSCRIPTION", "SUBSET", "SUBSTRING", "SUBSTRING_REGEX", "SUCCEEDS", "SUM", "SUPPORT", "SYMMETRIC", "SYSID", "SYSTEM", "SYSTEM_TIME", "SYSTEM_USER", "T", "TABLE", "TABLES", "TABLESAMPLE", "TABLESPACE", "TABLE_NAME", "TAN", "TANH", "TEMP", "TEMPLATE", "TEMPORARY", "TEXT", "THEN", "THROUGH", "TIES", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TOKEN", "TOP_LEVEL_COUNT", "TRAILING", "TRANSACTION", "TRANSACTIONS_COMMITTED", "TRANSACTIONS_ROLLED_BACK", "TRANSACTION_ACTIVE", "TRANSFORM", "TRANSFORMS", "TRANSLATE", "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRIGGER_CATALOG", "TRIGGER_NAME", "TRIGGER_SCHEMA", "TRIM", "TRIM_ARRAY", "TRUE", "TRUNCATE", "TRUSTED", "TYPE", "TYPES", "UESCAPE", "UNBOUNDED", "UNCOMMITTED", "UNCONDITIONAL", "UNDER", "UNENCRYPTED", "UNION", "UNIQUE", "UNKNOWN", "UNLINK", "UNLISTEN", "UNLOGGED", "UNMATCHED", "UNNAMED", "UNNEST", "UNTIL", "UNTYPED", "UPDATE", "UPPER", "URI", "USAGE", "USER", "USER_DEFINEDTYPE_CATALOG", "USER_DEFINED_TYPE_CODE", "USER_DEFINED_TYPE_NAME", "USER_DEFINED_TYPE_SCHEMA", "USING", "UTF16", "UTF32", "UTF8", "VACUUM", "VALID", "VALIDATE", "VALIDATOR", "VALUE", "VALUES", "VALUE_OF", "VARBINARY", "VARCHAR", "VARIADIC", "VARYING", "VAR_POP", "VAR_SAMP", "VERBOSE", "VERSION", "VERSIONING", "VIEW", "VIEWS", "VOLATILE", "WHEN", "WHENEVER", "WHERE", "WHITESPACE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRAPPER", "WRITE", "XML", "XMLAGG", "XMLATTRIBUTES", "XMLBINARY", "XMLCAST", "XMLCOMMENT", "XMLCONCAT", "XMLDECLARATION", "XMLDOCUMENT", "XMLELEMENT", "XMLEXISTS", "XMLFOREST", "XMLITERATE", "XMLNAMESPACES", "XMLPARSE", "XMLPI", "XMLQUERY", "XMLROOT", "XMLSCHEMA", "XMLSERIALIZE", "XMLTABLE", "XMLTEXT", "XMLVALIDATE", "YEAR", "YES", "ZONE" }; +/* window.vala + * + * Copyright 2023 Unknown + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +using GLib; - private List static_candidates; - private List dynamic_candidates; - private Gtk.FilterListModel model; - private Gtk.StringFilter filter; +namespace Psequel { + [GtkTemplate (ui = "/me/ppvan/psequel/gtk/window.ui")] + public class Window : Adw.ApplicationWindow { - private SchemaViewModel schema_viewmodel; - public SQLCompletionProvider () { - base (); - debug ("SQLCompletionProvider"); - this.schema_viewmodel = Window.temp.find_type (typeof (SchemaViewModel)) as SchemaViewModel; + public static Container? temp; + public Container containter { get; construct; } - static_candidates = new List (); - for (int i = 0; i < PGListerals.KEYWORDS.length; i++) { - static_candidates.append (new Model (PGListerals.KEYWORDS[i], "KEYWORD")); - } + const ActionEntry[] ACTIONS = { + { "import", import_connection }, + { "export", export_connection }, - for (int i = 0; i < PGListerals.FUNCTIONS.length; i++) { - static_candidates.append (new Model (PGListerals.FUNCTIONS[i], "FUNCTION")); - } + { "run-query", run_query }, + }; - for (int i = 0; i < PGListerals.DATATYPES.length; i++) { - static_candidates.append (new Model (PGListerals.DATATYPES[i], "DATATYPE")); - } + public NavigationService navigation { get; private set; } + public ConnectionViewModel connection_viewmodel { get; construct; } + public QueryViewModel query_viewmodel { get; private set; } - for (int i = 0; i < PGListerals.RESERVED.length; i++) { - static_candidates.append (new Model (PGListerals.RESERVED[i], "RESERVED")); - } + public Window (Application app, Container container) { + Object ( + application: app, + containter: container + ); + } + construct { + this.navigation = autowire (); + this.connection_viewmodel = autowire (); + this.query_viewmodel = autowire (); - // this.notify["query-viewmodel"].connect (() => { + debug ("[CONTRUCT] %s", this.name); + Application.settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT); + Application.settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT); + this.add_action_entries (ACTIONS, this); + } - // dynamic_candidates = new List (); - // query_viewmodel.current_schema.tables.foreach ((table) => { - // dynamic_candidates.append (new Model (table.name, "TABLE")); - // }); + public void add_toast (Adw.Toast toast) { + overlay.add_toast (toast); + } - // query_viewmodel.current_schema.views.foreach ((view) => { - // dynamic_candidates.append (new Model (view.name, "VIEW")); - // }); - // }); + // Actions: + public void run_query () { + if (query_viewmodel == null) { + return; + } - var expression = new Gtk.PropertyExpression (typeof (Model), null, "value"); - filter = new Gtk.StringFilter (expression); - filter.match_mode = Gtk.StringFilterMatchMode.PREFIX; + query_viewmodel.run_selected_query.begin (); + } - model = new Gtk.FilterListModel (null, filter); + public void import_connection () { + open_file_dialog.begin ("Import connections"); } - public void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal) { - debug ("activate 1"); + public void export_connection () { + save_file_dialog.begin ("Export connections"); + } - var model = (Model) proposal; - var buf = context.get_buffer (); - Gtk.TextIter start, end; + private async void open_file_dialog (string title = "Open File") { + var filter = new Gtk.FileFilter (); + filter.add_pattern ("*.json"); - last_word (buf, out start, out end); + var filters = new ListStore (typeof (Gtk.FileFilter)); + filters.append (filter); - buf.delete_range (start, end); - buf.insert_at_cursor (model.value, model.value.length); - // buf.insert (ref iter, model.value, model.value.length); - } + var window = (Window) get_parrent_window (this); - public void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell) { - - var model = (Model) proposal; - - // cell.text = cell.column.to_string (); - - switch (cell.column) { - // name - case GtkSource.CompletionColumn.TYPED_TEXT: - cell.text = model.value; - break; - - // group - case GtkSource.CompletionColumn.AFTER: - cell.text = model.group; - break; - // case GtkSource.CompletionColumn.DETAILS: - // cell.text = "DETAILS"; - // break; - // case GtkSource.CompletionColumn.COMMENT: - // cell.text = "COMMENT"; - // break; - // case GtkSource.CompletionColumn.ICON: - // cell.text = "ICON"; - // break; - // case GtkSource.CompletionColumn.BEFORE: - // cell.text = "BEFORE"; - // break; - default: break; - } - } + var file_dialog = new Gtk.FileDialog () { + modal = true, + initial_folder = File.new_for_path (Environment.get_home_dir ()), + title = title, + initial_name = "connections", + default_filter = filter, + filters = filters + }; - public async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) { + uint8[] contents; - /* - Query viewmodel is not set until the query view is created. - */ - dynamic_candidates = new List (); - schema_viewmodel.current_schema.tables.foreach ((table) => { - dynamic_candidates.append (new Model (table.name, "TABLE")); - }); - - schema_viewmodel.current_schema.views.foreach ((view) => { - dynamic_candidates.append (new Model (view.name, "VIEW")); - }); + try { + var file = yield file_dialog.open (window, null); - var candidates = new ObservableList (); - candidates.append_all (static_candidates); - candidates.append_all (dynamic_candidates); - model.model = candidates; + yield file.load_contents_async (null, out contents, null); + var json_str = (string) contents; + var conns = ValueConverter.deserialize_connection (json_str); + connection_viewmodel.import_connections (conns); - var word = last_word (context.get_buffer ()); - filter.search = word; - debug ("populate_async: %s", word); - debug ("size: %u", model.get_n_items ()); + var toast = new Adw.Toast (@"Loaded $(conns.length ()) connections") { + timeout = 3, + }; + window.add_toast (toast); + } catch (Error err) { + debug (err.message); - return model; + var toast = new Adw.Toast (err.message) { + timeout = 3, + }; + window.add_toast (toast); + } } - public void refilter (GtkSource.CompletionContext context, GLib.ListModel _model) { + private async void save_file_dialog (string title = "Save to file") { - debug ("refilter"); + var filter = new Gtk.FileFilter (); + filter.add_suffix ("json"); - var word = last_word (context.get_buffer ()); - filter.search = word; - filter.changed (Gtk.FilterChange.MORE_STRICT); - debug ("populate_async: %s", word); - } + var filters = new ListStore (typeof (Gtk.FileFilter)); + filters.append (filter); - public class Model : Object, GtkSource.CompletionProposal { - public string value { get; set; } - public string group { get; set; } + var file_dialog = new Gtk.FileDialog () { + modal = true, + initial_folder = File.new_for_path (Environment.get_home_dir ()), + title = title, + initial_name = "connections", + default_filter = filter, + filters = filters, + }; - public Model (string value, string group) { - this.value = value; - this.group = group; - } - } + unowned var conns = connection_viewmodel.export_connections (); + var content = ValueConverter.serialize_connection (conns); + var bytes = new Bytes.take (content.data); // Move data to byte so it live when out scope + var window = (Window) get_parrent_window (this); - private string last_word (GtkSource.Buffer buf, out Gtk.TextIter start = null, out Gtk.TextIter end = null) { - Gtk.TextIter iter; - buf.get_iter_at_offset (out iter, buf.cursor_position); + try { + var file = yield file_dialog.save (window, null); - start = iter; - end = iter; + yield file.replace_contents_bytes_async (bytes, null, false, FileCreateFlags.NONE, null, null); - start.backward_word_start (); - end.forward_word_end (); + var toast = new Adw.Toast ("Data saved successfully.") { + timeout = 2, + }; + window.add_toast (toast); + } catch (Error err) { + debug (err.message); - return buf.get_text (start, end, false); + var toast = new Adw.Toast (err.message) { + timeout = 3, + }; + window.add_toast (toast); + } } - } - - public class PGListerals { - public const string[] KEYWORDS = { "ADD", "ADD CONSTRAINT", "ALL", "ALTER", "ALTER COLUMN", "ALTER TABLE", "AND", "ANY", "AS", "ASC", "BACKUP DATABASE", "BETWEEN", "CASE", "CHECK", "COLUMN", "CONSTRAINT", "CREATE", "CREATE DATABASE", "CREATE INDEX", "CREATE OR REPLACE VIEW", "CREATE TABLE", "CREATE PROCEDURE", "CREATE UNIQUE INDEX", "CREATE VIEW", "DATABASE", "DEFAULT", "DELETE", "DESC", "DISTINCT", "DROP", "DROP COLUMN", "DROP CONSTRAINT", "DROP DATABASE", "DROP DEFAULT", "DROP INDEX", "DROP TABLE", "DROP VIEW", "EXEC", "EXISTS", "FOREIGN KEY", "FROM", "FULL OUTER JOIN", "GROUP BY", "HAVING", "IN", "INDEX", "INNER JOIN", "INSERT INTO", "INSERT INTO SELECT", "IS NULL", "IS NOT NULL", "JOIN", "LEFT JOIN", "LIKE", "LIMIT", "NOT", "NOT NULL", "OR", "ORDER BY", "OUTER JOIN", "PRIMARY KEY", "PROCEDURE", "RIGHT JOIN", "ROWNUM", "SELECT", "SELECT DISTINCT", "SELECT INTO", "SELECT TOP", "SET", "TABLE", "TOP", "TRUNCATE TABLE", "UNION", "UNION ALL", "UNIQUE", "UPDATE", "VALUES", "VIEW", "WHERE" }; - public const string[] FUNCTIONS = { "ABBREV", "ABS", "AGE", "AREA", "ARRAY_AGG", "ARRAY_APPEND", "ARRAY_CAT", "ARRAY_DIMS", "ARRAY_FILL", "ARRAY_LENGTH", "ARRAY_LOWER", "ARRAY_NDIMS", "ARRAY_POSITION", "ARRAY_POSITIONS", "ARRAY_PREPEND", "ARRAY_REMOVE", "ARRAY_REPLACE", "ARRAY_TO_STRING", "ARRAY_UPPER", "ASCII", "AVG", "BIT_AND", "BIT_LENGTH", "BIT_OR", "BOOL_AND", "BOOL_OR", "BOUND_BOX", "BOX", "BROADCAST", "BTRIM", "CARDINALITY", "CBRT", "CEIL", "CEILING", "CENTER", "CHAR_LENGTH", "CHR", "CIRCLE", "CLOCK_TIMESTAMP", "CONCAT", "CONCAT_WS", "CONVERT", "CONVERT_FROM", "CONVERT_TO", "COUNT", "CUME_DIST", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATE_PART", "DATE_TRUNC", "DECODE", "DEGREES", "DENSE_RANK", "DIAMETER", "DIV", "ENCODE", "ENUM_FIRST", "ENUM_LAST", "ENUM_RANGE", "EVERY", "EXP", "EXTRACT", "FAMILY", "FIRST_VALUE", "FLOOR", "FORMAT", "GET_BIT", "GET_BYTE", "HEIGHT", "HOST", "HOSTMASK", "INET_MERGE", "INET_SAME_FAMILY", "INITCAP", "ISCLOSED", "ISFINITE", "ISOPEN", "JUSTIFY_DAYS", "JUSTIFY_HOURS", "JUSTIFY_INTERVAL", "LAG", "LAST_VALUE", "LEAD", "LEFT", "LENGTH", "LINE", "LN", "LOCALTIME", "LOCALTIMESTAMP", "LOG", "LOG10", "LOWER", "LPAD", "LSEG", "LTRIM", "MAKE_DATE", "MAKE_INTERVAL", "MAKE_TIME", "MAKE_TIMESTAMP", "MAKE_TIMESTAMPTZ", "MASKLEN", "MAX", "MD5", "MIN", "MOD", "NETMASK", "NETWORK", "NOW", "NPOINTS", "NTH_VALUE", "NTILE", "NUM_NONNULLS", "NUM_NULLS", "OCTET_LENGTH", "OVERLAY", "PARSE_IDENT", "PATH", "PCLOSE", "PERCENT_RANK", "PG_CLIENT_ENCODING", "PI", "POINT", "POLYGON", "POPEN", "POSITION", "POWER", "QUOTE_IDENT", "QUOTE_LITERAL", "QUOTE_NULLABLE", "RADIANS", "RADIUS", "RANDOM", "RANK", "REGEXP_MATCH", "REGEXP_MATCHES", "REGEXP_REPLACE", "REGEXP_SPLIT_TO_ARRAY", "REGEXP_SPLIT_TO_TABLE", "REPEAT", "REPLACE", "REVERSE", "RIGHT", "ROUND", "ROW_NUMBER", "RPAD", "RTRIM", "SCALE", "SET_BIT", "SET_BYTE", "SET_MASKLEN", "SHA224", "SHA256", "SHA384", "SHA512", "SIGN", "SPLIT_PART", "SQRT", "STARTS_WITH", "STATEMENT_TIMESTAMP", "STRING_TO_ARRAY", "STRPOS", "SUBSTR", "SUBSTRING", "SUM", "TEXT", "TIMEOFDAY", "TO_ASCII", "TO_CHAR", "TO_DATE", "TO_HEX", "TO_NUMBER", "TO_TIMESTAMP", "TRANSACTION_TIMESTAMP", "TRANSLATE", "TRIM", "TRUNC", "UNNEST", "UPPER", "WIDTH", "WIDTH_BUCKET", "XMLAGG" }; - public const string[] DATATYPES = { "ANY", "ANYARRAY", "ANYELEMENT", "ANYENUM", "ANYNONARRAY", "ANYRANGE", "BIGINT", "BIGSERIAL", "BIT", "BIT VARYING", "BOOL", "BOOLEAN", "BOX", "BYTEA", "CHAR", "CHARACTER", "CHARACTER VARYING", "CIDR", "CIRCLE", "CSTRING", "DATE", "DECIMAL", "DOUBLE PRECISION", "EVENT_TRIGGER", "FDW_HANDLER", "FLOAT4", "FLOAT8", "INET", "INT", "INT2", "INT4", "INT8", "INTEGER", "INTERNAL", "INTERVAL", "JSON", "JSONB", "LANGUAGE_HANDLER", "LINE", "LSEG", "MACADDR", "MACADDR8", "MONEY", "NUMERIC", "OID", "OPAQUE", "PATH", "PG_LSN", "POINT", "POLYGON", "REAL", "RECORD", "REGCLASS", "REGCONFIG", "REGDICTIONARY", "REGNAMESPACE", "REGOPER", "REGOPERATOR", "REGPROC", "REGPROCEDURE", "REGROLE", "REGTYPE", "SERIAL", "SERIAL2", "SERIAL4", "SERIAL8", "SMALLINT", "SMALLSERIAL", "TEXT", "TIME", "TIMESTAMP", "TRIGGER", "TSQUERY", "TSVECTOR", "TXID_SNAPSHOT", "UUID", "VARBIT", "VARCHAR", "VOID", "XML" }; - public const string[] RESERVED = { "ALL", "ANALYSE", "ANALYZE", "AND", "ANY", "ARRAY", "AS", "ASC", "ASYMMETRIC", "BOTH", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "CONSTRAINT", "CREATE", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT", "DEFERRABLE", "DESC", "DISTINCT", "DO", "ELSE", "END", "EXCEPT", "FALSE", "FETCH", "FOR", "FOREIGN", "FROM", "GRANT", "GROUP", "HAVING", "IN", "INITIALLY", "INTERSECT", "INTO", "LATERAL", "LEADING", "LIMIT", "LOCALTIME", "LOCALTIMESTAMP", "NOT", "NULL", "OFFSET", "ON", "ONLY", "OR", "ORDER", "PLACING", "PRIMARY", "REFERENCES", "RETURNING", "SELECT", "SESSION_USER", "SOME", "SYMMETRIC", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION", "UNIQUE", "USER", "USING", "VARIADIC", "WHEN", "WHERE", "WINDOW", "WITH", "AUTHORIZATION", "BINARY", "COLLATION", "CONCURRENTLY", "CROSS", "CURRENT_SCHEMA", "FREEZE", "FULL", "ILIKE", "INNER", "IS", "ISNULL", "JOIN", "LEFT", "LIKE", "NATURAL", "NOTNULL", "OUTER", "OVERLAPS", "RIGHT", "SIMILAR", "TABLESAMPLE", "VERBOSE" }; + [GtkChild] + private unowned Adw.ToastOverlay overlay; } } \ No newline at end of file diff --git a/src/ui/connection/ConnectionView.vala b/src/ui/connection/ConnectionView.vala index 9c072d0..f490c13 100644 --- a/src/ui/connection/ConnectionView.vala +++ b/src/ui/connection/ConnectionView.vala @@ -14,8 +14,7 @@ namespace Psequel { construct { debug ("[CONTRUCT] %s", this.name); setup_paned (paned); - var container = Window.temp as Psequel.Container; - viewmodel = container.find_type (typeof (ConnectionViewModel)) as ConnectionViewModel; + viewmodel = autowire (); } [GtkCallback] diff --git a/src/ui/schema/QueryEditor.vala b/src/ui/schema/QueryEditor.vala index 22f1717..3856a06 100644 --- a/src/ui/schema/QueryEditor.vala +++ b/src/ui/schema/QueryEditor.vala @@ -31,8 +31,8 @@ namespace Psequel { construct { debug ("[CONTRUCT] %s", this.name); - this.query_viewmodel = Window.temp.find_type (typeof (QueryViewModel)) as QueryViewModel; - this.query_history_viewmodel = Window.temp.find_type (typeof (QueryHistoryViewModel)) as QueryHistoryViewModel; + this.query_viewmodel = autowire (); + this.query_history_viewmodel = autowire (); default_setttings (); selection_model.bind_property ("selected", this, "selected-query", BindingFlags.BIDIRECTIONAL, from_selected, to_selected); diff --git a/src/ui/schema/SchemaSidebar.vala b/src/ui/schema/SchemaSidebar.vala index 24859ac..22cb743 100644 --- a/src/ui/schema/SchemaSidebar.vala +++ b/src/ui/schema/SchemaSidebar.vala @@ -16,10 +16,10 @@ namespace Psequel { } construct { - this.table_viewmodel = (TableViewModel)Window.temp.find_type (typeof (TableViewModel)); - this.view_viewmodel = (ViewViewModel)Window.temp.find_type (typeof (ViewViewModel)); - this.schema_viewmodel = (SchemaViewModel)Window.temp.find_type (typeof (SchemaViewModel)); - this.navigation_service = (NavigationService)Window.temp.find_type (typeof (NavigationService)); + this.table_viewmodel = autowire (); + this.view_viewmodel = autowire (); + this.schema_viewmodel = autowire (); + this.navigation_service = autowire (); sql_views.bind_property ("visible-child-name", this, "view-mode", DEFAULT); diff --git a/src/ui/schema/SchemaView.vala b/src/ui/schema/SchemaView.vala index bde62ad..75600b5 100644 --- a/src/ui/schema/SchemaView.vala +++ b/src/ui/schema/SchemaView.vala @@ -13,8 +13,7 @@ namespace Psequel { construct { setup_paned (paned); - var container = Window.temp; - schema_viewmodel = container.find_type (typeof (SchemaViewModel)) as SchemaViewModel; + schema_viewmodel = autowire (); } diff --git a/src/ui/schema/TableDataView.vala b/src/ui/schema/TableDataView.vala index 21763d9..4ebdc88 100644 --- a/src/ui/schema/TableDataView.vala +++ b/src/ui/schema/TableDataView.vala @@ -10,7 +10,7 @@ namespace Psequel { } construct { - tabledata_viewmodel = Window.temp.find_type (typeof (TableDataViewModel)) as TableDataViewModel; + tabledata_viewmodel = autowire (); } [GtkCallback] diff --git a/src/ui/schema/TableStructureView.vala b/src/ui/schema/TableStructureView.vala index 3f370d1..436c52c 100644 --- a/src/ui/schema/TableStructureView.vala +++ b/src/ui/schema/TableStructureView.vala @@ -14,7 +14,7 @@ namespace Psequel { } construct { - this.tablestructure_viewmodel = Window.temp.find_type (typeof (TableStructureViewModel)) as TableStructureViewModel; + this.tablestructure_viewmodel = autowire (); this.filter = new Gtk.StringFilter (null); filter.expression = new Gtk.PropertyExpression (typeof (BaseType), null, "table"); diff --git a/src/ui/schema/ViewDataView.vala b/src/ui/schema/ViewDataView.vala index a9327ac..8e7b68a 100644 --- a/src/ui/schema/ViewDataView.vala +++ b/src/ui/schema/ViewDataView.vala @@ -10,7 +10,7 @@ namespace Psequel { } construct { - viewdata_viewmodel = Window.temp.find_type (typeof (ViewDataViewModel)) as ViewDataViewModel; + viewdata_viewmodel = autowire (); } [GtkCallback] diff --git a/src/ui/schema/ViewStructureView.vala b/src/ui/schema/ViewStructureView.vala index 479a382..71f77c6 100644 --- a/src/ui/schema/ViewStructureView.vala +++ b/src/ui/schema/ViewStructureView.vala @@ -13,7 +13,7 @@ namespace Psequel { } construct { - this.viewstructure_viewmodel = Window.temp.find_type (typeof (ViewStructureViewModel)) as ViewStructureViewModel; + this.viewstructure_viewmodel = autowire (); var expresion = new Gtk.PropertyExpression (typeof(BaseType), null, "table"); this.filter = new Gtk.StringFilter (expresion); diff --git a/src/utils/helpers.vala b/src/utils/helpers.vala index a7246ba..4326d83 100644 --- a/src/utils/helpers.vala +++ b/src/utils/helpers.vala @@ -40,6 +40,11 @@ namespace Psequel { }); } + public T autowire () { + var container = Window.temp; + return container.find_type (typeof (T)) as T; + } + public Adw.MessageDialog create_dialog (string heading, string body) { var window = Application.app.active_window; var dialog = new Adw.MessageDialog (window, heading, body); From 91e456adad54c7c62ad0729595492d158188b046 Mon Sep 17 00:00:00 2001 From: ppvan Date: Tue, 29 Aug 2023 19:23:04 +0700 Subject: [PATCH 4/4] hotfix --- src/utils/helpers.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/helpers.vala b/src/utils/helpers.vala index 4326d83..6232350 100644 --- a/src/utils/helpers.vala +++ b/src/utils/helpers.vala @@ -42,7 +42,7 @@ namespace Psequel { public T autowire () { var container = Window.temp; - return container.find_type (typeof (T)) as T; + return (T)container.find_type (typeof (T)); } public Adw.MessageDialog create_dialog (string heading, string body) {