[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