[dconf-editor] Split window-related code in its own files.
- From: Arnaud Bonatti <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [dconf-editor] Split window-related code in its own files.
- Date: Sun, 19 Jun 2016 00:48:17 +0000 (UTC)
commit 4d701bfac7feb763231e66ce08c315e511eca0f2
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Sun Jun 19 02:47:19 2016 +0200
Split window-related code in its own files.
editor/Makefile.am | 6 +-
editor/dconf-editor.gresource.xml | 1 +
editor/dconf-editor.ui | 113 +---------
editor/dconf-window.vala | 479 +++---------------------------------
editor/registry-view.ui | 115 +++++++++
editor/registry-view.vala | 495 +++++++++++++++++++++++++++++++++++++
po/POTFILES.in | 2 +
po/POTFILES.skip | 1 +
8 files changed, 652 insertions(+), 560 deletions(-)
---
diff --git a/editor/Makefile.am b/editor/Makefile.am
index 9755ef4..f59d2d7 100644
--- a/editor/Makefile.am
+++ b/editor/Makefile.am
@@ -33,7 +33,8 @@ resource_data = \
modifications-revealer.ui \
pathbar.ui \
pathbar-item.ui \
- property-row.ui
+ property-row.ui \
+ registry-view.ui
resources.c: $(resource_data)
$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --target=$@ --generate-source $<
@@ -49,7 +50,8 @@ dconf_editor_SOURCES = \
bookmarks.vala \
key-list-box-row.vala \
modifications-revealer.vala \
- pathbar.vala
+ pathbar.vala \
+ registry-view.vala
desktopdir = $(datadir)/applications
desktop_in_files = ca.desrt.dconf-editor.desktop.in.in
diff --git a/editor/dconf-editor.gresource.xml b/editor/dconf-editor.gresource.xml
index 4ccad1a..3ffabbb 100644
--- a/editor/dconf-editor.gresource.xml
+++ b/editor/dconf-editor.gresource.xml
@@ -12,6 +12,7 @@
<file preprocess="xml-stripblanks">pathbar.ui</file>
<file preprocess="xml-stripblanks">pathbar-item.ui</file>
<file preprocess="xml-stripblanks">property-row.ui</file>
+ <file preprocess="xml-stripblanks">registry-view.ui</file>
</gresource>
<gresource prefix="/ca/desrt/dconf-editor/gtk">
<file preprocess="xml-stripblanks" alias="menus.ui">dconf-editor-menu.ui</file>
diff --git a/editor/dconf-editor.ui b/editor/dconf-editor.ui
index 54b595a..001996f 100644
--- a/editor/dconf-editor.ui
+++ b/editor/dconf-editor.ui
@@ -64,7 +64,7 @@
<property name="visible">True</property>
<property name="valign">center</property>
<!-- TODO <property name="focus-on-click">False</property> does not work here because of
explicit focus grab -->
- <property name="active" bind-source="search_bar" bind-property="search-mode-enabled"
bind-flags="bidirectional|sync-create"/>
+ <property name="active" bind-source="registry_view" bind-property="show-search-bar"
bind-flags="bidirectional|sync-create"/>
<!-- <accelerator key="F" signal="toggled" modifiers="GDK_CONTROL_MASK"/> TODO -->
<style>
<class name="image-button"/>
@@ -107,117 +107,8 @@
</object>
</child>
<child>
- <object class="GtkGrid">
+ <object class="RegistryView" id="registry_view">
<property name="visible">True</property>
- <property name="orientation">vertical</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="GtkGrid">
- <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="activate" handler="find_next_cb"/>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="search_next_button">
- <property name="visible">True</property>
- <signal name="clicked" handler="find_next_cb"/>
- <property name="sensitive" bind-source="search_button" bind-property="active"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="icon-size">1</property>
- <property name="icon-name">go-down-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkStack">
- <property name="visible">True</property>
- <property name="visible-child">browse-view</property>
- <property name="expand">True</property>
- <child>
- <object class="GtkTreeView" id="dir_tree_view">
- <property name="visible">False</property>
- <child internal-child="selection">
- <object class="GtkTreeSelection" id="dir_tree_selection">
- <signal name="changed" handler="dir_selected_cb"/>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkScrolledWindow" id="browse-view">
- <property name="visible">True</property>
- <child>
- <object class="GtkListBox" id="key_list_box">
- <property name="visible">True</property>
- <property name="activate-on-single-click">True</property>
- <property name="selection-mode">browse</property><!-- permits to not have an item
selected -->
- <style>
- <class name="dconf-list"/>
- </style>
- <signal name="row-activated" handler="row_activated_cb"/>
- <child type="placeholder"> <!-- see
nautilus/src/resources/ui/nautilus-folder-is-empty.ui -->
- <object class="GtkGrid">
- <property name="visible">True</property>
- <property name="row-spacing">12</property>
- <property name="expand">True</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="orientation">vertical</property>
- <style>
- <class name="dim-label"/>
- </style>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="icon-name">dconf-editor-symbolic</property>
- <property name="pixel-size">72</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="label" translatable="yes">No keys in this path</property>
- <attributes>
- <attribute name="weight" value="bold"/>
- <attribute name="scale" value="1.44"/>
- </attributes>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="ModificationsRevealer" id="revealer">
- <property name="visible">True</property>
- </object>
- </child>
</object>
</child>
</template>
diff --git a/editor/dconf-window.vala b/editor/dconf-window.vala
index d777531..2f4979b 100644
--- a/editor/dconf-window.vala
+++ b/editor/dconf-window.vala
@@ -26,34 +26,20 @@ class DConfWindow : ApplicationWindow
{ "reset-visible", reset }
};
- private string current_path = "/";
+ public string current_path { private get; set; default = "/"; } // not synced bidi, needed for saving on
destroy, even after child destruction
+
private int window_width = 0;
private int window_height = 0;
private bool window_is_maximized = false;
private bool window_is_fullscreen = false;
private bool window_is_tiled = false;
- private SettingsModel model = new SettingsModel ();
- [GtkChild] private TreeView dir_tree_view;
- [GtkChild] private TreeSelection dir_tree_selection;
-
- [GtkChild] private ListBox key_list_box;
- private GLib.ListStore? key_model = null;
-
private GLib.Settings settings = new GLib.Settings ("ca.desrt.dconf-editor.Settings");
- [GtkChild] private Bookmarks bookmarks_button;
-
- private GLib.ListStore rows_possibly_with_popover = new GLib.ListStore (typeof (ClickableListBoxRow));
-
- [GtkChild] private ModificationsRevealer revealer;
-
- [GtkChild] private SearchBar search_bar;
- [GtkChild] private SearchEntry search_entry;
- [GtkChild] private Button search_next_button;
+ [GtkChild] private Bookmarks bookmarks_button;
[GtkChild] private MenuButton info_button;
-
[GtkChild] private PathBar pathbar;
+ [GtkChild] private RegistryView registry_view;
public DConfWindow ()
{
@@ -61,19 +47,16 @@ class DConfWindow : ApplicationWindow
add_action (settings.create_action ("delayed-apply-menu"));
settings.changed["delayed-apply-menu"].connect (invalidate_popovers);
- revealer.invalidate_popovers.connect (invalidate_popovers);
set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));
if (settings.get_boolean ("window-is-fullscreen"))
- fullscreen ();
+ fullscreen (); // TODO deprecate
else if (settings.get_boolean ("window-is-maximized"))
maximize ();
- search_bar.connect_entry (search_entry);
-
settings.changed["theme"].connect (() => {
string theme = settings.get_string ("theme");
- StyleContext context = get_style_context ();
+ StyleContext context = get_style_context (); // TODO only check once?
if (theme == "three-twenty-two" && context.has_class ("small-rows"))
context.remove_class ("small-rows");
else if (theme == "small-rows" && !context.has_class ("small-rows"))
@@ -82,15 +65,9 @@ class DConfWindow : ApplicationWindow
if (settings.get_string ("theme") == "small-rows")
get_style_context ().add_class ("small-rows");
- dir_tree_view.set_model (model);
-
- current_path = settings.get_string ("saved-view");
- if (!settings.get_boolean ("restore-view") || current_path == "" || !scroll_to_path (current_path))
- {
- current_path = "/";
- if (!scroll_to_path ("/"))
- assert_not_reached ();
- }
+ registry_view.bind_property ("current-path", this, "current-path"); // TODO in UI file?
+ settings.bind ("delayed-apply-menu", registry_view, "delayed-apply-menu",
SettingsBindFlags.GET|SettingsBindFlags.NO_SENSITIVITY);
+ registry_view.init (settings.get_string ("saved-view"), settings.get_boolean ("restore-view")); //
TODO better?
}
private static string stripped_path (string path)
@@ -164,283 +141,28 @@ class DConfWindow : ApplicationWindow
}
/*\
- * * Dir TreeView
+ * *
\*/
- [GtkCallback]
- private void dir_selected_cb ()
+ private void invalidate_popovers ()
{
- search_next_button.set_sensitive (true); // TODO better, or maybe just hide search_bar 1/2
-
- key_model = null;
-
- TreeIter iter;
- Directory dir;
- if (dir_tree_selection.get_selected (null, out iter))
- dir = model.get_directory (iter);
- else
- dir = model.get_root_directory ();
-
- key_model = dir.key_model;
- update_current_path (dir.full_name);
-
- key_list_box.bind_model (key_model, new_list_box_row);
+ registry_view.invalidate_popovers ();
}
[GtkCallback]
private bool scroll_to_path (string full_name)
{
- invalidate_popovers ();
-
- if (full_name == "/")
- {
- dir_tree_selection.unselect_all ();
- return true;
- }
-
- TreeIter iter;
- if (model.get_iter_first (out iter))
- {
- do
- {
- Directory dir = model.get_directory (iter);
-
- if (dir.full_name == full_name)
- {
- select_dir (iter);
- return true;
- }
- }
- while (get_next_iter (ref iter));
- }
- MessageDialog dialog = new MessageDialog (this, DialogFlags.MODAL, MessageType.ERROR,
ButtonsType.OK, _("Oops! Cannot find something at this path."));
- dialog.run ();
- dialog.destroy ();
- return false;
- }
-
- /*\
- * * Key ListBox
- \*/
-
- private Widget new_list_box_row (Object item)
- {
- ClickableListBoxRow row;
- if (((SettingObject) item).is_view)
- {
- row = new FolderListBoxRow (((SettingObject) item).name, ((SettingObject) item).full_name);
- row.on_row_clicked.connect (() => {
- if (!scroll_to_path (((SettingObject) item).full_name))
- warning ("Something got wrong with this folder.");
- });
- }
- else
- {
- Key key = (Key) item;
- if (key.has_schema)
- {
- GSettingsKey gkey = (GSettingsKey) key;
- row = new KeyListBoxRowEditable (gkey);
- ((KeyListBoxRow) row).set_key_value.connect ((variant) => { set_glib_key_value (gkey,
variant); });
- ((KeyListBoxRow) row).change_dismissed.connect (() => { revealer.dismiss_glib_change (gkey);
});
- }
- else
- {
- DConfKey dkey = (DConfKey) key;
- row = new KeyListBoxRowEditableNoSchema (dkey);
- ((KeyListBoxRow) row).set_key_value.connect ((variant) => { set_dconf_key_value (dkey,
variant); });
- ((KeyListBoxRow) row).change_dismissed.connect (() => { revealer.dismiss_dconf_change
(dkey); });
- }
- row.on_row_clicked.connect (() => { new_key_editor (key); });
- // TODO bug: row is always visually activated after the dialog destruction if mouse is over at
this time
- }
- row.button_press_event.connect (on_button_pressed);
- return row;
- }
-
- private void new_key_editor (Key key)
- {
- if (!key.has_schema && ((DConfKey) key).is_ghost)
- return;
-
- bool has_schema;
- unowned Variant [] dict_container;
- key.properties.get ("(ba{ss})", out has_schema, out dict_container);
- Variant dict = dict_container [0];
-
- // TODO use VariantDict
- string key_name, tmp_string;
-
- if (!dict.lookup ("key-name", "s", out key_name)) assert_not_reached ();
- if (!dict.lookup ("parent-path", "s", out tmp_string)) assert_not_reached ();
-
- KeyEditor key_editor = new KeyEditor (has_schema, key_name, tmp_string);
-
- if (dict.lookup ("schema-id", "s", out tmp_string)) key_editor.add_row_from_label (_("Schema"),
tmp_string);
- if (dict.lookup ("summary", "s", out tmp_string)) key_editor.add_row_from_label (_("Summary"),
tmp_string);
- if (dict.lookup ("description", "s", out tmp_string)) key_editor.add_row_from_label
(_("Description"), tmp_string);
- /* Translators: as in datatype (integer, boolean, string, etc.) */
- if (dict.lookup ("type-name", "s", out tmp_string)) key_editor.add_row_from_label (_("Type"),
tmp_string);
- else assert_not_reached ();
- if (dict.lookup ("minimum", "s", out tmp_string)) key_editor.add_row_from_label (_("Minimum"),
tmp_string);
- if (dict.lookup ("maximum", "s", out tmp_string)) key_editor.add_row_from_label (_("Maximum"),
tmp_string);
- if (dict.lookup ("default-value", "s", out tmp_string)) key_editor.add_row_from_label (_("Default"),
tmp_string);
-
- if (!dict.lookup ("type-code", "s", out tmp_string)) assert_not_reached ();
-
- KeyEditorChild key_editor_child = create_child (key_editor, key);
- if (has_schema)
- {
- Switch custom_value_switch = new Switch ();
- custom_value_switch.width_request = 100; /* same request than for button_cancel/button_apply on
scale 1; TODO better */
- custom_value_switch.halign = Align.END;
- custom_value_switch.hexpand = true;
- custom_value_switch.show ();
- key_editor.add_row_from_widget (_("Use default value"), custom_value_switch, null);
-
- custom_value_switch.bind_property ("active", key_editor_child, "sensitive",
BindingFlags.SYNC_CREATE | BindingFlags.INVERT_BOOLEAN);
-
- GSettingsKey gkey = (GSettingsKey) key;
- custom_value_switch.set_active (gkey.is_default);
- custom_value_switch.notify ["active"].connect (() => {
- bool is_active = custom_value_switch.get_active ();
- key_editor.switch_is_active (is_active);
- });
-
- key_editor.response.connect ((dialog, response_id) => {
- if (response_id == ResponseType.APPLY)
- {
- if (!custom_value_switch.active)
- {
- Variant variant = key_editor_child.get_variant ();
- if (key.value != variant)
- key.value = variant;
- }
- else if (!gkey.is_default)
- gkey.set_to_default ();
- }
- dialog.destroy ();
- });
- }
- else
- {
- key_editor.response.connect ((dialog, response_id) => {
- if (response_id == ResponseType.APPLY)
- {
- Variant variant = key_editor_child.get_variant ();
- if (key.value != variant)
- key.value = variant;
- }
- dialog.destroy ();
- });
- }
- key_editor_child.value_has_changed.connect ((is_valid) => { key_editor.custom_value_is_valid =
is_valid; }); // TODO not always useful
- key_editor_child.child_activated.connect (() => { // TODO "only" used for string-based and
spin widgets
- if (key_editor.custom_value_is_valid)
- key_editor.response (ResponseType.APPLY);
- });
- key_editor.add_row_from_widget (_("Custom value"), key_editor_child, tmp_string);
-
- key_editor.set_transient_for (this);
- key_editor.run ();
- }
-
- private static KeyEditorChild create_child (KeyEditor dialog, Key key)
- {
- switch (key.type_string)
- {
- case "<enum>":
- return (KeyEditorChild) new KeyEditorChildEnum (key);
- case "<flags>":
- return (KeyEditorChild) new KeyEditorChildFlags ((GSettingsKey) key);
- case "b":
- return (KeyEditorChild) new KeyEditorChildBool (key.value.get_boolean ());
- case "y":
- case "n":
- case "q":
- case "i":
- case "u":
- case "h": // TODO "x" and "t" are not working in spinbuttons (double-based)
- return (KeyEditorChild) new KeyEditorChildNumberInt (key);
- case "d":
- return (KeyEditorChild) new KeyEditorChildNumberDouble (key);
- case "mb":
- return (KeyEditorChild) new KeyEditorChildNullableBool (key);
- default:
- return (KeyEditorChild) new KeyEditorChildDefault (key.type_string, key.value);
- }
- }
-
- private bool on_button_pressed (Widget widget, Gdk.EventButton event)
- {
- ListBoxRow list_box_row = (ListBoxRow) widget.get_parent ();
- key_list_box.select_row (list_box_row);
- list_box_row.grab_focus ();
-
- ClickableListBoxRow row = (ClickableListBoxRow) widget;
- if (event.button == Gdk.BUTTON_SECONDARY)
- {
- row.show_right_click_popover (settings.get_boolean ("delayed-apply-menu"), (int) (event.x -
row.get_allocated_width () / 2.0));
- rows_possibly_with_popover.append (row);
- }
-
- return false;
- }
-
- [GtkCallback]
- private void row_activated_cb (ListBoxRow list_box_row)
- {
- search_next_button.set_sensitive (true); // TODO better, or maybe just hide search_bar 2/2
-
- ((ClickableListBoxRow) list_box_row.get_child ()).on_row_clicked ();
- }
-
- private void invalidate_popovers ()
- {
- uint position = 0;
- ClickableListBoxRow? row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (0);
- while (row != null)
- {
- row.destroy_popover ();
- position++;
- row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (position);
- }
- rows_possibly_with_popover.remove_all ();
- }
-
- /*\
- * * Revealer stuff
- \*/
-
- private void set_dconf_key_value (DConfKey key, Variant? new_value)
- {
- if (settings.get_boolean ("delayed-apply-menu") || key.planned_change)
- revealer.add_delayed_dconf_settings (key, new_value);
- else if (new_value != null)
- key.value = new_value;
- else
- assert_not_reached ();
- }
-
- private void set_glib_key_value (GSettingsKey key, Variant? new_value)
- {
- if (settings.get_boolean ("delayed-apply-menu") || key.planned_change)
- revealer.add_delayed_glib_settings (key, new_value);
- else if (new_value != null)
- key.value = new_value;
- else
- key.set_to_default ();
+ return registry_view.scroll_to_path (full_name);
}
/*\
* * Action entries
\*/
- private void update_current_path (string path)
+ public void update_current_path ()
{
GLib.Menu section;
- current_path = path;
bookmarks_button.current_path = stripped_path (current_path);
pathbar.set_path (current_path);
@@ -467,56 +189,18 @@ class DConfWindow : ApplicationWindow
private void reset ()
{
- reset_generic (key_model, false);
- invalidate_popovers ();
+ registry_view.reset (false);
}
private void reset_recursively ()
{
- reset_generic (key_model, true);
- invalidate_popovers ();
- }
-
- private void reset_generic (GLib.ListStore? objects, bool recursively) // TODO notification if nothing
to reset
- {
- if (objects == null)
- return;
-
- for (uint position = 0;; position++)
- {
- Object? object = ((!) objects).get_object (position);
- if (object == null)
- return;
-
- SettingObject setting_object = (SettingObject) ((!) object);
- if (setting_object.is_view)
- {
- if (recursively)
- reset_generic (((Directory) setting_object).key_model, true);
- continue;
- }
- if (!((Key) setting_object).has_schema)
- {
- if (!((DConfKey) setting_object).is_ghost)
- revealer.add_delayed_dconf_settings ((DConfKey) setting_object, null);
- }
- else if (!((GSettingsKey) setting_object).is_default)
- revealer.add_delayed_glib_settings ((GSettingsKey) setting_object, null);
- }
+ registry_view.reset (true);
}
/*\
- * * Search box
+ * * Other callbacks
\*/
- private void discard_row_popover ()
- {
- ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
- if (selected_row == null)
- return;
- ((ClickableListBoxRow) ((!) selected_row).get_child ()).hide_right_click_popover ();
- }
-
[GtkCallback]
private bool on_key_press_event (Widget widget, Gdk.EventKey event) // TODO better?
{
@@ -529,19 +213,19 @@ class DConfWindow : ApplicationWindow
case "b":
if (info_button.active)
info_button.active = false;
- discard_row_popover ();
+ registry_view.discard_row_popover ();
bookmarks_button.clicked ();
return true;
case "d":
if (info_button.active)
info_button.active = false;
- discard_row_popover ();
+ registry_view.discard_row_popover ();
bookmarks_button.set_bookmarked (true);
return true;
case "D":
if (info_button.active)
info_button.active = false;
- discard_row_popover ();
+ registry_view.discard_row_popover ();
bookmarks_button.set_bookmarked (false);
return true;
case "f":
@@ -549,21 +233,21 @@ class DConfWindow : ApplicationWindow
bookmarks_button.active = false;
if (info_button.active)
info_button.active = false;
- discard_row_popover ();
- search_bar.set_search_mode (!search_bar.get_search_mode ());
+ registry_view.discard_row_popover ();
+ registry_view.search_bar.set_search_mode (!registry_view.search_bar.get_search_mode ());
return true;
case "c":
- discard_row_popover (); // TODO avoid duplicate get_selected_row () call
- ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
+ registry_view.discard_row_popover (); // TODO avoid duplicate get_selected_row () call
+ string? selected_row_text = registry_view.get_selected_row_text ();
ConfigurationEditor application = (ConfigurationEditor) get_application ();
- application.copy (selected_row == null ? current_path : ((ClickableListBoxRow) ((!)
selected_row).get_child ()).get_text ());
+ application.copy (selected_row_text == null ? current_path : (!) selected_row_text);
return true;
case "C":
- discard_row_popover ();
+ registry_view.discard_row_popover ();
((ConfigurationEditor) get_application ()).copy (current_path);
return true;
case "F1":
- discard_row_popover ();
+ registry_view.discard_row_popover ();
if ((event.state & Gdk.ModifierType.SHIFT_MASK) == 0)
return false; // help overlay
((ConfigurationEditor) get_application ()).about_cb ();
@@ -576,23 +260,19 @@ class DConfWindow : ApplicationWindow
/* don't use "else if", or some widgets will not be hidden on <ctrl>F10 or such things */
if (name == "F10")
{
- discard_row_popover ();
+ registry_view.discard_row_popover ();
if (bookmarks_button.active)
bookmarks_button.active = false;
return false;
}
else if (name == "Menu")
{
- ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
- if (selected_row != null)
+ if (registry_view.show_row_popover ())
{
if (bookmarks_button.active)
bookmarks_button.active = false;
if (info_button.active)
info_button.active = false;
- ClickableListBoxRow row = (ClickableListBoxRow) ((!) selected_row).get_child ();
- row.show_right_click_popover (settings.get_boolean ("delayed-apply-menu"));
- rows_possibly_with_popover.append (row);
}
else if (info_button.active == false)
{
@@ -608,108 +288,13 @@ class DConfWindow : ApplicationWindow
if (bookmarks_button.active || info_button.active) // TODO open bug about modal popovers and
search_bar
return false;
- return search_bar.handle_event (event);
+ return registry_view.search_bar.handle_event (event);
}
[GtkCallback]
private void on_menu_button_clicked ()
{
- discard_row_popover ();
- search_bar.set_search_mode (false);
- }
-
- [GtkCallback]
- private void find_next_cb ()
- {
- if (!search_bar.get_search_mode ()) // TODO better; switches to next list_box_row when
keyboard-activating an entry of the popover
- return;
-
- TreeIter iter;
- bool on_first_directory;
- int position = 0;
- if (dir_tree_selection.get_selected (null, out iter))
- {
- ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
- if (selected_row != null)
- position = ((!) selected_row).get_index () + 1;
-
- on_first_directory = true;
- }
- else if (model.get_iter_first (out iter))
- on_first_directory = false;
- else
- return; // TODO better
-
- do
- {
- Directory dir = model.get_directory (iter);
-
- if (!on_first_directory && dir.name.index_of (search_entry.text) >= 0)
- {
- select_dir (iter);
- return;
- }
- on_first_directory = false;
-
- /* Select next key that matches */
- GLib.ListStore key_model = dir.key_model;
- while (position < key_model.get_n_items ())
- {
- SettingObject object = (SettingObject) key_model.get_object (position);
- if (object.name.index_of (search_entry.text) >= 0 ||
- (!object.is_view && key_matches ((Key) object, search_entry.text)))
- {
- select_dir (iter);
- key_list_box.select_row (key_list_box.get_row_at_index (position));
- // TODO select key in ListBox
- return;
- }
- position++;
- }
-
- position = 0;
- }
- while (get_next_iter (ref iter));
-
- search_next_button.set_sensitive (false);
- }
-
- private void select_dir (TreeIter iter)
- {
- dir_tree_view.expand_to_path (model.get_path (iter));
- dir_tree_selection.select_iter (iter);
- }
-
- private bool key_matches (Key key, string text)
- {
- /* Check in key's metadata */
- if (key.has_schema && ((GSettingsKey) key).search_for (text))
- return true;
-
- /* Check key value */
- if (key.value.is_of_type (VariantType.STRING) && key.value.get_string ().index_of (text) >= 0)
- return true;
-
- return false;
- }
-
- private bool get_next_iter (ref TreeIter iter)
- {
- /* Search children next */
- if (model.iter_has_child (iter))
- {
- model.iter_nth_child (out iter, iter, 0);
- return true;
- }
-
- /* Move to the next branch */
- while (!model.iter_next (ref iter))
- {
- /* Otherwise move to the parent and onto the next iter */
- if (!model.iter_parent (out iter, iter))
- return false;
- }
-
- return true;
+ registry_view.discard_row_popover ();
+ registry_view.search_bar.set_search_mode (false);
}
}
diff --git a/editor/registry-view.ui b/editor/registry-view.ui
new file mode 100644
index 0000000..97927d1
--- /dev/null
+++ b/editor/registry-view.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="RegistryView" parent="GtkGrid">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkTreeView" id="dir_tree_view">
+ <property name="visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="dir_tree_selection">
+ <signal name="changed" handler="dir_selected_cb"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <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="GtkGrid">
+ <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="activate" handler="find_next_cb"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="search_next_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="find_next_cb"/>
+ <property name="sensitive" bind-source="search_bar" bind-property="search-mode-enabled"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-size">1</property>
+ <property name="icon-name">go-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack">
+ <property name="visible">True</property>
+ <property name="visible-child">browse-view</property>
+ <property name="expand">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="browse-view">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkListBox" id="key_list_box">
+ <property name="visible">True</property>
+ <property name="activate-on-single-click">True</property>
+ <property name="selection-mode">browse</property><!-- permits to not have an item selected
-->
+ <style>
+ <class name="dconf-list"/>
+ </style>
+ <signal name="row-activated" handler="row_activated_cb"/>
+ <child type="placeholder"> <!-- see
nautilus/src/resources/ui/nautilus-folder-is-empty.ui -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="row-spacing">12</property>
+ <property name="expand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">dconf-editor-symbolic</property>
+ <property name="pixel-size">72</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">No keys in this path</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="ModificationsRevealer" id="revealer">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/editor/registry-view.vala b/editor/registry-view.vala
new file mode 100644
index 0000000..294a661
--- /dev/null
+++ b/editor/registry-view.vala
@@ -0,0 +1,495 @@
+/*
+ This file is part of Dconf Editor
+
+ Dconf Editor is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Dconf Editor is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Dconf Editor. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/registry-view.ui")]
+class RegistryView : Grid
+{
+ public string current_path { get; private set; }
+ public bool show_search_bar { get; set; }
+ public bool delayed_apply_menu { get; set; }
+
+ private SettingsModel model = new SettingsModel ();
+ [GtkChild] private TreeView dir_tree_view;
+ [GtkChild] private TreeSelection dir_tree_selection;
+
+ [GtkChild] private ListBox key_list_box;
+ private GLib.ListStore? key_model = null;
+
+ private GLib.ListStore rows_possibly_with_popover = new GLib.ListStore (typeof (ClickableListBoxRow));
+
+ [GtkChild] private ModificationsRevealer revealer;
+
+ [GtkChild] public SearchBar search_bar;
+ [GtkChild] private SearchEntry search_entry;
+ [GtkChild] private Button search_next_button;
+
+ construct
+ {
+ revealer.invalidate_popovers.connect (invalidate_popovers);
+
+ search_entry.get_buffer ().deleted_text.connect (() => { search_next_button.set_sensitive (true); });
+ search_bar.connect_entry (search_entry);
+ bind_property ("show-search-bar", search_bar, "search-mode-enabled", BindingFlags.BIDIRECTIONAL);
// TODO in UI file?
+ }
+
+ public void init (string path, bool restore_view)
+ {
+ dir_tree_view.set_model (model);
+
+ current_path = path;
+ if (!restore_view || current_path == "" || !scroll_to_path (current_path))
+ {
+ current_path = "/";
+ if (!scroll_to_path ("/"))
+ assert_not_reached ();
+ }
+ }
+
+ /*\
+ * * Dir TreeView
+ \*/
+
+ [GtkCallback]
+ private void dir_selected_cb ()
+ {
+ search_next_button.set_sensitive (true); // TODO better, or maybe just hide search_bar 1/2
+
+ key_model = null;
+
+ TreeIter iter;
+ Directory dir;
+ if (dir_tree_selection.get_selected (null, out iter))
+ dir = model.get_directory (iter);
+ else
+ dir = model.get_root_directory ();
+
+ key_model = dir.key_model;
+ current_path = dir.full_name;
+ ((DConfWindow) this.get_parent ()).update_current_path ();
+
+ key_list_box.bind_model (key_model, new_list_box_row);
+ }
+
+ public bool scroll_to_path (string full_name)
+ {
+ invalidate_popovers ();
+
+ if (full_name == "/")
+ {
+ dir_tree_selection.unselect_all ();
+ return true;
+ }
+
+ TreeIter iter;
+ if (model.get_iter_first (out iter))
+ {
+ do
+ {
+ Directory dir = model.get_directory (iter);
+
+ if (dir.full_name == full_name)
+ {
+ select_dir (iter);
+ return true;
+ }
+ }
+ while (get_next_iter (ref iter));
+ }
+ MessageDialog dialog = new MessageDialog ((Window) this.get_parent (), DialogFlags.MODAL,
MessageType.ERROR, ButtonsType.OK, _("Oops! Cannot find something at this path."));
+ dialog.run ();
+ dialog.destroy ();
+ return false;
+ }
+
+ /*\
+ * * Key ListBox
+ \*/
+
+ private Widget new_list_box_row (Object item)
+ {
+ ClickableListBoxRow row;
+ if (((SettingObject) item).is_view)
+ {
+ row = new FolderListBoxRow (((SettingObject) item).name, ((SettingObject) item).full_name);
+ row.on_row_clicked.connect (() => {
+ if (!scroll_to_path (((SettingObject) item).full_name))
+ warning ("Something got wrong with this folder.");
+ });
+ }
+ else
+ {
+ Key key = (Key) item;
+ if (key.has_schema)
+ {
+ GSettingsKey gkey = (GSettingsKey) key;
+ row = new KeyListBoxRowEditable (gkey);
+ ((KeyListBoxRow) row).set_key_value.connect ((variant) => { set_glib_key_value (gkey,
variant); });
+ ((KeyListBoxRow) row).change_dismissed.connect (() => { revealer.dismiss_glib_change (gkey);
});
+ }
+ else
+ {
+ DConfKey dkey = (DConfKey) key;
+ row = new KeyListBoxRowEditableNoSchema (dkey);
+ ((KeyListBoxRow) row).set_key_value.connect ((variant) => { set_dconf_key_value (dkey,
variant); });
+ ((KeyListBoxRow) row).change_dismissed.connect (() => { revealer.dismiss_dconf_change
(dkey); });
+ }
+ row.on_row_clicked.connect (() => { new_key_editor (key); });
+ // TODO bug: row is always visually activated after the dialog destruction if mouse is over at
this time
+ }
+ row.button_press_event.connect (on_button_pressed);
+ return row;
+ }
+
+ private void new_key_editor (Key key)
+ {
+ if (!key.has_schema && ((DConfKey) key).is_ghost)
+ return;
+
+ bool has_schema;
+ unowned Variant [] dict_container;
+ key.properties.get ("(ba{ss})", out has_schema, out dict_container);
+ Variant dict = dict_container [0];
+
+ // TODO use VariantDict
+ string key_name, tmp_string;
+
+ if (!dict.lookup ("key-name", "s", out key_name)) assert_not_reached ();
+ if (!dict.lookup ("parent-path", "s", out tmp_string)) assert_not_reached ();
+
+ KeyEditor key_editor = new KeyEditor (has_schema, key_name, tmp_string);
+
+ if (dict.lookup ("schema-id", "s", out tmp_string)) key_editor.add_row_from_label (_("Schema"),
tmp_string);
+ if (dict.lookup ("summary", "s", out tmp_string)) key_editor.add_row_from_label (_("Summary"),
tmp_string);
+ if (dict.lookup ("description", "s", out tmp_string)) key_editor.add_row_from_label
(_("Description"), tmp_string);
+ /* Translators: as in datatype (integer, boolean, string, etc.) */
+ if (dict.lookup ("type-name", "s", out tmp_string)) key_editor.add_row_from_label (_("Type"),
tmp_string);
+ else assert_not_reached ();
+ if (dict.lookup ("minimum", "s", out tmp_string)) key_editor.add_row_from_label (_("Minimum"),
tmp_string);
+ if (dict.lookup ("maximum", "s", out tmp_string)) key_editor.add_row_from_label (_("Maximum"),
tmp_string);
+ if (dict.lookup ("default-value", "s", out tmp_string)) key_editor.add_row_from_label (_("Default"),
tmp_string);
+
+ if (!dict.lookup ("type-code", "s", out tmp_string)) assert_not_reached ();
+
+ KeyEditorChild key_editor_child = create_child (key_editor, key);
+ if (has_schema)
+ {
+ Switch custom_value_switch = new Switch ();
+ custom_value_switch.width_request = 100; /* same request than for button_cancel/button_apply on
scale 1; TODO better */
+ custom_value_switch.halign = Align.END;
+ custom_value_switch.hexpand = true;
+ custom_value_switch.show ();
+ key_editor.add_row_from_widget (_("Use default value"), custom_value_switch, null);
+
+ custom_value_switch.bind_property ("active", key_editor_child, "sensitive",
BindingFlags.SYNC_CREATE | BindingFlags.INVERT_BOOLEAN);
+
+ GSettingsKey gkey = (GSettingsKey) key;
+ custom_value_switch.set_active (gkey.is_default);
+ custom_value_switch.notify ["active"].connect (() => {
+ bool is_active = custom_value_switch.get_active ();
+ key_editor.switch_is_active (is_active);
+ });
+
+ key_editor.response.connect ((dialog, response_id) => {
+ if (response_id == ResponseType.APPLY)
+ {
+ if (!custom_value_switch.active)
+ {
+ Variant variant = key_editor_child.get_variant ();
+ if (key.value != variant)
+ key.value = variant;
+ }
+ else if (!gkey.is_default)
+ gkey.set_to_default ();
+ }
+ dialog.destroy ();
+ });
+ }
+ else
+ {
+ key_editor.response.connect ((dialog, response_id) => {
+ if (response_id == ResponseType.APPLY)
+ {
+ Variant variant = key_editor_child.get_variant ();
+ if (key.value != variant)
+ key.value = variant;
+ }
+ dialog.destroy ();
+ });
+ }
+ key_editor_child.value_has_changed.connect ((is_valid) => { key_editor.custom_value_is_valid =
is_valid; }); // TODO not always useful
+ key_editor_child.child_activated.connect (() => { // TODO "only" used for string-based and
spin widgets
+ if (key_editor.custom_value_is_valid)
+ key_editor.response (ResponseType.APPLY);
+ });
+ key_editor.add_row_from_widget (_("Custom value"), key_editor_child, tmp_string);
+
+ key_editor.set_transient_for ((Window) this.get_parent ());
+ key_editor.run ();
+ }
+
+ private static KeyEditorChild create_child (KeyEditor dialog, Key key)
+ {
+ switch (key.type_string)
+ {
+ case "<enum>":
+ return (KeyEditorChild) new KeyEditorChildEnum (key);
+ case "<flags>":
+ return (KeyEditorChild) new KeyEditorChildFlags ((GSettingsKey) key);
+ case "b":
+ return (KeyEditorChild) new KeyEditorChildBool (key.value.get_boolean ());
+ case "y":
+ case "n":
+ case "q":
+ case "i":
+ case "u":
+ case "h": // TODO "x" and "t" are not working in spinbuttons (double-based)
+ return (KeyEditorChild) new KeyEditorChildNumberInt (key);
+ case "d":
+ return (KeyEditorChild) new KeyEditorChildNumberDouble (key);
+ case "mb":
+ return (KeyEditorChild) new KeyEditorChildNullableBool (key);
+ default:
+ return (KeyEditorChild) new KeyEditorChildDefault (key.type_string, key.value);
+ }
+ }
+
+ private bool on_button_pressed (Widget widget, Gdk.EventButton event)
+ {
+ ListBoxRow list_box_row = (ListBoxRow) widget.get_parent ();
+ key_list_box.select_row (list_box_row);
+ list_box_row.grab_focus ();
+
+ if (event.button == Gdk.BUTTON_SECONDARY)
+ {
+ ClickableListBoxRow row = (ClickableListBoxRow) widget;
+ row.show_right_click_popover (delayed_apply_menu, (int) (event.x - row.get_allocated_width () /
2.0));
+ rows_possibly_with_popover.append (row);
+ }
+
+ return false;
+ }
+
+ [GtkCallback]
+ private void row_activated_cb (ListBoxRow list_box_row)
+ {
+ search_next_button.set_sensitive (true); // TODO better, or maybe just hide search_bar 2/2
+
+ ((ClickableListBoxRow) list_box_row.get_child ()).on_row_clicked ();
+ }
+
+ public void invalidate_popovers ()
+ {
+ uint position = 0;
+ ClickableListBoxRow? row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (0);
+ while (row != null)
+ {
+ row.destroy_popover ();
+ position++;
+ row = (ClickableListBoxRow?) rows_possibly_with_popover.get_item (position);
+ }
+ rows_possibly_with_popover.remove_all ();
+ }
+
+ /*\
+ * * Revealer stuff
+ \*/
+
+ private void set_dconf_key_value (DConfKey key, Variant? new_value)
+ {
+ if (delayed_apply_menu || key.planned_change)
+ revealer.add_delayed_dconf_settings (key, new_value);
+ else if (new_value != null)
+ key.value = new_value;
+ else
+ assert_not_reached ();
+ }
+
+ private void set_glib_key_value (GSettingsKey key, Variant? new_value)
+ {
+ if (delayed_apply_menu || key.planned_change)
+ revealer.add_delayed_glib_settings (key, new_value);
+ else if (new_value != null)
+ key.value = new_value;
+ else
+ key.set_to_default ();
+ }
+
+ /*\
+ * * Action entries
+ \*/
+
+ public void reset (bool recursively)
+ {
+ reset_generic (key_model, recursively);
+ invalidate_popovers ();
+ }
+
+ private void reset_generic (GLib.ListStore? objects, bool recursively) // TODO notification if nothing
to reset
+ {
+ if (objects == null)
+ return;
+
+ for (uint position = 0;; position++)
+ {
+ Object? object = ((!) objects).get_object (position);
+ if (object == null)
+ return;
+
+ SettingObject setting_object = (SettingObject) ((!) object);
+ if (setting_object.is_view)
+ {
+ if (recursively)
+ reset_generic (((Directory) setting_object).key_model, true);
+ continue;
+ }
+ if (!((Key) setting_object).has_schema)
+ {
+ if (!((DConfKey) setting_object).is_ghost)
+ revealer.add_delayed_dconf_settings ((DConfKey) setting_object, null);
+ }
+ else if (!((GSettingsKey) setting_object).is_default)
+ revealer.add_delayed_glib_settings ((GSettingsKey) setting_object, null);
+ }
+ }
+
+ /*\
+ * * Search box
+ \*/
+
+ public bool show_row_popover ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return false;
+
+ ClickableListBoxRow row = (ClickableListBoxRow) ((!) selected_row).get_child ();
+ row.show_right_click_popover (delayed_apply_menu);
+ rows_possibly_with_popover.append (row);
+ return true;
+ }
+
+ public string? get_selected_row_text ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
+ return selected_row == null ? null : ((ClickableListBoxRow) ((!) selected_row).get_child
()).get_text ();
+ }
+
+ public void discard_row_popover ()
+ {
+ ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
+ if (selected_row == null)
+ return;
+ ((ClickableListBoxRow) ((!) selected_row).get_child ()).hide_right_click_popover ();
+ }
+
+ [GtkCallback]
+ private void find_next_cb ()
+ {
+ if (!search_bar.get_search_mode ()) // TODO better; switches to next list_box_row when
keyboard-activating an entry of the popover
+ return;
+
+ TreeIter iter;
+ bool on_first_directory;
+ int position = 0;
+ if (dir_tree_selection.get_selected (null, out iter))
+ {
+ ListBoxRow? selected_row = (ListBoxRow) key_list_box.get_selected_row ();
+ if (selected_row != null)
+ position = ((!) selected_row).get_index () + 1;
+
+ on_first_directory = true;
+ }
+ else if (model.get_iter_first (out iter))
+ on_first_directory = false;
+ else
+ return; // TODO better
+
+ do
+ {
+ Directory dir = model.get_directory (iter);
+
+ if (!on_first_directory && dir.name.index_of (search_entry.text) >= 0)
+ {
+ select_dir (iter);
+ return;
+ }
+ on_first_directory = false;
+
+ /* Select next key that matches */
+ GLib.ListStore key_model = dir.key_model;
+ while (position < key_model.get_n_items ())
+ {
+ SettingObject object = (SettingObject) key_model.get_object (position);
+ if (object.name.index_of (search_entry.text) >= 0 ||
+ (!object.is_view && key_matches ((Key) object, search_entry.text)))
+ {
+ select_dir (iter);
+ key_list_box.select_row (key_list_box.get_row_at_index (position));
+ // TODO select key in ListBox
+ return;
+ }
+ position++;
+ }
+
+ position = 0;
+ }
+ while (get_next_iter (ref iter));
+
+ search_next_button.set_sensitive (false);
+ }
+
+ private void select_dir (TreeIter iter)
+ {
+ dir_tree_view.expand_to_path (model.get_path (iter));
+ dir_tree_selection.select_iter (iter);
+ }
+
+ private bool key_matches (Key key, string text)
+ {
+ /* Check in key's metadata */
+ if (key.has_schema && ((GSettingsKey) key).search_for (text))
+ return true;
+
+ /* Check key value */
+ if (key.value.is_of_type (VariantType.STRING) && key.value.get_string ().index_of (text) >= 0)
+ return true;
+
+ return false;
+ }
+
+ private bool get_next_iter (ref TreeIter iter)
+ {
+ /* Search children next */
+ if (model.iter_has_child (iter))
+ {
+ model.iter_nth_child (out iter, iter, 0);
+ return true;
+ }
+
+ /* Move to the next branch */
+ while (!model.iter_next (ref iter))
+ {
+ /* Otherwise move to the parent and onto the next iter */
+ if (!model.iter_parent (out iter, iter))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f303e9d..087382e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -14,3 +14,5 @@ editor/dconf-window.vala
editor/key-list-box-row.vala
[type: gettext/glade]editor/modifications-revealer.ui
editor/modifications-revealer.vala
+[type: gettext/glade]editor/registry-view.ui
+editor/registry-view.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 739e021..12aaa33 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -5,5 +5,6 @@ editor/dconf-view.c
editor/dconf-window.c
editor/key-list-box-row.c
editor/modifications-revealer.c
+editor/registry-view.c
# Autotools failure
sub/editor/ca.desrt.dconf-editor.desktop.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]