[dconf-editor] Fusion pathbar and searchentry.



commit 6ae65cf86d4ca650f08612c8e0a71dda8af5a818
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Mon Sep 10 19:08:26 2018 +0200

    Fusion pathbar and searchentry.

 editor/browser-stack.vala         |   6 ++
 editor/browser-view.vala          |   5 +
 editor/dconf-editor.css           |  80 ++++++++++++----
 editor/dconf-editor.gresource.xml |   2 +
 editor/dconf-editor.ui            | 103 ++-------------------
 editor/dconf-model.vala           |  11 ++-
 editor/dconf-window.vala          | 189 ++++++++++++++++++++++++++------------
 editor/help-overlay.ui            |  41 +++++++--
 editor/meson.build                |   4 +
 editor/pathentry.ui               |  55 +++++++++++
 editor/pathentry.vala             | 119 ++++++++++++++++++++++++
 editor/pathwidget.ui              |  69 ++++++++++++++
 editor/pathwidget.vala            | 141 ++++++++++++++++++++++++++++
 editor/registry-list.vala         |  16 +++-
 editor/registry-search.vala       | 156 ++++++++++++++++++++++++-------
 editor/setting-object.vala        |  10 +-
 16 files changed, 789 insertions(+), 218 deletions(-)
---
diff --git a/editor/browser-stack.vala b/editor/browser-stack.vala
index c6a2a1d..f353ef0 100644
--- a/editor/browser-stack.vala
+++ b/editor/browser-stack.vala
@@ -196,6 +196,12 @@ private class BrowserStack : Grid
     * * Keyboard calls
     \*/
 
