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