[Xfce4-commits] <xfce4-notes-plugin:master> Add notes monitoring with GIO
Mike Massonnet
noreply at xfce.org
Sun Jan 30 00:42:01 CET 2011
Updating branch refs/heads/master
to 506b2b7861bd6cb6bfc1108f8f042651238017ea (commit)
from 09f147a293fef58f89204a105afad2bc623042a6 (commit)
commit 506b2b7861bd6cb6bfc1108f8f042651238017ea
Author: Mike Massonnet <mmassonnet at xfce.org>
Date: Sat Jan 29 23:46:52 2011 +0100
Add notes monitoring with GIO
Ability to reload a group when notes have been edited through an
external editor.
Refresh button (Xnp.IconButton) is drawn inside the window title bar
when updates are done outside. A prompt allows to reload the whole
group by clicking the button.
New class Xnp.WindowMonitor to send signals on note updates.
configure.ac.in | 1 +
lib/Makefile.am | 3 +
lib/application.vala | 136 +++++++++++++++++++++++++++++++++++++++++++++-
lib/icon-button.vala | 50 +++++++++++++++++-
lib/window-monitor.vala | 78 +++++++++++++++++++++++++++
lib/window.vala | 30 ++++++++++-
6 files changed, 292 insertions(+), 6 deletions(-)
diff --git a/configure.ac.in b/configure.ac.in
index 9c323f3..d59022b 100644
--- a/configure.ac.in
+++ b/configure.ac.in
@@ -47,6 +47,7 @@ AC_SUBST([MATH_LIBS], [" -lm"])
dnl Check for required packages
XDT_CHECK_LIBX11_REQUIRE()
XDT_CHECK_PACKAGE([GLIB], [glib-2.0], [2.16.0])
+XDT_CHECK_PACKAGE([GIO], [gio-2.0], [2.16.0])
XDT_CHECK_PACKAGE([GTK], [gtk+-2.0], [2.14.0])
XDT_CHECK_PACKAGE([LIBXFCEGUI4], [libxfcegui4-1.0], [4.4.0])
XDT_CHECK_PACKAGE([LIBXFCE4UTIL], [libxfce4util-1.0], [4.4.0])
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 3c40d64..48864c3 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -13,6 +13,7 @@ libnotes_la_VALAFLAGS = \
libnotes_la_SOURCES = \
icon-button.vala \
+ window-monitor.vala \
application.vala \
hypertextview.vala \
note.vala \
@@ -24,6 +25,7 @@ libnotes_la_CFLAGS = \
-DPKGDATADIR=\""$(pkgdatadir)"\" \
-DGETTEXT_PACKAGE=\""$(GETTEXT_PACKAGE)"\" \
@LIBX11_CFLAGS@ \
+ @GIO_CFLAGS@ \
@GTK_CFLAGS@ \
@LIBXFCE4UTIL_CFLAGS@ \
@XFCONF_CFLAGS@
@@ -31,6 +33,7 @@ libnotes_la_CFLAGS = \
libnotes_la_LIBADD = \
@MATH_LIBS@ \
@LIBX11_LIBS@ \
+ @GIO_LIBS@ \
@GTK_LIBS@ \
@LIBXFCE4UTIL_LIBS@ \
@XFCONF_LIBS@
diff --git a/lib/application.vala b/lib/application.vala
index 1f45804..c45ab31 100644
--- a/lib/application.vala
+++ b/lib/application.vala
@@ -24,6 +24,7 @@ namespace Xnp {
public class Application : GLib.Object {
+ private SList<Xnp.WindowMonitor> window_monitor_list;
private SList<Xnp.Window> window_list;
public string notes_path { get; set construct; }
public string config_file { get; construct; }
@@ -203,9 +204,9 @@ namespace Xnp {
}
/* Insert initial notes */
- if (name == null) {
+ string window_path = "%s/%s".printf (notes_path, window.name);
+ if (name == null || !GLib.FileUtils.test (window_path, GLib.FileTest.IS_DIR|GLib.FileTest.EXISTS)) {
try {
- string window_path = "%s/%s".printf (notes_path, window.name);
GLib.DirUtils.create_with_parents (window_path, 0700);
string note_path = "%s/%s".printf (window_path, _("Notes"));
GLib.FileUtils.set_contents (note_path, "", -1);
@@ -219,6 +220,9 @@ namespace Xnp {
this.load_window_data (window);
}
+ /* Window monitor */
+ window_monitor_list_add (window);
+
/* Global settings */
Xfconf.Property.bind (xfconf_channel, "/global/skip-taskbar-hint",
typeof (bool), window, "skip-taskbar-hint");
@@ -229,13 +233,19 @@ namespace Xnp {
window.action.connect ((win, action) => {
if (action == "rename") {
rename_window (win);
+ set_data_value (win, "internal-change", true);
}
else if (action == "delete") {
delete_window (win);
+ set_data_value (win, "internal-change", true);
}
else if (action == "create-new-window") {
var new_win = create_window ();
new_win.show ();
+ set_data_value (win, "internal-change", true);
+ }
+ else if (action == "refresh-notes") {
+ refresh_notes (win);
}
else if (action == "properties") {
open_settings_dialog ();
@@ -245,7 +255,10 @@ namespace Xnp {
}
});
window.save_data.connect ((win, note) => {
- save_note (win, note);
+ if (!get_data_value (win, "external-change")) {
+ set_data_value (win, "internal-change", true);
+ save_note (win, note);
+ }
});
window.note_inserted.connect ((win, note) => {
Xfconf.Property.bind (xfconf_channel, "/global/font-description",
@@ -254,6 +267,7 @@ namespace Xnp {
string path = "%s/%s/%s".printf (notes_path, win.name, note.name);
try {
GLib.FileUtils.set_contents (path, "", -1);
+ set_data_value (win, "internal-change", true);
}
catch (FileError e) {
}
@@ -261,6 +275,7 @@ namespace Xnp {
window.note_deleted.connect ((win, note) => {
string path = "%s/%s/%s".printf (notes_path, win.name, note.name);
GLib.FileUtils.unlink (path);
+ set_data_value (win, "internal-change", true);
});
window.note_renamed.connect ((win, note, old_name) => {
if (!name_is_valid (note.name)) {
@@ -270,6 +285,7 @@ namespace Xnp {
string old_path = "%s/%s/%s".printf (notes_path, win.name, old_name);
string new_path = "%s/%s/%s".printf (notes_path, win.name, note.name);
GLib.FileUtils.rename (old_path, new_path);
+ set_data_value (win, "internal-change", true);
});
return window;
@@ -379,6 +395,7 @@ namespace Xnp {
*/
public void save_notes () {
foreach (var win in this.window_list) {
+ set_data_value (win, "external-change", false);
win.save_notes ();
}
}
@@ -442,6 +459,9 @@ namespace Xnp {
window.name = name;
GLib.FileUtils.rename (old_path, new_path);
this.window_list.sort ((GLib.CompareFunc)window.compare_func);
+
+ window_monitor_list_remove (window);
+ window_monitor_list_add (window);
}
}
dialog.destroy ();
@@ -475,6 +495,8 @@ namespace Xnp {
catch (FileError e) {
}
+ window_monitor_list_remove (window);
+
this.window_list.remove (window);
window.destroy ();
@@ -490,6 +512,114 @@ namespace Xnp {
}
/**
+ * refresh_notes:
+ *
+ * Prompt for reloading notes from disk.
+ */
+ private void refresh_notes (Xnp.Window window) {
+ var dialog = new Gtk.MessageDialog (window, Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("The group \"%s\" has been modified on the disk"), window.name);
+ dialog.set_title (window.name);
+ dialog.set_icon_name ("xfce4-notes-plugin");
+ dialog.format_secondary_text (_("Do you want to reload the group?"));
+ var res = dialog.run ();
+ dialog.destroy ();
+
+ if (res == Gtk.ResponseType.YES) {
+ // Delete existing window object
+ var name = window.name;
+ window_monitor_list_remove (window);
+ this.window_list.remove (window);
+ window.destroy ();
+ // Create new window object
+ var win = create_window (name);
+ win.show ();
+ }
+
+ set_data_value (window, "external-change", false);
+ window.show_refresh_button = false;
+ }
+
+ /*
+ * Window monitor list management
+ */
+
+ /**
+ * window_monitor_list_add:
+ *
+ * Creates an Xnp.WindowMonitor object and stores it inside window_monitor_list.
+ */
+ private void window_monitor_list_add (Xnp.Window window) {
+ var file = File.new_for_path ("%s/%s".printf (notes_path, window.name));
+ var monitor = new Xnp.WindowMonitor (window, file);
+
+ monitor.window_updated.connect ((window) => {
+ if (get_data_value (window, "internal-change")) {
+ set_data_value (window, "internal-change", false);
+ }
+ else {
+ set_data_value (window, "external-change", true);
+ window.show_refresh_button = true;
+ }
+ });
+
+ this.window_monitor_list.prepend (monitor);
+ }
+
+ /**
+ * window_monitor_list_remove:
+ *
+ * Removes a monitor from window_monitor_list matching @window.
+ */
+ private void window_monitor_list_remove (Xnp.Window window) {
+ var monitor = window_monitor_list_lookup (window);
+ if (monitor != null) {
+ this.window_monitor_list.remove (monitor);
+ monitor.unref ();
+ monitor = null;
+ }
+ }
+
+ /**
+ * window_monitor_list_lookup:
+ *
+ * Returns the window_monitor object that contains @window from the window_monitor_list.
+ */
+ private Xnp.WindowMonitor window_monitor_list_lookup (Xnp.Window window) {
+ Xnp.WindowMonitor window_monitor = null;
+ foreach (var monitor in this.window_monitor_list) {
+ if (monitor.window == window) {
+ window_monitor = monitor;
+ break;
+ }
+ }
+ return window_monitor;
+ }
+
+ /*
+ * Utility functions
+ */
+
+ /**
+ * get_data_value:
+ *
+ * Convenience function to return a GObject data boolean value.
+ */
+ private bool get_data_value (GLib.Object object, string data) {
+ return object.get_data<bool> (data);
+ }
+
+ /**
+ * set_data_value:
+ *
+ * Convenience function to set a GObject data boolean value.
+ */
+ private void set_data_value (GLib.Object object, string data, bool val) {
+ object.set_data (data, ((int)val).to_pointer ());
+ }
+
+ /**
* window_name_exists:
*
* Verify if the given name already exists in the window list.
diff --git a/lib/icon-button.vala b/lib/icon-button.vala
index 7816ce1..dff687a 100644
--- a/lib/icon-button.vala
+++ b/lib/icon-button.vala
@@ -17,6 +17,8 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+const double M_PI = 3.14159265358979323846;
+
namespace Xnp {
public abstract class IconButton : Gtk.EventBox {
@@ -101,9 +103,10 @@ namespace Xnp {
public enum TitleBarButtonType {
EMPTY,
+ CLOSE,
LEFT_ARROW,
RIGHT_ARROW,
- CLOSE,
+ REFRESH,
}
public class TitleBarButton : IconButton {
@@ -125,6 +128,9 @@ namespace Xnp {
case TitleBarButtonType.RIGHT_ARROW:
draw_right_arrow_button (cr, width, height);
break;
+ case TitleBarButtonType.REFRESH:
+ draw_refresh_button (cr, width, height);
+ break;
default:
break;
}
@@ -218,7 +224,47 @@ namespace Xnp {
cr.stroke ();
}
}
+
+ private void draw_refresh_button (Cairo.Context cr, int width, int height) {
+ int border = 6;
+ int x1 = border;
+ int x2 = width - border;
+ int y1 = border;
+ int y2 = height - border;
+ if (x2 <= x1 || y2 <= y1) {
+ return;
+ }
+
+ cr.set_line_cap (Cairo.LineCap.ROUND);
+
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < 2; i++) {
+ if (i == 0) {
+ cr.set_source_rgba (1, 1, 1, active ? 0.4 : 0.2);
+ cr.set_line_width (4);
+ }
+ else {
+ set_widget_source_color (cr);
+ cr.set_line_width (2.44);
+ }
+ cr.save ();
+ cr.translate (x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2);
+ if (j == 0) {
+ cr.rotate (-M_PI / 16.0);
+ }
+ else {
+ cr.rotate ((15.0 * M_PI) / 16.0);
+ }
+ cr.arc (0, 0, x2 - x1, (5.0 * M_PI) / 16.0, M_PI);
+ var r = (x2 - x1) / 2.0;
+ cr.line_to (-r * 2.0, (3.0 * r) / 2.0);
+ cr.move_to (-r * 2.0, 0.0);
+ cr.line_to (-r, r / 2.0);
+ cr.stroke ();
+ cr.restore ();
+ }
+ }
+ }
}
}
-
diff --git a/lib/window-monitor.vala b/lib/window-monitor.vala
new file mode 100644
index 0000000..d790e8a
--- /dev/null
+++ b/lib/window-monitor.vala
@@ -0,0 +1,78 @@
+/*
+ * Notes - panel plugin for Xfce Desktop Environment
+ * Copyright (c) 2009-2010 Mike Massonnet <mmassonnet at xfce.org>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+namespace Xnp {
+
+ public class WindowMonitor : GLib.Object {
+
+ public Xnp.Window window;
+ private GLib.FileMonitor monitor;
+ private uint monitor_timeout = 0;
+
+ public signal void window_updated (Xnp.Window window);
+ public signal void note_updated (string note_name);
+ public signal void note_deleted (string note_name);
+ public signal void note_created (string note_name);
+
+ public WindowMonitor (Xnp.Window window, GLib.File file) {
+ this.window = window;
+ try {
+ monitor = file.monitor_directory (GLib.FileMonitorFlags.NONE, null);
+ monitor.set_rate_limit (1000);
+ monitor.changed.connect (monitor_change_cb);
+ }
+ catch (Error e) {
+ message ("Unable to create a directory monitor: %s", e.message);
+ }
+ }
+
+ private void monitor_change_cb (File file, File? other_file, FileMonitorEvent event) {
+ string note_name = file.get_basename ();
+ switch (event) {
+ case GLib.FileMonitorEvent.CHANGES_DONE_HINT:
+ note_updated (note_name);
+ window_updated_cb ();
+ break;
+
+ case GLib.FileMonitorEvent.DELETED:
+ note_deleted (note_name);
+ window_updated_cb ();
+ break;
+
+ case GLib.FileMonitorEvent.CREATED:
+ // Don't send window-updated signal, as a CHANGES_DONE_HINT is emitted anyway
+ note_created (note_name);
+ break;
+ }
+ }
+
+ private void window_updated_cb () {
+ if (monitor_timeout != 0) {
+ Source.remove (monitor_timeout);
+ }
+ monitor_timeout = Timeout.add_seconds (1, () => {
+ window_updated (window);
+ monitor_timeout = 0;
+ return false;
+ });
+ }
+
+ }
+
+}
diff --git a/lib/window.vala b/lib/window.vala
index b2b4581..57d7219 100644
--- a/lib/window.vala
+++ b/lib/window.vala
@@ -36,6 +36,7 @@ namespace Xnp {
private Gdk.Pixbuf menu_pixbuf;
private Gdk.Pixbuf menu_hover_pixbuf;
private Gtk.Label title_label;
+ private Xnp.TitleBarButton refresh_button;
private Xnp.TitleBarButton left_arrow_button;
private Xnp.TitleBarButton right_arrow_button;
private Xnp.TitleBarButton close_button;
@@ -148,6 +149,22 @@ namespace Xnp {
}
}
+ private bool _show_refresh_button;
+ public bool show_refresh_button {
+ get {
+ return this._show_refresh_button;
+ }
+ set {
+ this._show_refresh_button = value;
+ if (value == true) {
+ this.refresh_button.show ();
+ }
+ else {
+ this.refresh_button.hide ();
+ }
+ }
+ }
+
public signal void action (string action);
public signal void save_data (Xnp.Note note);
public signal void note_inserted (Xnp.Note note);
@@ -234,6 +251,11 @@ namespace Xnp {
this.title_label.xalign = (float)0.0;
title_evbox.add (this.title_label);
title_box.pack_start (title_evbox, true, true, 6);
+ this.refresh_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.REFRESH);
+ this.refresh_button.tooltip_text = _("Refresh notes");
+ this.refresh_button.no_show_all = true;
+ this.refresh_button.sensitive = false;
+ title_box.pack_start (this.refresh_button, false, false, 2);
this.left_arrow_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.LEFT_ARROW);
this.left_arrow_button.tooltip_text = Gtk.accelerator_get_label (0xff55, Gdk.ModifierType.CONTROL_MASK); // GDK_Page_Up
this.left_arrow_button.sensitive = false;
@@ -266,6 +288,7 @@ namespace Xnp {
/* Connect mouse click signals */
menu_evbox.button_press_event.connect (menu_evbox_pressed_cb);
+ this.refresh_button.clicked.connect (action_refresh_notes);
this.left_arrow_button.clicked.connect (action_prev_note);
this.right_arrow_button.clicked.connect (action_next_note);
this.close_button.clicked.connect (() => { hide (); });
@@ -279,6 +302,7 @@ namespace Xnp {
focus_in_event.connect (() => {
menu_image.sensitive = true;
title_label.sensitive = true;
+ refresh_button.sensitive = true;
update_navigation_sensitivity (this.notebook.get_current_page ());
close_button.sensitive = true;
return false;
@@ -286,6 +310,7 @@ namespace Xnp {
focus_out_event.connect (() => {
menu_image.sensitive = false;
title_label.sensitive = false;
+ refresh_button.sensitive = false;
left_arrow_button.sensitive = false;
right_arrow_button.sensitive = false;
close_button.sensitive = false;
@@ -558,6 +583,10 @@ namespace Xnp {
((Xnp.Note)child).text_view.undo ();
}
+ private void action_refresh_notes () {
+ action ("refresh-notes");
+ }
+
private void action_next_note () {
notebook.next_page ();
}
@@ -1107,4 +1136,3 @@ namespace Xnp {
}
}
-
More information about the Xfce4-commits
mailing list