+    internal void row_grab_focus ()
+    {
+        if (current_view != ViewType.OBJECT)
+            ((RegistryList) stack.get_visible_child ()).row_grab_focus ();
+    }
+
     internal bool return_pressed ()
         requires (current_view == ViewType.SEARCH)
     {
diff --git a/editor/browser-view.vala b/editor/browser-view.vala
index f2671ac..5ee7b44 100644
--- a/editor/browser-view.vala
+++ b/editor/browser-view.vala
@@ -278,6 +278,11 @@ private class BrowserView : Grid
     * * Proxy calls
     \*/
 
+    internal void row_grab_focus ()
+    {
+        current_child.row_grab_focus ();
+    }
+
     internal ViewType current_view { get { return current_child.current_view; }}
 
     // popovers invalidation and toggles hiding/revealing
diff --git a/editor/dconf-editor.css b/editor/dconf-editor.css
index bff7b9e..d673cf2 100644
--- a/editor/dconf-editor.css
+++ b/editor/dconf-editor.css
@@ -56,6 +56,12 @@
 * * lists rows height and icon
 \*/
 
+/* hack: fix next lines misplacement when the first line is a header */
+
+list.keys-list > grid.big-popover.dim-label.vertical + grid.vertical {
+  margin-top:-1px;
+}
+
 /* row height */
                                    .keys-list          > row         { transition:min-height 0.3s,
                                                                                   margin-left 0.3s, 
margin-right 0.3s,
@@ -165,21 +171,56 @@
 * * pathbar
 \*/
 
-.root-button > label {
+/* evolutions on large window */
+              .pathbar {
+  /* nautilus/src/resources/css/Adwaita.css:71 .path-bar-box uses 200ms */
+  transition: border-color     0.3s,
+              background-color 0.3s;
+  border-color:transparent;
+
+  border-width:1px;
+  border-style:solid;
+  border-radius:3px;
+
+  padding:0;
+  margin:6px 0;
+}
+
+.large-window .pathbar {
+  /* like in nautilus/src/resources/css/Adwaita.css:75
+     .path-bar-box.width-maximized */
+  border-color:@borders;
+}
+
+.large-window .pathbar:not(:backdrop) {
+  /* like in nautilus/src/resources/css/Adwaita.css:79
+     .path-bar-box.width-maximized:not(:backdrop) */
+  background-color:@theme_bg_color;
+}
+
+/* root button */
+              .pathbar:dir(ltr) > button.root-button { padding-left :0; margin-left :0; }
+              .pathbar:dir(rtl) > button.root-button { padding-right:0; margin-right:0; }
+
+              .pathbar          > button.root-button > label {
+  padding-left:3px;
+  transition:border-width 0 ease, padding 0 ease;
+  background-position:50% center;
+  margin-left:0;
+  margin-right:0;
   background-image:-gtk-icontheme("ca.desrt.dconf-editor-symbolic");
-  background-position:center;
   background-size:1.1rem;
   background-repeat:no-repeat;
   min-width:1.1rem;
 }
-
-.pathbar {
-  margin:0 4px;
+.large-window .pathbar          > button.root-button > label {
+  padding-left:5px;
+  border-left-width:0;
+  background-position:60% center;
 }
 
+/* pathbar internals */
 .pathbar > label {
-  padding-left:0.1em;
-  padding-right:0.1em;
   color:alpha(currentColor,0.8);
 }
 .pathbar > label:backdrop {
@@ -187,12 +228,12 @@
 }
 
 .pathbar > button {
-  padding-left:0.1em;
-  padding-right:0.1em;
-  margin-left:0.1em;
-  margin-right:0.1em;
+  padding:0 0.1em;
+  margin:0 0.1em;
+
+  outline-offset:-4px;
 
-  outline-offset:0;
+  border-radius:0;
 
   border:none;
   border-color:transparent;
@@ -204,14 +245,21 @@
 
 /* underline selected label */
 .pathbar > button > .item {
-  border-width:2px 0px;
+  border-width:3px 2px;
+  border-radius:0;
+  padding:2px 4px;
   border-style:solid;
   border-color:transparent;
   transition:border-bottom-color 0.3s;
 }
 
+.small-window .pathbar > button > .item {
+  transition:padding             0.3s;  /* here because the class is applied on startup */
+  padding:2px 1px;
+}
+
 .pathbar > button:hover > .item {
-  border-bottom-color:alpha(currentColor,0.5);
+  border-bottom-color:@borders;
 }
 
 .pathbar          > button.active                  > .item,
@@ -259,8 +307,8 @@
 .delayed-settings-popover list > row > grid > label,
 .delayed-settings-popover list > row > grid > grid > label,
 .modifications-revealer button,
-.titlebar > box.pathbar > button,
-.titlebar > box.pathbar > label {
+.titlebar > box > stack > box.pathbar > button,
+.titlebar > box > stack > box.pathbar > label {
   transition:all 0 ease;
 }
 
diff --git a/editor/dconf-editor.gresource.xml b/editor/dconf-editor.gresource.xml
index d6db7d0..6ea530c 100644
--- a/editor/dconf-editor.gresource.xml
+++ b/editor/dconf-editor.gresource.xml
@@ -14,6 +14,8 @@
     <file preprocess="xml-stripblanks">modifications-revealer.ui</file>
     <file preprocess="xml-stripblanks">pathbar.ui</file>
     <file preprocess="xml-stripblanks">pathbar-item.ui</file>
+    <file preprocess="xml-stripblanks">pathentry.ui</file>
+    <file preprocess="xml-stripblanks">pathwidget.ui</file>
     <file preprocess="xml-stripblanks">property-row.ui</file>
     <file preprocess="xml-stripblanks">registry-info.ui</file>
     <file preprocess="xml-stripblanks">registry-placeholder.ui</file>
diff --git a/editor/dconf-editor.ui b/editor/dconf-editor.ui
index b128e20..ffa31ff 100644
--- a/editor/dconf-editor.ui
+++ b/editor/dconf-editor.ui
@@ -24,7 +24,7 @@
             <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="focus-on-click">False</property>
-            <property name="sensitive" bind-source="search-toggle" bind-property="active" 
bind-flags="sync-create|invert-boolean"/>
+            <property name="sensitive" bind-source="path_widget" bind-property="search-mode-enabled" 
bind-flags="sync-create|invert-boolean"/>
             <accelerator key="F10" signal="clicked"/> <!-- TODO report bug: property binding has to be 
defined before accelerator -->
             <style>
               <class name="image-button"/>
@@ -47,41 +47,13 @@
             <property name="pack-type">end</property>
           </packing>
         </child>
-        <child>
-          <object class="GtkToggleButton" id="search-toggle">
-            <property name="visible">True</property>
-            <property name="valign">center</property>
-            <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"/>
-            </style>
-            <child internal-child="accessible">
-              <object class="AtkObject">
-                <property name="AtkObject::accessible-name" translatable="yes">Search</property>
-                <property name="AtkObject::accessible-description" translatable="yes">Search keys</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">edit-find-symbolic</property>
-                <property name="icon-size">1</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
-        </child>
         <child>
           <object class="Bookmarks" id="bookmarks_button">
             <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="focus-on-click">False</property>
             <property name="schema-path">/ca/desrt/dconf-editor/</property>
-            <property name="sensitive" bind-source="search-toggle" bind-property="active" 
bind-flags="sync-create|invert-boolean"/>
+            <property name="sensitive" bind-source="path_widget" bind-property="search-mode-enabled" 
bind-flags="sync-create|invert-boolean"/>
             <!-- <accelerator key="B" signal="activate" modifiers="GDK_CONTROL_MASK"/> TODO -->
             <style>
               <class name="image-button"/> <!-- TODO https://bugzilla.gnome.org/show_bug.cgi?id=756731 -->
@@ -92,10 +64,11 @@
           </packing>
         </child>
         <child type="title">
-          <object class="PathBar" id="pathbar">
+          <object class="PathWidget" id="path_widget">
             <property name="visible">True</property>
-            <property name="hexpand">True</property>
-            <property name="valign">center</property>
+            <property name="hexpand">False</property>
+            <signal name="search-changed" handler="search_changed_cb"/>
+            <signal name="search-stopped" handler="search_stopped_cb"/>
           </object>
           <packing>
             <property name="pack-type">start</property>
@@ -110,70 +83,6 @@
           <object class="GtkGrid">
             <property name="visible">True</property>
             <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 -->
-                        <child>
-                          <object class="GtkButton">
-                            <property name="visible">True</property>
-                            <property name="action-name">ui.reload-search</property>
-                            <style>
-                              <class name="image-button"/>
-                              <class name="suggested-action"/>
-                            </style>
-                            <child internal-child="accessible">
-                              <object class="AtkObject">
-                                <property name="AtkObject::accessible-name" 
translatable="yes">Refresh</property>
-                                <property name="AtkObject::accessible-description" 
translatable="yes">Refresh search results</property>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkImage">
-                                <property name="visible">True</property>
-                                <property name="icon-name">view-refresh-symbolic</property>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-             </object>
-            </child>
             <child>
               <object class="BrowserView" id="browser_view">
                 <property name="visible">True</property>
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
index 3a805a5..34dd423 100644
--- a/editor/dconf-model.vala
+++ b/editor/dconf-model.vala
@@ -195,7 +195,7 @@ private abstract class SettingsModelCore : Object
         return builder.end ();
     }
 
-    protected bool _get_object (string path, out uint16 context_id, out string name)
+    protected bool _get_object (string path, out uint16 context_id, out string name, bool watch)
     {
         SettingObject? object;
         if (ModelUtils.is_key_path (path))
@@ -210,7 +210,7 @@ private abstract class SettingsModelCore : Object
             return false;
         }
 
-        if ((!) object is Key)
+        if (watch && (!) object is Key)
             add_watched_key ((Key) (!) object);
 
         context_id = get_context_id_from_object ((!) object);
@@ -631,7 +631,8 @@ private abstract class SettingsModelCore : Object
                 ((DConfKey) key).disconnect_client (client);
             else assert_not_reached ();
 
-            key.disconnect (key.key_value_changed_handler);
+            if (key.key_value_changed_handler != 0) // FIXME happens since editable paths 3/3
+                key.disconnect (key.key_value_changed_handler);
             key.key_value_changed_handler = 0;
 
             position++;
@@ -1015,9 +1016,9 @@ private class SettingsModel : SettingsModelCore
         copy_action = true;
     }
 
-    internal bool get_object (string path, out uint16 context_id, out string name)
+    internal bool get_object (string path, out uint16 context_id, out string name, bool watch = true)
     {
-        return _get_object (path, out context_id, out name);
+        return _get_object (path, out context_id, out name, watch);
     }
 
     internal string get_fallback_path (string path)
diff --git a/editor/dconf-window.vala b/editor/dconf-window.vala
index ea0e764..7e3fe9d 100644
--- a/editor/dconf-window.vala
+++ b/editor/dconf-window.vala
@@ -57,9 +57,7 @@ private 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 PathWidget path_widget;
 
     [GtkChild] private BrowserView browser_view;
     [GtkChild] private ModificationsRevealer revealer;
@@ -87,16 +85,12 @@ private class DConfWindow : ApplicationWindow
         if (!disable_warning && settings.get_boolean ("show-warning"))
             show.connect (show_initial_warning);
 
-        // maximize before setting default size: only one call to on_size_allocate() so no change of the 
large-window CSS class at start
         if (settings.get_boolean ("window-is-maximized"))
             maximize ();
         set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));
 
         set_css_styles ();
 
-        search_bar.connect_entry (search_entry);
-        search_bar.notify ["search-mode-enabled"].connect (search_changed);
-
         settings.bind ("mouse-use-extra-buttons", this, "mouse-extra-buttons", 
SettingsBindFlags.GET|SettingsBindFlags.NO_SENSITIVITY);
         settings.bind ("mouse-back-button", this, "mouse-back-button", 
SettingsBindFlags.GET|SettingsBindFlags.NO_SENSITIVITY);
         settings.bind ("mouse-forward-button", this, "mouse-forward-button", 
SettingsBindFlags.GET|SettingsBindFlags.NO_SENSITIVITY);
@@ -223,7 +217,7 @@ private class DConfWindow : ApplicationWindow
         else if (browser_view.check_reload (current_type, current_path, !internal_changes))    // handle 
infobars in needed
             reload_view ();
 
-        pathbar.update_ghosts (((SettingsModel) _model).get_fallback_path (pathbar.complete_path), 
search_bar.search_mode_enabled);
+        path_widget.update_ghosts (((SettingsModel) _model).get_fallback_path (path_widget.complete_path));
     }
     private void propagate_gkey_value_push (string full_name, uint16 context, Variant key_value, bool 
is_key_default)
     {
@@ -336,9 +330,20 @@ private class DConfWindow : ApplicationWindow
 
         StyleContext context = get_style_context ();
         if (allocation.width > MAX_ROW_WIDTH + 42)
+        {
+            context.remove_class ("small-window");
             context.add_class ("large-window");
+        }
+        else if (allocation.width < 787)
+        {
+            context.remove_class ("large-window");
+            context.add_class ("small-window");
+        }
         else
+        {
             context.remove_class ("large-window");
+            context.remove_class ("small-window");
+        }
 
         /* save size */
 
@@ -424,6 +429,8 @@ private class DConfWindow : ApplicationWindow
         { "reload-object", reload_object },
         { "reload-search", reload_search },
 
+        { "toggle-search", toggle_search, "b" },
+
         { "reset-recursive", reset_recursively, "s" },
         { "reset-visible", reset_visible, "s" },
 
@@ -505,6 +512,16 @@ private class DConfWindow : ApplicationWindow
         request_search (true);
     }
 
+    private void toggle_search (SimpleAction action, Variant? path_variant)
+        requires (path_variant != null)
+    {
+        bool search_request = ((!) path_variant).get_boolean ();
+        if (search_request && !path_widget.search_mode_enabled)
+            request_search (true, PathEntry.SearchMode.EDIT_PATH_SELECT_ALL);
+        else if (!search_request && path_widget.search_mode_enabled)
+            stop_search ();
+    }
+
     private void reset_recursively (SimpleAction action, Variant? path_variant)
         requires (path_variant != null)
     {
@@ -560,7 +577,7 @@ private class DConfWindow : ApplicationWindow
     {
         browser_view.discard_row_popover ();
 
-        if (search_bar.search_mode_enabled)
+        if (path_widget.search_mode_enabled)
         {
             model.copy_action_called ();
             string selected_row_text = browser_view.get_copy_path_text () ?? saved_view;
@@ -594,11 +611,12 @@ private class DConfWindow : ApplicationWindow
         update_current_path (ViewType.FOLDER, fallback_path);
 
         if (selected_or_empty == "")
-            browser_view.select_row (pathbar.get_selected_child (fallback_path));
+            browser_view.select_row (path_widget.get_selected_child (fallback_path));
         else
             browser_view.select_row (selected_or_empty);
 
-        search_bar.search_mode_enabled = false; // do last to avoid flickering RegistryView before 
PropertiesView when selecting a search result
+        stop_search ();
+        // path_widget.search_mode_enabled = false; // do last to avoid flickering RegistryView before 
PropertiesView when selecting a search result
     }
     private static GLib.ListStore create_key_model (string base_path, Variant? children)
     {
@@ -633,7 +651,7 @@ private class DConfWindow : ApplicationWindow
                     cannot_find_folder (full_name);
             }
             request_folder (ModelUtils.get_parent_path (full_name), full_name, false);
-            pathbar.update_ghosts (model.get_fallback_path (pathbar.complete_path), false);
+            path_widget.update_ghosts (model.get_fallback_path (path_widget.complete_path));
         }
         else
         {
@@ -643,10 +661,11 @@ private class DConfWindow : ApplicationWindow
             update_current_path (ViewType.OBJECT, strdup (full_name));
         }
 
-        search_bar.search_mode_enabled = false; // do last to avoid flickering RegistryView before 
PropertiesView when selecting a search result
+        stop_search ();
+        // path_widget.search_mode_enabled = false; // do last to avoid flickering RegistryView before 
PropertiesView when selecting a search result
     }
 
-    private void request_search (bool reload)
+    private void request_search (bool reload, PathEntry.SearchMode mode = PathEntry.SearchMode.UNCLEAR)
     {
         string selected_row = browser_view.get_selected_row_name ();
         if (reload)
@@ -655,10 +674,12 @@ private class DConfWindow : ApplicationWindow
             browser_view.set_search_parameters (saved_view, bookmarks_button.get_bookmarks ());
             reload_search_next = false;
         }
-        update_current_path (ViewType.SEARCH, search_entry.text);
+        if (mode != PathEntry.SearchMode.UNCLEAR)
+            path_widget.prepare_search (mode);
+        update_current_path (ViewType.SEARCH, path_widget.text);
         browser_view.select_row (selected_row);
-        if (!search_entry.has_focus)
-            search_entry.grab_focus_without_selecting ();
+        if (!path_widget.entry_has_focus)
+            path_widget.entry_grab_focus_without_selecting ();
     }
 
     private void reload_view ()
