[dconf-editor] Add search results view
- From: Arnaud Bonatti <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [dconf-editor] Add search results view
- Date: Tue, 28 Nov 2017 06:29:14 +0000 (UTC)
commit 201534436a47e33f15c730063475ede61bf90b41
Author: Davi da Silva Böger <dsboger gmail com>
Date: Thu Nov 23 19:25:32 2017 -0200
Add search results view
editor/browser-view.ui | 10 +
editor/browser-view.vala | 82 ++++--
editor/dconf-editor.gresource.xml | 1 +
editor/dconf-editor.ui | 58 ++++-
editor/dconf-model.vala | 4 +-
editor/dconf-window.vala | 92 ++++--
editor/key-list-box-row.vala | 43 ++-
editor/meson.build | 2 +
editor/registry-search.ui | 32 ++
editor/registry-search.vala | 599 +++++++++++++++++++++++++++++++++++++
editor/registry-view.vala | 5 +
11 files changed, 865 insertions(+), 63 deletions(-)
---
diff --git a/editor/browser-view.ui b/editor/browser-view.ui
index 83bea2b..8199245 100644
--- a/editor/browser-view.ui
+++ b/editor/browser-view.ui
@@ -63,6 +63,16 @@
<property name="name">properties-view</property>
</packing>
</child>
+ <child>
+ <object class="RegistrySearch" id="search_results_view">
+ <property name="visible">True</property>
+ <property name="revealer">revealer</property>
+ <signal name="request_path" handler="request_path_test"/>
+ </object>
+ <packing>
+ <property name="name">search-results-view</property>
+ </packing>
+ </child>
</object>
</child>
<child>
diff --git a/editor/browser-view.vala b/editor/browser-view.vala
index fbddf58..7b7c201 100644
--- a/editor/browser-view.vala
+++ b/editor/browser-view.vala
@@ -38,8 +38,10 @@ class BrowserView : Grid, PathElement
[GtkChild] private Stack stack;
[GtkChild] private RegistryView browse_view;
[GtkChild] private RegistryInfo properties_view;
+ [GtkChild] private RegistrySearch search_results_view;
+ private Widget? pre_search_view = null;
- private SortingOptions sorting_options;
+ public SortingOptions sorting_options { get; private set; }
public bool small_keys_list_rows
{
@@ -55,7 +57,7 @@ class BrowserView : Grid, PathElement
private DConfWindow window {
get {
if (_window == null)
- _window = (DConfWindow) DConfWindow._get_parent (DConfWindow._get_parent (this));
+ _window = (DConfWindow) DConfWindow._get_parent (DConfWindow._get_parent
(DConfWindow._get_parent (this)));
return (!) _window;
}
}
@@ -73,8 +75,9 @@ class BrowserView : Grid, PathElement
settings.bind ("sort-folders", sorting_options, "sort-folders", GLib.SettingsBindFlags.GET);
sorting_options.notify.connect (() => {
- if (!is_not_browsing_view () && current_directory.need_sorting (sorting_options))
+ if (current_view_is_browse_view () && current_directory.need_sorting (sorting_options))
need_reload_warning_revealer.set_reveal_child (true);
+ // TODO reload search results too
});
destroy.connect (() => {
@@ -113,7 +116,8 @@ class BrowserView : Grid, PathElement
}
private void _show_browse_view (string path)
{
- stack.set_transition_type (current_path.has_prefix (path) ? StackTransitionType.CROSSFADE :
StackTransitionType.NONE);
+ stack.set_transition_type (current_path.has_prefix (path) && pre_search_view == null ?
StackTransitionType.CROSSFADE : StackTransitionType.NONE);
+ pre_search_view = null;
need_reload_warning_revealer.set_reveal_child (false);
browse_view.show_multiple_schemas_warning (current_directory.warning_multiple_schemas);
@@ -136,11 +140,34 @@ class BrowserView : Grid, PathElement
need_reload_warning_revealer.set_reveal_child (false);
browse_view.show_multiple_schemas_warning (false);
- stack.set_transition_type (path.has_prefix (current_path) && current_path.length ==
path.last_index_of_char ('/') + 1 ? StackTransitionType.CROSSFADE : StackTransitionType.NONE);
+ stack.set_transition_type (path.has_prefix (current_path) && current_path.length ==
path.last_index_of_char ('/') + 1 && pre_search_view == null ? StackTransitionType.CROSSFADE :
StackTransitionType.NONE);
+ pre_search_view = null;
update_current_path (path);
stack.set_visible_child (properties_view);
}
+ public void show_search_view (string term)
+ {
+ search_results_view.start_search (term);
+ if (pre_search_view == null)
+ {
+ pre_search_view = stack.visible_child;
+ stack.set_transition_type (StackTransitionType.NONE);
+ stack.visible_child = search_results_view;
+ }
+ }
+
+ public void hide_search_view ()
+ {
+ if (pre_search_view != null)
+ {
+ stack.set_transition_type (StackTransitionType.NONE);
+ stack.visible_child = (!) pre_search_view;
+ pre_search_view = null;
+ }
+ search_results_view.stop_search ();
+ }
+
private void update_current_path (string path)
{
revealer.path_changed ();
@@ -161,42 +188,57 @@ class BrowserView : Grid, PathElement
public bool show_row_popover ()
{
- if (is_not_browsing_view ())
- return false;
- return browse_view.show_row_popover ();
+ if (current_view_is_browse_view ())
+ return browse_view.show_row_popover ();
+ if (current_view_is_search_results_view ())
+ return search_results_view.show_row_popover ();
+ return false;
}
public void toggle_boolean_key ()
{
- if (is_not_browsing_view ())
- return; // TODO something, probably
- browse_view.toggle_boolean_key ();
+ if (current_view_is_browse_view ())
+ browse_view.toggle_boolean_key ();
+ else if (current_view_is_search_results_view ())
+ search_results_view.toggle_boolean_key ();
}
public void set_to_default ()
{
- if (is_not_browsing_view ())
- return;
- browse_view.set_to_default ();
+ if (current_view_is_browse_view ())
+ browse_view.set_to_default ();
+ else if (current_view_is_search_results_view ())
+ search_results_view.set_to_default ();
}
public void discard_row_popover ()
{
- if (is_not_browsing_view ())
- return;
- browse_view.discard_row_popover ();
+ if (current_view_is_browse_view ())
+ browse_view.discard_row_popover ();
+ else if (current_view_is_search_results_view ())
+ search_results_view.discard_row_popover ();
}
private void invalidate_popovers ()
{
browse_view.invalidate_popovers ();
+ search_results_view.invalidate_popovers ();
window.update_hamburger_menu ();
}
- private bool is_not_browsing_view ()
+ public bool current_view_is_browse_view ()
+ {
+ return stack.get_visible_child () == browse_view;
+ }
+
+ public bool current_view_is_properties_view ()
+ {
+ return stack.get_visible_child () == properties_view;
+ }
+
+ public bool current_view_is_search_results_view ()
{
- string? visible_child_name = stack.get_visible_child_name ();
- return (visible_child_name == null || ((!) visible_child_name) != "browse-view");
+ return stack.get_visible_child () == search_results_view;
}
/*\
diff --git a/editor/dconf-editor.gresource.xml b/editor/dconf-editor.gresource.xml
index 186906b..5e240a8 100644
--- a/editor/dconf-editor.gresource.xml
+++ b/editor/dconf-editor.gresource.xml
@@ -14,6 +14,7 @@
<file preprocess="xml-stripblanks">property-row.ui</file>
<file preprocess="xml-stripblanks">registry-info.ui</file>
<file preprocess="xml-stripblanks">registry-placeholder.ui</file>
+ <file preprocess="xml-stripblanks">registry-search.ui</file>
<file preprocess="xml-stripblanks">registry-view.ui</file>
</gresource>
<gresource prefix="/ca/desrt/dconf-editor/gtk">
diff --git a/editor/dconf-editor.ui b/editor/dconf-editor.ui
index bd7889b..0f6f982 100644
--- a/editor/dconf-editor.ui
+++ b/editor/dconf-editor.ui
@@ -65,9 +65,10 @@
</child>
<child>
<object class="GtkToggleButton">
- <property name="visible">False</property>
+ <property name="visible">True</property>
<property name="valign">center</property>
- <!-- TODO <property name="focus-on-click">False</property> does not work here because of
explicit focus grab -->
+ <property name="active" bind-source="search_bar" bind-property="search-mode-enabled"
bind-flags="bidirectional">False</property>
+ <property name="focus-on-click">False</property>
<!-- <accelerator key="F" signal="toggled" modifiers="GDK_CONTROL_MASK"/> TODO -->
<style>
<class name="image-button"/>
@@ -113,9 +114,58 @@
<object class="GtkOverlay">
<property name="visible">True</property>
<child>
- <object class="BrowserView" id="browser_view">
+ <object class="GtkGrid">
<property name="visible">True</property>
- <signal name="request_path" handler="request_path"/>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRevealer">
+ <property name="visible">True</property>
+ <property name="reveal-child" bind-source="search_bar" bind-property="search-mode-enabled"
bind-flags="bidirectional">False</property>
+ <child>
+ <object class="GtkSearchBar" id="search_bar">
+ <property name="visible">True</property>
+ <property name="search-mode-enabled">False</property>
+ <property name="show-close-button">False</property>
+ <child>
+ <object class="GtkBox"> <!-- https://bugzilla.gnome.org/show_bug.cgi?id=769876 -->
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="width-request">350</property>
+ <signal name="search-changed" handler="search_changed"/>
+ <signal name="stop-search" handler="search_cancelled"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="search_options_button">
+ <property name="visible">False</property>
+ <property name="sensitive" bind-source="search_bar"
bind-property="search-mode-enabled"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="BrowserView" id="browser_view">
+ <property name="visible">True</property>
+ <property name="vexpand">True</property>
+ <signal name="request_path" handler="request_path"/>
+ </object>
+ </child>
</object>
</child>
<child type="overlay">
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
index 2508c73..5992ec0 100644
--- a/editor/dconf-model.vala
+++ b/editor/dconf-model.vala
@@ -728,7 +728,7 @@ public class SortingOptions : Object
public bool case_sensitive { get; set; default = false; }
public MergeType sort_folders { get; set; default = MergeType.MIXED; }
- private SettingComparator get_comparator ()
+ public SettingComparator get_comparator ()
{
if (sort_folders == MergeType.FIRST)
{
@@ -778,7 +778,7 @@ public class SortingOptions : Object
/* Comparison functions */
-interface SettingComparator : Object
+public interface SettingComparator : Object
{
public abstract int compare (SettingObject a, SettingObject b);
}
diff --git a/editor/dconf-window.vala b/editor/dconf-window.vala
index 12295fe..9ce224c 100644
--- a/editor/dconf-window.vala
+++ b/editor/dconf-window.vala
@@ -29,9 +29,9 @@ class DConfWindow : ApplicationWindow
{ "enter-delay-mode", enter_delay_mode }
};
- public string current_path { private get; set; default = "/"; } // not synced bidi, needed for saving on
destroy, even after child destruction
+ public string current_path { get; set; default = "/"; } // not synced bidi, needed for saving on
destroy, even after child destruction
- private SettingsModel model = new SettingsModel ();
+ public SettingsModel model { get; private set; default=new SettingsModel (); }
private int window_width = 0;
private int window_height = 0;
@@ -47,6 +47,9 @@ class DConfWindow : ApplicationWindow
[GtkChild] private Bookmarks bookmarks_button;
[GtkChild] private MenuButton info_button;
[GtkChild] private PathBar pathbar;
+ [GtkChild] private SearchBar search_bar;
+ [GtkChild] private SearchEntry search_entry;
+
[GtkChild] private BrowserView browser_view;
[GtkChild] private Revealer notification_revealer;
@@ -111,6 +114,9 @@ class DConfWindow : ApplicationWindow
if (settings.get_boolean ("small-bookmarks-rows"))
context.add_class ("small-bookmarks-rows");
+ search_bar.connect_entry (search_entry);
+ search_bar.notify ["search-mode-enabled"].connect (search_changed);
+
browser_view.bind_property ("current-path", this, "current-path"); // TODO in UI file?
settings.bind ("mouse-use-extra-buttons", this, "mouse-extra-buttons",
SettingsBindFlags.GET|SettingsBindFlags.NO_SENSITIVITY);
@@ -168,6 +174,11 @@ class DConfWindow : ApplicationWindow
return (!) parent;
}
+ public string[] get_bookmarks ()
+ {
+ return settings.get_strv ("bookmarks");
+ }
+
/*\
* * Window management callbacks
\*/
@@ -265,27 +276,25 @@ class DConfWindow : ApplicationWindow
Directory? dir = model.get_directory (folder_name);
if (dir == null)
- {
cannot_find_folder (folder_name);
- return;
- }
- if (full_name == folder_name)
- {
+ else if (full_name == folder_name)
browser_view.set_directory ((!) dir, pathbar.get_selected_child (full_name));
- return;
- }
+ else
+ {
+ string [] names = full_name.split ("/");
+ string object_name = names [names.length - 1];
- string [] names = full_name.split ("/");
- string object_name = names [names.length - 1];
+ Key? existing_key = SettingsModel.get_key_from_path_and_name (((!) dir).key_model, object_name);
- Key? existing_key = SettingsModel.get_key_from_path_and_name (((!) dir).key_model, object_name);
+ if (existing_key == null)
+ cannot_find_key (object_name, (!) dir);
+ else if (((!) existing_key) is DConfKey && ((DConfKey) (!) existing_key).is_ghost)
+ key_has_been_removed (object_name, (!) dir);
+ else
+ browser_view.show_properties_view ((Key) (!) existing_key, full_name, ((!)
dir).warning_multiple_schemas);
+ }
- if (existing_key == null)
- cannot_find_key (object_name, (!) dir);
- else if (((!) existing_key) is DConfKey && ((DConfKey) (!) existing_key).is_ghost)
- key_has_been_removed (object_name, (!) dir);
- else
- browser_view.show_properties_view ((Key) (!) existing_key, full_name, ((!)
dir).warning_multiple_schemas);
+ search_bar.search_mode_enabled = false; // do last to avoid flickering RegistryView before
PropertiesView when selecting a search result
}
/*\
@@ -352,6 +361,25 @@ class DConfWindow : ApplicationWindow
}
/*\
+ * * Search
+ \*/
+
+ [GtkCallback]
+ private void search_changed ()
+ {
+ if (search_bar.search_mode_enabled)
+ browser_view.show_search_view (search_entry.text);
+ else
+ browser_view.hide_search_view ();
+ }
+
+ [GtkCallback]
+ private void search_cancelled ()
+ {
+ browser_view.hide_search_view ();
+ }
+
+ /*\
* * Other callbacks
\*/
@@ -377,6 +405,11 @@ class DConfWindow : ApplicationWindow
[GtkCallback]
private bool on_key_press_event (Widget widget, Gdk.EventKey event) // TODO better?
{
+ Widget? focus = get_focus ();
+ if (!(focus is Entry) && !(focus is TextView)) // why is this needed?
+ if (search_bar.handle_event (event))
+ return true;
+
string name = (!) (Gdk.keyval_name (event.keyval) ?? "");
if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)
@@ -401,14 +434,19 @@ class DConfWindow : ApplicationWindow
browser_view.discard_row_popover ();
bookmarks_button.set_bookmarked (false);
return true;
-// case "f":
-// if (bookmarks_button.active)
-// bookmarks_button.active = false;
-// if (info_button.active)
-// info_button.active = false;
-// browser_view.discard_row_popover ();
-// browser_view.set_search_mode (null);
-// return true;
+ case "f":
+ if (bookmarks_button.active)
+ bookmarks_button.active = false;
+ if (info_button.active)
+ info_button.active = false;
+ browser_view.discard_row_popover ();
+ if (!search_bar.search_mode_enabled)
+ search_bar.search_mode_enabled = true;
+ else if (!search_entry.has_focus)
+ search_entry.grab_focus ();
+ else
+ search_bar.search_mode_enabled = false;
+ return true;
case "c":
browser_view.discard_row_popover (); // TODO avoid duplicate get_selected_row () call
string? selected_row_text = browser_view.get_copy_text ();
@@ -429,7 +467,6 @@ class DConfWindow : ApplicationWindow
case "KP_Enter":
if (info_button.active || bookmarks_button.active)
return false;
-// browser_view.set_search_mode (false);
browser_view.discard_row_popover ();
browser_view.toggle_boolean_key ();
return true;
@@ -441,7 +478,6 @@ class DConfWindow : ApplicationWindow
case "KP_Decimal":
if (info_button.active || bookmarks_button.active)
return false;
-// browser_view.set_search_mode (false);
browser_view.discard_row_popover ();
browser_view.set_to_default ();
return true;
diff --git a/editor/key-list-box-row.vala b/editor/key-list-box-row.vala
index 1130681..003adcd 100644
--- a/editor/key-list-box-row.vala
+++ b/editor/key-list-box-row.vala
@@ -28,7 +28,7 @@ private class ListBoxRowWrapper : ListBoxRow
}
}
-private class ListBoxRowHeader : Separator
+private class ListBoxRowHeader : Grid
{
public override void get_preferred_width (out int minimum_width, out int natural_width)
{
@@ -40,10 +40,13 @@ private class ListBoxRowHeader : Separator
private abstract class ClickableListBoxRow : EventBox
{
public signal void on_row_clicked ();
+ public signal void on_open_parent ();
public signal void on_delete_call ();
public abstract string get_text ();
+ public bool search_result_mode { protected get; construct; default=false; }
+
/*\
* * Dismiss popover on window resize
\*/
@@ -112,11 +115,12 @@ private abstract class ClickableListBoxRow : EventBox
private class FolderListBoxRow : ClickableListBoxRow
{
[GtkChild] private Label folder_name_label;
- private string full_name;
+ public string full_name;
- public FolderListBoxRow (string label, string path)
+ public FolderListBoxRow (string label, string path, bool search_result_mode=false)
{
- folder_name_label.set_text (label);
+ Object (search_result_mode: search_result_mode);
+ folder_name_label.set_text (search_result_mode ? path : label);
full_name = path;
}
@@ -127,6 +131,12 @@ private class FolderListBoxRow : ClickableListBoxRow
protected override bool generate_popover (ContextPopover popover, bool unused) // TODO better
{
+ if (search_result_mode)
+ {
+ popover.new_action ("open_parent", () => on_open_parent ());
+ popover.new_section ();
+ }
+
popover.new_action ("open", () => on_row_clicked ());
popover.new_copy_action (get_text ());
@@ -190,7 +200,7 @@ private abstract class KeyListBoxRow : ClickableListBoxRow
}
update ();
- key_name_label.set_label (abstract_key.name);
+ key_name_label.set_label (search_result_mode ? abstract_key.full_name : abstract_key.name);
ulong key_value_changed_handler = abstract_key.value_changed.connect (() => {
update ();
@@ -214,9 +224,9 @@ private class KeyListBoxRowEditableNoSchema : KeyListBoxRow
public DConfKey key { get; construct; }
private override Key abstract_key { get { return (Key) key; }}
- public KeyListBoxRowEditableNoSchema (DConfKey _key)
+ public KeyListBoxRowEditableNoSchema (DConfKey _key, bool search_result_mode=false)
{
- Object (key: _key);
+ Object (key: _key, search_result_mode : search_result_mode);
if (boolean_switch != null)
((!) boolean_switch).notify ["active"].connect (() => key.value = new Variant.boolean (((!)
boolean_switch).get_active ()));
@@ -269,6 +279,12 @@ private class KeyListBoxRowEditableNoSchema : KeyListBoxRow
return true;
}
+ if (search_result_mode)
+ {
+ popover.new_action ("open_parent", () => on_open_parent ());
+ popover.new_section ();
+ }
+
popover.new_action ("customize", () => on_row_clicked ());
popover.new_copy_action (get_text ());
@@ -326,9 +342,9 @@ private class KeyListBoxRowEditable : KeyListBoxRow
private override Key abstract_key { get { return (Key) key; }}
private ulong boolean_switch_toggled_handler = 0;
- public KeyListBoxRowEditable (GSettingsKey _key)
+ public KeyListBoxRowEditable (GSettingsKey _key, bool search_result_mode=false)
{
- Object (key: _key);
+ Object (key: _key, search_result_mode : search_result_mode);
if (boolean_switch != null)
boolean_switch_toggled_handler = ((!) boolean_switch).notify ["active"].connect (() => {
@@ -385,6 +401,12 @@ private class KeyListBoxRowEditable : KeyListBoxRow
protected override bool generate_popover (ContextPopover popover, bool delayed_apply_menu)
{
+ if (search_result_mode)
+ {
+ popover.new_action ("open_parent", () => on_open_parent ());
+ popover.new_section ();
+ }
+
popover.new_action ("customize", () => on_row_clicked ());
popover.new_copy_action (get_text ());
@@ -511,6 +533,9 @@ private class ContextPopover : Popover
case "open":
/* Translators: "open folder" action in the right-click menu on a folder */
current_section.append (_("Open"), group_dot_action); return;
+ case "open_parent":
+ /* Translators: "open parent folder" action in the right-click menu on a folder in a search
result */
+ current_section.append (_("Open parent folder"), group_dot_action); return;
case "erase":
/* Translators: "erase key" action in the right-click menu on a key without schema */
current_section.append (_("Erase key"), group_dot_action); return;
diff --git a/editor/meson.build b/editor/meson.build
index bcc14ae..1f5c55e 100644
--- a/editor/meson.build
+++ b/editor/meson.build
@@ -61,6 +61,7 @@ sources = files(
'pathbar.vala',
'registry-info.vala',
'registry-placeholder.vala',
+ 'registry-search.vala',
'registry-view.vala'
)
@@ -80,6 +81,7 @@ resource_data = files(
'property-row.ui',
'registry-info.ui',
'registry-placeholder.ui',
+ 'registry-search.ui',
'registry-view.ui'
)
diff --git a/editor/registry-search.ui b/editor/registry-search.ui
new file mode 100644
index 0000000..247e7e0
--- /dev/null
+++ b/editor/registry-search.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="RegistrySearch" parent="GtkGrid">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <child>
+ <object class="GtkListBox" id="key_list_box">
+ <property name="visible">True</property>
+ <property name="activate-on-single-click">True</property>
+ <property name="selection-mode">browse</property>
+ <style>
+ <class name="keys-list"/>
+ <class name="search-results-list"/>
+ </style>
+ <signal name="row-activated" handler="row_activated_cb"/>
+ <child type="placeholder">
+ <object class="RegistryPlaceholder">
+ <property name="label" translatable="yes">No matches</property>
+ <property name="icon-name">ca.desrt.dconf-editor-symbolic</property>
+ <property name="big">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/editor/registry-search.vala b/editor/registry-search.vala
new file mode 100644
index 0000000..47278cb
--- /dev/null
+++ b/editor/registry-search.vala
@@ -0,0 +1,599 @@
+/*
+ This file is part of Dconf Editor
+
+ Dconf Editor is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Dconf Editor is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Dconf Editor. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/registry-search.ui")]
+class RegistrySearch : Grid, PathElement, BrowsableView
+{
+ public Behaviour behaviour { private get; set; }
+
+ //[GtkChild] private ScrolledWindow scrolled;
+
+ [GtkChild] private ListBox key_list_box;
+
+ private GLib.ListStore rows_possibly_with_popover = new GLib.ListStore (typeof (ClickableListBoxRow));
+
+ private bool _small_keys_list_rows;
+ public bool small_keys_list_rows
+ {
+ set
+ {
+ _small_keys_list_rows = value;
+ key_list_box.foreach((row) => {
+ Widget row_child = ((ListBoxRow) row).get_child ();
+ if (row_child is KeyListBoxRow)
+ ((KeyListBoxRow) row_child).small_keys_list_rows = value;
+ });
+ }
+ }
+
+ public ModificationsRevealer revealer { private get; set; }
+
+ private BrowserView? _browser_view = null;
+ private BrowserView browser_view {
+ get {
+ if (_browser_view == null)
+ _browser_view = (BrowserView) DConfWindow._get_parent (DConfWindow._get_parent (this));
+ return (!) _browser_view;
+ }
+ }
+
+ private DConfWindow? _window = null;
+ private DConfWindow window {
+ get {
+ if (_window == null)
+ _window = (DConfWindow) DConfWindow._get_parent (DConfWindow._get_parent
(DConfWindow._get_parent (browser_view)));
+ return (!) _window;
+ }
+ }
+
+ private GLib.ListStore search_results_model = new GLib.ListStore (typeof (SettingObject));
+
+ construct
+ {
+ key_list_box.set_header_func (update_search_results_header);
+ }
+
+ /*\
+ * * Updating
+ \*/
+/*
+ public void select_row_named (string selected, bool grab_focus)
+ {
+ check_resize ();
+ ListBoxRow? row = key_list_box.get_row_at_index (get_row_position (selected));
+ if (row == null)
+ assert_not_reached ();
+ scroll_to_row ((!) row, grab_focus);
+ }
+ public void select_first_row (bool grab_focus)
+ {
+ ListBoxRow? row = key_list_box.get_row_at_index (0);
+ if (row != null)
+ scroll_to_row ((!) row, grab_focus);
+ }
+ private int get_row_position (string selected)
+ requires (key_model != null)
+ {
+ uint position = 0;
+ while (position < ((!) key_model).get_n_items ())
+ {
+ SettingObject object = (SettingObject) ((!) key_model).get_object (position);
+ if (object.full_name == selected)
+ return (int) position;
+ position++;
+ }
+ assert_not_reached ();
+ }
+ private void scroll_to_row (ListBoxRow row, bool grab_focus)
+ {
+ key_list_box.select_row (row);
+ if (grab_focus)
+ row.grab_focus ();
+
+ Allocation list_allocation, row_allocation;
+ scrolled.get_allocation (out list_allocation);
+ row.get_allocation (out row_allocation);
+ key_list_box.get_adjustment ().set_value (row_allocation.y + (int) ((row_allocation.height -
list_allocation.height) / 2.0));
+ }
+/*
+ /*\
+ * * Key ListBox
+ \*/
+
+ private Widget new_list_box_row (Object item)
+ {
+ ClickableListBoxRow row;
+ SettingObject setting_object = (SettingObject) item;
+ string full_name = setting_object.full_name;
+ string parent_path;
+ if (full_name.has_suffix ("/"))
+ parent_path = SettingsModel.get_base_path (full_name [0:full_name.length - 1]);
+ else
+ parent_path = SettingsModel.get_base_path (full_name);
+ bool is_local_result = parent_path == window.current_path;
+ ulong on_delete_call_handler;
+
+ if (setting_object is Directory)
+ {
+ row = new FolderListBoxRow (setting_object.name, setting_object.full_name, !is_local_result);
+ on_delete_call_handler = row.on_delete_call.connect (() => browser_view.reset_objects
(((Directory) setting_object).key_model, true));
+ }
+ else
+ {
+ if (setting_object is GSettingsKey)
+ row = new KeyListBoxRowEditable ((GSettingsKey) setting_object, !is_local_result);
+ else
+ row = new KeyListBoxRowEditableNoSchema ((DConfKey) setting_object, !is_local_result);
+
+ Key key = (Key) setting_object;
+ KeyListBoxRow key_row = (KeyListBoxRow) row;
+ key_row.small_keys_list_rows = _small_keys_list_rows;
+
+ on_delete_call_handler = row.on_delete_call.connect (() => set_key_value (key, null));
+ ulong set_key_value_handler = key_row.set_key_value.connect ((variant) => { set_key_value (key,
variant); set_delayed_icon (row, key); });
+ ulong change_dismissed_handler = key_row.change_dismissed.connect (() => revealer.dismiss_change
(key));
+
+ ulong key_planned_change_handler = key.notify ["planned-change"].connect (() => set_delayed_icon
(row, key));
+ ulong key_planned_value_handler = key.notify ["planned-value"].connect (() => set_delayed_icon
(row, key));
+ set_delayed_icon (row, key);
+
+ row.destroy.connect (() => {
+ key_row.disconnect (set_key_value_handler);
+ key_row.disconnect (change_dismissed_handler);
+ key.disconnect (key_planned_change_handler);
+ key.disconnect (key_planned_value_handler);
+ });
+ }
+
+ ulong on_row_clicked_handler = row.on_row_clicked.connect (() => request_path
(setting_object.full_name));
+ ulong on_row_open_parent_handler = row.on_open_parent.connect (() => {
+ request_path (parent_path); // TODO selected
+ });
+ ulong button_press_event_handler = row.button_press_event.connect (on_button_pressed);
+
+ row.destroy.connect (() => {
+ row.disconnect (on_delete_call_handler);
+ row.disconnect (on_row_clicked_handler);
+ row.disconnect (on_row_open_parent_handler);
+ row.disconnect (button_press_event_handler);
+ });
+
+ /* Wrapper ensures max width for rows */
+ ListBoxRowWrapper wrapper = new ListBoxRowWrapper ();
+ wrapper.set_halign (Align.CENTER);
+ wrapper.add (row);
+ if (row is FolderListBoxRow)
+ wrapper.get_style_context ().add_class ("folder-row");
+ else
+ wrapper.get_style_context ().add_class ("key-row");
+ return wrapper;
+ }
+
+ private void set_delayed_icon (ClickableListBoxRow row, Key key)
+ {
+ if (key.planned_change)
+ {
+ StyleContext context = row.get_style_context ();
+ context.add_class ("delayed");
+ if (key is DConfKey)
+ {
+ if (key.planned_value == null)
+ context.add_class ("erase");
+ else
+ context.remove_class ("erase");
+ }
+ }
+ else
+ row.get_style_context ().remove_class ("delayed");
+ }
+
+ private bool on_button_pressed (Widget widget, Gdk.EventButton event)
+ {
+ ListBoxRow list_box_row = (ListBoxRow) widget.get_parent ();
+ key_list_box.select_row (list_box_row);
+ list_box_row.grab_focus ();
+
+ if (event.button == Gdk.BUTTON_SECONDARY)
+ {
+ ClickableListBoxRow row = (ClickableListBoxRow) widget;
+
+ int event_x = (int) event.x;
+ if (event.window != widget.get_window ()) // boolean value switch
+ {
+ int widget_x, unused;
+ event.window.get_position (out widget_x, out unused);
+ event_x += widget_x;
+ }
+
+ row.show_right_click_popover (get_current_delay_mode (), event_x);
+ rows_possibly_with_popover.append (row);
+ }
+
+ return false;
+ }
+
+ [GtkCallback]
+ private void row_activated_cb (ListBoxRow list_box_row)
+ {
+ ((ClickableListBoxRow) list_box_row.get_child ()).on_row_clicked ();
+ }
+
+ public void invalidate_popovers ()
+ {
+ uint position = 0;
+ ClickableListBoxRow? row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (0);
+ while (row != null)
+ {
+ ((!) row).destroy_popover ();
+ position++;
+ row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (position);
+ }
+ rows_possibly_with_popover.remove_all ();
+ }
+
+ /*public string? get_selected_row_name ()
+ {
+ ListBoxRow? selected_row = key_list_box.get_selected_row ();
+ if (selected_row != null)
+ {
+ int position = ((!) selected_row).get_index ();
+ return ((SettingObject) ((!) key_model).get_object (position)).full_name;
+ }
+ else
+ return null;
+ }*/
+
+ /*\
+ * * Revealer stuff
+ \*/
+
+ public bool get_current_delay_mode ()
+ {
+ return browser_view.get_current_delay_mode ();
+ }
+
+ private void set_key_value (Key key, Variant? new_value)
+ {
+ if (get_current_delay_mode ())
+ revealer.add_delayed_setting (key, new_value);
+ else if (new_value != null)
+ key.value = (!) new_value;
+ else if (key is GSettingsKey)
+ ((GSettingsKey) key).set_to_default ();
+ else if (behaviour != Behaviour.UNSAFE)
+ {
+ browser_view.enter_delay_mode ();
+ revealer.add_delayed_setting (key, null);
+ }
+ else
+ ((DConfKey) key).erase ();
+ }
+
+ /*\
+ * * Keyboard calls
+ \*/
+
+ public bool show_row_popover ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return false;
+
+ ClickableListBoxRow row = (ClickableListBoxRow) ((!) selected_row).get_child ();
+ row.show_right_click_popover (get_current_delay_mode ());
+ rows_possibly_with_popover.append (row);
+ return true;
+ }
+
+ public string? get_copy_text ()
+ {
+ ListBoxRow? selected_row = key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return null;
+ else
+ return ((ClickableListBoxRow) ((!) selected_row).get_child ()).get_text ();
+ }
+
+ public void toggle_boolean_key ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return;
+
+ if (!(((!) selected_row).get_child () is KeyListBoxRow))
+ return;
+
+ ((KeyListBoxRow) ((!) selected_row).get_child ()).toggle_boolean_key ();
+ }
+
+ public void set_to_default ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return;
+
+ ((ClickableListBoxRow) ((!) selected_row).get_child ()).on_delete_call ();
+ }
+
+ public void discard_row_popover ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return;
+
+ ((ClickableListBoxRow) ((!) selected_row).get_child ()).hide_right_click_popover ();
+ }
+
+ /*\
+ * * Search
+ \*/
+
+ private string? old_term;
+ // indices for the start of each section. used to know where to insert search hits and to update the
headers
+ // must be updated before changing the list model, so that the header function works correctly
+ private int post_local;
+ private int post_bookmarks;
+ private int post_folders;
+ private uint? search_source = null;
+ private GLib.Queue<Directory> search_nodes = new GLib.Queue<Directory> ();
+
+ public void stop_search ()
+ {
+ key_list_box.bind_model (null, null);
+ stop_global_search ();
+ search_results_model.remove_all ();
+ post_local = -1;
+ post_bookmarks = -1;
+ post_folders = -1;
+ old_term = null;
+ }
+
+ public void start_search (string term)
+ {
+ if (old_term == null || term != (!) old_term)
+ {
+ SettingsModel model = window.model;
+ string current_path = window.current_path;
+ if (old_term != null && term.has_prefix((!) old_term))
+ {
+ pause_global_search ();
+ refine_local_results (term);
+ refine_bookmarks_results (term);
+ if ((!) old_term == "")
+ start_global_search (model, current_path, term);
+ else
+ {
+ refine_global_results (term);
+ resume_global_search (current_path, term); // update search term
+ }
+ }
+ else
+ {
+ stop_global_search ();
+ search_results_model.remove_all ();
+ post_local = -1;
+ post_folders = -1;
+
+ local_search (model, current_path, term);
+ bookmark_search (model, current_path, term);
+ key_list_box.bind_model (search_results_model, new_list_box_row);
+ if (term != "")
+ start_global_search (model, current_path, term);
+ }
+ old_term = term;
+ }
+ }
+
+ private void refine_local_results (string term)
+ {
+ for (int i = post_local - 1; i >= 0; i--)
+ {
+ SettingObject item = (SettingObject) search_results_model.get_item (i);
+ if (!(term in item.name))
+ {
+ post_local--;
+ post_bookmarks--;
+ post_folders--;
+ search_results_model.remove (i);
+ }
+ }
+ }
+
+ private void refine_bookmarks_results (string term)
+ {
+ for (int i = post_bookmarks - 1; i >= post_local; i--)
+ {
+ SettingObject item = (SettingObject) search_results_model.get_item (i);
+ if (!(term in item.name))
+ {
+ post_bookmarks--;
+ post_folders--;
+ search_results_model.remove (i);
+ }
+ }
+ }
+
+ private void refine_global_results (string term)
+ {
+ for (int i = (int) search_results_model.get_n_items () - 1; i >= post_folders; i--)
+ {
+ SettingObject item = (SettingObject) search_results_model.get_item (i);
+ if (!(term in item.name))
+ search_results_model.remove (i);
+ }
+ for (int i = post_folders - 1; i >= post_local; i--)
+ {
+ SettingObject item = (SettingObject) search_results_model.get_item (i);
+ if (!(term in item.name))
+ {
+ post_folders--;
+ search_results_model.remove (i);
+ }
+ }
+ }
+
+ private bool local_search (SettingsModel model, string current_path, string term)
+ {
+ SettingComparator comparator = browser_view.sorting_options.get_comparator ();
+ GLib.CompareDataFunc compare = (a, b) => comparator.compare((SettingObject) a, (SettingObject) b);
+
+ if (current_path.has_suffix ("/"))
+ {
+ Directory? local = model.get_directory (current_path);
+ if (local != null)
+ {
+ GLib.ListStore key_model = ((!) local).key_model;
+ for (int i = 0; i < key_model.get_n_items (); i++)
+ {
+ SettingObject item = (SettingObject) key_model.get_item (i);
+ if (term in item.name)
+ search_results_model.insert_sorted (item, compare);
+ }
+ }
+ }
+ post_local = (int) search_results_model.get_n_items ();
+ post_bookmarks = post_local;
+ post_folders = post_local;
+
+ if (term == "")
+ return false;
+ return true;
+ }
+
+ private bool bookmark_search (SettingsModel model, string current_path, string term)
+ {
+ foreach (string bookmark in window.get_bookmarks ())
+ {
+ bool local_again = model.get_parent_path (bookmark) == current_path;
+ if (local_again)
+ continue;
+ SettingObject? setting_object = model.get_object (bookmark);
+ if (setting_object == null)
+ continue;
+ if (term in ((!) setting_object).name)
+ {
+ post_bookmarks++;
+ post_folders++;
+ search_results_model.insert (post_bookmarks - 1, (!) setting_object);
+ }
+ }
+
+ return true;
+ }
+
+ private void stop_global_search ()
+ {
+ pause_global_search ();
+ search_nodes.clear ();
+ }
+
+ private void start_global_search (SettingsModel model, string current_path, string term)
+ {
+ search_nodes.push_head ((!) model.get_directory ("/"));
+ resume_global_search (current_path, term);
+ }
+
+ private void pause_global_search ()
+ {
+ if (search_source != null)
+ {
+ Source.remove ((!) search_source);
+ search_source = null;
+ }
+ }
+
+ private void resume_global_search (string current_path, string term)
+ {
+ search_source = Idle.add (() => {
+ if (global_search_step (current_path, term))
+ return true;
+ search_source = null;
+ return false;
+ });
+ }
+
+ private bool global_search_step (string current_path, string term)
+ {
+ if (!search_nodes.is_empty ())
+ {
+ Directory next = (!) search_nodes.pop_head ();
+ bool local_again = next.full_name == current_path;
+
+ GLib.ListStore next_key_model = next.key_model;
+ for (int i = 0; i < next_key_model.get_n_items (); i++)
+ {
+ SettingObject item = (SettingObject) next_key_model.get_item (i);
+ if (item is Directory)
+ {
+ if (!local_again && term in item.name)
+ search_results_model.insert (post_folders++, item);
+ search_nodes.push_tail ((Directory) item); // we still search local children
+ }
+ else
+ {
+ if (!local_again && term in item.name)
+ search_results_model.append (item);
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void update_search_results_header (ListBoxRow row, ListBoxRow? before)
+ {
+ ListBoxRowHeader header = new ListBoxRowHeader ();
+ header.visible = true;
+ header.orientation = Orientation.VERTICAL;
+ header.halign = Align.CENTER;
+
+ string? label_text = null;
+ if (before == null && post_local > 0)
+ label_text = _("Current folder");
+ else if (row.get_index () == post_local && post_local != post_bookmarks)
+ label_text = _("Bookmarks");
+ else if (row.get_index () == post_bookmarks && post_bookmarks != post_folders)
+ label_text = _("Folders");
+ else if (row.get_index () == post_folders)
+ label_text = _("Keys");
+
+ if (label_text != null)
+ {
+ Label label = new Label ((!) label_text);
+ label.visible = true;
+ label.halign = Align.START;
+ label.margin_top = 10; // TODO CSS
+ label.margin_left = 10; // TODO CSS
+ label.get_style_context ().add_class ("dim-label");
+ label.get_style_context ().add_class ("bold-label");
+ header.add (label);
+ }
+
+ if (before != null || label_text != null)
+ {
+ Separator separator = new Separator (Orientation.HORIZONTAL);
+ separator.visible = true;
+ separator.hexpand = true;
+ header.add (separator);
+ }
+
+ row.set_header (header);
+ }
+}
diff --git a/editor/registry-view.vala b/editor/registry-view.vala
index 640cdd7..2fdca7c 100644
--- a/editor/registry-view.vala
+++ b/editor/registry-view.vala
@@ -126,6 +126,11 @@ class RegistryView : Grid, PathElement, BrowsableView
ListBoxRowHeader header = new ListBoxRowHeader ();
header.set_halign (Align.CENTER);
header.show ();
+
+ Separator separator = new Separator (Orientation.HORIZONTAL);
+ separator.show ();
+ separator.set_hexpand (true);
+ header.add (separator);
row.set_header (header);
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]