1/*
2  This file is part of Dconf Editor
3
4  Dconf Editor is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8
9  Dconf Editor is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  GNU General Public License for more details.
13
14  You should have received a copy of the GNU General Public License
15  along with Dconf Editor.  If not, see <https://www.gnu.org/licenses/>.
16*/
17
18using Gtk;
19
20internal enum BookmarkIcon {
21    VALID_FOLDER,
22    SEARCH,       /* TODO valid and invalid search; broken thing also, etc. */
23    DCONF_OBJECT,
24    KEY_DEFAULTS,
25    EDITED_VALUE,
26
27    /* same icon */
28    EMPTY_FOLDER,
29    EMPTY_OBJECT;
30}
31
32[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/bookmarks.ui")]
33private class Bookmarks : MenuButton
34{
35    [GtkChild] private unowned Image                bookmarks_icon;
36    [GtkChild] private unowned Popover              bookmarks_popover;
37    [GtkChild] private unowned Stack                edit_mode_stack;
38    [GtkChild] private unowned BookmarksList        bookmarks_list;
39    [GtkChild] private unowned Switch               bookmarked_switch;
40    [GtkChild] private unowned Label                switch_label;
41    [GtkChild] private unowned BookmarksController  bookmarks_controller;
42
43    private string   current_path = "/";
44    private ViewType current_type = ViewType.FOLDER;
45
46    private string schema_id = "ca.desrt.dconf-editor.Bookmarks";   // TODO move in a library
47    GLib.Settings settings;
48    [CCode (notify = false)] public string schema_path
49    {
50        construct
51        {
52            bookmarks_list.schema_path = value;
53
54            settings = new GLib.Settings.with_path (schema_id, value);
55
56            StyleContext context = bookmarks_popover.get_style_context ();
57            bool has_small_bookmarks_rows_class = false;
58            ulong small_bookmarks_rows_handler = settings.changed ["small-bookmarks-rows"].connect (() => {
59                    bool small_bookmarks_rows = settings.get_boolean ("small-bookmarks-rows");
60                    if (small_bookmarks_rows)
61                    {
62                        if (!has_small_bookmarks_rows_class) context.add_class ("small-bookmarks-rows");
63                    }
64                    else if (has_small_bookmarks_rows_class) context.remove_class ("small-bookmarks-rows");
65                    has_small_bookmarks_rows_class = small_bookmarks_rows;
66                    bookmarks_controller.update_rows_size_button_icon (small_bookmarks_rows);
67                });
68
69            has_small_bookmarks_rows_class = settings.get_boolean ("small-bookmarks-rows");
70            if (has_small_bookmarks_rows_class)
71                context.add_class ("small-bookmarks-rows");
72            bookmarks_controller.update_rows_size_button_icon (has_small_bookmarks_rows_class);
73
74            destroy.connect (() => settings.disconnect (small_bookmarks_rows_handler));
75        }
76    }
77
78    internal signal void update_bookmarks_icons (Variant bookmarks_variant);
79    [GtkCallback]
80    private void on_update_bookmarks_icons (Variant bookmarks_variant)
81    {
82        update_bookmarks_icons (bookmarks_variant);
83    }
84
85    construct
86    {
87        update_switch_label (ViewType.SEARCH, ViewType.FOLDER, switch_label); // init text with "Bookmark this Location"
88
89        install_action_entries ();
90
91        clicked.connect (() => { if (active) bookmarked_switch.grab_focus (); });
92    }
93
94    internal Bookmarks (string _schema_path)
95    {
96        Object (schema_path: _schema_path);
97    }
98
99    [GtkCallback]
100    private void on_bookmarks_changed (Variant bookmarks_variant, bool writable)
101    {
102        set_detailed_action_name ("bw.update-bookmarks-icons(" + bookmarks_variant.print (true) + ")");  // TODO disable action on popover closed
103
104        if (bookmarks_variant.get_strv ().length == 0)
105        {
106            string? visible_child_name = edit_mode_stack.get_visible_child_name (); // do it like that
107            if (visible_child_name != null && (!) visible_child_name == "edit-mode-on")
108                leave_edit_mode ();
109        }
110
111        update_icon_and_switch (bookmarks_variant);
112        set_switch_sensitivity (writable);
113    }
114
115    [GtkCallback]
116    private void on_writability_changed (bool writable)
117    {
118        set_switch_sensitivity (writable);
119    }
120
121    private void set_switch_sensitivity (bool writable)
122    {
123        if (writable)
124        {
125            string? visible_child_name = edit_mode_stack.get_visible_child_name (); // do it like that
126            if (visible_child_name != null && (!) visible_child_name == "edit-mode-disabled")
127                edit_mode_stack.set_visible_child_name ("edit-mode-off");
128        }
129        else
130        {
131            edit_mode_stack.set_visible_child_name ("edit-mode-disabled");
132            bookmarks_list.grab_focus ();
133        }
134    }
135
136    /*\
137    * * Callbacks
138    \*/
139
140    [GtkCallback]
141    private bool on_key_press_event (Widget widget, Gdk.EventKey event)
142    {
143        uint keyval = event.keyval;
144        string name = (!) (Gdk.keyval_name (keyval) ?? "");
145
146        string? visible_child_name = edit_mode_stack.get_visible_child_name ();
147        bool edit_mode_on = visible_child_name != null && (!) visible_child_name == "edit-mode-on";
148
149        if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)
150        {
151            if (edit_mode_on)
152            {
153                if (name == "a")
154                {
155                    bookmarks_list.select_all ();
156                    return true;
157                }
158                if (name == "A")
159                {
160                    bookmarks_list.unselect_all ();
161                    return true;
162                }
163            }
164        }
165
166        if (keyval == Gdk.Key.Escape && edit_mode_on)
167        {
168            leave_edit_mode ();
169            return true;
170        }
171        return false;
172    }
173
174    [GtkCallback]
175    private void on_selection_changed ()
176    {
177        if (actions_init_done)
178            update_actions ();
179    }
180
181    /*\
182    * * Public calls
183    \*/
184
185    internal void set_path (ViewType type, string path)
186    {
187        update_switch_label (current_type, type, switch_label);
188
189        current_path = path;
190        current_type = type;
191
192        update_icon_and_switch (bookmarks_list.get_bookmarks_as_variant ());
193    }
194
195    // for search
196    internal string [] get_bookmarks ()
197    {
198        return bookmarks_list.get_bookmarks_as_array ();
199    }
200
201    // keyboard call
202
203    internal bool next_match ()
204        requires (active)
205    {
206        return bookmarks_list.next_match ();
207    }
208
209    internal bool previous_match ()
210        requires (active)
211    {
212        return bookmarks_list.previous_match ();
213    }
214
215    internal bool handle_copy_text (out string copy_text)
216    {
217        return bookmarks_list.handle_copy_text (out copy_text);
218    }
219
220    internal void bookmark_current_path ()
221    {
222        if (bookmarked_switch.get_active ())
223            return;
224        bookmarks_list.append_bookmark (current_type, current_path);
225    }
226
227    internal void unbookmark_current_path ()
228    {
229        if (!bookmarked_switch.get_active ())
230            return;
231        bookmarks_list.remove_bookmark (current_type, current_path);
232    }
233
234    internal void update_bookmark_icon (string bookmark, BookmarkIcon icon)
235    {
236        bookmarks_list.update_bookmark_icon (bookmark, icon);
237    }
238
239    /*\
240    * * Action entries
241    \*/
242
243    bool actions_init_done = false;
244    private SimpleAction move_top_action;
245    private SimpleAction move_up_action;
246    private SimpleAction move_down_action;
247    private SimpleAction move_bottom_action;
248    private SimpleAction trash_bookmark_action;
249    private SimpleAction edit_mode_state_action;
250
251    private void update_actions ()
252        requires (actions_init_done)
253    {
254        _update_actions (bookmarks_list.get_selection_state (), ref move_top_action, ref move_up_action, ref move_down_action, ref move_bottom_action, ref trash_bookmark_action);
255    }
256
257    internal static void _update_actions (OverlayedList.SelectionState selection_state, ref SimpleAction move_top_action, ref SimpleAction move_up_action, ref SimpleAction move_down_action, ref SimpleAction move_bottom_action, ref SimpleAction trash_bookmark_action)
258    {
259        trash_bookmark_action.set_enabled (selection_state != OverlayedList.SelectionState.EMPTY);
260
261        bool one_middle_selection = selection_state == OverlayedList.SelectionState.MIDDLE;
262        bool enable_move_up_action      = one_middle_selection || (selection_state == OverlayedList.SelectionState.LAST);
263        bool enable_move_down_action    = one_middle_selection || (selection_state == OverlayedList.SelectionState.FIRST);
264        move_up_action.set_enabled     (enable_move_up_action);
265        move_down_action.set_enabled   (enable_move_down_action);
266
267        bool multiple_middle_selections = selection_state == OverlayedList.SelectionState.MULTIPLE;
268        move_top_action.set_enabled    ((selection_state == OverlayedList.SelectionState.MULTIPLE_LAST)
269                                        || multiple_middle_selections
270                                        || enable_move_up_action);
271        move_bottom_action.set_enabled ((selection_state == OverlayedList.SelectionState.MULTIPLE_FIRST)
272                                        || multiple_middle_selections
273                                        || enable_move_down_action);
274    }
275
276    private void install_action_entries ()
277    {
278        SimpleActionGroup action_group = new SimpleActionGroup ();
279        action_group.add_action_entries (action_entries, this);
280        insert_action_group ("bookmarks", action_group);
281
282        move_top_action         = (SimpleAction) action_group.lookup_action ("move-top");
283        move_up_action          = (SimpleAction) action_group.lookup_action ("move-up");
284        move_down_action        = (SimpleAction) action_group.lookup_action ("move-down");
285        move_bottom_action      = (SimpleAction) action_group.lookup_action ("move-bottom");
286        trash_bookmark_action   = (SimpleAction) action_group.lookup_action ("trash-bookmark");
287        edit_mode_state_action  = (SimpleAction) action_group.lookup_action ("set-edit-mode");
288        actions_init_done = true;
289    }
290
291    private const GLib.ActionEntry [] action_entries =
292    {
293        { "set-edit-mode", set_edit_mode, "b", "false" },
294
295        { "trash-bookmark", trash_bookmark },
296        { "set-small-rows", set_small_rows },
297
298        { "move-top",    move_top    },
299        { "move-up",     move_up     },
300        { "move-down",   move_down   },
301        { "move-bottom", move_bottom },
302
303        {   "bookmark",    bookmark, "(ys)" },
304        { "unbookmark",  unbookmark, "(ys)" }
305    };
306
307    private void set_edit_mode (SimpleAction action, Variant? variant)
308        requires (variant != null)
309    {
310        bool new_state = ((!) variant).get_boolean ();
311        action.set_state (new_state);
312
313        if (new_state)
314            enter_edit_mode ();
315        else
316            leave_edit_mode ();
317    }
318
319    private void enter_edit_mode ()
320    {
321        edit_mode_state_action.set_state (true);
322
323        edit_mode_stack.set_visible_child_name ("edit-mode-on");
324        bookmarks_list.enter_edit_mode ();
325    }
326
327    [GtkCallback]
328    private void leave_edit_mode (/* used both as action and callback */)
329    {
330        edit_mode_state_action.set_state (false);
331
332        bool give_focus_to_switch = bookmarks_list.leave_edit_mode ();
333        edit_mode_stack.set_visible_child_name ("edit-mode-off");
334
335        if (give_focus_to_switch)
336            bookmarked_switch.grab_focus ();
337    }
338
339    private void trash_bookmark (/* SimpleAction action, Variant? variant */)
340    {
341        bookmarks_list.trash_bookmark ();
342    }
343
344    private void set_small_rows (/* SimpleAction action, Variant? variant */)
345    {
346        settings.set_boolean ("small-bookmarks-rows", bookmarks_controller.get_small_rows_state ());
347    }
348
349    private void move_top       (/* SimpleAction action, Variant? variant */)
350    {
351        bookmarks_list.move_top ();
352    }
353
354    private void move_up        (/* SimpleAction action, Variant? variant */)
355    {
356        bookmarks_list.move_up ();
357    }
358
359    private void move_down      (/* SimpleAction action, Variant? variant */)
360    {
361        bookmarks_list.move_down ();
362    }
363
364    private void move_bottom    (/* SimpleAction action, Variant? variant */)
365    {
366        bookmarks_list.move_bottom ();
367    }
368
369    private void bookmark (SimpleAction action, Variant? path_variant)
370        requires (path_variant != null)
371    {
372        bookmarks_popover.closed ();    // if the popover is visible, the size of the listbox could change 1/2
373
374        uint8 type;
375        string bookmark;
376        ((!) path_variant).@get ("(ys)", out type, out bookmark);
377        bookmarks_list.append_bookmark (ViewType.from_byte (type), bookmark);
378    }
379
380    private void unbookmark (SimpleAction action, Variant? path_variant)
381        requires (path_variant != null)
382    {
383        bookmarks_popover.closed ();    // if the popover is visible, the size of the listbox could change 2/2
384
385        uint8 type;
386        string bookmark;
387        ((!) path_variant).@get ("(ys)", out type, out bookmark);
388        bookmarks_list.remove_bookmark (ViewType.from_byte (type), bookmark);
389    }
390
391    /*\
392    * * Bookmarks management
393    \*/
394
395    private static void update_switch_label (ViewType old_type, ViewType new_type, Label switch_label)
396    {
397        if (new_type == ViewType.SEARCH && old_type != ViewType.SEARCH)
398            switch_label.label = bookmark_this_search_text;
399        else if (new_type != ViewType.SEARCH && old_type == ViewType.SEARCH)
400            switch_label.label = bookmark_this_location_text;
401    }
402
403    /* Translators: label of the switch in the bookmarks popover, when searching */
404    private const string bookmark_this_search_text = _("Bookmark this Search");
405
406    /* Translators: label of the switch in the bookmarks popover, when browsing */
407    private const string bookmark_this_location_text = _("Bookmark this Location");
408
409    private void update_icon_and_switch (Variant bookmarks_variant)
410    {
411        Variant variant = new Variant ("(ys)", ViewType.to_byte (current_type), current_path);
412        string bookmark_name = BookmarksList.get_bookmark_name (current_type, current_path);
413        if (bookmark_name in bookmarks_variant.get_strv ())
414        {
415            if (bookmarks_icon.icon_name != "starred-symbolic")
416                bookmarks_icon.icon_name = "starred-symbolic";
417            update_switch_state (true, bookmarked_switch);
418            bookmarked_switch.set_detailed_action_name ("bookmarks.unbookmark(" + variant.print (true) + ")");
419        }
420        else
421        {
422            if (bookmarks_icon.icon_name != "non-starred-symbolic")
423                bookmarks_icon.icon_name = "non-starred-symbolic";
424            update_switch_state (false, bookmarked_switch);
425            bookmarked_switch.set_detailed_action_name ("bookmarks.bookmark(" + variant.print (true) + ")");
426        }
427    }
428    private static void update_switch_state (bool bookmarked, Switch bookmarked_switch)
429    {
430        if (bookmarked == bookmarked_switch.active)
431            return;
432        bookmarked_switch.set_detailed_action_name ("browser.empty((byte 255,''))");
433        bookmarked_switch.active = bookmarked;
434    }
435}
436