From 357633bbd72da7c8d434517a314269fdd47cdbed Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Wed, 24 Apr 2024 21:56:33 +0900 Subject: [PATCH 01/11] MVC --- data/resources/ui/main-window.blp | 4 +- po/POTFILES | 2 +- src/ComboEntry.vala | 136 ------------------------------ src/Define.vala | 5 ++ src/Model/ComboEntryModel.vala | 59 +++++++++++++ src/Model/MainWindowModel.vala | 24 ++++++ src/View/ComboEntry.vala | 105 +++++++++++++++++++++++ src/{ => View}/MainWindow.vala | 20 +++++ src/meson.build | 6 +- 9 files changed, 220 insertions(+), 141 deletions(-) delete mode 100644 src/ComboEntry.vala create mode 100644 src/Model/ComboEntryModel.vala create mode 100644 src/Model/MainWindowModel.vala create mode 100644 src/View/ComboEntry.vala rename src/{ => View}/MainWindow.vala (58%) diff --git a/data/resources/ui/main-window.blp b/data/resources/ui/main-window.blp index f9b5079..9cadfca 100644 --- a/data/resources/ui/main-window.blp +++ b/data/resources/ui/main-window.blp @@ -54,7 +54,7 @@ template $MainWindow : Gtk.ApplicationWindow { // Left pane for input text $ComboEntry source_combo_entry { - id: "source"; + text-type: source; description: _("Convert from:"); editable: true; } @@ -66,7 +66,7 @@ template $MainWindow : Gtk.ApplicationWindow { // Right pane for output text $ComboEntry result_combo_entry { - id: "result"; + text-type: result; description: _("Convert to:"); // Make the text view uneditable, otherwise the app freezes editable: false; diff --git a/po/POTFILES b/po/POTFILES index bf415e8..787a176 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -2,4 +2,4 @@ data/konbucase.desktop.in data/konbucase.metainfo.xml.in data/resources/ui/combo-entry.blp data/resources/ui/main-window.blp -src/ComboEntry.vala +src/View/ComboEntry.vala diff --git a/src/ComboEntry.vala b/src/ComboEntry.vala deleted file mode 100644 index 1e5ecaf..0000000 --- a/src/ComboEntry.vala +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano - */ - -[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/combo-entry.ui")] -public class ComboEntry : Gtk.Box { - public signal void text_copied (); - - public string id { get; construct; } - public string description { get; construct; } - public bool editable { get; construct; } - - private static ChCase.Converter _converter; - public static ChCase.Converter converter { - get { - var source_case_type = (Define.CaseType) Application.settings.get_enum ("source-case-type"); - var result_case_type = (Define.CaseType) Application.settings.get_enum ("result-case-type"); - - if (_converter == null) { - _converter = new ChCase.Converter.with_case ( - Util.to_chcase_case (source_case_type), - Util.to_chcase_case (result_case_type) - ); - } - - return _converter; - } - } - - [GtkChild] - private unowned Gtk.DropDown case_dropdown; - [GtkChild] - private unowned Gtk.Image case_info_button_icon; - [GtkChild] - private unowned Gtk.Button copy_clipboard_button; - [GtkChild] - private unowned GtkSource.View source_view; - - private GtkSource.Buffer source_buffer; - - public ComboEntry (string id, string description, bool editable) { - Object ( - id: id, - description: description, - editable: editable - ); - } - - construct { - case_dropdown.selected = Application.settings.get_enum ("%s-case-type".printf (id)); - - case_info_button_icon.tooltip_text = set_info_button_tooltip ((Define.CaseType) case_dropdown.selected); - - source_buffer = new GtkSource.Buffer (null); - source_view.buffer = source_buffer; - - update_buttons (); - - Application.settings.bind ("%s-text".printf (id), source_buffer, "text", SettingsBindFlags.DEFAULT); - - source_buffer.notify["text"].connect (() => { - update_buttons (); - convert_case (); - }); - - case_dropdown.notify["selected"].connect (() => { - case_info_button_icon.tooltip_text = set_info_button_tooltip ((Define.CaseType) case_dropdown.selected); - - Application.settings.set_enum ("%s-case-type".printf (id), (Define.CaseType) case_dropdown.selected); - - if (Application.settings.get_string ("%s-text".printf (id)) != "") { - var source_case_type = (Define.CaseType) Application.settings.get_enum ("source-case-type"); - var result_case_type = (Define.CaseType) Application.settings.get_enum ("result-case-type"); - - converter.source_case = Util.to_chcase_case (source_case_type); - converter.result_case = Util.to_chcase_case (result_case_type); - convert_case (); - } - }); - - copy_clipboard_button.clicked.connect (() => { - get_clipboard ().set_text (source_buffer.text); - text_copied (); - }); - - var gtk_settings = Gtk.Settings.get_default (); - gtk_settings.notify["gtk-application-prefer-dark-theme"].connect (() => { - update_color_style (gtk_settings.gtk_application_prefer_dark_theme); - }); - update_color_style (gtk_settings.gtk_application_prefer_dark_theme); - } - - private void update_buttons () { - copy_clipboard_button.sensitive = Application.settings.get_string ("%s-text".printf (id)) != ""; - } - - private void convert_case () { - Application.settings.set_string ( - "result-text", - converter.convert_case (Application.settings.get_string ("source-text")) - ); - } - - public void update_color_style (bool is_prefer_dark) { - if (is_prefer_dark) { - source_buffer.style_scheme = new GtkSource.StyleSchemeManager ().get_scheme ("solarized-dark"); - } else { - source_buffer.style_scheme = new GtkSource.StyleSchemeManager ().get_scheme ("solarized-light"); - } - } - - private string set_info_button_tooltip (Define.CaseType case_type) { - switch (case_type) { - case Define.CaseType.SPACE_SEPARATED: - return _("Each word is separated by a space"); - case Define.CaseType.CAMEL: - return _("The first character of compound words is in lowercase"); - case Define.CaseType.PASCAL: - return _("The first character of compound words is in uppercase"); - case Define.CaseType.SNAKE: - return _("Each word is separated by an underscore"); - case Define.CaseType.KEBAB: - return _("Each word is separated by a hyphen"); - case Define.CaseType.SENTENCE: - return _("The first character of the first word in the sentence is in uppercase"); - default: - warning ("Unexpected case, no tooltip is shown."); - return ""; - } - } - - public unowned GtkSource.View get_source_view () { - return source_view; - } -} diff --git a/src/Define.vala b/src/Define.vala index c057dae..aa42b1d 100644 --- a/src/Define.vala +++ b/src/Define.vala @@ -16,6 +16,11 @@ namespace Define { public const string DARK = "dark"; } + public enum TextType { + SOURCE, + RESULT, + } + // Make sure to match with source-type enum in the gschema public enum CaseType { SPACE_SEPARATED = ChCase.Case.SPACE_SEPARATED, diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala new file mode 100644 index 0000000..9e287ff --- /dev/null +++ b/src/Model/ComboEntryModel.vala @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano + */ + +public class ComboEntryModel : Object { + public Define.CaseType case_type { get; set; } + public GtkSource.Buffer buffer { get; private set; } + public string text { get; set; } + + public Define.TextType text_type { get; construct; } + + private GtkSource.StyleSchemeManager style_scheme_manager; + + private struct ComboEntryCtx { + string key_case_type; + string key_text; + } + + private const ComboEntryCtx[] CTX_TABLE = { + { "source-case-type", "source-text" }, + { "result-case-type", "result-text" }, + }; + + public ComboEntryModel (Define.TextType text_type) { + Object ( + text_type: text_type + ); + } + + construct { + style_scheme_manager = new GtkSource.StyleSchemeManager (); + + case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); + + buffer = new GtkSource.Buffer (null); + + Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); + buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + + var gtk_settings = Gtk.Settings.get_default (); + gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var prefer_dark = (bool) from_value; + if (prefer_dark) { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-dark")); + } else { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-light")); + } + + return true; + }); + + notify["case-type"].connect (() => { + Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); + }); + } +} diff --git a/src/Model/MainWindowModel.vala b/src/Model/MainWindowModel.vala new file mode 100644 index 0000000..72be638 --- /dev/null +++ b/src/Model/MainWindowModel.vala @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano + */ + +public class MainWindowModel : Object { + private ChCase.Converter converter; + + public MainWindowModel () { + } + + construct { + converter = new ChCase.Converter (); + } + + public void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { + converter.source_case = Util.to_chcase_case (source_case); + converter.result_case = Util.to_chcase_case (result_case); + } + + public string convert_case (string source_text) { + return converter.convert_case (source_text); + } +} diff --git a/src/View/ComboEntry.vala b/src/View/ComboEntry.vala new file mode 100644 index 0000000..ad36ef9 --- /dev/null +++ b/src/View/ComboEntry.vala @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano + */ + +[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/combo-entry.ui")] +public class ComboEntry : Gtk.Box { + public signal void dropdown_changed (); + public signal void text_changed (); + public signal void text_copied (); + + public Define.TextType text_type { get; construct; } + public string description { get; construct; } + public bool editable { get; construct; } + public string text { + get { + return model.text; + } + set { + model.text = value; + } + } + public Define.CaseType case_type { + get { + return model.case_type; + } + } + + [GtkChild] + private unowned Gtk.DropDown case_dropdown; + [GtkChild] + private unowned Gtk.Image case_info_button_icon; + [GtkChild] + private unowned Gtk.Button copy_clipboard_button; + [GtkChild] + private unowned GtkSource.View source_view; + + private ComboEntryModel model; + + public ComboEntry (Define.TextType text_type, string description, bool editable) { + Object ( + text_type: text_type, + description: description, + editable: editable + ); + } + + construct { + model = new ComboEntryModel (text_type); + + case_dropdown.selected = model.case_type; + + case_info_button_icon.tooltip_text = get_info_button_tooltip (model.case_type); + + source_view.buffer = model.buffer; + + case_dropdown.notify["selected"].connect (() => { + model.case_type = (Define.CaseType) case_dropdown.selected; + case_info_button_icon.tooltip_text = get_info_button_tooltip (model.case_type); + + dropdown_changed (); + }); + + model.bind_property ("text", copy_clipboard_button, "sensitive", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var text = (string) from_value; + to_value.set_boolean (text != ""); + return true; + }); + + model.notify["text"].connect (() => { + text_changed (); + }); + + copy_clipboard_button.clicked.connect (() => { + get_clipboard ().set_text (model.text); + text_copied (); + }); + } + + private string get_info_button_tooltip (Define.CaseType case_type) { + switch (case_type) { + case Define.CaseType.SPACE_SEPARATED: + return _("Each word is separated by a space"); + case Define.CaseType.CAMEL: + return _("The first character of compound words is in lowercase"); + case Define.CaseType.PASCAL: + return _("The first character of compound words is in uppercase"); + case Define.CaseType.SNAKE: + return _("Each word is separated by an underscore"); + case Define.CaseType.KEBAB: + return _("Each word is separated by a hyphen"); + case Define.CaseType.SENTENCE: + return _("The first character of the first word in the sentence is in uppercase"); + default: + warning ("Unexpected case, no tooltip is shown."); + return ""; + } + } + + public unowned GtkSource.View get_source_view () { + return source_view; + } +} diff --git a/src/MainWindow.vala b/src/View/MainWindow.vala similarity index 58% rename from src/MainWindow.vala rename to src/View/MainWindow.vala index 17f7d42..4c82a0b 100644 --- a/src/MainWindow.vala +++ b/src/View/MainWindow.vala @@ -12,6 +12,8 @@ public class MainWindow : Gtk.ApplicationWindow { [GtkChild] private unowned Granite.Toast toast; + private MainWindowModel model; + public MainWindow (Application app) { Object ( application: app @@ -19,12 +21,30 @@ public class MainWindow : Gtk.ApplicationWindow { } construct { + model = new MainWindowModel (); + source_combo_entry.get_source_view ().grab_focus (); + source_combo_entry.text_changed.connect (() => { + do_convert (); + }); + + source_combo_entry.dropdown_changed.connect (() => { + do_convert (); + }); + result_combo_entry.dropdown_changed.connect (() => { + do_convert (); + }); + source_combo_entry.text_copied.connect (show_toast); result_combo_entry.text_copied.connect (show_toast); } + private void do_convert () { + model.set_case_type (source_combo_entry.case_type, result_combo_entry.case_type); + result_combo_entry.text = model.convert_case (source_combo_entry.text); + } + private void show_toast () { toast.send_notification (); } diff --git a/src/meson.build b/src/meson.build index 6f9d60d..1ebeb09 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,10 +14,12 @@ dependencies = [ ] sources = files( + 'Model/ComboEntryModel.vala', + 'Model/MainWindowModel.vala', + 'View/ComboEntry.vala', + 'View/MainWindow.vala', 'Application.vala', - 'ComboEntry.vala', 'Define.vala', - 'MainWindow.vala', 'StyleManager.vala', 'Util.vala', ) From 651f5ecba7fb5073e3bae30b598b22b9c527c805 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Wed, 24 Apr 2024 23:22:55 +0900 Subject: [PATCH 02/11] Review --- data/meson.build | 2 - data/resources/meson.build | 21 ------- src/Model/ComboEntryModel.vala | 11 ++-- .../View/ComboEntry.blp | 0 src/View/ComboEntry.vala | 55 +++++++++---------- .../View/MainWindow.blp | 0 src/View/MainWindow.vala | 10 ++-- .../resources => src}/konbucase.gresource.xml | 4 +- src/meson.build | 40 +++++++++++--- 9 files changed, 69 insertions(+), 74 deletions(-) delete mode 100644 data/resources/meson.build rename data/resources/ui/combo-entry.blp => src/View/ComboEntry.blp (100%) rename data/resources/ui/main-window.blp => src/View/MainWindow.blp (100%) rename {data/resources => src}/konbucase.gresource.xml (51%) diff --git a/data/meson.build b/data/meson.build index cd3adaf..c33ca3e 100644 --- a/data/meson.build +++ b/data/meson.build @@ -29,5 +29,3 @@ install_data( rename: meson.project_name() + '.gschema.xml', install_dir: get_option('datadir') / 'glib-2.0' / 'schemas', ) - -subdir('resources') diff --git a/data/resources/meson.build b/data/resources/meson.build deleted file mode 100644 index 8b3b170..0000000 --- a/data/resources/meson.build +++ /dev/null @@ -1,21 +0,0 @@ -blueprints = custom_target('blueprints', - input: files( - 'ui/combo-entry.blp', - 'ui/main-window.blp', - ), - output: '.', - command: [ - find_program('blueprint-compiler'), - 'batch-compile', - '@OUTPUT@', - '@CURRENT_SOURCE_DIR@', - '@INPUT@' - ] -) - -asresources = gnome.compile_resources( - 'as-resources', - 'konbucase.gresource.xml', - dependencies: blueprints, - c_name: 'as' -) diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala index 9e287ff..e0db76e 100644 --- a/src/Model/ComboEntryModel.vala +++ b/src/Model/ComboEntryModel.vala @@ -4,19 +4,19 @@ */ public class ComboEntryModel : Object { + public Define.TextType text_type { get; construct; } + public Define.CaseType case_type { get; set; } public GtkSource.Buffer buffer { get; private set; } public string text { get; set; } - public Define.TextType text_type { get; construct; } - private GtkSource.StyleSchemeManager style_scheme_manager; + private Gtk.Settings gtk_settings; private struct ComboEntryCtx { string key_case_type; string key_text; } - private const ComboEntryCtx[] CTX_TABLE = { { "source-case-type", "source-text" }, { "result-case-type", "result-text" }, @@ -29,16 +29,15 @@ public class ComboEntryModel : Object { } construct { - style_scheme_manager = new GtkSource.StyleSchemeManager (); - case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); buffer = new GtkSource.Buffer (null); + style_scheme_manager = new GtkSource.StyleSchemeManager (); + gtk_settings = Gtk.Settings.get_default (); Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); - var gtk_settings = Gtk.Settings.get_default (); gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { diff --git a/data/resources/ui/combo-entry.blp b/src/View/ComboEntry.blp similarity index 100% rename from data/resources/ui/combo-entry.blp rename to src/View/ComboEntry.blp diff --git a/src/View/ComboEntry.vala b/src/View/ComboEntry.vala index ad36ef9..4140953 100644 --- a/src/View/ComboEntry.vala +++ b/src/View/ComboEntry.vala @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano */ -[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/combo-entry.ui")] +[GtkTemplate (ui = "/com/github/ryonakano/konbucase/View/ComboEntry.ui")] public class ComboEntry : Gtk.Box { public signal void dropdown_changed (); public signal void text_changed (); @@ -35,6 +35,18 @@ public class ComboEntry : Gtk.Box { [GtkChild] private unowned GtkSource.View source_view; + private struct CaseInfo { + string info_text; + } + private const CaseInfo[] CASE_INFO_TBL = { + { N_("Each word is separated by a space") }, + { N_("The first character of compound words is in lowercase") }, + { N_("The first character of compound words is in uppercase") }, + { N_("Each word is separated by an underscore") }, + { N_("Each word is separated by a hyphen") }, + { N_("The first character of the first word in the sentence is in uppercase") }, + }; + private ComboEntryModel model; public ComboEntry (Define.TextType text_type, string description, bool editable) { @@ -50,17 +62,19 @@ public class ComboEntry : Gtk.Box { case_dropdown.selected = model.case_type; - case_info_button_icon.tooltip_text = get_info_button_tooltip (model.case_type); - source_view.buffer = model.buffer; case_dropdown.notify["selected"].connect (() => { model.case_type = (Define.CaseType) case_dropdown.selected; - case_info_button_icon.tooltip_text = get_info_button_tooltip (model.case_type); dropdown_changed (); }); + copy_clipboard_button.clicked.connect (() => { + get_clipboard ().set_text (model.text); + text_copied (); + }); + model.bind_property ("text", copy_clipboard_button, "sensitive", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { @@ -69,34 +83,17 @@ public class ComboEntry : Gtk.Box { return true; }); + model.bind_property ("case-type", case_info_button_icon, "tooltip-text", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var case_type = (Define.CaseType) from_value; + to_value.set_string (_(CASE_INFO_TBL[case_type].info_text)); + return true; + }); + model.notify["text"].connect (() => { text_changed (); }); - - copy_clipboard_button.clicked.connect (() => { - get_clipboard ().set_text (model.text); - text_copied (); - }); - } - - private string get_info_button_tooltip (Define.CaseType case_type) { - switch (case_type) { - case Define.CaseType.SPACE_SEPARATED: - return _("Each word is separated by a space"); - case Define.CaseType.CAMEL: - return _("The first character of compound words is in lowercase"); - case Define.CaseType.PASCAL: - return _("The first character of compound words is in uppercase"); - case Define.CaseType.SNAKE: - return _("Each word is separated by an underscore"); - case Define.CaseType.KEBAB: - return _("Each word is separated by a hyphen"); - case Define.CaseType.SENTENCE: - return _("The first character of the first word in the sentence is in uppercase"); - default: - warning ("Unexpected case, no tooltip is shown."); - return ""; - } } public unowned GtkSource.View get_source_view () { diff --git a/data/resources/ui/main-window.blp b/src/View/MainWindow.blp similarity index 100% rename from data/resources/ui/main-window.blp rename to src/View/MainWindow.blp diff --git a/src/View/MainWindow.vala b/src/View/MainWindow.vala index 4c82a0b..15dfcf8 100644 --- a/src/View/MainWindow.vala +++ b/src/View/MainWindow.vala @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano */ -[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/main-window.ui")] +[GtkTemplate (ui = "/com/github/ryonakano/konbucase/View/MainWindow.ui")] public class MainWindow : Gtk.ApplicationWindow { [GtkChild] private unowned ComboEntry source_combo_entry; @@ -25,10 +25,6 @@ public class MainWindow : Gtk.ApplicationWindow { source_combo_entry.get_source_view ().grab_focus (); - source_combo_entry.text_changed.connect (() => { - do_convert (); - }); - source_combo_entry.dropdown_changed.connect (() => { do_convert (); }); @@ -36,6 +32,10 @@ public class MainWindow : Gtk.ApplicationWindow { do_convert (); }); + source_combo_entry.text_changed.connect (() => { + do_convert (); + }); + source_combo_entry.text_copied.connect (show_toast); result_combo_entry.text_copied.connect (show_toast); } diff --git a/data/resources/konbucase.gresource.xml b/src/konbucase.gresource.xml similarity index 51% rename from data/resources/konbucase.gresource.xml rename to src/konbucase.gresource.xml index 8d33ab9..3d1dfb1 100644 --- a/data/resources/konbucase.gresource.xml +++ b/src/konbucase.gresource.xml @@ -1,7 +1,7 @@ - ui/combo-entry.ui - ui/main-window.ui + View/ComboEntry.ui + View/MainWindow.ui diff --git a/src/meson.build b/src/meson.build index 1ebeb09..73ca227 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,6 +13,36 @@ dependencies = [ dependency('gtksourceview-5'), ] +if get_option('use_submodule') + chcase_subproject = subproject('chcase') + chcase_deps = chcase_subproject.get_variable('libchcase') + dependencies += chcase_deps +else + dependencies += dependency('chcase') +endif + +blueprints = custom_target('blueprints', + input: files( + 'View/ComboEntry.blp', + 'View/MainWindow.blp', + ), + output: '.', + command: [ + find_program('blueprint-compiler'), + 'batch-compile', + '@OUTPUT@', + '@CURRENT_SOURCE_DIR@', + '@INPUT@' + ] +) + +asresources = gnome.compile_resources( + 'as-resources', + 'konbucase.gresource.xml', + dependencies: blueprints, + c_name: 'as' +) + sources = files( 'Model/ComboEntryModel.vala', 'Model/MainWindowModel.vala', @@ -24,14 +54,6 @@ sources = files( 'Util.vala', ) -if get_option('use_submodule') - chcase_subproject = subproject('chcase') - chcase_deps = chcase_subproject.get_variable('libchcase') - dependencies += chcase_deps -else - dependencies += dependency('chcase') -endif - executable( meson.project_name(), asresources, @@ -41,7 +63,7 @@ executable( # https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/18 # https://github.com/mesonbuild/meson/commit/ae857e841b0a6b9b595583e74f5e21676bb83f9d vala_args: [ - '--gresourcesdir=data/resources' + '--gresourcesdir=src' ], dependencies: dependencies, install: true From 3c99f3e038325258189a70ed0051ad2dc2634249 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Wed, 24 Apr 2024 23:24:09 +0900 Subject: [PATCH 03/11] Update POTFILES --- po/POTFILES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/POTFILES b/po/POTFILES index 787a176..fcea920 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,5 +1,5 @@ data/konbucase.desktop.in data/konbucase.metainfo.xml.in -data/resources/ui/combo-entry.blp -data/resources/ui/main-window.blp +src/View/ComboEntry.blp src/View/ComboEntry.vala +src/View/MainWindow.blp From 26a654112bd4e2a37e55ba707cf119b062da1933 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Thu, 25 Apr 2024 07:51:32 +0900 Subject: [PATCH 04/11] Review --- src/Define.vala | 5 +++++ src/Model/ComboEntryModel.vala | 15 ++++++++++----- src/Model/MainWindowModel.vala | 12 ++++++++++++ src/View/ComboEntry.vala | 17 +++++++++++------ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Define.vala b/src/Define.vala index aa42b1d..3badac2 100644 --- a/src/Define.vala +++ b/src/Define.vala @@ -16,8 +16,13 @@ namespace Define { public const string DARK = "dark"; } + /** + * Type of the text. + */ public enum TextType { + /** Text that is converted. */ SOURCE, + /** Text after conversion. */ RESULT, } diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala index e0db76e..59997a5 100644 --- a/src/Model/ComboEntryModel.vala +++ b/src/Model/ComboEntryModel.vala @@ -14,12 +14,14 @@ public class ComboEntryModel : Object { private Gtk.Settings gtk_settings; private struct ComboEntryCtx { + /** GSettings key name that stores last case type. */ string key_case_type; + /** GSettings key name that stores last text. */ string key_text; } private const ComboEntryCtx[] CTX_TABLE = { - { "source-case-type", "source-text" }, - { "result-case-type", "result-text" }, + { "source-case-type", "source-text" }, // Define.TextType.SOURCE + { "result-case-type", "result-text" }, // Define.TextType.RESULT }; public ComboEntryModel (Define.TextType text_type) { @@ -29,15 +31,14 @@ public class ComboEntryModel : Object { } construct { - case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); - buffer = new GtkSource.Buffer (null); style_scheme_manager = new GtkSource.StyleSchemeManager (); gtk_settings = Gtk.Settings.get_default (); - Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); + // Sync with buffer text buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + // Apply theme changes to the source view gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { @@ -51,6 +52,10 @@ public class ComboEntryModel : Object { return true; }); + // Sync with GSettings + Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); + // We can't use Settings.bind here because it seems to expose the data in string instead of enum + case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); notify["case-type"].connect (() => { Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); }); diff --git a/src/Model/MainWindowModel.vala b/src/Model/MainWindowModel.vala index 72be638..065c489 100644 --- a/src/Model/MainWindowModel.vala +++ b/src/Model/MainWindowModel.vala @@ -13,11 +13,23 @@ public class MainWindowModel : Object { converter = new ChCase.Converter (); } + /** + * Set case type of source and result texts. + * + * @param source_case case type of source text + * @param result_case case type of result text + */ public void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { converter.source_case = Util.to_chcase_case (source_case); converter.result_case = Util.to_chcase_case (result_case); } + /** + * Convert case of source_text to the case set with {@link set_case_type}. + * + * @param source_text text that is converted + * @return text after conversion + */ public string convert_case (string source_text) { return converter.convert_case (source_text); } diff --git a/src/View/ComboEntry.vala b/src/View/ComboEntry.vala index 4140953..589d47c 100644 --- a/src/View/ComboEntry.vala +++ b/src/View/ComboEntry.vala @@ -5,8 +5,11 @@ [GtkTemplate (ui = "/com/github/ryonakano/konbucase/View/ComboEntry.ui")] public class ComboEntry : Gtk.Box { + /** Notify change of currently selected item in {@link case_dropdown}. */ public signal void dropdown_changed (); + /** Notify change of text in {@link source_view}. */ public signal void text_changed (); + /** Notify that text in {@link source_view} is copied. */ public signal void text_copied (); public Define.TextType text_type { get; construct; } @@ -36,15 +39,16 @@ public class ComboEntry : Gtk.Box { private unowned GtkSource.View source_view; private struct CaseInfo { + /** Tooltip text for {@link case_info_button_icon}. */ string info_text; } private const CaseInfo[] CASE_INFO_TBL = { - { N_("Each word is separated by a space") }, - { N_("The first character of compound words is in lowercase") }, - { N_("The first character of compound words is in uppercase") }, - { N_("Each word is separated by an underscore") }, - { N_("Each word is separated by a hyphen") }, - { N_("The first character of the first word in the sentence is in uppercase") }, + { N_("Each word is separated by a space") }, // Define.CaseType.SPACE_SEPARATED + { N_("The first character of compound words is in lowercase") }, // Define.CaseType.CAMEL + { N_("The first character of compound words is in uppercase") }, // Define.CaseType.PASCAL + { N_("Each word is separated by an underscore") }, // Define.CaseType.SNAKE + { N_("Each word is separated by a hyphen") }, // Define.CaseType.KEBAB + { N_("The first character of the first word in the sentence is in uppercase") }, // Define.CaseType.SENTENCE }; private ComboEntryModel model; @@ -96,6 +100,7 @@ public class ComboEntry : Gtk.Box { }); } + // UI elements defined in .ui files can't be a property, so defines a getter method instead public unowned GtkSource.View get_source_view () { return source_view; } From 657484a5ccb30dfdb070692738ea8041f17a7529 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Fri, 26 Apr 2024 21:44:23 +0900 Subject: [PATCH 05/11] Review --- data/meson.build | 2 + .../resources}/konbucase.gresource.xml | 4 +- data/resources/meson.build | 21 +++++ .../resources/ui/combo-entry.blp | 0 .../resources/ui/main-window.blp | 0 po/POTFILES | 6 +- src/{View => }/ComboEntry.vala | 81 +++++++++++++------ src/{View => }/MainWindow.vala | 35 ++++++-- src/Model/ComboEntryModel.vala | 63 --------------- src/Model/MainWindowModel.vala | 36 --------- src/meson.build | 30 +------ 11 files changed, 115 insertions(+), 163 deletions(-) rename {src => data/resources}/konbucase.gresource.xml (51%) create mode 100644 data/resources/meson.build rename src/View/ComboEntry.blp => data/resources/ui/combo-entry.blp (100%) rename src/View/MainWindow.blp => data/resources/ui/main-window.blp (100%) rename src/{View => }/ComboEntry.vala (52%) rename src/{View => }/MainWindow.vala (51%) delete mode 100644 src/Model/ComboEntryModel.vala delete mode 100644 src/Model/MainWindowModel.vala diff --git a/data/meson.build b/data/meson.build index c33ca3e..cd3adaf 100644 --- a/data/meson.build +++ b/data/meson.build @@ -29,3 +29,5 @@ install_data( rename: meson.project_name() + '.gschema.xml', install_dir: get_option('datadir') / 'glib-2.0' / 'schemas', ) + +subdir('resources') diff --git a/src/konbucase.gresource.xml b/data/resources/konbucase.gresource.xml similarity index 51% rename from src/konbucase.gresource.xml rename to data/resources/konbucase.gresource.xml index 3d1dfb1..8d33ab9 100644 --- a/src/konbucase.gresource.xml +++ b/data/resources/konbucase.gresource.xml @@ -1,7 +1,7 @@ - View/ComboEntry.ui - View/MainWindow.ui + ui/combo-entry.ui + ui/main-window.ui diff --git a/data/resources/meson.build b/data/resources/meson.build new file mode 100644 index 0000000..8b3b170 --- /dev/null +++ b/data/resources/meson.build @@ -0,0 +1,21 @@ +blueprints = custom_target('blueprints', + input: files( + 'ui/combo-entry.blp', + 'ui/main-window.blp', + ), + output: '.', + command: [ + find_program('blueprint-compiler'), + 'batch-compile', + '@OUTPUT@', + '@CURRENT_SOURCE_DIR@', + '@INPUT@' + ] +) + +asresources = gnome.compile_resources( + 'as-resources', + 'konbucase.gresource.xml', + dependencies: blueprints, + c_name: 'as' +) diff --git a/src/View/ComboEntry.blp b/data/resources/ui/combo-entry.blp similarity index 100% rename from src/View/ComboEntry.blp rename to data/resources/ui/combo-entry.blp diff --git a/src/View/MainWindow.blp b/data/resources/ui/main-window.blp similarity index 100% rename from src/View/MainWindow.blp rename to data/resources/ui/main-window.blp diff --git a/po/POTFILES b/po/POTFILES index fcea920..bf415e8 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,5 +1,5 @@ data/konbucase.desktop.in data/konbucase.metainfo.xml.in -src/View/ComboEntry.blp -src/View/ComboEntry.vala -src/View/MainWindow.blp +data/resources/ui/combo-entry.blp +data/resources/ui/main-window.blp +src/ComboEntry.vala diff --git a/src/View/ComboEntry.vala b/src/ComboEntry.vala similarity index 52% rename from src/View/ComboEntry.vala rename to src/ComboEntry.vala index 589d47c..060af86 100644 --- a/src/View/ComboEntry.vala +++ b/src/ComboEntry.vala @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano */ -[GtkTemplate (ui = "/com/github/ryonakano/konbucase/View/ComboEntry.ui")] +[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/combo-entry.ui")] public class ComboEntry : Gtk.Box { /** Notify change of currently selected item in {@link case_dropdown}. */ public signal void dropdown_changed (); @@ -15,19 +15,24 @@ public class ComboEntry : Gtk.Box { public Define.TextType text_type { get; construct; } public string description { get; construct; } public bool editable { get; construct; } - public string text { - get { - return model.text; - } - set { - model.text = value; - } - } - public Define.CaseType case_type { - get { - return model.case_type; - } + + public Define.CaseType case_type { get; set; } + public GtkSource.Buffer buffer { get; private set; } + public string text { get; set; } + + private GtkSource.StyleSchemeManager style_scheme_manager; + private Gtk.Settings gtk_settings; + + private struct ComboEntryCtx { + /** GSettings key name that stores last case type. */ + string key_case_type; + /** GSettings key name that stores last text. */ + string key_text; } + private const ComboEntryCtx[] CTX_TABLE = { + { "source-case-type", "source-text" }, // Define.TextType.SOURCE + { "result-case-type", "result-text" }, // Define.TextType.RESULT + }; [GtkChild] private unowned Gtk.DropDown case_dropdown; @@ -51,8 +56,6 @@ public class ComboEntry : Gtk.Box { { N_("The first character of the first word in the sentence is in uppercase") }, // Define.CaseType.SENTENCE }; - private ComboEntryModel model; - public ComboEntry (Define.TextType text_type, string description, bool editable) { Object ( text_type: text_type, @@ -62,24 +65,52 @@ public class ComboEntry : Gtk.Box { } construct { - model = new ComboEntryModel (text_type); - - case_dropdown.selected = model.case_type; - - source_view.buffer = model.buffer; + buffer = new GtkSource.Buffer (null); + style_scheme_manager = new GtkSource.StyleSchemeManager (); + gtk_settings = Gtk.Settings.get_default (); + + case_dropdown.selected = case_type; + + source_view.buffer = buffer; + + // Sync with buffer text + buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + + // Apply theme changes to the source view + gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var prefer_dark = (bool) from_value; + if (prefer_dark) { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-dark")); + } else { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-light")); + } + + return true; + }); + + // Sync with GSettings + Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); + // We can't use Settings.bind here because it seems to expose the data in string instead of enum + case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); + notify["case-type"].connect (() => { + Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); + }); case_dropdown.notify["selected"].connect (() => { - model.case_type = (Define.CaseType) case_dropdown.selected; + case_type = (Define.CaseType) case_dropdown.selected; dropdown_changed (); }); copy_clipboard_button.clicked.connect (() => { - get_clipboard ().set_text (model.text); + get_clipboard ().set_text (text); + text_copied (); }); - model.bind_property ("text", copy_clipboard_button, "sensitive", + bind_property ("text", copy_clipboard_button, "sensitive", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { var text = (string) from_value; @@ -87,7 +118,7 @@ public class ComboEntry : Gtk.Box { return true; }); - model.bind_property ("case-type", case_info_button_icon, "tooltip-text", + bind_property ("case-type", case_info_button_icon, "tooltip-text", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { var case_type = (Define.CaseType) from_value; @@ -95,7 +126,7 @@ public class ComboEntry : Gtk.Box { return true; }); - model.notify["text"].connect (() => { + buffer.changed.connect (() => { text_changed (); }); } diff --git a/src/View/MainWindow.vala b/src/MainWindow.vala similarity index 51% rename from src/View/MainWindow.vala rename to src/MainWindow.vala index 15dfcf8..34da5fe 100644 --- a/src/View/MainWindow.vala +++ b/src/MainWindow.vala @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano */ -[GtkTemplate (ui = "/com/github/ryonakano/konbucase/View/MainWindow.ui")] +[GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/main-window.ui")] public class MainWindow : Gtk.ApplicationWindow { [GtkChild] private unowned ComboEntry source_combo_entry; @@ -12,7 +12,7 @@ public class MainWindow : Gtk.ApplicationWindow { [GtkChild] private unowned Granite.Toast toast; - private MainWindowModel model; + private ChCase.Converter converter; public MainWindow (Application app) { Object ( @@ -21,7 +21,7 @@ public class MainWindow : Gtk.ApplicationWindow { } construct { - model = new MainWindowModel (); + converter = new ChCase.Converter (); source_combo_entry.get_source_view ().grab_focus (); @@ -40,12 +40,33 @@ public class MainWindow : Gtk.ApplicationWindow { result_combo_entry.text_copied.connect (show_toast); } + private void show_toast () { + toast.send_notification (); + } + private void do_convert () { - model.set_case_type (source_combo_entry.case_type, result_combo_entry.case_type); - result_combo_entry.text = model.convert_case (source_combo_entry.text); + set_case_type (source_combo_entry.case_type, result_combo_entry.case_type); + result_combo_entry.text = convert_case (source_combo_entry.text); } - private void show_toast () { - toast.send_notification (); + /** + * Set case type of source and result texts. + * + * @param source_case case type of source text + * @param result_case case type of result text + */ + public void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { + converter.source_case = Util.to_chcase_case (source_case); + converter.result_case = Util.to_chcase_case (result_case); + } + + /** + * Convert case of source_text to the case set with {@link set_case_type}. + * + * @param source_text text that is converted + * @return text after conversion + */ + public string convert_case (string source_text) { + return converter.convert_case (source_text); } } diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala deleted file mode 100644 index 59997a5..0000000 --- a/src/Model/ComboEntryModel.vala +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano - */ - -public class ComboEntryModel : Object { - public Define.TextType text_type { get; construct; } - - public Define.CaseType case_type { get; set; } - public GtkSource.Buffer buffer { get; private set; } - public string text { get; set; } - - private GtkSource.StyleSchemeManager style_scheme_manager; - private Gtk.Settings gtk_settings; - - private struct ComboEntryCtx { - /** GSettings key name that stores last case type. */ - string key_case_type; - /** GSettings key name that stores last text. */ - string key_text; - } - private const ComboEntryCtx[] CTX_TABLE = { - { "source-case-type", "source-text" }, // Define.TextType.SOURCE - { "result-case-type", "result-text" }, // Define.TextType.RESULT - }; - - public ComboEntryModel (Define.TextType text_type) { - Object ( - text_type: text_type - ); - } - - construct { - buffer = new GtkSource.Buffer (null); - style_scheme_manager = new GtkSource.StyleSchemeManager (); - gtk_settings = Gtk.Settings.get_default (); - - // Sync with buffer text - buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); - - // Apply theme changes to the source view - gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", - BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, - (binding, from_value, ref to_value) => { - var prefer_dark = (bool) from_value; - if (prefer_dark) { - to_value.set_object (style_scheme_manager.get_scheme ("solarized-dark")); - } else { - to_value.set_object (style_scheme_manager.get_scheme ("solarized-light")); - } - - return true; - }); - - // Sync with GSettings - Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); - // We can't use Settings.bind here because it seems to expose the data in string instead of enum - case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); - notify["case-type"].connect (() => { - Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); - }); - } -} diff --git a/src/Model/MainWindowModel.vala b/src/Model/MainWindowModel.vala deleted file mode 100644 index 065c489..0000000 --- a/src/Model/MainWindowModel.vala +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano - */ - -public class MainWindowModel : Object { - private ChCase.Converter converter; - - public MainWindowModel () { - } - - construct { - converter = new ChCase.Converter (); - } - - /** - * Set case type of source and result texts. - * - * @param source_case case type of source text - * @param result_case case type of result text - */ - public void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { - converter.source_case = Util.to_chcase_case (source_case); - converter.result_case = Util.to_chcase_case (result_case); - } - - /** - * Convert case of source_text to the case set with {@link set_case_type}. - * - * @param source_text text that is converted - * @return text after conversion - */ - public string convert_case (string source_text) { - return converter.convert_case (source_text); - } -} diff --git a/src/meson.build b/src/meson.build index 73ca227..24d3b8f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,35 +21,11 @@ else dependencies += dependency('chcase') endif -blueprints = custom_target('blueprints', - input: files( - 'View/ComboEntry.blp', - 'View/MainWindow.blp', - ), - output: '.', - command: [ - find_program('blueprint-compiler'), - 'batch-compile', - '@OUTPUT@', - '@CURRENT_SOURCE_DIR@', - '@INPUT@' - ] -) - -asresources = gnome.compile_resources( - 'as-resources', - 'konbucase.gresource.xml', - dependencies: blueprints, - c_name: 'as' -) - sources = files( - 'Model/ComboEntryModel.vala', - 'Model/MainWindowModel.vala', - 'View/ComboEntry.vala', - 'View/MainWindow.vala', 'Application.vala', + 'ComboEntry.vala', 'Define.vala', + 'MainWindow.vala', 'StyleManager.vala', 'Util.vala', ) @@ -63,7 +39,7 @@ executable( # https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/18 # https://github.com/mesonbuild/meson/commit/ae857e841b0a6b9b595583e74f5e21676bb83f9d vala_args: [ - '--gresourcesdir=src' + '--gresourcesdir=data/resources' ], dependencies: dependencies, install: true From 291fe0c46b9b63aaa7ec04b0f43e8f6aa4c2919c Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Fri, 26 Apr 2024 22:17:04 +0900 Subject: [PATCH 06/11] Review --- src/ComboEntry.vala | 38 +++++++++++++++++++------------------- src/MainWindow.vala | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ComboEntry.vala b/src/ComboEntry.vala index 060af86..06df084 100644 --- a/src/ComboEntry.vala +++ b/src/ComboEntry.vala @@ -34,15 +34,6 @@ public class ComboEntry : Gtk.Box { { "result-case-type", "result-text" }, // Define.TextType.RESULT }; - [GtkChild] - private unowned Gtk.DropDown case_dropdown; - [GtkChild] - private unowned Gtk.Image case_info_button_icon; - [GtkChild] - private unowned Gtk.Button copy_clipboard_button; - [GtkChild] - private unowned GtkSource.View source_view; - private struct CaseInfo { /** Tooltip text for {@link case_info_button_icon}. */ string info_text; @@ -56,6 +47,15 @@ public class ComboEntry : Gtk.Box { { N_("The first character of the first word in the sentence is in uppercase") }, // Define.CaseType.SENTENCE }; + [GtkChild] + private unowned Gtk.DropDown case_dropdown; + [GtkChild] + private unowned Gtk.Image case_info_button_icon; + [GtkChild] + private unowned Gtk.Button copy_clipboard_button; + [GtkChild] + private unowned GtkSource.View source_view; + public ComboEntry (Define.TextType text_type, string description, bool editable) { Object ( text_type: text_type, @@ -65,14 +65,15 @@ public class ComboEntry : Gtk.Box { } construct { - buffer = new GtkSource.Buffer (null); - style_scheme_manager = new GtkSource.StyleSchemeManager (); - gtk_settings = Gtk.Settings.get_default (); - + case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); case_dropdown.selected = case_type; + buffer = new GtkSource.Buffer (null); source_view.buffer = buffer; + style_scheme_manager = new GtkSource.StyleSchemeManager (); + gtk_settings = Gtk.Settings.get_default (); + // Sync with buffer text buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); @@ -90,10 +91,8 @@ public class ComboEntry : Gtk.Box { return true; }); - // Sync with GSettings - Application.settings.bind (CTX_TABLE[text_type].key_text, buffer, "text", SettingsBindFlags.DEFAULT); - // We can't use Settings.bind here because it seems to expose the data in string instead of enum - case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); + Application.settings.bind (CTX_TABLE[text_type].key_text, this, "text", SettingsBindFlags.DEFAULT); + notify["case-type"].connect (() => { Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); }); @@ -106,10 +105,10 @@ public class ComboEntry : Gtk.Box { copy_clipboard_button.clicked.connect (() => { get_clipboard ().set_text (text); - text_copied (); }); + // Make copy button insensitive when text is blank bind_property ("text", copy_clipboard_button, "sensitive", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { @@ -118,6 +117,7 @@ public class ComboEntry : Gtk.Box { return true; }); + // Set tooltip text of info button depending on selected case type bind_property ("case-type", case_info_button_icon, "tooltip-text", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { @@ -126,7 +126,7 @@ public class ComboEntry : Gtk.Box { return true; }); - buffer.changed.connect (() => { + notify["text"].connect (() => { text_changed (); }); } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 34da5fe..df8ac76 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -55,7 +55,7 @@ public class MainWindow : Gtk.ApplicationWindow { * @param source_case case type of source text * @param result_case case type of result text */ - public void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { + private void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { converter.source_case = Util.to_chcase_case (source_case); converter.result_case = Util.to_chcase_case (result_case); } @@ -66,7 +66,7 @@ public class MainWindow : Gtk.ApplicationWindow { * @param source_text text that is converted * @return text after conversion */ - public string convert_case (string source_text) { + private string convert_case (string source_text) { return converter.convert_case (source_text); } } From 9a60c34501cb8c83cfd8b2a74ed0094d60645cd3 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 27 Apr 2024 16:33:54 +0900 Subject: [PATCH 07/11] Separate to model --- data/resources/ui/combo-entry.blp | 2 + data/resources/ui/main-window.blp | 17 +++++- src/ComboEntry.vala | 98 +++---------------------------- src/MainWindow.vala | 53 ++++++----------- src/Model/ComboEntryModel.vala | 84 ++++++++++++++++++++++++++ src/Model/MainWindowModel.vala | 48 +++++++++++++++ src/meson.build | 2 + 7 files changed, 177 insertions(+), 127 deletions(-) create mode 100644 src/Model/ComboEntryModel.vala create mode 100644 src/Model/MainWindowModel.vala diff --git a/data/resources/ui/combo-entry.blp b/data/resources/ui/combo-entry.blp index ac39e2f..59b57b2 100644 --- a/data/resources/ui/combo-entry.blp +++ b/data/resources/ui/combo-entry.blp @@ -44,6 +44,7 @@ template $ComboEntry : Gtk.Box { // Info about current choice of case type Gtk.Image case_info_button_icon { icon-name: "dialog-information-symbolic"; + tooltip-text: bind template.model as <$ComboEntryModel>.case-type-description; } // Button to copy text in the text view @@ -56,6 +57,7 @@ template $ComboEntry : Gtk.Box { // Text area Gtk.ScrolledWindow { GtkSource.View source_view { + buffer: bind template.model as <$ComboEntryModel>.buffer; wrap-mode: word_char; hexpand: true; vexpand: true; diff --git a/data/resources/ui/main-window.blp b/data/resources/ui/main-window.blp index 9cadfca..9ccc6ee 100644 --- a/data/resources/ui/main-window.blp +++ b/data/resources/ui/main-window.blp @@ -26,6 +26,19 @@ menu main_menu { } } +$ComboEntryModel source_model { + text-type: source; +} + +$ComboEntryModel result_model { + text-type: result; +} + +$MainWindowModel window_model { + source-model: source_model; + result-model: result_model; +} + /** * MainWindow: The app window */ @@ -54,7 +67,7 @@ template $MainWindow : Gtk.ApplicationWindow { // Left pane for input text $ComboEntry source_combo_entry { - text-type: source; + model: source_model; description: _("Convert from:"); editable: true; } @@ -66,7 +79,7 @@ template $MainWindow : Gtk.ApplicationWindow { // Right pane for output text $ComboEntry result_combo_entry { - text-type: result; + model: result_model; description: _("Convert to:"); // Make the text view uneditable, otherwise the app freezes editable: false; diff --git a/src/ComboEntry.vala b/src/ComboEntry.vala index 06df084..de452e6 100644 --- a/src/ComboEntry.vala +++ b/src/ComboEntry.vala @@ -5,111 +5,39 @@ [GtkTemplate (ui = "/com/github/ryonakano/konbucase/ui/combo-entry.ui")] public class ComboEntry : Gtk.Box { - /** Notify change of currently selected item in {@link case_dropdown}. */ public signal void dropdown_changed (); - /** Notify change of text in {@link source_view}. */ - public signal void text_changed (); - /** Notify that text in {@link source_view} is copied. */ - public signal void text_copied (); + public signal void copy_button_clicked (); - public Define.TextType text_type { get; construct; } + public ComboEntryModel model { get; set construct; } public string description { get; construct; } public bool editable { get; construct; } - public Define.CaseType case_type { get; set; } - public GtkSource.Buffer buffer { get; private set; } - public string text { get; set; } - - private GtkSource.StyleSchemeManager style_scheme_manager; - private Gtk.Settings gtk_settings; - - private struct ComboEntryCtx { - /** GSettings key name that stores last case type. */ - string key_case_type; - /** GSettings key name that stores last text. */ - string key_text; - } - private const ComboEntryCtx[] CTX_TABLE = { - { "source-case-type", "source-text" }, // Define.TextType.SOURCE - { "result-case-type", "result-text" }, // Define.TextType.RESULT - }; - - private struct CaseInfo { - /** Tooltip text for {@link case_info_button_icon}. */ - string info_text; - } - private const CaseInfo[] CASE_INFO_TBL = { - { N_("Each word is separated by a space") }, // Define.CaseType.SPACE_SEPARATED - { N_("The first character of compound words is in lowercase") }, // Define.CaseType.CAMEL - { N_("The first character of compound words is in uppercase") }, // Define.CaseType.PASCAL - { N_("Each word is separated by an underscore") }, // Define.CaseType.SNAKE - { N_("Each word is separated by a hyphen") }, // Define.CaseType.KEBAB - { N_("The first character of the first word in the sentence is in uppercase") }, // Define.CaseType.SENTENCE - }; - [GtkChild] private unowned Gtk.DropDown case_dropdown; [GtkChild] - private unowned Gtk.Image case_info_button_icon; - [GtkChild] private unowned Gtk.Button copy_clipboard_button; [GtkChild] private unowned GtkSource.View source_view; - public ComboEntry (Define.TextType text_type, string description, bool editable) { + public ComboEntry (ComboEntryModel model, string description, bool editable) { Object ( - text_type: text_type, + model: model, description: description, editable: editable ); } construct { - case_type = (Define.CaseType) Application.settings.get_enum (CTX_TABLE[text_type].key_case_type); - case_dropdown.selected = case_type; - - buffer = new GtkSource.Buffer (null); - source_view.buffer = buffer; - - style_scheme_manager = new GtkSource.StyleSchemeManager (); - gtk_settings = Gtk.Settings.get_default (); - - // Sync with buffer text - buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); - - // Apply theme changes to the source view - gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", - BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, - (binding, from_value, ref to_value) => { - var prefer_dark = (bool) from_value; - if (prefer_dark) { - to_value.set_object (style_scheme_manager.get_scheme ("solarized-dark")); - } else { - to_value.set_object (style_scheme_manager.get_scheme ("solarized-light")); - } - - return true; - }); - - Application.settings.bind (CTX_TABLE[text_type].key_text, this, "text", SettingsBindFlags.DEFAULT); - - notify["case-type"].connect (() => { - Application.settings.set_enum (CTX_TABLE[text_type].key_case_type, case_type); - }); - case_dropdown.notify["selected"].connect (() => { - case_type = (Define.CaseType) case_dropdown.selected; - dropdown_changed (); }); copy_clipboard_button.clicked.connect (() => { - get_clipboard ().set_text (text); - text_copied (); + copy_button_clicked (); }); // Make copy button insensitive when text is blank - bind_property ("text", copy_clipboard_button, "sensitive", + model.bind_property ("text", copy_clipboard_button, "sensitive", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { var text = (string) from_value; @@ -117,18 +45,8 @@ public class ComboEntry : Gtk.Box { return true; }); - // Set tooltip text of info button depending on selected case type - bind_property ("case-type", case_info_button_icon, "tooltip-text", - BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, - (binding, from_value, ref to_value) => { - var case_type = (Define.CaseType) from_value; - to_value.set_string (_(CASE_INFO_TBL[case_type].info_text)); - return true; - }); - - notify["text"].connect (() => { - text_changed (); - }); + model.bind_property ("case-type", case_dropdown, "selected", + BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); } // UI elements defined in .ui files can't be a property, so defines a getter method instead diff --git a/src/MainWindow.vala b/src/MainWindow.vala index df8ac76..4524949 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -12,7 +12,12 @@ public class MainWindow : Gtk.ApplicationWindow { [GtkChild] private unowned Granite.Toast toast; - private ChCase.Converter converter; + [GtkChild] + private unowned ComboEntryModel source_model; + [GtkChild] + private unowned ComboEntryModel result_model; + [GtkChild] + private unowned MainWindowModel window_model; public MainWindow (Application app) { Object ( @@ -21,52 +26,30 @@ public class MainWindow : Gtk.ApplicationWindow { } construct { - converter = new ChCase.Converter (); - source_combo_entry.get_source_view ().grab_focus (); source_combo_entry.dropdown_changed.connect (() => { - do_convert (); + window_model.do_convert (); }); result_combo_entry.dropdown_changed.connect (() => { - do_convert (); + window_model.do_convert (); }); - source_combo_entry.text_changed.connect (() => { - do_convert (); + source_model.notify["text"].connect (() => { + window_model.do_convert (); }); - source_combo_entry.text_copied.connect (show_toast); - result_combo_entry.text_copied.connect (show_toast); + source_combo_entry.copy_button_clicked.connect (() => { + get_clipboard ().set_text (source_model.text); + show_toast (); + }); + result_combo_entry.copy_button_clicked.connect (() => { + get_clipboard ().set_text (result_model.text); + show_toast (); + }); } private void show_toast () { toast.send_notification (); } - - private void do_convert () { - set_case_type (source_combo_entry.case_type, result_combo_entry.case_type); - result_combo_entry.text = convert_case (source_combo_entry.text); - } - - /** - * Set case type of source and result texts. - * - * @param source_case case type of source text - * @param result_case case type of result text - */ - private void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { - converter.source_case = Util.to_chcase_case (source_case); - converter.result_case = Util.to_chcase_case (result_case); - } - - /** - * Convert case of source_text to the case set with {@link set_case_type}. - * - * @param source_text text that is converted - * @return text after conversion - */ - private string convert_case (string source_text) { - return converter.convert_case (source_text); - } } diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala new file mode 100644 index 0000000..c8bc635 --- /dev/null +++ b/src/Model/ComboEntryModel.vala @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano + */ + +public class ComboEntryModel : Object { + public Define.TextType text_type { get; construct; } + public Define.CaseType case_type { get; set; } + public string case_type_description { get; set; } + public GtkSource.Buffer buffer { get; private set; } + public string text { get; set; } + + private GtkSource.StyleSchemeManager style_scheme_manager; + private Gtk.Settings gtk_settings; + + private struct TextTypeData { + /** GSettings key name that stores last case type. */ + string key_case_type; + /** GSettings key name that stores last text. */ + string key_text; + } + private const TextTypeData[] TEXT_TYPE_DATA_TABLE = { + { "source-case-type", "source-text" }, // Define.TextType.SOURCE + { "result-case-type", "result-text" }, // Define.TextType.RESULT + }; + + private struct CaseTypeData { + string description; + } + private const CaseTypeData[] CASE_TYPE_DATA_TBL = { + { N_("Each word is separated by a space") }, // Define.CaseType.SPACE_SEPARATED + { N_("The first character of compound words is in lowercase") }, // Define.CaseType.CAMEL + { N_("The first character of compound words is in uppercase") }, // Define.CaseType.PASCAL + { N_("Each word is separated by an underscore") }, // Define.CaseType.SNAKE + { N_("Each word is separated by a hyphen") }, // Define.CaseType.KEBAB + { N_("The first character of the first word in the sentence is in uppercase") }, // Define.CaseType.SENTENCE + }; + + public ComboEntryModel (Define.TextType text_type) { + Object ( + text_type: text_type + ); + } + + construct { + case_type = (Define.CaseType) Application.settings.get_enum (TEXT_TYPE_DATA_TABLE[text_type].key_case_type); + + buffer = new GtkSource.Buffer (null); + + style_scheme_manager = new GtkSource.StyleSchemeManager (); + gtk_settings = Gtk.Settings.get_default (); + + // Sync with buffer text + buffer.bind_property ("text", this, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + + // Apply theme changes to the source view + gtk_settings.bind_property ("gtk-application-prefer-dark-theme", buffer, "style-scheme", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var prefer_dark = (bool) from_value; + if (prefer_dark) { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-dark")); + } else { + to_value.set_object (style_scheme_manager.get_scheme ("solarized-light")); + } + + return true; + }); + + Application.settings.bind (TEXT_TYPE_DATA_TABLE[text_type].key_text, this, "text", SettingsBindFlags.DEFAULT); + + bind_property ("case-type", this, "case-type-description", + BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, + (binding, from_value, ref to_value) => { + var case_type = (Define.CaseType) from_value; + to_value.set_string (_(CASE_TYPE_DATA_TBL[case_type].description)); + return true; + }); + + notify["case-type"].connect (() => { + Application.settings.set_enum (TEXT_TYPE_DATA_TABLE[text_type].key_case_type, case_type); + }); + } +} diff --git a/src/Model/MainWindowModel.vala b/src/Model/MainWindowModel.vala new file mode 100644 index 0000000..361caf1 --- /dev/null +++ b/src/Model/MainWindowModel.vala @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020-2024 Ryo Nakano + */ + +public class MainWindowModel : Object { + public ComboEntryModel source_model { get; construct; } + public ComboEntryModel result_model { get; construct; } + + private ChCase.Converter converter; + + public MainWindowModel (ComboEntryModel source_model, ComboEntryModel result_model) { + Object ( + source_model: source_model, + result_model: result_model + ); + } + + construct { + converter = new ChCase.Converter (); + } + + public void do_convert () { + set_case_type (source_model.case_type, result_model.case_type); + result_model.text = convert_case (source_model.text); + } + + /** + * Set case type of source and result texts. + * + * @param source_case case type of source text + * @param result_case case type of result text + */ + private void set_case_type (Define.CaseType source_case, Define.CaseType result_case) { + converter.source_case = Util.to_chcase_case (source_case); + converter.result_case = Util.to_chcase_case (result_case); + } + + /** + * Convert case of source_text to the case set with {@link set_case_type}. + * + * @param source_text text that is converted + * @return text after conversion + */ + private string convert_case (string source_text) { + return converter.convert_case (source_text); + } +} diff --git a/src/meson.build b/src/meson.build index 24d3b8f..152e181 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,8 @@ else endif sources = files( + 'Model/ComboEntryModel.vala', + 'Model/MainWindowModel.vala', 'Application.vala', 'ComboEntry.vala', 'Define.vala', From b24d231b23b2be98c301e6a0b49cf9cd8a45a8b3 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 27 Apr 2024 17:05:50 +0900 Subject: [PATCH 08/11] Remove unnecessary include --- data/resources/ui/combo-entry.blp | 1 - 1 file changed, 1 deletion(-) diff --git a/data/resources/ui/combo-entry.blp b/data/resources/ui/combo-entry.blp index 59b57b2..e04a5b4 100644 --- a/data/resources/ui/combo-entry.blp +++ b/data/resources/ui/combo-entry.blp @@ -1,6 +1,5 @@ using Gtk 4.0; using GtkSource 5; -using Granite 7.0; // Choices of case types Gtk.StringList case_list { From 89b263048406fd15a50a4a9af71117af1e08fa22 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 27 Apr 2024 17:10:24 +0900 Subject: [PATCH 09/11] Fix text converted with previous case type --- src/ComboEntry.vala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ComboEntry.vala b/src/ComboEntry.vala index de452e6..807789b 100644 --- a/src/ComboEntry.vala +++ b/src/ComboEntry.vala @@ -28,7 +28,11 @@ public class ComboEntry : Gtk.Box { } construct { + case_dropdown.selected = model.case_type; + case_dropdown.notify["selected"].connect (() => { + model.case_type = (Define.CaseType) case_dropdown.selected; + dropdown_changed (); }); @@ -44,9 +48,6 @@ public class ComboEntry : Gtk.Box { to_value.set_boolean (text != ""); return true; }); - - model.bind_property ("case-type", case_dropdown, "selected", - BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); } // UI elements defined in .ui files can't be a property, so defines a getter method instead From bb7c1a835a19ab8479b88068b0cb5aaaa4586874 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 27 Apr 2024 17:13:54 +0900 Subject: [PATCH 10/11] Create View directory and update POTFILES --- po/POTFILES | 2 +- src/{ => View}/ComboEntry.vala | 0 src/{ => View}/MainWindow.vala | 0 src/meson.build | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{ => View}/ComboEntry.vala (100%) rename src/{ => View}/MainWindow.vala (100%) diff --git a/po/POTFILES b/po/POTFILES index bf415e8..4b99eaf 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -2,4 +2,4 @@ data/konbucase.desktop.in data/konbucase.metainfo.xml.in data/resources/ui/combo-entry.blp data/resources/ui/main-window.blp -src/ComboEntry.vala +src/Model/ComboEntryModel.vala diff --git a/src/ComboEntry.vala b/src/View/ComboEntry.vala similarity index 100% rename from src/ComboEntry.vala rename to src/View/ComboEntry.vala diff --git a/src/MainWindow.vala b/src/View/MainWindow.vala similarity index 100% rename from src/MainWindow.vala rename to src/View/MainWindow.vala diff --git a/src/meson.build b/src/meson.build index 152e181..f3366e2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -24,10 +24,10 @@ endif sources = files( 'Model/ComboEntryModel.vala', 'Model/MainWindowModel.vala', + 'View/ComboEntry.vala', + 'View/MainWindow.vala', 'Application.vala', - 'ComboEntry.vala', 'Define.vala', - 'MainWindow.vala', 'StyleManager.vala', 'Util.vala', ) From 4ffea0e4dc0a004875e4a19cf407f0665ff6351b Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 27 Apr 2024 17:20:28 +0900 Subject: [PATCH 11/11] Shorten property name --- data/resources/ui/combo-entry.blp | 2 +- src/Model/ComboEntryModel.vala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/resources/ui/combo-entry.blp b/data/resources/ui/combo-entry.blp index e04a5b4..fd8e819 100644 --- a/data/resources/ui/combo-entry.blp +++ b/data/resources/ui/combo-entry.blp @@ -43,7 +43,7 @@ template $ComboEntry : Gtk.Box { // Info about current choice of case type Gtk.Image case_info_button_icon { icon-name: "dialog-information-symbolic"; - tooltip-text: bind template.model as <$ComboEntryModel>.case-type-description; + tooltip-text: bind template.model as <$ComboEntryModel>.case-description; } // Button to copy text in the text view diff --git a/src/Model/ComboEntryModel.vala b/src/Model/ComboEntryModel.vala index c8bc635..2a5c435 100644 --- a/src/Model/ComboEntryModel.vala +++ b/src/Model/ComboEntryModel.vala @@ -6,7 +6,7 @@ public class ComboEntryModel : Object { public Define.TextType text_type { get; construct; } public Define.CaseType case_type { get; set; } - public string case_type_description { get; set; } + public string case_description { get; set; } public GtkSource.Buffer buffer { get; private set; } public string text { get; set; } @@ -69,7 +69,7 @@ public class ComboEntryModel : Object { Application.settings.bind (TEXT_TYPE_DATA_TABLE[text_type].key_text, this, "text", SettingsBindFlags.DEFAULT); - bind_property ("case-type", this, "case-type-description", + bind_property ("case-type", this, "case-description", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE, (binding, from_value, ref to_value) => { var case_type = (Define.CaseType) from_value;