diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9d926d6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(lldb) Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/src/psequel", + "args": [], + "stopOnEntry": false, + "cwd": "${workspaceFolder}", + "env": { + + }, + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 96a330f..ac155ce 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,14 @@ # This is just a file for me to type command faster, not the build file. -debug: - cd build; ninja && G_MESSAGES_DEBUG=Psequel ./src/psequel +debug: clean + ninja -C build/ && G_MESSAGES_DEBUG=Psequel ./build/src/psequel + +clean: + rm -rf build/res + +test: + ninja -C build/ && ./build/test/psequel-test flatpak: flatpak-builder _build/ me.ppvan.psequel-debug.json --force-clean diff --git a/data/me.ppvan.psequel.desktop.in b/data/me.ppvan.psequel.desktop.in index 082ffb7..b980bda 100644 --- a/data/me.ppvan.psequel.desktop.in +++ b/data/me.ppvan.psequel.desktop.in @@ -4,5 +4,5 @@ Exec=psequel Icon=me.ppvan.psequel Terminal=false Type=Application -Categories=GTK; +Categories=Database;Development;GTK; StartupNotify=true diff --git a/data/meson.build b/data/meson.build index 7680fc6..a4ce219 100644 --- a/data/meson.build +++ b/data/meson.build @@ -12,7 +12,7 @@ if desktop_utils.found() test('Validate desktop file', desktop_utils, args: [desktop_file]) endif -appstream_file = i18n.merge_file( +i18n.merge_file( input: 'me.ppvan.psequel.appdata.xml.in', output: 'me.ppvan.psequel.appdata.xml', po_dir: '../po', @@ -20,10 +20,11 @@ appstream_file = i18n.merge_file( install_dir: join_paths(get_option('datadir'), 'appdata') ) -appstream_util = find_program('appstream-util', required: false) -if appstream_util.found() - test('Validate appstream file', appstream_util, args: ['validate', appstream_file]) -endif +# Disable redudant check. +# appstream_util = find_program('appstream-util', required: false) +# if appstream_util.found() +# test('Validate appstream file', appstream_util, args: ['validate', appstream_file]) +# endif install_data('me.ppvan.psequel.gschema.xml', diff --git a/meson.build b/meson.build index 105f7f1..95f9111 100644 --- a/meson.build +++ b/meson.build @@ -1,27 +1,37 @@ -project('psequel', ['c', 'vala'], - version: '0.1.8', - meson_version: '>= 0.62.0', - default_options: [ 'warning_level=2', 'werror=false', ], +project( + 'psequel', + ['c', 'vala'], + version: '0.1.8', + meson_version: '>= 1.3.0', + default_options: [ + 'warning_level=2', + 'werror=false', + ], ) app_id = 'me.ppvan.psequel' i18n = import('i18n') gnome = import('gnome') -cc = meson.get_compiler('c') +# cc = meson.get_compiler('c') valac = meson.get_compiler('vala') -vapi_dir = join_paths (meson.project_source_root(), 'src', 'vapi') + +# Pass custom vapi to compiler (for libpq) +vapi_dir = join_paths(meson.project_source_root(), 'src', 'vapi') add_project_arguments(['--vapidir', vapi_dir], language: 'vala') subdir('data') subdir('res') subdir('src') +subdir('test') subdir('po') +# Using gnome module to do some task after the app is installed, like compile schema (settings data) +# Update icon cache. gnome.post_install( - glib_compile_schemas: true, + glib_compile_schemas: true, gtk_update_icon_cache: true, - update_desktop_database: true, + update_desktop_database: true, ) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..e2a4355 --- /dev/null +++ b/meson.options @@ -0,0 +1 @@ +option('with_query', type : 'boolean', value : false, description : 'Run real query test(need postgresql server setup)') \ No newline at end of file diff --git a/res/gtk/connection-form.blp b/res/gtk/connection-form.blp deleted file mode 100644 index 7b13b47..0000000 --- a/res/gtk/connection-form.blp +++ /dev/null @@ -1,299 +0,0 @@ -using Gtk 4.0; -using Adw 1; - - -menu primary_menu { - - section { - item { - custom: "style-switcher"; - } - } - - section { - item { - label: _("_New Window"); - action: "app.new-window"; - } - } - section { - item { - label: _("_Import"); - action: "win.import"; - } - - item { - label: _("_Export"); - action: "win.export"; - } - } - - section { - item { - label: _("_Preferences"); - action: "app.preferences"; - } - - item { - label: _("_Keyboard Shortcuts"); - action: "win.show-help-overlay"; - } - - item { - label: _("_About"); - action: "app.about"; - } - } -} - -template $PsequelConnectionForm : Adw.Bin { - - child: WindowHandle { - Box { - orientation: vertical; - hexpand: true; - vexpand: true; - Adw.HeaderBar header { - styles ["flat"] - - [title] - Label { - label: ""; - } - - // Dupplicate primary_menu in query-view.blp. - // Update both if you change something - [end] - MenuButton { - icon-name: "open-menu-symbolic"; - menu-model: primary_menu; - popover: Gtk.PopoverMenu { - menu-model: primary_menu; - - [style-switcher] - $PsequelStyleSwitcher { - - } - }; - } - } - - Adw.Clamp { - valign: start; - hexpand: true; - vexpand: true; - maximum-size: 700; - Box { - orientation: vertical; - styles ["connection-form"] - // vexpand: true; - spacing: 4; - - Label { - margin-top: 20; - margin-bottom: 60; - styles ["title-0"] - - label: "Connect with Psequel"; - } - Grid { - - hexpand: true; - vexpand: true; - row-homogeneous: true; - row-spacing: 8; - column-spacing: 8; - margin-start: 4; - margin-end: 4; - - Label { - halign: end; - label: "Name:"; - - layout { - row: 0; - column: 0; - column-span: 3; - } - } - - Label { - halign: end; - label: "Host:"; - - layout { - row: 1; - column: 0; - column-span: 3; - } - } - - Label { - halign: end; - label: "User:"; - - layout { - row: 2; - column: 0; - column-span: 3; - } - } - - Label { - halign: end; - label: "Password:"; - - layout { - row: 3; - column: 0; - column-span: 3; - } - } - - Label { - halign: end; - label: "Database:"; - - layout { - row: 4; - column: 0; - column-span: 3; - } - } - - Entry name_entry { - placeholder-text: "Connection name"; - hexpand: true; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 0; - column: 3; - column-span: 7; - } - } - - Entry host_entry { - placeholder-text: "localhost"; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 1; - column: 3; - column-span: 5; - } - } - - Label { - label: "Port"; - halign: end; - layout { - row: 1; - column: 8; - } - } - - Entry port_entry { - placeholder-text: "5432"; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 1; - column: 9; - column-span: 1; - } - } - - Entry user_entry { - placeholder-text: "postgres"; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 2; - column: 3; - column-span: 7; - } - } - - PasswordEntry password_entry { - placeholder-text: ""; - show-peek-icon: true; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 3; - column: 3; - column-span: 7; - } - } - - - Entry database_entry { - placeholder-text: "postgres"; - activate => $on_entry_activated(); - changed => $on_text_changed(); - layout { - row: 4; - column: 3; - column-span: 7; - } - } - - Label { - label: "SSL:"; - halign: end; - layout { - row: 5; - column: 0; - column-span: 3; - } - } - - Box { - orientation: horizontal; - Box { - valign: center; - orientation: vertical; - Switch ssl_switch { - activate => $on_switch_changed(); - } - } - layout { - row: 5; - column: 3; - } - } - } - - Box { - - margin-top: 20; - margin-bottom: 10; - - Label status_label { - label: ""; - halign: start; - hexpand: true; - } - - Box { - - spacing: 8; - - Spinner spinner { - spinning: bind template.is-connectting; - } - - Button connect_btn { - styles ["suggested-action"] - label: "Connect"; - clicked => $on_connect_clicked(); - } - } - } - - } - } - } - }; -} \ No newline at end of file diff --git a/res/gtk/connection-listitem.blp b/res/gtk/connection-listitem.blp index 79f12f4..3425fd2 100644 --- a/res/gtk/connection-listitem.blp +++ b/res/gtk/connection-listitem.blp @@ -1,8 +1,8 @@ using Gtk 4.0; template ListItem { - child: $PsequelConnectionRow { - item: bind template.item; - pos: bind template.position; - }; + child: $PsequelConnectionRow { + item: bind template.item; + pos: bind template.position; + }; } diff --git a/res/gtk/connection-row.blp b/res/gtk/connection-row.blp index 3443e5b..7f24247 100644 --- a/res/gtk/connection-row.blp +++ b/res/gtk/connection-row.blp @@ -1,28 +1,28 @@ using Gtk 4.0; -template $PsequelConnectionRow : Gtk.Box { - orientation: horizontal; - spacing: 12; +template $PsequelConnectionRow: Gtk.Box { + orientation: horizontal; + spacing: 12; - Image { - icon-name: "text-sql-symbolic"; - } + Image { + icon-name: "text-sql-symbolic"; + } - Label { - label: bind template.item as <$PsequelConnection>.name; - halign: start; - ellipsize: end; - single-line-mode: true; - } + Label { + label: bind template.item as <$PsequelConnection>.name; + halign: start; + ellipsize: end; + single-line-mode: true; + } - PopoverMenu popover { - menu-model: menu; - } + PopoverMenu popover { + menu-model: menu; + } - GestureClick { - button: 3; // right clicked - released => $on_right_clicked(); - } + GestureClick { + button: 3; // right clicked + released => $on_right_clicked(); + } } menu menu { @@ -44,4 +44,4 @@ menu menu { action: "conn.delete"; } } -} \ No newline at end of file +} diff --git a/res/gtk/connection-sidebar.blp b/res/gtk/connection-sidebar.blp deleted file mode 100644 index f1484a7..0000000 --- a/res/gtk/connection-sidebar.blp +++ /dev/null @@ -1,50 +0,0 @@ -using Gtk 4.0; - -template $PsequelConnectionSidebar : Gtk.Box { - orientation: vertical; - spacing: 4; - margin-top: 4; - - Box { - Label { - styles ["text-bold"] - margin-top: 6; - margin-start: 8; - halign: start; - margin-bottom: 6; - use-markup: true; - label: "Connections"; - } - - Box { - spacing: 4; - hexpand: true; - halign: end; - - - Button { - tooltip-text: "Add new connection"; - styles ["flat"] - icon-name: "plus-large-symbolic"; - clicked => $on_add_connection(); - } - } - } - ScrolledWindow { - vexpand: true; - ListView listview { - styles ["navigation-sidebar"] - - model: SingleSelection selection_model { - model: bind template.connections; - autoselect: true; - }; - - activate => $on_connection_active(); - - factory: BuilderListItemFactory { - resource: "/me/ppvan/psequel/gtk/connection-listitem.ui"; - }; - } - } -} \ No newline at end of file diff --git a/res/gtk/connection-view.blp b/res/gtk/connection-view.blp index 9027181..b585328 100644 --- a/res/gtk/connection-view.blp +++ b/res/gtk/connection-view.blp @@ -2,45 +2,7 @@ using Gtk 4.0; using Gio 2.0; using Adw 1; -template $PsequelConnectionView : Adw.Bin { - hexpand: true; - vexpand: true; - - Gtk.Paned paned { - // can-shrink: false; - shrink-start-child: false; - shrink-end-child: false; - - - [start] - $PsequelConnectionSidebar sidebar { - width-request: 300; - - connections: bind template.viewmodel as <$PsequelConnectionViewModel>.connections; - selected-connection: bind template.viewmodel as <$PsequelConnectionViewModel>.selected-connection; - - request-new-connection => $add_new_connection(); - request_dup_connection => $dup_connection (); - request_remove_connection => $remove_connection (); - request_connect_database => $active_connection (); - } - - [end] - $PsequelConnectionForm form { - width-request: 800; - - selected-connection: bind sidebar.selected-connection; - is-connectting: bind template.viewmodel as <$PsequelConnectionViewModel>.is-connectting; - menu-model: primary_menu; - request-database => $active_connection (); - connections-changed => $save_connections (); - } - } -} - - menu primary_menu { - section { item { custom: "style-switcher"; @@ -53,14 +15,15 @@ menu primary_menu { action: "app.new-window"; } } + section { item { - label: _("_Import Connections"); + label: _("_Import"); action: "win.import"; } item { - label: _("_Export Connections"); + label: _("_Export"); action: "win.export"; } } @@ -80,10 +43,374 @@ menu primary_menu { label: _("_About"); action: "app.about"; } + } +} - item { - label: _("_Dark"); - action: "app.dark"; +template $PsequelConnectionView: Adw.Bin { + hexpand: true; + vexpand: true; + + Gtk.Paned paned { + // can-shrink: false; + shrink-start-child: false; + shrink-end-child: false; + + [start] + Box { + width-request: 300; + orientation: vertical; + spacing: 4; + margin-top: 4; + + Box { + Label { + styles [ + "text-bold" + ] + + margin-top: 6; + margin-start: 8; + halign: start; + margin-bottom: 6; + use-markup: true; + label: "Connections"; + } + + Box { + spacing: 4; + hexpand: true; + halign: end; + + Button { + tooltip-text: "Add new connection"; + + styles [ + "flat" + ] + + icon-name: "plus-large-symbolic"; + clicked => $add_new_connection(); + } + } + } + + ScrolledWindow { + vexpand: true; + + ListView listview { + styles [ + "navigation-sidebar" + ] + + model: SingleSelection selection_model { + model: bind template.viewmodel as <$PsequelConnectionViewModel>.connections; + autoselect: true; + }; + + activate => $active_connection(); + + factory: BuilderListItemFactory { + resource: "/me/ppvan/psequel/gtk/connection-listitem.ui"; + }; + } + } } + + // $PsequelConnectionSidebar sidebar { + // width-request: 300; + // connections: bind template.viewmodel as <$PsequelConnectionViewModel>.connections; + // selected-connection: bind template.viewmodel as <$PsequelConnectionViewModel>.selected-connection; + // request-new-connection => $add_new_connection(); + // request_dup_connection => $dup_connection(); + // request_remove_connection => $remove_connection(); + // request_connect_database => $active_connection(); + // } + + [end] + Adw.Bin { + child: WindowHandle { + Box { + orientation: vertical; + hexpand: true; + vexpand: true; + + Adw.HeaderBar header { + styles [ + "flat" + ] + + [title] + Label { + label: ""; + } + + // Dupplicate primary_menu in query-view.blp. + // Update both if you change something + + [end] + MenuButton { + icon-name: "open-menu-symbolic"; + menu-model: primary_menu; + + popover: Gtk.PopoverMenu { + menu-model: primary_menu; + + [style-switcher] + $PsequelStyleSwitcher {} + }; + } + } + + Adw.Clamp { + valign: start; + hexpand: true; + vexpand: true; + maximum-size: 700; + + child: Box { + orientation: vertical; + + Box { + orientation: vertical; + + styles [ + "connection-form" + ] + + // vexpand: true; + spacing: 4; + + Label { + margin-top: 20; + margin-bottom: 60; + + styles [ + "title-0" + ] + + label: "Connect with Psequel"; + } + + Grid { + hexpand: true; + vexpand: true; + row-homogeneous: true; + row-spacing: 8; + column-spacing: 8; + margin-start: 4; + margin-end: 4; + + Label { + halign: end; + label: "Name:"; + + layout { + row: 0; + column: 0; + column-span: 3; + } + } + + Label { + halign: end; + label: "Host:"; + + layout { + row: 1; + column: 0; + column-span: 3; + } + } + + Label { + halign: end; + label: "User:"; + + layout { + row: 2; + column: 0; + column-span: 3; + } + } + + Label { + halign: end; + label: "Password:"; + + layout { + row: 3; + column: 0; + column-span: 3; + } + } + + Label { + halign: end; + label: "Database:"; + + layout { + row: 4; + column: 0; + column-span: 3; + } + } + + Entry name_entry { + placeholder-text: "Connection name"; + hexpand: true; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 0; + column: 3; + column-span: 7; + } + } + + Entry host_entry { + placeholder-text: "localhost"; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 1; + column: 3; + column-span: 5; + } + } + + Label { + label: "Port"; + halign: end; + + layout { + row: 1; + column: 8; + } + } + + Entry port_entry { + placeholder-text: "5432"; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 1; + column: 9; + column-span: 1; + } + } + + Entry user_entry { + placeholder-text: "postgres"; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 2; + column: 3; + column-span: 7; + } + } + + PasswordEntry password_entry { + placeholder-text: ""; + show-peek-icon: true; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 3; + column: 3; + column-span: 7; + } + } + + Entry database_entry { + placeholder-text: "postgres"; + activate => $on_entry_activated(); + changed => $on_text_changed(); + + layout { + row: 4; + column: 3; + column-span: 7; + } + } + + Label { + label: "SSL:"; + halign: end; + + layout { + row: 5; + column: 0; + column-span: 3; + } + } + + Box { + orientation: horizontal; + + Box { + valign: center; + orientation: vertical; + + Switch ssl_switch { + activate => $on_switch_changed(); + } + } + + layout { + row: 5; + column: 3; + } + } + } + } + + Box { + margin-top: 20; + margin-bottom: 10; + + Label status_label { + label: "Hello world"; + halign: start; + hexpand: true; + } + + Box { + spacing: 8; + + Spinner spinner { + spinning: bind template.viewmodel as <$PsequelConnectionViewModel>.is-connectting; + } + + Button connect_btn { + styles [ + "suggested-action" + ] + + label: "Connect"; + clicked => $on_connect_clicked(); + } + } + } + }; + } + } + }; + } + + // $PsequelConnectionForm form { + // width-request: 800; + // selected-connection: bind sidebar.selected-connection; + // is-connectting: bind template.viewmodel as <$PsequelConnectionViewModel>.is-connectting; + // current-state: bind template.viewmodel as <$PsequelConnectionViewModel>.current-state; + // menu-model: primary_menu; + // request-database => $active_connection (); + // connections-changed => $save_connections (); + // } } -} \ No newline at end of file +} diff --git a/res/gtk/help-overlay.blp b/res/gtk/help-overlay.blp index 7ccfa7c..05dcdf2 100644 --- a/res/gtk/help-overlay.blp +++ b/res/gtk/help-overlay.blp @@ -26,7 +26,6 @@ ShortcutsWindow help_overlay { } } - ShortcutsGroup { title: C_("shortcut window", "Query Editor"); diff --git a/res/gtk/preferences-window.blp b/res/gtk/preferences-window.blp index 8c852ed..f9ce5ba 100644 --- a/res/gtk/preferences-window.blp +++ b/res/gtk/preferences-window.blp @@ -1,92 +1,88 @@ using Gtk 4.0; using Adw 1; -template $PsequelPreferencesWindow : Adw.PreferencesWindow { - - default-width: 640; - default-height: 640; - modal: false; - - Adw.PreferencesPage { - name: "General"; - icon-name: "settings-symbolic"; - - Adw.PreferencesGroup { - name: "database"; - title: "Database"; - - Adw.ActionRow { - title: "Connection timeout"; - subtitle: "in seconds"; - - SpinButton conn_timeout { - valign: center; - numeric: true; - value: 5; - adjustment: - Adjustment { - lower: 1; - upper: 100; - value: 5; - step-increment: 1; - } - ; - } - } - - Adw.ActionRow { - title: "Query timeout"; - subtitle: "in seconds"; - - SpinButton query_timeout { - valign: center; - numeric: true; - value: 5; - adjustment: - Adjustment { - lower: 1; - upper: 100; - step-increment: 1; - value: 5; - } - ; - } - } - - Adw.ActionRow { - title: "Query limit"; - subtitle: "Max rows return in SELECT"; - - SpinButton query_limit { - valign: center; - numeric: true; - value: 200; - adjustment: - Adjustment { - lower: 100; - upper: 900; - step-increment: 100; - value: 200; - } - ; - } - } +template $PsequelPreferencesWindow: Adw.PreferencesWindow { + default-width: 640; + default-height: 640; + modal: false; + + Adw.PreferencesPage { + name: "General"; + icon-name: "settings-symbolic"; + + Adw.PreferencesGroup { + name: "database"; + title: "Database"; + + Adw.ActionRow { + title: "Connection timeout"; + subtitle: "in seconds"; + + SpinButton conn_timeout { + valign: center; + numeric: true; + value: 5; + + adjustment: Adjustment { + lower: 1; + upper: 100; + value: 5; + step-increment: 1; + }; } + } - Adw.PreferencesGroup { - name: "editor"; - title: "Editor"; - Adw.ActionRow { - title: "Editor Font"; - activatable: true; + Adw.ActionRow { + title: "Query timeout"; + subtitle: "in seconds"; - Label font_label { - label: "Source Code Pro 12"; - } + SpinButton query_timeout { + valign: center; + numeric: true; + value: 5; - activated => $on_font_chooser(); - } + adjustment: Adjustment { + lower: 1; + upper: 100; + step-increment: 1; + value: 5; + }; } + } + + Adw.ActionRow { + title: "Query limit"; + subtitle: "Max rows return in SELECT"; + + SpinButton query_limit { + valign: center; + numeric: true; + value: 200; + + adjustment: Adjustment { + lower: 100; + upper: 900; + step-increment: 100; + value: 200; + }; + } + } + } + + Adw.PreferencesGroup { + name: "editor"; + title: "Editor"; + + Adw.ActionRow { + title: "Editor Font"; + activatable: true; + + Label font_label { + label: "Source Code Pro 12"; + } + + activated => $on_font_chooser(); + } } - + } } diff --git a/res/gtk/query-editor.blp b/res/gtk/query-editor.blp index 69976f2..25b3908 100644 --- a/res/gtk/query-editor.blp +++ b/res/gtk/query-editor.blp @@ -3,171 +3,186 @@ using Adw 1; using Gdk 4.0; using GtkSource 5; -template $PsequelQueryEditor : Adw.Bin { - - selected-query: bind template.query-viewmodel as <$PsequelQueryViewModel>.selected-query; - - Paned paned { - orientation: vertical; - shrink-start-child: false; - resize-start-child: true; - - shrink-end-child: false; - resize-end-child: false; - - Adw.Bin { - height-request: 250; - ScrolledWindow { - hscrollbar-policy: never; - GtkSource.View editor { - buffer: GtkSource.Buffer buffer { - - }; - - wrap-mode: word; - monospace: true; - auto-indent: true; - show-line-numbers: true; - smart-backspace: true; - smart-home-end: before; - top-margin: 6; - - highlight-current-line: true; - insert-spaces-instead-of-tabs: true; - } - } +template $PsequelQueryEditor: Adw.Bin { + selected-query: bind template.query-viewmodel as <$PsequelQueryViewModel>.selected-query; + + Paned paned { + orientation: vertical; + shrink-start-child: false; + resize-start-child: true; + shrink-end-child: false; + resize-end-child: false; + + Adw.Bin { + height-request: 250; + + ScrolledWindow { + hscrollbar-policy: never; + + GtkSource.View editor { + buffer: GtkSource.Buffer buffer {}; + + wrap-mode: word; + monospace: true; + auto-indent: true; + show-line-numbers: true; + smart-backspace: true; + smart-home-end: before; + top-margin: 6; + highlight-current-line: true; + insert-spaces-instead-of-tabs: true; } + } + } - Box { - orientation: vertical; - height-request: 400; - Box { - margin-top: 8; - margin-bottom: 8; - margin-start: 8; - margin-end: 8; - spacing: 8; - - MenuButton { - styles ["big-icon"] - tooltip-text: "editor setting"; - icon-name: "settings-symbolic"; - halign: start; - valign: start; - direction: down; - menu-model: menu; - } + Box { + orientation: vertical; + height-request: 400; + + Box { + margin-top: 8; + margin-bottom: 8; + margin-start: 8; + margin-end: 8; + spacing: 8; + + MenuButton { + styles [ + "big-icon" + ] + + tooltip-text: "editor setting"; + icon-name: "settings-symbolic"; + halign: start; + valign: start; + direction: down; + menu-model: menu; + } - MenuButton { - styles ["big-icon"] - icon-name: "history-undo-symbolic"; - tooltip-text: "Query History"; - popover: Popover popover { - height-request: 300; - width-request: 600; - Box { - spacing: 4; - orientation: vertical; - // SearchEntry { - // placeholder-text: "Find Query"; - // } - - ScrolledWindow { - vexpand: true; - min-content-width: 300; - max-content-width: 600; - - ListView { - styles ["navigation-sidebar"] - model: SingleSelection selection_model { - model: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.query-history; - }; - - factory: BuilderListItemFactory { - resource: "/me/ppvan/psequel/gtk/query-listitem.ui"; - }; - - activate => $on_query_history_exec(); - } - } - - Separator {} - - Button { - styles ["destructive-action"] - label: "Clear History"; - clicked => $on_clear_history(); - } - - } - }; - } + MenuButton { + styles [ + "big-icon" + ] + icon-name: "history-undo-symbolic"; + tooltip-text: "Query History"; - Label { - hexpand: true; - } + popover: Popover popover { + height-request: 300; + width-request: 600; - Spinner spinner { - spinning: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.is-loading; + Box { + spacing: 4; + orientation: vertical; + // SearchEntry { + // placeholder-text: "Find Query"; + // } + ScrolledWindow { + vexpand: true; + min-content-width: 300; + max-content-width: 600; + + ListView { + styles [ + "navigation-sidebar" + ] + + model: SingleSelection selection_model { + model: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.query-history; + }; + + factory: BuilderListItemFactory { + resource: "/me/ppvan/psequel/gtk/query-listitem.ui"; + }; + + activate => $on_query_history_exec(); } + } - // Label query_time { - // label: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.query-time; - // } + Separator {} - Button run_query_btn { - styles ["suggested-action"] - label: "Run Query"; - tooltip-text: "Ctrl+Enter"; - clicked => $run_query_cb(); - } + Button { + styles [ + "destructive-action" + ] + + label: "Clear History"; + clicked => $on_clear_history(); + } } + }; + } - Separator {} + Label { + hexpand: true; + } - $PsequelQueryResults query_results { - wellcome_message: "Run a query"; - current-relation: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.current-relation; - is-loading: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.is-loading; - err-msg: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.err-msg; - } + Spinner spinner { + spinning: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.is-loading; + } - Box { - spacing: 4; - margin-start: 12; - margin-top: 8; - margin-bottom: 8; - margin-end: 12; - Image { - icon-size: normal; - margin-end: 6; - styles ["success"] - icon-name: "test-pass-symbolic"; - pixel-size: 20; - visible: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.success; - } + // Label query_time { + // label: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.query-time; + // } + Button run_query_btn { + styles [ + "suggested-action" + ] + + label: "Run Query"; + tooltip-text: "Ctrl+Enter"; + clicked => $run_query_cb(); + } + } + + Separator {} + + $PsequelQueryResults query_results { + wellcome_message: "Run a query"; + current-relation: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.current-relation; + is-loading: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.is-loading; + err-msg: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.err-msg; + } + + Box { + spacing: 4; + margin-start: 12; + margin-top: 8; + margin-bottom: 8; + margin-end: 12; + + Image { + icon-size: normal; + margin-end: 6; + + styles [ + "success" + ] + + icon-name: "test-pass-symbolic"; + pixel-size: 20; + visible: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.success; + } - Label row_affect { - label: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.row-affected; - } + Label row_affect { + label: bind template.query-history-viewmodel as <$PsequelQueryHistoryViewModel>.row-affected; + } - Label { - hexpand: true; - } + Label { + hexpand: true; + } - Button export { - label: "Export"; - clicked => $on_export_csv(); - } - } + Button export { + label: "Export"; + clicked => $on_export_csv(); } + } } + } } menu menu { section { - item { label: _("_Editor Font"); action: "app.preferences"; @@ -188,4 +203,4 @@ menu menu { // action: "editor.auto-completion"; // } } -} \ No newline at end of file +} diff --git a/res/gtk/query-listitem.blp b/res/gtk/query-listitem.blp index c97ac1d..c1494f7 100644 --- a/res/gtk/query-listitem.blp +++ b/res/gtk/query-listitem.blp @@ -1,10 +1,10 @@ using Gtk 4.0; template ListItem { - child: Label { - label: bind template.item as <$PsequelQuery>.sql; - halign: start; - ellipsize: end; - single-line-mode: true; - }; -} \ No newline at end of file + child: Label { + label: bind template.item as <$PsequelQuery>.sql; + halign: start; + ellipsize: end; + single-line-mode: true; + }; +} diff --git a/res/gtk/query-results.blp b/res/gtk/query-results.blp index 547949e..b18e564 100644 --- a/res/gtk/query-results.blp +++ b/res/gtk/query-results.blp @@ -1,79 +1,93 @@ using Gtk 4.0; using Adw 1; -template $PsequelQueryResults : Adw.Bin { - - wellcome_message: "Select a table"; - Stack stack { - StackPage { - name: "empty"; - - child: Adw.Clamp { - maximum-size: 32; - - Box { - styles ["icon-xl"] - orientation: vertical; - valign: center; - Image { - icon-name: "webview-filler-symbolic"; - } - - Label label { - styles ["title-2"] - halign: center; - valign: center; - margin-start: 24; - margin-top: 24; - margin-bottom: 24; - margin-end: 24; - - label: bind template.wellcome_message as ; - } - } - }; +template $PsequelQueryResults: Adw.Bin { + wellcome_message: "Select a table"; + + Stack stack { + StackPage { + name: "empty"; + + child: Adw.Clamp { + maximum-size: 32; + + Box { + styles [ + "icon-xl" + ] + + orientation: vertical; + valign: center; + + Image { + icon-name: "webview-filler-symbolic"; + } + + Label label { + styles [ + "title-2" + ] + + halign: center; + valign: center; + margin-start: 24; + margin-top: 24; + margin-bottom: 24; + margin-end: 24; + label: bind template.wellcome_message as ; + } } + }; + } + StackPage { + name: "data"; - StackPage { - name: "data"; - child: ScrolledWindow { - ColumnView data_view { - show-row-separators: true; - show-column-separators: true; - styles ["data-table"] - vexpand: true; - } - }; + child: ScrolledWindow { + ColumnView data_view { + show-row-separators: true; + show-column-separators: true; + + styles [ + "data-table" + ] + + vexpand: true; } + }; + } + + StackPage { + name: "loading"; - StackPage { - name: "loading"; - child: Adw.Clamp { - maximum-size: 32; + child: Adw.Clamp { + maximum-size: 32; - Spinner spinner { - spinning: bind template.is-loading; - } - }; + Spinner spinner { + spinning: bind template.is-loading; } + }; + } + + StackPage { + name: "error"; + + child: ScrolledWindow { + Label status_label { + halign: start; + valign: start; + margin-start: 24; + margin-top: 24; + margin-bottom: 24; + margin-end: 24; + + styles [ + "error" + ] - StackPage { - name: "error"; - - child: ScrolledWindow { - Label status_label { - halign: start; - valign: start; - margin-start: 24; - margin-top: 24; - margin-bottom: 24; - margin-end: 24; - - styles ["error"] - label: bind template.err_msg; - } - }; + label: bind template.err_msg; } + }; } -} \ No newline at end of file + } +} diff --git a/res/gtk/schema-main.blp b/res/gtk/schema-main.blp deleted file mode 100644 index 66b945b..0000000 --- a/res/gtk/schema-main.blp +++ /dev/null @@ -1,127 +0,0 @@ -using Gtk 4.0; -using Adw 1; - - -menu primary_menu { - - section { - item { - custom: "style-switcher"; - } - } - - section { - item { - label: _("_New Window"); - action: "app.new-window"; - } - } - - section { - item { - label: _("_Preferences"); - action: "app.preferences"; - } - - item { - label: _("_Keyboard Shortcuts"); - action: "win.show-help-overlay"; - } - - item { - label: _("_About"); - action: "app.about"; - } - } -} - - -template $PsequelSchemaMain : Gtk.Box { - orientation: vertical; - width-request: 920; - Adw.HeaderBar { - styles ["flat"] - - [title] - Adw.ViewSwitcher { - policy: wide; - stack: stack; - } - - [end] - MenuButton { - icon-name: "open-menu-symbolic"; - menu-model: primary_menu; - popover: Gtk.PopoverMenu { - menu-model: primary_menu; - - [style-switcher] - $PsequelStyleSwitcher { - - } - }; - - } - } - - Adw.ViewStack stack { - hexpand: true; - vexpand: true; - Adw.ViewStackPage { - name: "structure-view"; - icon-name: "library-symbolic"; - title: "Structure"; - child: Stack { - - visible-child-name: bind template.view-mode; - transition-type: slide_left_right; - - - StackPage { - name: "table"; - child: $PsequelTableStructureView { - }; - } - - StackPage { - name: "view"; - child: $PsequelViewStructureView { - }; - } - }; - } - - Adw.ViewStackPage { - name: "data-view"; - title: "Data"; - icon-name: "object-rows-symbolic"; - - child: Stack { - - visible-child-name: bind template.view-mode; - transition-type: slide_left_right; - - - StackPage { - name: "table"; - child: $PsequelTableDataView { - }; - } - - StackPage { - name: "view"; - child: $PsequelViewDataView { - }; - } - }; - } - - Adw.ViewStackPage { - name: "query-editor"; - icon-name: "terminal-symbolic"; - title: "Query"; - child: $PsequelQueryEditor { - }; - } - } -} diff --git a/res/gtk/schema-sidebar.blp b/res/gtk/schema-sidebar.blp deleted file mode 100644 index 75f798a..0000000 --- a/res/gtk/schema-sidebar.blp +++ /dev/null @@ -1,225 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $PsequelSchemaSidebar : Gtk.Box { - orientation: vertical; - width-request: 300; - - Box { - margin-start: 8; - margin-end: 8; - margin-top: 8; - margin-bottom: 8; - hexpand: true; - spacing: 12; - Label { - styles ["title-3"] - halign: start; - margin-bottom: 0; - label: "Schema"; - } - DropDown dropdown { - hexpand: true; - model: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.schemas; - } - } - - - Adw.ViewSwitcher tables_views_switcher { - - styles ["big-icon"] - height-request: 40; - margin-start: 8; - margin-end: 8; - margin-top: 4; - margin-bottom: 8; - policy: wide; - stack: sql_views; - } - - Separator {} - - Adw.ViewStack sql_views { - hexpand: true; - vexpand: true; - Adw.ViewStackPage { - icon-name: "table-symbolic"; - name: "table"; - - child: Box { - orientation: vertical; - Box { - margin-start: 8; - margin-end: 8; - margin-top: 4; - margin-bottom: 8; - - Label { - styles ["title-3"] - margin-top: 8; - margin-start: 4; - halign: start; - margin-bottom: 0; - label: "Tables"; - } - - Separator { - hexpand: true; - styles ["spacer"] - } - - ToggleButton search_btn { - styles ["flat"] - tooltip-text: "Search tables"; - icon-name: "loupe-large-symbolic"; - - toggled => $table_search_reveal(); - } - } - - Revealer { - transition-duration: 300; - reveal-child: bind search_btn.active; - styles ["background"] - SearchEntry search_table_entry { - placeholder-text: "Search Tables"; - margin-start: 4; - margin-bottom: 4; - - search-changed => $on_table_search(); - } - - } - - ScrolledWindow { - vexpand: true; - ListView table_list { - styles ["navigation-sidebar"] - model: SingleSelection table_selection { - model: FilterListModel table_model { - incremental: true; - model: bind template.table-viewmodel as <$PsequelTableViewModel>.tables; - filter: StringFilter table_filter { - - }; - }; - autoselect: true; - }; - - factory: BuilderListItemFactory { - resource: "/me/ppvan/psequel/gtk/table-listitem.ui"; - }; - - } - } - }; - } - - Adw.ViewStackPage { - icon-name: "category-search-symbolic"; - name: "view"; - - child: Box { - orientation: vertical; - Box { - margin-start: 8; - margin-end: 8; - margin-top: 4; - margin-bottom: 8; - - Label { - styles ["title-3"] - margin-top: 8; - margin-start: 4; - halign: start; - margin-bottom: 0; - label: "Views"; - } - - Separator { - hexpand: true; - styles ["spacer"] - } - - ToggleButton search_views_btn { - styles ["flat"] - tooltip-text: "Search views"; - icon-name: "loupe-large-symbolic"; - - toggled => $view_search_reveal(); - } - } - - Revealer { - transition-duration: 300; - reveal-child: bind search_views_btn.active; - styles ["background"] - SearchEntry search_views_entry { - placeholder-text: "Search Views"; - margin-start: 4; - margin-bottom: 4; - - search-changed => $on_view_search(); - } - - } - - ScrolledWindow { - vexpand: true; - ListView views_list { - styles ["navigation-sidebar"] - model: SingleSelection view_selection { - model: FilterListModel view_model { - incremental: true; - model: bind template.view-viewmodel as <$PsequelViewViewModel>.views; - filter: StringFilter view_filter { - - }; - }; - autoselect: true; - }; - - factory: BuilderListItemFactory { - resource: "/me/ppvan/psequel/gtk/view-listitem.ui"; - }; - - } - } - }; - } - } - - Box { - spacing: 4; - margin-bottom: 2; - margin-end: 2; - margin-start: 4; - - Button { - visible: false; - styles ["flat"] - icon-name: "plus-large-symbolic"; - tooltip-text: "Create new table"; - } - - Button reload { - styles ["flat"] - icon-name: "refresh-large-symbolic"; - tooltip-text: "Reload tables"; - - clicked => $reload_btn_clicked(); - } - - Label { - hexpand: true; - } - - Button logout { - styles ["flat"] - icon-name: "step-out-symbolic"; - tooltip-text: "Logout"; - clicked => $logout_btn_clicked(); - } - - } -} diff --git a/res/gtk/schema-view.blp b/res/gtk/schema-view.blp index e41073b..caeb35a 100644 --- a/res/gtk/schema-view.blp +++ b/res/gtk/schema-view.blp @@ -1,41 +1,21 @@ using Gtk 4.0; using Adw 1; -template $PsequelSchemaView : Adw.Bin { - hexpand: true; - vexpand: true; - - Box { - orientation: vertical; - - Gtk.Paned paned { - shrink-end-child: false; - shrink-start-child: false; - - $PsequelSchemaSidebar sidebar { - width-request: 320; - } - - $PsequelSchemaMain { - - width-request: 300; - - menu: primary_menu; - view-mode: bind sidebar.view-mode; - } - } - } -} - - menu primary_menu { section { + item { + custom: "style-switcher"; + } + } + section { item { label: _("_New Window"); action: "app.new-window"; } + } + section { item { label: _("_Preferences"); action: "app.preferences"; @@ -51,4 +31,401 @@ menu primary_menu { action: "app.about"; } } -} \ No newline at end of file +} + +template $PsequelSchemaView: Adw.Bin { + hexpand: true; + vexpand: true; + + Box { + orientation: vertical; + + Gtk.Paned paned { + shrink-end-child: false; + shrink-start-child: false; + + [start] + Box sidebar { + orientation: vertical; + width-request: 300; + + Box { + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 8; + hexpand: true; + spacing: 12; + + Label { + styles [ + "title-3" + ] + + halign: start; + margin-bottom: 0; + label: "Schema"; + } + + DropDown dropdown { + hexpand: true; + model: bind template.schema-viewmodel as <$PsequelSchemaViewModel>.schemas; + } + } + + Adw.ViewSwitcher tables_views_switcher { + styles [ + "big-icon" + ] + + height-request: 40; + margin-start: 8; + margin-end: 8; + margin-top: 4; + margin-bottom: 8; + policy: wide; + stack: sql_views; + } + + Separator {} + + Adw.ViewStack sql_views { + hexpand: true; + vexpand: true; + + Adw.ViewStackPage { + icon-name: "table-symbolic"; + name: "table"; + + child: Box { + orientation: vertical; + + Box { + margin-start: 8; + margin-end: 8; + margin-top: 4; + margin-bottom: 8; + + Label { + styles [ + "title-3" + ] + + margin-top: 8; + margin-start: 4; + halign: start; + margin-bottom: 0; + label: "Tables"; + } + + Label { + styles [ + "badges" + ] + + margin-top: 8; + margin-start: 4; + halign: center; + valign: center; + margin-bottom: 0; + label: bind table_model.n-items; + } + + Separator { + hexpand: true; + + styles [ + "spacer" + ] + } + + ToggleButton search_btn { + styles [ + "flat" + ] + + tooltip-text: "Search tables"; + icon-name: "loupe-large-symbolic"; + toggled => $table_search_reveal(); + } + } + + Revealer { + transition-duration: 300; + reveal-child: bind search_btn.active; + + styles [ + "background" + ] + + SearchEntry search_table_entry { + placeholder-text: "Search Tables"; + margin-start: 4; + margin-bottom: 4; + search-changed => $on_table_search(); + } + } + + ScrolledWindow { + vexpand: true; + + ListView table_list { + styles [ + "navigation-sidebar" + ] + + model: SingleSelection table_selection { + model: FilterListModel table_model { + incremental: true; + model: bind template.table-viewmodel as <$PsequelTableViewModel>.tables; + + filter: StringFilter table_filter {}; + }; + + autoselect: true; + }; + + factory: BuilderListItemFactory { + resource: "/me/ppvan/psequel/gtk/table-listitem.ui"; + }; + } + } + }; + } + + Adw.ViewStackPage { + icon-name: "category-search-symbolic"; + name: "view"; + + child: Box { + orientation: vertical; + + Box { + margin-start: 8; + margin-end: 8; + margin-top: 4; + margin-bottom: 8; + + Label { + styles [ + "title-3" + ] + + margin-top: 8; + margin-start: 4; + halign: start; + margin-bottom: 0; + label: "Views"; + } + + Label { + styles [ + "badges" + ] + + margin-top: 8; + margin-start: 4; + halign: center; + valign: center; + margin-bottom: 0; + label: bind view_model.n-items; + } + + Separator { + hexpand: true; + + styles [ + "spacer" + ] + } + + ToggleButton search_views_btn { + styles [ + "flat" + ] + + tooltip-text: "Search views"; + icon-name: "loupe-large-symbolic"; + toggled => $view_search_reveal(); + } + } + + Revealer { + transition-duration: 300; + reveal-child: bind search_views_btn.active; + + styles [ + "background" + ] + + SearchEntry search_views_entry { + placeholder-text: "Search Views"; + margin-start: 4; + margin-bottom: 4; + search-changed => $on_view_search(); + } + } + + ScrolledWindow { + vexpand: true; + + ListView views_list { + styles [ + "navigation-sidebar" + ] + + model: SingleSelection view_selection { + model: FilterListModel view_model { + incremental: true; + model: bind template.view-viewmodel as <$PsequelViewViewModel>.views; + + filter: StringFilter view_filter {}; + }; + + autoselect: true; + }; + + factory: BuilderListItemFactory { + resource: "/me/ppvan/psequel/gtk/view-listitem.ui"; + }; + } + } + }; + } + } + + Box { + spacing: 4; + margin-bottom: 2; + margin-end: 2; + margin-start: 4; + + Button { + visible: false; + + styles [ + "flat" + ] + + icon-name: "plus-large-symbolic"; + tooltip-text: "Create new table"; + } + + Button reload { + styles [ + "flat" + ] + + icon-name: "refresh-large-symbolic"; + tooltip-text: "Reload tables"; + clicked => $reload_btn_clicked(); + } + + Label { + hexpand: true; + } + + Button logout { + styles [ + "flat" + ] + + icon-name: "step-out-symbolic"; + tooltip-text: "Logout"; + clicked => $logout_btn_clicked(); + } + } + } + + [end] + Box main-view { + orientation: vertical; + width-request: 920; + + Adw.HeaderBar { + styles [ + "flat" + ] + + [title] + Adw.ViewSwitcher { + policy: wide; + stack: stack; + } + + [end] + MenuButton { + icon-name: "open-menu-symbolic"; + menu-model: primary_menu; + + popover: Gtk.PopoverMenu { + menu-model: primary_menu; + + [style-switcher] + $PsequelStyleSwitcher {} + }; + } + } + + Adw.ViewStack stack { + hexpand: true; + vexpand: true; + + Adw.ViewStackPage { + name: "structure-view"; + icon-name: "library-symbolic"; + title: "Structure"; + + child: Stack { + visible-child-name: bind template.view-mode; + transition-type: slide_left_right; + + StackPage { + name: "table"; + + child: $PsequelTableStructureView {}; + } + + StackPage { + name: "view"; + + child: $PsequelViewStructureView {}; + } + }; + } + + Adw.ViewStackPage { + name: "data-view"; + title: "Data"; + icon-name: "object-rows-symbolic"; + + child: Stack { + visible-child-name: bind template.view-mode; + transition-type: slide_left_right; + + StackPage { + name: "table"; + + child: $PsequelTableDataView {}; + } + + StackPage { + name: "view"; + + child: $PsequelViewDataView {}; + } + }; + } + + Adw.ViewStackPage { + name: "query-editor"; + icon-name: "terminal-symbolic"; + title: "Query"; + + child: $PsequelQueryEditor {}; + } + } + } + } + } +} diff --git a/res/gtk/style-switcher.blp b/res/gtk/style-switcher.blp index 38d4096..fb922e9 100644 --- a/res/gtk/style-switcher.blp +++ b/res/gtk/style-switcher.blp @@ -1,8 +1,6 @@ using Gtk 4.0; - // Adapt from https://gitlab.gnome.org/raggesilver/blackbox/-/blob/dc3417f2185e539adcaf6e0775ba83bb238b1d14/src/gtk/style-switcher.ui - -template $PsequelStyleSwitcher : Widget { +template $PsequelStyleSwitcher: Widget { Box box { orientation: horizontal; homogeneous: true; @@ -91,4 +89,4 @@ template $PsequelStyleSwitcher : Widget { } } } -} \ No newline at end of file +} diff --git a/res/gtk/style.css b/res/gtk/style.css index 855e3ee..39d17f8 100644 --- a/res/gtk/style.css +++ b/res/gtk/style.css @@ -8,6 +8,14 @@ margin-bottom: 32px; } +.badges { + font-size: 14px; + padding: 4px 6px; + font-weight: bold; + background-color: @sidebar_bg_color; + border-radius: 8px; +} + .big-icon image { -gtk-icon-size: 20px; diff --git a/res/gtk/table-cols.blp b/res/gtk/table-cols.blp index 134bd0f..14e5cfc 100644 --- a/res/gtk/table-cols.blp +++ b/res/gtk/table-cols.blp @@ -1,18 +1,20 @@ using Gtk 4.0; using Adw 1; -template $PsequelTableColInfo : Adw.Bin { - ScrolledWindow { - ColumnView view { +template $PsequelTableColInfo: Adw.Bin { + ScrolledWindow { + ColumnView view { + model: NoSelection { + model: bind template.columns; + }; - model: NoSelection { - model: bind template.columns; - }; + styles [ + "data-table" + ] - styles ["data-table"] - show-column-separators: true; - show-row-separators: true; - vexpand: true; - } + show-column-separators: true; + show-row-separators: true; + vexpand: true; } -} \ No newline at end of file + } +} diff --git a/res/gtk/table-data-view.blp b/res/gtk/table-data-view.blp index 597a36c..eef15dd 100644 --- a/res/gtk/table-data-view.blp +++ b/res/gtk/table-data-view.blp @@ -1,89 +1,99 @@ using Gtk 4.0; - using Adw 1; -template $PsequelTableDataView : Gtk.Box { - - width-request: 900; - height-request: 600; +template $PsequelTableDataView: Gtk.Box { + width-request: 900; + height-request: 600; + orientation: vertical; + spacing: 4; + margin-start: 8; + margin-top: 8; + margin-end: 8; + margin-bottom: 8; - orientation: vertical; - spacing: 4; - margin-start: 8; + Box { + visible: false; + spacing: 12; margin-top: 8; - margin-end: 8; margin-bottom: 8; - - Box { - visible: false; - spacing: 12; - margin-top: 8; - margin-bottom: 8; - Entry filter_entry { - hexpand: true; - placeholder-text: "WHERE clause"; - // activate => $on_entry_activated(); - } - - Button filter_btn { - styles ["suggested-action"] - label: "Filter"; - - // clicked => $filter_query(); - } + + Entry filter_entry { + hexpand: true; + placeholder-text: "WHERE clause"; + // activate => $on_entry_activated(); + } + + Button filter_btn { + styles [ + "suggested-action" + ] + + label: "Filter"; + // clicked => $filter_query(); } + } + + $PsequelQueryResults query_results { + current-relation: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.current-relation; + is-loading: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.is-loading; + err-msg: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.err-msg; + } + + Box { + spacing: 8; + // height-request: 40; + Button { + visible: false; + + styles [ + "flat" + ] + + icon-name: "plus-large-symbolic"; + tooltip-text: "Insert Row"; + } + + Button reload { + styles [ + "flat" + ] + + icon-name: "refresh-large-symbolic"; + tooltip-text: "Reload Data"; + clicked => $reload_data(); + } + + // Separator {} + Label status_label { + label: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.row-ranges; + halign: start; + } + + // Separator {} + Label { + hexpand: true; + } + + Button left_page { + styles [ + "flat" + ] - $PsequelQueryResults query_results { - current-relation: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.current-relation; - is-loading: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.is-loading; - err-msg: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.err-msg; + sensitive: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.has-pre-page; + icon-name: "left-large-symbolic"; + tooltip-text: "Last Page"; + clicked => $pre_page(); } + Button right_page { + styles [ + "flat" + ] - Box { - spacing: 8; - // height-request: 40; - Button { - visible: false; - styles ["flat"] - icon-name: "plus-large-symbolic"; - tooltip-text: "Insert Row"; - } - - Button reload { - styles ["flat"] - icon-name: "refresh-large-symbolic"; - tooltip-text: "Reload Data"; - clicked => $reload_data(); - } - - // Separator {} - - Label status_label { - label: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.row-ranges; - halign: start; - } - - // Separator {} - - Label { - hexpand: true; - } - - Button left_page { - styles ["flat"] - sensitive: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.has-pre-page; - icon-name: "left-large-symbolic"; - tooltip-text: "Last Page"; - clicked => $pre_page(); - } - - Button right_page { - styles ["flat"] - sensitive: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.has-next-page; - icon-name: "right-large-symbolic"; - tooltip-text: "Next Page"; - clicked => $next_page(); - } + sensitive: bind template.tabledata-viewmodel as <$PsequelTableDataViewModel>.has-next-page; + icon-name: "right-large-symbolic"; + tooltip-text: "Next Page"; + clicked => $next_page(); } -} \ No newline at end of file + } +} diff --git a/res/gtk/table-fk.blp b/res/gtk/table-fk.blp index a299e5c..87497ba 100644 --- a/res/gtk/table-fk.blp +++ b/res/gtk/table-fk.blp @@ -1,18 +1,20 @@ using Gtk 4.0; using Adw 1; -template $PsequelTableFKInfo : Adw.Bin { - ScrolledWindow { - ColumnView view { +template $PsequelTableFKInfo: Adw.Bin { + ScrolledWindow { + ColumnView view { + model: NoSelection { + model: bind template.fks; + }; - model: NoSelection { - model: bind template.fks; - }; + styles [ + "data-table" + ] - styles ["data-table"] - show-column-separators: true; - show-row-separators: true; - vexpand: true; - } + show-column-separators: true; + show-row-separators: true; + vexpand: true; } -} \ No newline at end of file + } +} diff --git a/res/gtk/table-index.blp b/res/gtk/table-index.blp index 8c98dec..b1a5d98 100644 --- a/res/gtk/table-index.blp +++ b/res/gtk/table-index.blp @@ -1,18 +1,20 @@ using Gtk 4.0; using Adw 1; -template $PsequelTableIndexInfo : Adw.Bin { - ScrolledWindow { - ColumnView view { +template $PsequelTableIndexInfo: Adw.Bin { + ScrolledWindow { + ColumnView view { + model: NoSelection { + model: bind template.indexes; + }; - model: NoSelection { - model: bind template.indexes; - }; + styles [ + "data-table" + ] - styles ["data-table"] - show-column-separators: true; - show-row-separators: true; - vexpand: true; - } + show-column-separators: true; + show-row-separators: true; + vexpand: true; } -} \ No newline at end of file + } +} diff --git a/res/gtk/table-listitem.blp b/res/gtk/table-listitem.blp index 1c5f136..1e03294 100644 --- a/res/gtk/table-listitem.blp +++ b/res/gtk/table-listitem.blp @@ -1,19 +1,19 @@ using Gtk 4.0; template ListItem { - child: Box { - orientation: horizontal; - spacing: 12; + child: Box { + orientation: horizontal; + spacing: 12; - Image { - icon-name: "table-symbolic"; - } + Image { + icon-name: "table-symbolic"; + } - Label { - label: bind template.item as <$PsequelTable>.name; - halign: start; - ellipsize: end; - single-line-mode: true; - } - }; -} \ No newline at end of file + Label { + label: bind template.item as <$PsequelTable>.name; + halign: start; + ellipsize: end; + single-line-mode: true; + } + }; +} diff --git a/res/gtk/table-row.blp b/res/gtk/table-row.blp index 055003c..0f2034b 100644 --- a/res/gtk/table-row.blp +++ b/res/gtk/table-row.blp @@ -1,2 +1 @@ using Gtk 4.0; - diff --git a/res/gtk/table-structure-view.blp b/res/gtk/table-structure-view.blp index 1d4411e..a2d02b2 100644 --- a/res/gtk/table-structure-view.blp +++ b/res/gtk/table-structure-view.blp @@ -2,44 +2,50 @@ using Gtk 4.0; using Gio 2.0; using Adw 1; -template $PsequelTableStructureView : Gtk.Box { - orientation: vertical; - spacing: 4; - margin-start: 8; - margin-top: 12; - margin-bottom: 12; - - // width-request: 960; - // height-request: 800; - - Label { - styles ["title-3"] - label: "Column Info"; - halign: start; - } - - $PsequelTableColInfo columns { - columns: bind template.columns; - } - - - Label { - styles ["title-3"] - label: "Indexes"; - halign: start; - } - - $PsequelTableIndexInfo indexes { - indexes: bind template.indexes; - } - - Label { - styles ["title-3"] - label: "Foreign Keys"; - halign: start; - } - - $PsequelTableFKInfo foreign_keys { - fks: bind template.fks; - } +template $PsequelTableStructureView: Gtk.Box { + orientation: vertical; + spacing: 4; + margin-start: 8; + margin-top: 12; + margin-bottom: 12; + // width-request: 960; + // height-request: 800; + Label { + styles [ + "title-3" + ] + + label: "Column Info"; + halign: start; + } + + $PsequelTableColInfo columns { + columns: bind template.columns; + } + + Label { + styles [ + "title-3" + ] + + label: "Indexes"; + halign: start; + } + + $PsequelTableIndexInfo indexes { + indexes: bind template.indexes; + } + + Label { + styles [ + "title-3" + ] + + label: "Foreign Keys"; + halign: start; + } + + $PsequelTableFKInfo foreign_keys { + fks: bind template.fks; + } } diff --git a/res/gtk/view-data-view.blp b/res/gtk/view-data-view.blp index 2b9fe28..a5a6df0 100644 --- a/res/gtk/view-data-view.blp +++ b/res/gtk/view-data-view.blp @@ -1,86 +1,98 @@ using Gtk 4.0; - using Adw 1; -template $PsequelViewDataView : Gtk.Box { - - width-request: 900; - height-request: 600; +template $PsequelViewDataView: Gtk.Box { + width-request: 900; + height-request: 600; + orientation: vertical; + spacing: 4; + margin-start: 8; + margin-top: 8; + margin-end: 8; + margin-bottom: 8; - orientation: vertical; - spacing: 4; - margin-start: 8; + Box { + visible: false; + spacing: 12; margin-top: 8; - margin-end: 8; margin-bottom: 8; - - Box { - visible: false; - spacing: 12; - margin-top: 8; - margin-bottom: 8; - Entry filter_entry { - hexpand: true; - placeholder-text: "WHERE clause"; - // activate => $on_entry_activated(); - } - - Button filter_btn { - styles ["suggested-action"] - label: "Filter"; - - // clicked => $filter_query(); - } + + Entry filter_entry { + hexpand: true; + placeholder-text: "WHERE clause"; + // activate => $on_entry_activated(); + } + + Button filter_btn { + styles [ + "suggested-action" + ] + + label: "Filter"; + // clicked => $filter_query(); } + } + + $PsequelQueryResults query_results { + current-relation: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.current-relation; + is-loading: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.is-loading; + err-msg: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.err-msg; + } + + Box { + spacing: 8; + // height-request: 40; + Button { + visible: false; + + styles [ + "flat" + ] + + icon-name: "plus-large-symbolic"; + tooltip-text: "Insert Row"; + } + + Button reload { + styles [ + "flat" + ] + + icon-name: "refresh-large-symbolic"; + tooltip-text: "Reload Data"; + clicked => $reload_data(); + } + + Separator {} + + Label status_label { + label: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.row-ranges; + hexpand: true; + halign: start; + } + + Separator {} + + Button left_page { + styles [ + "flat" + ] - $PsequelQueryResults query_results { - current-relation: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.current-relation; - is-loading: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.is-loading; - err-msg: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.err-msg; + icon-name: "left-large-symbolic"; + tooltip-text: "Last Page"; + sensitive: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.has-pre-page; + clicked => $pre_page(); } + Button right_page { + styles [ + "flat" + ] - Box { - spacing: 8; - // height-request: 40; - Button { - visible: false; - styles ["flat"] - icon-name: "plus-large-symbolic"; - tooltip-text: "Insert Row"; - } - - Button reload { - styles ["flat"] - icon-name: "refresh-large-symbolic"; - tooltip-text: "Reload Data"; - clicked => $reload_data(); - } - - Separator {} - - Label status_label { - label: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.row-ranges; - hexpand: true; - halign: start; - } - - Separator {} - - Button left_page { - styles ["flat"] - icon-name: "left-large-symbolic"; - tooltip-text: "Last Page"; - sensitive: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.has-pre-page; - clicked => $pre_page(); - } - - Button right_page { - styles ["flat"] - icon-name: "right-large-symbolic"; - tooltip-text: "Next Page"; - sensitive: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.has-next-page; - clicked => $next_page(); - } + icon-name: "right-large-symbolic"; + tooltip-text: "Next Page"; + sensitive: bind template.viewdata-viewmodel as <$PsequelViewDataViewModel>.has-next-page; + clicked => $next_page(); } -} \ No newline at end of file + } +} diff --git a/res/gtk/view-listitem.blp b/res/gtk/view-listitem.blp index be15143..91364ea 100644 --- a/res/gtk/view-listitem.blp +++ b/res/gtk/view-listitem.blp @@ -1,19 +1,19 @@ using Gtk 4.0; template ListItem { - child: Box { - orientation: horizontal; - spacing: 12; + child: Box { + orientation: horizontal; + spacing: 12; - Image { - icon-name: "category-search-symbolic"; - } + Image { + icon-name: "category-search-symbolic"; + } - Label { - label: bind template.item as <$PsequelView>.name; - halign: start; - ellipsize: end; - single-line-mode: true; - } - }; -} \ No newline at end of file + Label { + label: bind template.item as <$PsequelView>.name; + halign: start; + ellipsize: end; + single-line-mode: true; + } + }; +} diff --git a/res/gtk/view-structure-view.blp b/res/gtk/view-structure-view.blp index ae1dd18..3a54af0 100644 --- a/res/gtk/view-structure-view.blp +++ b/res/gtk/view-structure-view.blp @@ -1,34 +1,36 @@ using Gtk 4.0; - -template $PsequelViewStructureView : Box { - orientation: vertical; - spacing: 4; - margin-start: 8; - margin-top: 12; - margin-bottom: 12; - - // width-request: 960; - // height-request: 800; - - Label { - styles ["title-3"] - label: "Column Info"; - halign: start; - } - - $PsequelTableColInfo columns { - columns: bind template.columns; - } - - - Label { - styles ["title-3"] - label: "Indexes"; - halign: start; - } - - $PsequelTableIndexInfo indexes { - indexes: bind template.indexes; - } -} \ No newline at end of file +template $PsequelViewStructureView: Box { + orientation: vertical; + spacing: 4; + margin-start: 8; + margin-top: 12; + margin-bottom: 12; + // width-request: 960; + // height-request: 800; + Label { + styles [ + "title-3" + ] + + label: "Column Info"; + halign: start; + } + + $PsequelTableColInfo columns { + columns: bind template.columns; + } + + Label { + styles [ + "title-3" + ] + + label: "Indexes"; + halign: start; + } + + $PsequelTableIndexInfo indexes { + indexes: bind template.indexes; + } +} diff --git a/res/gtk/window.blp b/res/gtk/window.blp index 6cfca66..a29db79 100644 --- a/res/gtk/window.blp +++ b/res/gtk/window.blp @@ -1,33 +1,29 @@ using Gtk 4.0; using Adw 1; -template $PsequelWindow : Adw.ApplicationWindow { - default-width: 1280; - default-height: 800; - - width-request: 1280; - height-request: 800; - title: "Psequel"; +template $PsequelWindow: Adw.ApplicationWindow { + default-width: 1280; + default-height: 800; + width-request: 1280; + height-request: 800; + title: "Psequel"; + Adw.ToastOverlay overlay { + Stack stack { + transition-type: slide_up_down; + visible-child-name: bind template.navigation as <$PsequelNavigationService>.current-view; - Adw.ToastOverlay overlay { - Stack stack { - transition-type: slide_up_down; - visible-child-name: bind template.navigation as <$PsequelNavigationService>.current-view; + StackPage { + name: "connection-view"; - StackPage { - name: "connection-view"; - child: $PsequelConnectionView { - }; - } + child: $PsequelConnectionView {}; + } - StackPage query-view { - name: "query-view"; - child: $PsequelSchemaView { - // schema-viewmodel: bind template.schema-viewmodel; - // request_logout => $on_request_logout (); - }; - } - } + StackPage query-view { + name: "query-view"; + + child: $PsequelSchemaView {}; + } } + } } diff --git a/res/meson.build b/res/meson.build index 44b8620..499de23 100644 --- a/res/meson.build +++ b/res/meson.build @@ -10,9 +10,6 @@ blueprints = custom_target('blueprints', input: files( 'gtk/style-switcher.blp', - - 'gtk/connection-form.blp', - 'gtk/connection-sidebar.blp', 'gtk/connection-view.blp', 'gtk/connection-row.blp', 'gtk/connection-listitem.blp', @@ -21,8 +18,6 @@ blueprints = custom_target('blueprints', 'gtk/query-results.blp', 'gtk/schema-view.blp', - 'gtk/schema-sidebar.blp', - 'gtk/schema-main.blp', 'gtk/table-data-view.blp', 'gtk/view-data-view.blp', diff --git a/res/psequel.gresource.xml b/res/psequel.gresource.xml index cb9359a..9bb6095 100644 --- a/res/psequel.gresource.xml +++ b/res/psequel.gresource.xml @@ -3,15 +3,10 @@ gtk/style.css gtk/style-switcher.ui - gtk/connection-form.ui - gtk/connection-sidebar.ui gtk/connection-view.ui gtk/connection-row.ui gtk/connection-listitem.ui - - gtk/schema-main.ui gtk/schema-view.ui - gtk/schema-sidebar.ui gtk/query-editor.ui gtk/query-results.ui diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png index 1a8bd5b..56b5baf 100644 Binary files a/screenshots/screenshot.png and b/screenshots/screenshot.png differ diff --git a/screenshots/screenshot1.png b/screenshots/screenshot1.png index 4c2c8ab..380e488 100644 Binary files a/screenshots/screenshot1.png and b/screenshots/screenshot1.png differ diff --git a/screenshots/screenshot2.png b/screenshots/screenshot2.png index 106eb44..f633033 100644 Binary files a/screenshots/screenshot2.png and b/screenshots/screenshot2.png differ diff --git a/screenshots/screenshot3.png b/screenshots/screenshot3.png index f9e62c8..440ca8d 100644 Binary files a/screenshots/screenshot3.png and b/screenshots/screenshot3.png differ diff --git a/screenshots/screenshot4.png b/screenshots/screenshot4.png index 1a8bd5b..9196496 100644 Binary files a/screenshots/screenshot4.png and b/screenshots/screenshot4.png differ diff --git a/src/application.vala b/src/application.vala index 6f7be87..1f9c3c2 100644 --- a/src/application.vala +++ b/src/application.vala @@ -34,8 +34,6 @@ namespace Psequel { * Static field for easy access in other places. * If need to create many application instance (rarely happens) reconsider this approach. */ - public static Application app; - public static Settings settings; public static ThreadPool background; public int color_scheme { get; set; } @@ -74,22 +72,24 @@ namespace Psequel { base.startup (); GtkSource.init (); set_up_logging (); - debug ("Begin to load resources"); - Application.app = this; - Application.settings = new Settings (this.application_id); + var settings = new Settings (this.application_id); settings.bind ("color-scheme", this, "color_scheme", SettingsBindFlags.GET); this.notify["color-scheme"].connect (update_color_scheme); - try { + var container = Container.instance (); + container.register (settings); + container.register (this); + debug ("Begin to load resources"); + try { // Don't change the max_thread because libpq did not support many query with 1 connection. background = new ThreadPool.with_owned_data ((worker) => { worker.run (); }, 1, false); } catch (ThreadError err) { debug (err.message); - return_if_reached (); + assert_not_reached (); } debug ("Resources loaded"); } @@ -141,13 +141,9 @@ namespace Psequel { typeof (Psequel.StyleSwitcher).ensure (); typeof (Psequel.ConnectionViewModel).ensure (); typeof (Psequel.SchemaView).ensure (); - typeof (Psequel.SchemaSidebar).ensure (); - typeof (Psequel.SchemaMain).ensure (); typeof (Psequel.ConnectionRow).ensure (); typeof (Psequel.ConnectionView).ensure (); - typeof (Psequel.ConnectionSidebar).ensure (); - typeof (Psequel.ConnectionForm).ensure (); typeof (Psequel.QueryResults).ensure (); typeof (Psequel.QueryEditor).ensure (); typeof (Psequel.TableStructureView).ensure (); @@ -206,26 +202,28 @@ namespace Psequel { 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; + create_viewmodels (); + var window = new Window (this); return window; } private Container create_viewmodels () { - var container = new Container (); + var container = Container.instance (); + + var settings = autowire (); // services var sql_service = new SQLService (Application.background); var schema_service = new SchemaService (sql_service); - var repository = new ConnectionRepository (Application.settings); + var connection_repo = new ConnectionRepository (settings); + var query_repo = new QueryRepository (settings); var navigation = new NavigationService (); var export = new ExportService (); var completer = new CompleterService (sql_service); // viewmodels - var conn_vm = new ConnectionViewModel (repository, sql_service, navigation); + var conn_vm = new ConnectionViewModel (connection_repo, sql_service, navigation); var sche_vm = new SchemaViewModel (schema_service); var table_vm = new TableViewModel (sql_service); var view_vm = new ViewViewModel (sql_service); @@ -233,14 +231,14 @@ 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_history_vm = new QueryHistoryViewModel (sql_service, query_repo); var query_vm = new QueryViewModel (query_history_vm); container.register (sql_service); container.register (completer); container.register (schema_service); container.register (export); - container.register (repository); + container.register (connection_repo); container.register (navigation); container.register (conn_vm); container.register (sche_vm); diff --git a/src/meson.build b/src/meson.build index 4c4da12..9c13ad9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,30 +1,5 @@ # Source code -psequel_sources = [ - 'application.vala', - 'ui/connection/ConnectionForm.vala', - 'ui/connection/ConnectionSidebar.vala', - 'ui/connection/ConnectionView.vala', - - - 'ui/schema/SchemaView.vala', - 'ui/schema/SchemaSidebar.vala', - 'ui/schema/SchemaMain.vala', - 'ui/schema/QueryResult.vala', - 'ui/schema/TableStructureView.vala', - 'ui/schema/ViewStructureView.vala', - 'ui/schema/TableColumnInfo.vala', - 'ui/schema/TableForeignKeyInfo.vala', - 'ui/schema/TableIndexInfo.vala', - 'ui/schema/TableDataView.vala', - 'ui/schema/ViewDataView.vala', - - 'ui/editor/QueryEditor.vala', - 'ui/editor/SQLCompletionProvider.vala', - - 'ui/widgets/StyleSwitcher.vala', - 'ui/Window.vala', - 'ui/PreferencesWindow.vala', - +psequel_sources = files([ 'models/Connection.vala', 'models/Schema.vala', 'models/Relation.vala', @@ -33,7 +8,7 @@ psequel_sources = [ # 'models/utils.vala', 'services/SQLService.vala', - 'services/CompleterService.vala', + 'services/SQLCompletionService.vala', 'services/ExportService.vala', 'services/NavigationService.vala', 'services/ConnectionService.vala', @@ -60,15 +35,38 @@ psequel_sources = [ 'utils/ObservableList.vala', 'utils/ValueConverter.vala', 'utils/Event.vala', - 'utils/helpers.vala', 'utils/logging.vala', 'utils/errors.vala', -] + 'utils/types.vala', +]) +ui_sources = files([ + 'ui/views/ConnectionView.vala', + 'ui/views/SchemaView.vala', + 'ui/schema/QueryResult.vala', + 'ui/schema/TableStructureView.vala', + 'ui/schema/ViewStructureView.vala', + 'ui/schema/TableColumnInfo.vala', + 'ui/schema/TableForeignKeyInfo.vala', + 'ui/schema/TableIndexInfo.vala', + 'ui/schema/TableDataView.vala', + 'ui/schema/ViewDataView.vala', + + 'ui/editor/QueryEditor.vala', + + 'ui/widgets/StyleSwitcher.vala', + 'ui/Window.vala', + 'ui/PreferencesWindow.vala', + + 'utils/helpers.vala', + +]) # add_project_arguments(['--gresourcesdir', 'res/' ], language: 'vala') # add_project_arguments(['--enable-experimental' ], language: 'vala') +# Create config data, this will create a config.h file, then be bind to Config class. + conf = configuration_data() conf.set_quoted('APP_ID', app_id) conf.set_quoted('APP_NAME', 'Psequel') @@ -92,17 +90,19 @@ psequel_deps = [ ] + add_project_arguments( '-include', 'config.h', '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()), language: 'c' ) +psequel_sources_main = psequel_sources + ui_sources + files(['application.vala']) -executable('psequel', [blueprint_hack, psequel_resources, psequel_sources], +executable('psequel', [blueprint_hack, psequel_resources, psequel_sources_main], dependencies: psequel_deps, vala_args: [ '--gresourcesdir=' + blueprints_output_dir, ], install: true, -) +) \ No newline at end of file diff --git a/src/models/Connection.vala b/src/models/Connection.vala index 0cdc9c3..21df27b 100644 --- a/src/models/Connection.vala +++ b/src/models/Connection.vala @@ -26,6 +26,7 @@ namespace Psequel { public class Connection : Object, Json.Serializable { public const string DEFAULT = ""; + public const string SCHEME = "postgresql"; public string name { get; set; default = DEFAULT; } public string host { get; set; default = DEFAULT; } @@ -35,6 +36,8 @@ namespace Psequel { public string database { get; set; default = DEFAULT; } public bool use_ssl { get; set; default = false; } + public string options { get; set; default = DEFAULT; } + public Connection (string name = "New Connection") { this._name = name; @@ -47,23 +50,28 @@ namespace Psequel { */ public string url_form () { // postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...] - var builder = new StringBuilder ("postgres://"); - if (user != DEFAULT) { - builder.append (user); - if (password != DEFAULT) { - builder.append (@":$password@"); - } else { - builder.append ("@"); - } + + var parsed_port = 5432; + if (!int.try_parse (port, out parsed_port, null, 10)) { + debug ("Parse port error: defautl to 5432"); + } + + + var safe_user = user != DEFAULT ? user : "postgres"; + var safe_password = password != DEFAULT ? password : "postgres"; + var safe_host = host != DEFAULT ? host : "localhost"; // TODO IPv6 check + var safe_port = port != DEFAULT ? parsed_port : 5432; + var safe_db = database != DEFAULT ? @"/$database" : "/postgres"; + + var safe_options = use_ssl ? "sslmode=required" : "sslmode=disable"; + if (options != DEFAULT) { + safe_options = @"$safe_options&$options"; } - builder.append (host != DEFAULT ? host : "127.0.0.1"); - builder.append (port != DEFAULT ? @":$port" : ":5432"); - builder.append (database != DEFAULT ? @"/$database" : "/postgres"); - builder.append (use_ssl ? "?sslmode=require" : "?sslmode=disable"); + var url = Uri.join_with_user (UriFlags.NONE, SCHEME, safe_user, safe_password, null, safe_host, safe_port, safe_db, safe_options, null); - return builder.free_and_steal (); + return url.to_string (); } /** diff --git a/src/models/Query.vala b/src/models/Query.vala index c4dace8..3b0254d 100644 --- a/src/models/Query.vala +++ b/src/models/Query.vala @@ -4,6 +4,9 @@ namespace Psequel { // Properties must be public, get, set inorder to Json.Serializable works public string sql { get; set; } public string[] params {get; set;} + + public static Regex DDL_REG = /^(CREATE|DROP|RENAME|ALTER|INSERT|UPDATE|DELETE).*$/i; + public Query (string sql) { base(); this.sql = sql; @@ -26,9 +29,7 @@ namespace Psequel { } public bool is_ddl () { - var regex = /CREATE|DROP|RENAME|ALTER|INSERT|UPDATE|DELETE/; - var query = sql.up (6); - return regex.match (query, 0, null); + return DDL_REG.match (sql, 0, null); } public Query clone () { diff --git a/src/models/Schema.vala b/src/models/Schema.vala index 253dd09..0daa201 100644 --- a/src/models/Schema.vala +++ b/src/models/Schema.vala @@ -145,9 +145,8 @@ namespace Psequel { } private void extract_info () { - // Match the index type and column from fk_def - var regex = /FOREIGN KEY \(([$a-zA-Z_, ]+)\) REFERENCES ([a-zA-Z_, ]+)\(([a-zA-Z_, ]+)\)( ON UPDATE (CASCADE))?( ON DELETE (RESTRICT))?/; + var regex = /FOREIGN KEY \(([$a-zA-Z_, ]+)\) REFERENCES ([a-zA-Z_,"]+)\(([a-zA-Z_, ]+)\)( ON UPDATE (CASCADE))?( ON DELETE (RESTRICT))?/; MatchInfo match_info; if (regex.match (fk_def, 0, out match_info)) { diff --git a/src/services/Container.vala b/src/services/Container.vala index 14dac8d..e3e3a4e 100644 --- a/src/services/Container.vala +++ b/src/services/Container.vala @@ -2,6 +2,15 @@ namespace Psequel { public class Container : Object { private HashTable dependencies; + private static Container? _instance; + + public static Container instance() { + if (_instance == null) { + _instance = new Container(); + } + + return _instance; + } /** Manual dependency injection map */ diff --git a/src/services/ExportService.vala b/src/services/ExportService.vala index 0ab2c46..3072e75 100644 --- a/src/services/ExportService.vala +++ b/src/services/ExportService.vala @@ -1,4 +1,4 @@ -namespace Psequel{ +namespace Psequel { public class ExportService : Object { public const string DELIMETER = ","; @@ -28,13 +28,13 @@ namespace Psequel{ rows[i + 1] = string.joinv (DELIMETER, cols); } - + var bytes = new Bytes.take (string.joinv (NEWLINE, rows).data); try { yield dest.replace_contents_bytes_async (bytes, null, false, FileCreateFlags.PRIVATE, null, null); } catch (GLib.Error err) { - throw new PsequelError.EXPORT_ERROR(err.message); + throw new PsequelError.EXPORT_ERROR (err.message); } } @@ -44,6 +44,6 @@ namespace Psequel{ } return str; - } + } } } \ No newline at end of file diff --git a/src/services/NavigationService.vala b/src/services/NavigationService.vala index ee7e899..6af1a5d 100644 --- a/src/services/NavigationService.vala +++ b/src/services/NavigationService.vala @@ -3,18 +3,18 @@ namespace Psequel { public const string CONNECTION_VIEW = "connection-view"; public const string QUERY_VIEW = "query-view"; - public const string[] VIEW_NAMES = {CONNECTION_VIEW, QUERY_VIEW}; + public const string[] VIEW_NAMES = { CONNECTION_VIEW, QUERY_VIEW }; - public string current_view { get; set; default = CONNECTION_VIEW;} + public string current_view { get; set; default = CONNECTION_VIEW; } - public NavigationService() { + 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); diff --git a/src/services/CompleterService.vala b/src/services/SQLCompletionService.vala similarity index 69% rename from src/services/CompleterService.vala rename to src/services/SQLCompletionService.vala index d7bcb7a..509af02 100644 --- a/src/services/CompleterService.vala +++ b/src/services/SQLCompletionService.vala @@ -1,4 +1,127 @@ namespace Psequel { + public class SQLCompletionService : Object, GtkSource.CompletionProvider { + private CompleterService completer; + private Gtk.FilterListModel model; + private Gtk.StringFilter filter; + + private SchemaContext ctx; + + + private SchemaViewModel schema_viewmodel; + public SQLCompletionService () { + base (); + this.schema_viewmodel = autowire (); + this.completer = autowire (); + this.ctx = new SchemaContext (); + + // static_candidates = new List (); + // for (int i = 0; i < PGListerals.KEYWORDS.length; i++) { + // static_candidates.append (new Candidate (PGListerals.KEYWORDS[i], "KEYWORD")); + // } + + // for (int i = 0; i < PGListerals.FUNCTIONS.length; i++) { + // static_candidates.append (new Candidate (PGListerals.FUNCTIONS[i], "FUNCTION")); + // } + + // for (int i = 0; i < PGListerals.DATATYPES.length; i++) { + // static_candidates.append (new Candidate (PGListerals.DATATYPES[i], "DATATYPE")); + // } + + // for (int i = 0; i < PGListerals.RESERVED.length; i++) { + // static_candidates.append (new Candidate (PGListerals.RESERVED[i], "RESERVED")); + // } + + // 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 (Candidate), null, "value"); + filter = new Gtk.StringFilter (expression); + filter.match_mode = Gtk.StringFilterMatchMode.PREFIX; + + model = new Gtk.FilterListModel (null, filter); + } + + public void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal) { + var model = (Candidate) proposal; + var buf = context.get_buffer (); + Gtk.TextIter start, end; + + last_word (buf, out start, out end); + + buf.delete_range (start, end); + buf.insert_at_cursor (model.value, model.value.length); + // buf.insert (ref iter, model.value, model.value.length); + } + + public void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell) { + + var candidate = (Candidate) proposal; + switch (cell.column) { + // name + case GtkSource.CompletionColumn.TYPED_TEXT: + cell.text = candidate.value; + break; + + // group + case GtkSource.CompletionColumn.AFTER: + cell.text = candidate.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 = candidate.value; + // break; + default: break; + } + } + + public async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) { + + var word = last_word (context.get_buffer ()); + var candidates = new ObservableList (); + + candidates.append_all (completer.get_suggestions (this.ctx, word)); + model.model = candidates; + + return model; + } + + public void refilter (GtkSource.CompletionContext context, GLib.ListModel _model) { + var word = last_word (context.get_buffer ()); + var candidates = new ObservableList (); + candidates.append_all (completer.get_suggestions (this.ctx, word)); + model.model = candidates; + } + + 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); + + start = iter; + end = iter; + + start.backward_word_start (); + end.forward_word_end (); + + return buf.get_text (start, end, false); + } + } + public class CompleterService : Object, Observer { public SQLService sql_service { get; construct; } diff --git a/src/services/SQLService.vala b/src/services/SQLService.vala index 58a3735..4b17c40 100644 --- a/src/services/SQLService.vala +++ b/src/services/SQLService.vala @@ -7,13 +7,15 @@ namespace Psequel { */ public class SQLService : Object { - public int query_limit { get; set; } + public int query_limit { get; set; default = 100; } + public int query_timeout { get; set; } public SQLService (ThreadPool background) { Object (); this.background = background; + var settings = autowire (); - Application.settings.bind ("query-limit", this, "query-limit", SettingsBindFlags.GET); + settings.bind ("query-limit", this, "query-limit", SettingsBindFlags.GET); } /** Select info from a table. */ diff --git a/src/ui/PreferencesWindow.vala b/src/ui/PreferencesWindow.vala index 1d7e904..dbdfd0c 100644 --- a/src/ui/PreferencesWindow.vala +++ b/src/ui/PreferencesWindow.vala @@ -7,18 +7,21 @@ namespace Psequel { // { "editor-font", old_choser }, // }; + private Settings? settings; + public PreferencesWindow () { Object (); } construct { + this.settings = autowire (); setup_binding (); defaults (); } private void defaults () { - var desc = Pango.FontDescription.from_string (Application.settings.get_string ("editor-font")); + var desc = Pango.FontDescription.from_string (settings.get_string ("editor-font")); font_label.get_pango_context ().set_font_description (desc); font_label.label = desc.to_string (); @@ -27,12 +30,12 @@ namespace Psequel { } private void setup_binding () { - // Application.settings.bind_with_mapping (string key, GLib.Object object, string property, GLib.SettingsBindFlags flags, GLib.SettingsBindGetMappingShared get_mapping, GLib.SettingsBindSetMappingShared set_mapping, void* user_data, GLib.DestroyNotify? notify) - Application.settings.bind ("query-limit", query_limit, "value", SettingsBindFlags.DEFAULT); - Application.settings.bind ("query-timeout", query_timeout, "value", SettingsBindFlags.DEFAULT); - Application.settings.bind ("connection-timeout", conn_timeout, "value", SettingsBindFlags.DEFAULT); + // settings.bind_with_mapping (string key, GLib.Object object, string property, GLib.SettingsBindFlags flags, GLib.SettingsBindGetMappingShared get_mapping, GLib.SettingsBindSetMappingShared set_mapping, void* user_data, GLib.DestroyNotify? notify) + settings.bind ("query-limit", query_limit, "value", SettingsBindFlags.DEFAULT); + settings.bind ("query-timeout", query_timeout, "value", SettingsBindFlags.DEFAULT); + settings.bind ("connection-timeout", conn_timeout, "value", SettingsBindFlags.DEFAULT); - Application.settings.changed["editor-font"].connect ((_setting, key) => { + settings.changed["editor-font"].connect ((_setting, key) => { var desc = Pango.FontDescription.from_string (_setting.get_string (key)); @@ -46,7 +49,7 @@ namespace Psequel { modal = true, transient_for = this, level = Gtk.FontChooserLevel.FAMILY | Gtk.FontChooserLevel.SIZE | Gtk.FontChooserLevel.STYLE, - font = Application.settings.get_string ("editor-font"), + font = settings.get_string ("editor-font"), }; dialog.set_filter_func ((desc) => { @@ -60,7 +63,7 @@ namespace Psequel { font_label.get_pango_context ().set_font_description (dialog.font_desc); font_label.label = dialog.font_desc.to_string (); - Application.settings.set_string ("editor-font", dialog.font_desc.to_string ()); + settings.set_string ("editor-font", dialog.font_desc.to_string ()); } dialog.destroy (); diff --git a/src/ui/Window.vala b/src/ui/Window.vala index 68d2311..dc162f4 100644 --- a/src/ui/Window.vala +++ b/src/ui/Window.vala @@ -25,9 +25,6 @@ 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 }, @@ -39,11 +36,12 @@ namespace Psequel { public ConnectionViewModel connection_viewmodel { get; construct; } public QueryViewModel query_viewmodel { get; private set; } + private Settings? settings; + - public Window (Application app, Container container) { + public Window (Application app) { Object ( - application: app, - containter: container + application: app ); } @@ -51,10 +49,11 @@ namespace Psequel { this.navigation = autowire (); this.connection_viewmodel = autowire (); this.query_viewmodel = autowire (); + this.settings = autowire (); 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); + settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT); + settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT); this.add_action_entries (ACTIONS, this); } diff --git a/src/ui/connection/ConnectionForm.vala b/src/ui/connection/ConnectionForm.vala deleted file mode 100644 index 8cafab0..0000000 --- a/src/ui/connection/ConnectionForm.vala +++ /dev/null @@ -1,107 +0,0 @@ -namespace Psequel { - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-form.ui")] - public class ConnectionForm : Adw.Bin { - - - public MenuModel menu_model { get; set; } - BindingGroup binddings; - - - public Connection? selected_connection { get; set; } - public bool is_connectting { get; set; } - - public signal void request_database (Connection conn); - public signal void connections_changed (); - - public ConnectionForm () { - Object (); - } - - construct { - debug ("[CONTRUCT] %s", this.name); - // Create group to maped the entry widget to connection data. - this.binddings = create_form_bind_group (); - set_up_bindings (); - - connect_btn.grab_focus (); - } - - private BindingGroup create_form_bind_group () { - - var binddings = new BindingGroup (); - debug ("set_up connection form bindings group"); - binddings.bind ("name", name_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("host", host_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("port", port_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("user", user_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("password", password_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("database", database_entry, "text", SYNC_CREATE | BIDIRECTIONAL); - binddings.bind ("use_ssl", ssl_switch, "active", SYNC_CREATE | BIDIRECTIONAL); - debug ("set_up binddings done"); - - - return binddings; - } - - private void set_up_bindings () { - - this.bind_property ("selected-connection", binddings, "source", BindingFlags.SYNC_CREATE); - - this.bind_property ("is-connectting", connect_btn, "sensitive", INVERT_BOOLEAN | SYNC_CREATE); - - password_entry.bind_property ("text", - password_entry, - "show-peek-icon", - BindingFlags.SYNC_CREATE, - (binding, from_value, ref to_value) => { - - string text = from_value.get_string (); - to_value.set_boolean (text.length > 0); - - return true; - }); - } - - [GtkCallback] - private void on_connect_clicked (Gtk.Button btn) { - request_database (selected_connection); - } - - [GtkCallback] - private void on_entry_activated (Gtk.Entry entry) { - connect_btn.clicked (); - } - - [GtkCallback] - private void on_text_changed (Gtk.Editable editable) { - connections_changed (); - } - - [GtkCallback] - private void on_switch_changed () { - connections_changed (); - } - - [GtkChild] - private unowned Gtk.Button connect_btn; - - [GtkChild] - private unowned Gtk.Entry name_entry; - - [GtkChild] - private unowned Gtk.Entry host_entry; - [GtkChild] - private unowned Gtk.Entry port_entry; - [GtkChild] - private unowned Gtk.Entry user_entry; - [GtkChild] - private unowned Gtk.PasswordEntry password_entry; - - [GtkChild] - private unowned Gtk.Entry database_entry; - - [GtkChild] - private unowned Gtk.Switch ssl_switch; - } -} \ No newline at end of file diff --git a/src/ui/connection/ConnectionSidebar.vala b/src/ui/connection/ConnectionSidebar.vala deleted file mode 100644 index 6a2c0ae..0000000 --- a/src/ui/connection/ConnectionSidebar.vala +++ /dev/null @@ -1,113 +0,0 @@ - -namespace Psequel { - - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-sidebar.ui")] - public class ConnectionSidebar : Gtk.Box { - - - const ActionEntry[] ACTION_ENTRIES = { - { "connect", on_connect_connection }, - { "dupplicate", on_dupplicate_connection }, - { "delete", on_remove_connection }, - }; - - public ObservableList connections { get; set; } - public Connection? selected_connection { get; set; } - - public signal void request_new_connection (); - public signal void request_dup_connection (Connection conn); - public signal void request_remove_connection (Connection conn); - public signal void request_connect_database (Connection conn); - - public ConnectionSidebar () { - Object (); - } - - construct { - debug ("[CONTRUCT] %s", this.name); - - var action_group = new SimpleActionGroup (); - action_group.add_action_entries (ACTION_ENTRIES, this); - this.insert_action_group ("conn", action_group); - - selection_model.bind_property ("selected", this, "selected-connection", - DEFAULT | BIDIRECTIONAL, from_selected, to_selected); - } - - // On add, create new connection and select it. - [GtkCallback] - public void on_add_connection (Gtk.Button btn) { - request_new_connection (); - } - - [GtkCallback] - public void on_connection_active (Gtk.ListView view, uint pos) { - request_connect_database (selected_connection); - } - - // [GtkAction] - private void on_dupplicate_connection () { - request_dup_connection (selected_connection); - } - - // [GtkAction] - private void on_connect_connection () { - request_connect_database (selected_connection); - } - - // [GtkAction] - private void on_remove_connection () { - request_remove_connection (selected_connection); - } - - private bool from_selected (Binding binding, Value from, ref Value to) { - uint pos = from.get_uint (); - - if (pos != Gtk.INVALID_LIST_POSITION) { - to.set_object (selection_model.get_item (pos)); - } - - return true; - } - - private bool to_selected (Binding binding, Value from, ref Value to) { - - Connection conn = (Connection) from.get_object (); - for (uint i = 0; i < selection_model.get_n_items (); i++) { - if (selection_model.get_item (i) == conn) { - to.set_uint (i); - return true; - } - } - - to.set_uint (Gtk.INVALID_LIST_POSITION); - - return true; - } - - [GtkChild] - private unowned Gtk.SingleSelection selection_model; - - // [GtkChild] - // private unowned Gtk.ListView listview; - } - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-row.ui")] - public class ConnectionRow : Gtk.Box { - public Connection item { get; set; } - public uint pos { get; set; } - - - [GtkCallback] - public void on_right_clicked () { - var list_view = this.parent.parent as Gtk.ListView; - list_view.model.select_item (pos, true); - - popover.popup (); - } - - [GtkChild] - private unowned Gtk.PopoverMenu popover; - } -} \ No newline at end of file diff --git a/src/ui/connection/ConnectionView.vala b/src/ui/connection/ConnectionView.vala deleted file mode 100644 index f490c13..0000000 --- a/src/ui/connection/ConnectionView.vala +++ /dev/null @@ -1,48 +0,0 @@ - -namespace Psequel { - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-view.ui")] - public class ConnectionView : Adw.Bin { - - public ConnectionViewModel viewmodel { get; private set; } - - public ConnectionView (Application app) { - Object (); - } - - // Connect event. - construct { - debug ("[CONTRUCT] %s", this.name); - setup_paned (paned); - viewmodel = autowire (); - } - - [GtkCallback] - public void save_connections () { - viewmodel.save_connections (); - } - - [GtkCallback] - public void add_new_connection () { - viewmodel.new_connection (); - } - - [GtkCallback] - public void active_connection (Connection conn) { - viewmodel.active_connection.begin (conn); - } - - [GtkCallback] - public void dup_connection (Connection conn) { - viewmodel.dupplicate_connection (conn); - } - - [GtkCallback] - public void remove_connection (Connection conn) { - viewmodel.remove_connection (conn); - } - - [GtkChild] - private unowned Gtk.Paned paned; - } -} \ No newline at end of file diff --git a/src/ui/editor/QueryEditor.vala b/src/ui/editor/QueryEditor.vala index fd15572..0ac016a 100644 --- a/src/ui/editor/QueryEditor.vala +++ b/src/ui/editor/QueryEditor.vala @@ -21,12 +21,15 @@ namespace Psequel { private GtkSource.Completion completion; - private SQLCompletionProvider provider; + private SQLCompletionService provider; private LanguageManager lang_manager; private StyleSchemeManager style_manager; + private Settings? settings; + private Application? app; + public class QueryEditor () { Object (); } @@ -36,6 +39,8 @@ namespace Psequel { this.export_service = autowire (); this.query_viewmodel = autowire (); this.query_history_viewmodel = autowire (); + this.settings = autowire (); + this.app = autowire (); default_setttings (); selection_model.bind_property ("selected", this, "selected-query", BindingFlags.BIDIRECTIONAL, from_selected, to_selected); @@ -95,7 +100,7 @@ namespace Psequel { completion = editor.get_completion (); completion.select_on_show = true; completion.page_size = 8; - provider = new SQLCompletionProvider (); + provider = new SQLCompletionService (); completion.add_provider (provider); var lang = lang_manager.get_language ("sql"); @@ -114,7 +119,8 @@ namespace Psequel { buffer.tag_table.add (light_tag); buffer.tag_table.add (dark_tag); - Application.app.style_manager.bind_property ("dark", buffer, "style_scheme", BindingFlags.SYNC_CREATE, (binding, from, ref to) => { + + app.style_manager.bind_property ("dark", buffer, "style_scheme", BindingFlags.SYNC_CREATE, (binding, from, ref to) => { var is_dark = from.get_boolean (); if (is_dark) { var scheme = style_manager.get_scheme ("Adwaita-dark"); @@ -158,7 +164,7 @@ namespace Psequel { } - if (Application.app.style_manager.dark) { + if (app.style_manager.dark) { buffer.apply_tag_by_name (DARK_TAG, iter1, iter2); } else { buffer.apply_tag_by_name (LIGHT_TAG, iter1, iter2); @@ -199,7 +205,7 @@ namespace Psequel { } private SimpleAction boolean_state_factory (string name, owned ChangeStateFunc func) { - bool init = Application.settings.get_boolean (name); + bool init = settings.get_boolean (name); var action = new SimpleAction.stateful (name, null, new Variant.boolean (init)); action.activate.connect (toggle_boolean); @@ -221,7 +227,7 @@ namespace Psequel { debug ("Change auto uppercase"); action.set_state (new_state); - Application.settings.set_boolean ("auto-uppercase", autouppercase); + settings.set_boolean ("auto-uppercase", autouppercase); } private void change_auto_exec_history (SimpleAction action, Variant? new_state) { @@ -229,7 +235,7 @@ namespace Psequel { bool auto_exec = new_state.get_boolean (); action.set_state (new_state); - Application.settings.set_boolean ("auto-exec-history", auto_exec); + settings.set_boolean ("auto-exec-history", auto_exec); } private bool from_selected (Binding binding, Value from, ref Value to) { diff --git a/src/ui/editor/SQLCompletionProvider.vala b/src/ui/editor/SQLCompletionProvider.vala deleted file mode 100644 index 1cad55c..0000000 --- a/src/ui/editor/SQLCompletionProvider.vala +++ /dev/null @@ -1,124 +0,0 @@ -namespace Psequel { - public class SQLCompletionProvider : Object, GtkSource.CompletionProvider { - private CompleterService completer; - private Gtk.FilterListModel model; - private Gtk.StringFilter filter; - - private SchemaContext ctx; - - - private SchemaViewModel schema_viewmodel; - public SQLCompletionProvider () { - base (); - this.schema_viewmodel = autowire (); - this.completer = autowire (); - this.ctx = new SchemaContext (); - - // static_candidates = new List (); - // for (int i = 0; i < PGListerals.KEYWORDS.length; i++) { - // static_candidates.append (new Candidate (PGListerals.KEYWORDS[i], "KEYWORD")); - // } - - // for (int i = 0; i < PGListerals.FUNCTIONS.length; i++) { - // static_candidates.append (new Candidate (PGListerals.FUNCTIONS[i], "FUNCTION")); - // } - - // for (int i = 0; i < PGListerals.DATATYPES.length; i++) { - // static_candidates.append (new Candidate (PGListerals.DATATYPES[i], "DATATYPE")); - // } - - // for (int i = 0; i < PGListerals.RESERVED.length; i++) { - // static_candidates.append (new Candidate (PGListerals.RESERVED[i], "RESERVED")); - // } - - // 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 (Candidate), null, "value"); - filter = new Gtk.StringFilter (expression); - filter.match_mode = Gtk.StringFilterMatchMode.PREFIX; - - model = new Gtk.FilterListModel (null, filter); - } - - public void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal) { - var model = (Candidate) proposal; - var buf = context.get_buffer (); - Gtk.TextIter start, end; - - last_word (buf, out start, out end); - - buf.delete_range (start, end); - buf.insert_at_cursor (model.value, model.value.length); - // buf.insert (ref iter, model.value, model.value.length); - } - - public void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell) { - - var candidate = (Candidate) proposal; - switch (cell.column) { - // name - case GtkSource.CompletionColumn.TYPED_TEXT: - cell.text = candidate.value; - break; - - // group - case GtkSource.CompletionColumn.AFTER: - cell.text = candidate.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 = candidate.value; - // break; - default: break; - } - } - - public async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) { - - var word = last_word (context.get_buffer ()); - var candidates = new ObservableList (); - - candidates.append_all (completer.get_suggestions (this.ctx, word)); - model.model = candidates; - - return model; - } - - public void refilter (GtkSource.CompletionContext context, GLib.ListModel _model) { - var word = last_word (context.get_buffer ()); - var candidates = new ObservableList (); - candidates.append_all (completer.get_suggestions (this.ctx, word)); - model.model = candidates; - } - - 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); - - start = iter; - end = iter; - - start.backward_word_start (); - end.forward_word_end (); - - return buf.get_text (start, end, false); - } - } -} \ No newline at end of file diff --git a/src/ui/schema/SchemaMain.vala b/src/ui/schema/SchemaMain.vala deleted file mode 100644 index 877adc9..0000000 --- a/src/ui/schema/SchemaMain.vala +++ /dev/null @@ -1,20 +0,0 @@ - - -namespace Psequel { - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/schema-main.ui")] - public class SchemaMain : Gtk.Box { - public MenuModel menu {get; set;} - // public Table selected_table {get; set;} - public View selected_view {get; set;} - - public TableViewModel table_viewmodel {get; set;} - public ViewViewModel view_viewmodel {get; set;} - public QueryViewModel query_viewmodel { get; set; } - - - /** Whether should show view or table info */ - public string view_mode {get; set;} - - } -} \ No newline at end of file diff --git a/src/ui/schema/SchemaView.vala b/src/ui/schema/SchemaView.vala deleted file mode 100644 index 75600b5..0000000 --- a/src/ui/schema/SchemaView.vala +++ /dev/null @@ -1,23 +0,0 @@ -namespace Psequel { - - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/schema-view.ui")] - public class SchemaView : Adw.Bin { - public SchemaViewModel schema_viewmodel { get; set; } - - - public signal void request_logout (); - - public SchemaView () { - Object (); - } - - construct { - setup_paned (paned); - schema_viewmodel = autowire (); - } - - - [GtkChild] - private unowned Gtk.Paned paned; - } -} \ No newline at end of file diff --git a/src/ui/views/ConnectionView.vala b/src/ui/views/ConnectionView.vala new file mode 100644 index 0000000..dfbfeca --- /dev/null +++ b/src/ui/views/ConnectionView.vala @@ -0,0 +1,199 @@ + +namespace Psequel { + + [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-view.ui")] + public class ConnectionView : Adw.Bin { + + public ConnectionViewModel viewmodel { get; private set; } + public ObservableList connections { get; set; } + public Connection? selected_connection { get; set; } + + BindingGroup bindings; + + const ActionEntry[] ACTION_ENTRIES = { + { "connect", on_connect_connection }, + { "dupplicate", on_dupplicate_connection }, + { "delete", on_remove_connection }, + }; + + public ConnectionView (Application app) { + Object (); + } + + // Connect event. + construct { + debug ("[CONTRUCT] %s", this.name); + setup_paned (paned); + viewmodel = autowire (); + + var action_group = new SimpleActionGroup (); + action_group.add_action_entries (ACTION_ENTRIES, this); + this.insert_action_group ("conn", action_group); + + set_up_bindings (); + } + + // [GtkCallback] + // public void save_connections () { + // viewmodel.save_connections (); + // } + + [GtkCallback] + public void add_new_connection () { + viewmodel.new_connection (); + } + + [GtkCallback] + public void active_connection (Gtk.ListView view, uint pos) { + viewmodel.active_connection.begin (viewmodel.selected_connection); + } + + [GtkCallback] + public void on_connect_clicked (Gtk.Button btn) { + viewmodel.active_connection.begin (viewmodel.selected_connection); + } + + [GtkCallback] + private void on_entry_activated (Gtk.Entry entry) { + on_connect_connection(); + } + + [GtkCallback] + private void on_text_changed (Gtk.Editable editable) { + viewmodel.save_connections (); + } + + [GtkCallback] + private void on_switch_changed () { + viewmodel.save_connections (); + } + + // [GtkAction] + private void on_dupplicate_connection () { + viewmodel.dupplicate_connection (viewmodel.selected_connection); + } + + // [GtkAction] + private void on_connect_connection () { + viewmodel.active_connection.begin (viewmodel.selected_connection); + } + + // [GtkAction] + private void on_remove_connection () { + viewmodel.remove_connection (viewmodel.selected_connection); + } + + private bool from_selected (Binding binding, Value from, ref Value to) { + uint pos = from.get_uint (); + + if (pos != Gtk.INVALID_LIST_POSITION) { + to.set_object (selection_model.get_item (pos)); + } + + return true; + } + + private bool to_selected (Binding binding, Value from, ref Value to) { + + Connection conn = (Connection) from.get_object (); + for (uint i = 0; i < selection_model.get_n_items (); i++) { + if (selection_model.get_item (i) == conn) { + to.set_uint (i); + return true; + } + } + + to.set_uint (Gtk.INVALID_LIST_POSITION); + + return true; + } + + private void set_up_bindings () { + + // Save ref so it does not be cleaned + this.bindings = create_form_bind_group (); + + viewmodel.bind_property ("selected-connection", this.bindings, "source", BindingFlags.SYNC_CREATE); + viewmodel.bind_property ("is-connectting", connect_btn, "sensitive", INVERT_BOOLEAN | SYNC_CREATE); + selection_model.bind_property ("selected", viewmodel, "selected-connection", + DEFAULT | BIDIRECTIONAL, from_selected, to_selected); + password_entry.bind_property ("text", + password_entry, + "show-peek-icon", + BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + + string text = from_value.get_string (); + to_value.set_boolean (text.length > 0); + + return true; + }); + + + } + + private BindingGroup create_form_bind_group () { + + var binddings = new BindingGroup (); + debug ("set_up connection form bindings group"); + binddings.bind ("name", name_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("host", host_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("port", port_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("user", user_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("password", password_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("database", database_entry, "text", SYNC_CREATE | BIDIRECTIONAL); + binddings.bind ("use_ssl", ssl_switch, "active", SYNC_CREATE | BIDIRECTIONAL); + debug ("set_up binddings done"); + + + return binddings; + } + + [GtkChild] + private unowned Gtk.SingleSelection selection_model; + [GtkChild] + private unowned Gtk.Paned paned; + + + [GtkChild] + private unowned Gtk.Button connect_btn; + + [GtkChild] + private unowned Gtk.Entry name_entry; + + [GtkChild] + private unowned Gtk.Entry host_entry; + [GtkChild] + private unowned Gtk.Entry port_entry; + [GtkChild] + private unowned Gtk.Entry user_entry; + [GtkChild] + private unowned Gtk.PasswordEntry password_entry; + + [GtkChild] + private unowned Gtk.Entry database_entry; + + [GtkChild] + private unowned Gtk.Switch ssl_switch; + } + + + + [GtkTemplate (ui = "/me/ppvan/psequel/gtk/connection-row.ui")] + public class ConnectionRow : Gtk.Box { + public Connection item { get; set; } + public uint pos { get; set; } + + + [GtkCallback] + public void on_right_clicked () { + var list_view = this.parent.parent as Gtk.ListView; + list_view.model.select_item (pos, true); + + popover.popup (); + } + + [GtkChild] + private unowned Gtk.PopoverMenu popover; + } +} \ No newline at end of file diff --git a/src/ui/schema/SchemaSidebar.vala b/src/ui/views/SchemaView.vala similarity index 89% rename from src/ui/schema/SchemaSidebar.vala rename to src/ui/views/SchemaView.vala index 22cb743..2e2be9e 100644 --- a/src/ui/schema/SchemaSidebar.vala +++ b/src/ui/views/SchemaView.vala @@ -1,29 +1,35 @@ namespace Psequel { - [GtkTemplate (ui = "/me/ppvan/psequel/gtk/schema-sidebar.ui")] - public class SchemaSidebar : Gtk.Box { - - - public NavigationService navigation_service { get; set; } + [GtkTemplate (ui = "/me/ppvan/psequel/gtk/schema-view.ui")] + public class SchemaView : Adw.Bin { public SchemaViewModel schema_viewmodel { get; set; } public TableViewModel table_viewmodel {get; set;} public ViewViewModel view_viewmodel {get; set;} + public NavigationService navigation_service { get; set; } public string view_mode {get; set;} - public SchemaSidebar () { + public signal void request_logout (); + + public SchemaView () { Object (); } construct { + setup_paned (paned); + this.schema_viewmodel = autowire (); 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); dropdown.notify["selected"].connect (() => { + + if (dropdown.selected == Gtk.INVALID_LIST_POSITION) { + return; + } + schema_viewmodel.select_index ((int)dropdown.selected); }); table_selection.notify["selected"].connect (() => { @@ -40,6 +46,10 @@ namespace Psequel { view_filter.expression = new Gtk.PropertyExpression (typeof (View), null, "name"); } + + [GtkChild] + private unowned Gtk.Paned paned; + [GtkCallback] private void on_table_search (Gtk.SearchEntry entry) { table_filter.search = entry.text; diff --git a/src/ui/widgets/StyleSwitcher.vala b/src/ui/widgets/StyleSwitcher.vala index 4a75930..3576b00 100644 --- a/src/ui/widgets/StyleSwitcher.vala +++ b/src/ui/widgets/StyleSwitcher.vala @@ -18,7 +18,7 @@ namespace Psequel { construct { this.notify["style"].connect (this.on_style_changed); - var s = Application.settings; + var s = autowire (); s.bind ("color-scheme", this, "style", GLib.SettingsBindFlags.DEFAULT); } diff --git a/src/utils/ObservableList.vala b/src/utils/ObservableList.vala index 5f17ea5..6dca652 100644 --- a/src/utils/ObservableList.vala +++ b/src/utils/ObservableList.vala @@ -67,13 +67,13 @@ namespace Psequel { _data.remove (position); } - public void insert (uint pos, Connection conn) { - _data.insert (pos, conn); + public void insert (uint pos, T conn) { + _data.insert (pos, (Object)conn); } - public uint indexof (Connection conn) { + public uint indexof (T conn) { uint pos; - _data.find (conn, out pos); + _data.find ((Object)conn, out pos); return pos; } diff --git a/src/utils/helpers.vala b/src/utils/helpers.vala index 6232350..c431b10 100644 --- a/src/utils/helpers.vala +++ b/src/utils/helpers.vala @@ -40,13 +40,10 @@ namespace Psequel { }); } - public T autowire () { - var container = Window.temp; - return (T)container.find_type (typeof (T)); - } public Adw.MessageDialog create_dialog (string heading, string body) { - var window = Application.app.active_window; + var app = autowire (); + var window = app.active_window; var dialog = new Adw.MessageDialog (window, heading, body); dialog.close_response = "okay"; @@ -54,47 +51,4 @@ namespace Psequel { return dialog; } - - /** - * Util class to mesure execution time than log it using debug() - */ - public class TimePerf { - private static int64 _start; - private static int64 _end; - - public static void begin () { - _start = GLib.get_real_time (); - } - - public static void end () { - _end = GLib.get_real_time (); - - debug (@"Elapsed: %$(int64.FORMAT) ms", (_end - _start) / 1000); - } - - public static int64 uend () { - _end = GLib.get_real_time (); - - debug (@"Elapsed: %$(int64.FORMAT) μs", (_end - _start)); - - return _end - _start; - } - } - - public delegate void Job (); - public class Worker { - public string thread_name { private set; get; } - public Job task; - - public Worker (string name, owned Job task) { - this.thread_name = name; - this.task = (owned) task; - } - - public void run () { - - // Thread.usleep ((ulong)1e6); - this.task (); - } - } } \ No newline at end of file diff --git a/src/utils/types.vala b/src/utils/types.vala new file mode 100644 index 0000000..39a572e --- /dev/null +++ b/src/utils/types.vala @@ -0,0 +1,49 @@ +namespace Psequel { + /** + * Util class to mesure execution time than log it using debug() + */ + public class TimePerf { + private static int64 _start; + private static int64 _end; + + public static void begin () { + _start = GLib.get_real_time (); + } + + public static void end () { + _end = GLib.get_real_time (); + + debug (@"Elapsed: %$(int64.FORMAT) ms", (_end - _start) / 1000); + } + + public static int64 uend () { + _end = GLib.get_real_time (); + + debug (@"Elapsed: %$(int64.FORMAT) μs", (_end - _start)); + + return _end - _start; + } + } + + public delegate void Job (); + public class Worker { + public string thread_name { private set; get; } + public Job task; + + public Worker (string name, owned Job task) { + this.thread_name = name; + this.task = (owned) task; + } + + public void run () { + + // Thread.usleep ((ulong)1e6); + this.task (); + } + } + + public T autowire () { + var container = Container.instance (); + return (T)container.find_type (typeof (T)); + } +} \ No newline at end of file diff --git a/src/viewmodels/ConnectionViewModel.vala b/src/viewmodels/ConnectionViewModel.vala index 76ccefe..465c755 100644 --- a/src/viewmodels/ConnectionViewModel.vala +++ b/src/viewmodels/ConnectionViewModel.vala @@ -1,10 +1,23 @@ namespace Psequel { public class ConnectionViewModel : BaseViewModel { + + public enum State { + IDLE, + CONNECTING, + ERROR + } + + uint timeout_id = 0; public ConnectionRepository repository { get; private set; } public SQLService sql_service { get; private set; } public NavigationService navigation_service { get; private set; } + + + // States + + public State current_state {get; private set; default = State.IDLE;} public ObservableList connections { get; private set; default = new ObservableList (); } public Connection? selected_connection { get; set; } @@ -65,6 +78,7 @@ namespace Psequel { public async void active_connection (Connection connection) { this.is_connectting = true; + this.current_state = State.CONNECTING; try { yield sql_service.connect_db (connection); this.navigation_service.navigate (NavigationService.QUERY_VIEW); @@ -72,8 +86,10 @@ namespace Psequel { } catch (PsequelError err) { debug ("Error: %s", err.message); - create_dialog ("Connection Error", err.message.dup ()).present (); + this.current_state = State.ERROR; + return; } + this.current_state = State.IDLE; this.is_connectting = false; } diff --git a/src/viewmodels/QueryHistoryViewModel.vala b/src/viewmodels/QueryHistoryViewModel.vala index 553199d..3a2329a 100644 --- a/src/viewmodels/QueryHistoryViewModel.vala +++ b/src/viewmodels/QueryHistoryViewModel.vala @@ -2,7 +2,7 @@ namespace Psequel { public class QueryHistoryViewModel : BaseViewModel { const string AUTO_EXEC_HISTORY = "auto-exec-history"; - public QueryRepository query_repository {get; private set;} + public ObservableList query_history { get; set; default = new ObservableList (); } public Query? selected_query { get; set; } @@ -19,13 +19,13 @@ namespace Psequel { public string query_time { get; private set; } public SQLService sql_service { get; construct; } + public QueryRepository query_repository {get; construct;} - public QueryHistoryViewModel (SQLService sql_service) { - Object (sql_service: sql_service); - query_repository = new QueryRepository (Application.settings); - query_history.append_all (query_repository.get_queries ()); + public QueryHistoryViewModel (SQLService sql_service, QueryRepository query_repository) { + Object (sql_service: sql_service, query_repository: query_repository); + this.query_history.append_all (query_repository.get_queries ()); this.notify["current-relation"].connect (() => { success = true; row_affected = @"$(current_relation.row_affected) row affected."; @@ -51,9 +51,11 @@ namespace Psequel { } public async void exec_history (Query query) { - if (Application.settings.get_boolean (AUTO_EXEC_HISTORY)) { - yield run_query_internal (query); - } + // if (Application.settings.get_boolean (AUTO_EXEC_HISTORY)) { + + // } + + yield run_query_internal (query); query_history.remove (query); query_history.prepend (query); diff --git a/src/viewmodels/SchemaViewModel.vala b/src/viewmodels/SchemaViewModel.vala index ef1fa93..6d88f91 100644 --- a/src/viewmodels/SchemaViewModel.vala +++ b/src/viewmodels/SchemaViewModel.vala @@ -6,6 +6,9 @@ namespace Psequel { public ObservableList schemas { get; set; default = new ObservableList (); } public Schema? current_schema { get; set; } + // Needed to interact with gtk.dropdown properly. + public int current_index { get; set;} + public SchemaRepository repository; // Services @@ -22,9 +25,11 @@ namespace Psequel { } public void select_index (int index) { + if (index < 0 || index >= schemas.size) { return; } + debug ("Select index %d, %s\n", index, schemas[index].name); select_schema.begin (schemas[index]); } @@ -55,15 +60,23 @@ namespace Psequel { private async void select_schema (Schema schema) throws PsequelError { debug ("Select schema: %s", schema.name); current_schema = schema; - // force reload - this.notify_property ("current-schema"); } /** List schema from database. */ private async void list_schemas () throws PsequelError { var unload_schemas = yield schema_service.schema_list (); - schemas.append_all (unload_schemas); + var public_first_schemas = new List (); + + foreach(var s in unload_schemas) { + if (s.name == DEFAULT) { + public_first_schemas.insert (s, 0); + } else { + public_first_schemas.append (s); + } + } + + schemas.append_all (public_first_schemas); } } } \ No newline at end of file diff --git a/src/viewmodels/TableViewModel.vala b/src/viewmodels/TableViewModel.vala index a0c8871..3f3b566 100644 --- a/src/viewmodels/TableViewModel.vala +++ b/src/viewmodels/TableViewModel.vala @@ -8,6 +8,8 @@ namespace Psequel { public SQLService sql_service {get; private set;} + // public signal void table_changed (Table table); + public TableViewModel (SQLService sql_service) { base (); this.sql_service = sql_service; diff --git a/test/connection_test.vala b/test/connection_test.vala new file mode 100644 index 0000000..0ce1aa8 --- /dev/null +++ b/test/connection_test.vala @@ -0,0 +1,48 @@ +using Psequel; +using GLib; + + +public int main(string[] args) { + Test.init(ref args); + + Test.add_func("/connection/default", () => { + var conn = new Connection("test-conn") { + + }; + var expect_url = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"; + + assert_cmpstr(conn.url_form(), CompareOperator.EQ, expect_url); + }); + + Test.add_func("/connection/user", () => { + var conn = new Connection("test-conn") { + user = "psequel", + password = "psequel-pass" + }; + var expect_url = "postgresql://psequel:psequel-pass@localhost:5432/postgres?sslmode=disable"; + + assert_cmpstr(conn.url_form(), CompareOperator.EQ, expect_url); + }); + + Test.add_func("/connection/database", () => { + var conn = new Connection("test-conn") { + database = "psequel-test" + }; + var expect_url = "postgresql://postgres:postgres@localhost:5432/psequel-test?sslmode=disable"; + + assert_cmpstr(conn.url_form(), CompareOperator.EQ, expect_url); + }); + + Test.add_func("/connection/percent-encode", () => { + var conn = new Connection("test-conn") { + user = "psequel_test", + password = "===", + options = "options=-c synchronous_commit=off" + }; + var expect_url = "postgresql://psequel_test:===@localhost:5432/postgres?sslmode=disable&options=-c%20synchronous_commit=off"; + + assert_cmpstr(conn.url_form(), CompareOperator.EQ, expect_url); + }); + + return Test.run(); +} \ No newline at end of file diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..49525ed --- /dev/null +++ b/test/meson.build @@ -0,0 +1,36 @@ + + +# VSGI tests +psequel_tests = [ + 'connection', +] + + + + +configure_file(output: 'config.h', configuration: conf) +# env = environment() +# env.set('G_MESSAGES_DEBUG', 'Psequel') + +foreach name : psequel_tests + test(name, + executable(name, name + '_test.vala', [psequel_sources], dependencies: psequel_deps), + env: [ + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), + ], + protocol: 'tap', + ) +endforeach + + +# if get_option('with_query') +# test('query', +# executable('query', 'query_test.vala', [psequel_sources], dependencies: psequel_deps), +# env: [ +# 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), +# 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), +# ], +# protocol: 'tap', +# ) +# endif