diff --git a/meson.build b/meson.build index 2256d867..a746abf4 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,7 @@ sources = files( 'src' / 'View' / 'EditView.vala', 'src' / 'View' / 'FilesView.vala', 'src' / 'Widget' / 'CategoryChooser.vala', + 'src' / 'Widget' / 'KeywordsRow.vala', 'src' / 'Application.vala', 'src' / 'Define.vala', 'src' / 'MainWindow.vala' diff --git a/po/POTFILES b/po/POTFILES index f08c6ce0..6cea5720 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -3,3 +3,4 @@ src/MainWindow.vala src/View/EditView.vala src/View/FilesView.vala src/Widget/CategoryChooser.vala +src/Widget/KeywordsRow.vala diff --git a/src/Define.vala b/src/Define.vala index 725f1943..df367004 100644 --- a/src/Define.vala +++ b/src/Define.vala @@ -11,6 +11,21 @@ namespace Define { */ public const string APP_NAME = "Pin It!"; + /** + * A key under g_key_file_desktop_group, whose value is a list of strings giving the keywords which may be used in + * addition to other metadata to describe this entry. + * + * Using KeyFileDesktop.KEY_KEYWORDS will cause the cc failing with "‘G_KEY_FILE_DESKTOP_KEY_KEYWORDS’ undeclared" + * error. This constant does not seem to be defined in the original glib and defined in the following patch. + * (and maybe valac uses glibc with this patch and thus it does not complain any error.) + * + * https://sources.debian.org/patches/glib2.0/2.78.3-2/01_gettext-desktopfiles.patch/ + * + * I just keep to borrow the definition of KEY_KEYWORDS here instead of applying the patch, + * since it might have side effect. + */ + public const string KEY_KEYWORDS = "Keywords"; + /** * Defines response IDs used in Adw.MessageDialog. */ diff --git a/src/View/EditView.vala b/src/View/EditView.vala index 074d3345..ad394158 100644 --- a/src/View/EditView.vala +++ b/src/View/EditView.vala @@ -23,6 +23,7 @@ public class View.EditView : Adw.NavigationPage { private Adw.EntryRow generic_name_entry; private Adw.EntryRow comment_entry; private Widget.CategoryChooser categories_row; + private Widget.KeywordsRow keywords_row; private Adw.EntryRow startup_wm_class_entry; private Adw.SwitchRow terminal_row; @@ -119,6 +120,9 @@ public class View.EditView : Adw.NavigationPage { categories_row = new Widget.CategoryChooser (); optional_group.add (categories_row); + keywords_row = new Widget.KeywordsRow (); + optional_group.add (keywords_row); + /* * Content part - Advanced Entries */ @@ -331,6 +335,14 @@ public class View.EditView : Adw.NavigationPage { desktop_file.set_string_list (KeyFileDesktop.KEY_CATEGORIES, categories_row.selected); }); + keywords_row.keywords_changed.connect (() => { + if (is_loading) { + return; + } + + desktop_file.set_string_list (Define.KEY_KEYWORDS, keywords_row.keywords); + }); + startup_wm_class_entry.notify["text"].connect (() => { if (is_loading) { return; @@ -400,6 +412,7 @@ public class View.EditView : Adw.NavigationPage { comment_entry.text = desktop_file.get_locale_string (KeyFileDesktop.KEY_COMMENT, locale, false); categories_row.selected = desktop_file.get_string_list (KeyFileDesktop.KEY_CATEGORIES, false); + keywords_row.keywords = desktop_file.get_string_list (Define.KEY_KEYWORDS, false); startup_wm_class_entry.text = desktop_file.get_string (KeyFileDesktop.KEY_STARTUP_WM_CLASS, false); terminal_row.active = desktop_file.get_boolean (KeyFileDesktop.KEY_TERMINAL, false); diff --git a/src/Widget/KeywordsRow.vala b/src/Widget/KeywordsRow.vala new file mode 100644 index 00000000..029fcdde --- /dev/null +++ b/src/Widget/KeywordsRow.vala @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2021-2024 Ryo Nakano + */ + +/** + * List Keywords of the app. + * + * There are {@link Widget.KeywordsRow.KeywordRow} for each keyword. + */ +public class Widget.KeywordsRow : Adw.ExpanderRow { + /** + * A signal emitted when keywords are changed. + */ + public signal void keywords_changed (); + + /** + * Set/get keywords. + */ + public string[] keywords { + owned get { + string[] _keywords = {}; + + rows.foreach ((row) => { + _keywords += row.text; + return true; + }); + + return _keywords; + } + + set { + // Clear all of the currently added entries + rows.foreach ((row) => { + remove_row (row); + return true; + }); + + foreach (unowned string keyword in value) { + add_keyword (keyword); + } + } + } + + /** + * Stores all of the rows in this expander row. + */ + private Gee.ArrayList rows; + + public KeywordsRow () { + } + + construct { + title = _("Keywords"); + subtitle = _("These words can be used as search terms."); + + rows = new Gee.ArrayList (); + + var add_keyword_button = new Gtk.Button.from_icon_name ("list-add-symbolic") { + tooltip_text = _("Add a new keyword"), + valign = Gtk.Align.CENTER + }; + add_keyword_button.add_css_class ("flat"); + add_suffix (add_keyword_button); + + add_keyword_button.clicked.connect (() => { + expanded = true; + add_keyword (); + }); + } + + /** + * Add new keyword. + * + * @param keyword The keyword. + */ + public void add_keyword (string keyword = "") { + var row = new KeywordRow (keyword); + ((Gtk.Editable) row).changed.connect (() => { + keywords_changed (); + }); + row.delete_clicked.connect (() => { + remove_row (row); + keywords_changed (); + }); + rows.add (row); + add_row (row); + } + + /** + * Remove row. + * + * @param row The {@link KeywordRow} to remove. + */ + private void remove_row (KeywordRow row) { + rows.remove (row); + remove (row); + } + + private class KeywordRow : Adw.EntryRow { + /** + * A signal emitted when the delete button is clicked. + */ + public signal void delete_clicked (); + + /** + * The constructor. + * + * Create a new KeywordRow and set its text to keyword. + * + * @param keyword The keyword to set to the text property. + */ + public KeywordRow (string keyword) { + title = _("Keyword"); + text = keyword; + + var delete_button = new Gtk.Button.from_icon_name ("edit-delete-symbolic") { + tooltip_text = _("Delete keyword"), + valign = Gtk.Align.CENTER + }; + delete_button.add_css_class ("flat"); + add_suffix (delete_button); + + delete_button.clicked.connect (() => { + delete_clicked (); + }); + } + } +}