@@ -692,13 +713,13 @@ private class DConfWindow : ApplicationWindow
 
         browser_view.set_path (type, path);
         bookmarks_button.set_path (type, path);
-        pathbar.set_path (type, path);
+        path_widget.set_path (type, path);
         invalidate_popovers_without_reload ();
     }
 
     private void update_hamburger_menu ()
     {
-        if (search_bar.search_mode_enabled)
+        if (path_widget.search_mode_enabled)
             return;
 
         GLib.Menu section;
@@ -754,24 +775,16 @@ private class DConfWindow : ApplicationWindow
     \*/
 
     [GtkCallback]
-    private void search_changed ()
+    private void search_changed_cb ()
     {
-        if (search_bar.search_mode_enabled)
-            request_search (reload_search_next);
-        else
-            hide_search_view ();
+        request_search (reload_search_next);
     }
 
     [GtkCallback]
-    private void search_cancelled ()
+    private void search_stopped_cb ()
     {
-        if (!search_bar.search_mode_enabled)
-            return;
-        hide_search_view ();
-    }
+        browser_view.row_grab_focus ();
 
-    private void hide_search_view ()
-    {
         reload_search_action.set_enabled (false);
         if (saved_type == ViewType.FOLDER)
             request_folder (saved_view, saved_selection);
@@ -780,6 +793,12 @@ private class DConfWindow : ApplicationWindow
         reload_search_next = true;
     }
 
+    private void stop_search ()
+    {
+        if (path_widget.search_mode_enabled)
+            search_stopped_cb ();
+    }
+
     /*\
     * * Global callbacks
     \*/
@@ -812,14 +831,11 @@ private class DConfWindow : ApplicationWindow
     [GtkCallback]
     private bool on_key_press_event (Widget widget, Gdk.EventKey event)     // TODO better?
     {
-        string name = (!) (Gdk.keyval_name (event.keyval) ?? "");
+        uint keyval = event.keyval;
+        string name = (!) (Gdk.keyval_name (keyval) ?? "");
 
         Widget? focus = get_focus ();
         bool focus_is_text_widget = focus != null && (((!) focus is Entry) || ((!) focus is TextView));
-        if (!focus_is_text_widget)
-            if (name != "F10") // else <Shift>F10 toggles the search_entry popup; see if a976aa9740 fixes 
that in Gtk+ 4
-                if (search_bar.handle_event (event))
-                    return true;
 
         if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)
         {
@@ -866,19 +882,32 @@ private class DConfWindow : ApplicationWindow
                     return true;
 
                 case "f":
-                    if (bookmarks_button.active)
+                    if (bookmarks_button.active)    // should never happen if path_widget.search_mode_enabled
                         bookmarks_button.active = false;
-                    if (info_button.active)
+                    if (info_button.active)         // should never happen if path_widget.search_mode_enabled
                         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 ();
+                    browser_view.discard_row_popover ();   // could happen if path_widget.search_mode_enabled
+                    if (!path_widget.search_mode_enabled)
+                        request_search (true, PathEntry.SearchMode.SEARCH);
+                    else if (!path_widget.entry_has_focus)
+                        path_widget.entry_grab_focus ();
                     else
-                        search_bar.search_mode_enabled = false;
+                        stop_search ();
                     return true;
 
+                case "g":   // usual shortcut for "next-match" in a SearchEntry; see also "Down"
+                    if (bookmarks_button.active == false
+                     && info_button.active == false
+                     && !revealer.get_modifications_list_state ())
+                        return browser_view.down_pressed ();
+                    return false;
+                case "G":   // usual shortcut for "previous-match" in a SearchEntry; see also "Up"
+                    if (bookmarks_button.active == false
+                     && info_button.active == false
+                     && !revealer.get_modifications_list_state ())
+                        return browser_view.up_pressed ();
+                    return false;
+
                 case "i":
                     if (revealer.reveal_child)
                     {
@@ -887,6 +916,17 @@ private class DConfWindow : ApplicationWindow
                     }
                     return false;
 
+                case "l":
+                    if (path_widget.search_mode_enabled)
+                        return false;
+                    request_search (true, PathEntry.SearchMode.EDIT_PATH_MOVE_END);
+                    return true;
+                case "L":
+                    if (path_widget.search_mode_enabled)
+                        return false;
+                    request_search (true, PathEntry.SearchMode.EDIT_PATH_SELECT_LAST_WORD);
+                    return true;
+
                 case "F1":
                     browser_view.discard_row_popover ();
                     if ((event.state & Gdk.ModifierType.SHIFT_MASK) == 0)
@@ -928,7 +968,7 @@ private class DConfWindow : ApplicationWindow
             }
         }
 
-        if (((event.state & Gdk.ModifierType.MOD1_MASK) != 0))
+        if ((event.state & Gdk.ModifierType.MOD1_MASK) != 0)
         {
             if (name == "Up")
             {
@@ -951,24 +991,37 @@ private class DConfWindow : ApplicationWindow
             return false;
         }
 
-        if (name == "Up"
+        if (name == "Up"    // see also <ctrl>G
          && bookmarks_button.active == false
          && info_button.active == false
          && !revealer.get_modifications_list_state ())
             return browser_view.up_pressed ();
-        if (name == "Down"
+        if (name == "Down"  // see also <ctrl>g
          && bookmarks_button.active == false
          && info_button.active == false
          && !revealer.get_modifications_list_state ())
             return browser_view.down_pressed ();
 
-        if ((name == "Return" || name == "KP_Enter")
-         && browser_view.current_view == ViewType.SEARCH
-         && search_entry.has_focus
-         && browser_view.return_pressed ())
+        if (name == "Return" || name == "KP_Enter")
         {
-            search_bar.set_search_mode (false);
-            return true;
+            if (browser_view.current_view == ViewType.SEARCH
+            && path_widget.entry_has_focus
+            && browser_view.return_pressed ())
+            {
+                stop_search ();
+                return true;
+            }
+            return false;
+        }
+
+        if (name == "Escape")
+        {
+            if (path_widget.search_mode_enabled)
+            {
+                stop_search ();
+                return true;
+            }
+            return false;
         }
 
         if (name == "Menu")
@@ -997,12 +1050,34 @@ private class DConfWindow : ApplicationWindow
         if (bookmarks_button.active || info_button.active)
             return false;
 
-        return false;    // browser_view.handle_search_event (event);
+        if (!path_widget.search_mode_enabled &&
+            // see gtk_search_entry_is_keynav() in gtk+/gtk/gtksearchentry.c:388
+            (keyval == Gdk.Key.Tab          || keyval == Gdk.Key.KP_Tab         ||
+             keyval == Gdk.Key.Up           || keyval == Gdk.Key.KP_Up          ||
+             keyval == Gdk.Key.Down         || keyval == Gdk.Key.KP_Down        ||
+             keyval == Gdk.Key.Left         || keyval == Gdk.Key.KP_Left        ||
+             keyval == Gdk.Key.Right        || keyval == Gdk.Key.KP_Right       ||
+             keyval == Gdk.Key.Home         || keyval == Gdk.Key.KP_Home        ||
+             keyval == Gdk.Key.End          || keyval == Gdk.Key.KP_End         ||
+             keyval == Gdk.Key.Page_Up      || keyval == Gdk.Key.KP_Page_Up     ||
+             keyval == Gdk.Key.Page_Down    || keyval == Gdk.Key.KP_Page_Down   ||
+             ((event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK)) != 0) ||
+             name == "space" || name == "KP_Space"))
+            return false;
+
+        if ((!focus_is_text_widget)
+         && (event.is_modifier == 0)
+         && (event.length != 0)
+//       && (name != "F10")     // else <Shift>F10 toggles the search_entry popup; see if a976aa9740 fixes 
that in Gtk+ 4
+         && (path_widget.handle_event (event)))
+            return true;
+
+        return false;
     }
 
     private void go_backward (bool shift)
     {
-        if (search_bar.search_mode_enabled)
+        if (path_widget.search_mode_enabled)
             return;
 
         browser_view.discard_row_popover ();
@@ -1015,10 +1090,10 @@ private class DConfWindow : ApplicationWindow
     }
     private void go_forward (bool shift)
     {
-        if (search_bar.search_mode_enabled)
+        if (path_widget.search_mode_enabled)
             return;
 
-        string complete_path = pathbar.complete_path;
+        string complete_path = path_widget.complete_path;
 
         browser_view.discard_row_popover ();
         if (current_path == complete_path)  // TODO something?
diff --git a/editor/help-overlay.ui b/editor/help-overlay.ui
index 3f81f0b..f0e8476 100644
--- a/editor/help-overlay.ui
+++ b/editor/help-overlay.ui
@@ -30,13 +30,6 @@
                 <property name="accelerator">&lt;Primary&gt;&lt;Shift&gt;d</property>
               </object>
             </child>
-            <child>
-              <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
-                <property name="title" translatable="yes" context="shortcut window">Search bar</property>
-                <property name="accelerator">&lt;Primary&gt;f</property>
-              </object>
-            </child>
             <child>
               <object class="GtkShortcutsShortcut">
                 <property name="visible">True</property>
@@ -174,6 +167,40 @@
             </child>
           </object>
         </child>
+        <child>
+          <object class="GtkShortcutsGroup">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes" context="shortcut window">Search options</property>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes" context="shortcut window">Toggle search</property>
+                <property name="accelerator">&lt;Primary&gt;f</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes" context="shortcut window">Edit path</property>
+                <property name="accelerator">&lt;Primary&gt;l</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes" context="shortcut window">Edit parent 
path</property>
+                <property name="accelerator">&lt;Primary&gt;&lt;Shift&gt;l</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes" context="shortcut window">Browse keys 
tree</property>
+                <property name="accelerator">Escape</property>
+              </object>
+            </child>
+          </object>
+        </child>
         <child>
           <object class="GtkShortcutsGroup">
             <property name="visible">True</property>
diff --git a/editor/meson.build b/editor/meson.build
index 616853e..aef074f 100644
--- a/editor/meson.build
+++ b/editor/meson.build
@@ -80,6 +80,8 @@ sources = files(
   'modifications-handler.vala',
   'modifications-revealer.vala',
   'pathbar.vala',
+  'pathentry.vala',
+  'pathwidget.vala',
   'registry-info.vala',
   'registry-list.vala',
   'registry-placeholder.vala',
@@ -105,6 +107,8 @@ resource_data = files(
   'modifications-revealer.ui',
   'pathbar-item.ui',
   'pathbar.ui',
+  'pathentry.ui',
+  'pathwidget.ui',
   'property-row.ui',
   'registry-info.ui',
   'registry-placeholder.ui',
diff --git a/editor/pathentry.ui b/editor/pathentry.ui
new file mode 100644
index 0000000..2992702
--- /dev/null
+++ b/editor/pathentry.ui
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <template class="PathEntry" parent="GtkBox"> <!-- https://bugzilla.gnome.org/show_bug.cgi?id=769876 -->
+    <property name="orientation">horizontal</property>
+    <property name="valign">center</property>
+    <property name="hexpand">True</property>
+    <style>
+      <class name="linked"/>
+    </style>
+    <child>
+      <object class="GtkSearchEntry" id="search_entry">
+        <property name="visible">True</property>
+        <property name="hexpand">True</property>
+        <signal name="search-changed" handler="search_changed_cb"/>
+        <signal name="stop-search"    handler="search_stopped_cb"/>
+        <!-- "next-match" (<ctrl>g) and "previous-match" (<ctrl>G) are handled in dconf-window.vala -->
+      </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 -->
+    <child>
+      <object class="GtkButton">
+        <property name="visible">True</property>
+        <property name="action-name">ui.reload-search</property>
+        <style>
+          <class name="image-button"/>
+          <class name="suggested-action"/>
+        </style>
+        <child internal-child="accessible">
+          <object class="AtkObject">
+            <property name="AtkObject::accessible-name" translatable="yes">Refresh</property>
+            <property name="AtkObject::accessible-description" translatable="yes">Refresh search 
results</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon-name">view-refresh-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/editor/pathentry.vala b/editor/pathentry.vala
new file mode 100644
index 0000000..9b19366
--- /dev/null
+++ b/editor/pathentry.vala
@@ -0,0 +1,119 @@
+/*
+  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/pathentry.ui")]
+private class PathEntry : Box
+{
+    [GtkChild] SearchEntry search_entry;
+
+    private string current_path = "";
+
+    internal string text { get { return search_entry.text; }}
+    internal bool entry_has_focus { get { return search_entry.has_focus; }}
+
+    internal enum SearchMode {
+        UNCLEAR,
+        EDIT_PATH_MOVE_END,
+        EDIT_PATH_SELECT_ALL,
+        EDIT_PATH_SELECT_LAST_WORD,
+        SEARCH
+    }
+
+    internal signal void search_changed ();
+    internal signal void search_stopped ();
+
+    internal void entry_grab_focus_without_selecting ()
+    {
+        if (search_entry.text_length != 0)
+        {
+            if (search_entry.cursor_position == search_entry.text_length)
+                search_entry.move_cursor (MovementStep.DISPLAY_LINE_ENDS, -1, false);
+            search_entry.move_cursor (MovementStep.DISPLAY_LINE_ENDS, 1, false);
+        }
+        search_entry.grab_focus_without_selecting ();
+    }
+    internal void entry_grab_focus ()
+    {
+        search_entry.grab_focus ();
+    }
+    internal bool handle_event (Gdk.EventKey event)
+    {
+        return search_entry.handle_event (event);
+    }
+
+    internal void set_path (ViewType type, string path)
+    {
+        current_path = path;
+//        if (type == ViewType.SEARCH)
+    }
+
+    internal void prepare (SearchMode mode)
+    {
+        switch (mode)
+        {
+            case SearchMode.EDIT_PATH_MOVE_END:
+                search_entry.text = current_path;
+                entry_grab_focus_without_selecting ();
+                return;
+
+            case SearchMode.EDIT_PATH_SELECT_ALL:
+                search_entry.text = current_path;
+                search_entry.grab_focus ();
+                return;
+
+            case SearchMode.EDIT_PATH_SELECT_LAST_WORD:
+                search_entry.text = current_path;
+                if (search_entry.text_length == 1)  // root
+                {
+                    search_entry.grab_focus ();
+                    return;
+                }
+                if (search_entry.text_length != 0)
+                {
+                    if (search_entry.cursor_position == search_entry.text_length)
+                        search_entry.move_cursor (MovementStep.DISPLAY_LINE_ENDS, -1, false);
+                    search_entry.move_cursor (MovementStep.VISUAL_POSITIONS, ModelUtils.get_parent_path 
(current_path).length, false);
+                    search_entry.move_cursor (MovementStep.DISPLAY_LINE_ENDS, 1, true);
+                }
+                search_entry.grab_focus_without_selecting ();
+                return;
+
+            case SearchMode.SEARCH:
+                search_entry.text = "";
+                search_entry.grab_focus ();
+                return;
+
+            case SearchMode.UNCLEAR:
+            default:
+                assert_not_reached ();
+        }
+    }
+
+    [GtkCallback]
+    private void search_changed_cb ()
+    {
+        search_changed ();
+    }
+
+    [GtkCallback]
+    private void search_stopped_cb ()
+    {
+        search_stopped ();
+    }
+}
diff --git a/editor/pathwidget.ui b/editor/pathwidget.ui
new file mode 100644
index 0000000..65d1400
--- /dev/null
+++ b/editor/pathwidget.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <template class="PathWidget" parent="GtkBox">
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkStack" id="pathbar_stack">
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+        <property name="visible-child">pathbar</property> <!-- uses the "id" attribute -->
+        <child>
+          <object class="PathEntry" id="searchentry">
+            <property name="visible">True</property>
+            <signal name="search-changed" handler="search_changed_cb"/>
+            <signal name="search-stopped" handler="search_stopped_cb"/>
+          </object>
+          <packing>
+            <property name="icon-name">document-edit-symbolic</property> <!-- TODO document-edit-symbolic ? 
-->
+            <property name="title" translatable="yes">Edit path</property>
+            <property name="name">entry</property>
+          </packing>
+        </child>
+        <child>
+          <object class="PathBar" id="pathbar">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+          </object>
+          <packing>
+            <property name="icon-name">folder-visiting-symbolic</property>
+            <property name="title" translatable="yes">Browse keys</property>
+            <property name="name">pathbar</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="pack-type">start</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToggleButton" id="search_toggle">
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+        <property name="focus-on-click">False</property>
+        <property name="action-name">ui.toggle-search</property>
+        <property name="action-target">true</property>
+        <!-- <accelerator key="F" signal="toggled" modifiers="GDK_CONTROL_MASK"/> TODO -->
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child internal-child="accessible">
+          <object class="AtkObject">
+            <property name="AtkObject::accessible-name" translatable="yes">Search</property>
+            <property name="AtkObject::accessible-description" translatable="yes">Search keys</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon-name">edit-find-symbolic</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="pack-type">end</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/editor/pathwidget.vala b/editor/pathwidget.vala
new file mode 100644
index 0000000..01e38dd
--- /dev/null
+++ b/editor/pathwidget.vala
@@ -0,0 +1,141 @@
+/*
+  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 <https://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/pathwidget.ui")]
+private class PathWidget : Box
+{
+    [GtkChild] private ToggleButton search_toggle;
+    [GtkChild] private Stack        pathbar_stack;
+    [GtkChild] private PathBar      pathbar;
+    [GtkChild] private PathEntry    searchentry;
+
+    internal signal void search_changed ();
+    internal signal void search_stopped ();
+
+    /*\
+    * * search mode
+    \*/
+
+    internal bool search_mode_enabled { get; private set; default = false; }
+
+    private void enter_search_mode ()
+    {
+        search_mode_enabled = true;
+        search_toggle.active = true;
+        search_toggle.set_action_target_value (false);
+        pathbar_stack.set_visible_child (searchentry);
+    }
+
+    private void leave_search_mode ()
+    {
+        search_mode_enabled = false;
+        search_toggle.active = false;
+        search_toggle.set_action_target_value (true);
+        pathbar_stack.set_visible_child (pathbar);
+    }
+
+    /*\
+    * * callbacks
+    \*/
+
+    [GtkCallback]
+    private void search_changed_cb ()
+    {
+        if (search_mode_enabled)
+            search_changed ();
+    }
+
+    [GtkCallback]
+    private void search_stopped_cb ()
+    {
+        search_stopped ();
+    }
+
+    /*\
+    * * proxy calls
+    \*/
+
+    internal void set_path (ViewType type, string path)
+    {
+        pathbar.set_path (type, path);
+        searchentry.set_path (type, path);
+
+        if (type == ViewType.SEARCH && !search_mode_enabled)
+            enter_search_mode ();
+        else if (type != ViewType.SEARCH && search_mode_enabled)
+            leave_search_mode ();
+    }
+
+    /* path bar */
+    internal string complete_path { get { return pathbar.complete_path; }}
+
+    internal void update_ghosts (string fallback_path)
+    {
+        pathbar.update_ghosts (fallback_path, search_mode_enabled);
+    }
+
+    internal string get_selected_child (string fallback_path)
+    {
+        return pathbar.get_selected_child (fallback_path);
+    }
+
+    /* path entry */
+    internal string text                   { get { return searchentry.text; }}
+    internal bool entry_has_focus          { get { return searchentry.entry_has_focus; }}
+    internal void entry_grab_focus ()                   { searchentry.entry_grab_focus (); }
+    internal void entry_grab_focus_without_selecting () { searchentry.entry_grab_focus_without_selecting (); 
}
+
+    internal bool handle_event (Gdk.EventKey event)
+    {
+        searchentry.prepare (PathEntry.SearchMode.SEARCH);
+        if (!searchentry.handle_event (event))
+            return false;
+        enter_search_mode ();
+        return true;
+    }
+
+    internal void prepare_search (PathEntry.SearchMode mode)
+    {
+        searchentry.prepare (mode);
+    }
+
+/*      string [] tokens = full_name.split (" ");
+        uint index = 0;
+        string token;
+        while (index < tokens.length)
+        {
+            token = tokens [index];
+            if (token.has_prefix ("/"))
+            {
+                path_requested (token, pathbar.get_selected_child (token));
+                break;
+            }
+            index++;
+        } */
+
+    /*\
+    * * sizing
+    \*/
+
+    internal override void get_preferred_width (out int minimum_width, out int natural_width)
+    {
+        base.get_preferred_width (out minimum_width, out natural_width);
+        natural_width = MAX_ROW_WIDTH;  // see key-list-box-row.vala
+    }
+}
diff --git a/editor/registry-list.vala b/editor/registry-list.vala
index 13158a6..16551c1 100644
--- a/editor/registry-list.vala
+++ b/editor/registry-list.vala
@@ -25,7 +25,8 @@ private abstract class RegistryList : Grid, BrowsableView
     [GtkChild] private ScrolledWindow scrolled;
 
     protected bool search_mode { private get; set; }
-    protected string? current_path_if_search_mode = null;  // TODO only used in search mode
+    protected string? current_path_if_search_mode = null;   // TODO only used in search mode
+    protected bool search_is_path_search = false;           // TODO only used in search mode
 
     protected GLib.ListStore list_model = new GLib.ListStore (typeof (SimpleSettingObject));
 
@@ -233,6 +234,15 @@ private abstract class RegistryList : Grid, BrowsableView
         ((ClickableListBoxRow) ((!) selected_row).get_child ()).destroy_popover ();
     }
 
+    internal void row_grab_focus ()
+    {
+        ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
+        if (selected_row == null)
+            return;
+
+        ((!) selected_row).grab_focus ();
+    }
+
     internal bool up_or_down_pressed (bool is_down)
     {
         ListBoxRow? selected_row = (ListBoxRow?) key_list_box.get_selected_row ();
@@ -284,7 +294,9 @@ private abstract class RegistryList : Grid, BrowsableView
 
         if (search_mode && current_path_if_search_mode == null)
             assert_not_reached ();
-        bool search_mode_non_local_result = search_mode && ModelUtils.get_parent_path (full_name) != (!) 
current_path_if_search_mode;
+        bool search_mode_non_local_result = search_mode
+                                         && (search_is_path_search
+                                          || ModelUtils.get_parent_path (full_name) != (!) 
current_path_if_search_mode);
 
         if (ModelUtils.is_folder_context_id (context_id))
         {
diff --git a/editor/registry-search.vala b/editor/registry-search.vala
index a3f9b1e..a61213a 100644
--- a/editor/registry-search.vala
+++ b/editor/registry-search.vala
@@ -55,6 +55,7 @@ private class RegistrySearch : RegistryList
         stop_global_search ();
 
         post_local = -1;
+        post_paths = -1;
         post_bookmarks = -1;
         post_folders = -1;
 
@@ -139,6 +140,7 @@ private class RegistrySearch : RegistryList
     // 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_paths;
     private int post_bookmarks;
     private int post_folders;
     private uint? search_source = null;
@@ -153,17 +155,19 @@ private class RegistrySearch : RegistryList
             return;
         }
 
+        bool old_term_is_term_prefix = old_term != null && term.has_prefix ((!) old_term);
         SettingsModel model = modifications_handler.model;
-        if (old_term != null && term.has_prefix ((!) old_term))
+        if (old_term_is_term_prefix && !(term.has_prefix ("/") && term.slice (((!) old_term).length, 
term.length).contains ("/")))
         {
             pause_global_search (ref search_source);
-            refine_local_results (term, ref list_model, ref post_local, ref post_bookmarks, ref 
post_folders);
-            refine_bookmarks_results (term, post_local, ref list_model, ref post_bookmarks, ref 
post_folders);
+            refine_local_results (term, ref list_model, ref post_local, ref post_paths, ref post_bookmarks, 
ref post_folders);
+            refine_paths_results (term, post_local, ref list_model, ref post_paths, ref post_bookmarks, ref 
post_folders);
+            refine_bookmarks_results (term, post_paths, ref list_model, ref post_bookmarks, ref 
post_folders);
             if ((!) old_term == "")
                 start_global_search ((!) current_path_if_search_mode, term);
             else
             {
-                refine_global_results (term, post_local, ref list_model, ref post_folders);
+                refine_global_results (term, post_bookmarks, ref list_model, ref post_folders);
                 resume_global_search ((!) current_path_if_search_mode, term); // update search term
             }
 
@@ -178,11 +182,18 @@ private class RegistrySearch : RegistryList
             stop_global_search ();
 
             local_search (model, sorting_options, ModelUtils.get_base_path ((!) 
current_path_if_search_mode), term, ref list_model);
+
             post_local = (int) list_model.get_n_items ();
-            post_bookmarks = post_local;
-            post_folders = post_local;
+            post_paths = post_local;
+
+            search_is_path_search = term.has_prefix ("/");
+            if (search_is_path_search)
+                paths_search (model, term, ref list_model, ref post_paths);
+            post_bookmarks = post_paths;
+
+            bookmark_search (model, (!) current_path_if_search_mode, term, bookmarks, ref list_model, ref 
post_bookmarks);
+            post_folders = post_bookmarks;
 
-            bookmark_search (model, (!) current_path_if_search_mode, term, bookmarks, ref list_model, ref 
post_bookmarks, ref post_folders);
             key_list_box.bind_model (list_model, new_list_box_row);
 
             _select_first_row (key_list_box);
@@ -195,14 +206,22 @@ private class RegistrySearch : RegistryList
         old_term = term;
     }
 
-    private static void refine_local_results (string term, ref GLib.ListStore list_model, ref int 
post_local, ref int post_bookmarks, ref int post_folders)
+    private static void refine_local_results (string term, ref GLib.ListStore list_model, ref int 
post_local, ref int post_paths, ref int post_bookmarks, ref int post_folders)
     {
+        if (post_local < 0)
+            assert_not_reached ();
+        if (post_local == 0)
+            return;
+
         for (int i = post_local - 1; i >= 0; i--)
         {
-            SimpleSettingObject item = (SimpleSettingObject) list_model.get_item (i);
-            if (!(term in item.name))
+            SimpleSettingObject? item = (SimpleSettingObject?) list_model.get_item (i);
+            if (item == null)
+                assert_not_reached ();
+            if (!(term in ((!) item).name))
             {
                 post_local--;
+                post_paths--;
                 post_bookmarks--;
                 post_folders--;
                 list_model.remove (i);
@@ -210,12 +229,41 @@ private class RegistrySearch : RegistryList
         }
     }
 
-    private static void refine_bookmarks_results (string term, int post_local, ref GLib.ListStore 
list_model, ref int post_bookmarks, ref int post_folders)
+    private static void refine_paths_results (string term, int post_local, ref GLib.ListStore list_model, 
ref int post_paths, ref int post_bookmarks, ref int post_folders)
     {
-        for (int i = post_bookmarks - 1; i >= post_local; i--)
+        if (post_paths < post_local)
+            assert_not_reached ();
+        if (post_paths == post_local)
+            return;
+
+        for (int i = post_paths - 1; i >= post_local; i--)
         {
-            SimpleSettingObject item = (SimpleSettingObject) list_model.get_item (i);
-            if (!(term in item.name))
+            SimpleSettingObject? item = (SimpleSettingObject?) list_model.get_item (i);
+            if (item == null)
+                assert_not_reached ();
+            if (!(term in ((!) item).full_name))
+            {
+                post_paths--;
+                post_bookmarks--;
+                post_folders--;
+                list_model.remove (i);
+            }
+        }
+    }
+
+    private static void refine_bookmarks_results (string term, int post_paths, ref GLib.ListStore 
list_model, ref int post_bookmarks, ref int post_folders)
+    {
+        if (post_bookmarks < post_paths)
+            assert_not_reached ();
+        if (post_bookmarks == post_paths)
+            return;
+
+        for (int i = post_bookmarks - 1; i >= post_paths; i--)
+        {
+            SimpleSettingObject? item = (SimpleSettingObject?) list_model.get_item (i);
+            if (item == null)
+                assert_not_reached ();
+            if (!(term in ((!) item).name))
             {
                 post_bookmarks--;
                 post_folders--;
@@ -224,7 +272,7 @@ private class RegistrySearch : RegistryList
         }
     }
 
-    private static void refine_global_results (string term, int post_local, ref GLib.ListStore list_model, 
ref int post_folders)
+    private static void refine_global_results (string term, int post_bookmarks, ref GLib.ListStore 
list_model, ref int post_folders)
     {
         for (int i = (int) list_model.get_n_items () - 1; i >= post_folders; i--)
         {
@@ -232,7 +280,7 @@ private class RegistrySearch : RegistryList
             if (!(term in item.name))
                 list_model.remove (i);
         }
-        for (int i = post_folders - 1; i >= post_local; i--)
+        for (int i = post_folders - 1; i >= post_bookmarks; i--)
         {
             SimpleSettingObject item = (SimpleSettingObject) list_model.get_item (i);
             if (!(term in item.name))
@@ -250,23 +298,66 @@ private class RegistrySearch : RegistryList
         GLib.CompareDataFunc compare = (a, b) => comparator.compare ((SimpleSettingObject) a, 
(SimpleSettingObject) b);
 
         Variant? key_model = model.get_children (current_path, true, false); // here to update watched keys 
even coming from RegistryInfo
-        if (key_model != null)
+        if (key_model == null)
+            return;
+
+        VariantIter iter = new VariantIter ((!) key_model);
+        uint16 context_id;
+        string name;
+        while (iter.next ("(qs)", out context_id, out name))
         {
-            VariantIter iter = new VariantIter ((!) key_model);
-            uint16 context_id;
-            string name;
-            while (iter.next ("(qs)", out context_id, out name))
+            if (term in name)
+            {
+                SimpleSettingObject sso = new SimpleSettingObject.from_base_path (context_id, name, 
current_path);
+                list_model.insert_sorted (sso, compare);
+            }
+        }
+    }
+
+    private static void paths_search (SettingsModel model, string term, ref GLib.ListStore list_model, ref 
int post_paths)
+    {
+        uint16 context_id;
+        string name;
+
+        string base_path;
+        if (ModelUtils.is_key_path (term))
+            base_path = ModelUtils.get_parent_path (term);
+        else if (model.get_object (term, out context_id, out name, false))   // TODO model.get_folder
+        {
+            SimpleSettingObject sso = new SimpleSettingObject.from_full_name (context_id, name, term);
+            list_model.insert (post_paths, sso);
+            post_paths++;
+            base_path = term;
+        }
+        else    // strange construction because else something wrong happens
+            base_path = ModelUtils.get_parent_path (term);
+
+        Variant? key_model = model.get_children (base_path, true, false);
+        if (key_model == null)
+            return;
+
+        int post_subfolders = post_paths;
+        VariantIter iter = new VariantIter ((!) key_model);
+        while (iter.next ("(qs)", out context_id, out name))
+        {
+            bool is_folder = context_id == ModelUtils.folder_context_id;
+            string full_name = ModelUtils.recreate_full_name (base_path, name, is_folder);
+            if (term in full_name)
             {
-                if (term in name)
+                SimpleSettingObject sso = new SimpleSettingObject.from_full_name (context_id, name, 
full_name);
+                if (is_folder)
                 {
-                    SimpleSettingObject sso = new SimpleSettingObject.from_base_path (context_id, name, 
current_path);
-                    list_model.insert_sorted (sso, compare);
+                    list_model.insert (post_subfolders, sso);
+                    post_subfolders++;
                 }
+                else
+                    list_model.insert (post_paths, sso);
+                post_paths++;
             }
         }
     }
 
-    private static void bookmark_search (SettingsModel model, string current_path, string term, string [] 
bookmarks, ref GLib.ListStore list_model, ref int post_bookmarks, ref int post_folders)
+    private static void bookmark_search (SettingsModel model, string current_path, string term, string [] 
bookmarks, ref GLib.ListStore list_model, ref int post_bookmarks)
     {
         foreach (string bookmark in bookmarks)
         {
@@ -282,10 +373,9 @@ private class RegistrySearch : RegistryList
 
             if (term in name)
             {
-                post_bookmarks++;
-                post_folders++;
                 SimpleSettingObject sso = new SimpleSettingObject.from_full_name (context_id, name, 
bookmark);
-                list_model.insert (post_bookmarks - 1, sso);
+                list_model.insert (post_bookmarks, sso);
+                post_bookmarks++;
             }
         }
     }
@@ -346,7 +436,7 @@ private class RegistrySearch : RegistryList
                 if (!local_again && !(full_name in bookmarks) && term in name)
                 {
                     SimpleSettingObject sso = new SimpleSettingObject.from_full_name (context_id, name, 
full_name);
-                    list_model.insert (post_folders++, sso);
+                    list_model.insert (post_folders++, sso); // do not move the ++ outside
                 }
                 search_nodes.push_tail (full_name); // we still search local children
             }
@@ -367,15 +457,17 @@ private class RegistrySearch : RegistryList
 
     private void update_row_header (ListBoxRow row, ListBoxRow? before)
     {
-        string? label_text = get_header_text (row.get_index (), post_local, post_bookmarks, post_folders);
+        string? label_text = get_header_text (row.get_index (), post_local, post_paths, post_bookmarks, 
post_folders);
         ListBoxRowHeader header = new ListBoxRowHeader (before == null, label_text);
         row.set_header (header);
     }
-    private static string? get_header_text (int row_index, int post_local, int post_bookmarks, int 
post_folders)
+    private static string? get_header_text (int row_index, int post_local, int post_paths, int 
post_bookmarks, int post_folders)
     {
         if (row_index == 0 && post_local > 0)
             return _("Current folder");
-        if (row_index == post_local && post_local != post_bookmarks)
+        if (row_index == post_local && post_local != post_paths)
+            return _("Paths");
+        if (row_index == post_paths && post_paths != post_bookmarks)
             return _("Bookmarks");
         if (row_index == post_bookmarks && post_bookmarks != post_folders)
             return _("Folders");
diff --git a/editor/setting-object.vala b/editor/setting-object.vala
index 4ab926e..8a915a3 100644
--- a/editor/setting-object.vala
+++ b/editor/setting-object.vala
@@ -269,8 +269,11 @@ private class DConfKey : Key
 
     private ulong client_changed_handler = 0;
     internal void connect_client (DConf.Client client)
-        requires (client_changed_handler == 0)
+//        requires (client_changed_handler == 0)
     {
+        if (client_changed_handler != 0)    // FIXME happens since editable paths 1/3
+            return;
+
         client_changed_handler = client.changed.connect ((client, prefix, changes, tag) => {
                 foreach (string item in changes)
                     if (prefix + item == full_name)
@@ -281,8 +284,11 @@ private class DConfKey : Key
             });
     }
     internal void disconnect_client (DConf.Client client)
-        requires (client_changed_handler != 0)
+//        requires (client_changed_handler != 0)
     {
+        if (client_changed_handler == 0)    // FIXME happens since editable paths 2/3
+            return;
+
         client.disconnect (client_changed_handler);
         client_changed_handler = 0;
     }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]