1 /*
2 
3 Copyright (c) 2001-2007 Michael Terry
4 Copyright (c) 2009 Paul Ivanov
5 Copyright (c) 2011 Sergei Riaguzov
6 Copyright (c) 2011 Dennis Hilmar
7 Copyright (c) 2011 OBATA Akio
8 Copyright (c) 2013-2015 Arthur Borsboom
9 
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 3 of the License, or
13 (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 
24 */
25 
26 #include "../config.h"
27 
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <gtksourceview/gtksource.h>
31 
32 #include "xpad-pad.h"
33 #include "xpad-app.h"
34 #include "xpad-pad-properties.h"
35 #include "xpad-periodic.h"
36 #include "xpad-preferences.h"
37 #include "xpad-text-buffer.h"
38 #include "xpad-text-view.h"
39 #include "xpad-toolbar.h"
40 #include "xpad-tray.h"
41 #include "fio.h"
42 #include "help.h"
43 
44 struct XpadPadPrivate
45 {
46 	/* saved values */
47 	gint x, y;
48 	guint width, height;
49 	gboolean location_valid;
50 	gchar *infoname;
51 	gchar *contentname;
52 	gboolean sticky;
53 
54 	/* selected child widgets */
55 	GtkWidget *textview;
56 	GtkWidget *scrollbar;
57 
58 	/* toolbar stuff */
59 	GtkWidget *toolbar;
60 	guint toolbar_timeout;
61 	guint toolbar_height;
62 	gboolean toolbar_expanded;
63 	gboolean toolbar_pad_resized;
64 
65 	/* properties window */
66 	GtkWidget *properties;
67 
68 	/* preferences/xpad global settings */
69 	XpadSettings *settings;
70 
71 	/* menus */
72 	GtkWidget *menu;
73 	GtkWidget *highlight_menu;
74 
75 	gboolean unsaved_content;
76 	gboolean unsaved_info;
77 
78 	GtkClipboard *clipboard;
79 	GtkAccelGroup *accel_group;
80 
81 	XpadPadGroup *group;
82 };
83 
84 G_DEFINE_TYPE_WITH_PRIVATE (XpadPad, xpad_pad, GTK_TYPE_WINDOW)
85 
86 enum
87 {
88 	CLOSED,
89 	LAST_SIGNAL
90 };
91 
92 enum
93 {
94   PROP_0,
95   PROP_GROUP,
96   PROP_SETTINGS,
97   N_PROPERTIES
98 };
99 
100 static GParamSpec *obj_prop[N_PROPERTIES] = { NULL, };
101 static guint signals[LAST_SIGNAL] = { 0 };
102 
103 static void xpad_pad_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
104 static void xpad_pad_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
105 static void xpad_pad_constructed (GObject *object);
106 static void xpad_pad_dispose (GObject *object);
107 static void xpad_pad_finalize (GObject *object);
108 static void xpad_pad_load_info (XpadPad *pad, gboolean *show);
109 static GtkWidget *menu_get_popup_highlight (XpadPad *pad, GtkAccelGroup *accel_group);
110 static GtkWidget *menu_get_popup_no_highlight (XpadPad *pad, GtkAccelGroup *accel_group);
111 static void xpad_pad_show (XpadPad *pad);
112 static gboolean xpad_pad_configure_event (XpadPad *pad, GdkEventConfigure *event);
113 static gboolean xpad_pad_toolbar_size_allocate (XpadPad *pad, GtkAllocation *event);
114 static gboolean xpad_pad_delete_event (XpadPad *pad, GdkEvent *event);
115 static gboolean xpad_pad_popup_menu (XpadPad *pad);
116 static void menu_popup (XpadPad *pad);
117 static void menu_popdown (XpadPad *pad);
118 static gboolean xpad_pad_button_press_event (XpadPad *pad, GdkEventButton *event);
119 static void xpad_pad_text_changed (XpadPad *pad, GtkSourceBuffer *buffer);
120 static void xpad_pad_notify_has_scrollbar (XpadPad *pad);
121 static void xpad_pad_notify_has_decorations (XpadPad *pad);
122 static void xpad_pad_notify_has_toolbar (XpadPad *pad);
123 static void xpad_pad_notify_autohide_toolbar (XpadPad *pad);
124 static void xpad_pad_notify_hide_from_taskbar (XpadPad *pad);
125 static void xpad_pad_notify_hide_from_task_switcher (XpadPad *pad);
126 static void xpad_pad_hide_toolbar (XpadPad *pad);
127 static void xpad_pad_show_toolbar (XpadPad *pad);
128 static void xpad_pad_popup (XpadPad *pad, GdkEventButton *event);
129 static void xpad_pad_spawn (XpadPad *pad);
130 static void xpad_pad_clear (XpadPad *pad);
131 static void xpad_pad_undo (XpadPad *pad);
132 static void xpad_pad_redo (XpadPad *pad);
133 static void xpad_pad_cut (XpadPad *pad);
134 static void xpad_pad_copy (XpadPad *pad);
135 static void xpad_pad_paste (XpadPad *pad);
136 static void xpad_pad_delete (XpadPad *pad);
137 static void xpad_pad_open_properties (XpadPad *pad);
138 static void xpad_pad_open_preferences (XpadPad *pad);
139 static void xpad_pad_close_all (XpadPad *pad);
140 static void xpad_pad_sync_title (XpadPad *pad);
141 static void xpad_pad_stick_unstick (XpadPad *pad);
142 static gboolean xpad_pad_leave_notify_event (GtkWidget *pad, GdkEventCrossing *event);
143 static gboolean xpad_pad_enter_notify_event (GtkWidget *pad, GdkEventCrossing *event);
144 
145 /* Create a new empty pad. */
146 GtkWidget *
xpad_pad_new(XpadPadGroup * group,XpadSettings * settings)147 xpad_pad_new (XpadPadGroup *group, XpadSettings *settings)
148 {
149 	GtkWidget *pad = GTK_WIDGET (g_object_new (XPAD_TYPE_PAD, "group", group, "settings", settings, NULL));
150 
151 	xpad_pad_save_info_delayed (XPAD_PAD (pad));
152 
153 	return pad;
154 }
155 
156 /* Create a new pad based on the provided info-xxxxx file from the config directory and return this pad */
157 GtkWidget *
xpad_pad_new_with_info(XpadPadGroup * group,XpadSettings * settings,const gchar * info_filename,gboolean * show)158 xpad_pad_new_with_info (XpadPadGroup *group, XpadSettings *settings, const gchar *info_filename, gboolean *show)
159 {
160 	GtkWidget *pad = GTK_WIDGET (g_object_new (XPAD_TYPE_PAD, "group", group, "settings", settings, NULL));
161 
162 	XPAD_PAD (pad)->priv->infoname = g_strdup (info_filename);
163 	xpad_pad_load_info (XPAD_PAD (pad), show);
164 	xpad_pad_load_content (XPAD_PAD (pad));
165 	gtk_window_set_role (GTK_WINDOW (pad), XPAD_PAD (pad)->priv->infoname);
166 
167 	return pad;
168 }
169 
170 /* Create a new pad based on the provided filename from the command line */
171 GtkWidget *
xpad_pad_new_from_file(XpadPadGroup * group,XpadSettings * settings,const gchar * filename)172 xpad_pad_new_from_file (XpadPadGroup *group, XpadSettings *settings, const gchar *filename)
173 {
174 	GtkWidget *pad = NULL;
175 	gchar *content;
176 
177 	content = fio_get_file (filename, CURRENT_WORK_DIR);
178 
179 	if (!content) {
180 		gchar *usertext = g_strdup_printf (_("Could not read file %s."), filename);
181 		xpad_app_error (NULL, usertext, NULL);
182 		g_free (usertext);
183 	} else {
184 		GtkSourceBuffer *buffer;
185 
186 		pad = xpad_pad_new (group, settings);
187 
188 		buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (XPAD_PAD (pad)->priv->textview)));
189 
190 		xpad_text_buffer_freeze_undo (XPAD_TEXT_BUFFER (buffer));
191 
192 		g_signal_handlers_block_by_func (buffer, xpad_pad_text_changed, pad);
193 		xpad_text_buffer_set_text_with_tags (XPAD_TEXT_BUFFER (buffer), content ? content : "");
194 		g_signal_handlers_unblock_by_func (buffer, xpad_pad_text_changed, pad);
195 
196 		g_free (content);
197 
198 		xpad_text_buffer_thaw_undo (XPAD_TEXT_BUFFER (buffer));
199 
200 		xpad_pad_text_changed(XPAD_PAD(pad), buffer);
201 	}
202 
203 	return pad;
204 }
205 
206 /* Class pad - constructor */
207 static void
xpad_pad_class_init(XpadPadClass * klass)208 xpad_pad_class_init (XpadPadClass *klass)
209 {
210 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
211 
212 	gobject_class->constructed = xpad_pad_constructed;
213 	gobject_class->set_property = xpad_pad_set_property;
214 	gobject_class->get_property = xpad_pad_get_property;
215 	gobject_class->dispose = xpad_pad_dispose;
216 	gobject_class->finalize = xpad_pad_finalize;
217 
218 	signals[CLOSED] =
219 		g_signal_new ("closed",
220 						  G_OBJECT_CLASS_TYPE (gobject_class),
221 						  G_SIGNAL_RUN_FIRST,
222 						  G_STRUCT_OFFSET (XpadPadClass, closed),
223 						  NULL, NULL,
224 						  g_cclosure_marshal_VOID__VOID,
225 						  G_TYPE_NONE,
226 						  0);
227 
228 	/* Properties */
229 	obj_prop[PROP_GROUP] = g_param_spec_pointer ("group", "Pad group", "Pad group for this pad", G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
230 	obj_prop[PROP_SETTINGS] = g_param_spec_pointer ("settings", "Xpad settings", "Xpad global settings", G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
231 
232 	g_object_class_install_properties (gobject_class, N_PROPERTIES, obj_prop);
233 }
234 
235 static void
xpad_pad_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)236 xpad_pad_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
237 {
238 	XpadPad *pad = XPAD_PAD (object);
239 
240 	switch (prop_id)
241 	{
242 	case PROP_GROUP:
243 		pad->priv->group = g_value_get_pointer (value);
244 		g_object_ref (pad->priv->group);
245 		if (pad->priv->group)
246 			xpad_pad_group_add (pad->priv->group, GTK_WIDGET (pad));
247 		break;
248 
249 	case PROP_SETTINGS:
250 		pad->priv->settings = g_value_get_pointer (value);
251 		g_object_ref (pad->priv->settings);
252 		break;
253 
254 	default:
255 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
256 		break;
257 	}
258 }
259 
260 static void
xpad_pad_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)261 xpad_pad_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
262 {
263 	XpadPad *pad = XPAD_PAD (object);
264 
265 	switch (prop_id)
266 	{
267 	case PROP_GROUP:
268 		g_value_set_pointer (value, pad->priv->group);
269 		break;
270 
271 	case PROP_SETTINGS:
272 		g_value_set_pointer (value, pad->priv->settings);
273 		break;
274 
275 	default:
276 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
277 		break;
278 	}
279 }
280 
281 /* Class pad - initializer */
282 static void
xpad_pad_init(XpadPad * pad)283 xpad_pad_init (XpadPad *pad)
284 {
285 	pad->priv = xpad_pad_get_instance_private (pad);
286 
287 	pad->priv->x = 0;
288 	pad->priv->y = 0;
289 	pad->priv->location_valid = FALSE;
290 	pad->priv->infoname = NULL;
291 	pad->priv->contentname = NULL;
292 	pad->priv->textview = NULL;
293 	pad->priv->scrollbar = NULL;
294 	pad->priv->toolbar = NULL;
295 	pad->priv->toolbar_timeout = 0;
296 	pad->priv->toolbar_height = 0;
297 	pad->priv->toolbar_expanded = FALSE;
298 	pad->priv->toolbar_pad_resized = TRUE;
299 	pad->priv->properties = NULL;
300 	pad->priv->unsaved_content = FALSE;
301 	pad->priv->unsaved_info = FALSE;
302 }
303 
xpad_pad_constructed(GObject * object)304 static void xpad_pad_constructed (GObject *object)
305 {
306 	XpadPad *pad = XPAD_PAD (object);
307 
308 	gboolean decorations, hide_from_taskbar, hide_from_task_switcher;
309 	GtkBox *vbox;
310 
311 	g_object_get (pad->priv->settings,
312 			"width", &pad->priv->width,
313 			"height", &pad->priv->height,
314 			"autostart-sticky", &pad->priv->sticky, NULL);
315 
316 	GtkWindow *pad_window = GTK_WINDOW (pad);
317 
318 	pad->priv->textview = GTK_WIDGET (XPAD_TEXT_VIEW (xpad_text_view_new (pad->priv->settings, pad)));
319 
320 	pad->priv->scrollbar = GTK_WIDGET (g_object_new (GTK_TYPE_SCROLLED_WINDOW,
321 		"hadjustment", NULL,
322 		"hscrollbar-policy", GTK_POLICY_NEVER,
323 		"shadow-type", GTK_SHADOW_NONE,
324 		"vadjustment", NULL,
325 		"vscrollbar-policy", GTK_POLICY_NEVER,
326 		"child", pad->priv->textview,
327 		NULL));
328 
329 	pad->priv->toolbar = GTK_WIDGET (xpad_toolbar_new (pad));
330 
331 	pad->priv->accel_group = gtk_accel_group_new ();
332 	gtk_window_add_accel_group (pad_window, pad->priv->accel_group);
333 	pad->priv->menu = menu_get_popup_no_highlight (pad, pad->priv->accel_group);
334 	pad->priv->highlight_menu = menu_get_popup_highlight (pad, pad->priv->accel_group);
335 	gtk_accel_group_connect (pad->priv->accel_group, GDK_KEY_Q, GDK_CONTROL_MASK, 0, g_cclosure_new_swap (G_CALLBACK (xpad_app_quit), pad, NULL));
336 
337 	vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
338 	gtk_box_set_homogeneous (vbox, FALSE);
339 	gtk_box_pack_start (vbox, pad->priv->scrollbar, TRUE, TRUE, 0);
340 	gtk_box_pack_start (vbox, pad->priv->toolbar, FALSE, FALSE, 0);
341 
342 	gtk_container_child_set (GTK_CONTAINER (vbox), pad->priv->toolbar, "expand", FALSE, NULL);
343 
344 	g_object_get (pad->priv->settings, "has-decorations", &decorations, NULL);
345 	g_object_get (pad->priv->settings, "hide-from-taskbar", &hide_from_taskbar, NULL);
346 	g_object_get (pad->priv->settings, "hide-from-task-switcher", &hide_from_task_switcher, NULL);
347 	gtk_window_set_decorated (pad_window, decorations);
348 	gtk_window_set_default_size (pad_window, (gint) pad->priv->width, (gint) pad->priv->height);
349 	gtk_window_set_gravity (pad_window, GDK_GRAVITY_STATIC); /* static gravity makes saving pad x,y work */
350 	gtk_window_set_skip_taskbar_hint (pad_window, hide_from_taskbar);
351 	gtk_window_set_skip_pager_hint (pad_window, hide_from_task_switcher);
352 	gtk_window_set_position (pad_window, GTK_WIN_POS_MOUSE);
353 
354 	g_object_set (G_OBJECT (pad), "child", vbox, NULL);
355 
356         xpad_pad_notify_has_scrollbar (pad);
357         // xpad_pad_notify_has_selection (pad);
358         // xpad_pad_notify_clipboard_owner_changed (pad);
359         // xpad_pad_notify_undo_redo_changed (pad);
360 
361 	pad->priv->clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
362 
363 	xpad_pad_sync_title (pad);
364 
365 	/* Add CSS style class, so the styling can be overridden by a GTK theme */
366 	GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET (pad));
367 	gtk_style_context_add_class(context, "XpadPad");
368 
369 	gtk_widget_show_all (GTK_WIDGET (vbox));
370 
371 	gtk_widget_hide (pad->priv->toolbar);
372 	xpad_pad_notify_has_toolbar (pad);
373 
374 	/* Set up signals */
375 	gtk_widget_add_events (GTK_WIDGET (pad), GDK_BUTTON_PRESS_MASK | GDK_PROPERTY_CHANGE_MASK);
376 	gtk_widget_add_events (pad->priv->toolbar, GDK_ALL_EVENTS_MASK);
377 	g_signal_connect_swapped (GTK_TEXT_VIEW (pad->priv->textview), "button-press-event", G_CALLBACK (xpad_pad_button_press_event), pad);
378 	g_signal_connect_swapped (GTK_TEXT_VIEW (pad->priv->textview), "popup-menu", G_CALLBACK (xpad_pad_popup_menu), pad);
379 	g_signal_connect_swapped (pad->priv->toolbar, "size-allocate", G_CALLBACK (xpad_pad_toolbar_size_allocate), pad);
380 	g_signal_connect (pad, "button-press-event", G_CALLBACK (xpad_pad_button_press_event), NULL);
381 	g_signal_connect (pad, "configure-event", G_CALLBACK (xpad_pad_configure_event), NULL);
382 	g_signal_connect (pad, "delete-event", G_CALLBACK (xpad_pad_delete_event), NULL);
383 	g_signal_connect (pad, "popup-menu", G_CALLBACK (xpad_pad_popup_menu), NULL);
384 	g_signal_connect (pad, "show", G_CALLBACK (xpad_pad_show), NULL);
385 	g_signal_connect_swapped (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)), "changed", G_CALLBACK (xpad_pad_text_changed), pad);
386 
387 	g_signal_connect (pad, "enter-notify-event", G_CALLBACK (xpad_pad_enter_notify_event), NULL);
388 	g_signal_connect (pad, "leave-notify-event", G_CALLBACK (xpad_pad_leave_notify_event), NULL);
389 
390 	g_signal_connect_swapped (pad->priv->settings, "notify::hide-from-taskbar", G_CALLBACK (xpad_pad_notify_hide_from_taskbar), pad);
391 	g_signal_connect_swapped (pad->priv->settings, "notify::hide-from-task-switcher", G_CALLBACK (xpad_pad_notify_hide_from_task_switcher), pad);
392 	g_signal_connect_swapped (pad->priv->settings, "notify::has-decorations", G_CALLBACK (xpad_pad_notify_has_decorations), pad);
393 	g_signal_connect_swapped (pad->priv->settings, "notify::has-toolbar", G_CALLBACK (xpad_pad_notify_has_toolbar), pad);
394 	g_signal_connect_swapped (pad->priv->settings, "notify::autohide-toolbar", G_CALLBACK (xpad_pad_notify_autohide_toolbar), pad);
395 	g_signal_connect_swapped (pad->priv->settings, "notify::has-scrollbar", G_CALLBACK (xpad_pad_notify_has_scrollbar), pad);
396 	g_signal_connect_swapped (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)), "notify::has-selection", G_CALLBACK (xpad_pad_notify_has_selection), pad);
397 	g_signal_connect_swapped (pad->priv->clipboard, "owner-change", G_CALLBACK (xpad_pad_notify_clipboard_owner_changed), pad);
398 
399 	g_signal_connect_swapped (pad->priv->toolbar, "activate-new", G_CALLBACK (xpad_pad_spawn), pad);
400 	g_signal_connect_swapped (pad->priv->toolbar, "activate-clear", G_CALLBACK (xpad_pad_clear), pad);
401 	g_signal_connect_swapped (pad->priv->toolbar, "activate-close", G_CALLBACK (xpad_pad_close), pad);
402 	g_signal_connect_swapped (pad->priv->toolbar, "activate-undo", G_CALLBACK (xpad_pad_undo), pad);
403 	g_signal_connect_swapped (pad->priv->toolbar, "activate-redo", G_CALLBACK (xpad_pad_redo), pad);
404 	g_signal_connect_swapped (pad->priv->toolbar, "activate-cut", G_CALLBACK (xpad_pad_cut), pad);
405 	g_signal_connect_swapped (pad->priv->toolbar, "activate-copy", G_CALLBACK (xpad_pad_copy), pad);
406 	g_signal_connect_swapped (pad->priv->toolbar, "activate-paste", G_CALLBACK (xpad_pad_paste), pad);
407 	g_signal_connect_swapped (pad->priv->toolbar, "activate-delete", G_CALLBACK (xpad_pad_delete), pad);
408 	g_signal_connect_swapped (pad->priv->toolbar, "activate-properties", G_CALLBACK (xpad_pad_open_properties), pad);
409 	g_signal_connect_swapped (pad->priv->toolbar, "activate-preferences", G_CALLBACK (xpad_pad_open_preferences), pad);
410 	g_signal_connect_swapped (pad->priv->toolbar, "activate-quit", G_CALLBACK (xpad_pad_close_all), pad);
411 
412 	g_signal_connect_swapped (pad->priv->toolbar, "popup", G_CALLBACK (menu_popup), pad);
413 	g_signal_connect_swapped (pad->priv->toolbar, "popdown", G_CALLBACK (menu_popdown), pad);
414 
415 	g_signal_connect_swapped (pad->priv->menu, "deactivate", G_CALLBACK (menu_popdown), pad);
416 	g_signal_connect_swapped (pad->priv->highlight_menu, "deactivate", G_CALLBACK (menu_popdown), pad);
417 }
418 
419 static void
xpad_pad_dispose(GObject * object)420 xpad_pad_dispose (GObject *object)
421 {
422 	XpadPad *pad = XPAD_PAD (object);
423 
424 	xpad_pad_remove_accelerator_group (pad);
425 
426 	g_clear_object(&pad->priv->group);
427 
428 	if (GTK_IS_WIDGET(pad->priv->menu)) {
429 		gtk_widget_destroy (pad->priv->menu);
430 		pad->priv->menu = NULL;
431 	}
432 
433 	if (GTK_IS_WIDGET(pad->priv->highlight_menu)) {
434 		gtk_widget_destroy (pad->priv->highlight_menu);
435 		pad->priv->highlight_menu = NULL;
436 	}
437 
438 	if (XPAD_IS_PAD_PROPERTIES (pad->priv->properties)) {
439 		gtk_widget_destroy (pad->priv->properties);
440 		pad->priv->properties = NULL;
441 	}
442 
443 	gtk_clipboard_clear (pad->priv->clipboard);
444 
445 	/* For some reason the toolbar handler does not get automatically disconnected (or not at the right moment), leading to errors after deleting a pad. This manual disconnect prevents this error. */
446 	if (XPAD_IS_TOOLBAR (pad->priv->toolbar)) {
447 		g_signal_handlers_disconnect_matched (pad->priv->toolbar, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, pad);
448 		gtk_widget_destroy(pad->priv->toolbar);
449 		pad->priv->toolbar = NULL;
450 	}
451 
452 	G_OBJECT_CLASS (xpad_pad_parent_class)->dispose (object);
453 }
454 
455 static void
xpad_pad_finalize(GObject * object)456 xpad_pad_finalize (GObject *object)
457 {
458 	XpadPad *pad = XPAD_PAD (object);
459 
460 	if (pad->priv->settings) {
461 		g_signal_handlers_disconnect_matched (pad->priv->settings, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, pad);
462 		g_clear_object(&pad->priv->settings);
463 	}
464 
465 	g_free (pad->priv->infoname);
466 	g_free (pad->priv->contentname);
467 
468 	G_OBJECT_CLASS (xpad_pad_parent_class)->finalize (object);
469 }
470 
471 static void
xpad_pad_show(XpadPad * pad)472 xpad_pad_show (XpadPad *pad)
473 {
474 	/*
475 	 * Some wm's might not acknowledge our request for a specific
476 	 * location before we are shown.  What we do here is a little gimpy
477 	 * and not very respectful of wms' sovereignty, but it has the effect
478 	 * of making pads' locations very dependable.  We just move the pad
479 	 * again here after being shown.  This may create a visual effect if
480 	 * the wm did ignore us, but is better than being in the wrong
481 	 * place, I guess.
482 	 */
483 	if (pad->priv->location_valid)
484 		gtk_window_move (GTK_WINDOW (pad), pad->priv->x, pad->priv->y);
485 
486 	xpad_pad_stick_unstick (pad);
487 
488 	/* Show the pad and set the cursor into the pad */
489 	gtk_window_present (GTK_WINDOW (pad));
490 	gtk_widget_grab_focus (GTK_WIDGET (pad->priv->textview));
491 
492 	/* Save the new visibility status to the disk */
493 	xpad_pad_save_info_delayed (pad);
494 }
495 
496 void
xpad_pad_set_sticky(XpadPad * pad,gboolean is_sticky)497 xpad_pad_set_sticky (XpadPad *pad, gboolean is_sticky)
498 {
499 	pad->priv->sticky = is_sticky;
500 	xpad_pad_stick_unstick (pad);
501 	xpad_pad_save_info_delayed (pad);
502 }
503 
504 static void
xpad_pad_stick_unstick(XpadPad * pad)505 xpad_pad_stick_unstick (XpadPad *pad)
506 {
507 	if (pad->priv->sticky)
508 		gtk_window_stick (GTK_WINDOW (pad));
509 	else
510 		gtk_window_unstick (GTK_WINDOW (pad));
511 }
512 
513 
toolbar_timeout(XpadPad * pad)514 static gboolean toolbar_timeout (XpadPad *pad)
515 {
516 	if (!pad || !pad->priv || !pad->priv->toolbar_timeout)
517 		return FALSE;
518 
519 	gboolean has_toolbar, autohide_toolbar;
520 	g_object_get (pad->priv->settings, "has-toolbar", &has_toolbar, "autohide-toolbar", &autohide_toolbar, NULL);
521 
522 	if (pad->priv->toolbar_timeout && autohide_toolbar && has_toolbar)
523 		xpad_pad_hide_toolbar (pad);
524 
525 	pad->priv->toolbar_timeout = 0;
526 
527 	return FALSE;
528 }
529 
530 static void
xpad_pad_notify_has_decorations(XpadPad * pad)531 xpad_pad_notify_has_decorations (XpadPad *pad)
532 {
533 	GtkWidget *pad_widget = GTK_WIDGET (pad);
534 	GtkWindow *pad_window = GTK_WINDOW (pad);
535 	gboolean decorations;
536 	g_object_get (pad->priv->settings, "has-decorations", &decorations, NULL);
537 
538 	/*
539 	 *  There are two modes of operation:  a normal mode and a 'stealth' mode.
540 	 *  If decorations are disabled, we also don't show up in the taskbar or pager.
541 	 */
542 	gtk_window_set_decorated (pad_window, decorations);
543 
544 	/*
545 	 * reshow_with_initial_size() seems to set the window back to a never-shown state.
546 	 * This is good, as some WMs don't like us changing the above parameters mid-run,
547 	 * even if we do a hide/show cycle.
548 	 */
549 	gtk_window_set_default_size (pad_window, (gint) pad->priv->width, (gint) pad->priv->height);
550 	gtk_widget_hide (pad_widget);
551 	gtk_widget_unrealize (pad_widget);
552 	gtk_widget_show (pad_widget);
553 }
554 
555 static void
xpad_pad_notify_hide_from_taskbar(XpadPad * pad)556 xpad_pad_notify_hide_from_taskbar (XpadPad *pad)
557 {
558 	gboolean hide;
559 	g_object_get (pad->priv->settings, "hide-from-taskbar", &hide, NULL);
560 	gtk_window_set_skip_taskbar_hint (GTK_WINDOW (pad), hide);
561 }
562 
563 static void
xpad_pad_notify_hide_from_task_switcher(XpadPad * pad)564 xpad_pad_notify_hide_from_task_switcher (XpadPad *pad)
565 {
566 	gboolean hide;
567 	g_object_get (pad->priv->settings, "hide-from-task-switcher", &hide, NULL);
568 	gtk_window_set_skip_pager_hint (GTK_WINDOW (pad), hide);
569 }
570 
571 static void
xpad_pad_notify_has_toolbar(XpadPad * pad)572 xpad_pad_notify_has_toolbar (XpadPad *pad)
573 {
574 	gboolean has_toolbar, autohide_toolbar;
575 	g_object_get (pad->priv->settings, "has-toolbar", &has_toolbar, "autohide-toolbar", &autohide_toolbar, NULL);
576 
577 	if (has_toolbar && !autohide_toolbar)
578 		xpad_pad_show_toolbar (pad);
579 	else
580 		xpad_pad_hide_toolbar (pad);
581 }
582 
583 static void
xpad_pad_notify_autohide_toolbar(XpadPad * pad)584 xpad_pad_notify_autohide_toolbar (XpadPad *pad)
585 {
586 	gboolean autohide_toolbar;
587 	g_object_get (pad->priv->settings, "autohide-toolbar", &autohide_toolbar, NULL);
588 
589 	if (autohide_toolbar)
590 	{
591 		/* Likely not to be in pad when turning setting on */
592 		if (!pad->priv->toolbar_timeout)
593 			pad->priv->toolbar_timeout = g_timeout_add (1000, (GSourceFunc) toolbar_timeout, pad);
594 	}
595 	else
596 	{
597 		gboolean has_toolbar;
598 		g_object_get (pad->priv->settings, "has-toolbar", &has_toolbar, NULL);
599 
600 		if (has_toolbar)
601 			xpad_pad_show_toolbar(pad);
602 	}
603 }
604 
605 static void
xpad_pad_notify_has_scrollbar(XpadPad * pad)606 xpad_pad_notify_has_scrollbar (XpadPad *pad)
607 {
608 	gboolean has_scrollbar;
609 	g_object_get (pad->priv->settings, "has-scrollbar", &has_scrollbar, NULL);
610 
611 	if (has_scrollbar)
612 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (pad->priv->scrollbar),
613 			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
614 	else
615 	{
616 		GtkAdjustment *v, *h;
617 
618 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (pad->priv->scrollbar),
619 			GTK_POLICY_NEVER, GTK_POLICY_NEVER);
620 
621 		/* now we need to adjust view so that user can see whole pad */
622 		h = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (pad->priv->scrollbar));
623 		v = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (pad->priv->scrollbar));
624 
625 		gtk_adjustment_set_value (h, 0);
626 		gtk_adjustment_set_value (v, 0);
627 	}
628 }
629 
630 static guint
xpad_pad_text_and_toolbar_height(XpadPad * pad)631 xpad_pad_text_and_toolbar_height (XpadPad *pad)
632 {
633 	cairo_rectangle_int_t rec;
634 	gint textx, texty, x, y;
635 	GtkTextIter iter;
636 	GtkSourceView *pad_textview = GTK_SOURCE_VIEW (pad->priv->textview);
637 
638 	gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (pad_textview), &rec);
639 	gtk_text_buffer_get_end_iter (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad_textview)), &iter);
640 	gtk_text_view_get_iter_location (GTK_TEXT_VIEW (pad_textview), &iter, &rec);
641 	gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (pad_textview),
642 		GTK_TEXT_WINDOW_WIDGET, rec.x + rec.width, rec.y + rec.height,
643 		&textx, &texty);
644 	gtk_widget_translate_coordinates(GTK_WIDGET (pad_textview), GTK_WIDGET (pad), textx, texty, &x, &y);
645 
646 	/* Safe cast from gint to guint */
647 	if (y >= 0) {
648 		return (guint) y + pad->priv->toolbar_height + gtk_container_get_border_width (GTK_CONTAINER (pad_textview));
649 	}
650 	else {
651 		g_warning("There is a problem in the program Xpad. In function 'xpad_pad_toolbar_size_allocate' the variable 'event->height' is not a positive number. Please send a bugreport to https://bugs.launchpad.net/xpad/+filebug to help improve Xpad.");
652 		return 0;
653 	}
654 }
655 
656 static void
xpad_pad_show_toolbar(XpadPad * pad)657 xpad_pad_show_toolbar (XpadPad *pad)
658 {
659 	if (!gtk_widget_get_visible (pad->priv->toolbar))
660 	{
661 		GtkRequisition req;
662 		GtkWidget *pad_widget = GTK_WIDGET (pad);
663 
664 		if (gtk_widget_get_window (pad_widget))
665 			gdk_window_freeze_updates (gtk_widget_get_window (pad_widget));
666 		gtk_widget_show (pad->priv->toolbar);
667 		if (!pad->priv->toolbar_height)
668 		{
669 			gtk_widget_get_preferred_size (pad->priv->toolbar, &req, NULL);
670 			/* safe cast from gint to guint */
671 			if (req.height >= 0) {
672 				pad->priv->toolbar_height = (guint) req.height;
673 			}
674 			else {
675 				g_warning ("There is a problem in the program Xpad. In function 'xpad_pad_show_toolbar' the variable 'req.height' is not a positive number. Please send a bugreport to https://bugs.launchpad.net/xpad/+filebug to help improve Xpad.");
676 				pad->priv->toolbar_height = 0;
677 			}
678 		}
679 
680 		/* Do we have room for the toolbar without covering text? */
681 		if (xpad_pad_text_and_toolbar_height (pad) > pad->priv->height)
682 		{
683 			pad->priv->toolbar_expanded = TRUE;
684 			pad->priv->height += pad->priv->toolbar_height;
685 			gtk_window_resize (GTK_WINDOW (pad), (gint) pad->priv->width, (gint) pad->priv->height);
686 		}
687 		else
688 			pad->priv->toolbar_expanded = FALSE;
689 
690 		pad->priv->toolbar_pad_resized = FALSE;
691 
692 		if (gtk_widget_get_window (pad_widget))
693 			gdk_window_thaw_updates (gtk_widget_get_window (pad_widget));
694 	}
695 }
696 
697 static void
xpad_pad_hide_toolbar(XpadPad * pad)698 xpad_pad_hide_toolbar (XpadPad *pad)
699 {
700 	if (gtk_widget_get_visible (pad->priv->toolbar))
701 	{
702 		GtkWidget *pad_widget = GTK_WIDGET (pad);
703 		if (gtk_widget_get_window (pad_widget))
704 			gdk_window_freeze_updates (gtk_widget_get_window (pad_widget));
705 		gtk_widget_hide (pad->priv->toolbar);
706 
707 		if (pad->priv->toolbar_expanded ||
708 			 (pad->priv->toolbar_pad_resized && xpad_pad_text_and_toolbar_height (pad) >= pad->priv->height))
709 		{
710 				pad->priv->height -= pad->priv->toolbar_height;
711 				gtk_window_resize (GTK_WINDOW (pad), (gint) pad->priv->width, (gint) pad->priv->height);
712 				pad->priv->toolbar_expanded = FALSE;
713 		}
714 		if (gtk_widget_get_window (pad_widget))
715 			gdk_window_thaw_updates (gtk_widget_get_window (pad_widget));
716 	}
717 }
718 
719 void
xpad_pad_notify_has_selection(XpadPad * pad)720 xpad_pad_notify_has_selection (XpadPad *pad)
721 {
722 	g_return_if_fail (pad);
723 
724 	GtkSourceBuffer *buffer;
725 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
726 	gboolean has_selection = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
727 
728 	XpadToolbar *toolbar = XPAD_TOOLBAR (pad->priv->toolbar);
729 	if (toolbar == NULL)
730 		return;
731 
732 	xpad_toolbar_enable_cut_button (toolbar, has_selection);
733 	xpad_toolbar_enable_copy_button (toolbar, has_selection);
734 }
735 
736 void
xpad_pad_notify_clipboard_owner_changed(XpadPad * pad)737 xpad_pad_notify_clipboard_owner_changed (XpadPad *pad)
738 {
739 	g_return_if_fail (pad);
740 
741 	/* safe cast to toolbar */
742 	if (XPAD_IS_TOOLBAR (pad->priv->toolbar)) {
743 		XpadToolbar *toolbar = XPAD_TOOLBAR (pad->priv->toolbar);
744 		g_return_if_fail (toolbar);
745 
746 		GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
747 		xpad_toolbar_enable_paste_button (toolbar, gtk_clipboard_wait_is_text_available (clipboard));
748 	}
749 }
750 
751 void
xpad_pad_notify_undo_redo_changed(XpadPad * pad)752 xpad_pad_notify_undo_redo_changed (XpadPad *pad)
753 {
754 	g_return_if_fail (pad);
755 
756 	XpadTextBuffer *buffer = NULL;
757 	buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
758 	g_return_if_fail (buffer);
759 
760 	XpadToolbar *toolbar = NULL;
761 	toolbar = XPAD_TOOLBAR (pad->priv->toolbar);
762 	g_return_if_fail (toolbar);
763 
764 	xpad_toolbar_enable_undo_button (toolbar, xpad_text_buffer_undo_available (buffer));
765 	xpad_toolbar_enable_redo_button (toolbar, xpad_text_buffer_redo_available (buffer));
766 }
767 
768 static gboolean
xpad_pad_enter_notify_event(GtkWidget * pad,GdkEventCrossing * event)769 xpad_pad_enter_notify_event (GtkWidget *pad, GdkEventCrossing *event)
770 {
771 	gboolean has_toolbar, autohide_toolbar;
772 	g_object_get (XPAD_PAD (pad)->priv->settings, "has-toolbar", &has_toolbar, "autohide-toolbar", &autohide_toolbar, NULL);
773 
774 	if (has_toolbar && autohide_toolbar &&
775 		 event->detail != GDK_NOTIFY_INFERIOR &&
776 		 event->mode == GDK_CROSSING_NORMAL)
777 	{
778 		XPAD_PAD (pad)->priv->toolbar_timeout = 0;
779 		xpad_pad_show_toolbar (XPAD_PAD (pad));
780 	}
781 
782 	return FALSE;
783 }
784 
785 static gboolean
xpad_pad_leave_notify_event(GtkWidget * pad,GdkEventCrossing * event)786 xpad_pad_leave_notify_event (GtkWidget *pad, GdkEventCrossing *event)
787 {
788 	gboolean has_toolbar, autohide_toolbar;
789 	g_object_get (XPAD_PAD (pad)->priv->settings, "has-toolbar", &has_toolbar, "autohide-toolbar", &autohide_toolbar, NULL);
790 
791 	if (has_toolbar && autohide_toolbar &&
792 		 event->detail != GDK_NOTIFY_INFERIOR &&
793 		 event->mode == GDK_CROSSING_NORMAL)
794 	{
795 		if (!XPAD_PAD (pad)->priv->toolbar_timeout)
796 			XPAD_PAD (pad)->priv->toolbar_timeout = g_timeout_add (1000, (GSourceFunc) toolbar_timeout, pad);
797 	}
798 
799 	return FALSE;
800 }
801 
802 static void
xpad_pad_spawn(XpadPad * pad)803 xpad_pad_spawn (XpadPad *pad)
804 {
805 	GtkWidget *newpad = xpad_pad_new (pad->priv->group, pad->priv->settings);
806 	gtk_widget_show (newpad);
807 }
808 
809 static void
xpad_pad_clear(XpadPad * pad)810 xpad_pad_clear (XpadPad *pad)
811 {
812 	GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview));
813 
814 	GtkTextIter start, end;
815 	gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
816 
817 	gtk_text_buffer_begin_user_action (buffer);
818 	gtk_text_buffer_delete (buffer, &start, &end);
819 	gtk_text_buffer_end_user_action (buffer);
820 }
821 
822 void
xpad_pad_close(XpadPad * pad)823 xpad_pad_close (XpadPad *pad)
824 {
825 	gtk_widget_hide (GTK_WIDGET (pad));
826 
827 	/*
828 	 * If no tray and this is the last pad, we don't want to record this
829 	 * pad as closed, we want to start with just this pad next open.  So
830 	 * quit before we record.
831 	 */
832 	if (!xpad_tray_is_open () &&
833 		 xpad_pad_group_num_visible_pads (pad->priv->group) == 0)
834 	{
835 		xpad_app_quit ();
836 		return;
837 	}
838 
839 	if (pad->priv->properties)
840 		gtk_widget_destroy (pad->priv->properties);
841 
842 	xpad_pad_save_info_delayed (pad);
843 
844 	g_signal_emit (pad, signals[CLOSED], 0);
845 }
846 
847 void
xpad_pad_toggle(XpadPad * pad)848 xpad_pad_toggle(XpadPad *pad)
849 {
850 	 if (gtk_widget_get_visible (GTK_WIDGET(pad)))
851 		xpad_pad_close (pad);
852 	 else
853 		gtk_widget_show (GTK_WIDGET (pad));
854 }
855 
856 static gboolean
should_confirm_delete(XpadPad * pad)857 should_confirm_delete (XpadPad *pad)
858 {
859 	GtkSourceBuffer *buffer;
860 	GtkTextIter s, e;
861 	gchar *content;
862 	gboolean confirm;
863 
864 	g_object_get (pad->priv->settings, "confirm-destroy", &confirm, NULL);
865 	if (!confirm)
866 		return FALSE;
867 
868 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
869 	gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &s, &e);
870 	content = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &s, &e, FALSE);
871 
872 	confirm = strcmp (g_strstrip (content), "") != 0;
873 
874 	g_free (content);
875 
876 	return confirm;
877 }
878 
879 static void
xpad_pad_delete(XpadPad * pad)880 xpad_pad_delete (XpadPad *pad)
881 {
882 	g_return_if_fail (pad);
883 
884 	/* With the delayed saving functionality, it is necessary to clear the unsaved flags to prevent usage of non-existing object information. */
885 	pad->priv->unsaved_info = FALSE;
886 	pad->priv->unsaved_content = FALSE;
887 
888 	if (should_confirm_delete (pad))
889 	{
890 		GtkWidget *dialog;
891 		gint response;
892 
893 		dialog = xpad_app_alert_dialog (GTK_WINDOW (pad), "dialog-warning", _("Delete this pad?"), _("All text of this pad will be irrevocably lost."));
894 
895 		if (!dialog)
896 			return;
897 
898 		gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Delete"), GTK_RESPONSE_ACCEPT, _("_Cancel"), GTK_RESPONSE_REJECT, NULL);
899 		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT);
900 
901 		response = gtk_dialog_run (GTK_DIALOG (dialog));
902 
903 		gtk_widget_destroy (dialog);
904 
905 		if (response != GTK_RESPONSE_ACCEPT)
906 			return;
907 	}
908 
909 	/* These two if statements actually erase the pad on the harddisk. */
910 	if (pad->priv->infoname)
911 		fio_remove_file (pad->priv->infoname);
912 	if (pad->priv->contentname)
913 		fio_remove_file (pad->priv->contentname);
914 
915 	/*
916 	   This behavior used to be handy for debugging purposes, to create (CTRL+N) and delete (CTRL+DELETE)
917 	   pads in a rapid way. However the behavior is unexpected to the user, so it has been disabled by
918 	   commenting out the code.
919 	*/
920 
921 	/* Before deleting the current pad, find and set the focus to another pad (if any) */
922 	/*
923 	GSList *nextPad = g_slist_nth (xpad_pad_group_get_pads(pad->priv->group), 0);
924 	if (nextPad->data == pad)
925 		nextPad = g_slist_next (nextPad);
926 	if (nextPad)
927         	xpad_pad_show (nextPad->data);
928 	*/
929 
930 	/* Remove the pad from the group and destroy it. */
931 	gtk_widget_destroy (GTK_WIDGET (pad));
932 }
933 
934 static void
pad_properties_sync_title(XpadPad * pad)935 pad_properties_sync_title (XpadPad *pad)
936 {
937 	gchar *title;
938 
939 	if (!pad->priv->properties)
940 		return;
941 
942 	title = g_strdup_printf (_("'%s' Layout"), gtk_window_get_title (GTK_WINDOW (pad)));
943 	gtk_window_set_title (GTK_WINDOW (pad->priv->properties), title);
944 	g_free (title);
945 }
946 
947 static void
pad_properties_destroyed(XpadPad * pad)948 pad_properties_destroyed (XpadPad *pad)
949 {
950 	if (!pad->priv->properties)
951 		return;
952 
953 	g_signal_handlers_disconnect_by_func (pad, (gpointer) pad_properties_sync_title, NULL);
954 	pad->priv->properties = NULL;
955 }
956 
957 static void
prop_notify_font(XpadPad * pad)958 prop_notify_font (XpadPad *pad)
959 {
960 	XpadPadProperties *prop = XPAD_PAD_PROPERTIES (pad->priv->properties);
961 
962 	gboolean follow_font_style;
963 	g_object_get (prop, "follow-font-style", &follow_font_style, NULL);
964 	g_object_set (XPAD_TEXT_VIEW (pad->priv->textview), "follow-font-style", follow_font_style, NULL);
965 
966 	if (!follow_font_style)
967 	{
968 		const gchar *font;
969 		g_object_get (prop, "fontname", &font, NULL);
970 
971 		PangoFontDescription *fontdesc;
972 		fontdesc = font ? pango_font_description_from_string (font) : NULL;
973 		xpad_text_view_set_font (pad->priv->textview, fontdesc);
974 		if (fontdesc)
975 			pango_font_description_free (fontdesc);
976 	}
977 
978 	xpad_pad_save_info_delayed (pad);
979 }
980 
981 static void
prop_notify_colors(XpadPad * pad)982 prop_notify_colors (XpadPad *pad)
983 {
984 	XpadPadProperties *prop = XPAD_PAD_PROPERTIES (pad->priv->properties);
985 
986 	gboolean follow_color_style;
987 	const GdkRGBA *text_color, *back_color;
988 
989 	g_object_get (prop, "follow-color-style", &follow_color_style, NULL);
990 	g_object_set (XPAD_TEXT_VIEW (pad->priv->textview), "follow-color-style", follow_color_style, NULL);
991 
992 	if (follow_color_style) {
993 		/* Set the colors to the global preferences colors */
994 		g_object_get (pad->priv->settings, "text-color", &text_color, "back-color", &back_color, NULL);
995 	} else {
996 		/* Set the color to the individual pad properties colors */
997 		g_object_get (prop, "text-color", &text_color, "back-color", &back_color, NULL);
998 	}
999 
1000 	xpad_text_view_set_colors (pad->priv->textview, text_color, back_color);
1001 
1002 	xpad_pad_save_info_delayed (pad);
1003 }
1004 
1005 static void
xpad_pad_open_properties(XpadPad * pad)1006 xpad_pad_open_properties (XpadPad *pad)
1007 {
1008 	gboolean follow_font_style, follow_color_style;
1009 	GtkStyleContext *style = NULL;
1010 	PangoFontDescription *font;
1011 	GdkRGBA widget_text_color = {0, 0, 0, 0};
1012 	GdkRGBA widget_background_color = {0, 0, 0, 0};
1013 
1014 	if (pad->priv->properties)
1015 	{
1016 		gtk_window_present (GTK_WINDOW (pad->priv->properties));
1017 		return;
1018 	}
1019 
1020 	pad->priv->properties = xpad_pad_properties_new ();
1021 
1022 	gtk_window_set_transient_for (GTK_WINDOW (pad->priv->properties), GTK_WINDOW (pad));
1023 	gtk_window_set_resizable (GTK_WINDOW (pad->priv->properties), FALSE);
1024 
1025 	g_signal_connect_swapped (pad->priv->properties, "destroy", G_CALLBACK (pad_properties_destroyed), pad);
1026 	g_signal_connect (pad, "notify::title", G_CALLBACK (pad_properties_sync_title), NULL);
1027 
1028 	style = gtk_widget_get_style_context (pad->priv->textview);
1029 	gtk_style_context_get(style, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_FONT, &font, NULL);
1030 	gtk_style_context_get_color (style, GTK_STATE_FLAG_NORMAL, &widget_text_color);
1031 	get_background_color (style, GTK_STATE_FLAG_NORMAL, &widget_background_color);
1032 
1033 	g_object_get (XPAD_TEXT_VIEW (pad->priv->textview), "follow-font-style", &follow_font_style, "follow-color-style", &follow_color_style, NULL);
1034 	g_object_set (G_OBJECT (pad->priv->properties),
1035 		"follow-font-style", follow_font_style,
1036 		"follow-color-style", follow_color_style,
1037 		"text-color", &widget_text_color,
1038 		"back-color", &widget_background_color,
1039 		"fontname", pango_font_description_to_string(font),
1040 		NULL);
1041 	pango_font_description_free (font);
1042 
1043 	g_signal_connect_swapped (pad->priv->properties, "notify::follow-font-style", G_CALLBACK (prop_notify_font), pad);
1044 	g_signal_connect_swapped (pad->priv->properties, "notify::follow-color-style", G_CALLBACK (prop_notify_colors), pad);
1045 	g_signal_connect_swapped (pad->priv->properties, "notify::text-color", G_CALLBACK (prop_notify_colors), pad);
1046 	g_signal_connect_swapped (pad->priv->properties, "notify::back-color", G_CALLBACK (prop_notify_colors), pad);
1047 	g_signal_connect_swapped (pad->priv->properties, "notify::fontname", G_CALLBACK (prop_notify_font), pad);
1048 
1049 	pad_properties_sync_title (pad);
1050 
1051 	gtk_widget_show (pad->priv->properties);
1052 }
1053 
1054 static void
xpad_pad_open_preferences(XpadPad * pad)1055 xpad_pad_open_preferences (XpadPad *pad)
1056 {
1057 	xpad_preferences_open (pad->priv->settings);
1058 }
1059 
1060 static void
xpad_pad_text_changed(XpadPad * pad,GtkSourceBuffer * buffer)1061 xpad_pad_text_changed (XpadPad *pad, GtkSourceBuffer *buffer)
1062 {
1063 	/* A dirty way to silence the compiler for these unused variables. */
1064 	(void) buffer;
1065 
1066 	/* set title */
1067 	xpad_pad_sync_title (pad);
1068 
1069 	/* record change */
1070 	xpad_pad_save_content_delayed(pad);
1071 }
1072 
1073 static gboolean
xpad_pad_toolbar_size_allocate(XpadPad * pad,GtkAllocation * event)1074 xpad_pad_toolbar_size_allocate (XpadPad *pad, GtkAllocation *event)
1075 {
1076 	/* safe cast from gint to guint */
1077 	if (event->height >= 0) {
1078 		pad->priv->toolbar_height = (guint) event->height;
1079 	} else {
1080 		g_warning("There is a problem in the program Xpad. In function 'xpad_pad_toolbar_size_allocate' the variable 'event->height' is not a positive number. Please send a bugreport to https://bugs.launchpad.net/xpad/+filebug to help improve Xpad.");
1081 		pad->priv->toolbar_height = 0;
1082 	}
1083 
1084 	return FALSE;
1085 }
1086 
1087 static gboolean
xpad_pad_configure_event(XpadPad * pad,GdkEventConfigure * event)1088 xpad_pad_configure_event (XpadPad *pad, GdkEventConfigure *event)
1089 {
1090 	if (!gtk_widget_get_visible (GTK_WIDGET (pad)))
1091 		return FALSE;
1092 
1093 	int eWidth = event->width;
1094 	int eHeight = event->height;
1095 
1096 	/* safe cast from gint to guint */
1097 	if (eWidth >= 0 && eHeight >=0 ) {
1098 		/* If the width or height has changed, save it. */
1099 		if (pad->priv->width != (guint) eWidth || pad->priv->height != (guint) eHeight) {
1100 			pad->priv->toolbar_pad_resized = TRUE;
1101 			pad->priv->width = (guint) eWidth;
1102 			pad->priv->height = (guint) eHeight;
1103 			xpad_pad_save_info_delayed(pad);
1104 		}
1105 	}
1106 
1107 	/* If the location of the pad has changed, save it. */
1108 	if (pad->priv->x != event->x || pad->priv->y != event->y) {
1109 		pad->priv->x = event->x;
1110 		pad->priv->y = event->y;
1111 		pad->priv->location_valid = TRUE;
1112 		xpad_pad_save_info_delayed(pad);
1113 	}
1114 
1115 	/*
1116 	 * Sometimes when moving, if the toolbar tries to hide itself,
1117 	 * the window manager will not resize it correctly.  So, we make
1118 	 * sure not to end the timeout while moving.
1119 	 */
1120 	if (pad->priv->toolbar_timeout) {
1121 		g_source_remove (pad->priv->toolbar_timeout);
1122 		pad->priv->toolbar_timeout = g_timeout_add (1000, (GSourceFunc) toolbar_timeout, pad);
1123 	}
1124 
1125 	return FALSE;
1126 }
1127 
1128 static gboolean
xpad_pad_delete_event(XpadPad * pad,GdkEvent * event)1129 xpad_pad_delete_event (XpadPad *pad, GdkEvent *event)
1130 {
1131 	/* A dirty way to silence the compiler for these unused variables. */
1132 	(void) event;
1133 
1134 	xpad_pad_close (pad);
1135 
1136 	return TRUE;
1137 }
1138 
1139 static gboolean
xpad_pad_popup_menu(XpadPad * pad)1140 xpad_pad_popup_menu (XpadPad *pad)
1141 {
1142 	xpad_pad_popup (pad, NULL);
1143 
1144 	return TRUE;
1145 }
1146 
1147 static gboolean
xpad_pad_button_press_event(XpadPad * pad,GdkEventButton * event)1148 xpad_pad_button_press_event (XpadPad *pad, GdkEventButton *event)
1149 {
1150 	if (event->type == GDK_BUTTON_PRESS)
1151 	{
1152 		switch (event->button)
1153 		{
1154 		case 1:
1155 			if ((event->state & gtk_accelerator_get_default_mod_mask ()) == GDK_CONTROL_MASK)
1156 			{
1157 				gtk_window_begin_move_drag (GTK_WINDOW (pad), (gint) event->button, (gint) event->x_root, (gint) event->y_root, event->time);
1158 				return TRUE;
1159 			}
1160 			break;
1161 
1162 		case 3:
1163 			if ((event->state & gtk_accelerator_get_default_mod_mask ()) == GDK_CONTROL_MASK)
1164 			{
1165 				GdkWindowEdge edge;
1166 
1167 				if (gtk_widget_get_direction (GTK_WIDGET (pad)) == GTK_TEXT_DIR_LTR)
1168 					edge = GDK_WINDOW_EDGE_SOUTH_EAST;
1169 				else
1170 					edge = GDK_WINDOW_EDGE_SOUTH_WEST;
1171 
1172 				gtk_window_begin_resize_drag (GTK_WINDOW (pad), edge, (gint) event->button, (gint) event->x_root, (gint) event->y_root, event->time);
1173 			}
1174 			else
1175 			{
1176 				xpad_pad_popup (pad, event);
1177 			}
1178 			return TRUE;
1179 		}
1180 	}
1181 
1182 	return FALSE;
1183 }
1184 
1185 static void
xpad_pad_sync_title(XpadPad * pad)1186 xpad_pad_sync_title (XpadPad *pad)
1187 {
1188 	GtkSourceBuffer *buffer;
1189 	GtkTextIter s, e;
1190 	gchar *content, *end;
1191 
1192 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1193 	gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &s, &e);
1194 	content = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &s, &e, FALSE);
1195 	end = g_utf8_strchr (content, -1, '\n');
1196 	if (end)
1197 		*end = '\0';
1198 
1199 	gtk_window_set_title (GTK_WINDOW (pad), g_strstrip (content));
1200 
1201 	g_free (content);
1202 }
1203 
1204 void
xpad_pad_load_content(XpadPad * pad)1205 xpad_pad_load_content (XpadPad *pad)
1206 {
1207 	g_return_if_fail (pad);
1208 	g_return_if_fail (pad->priv->contentname);
1209 
1210 	gchar *content;
1211 	GtkSourceBuffer *buffer;
1212 
1213 	content = fio_get_file (pad->priv->contentname, CONFIG_DIR);
1214 
1215 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1216 
1217 	xpad_text_buffer_freeze_undo (XPAD_TEXT_BUFFER (buffer));
1218 
1219 	g_signal_handlers_block_by_func (buffer, xpad_pad_text_changed, pad);
1220 	xpad_text_buffer_set_text_with_tags (XPAD_TEXT_BUFFER (buffer), content ? content : "");
1221 	g_signal_handlers_unblock_by_func (buffer, xpad_pad_text_changed, pad);
1222 
1223 	g_free (content);
1224 	xpad_text_buffer_thaw_undo (XPAD_TEXT_BUFFER (buffer));
1225 
1226 	xpad_pad_sync_title (pad);
1227 	pad->priv->unsaved_content = FALSE;
1228 }
1229 
1230 void
xpad_pad_save_content(XpadPad * pad)1231 xpad_pad_save_content (XpadPad *pad)
1232 {
1233 	g_return_if_fail (pad);
1234 
1235 	gchar *content = NULL;
1236 	XpadTextBuffer *buffer;
1237 
1238 	if (!pad->priv->unsaved_content) {
1239 		return;
1240 	}
1241 
1242 	/* create content file if it doesn't exist yet */
1243 	if (!pad->priv->contentname)
1244 	{
1245 		pad->priv->contentname = fio_unique_name ("content-");
1246 		if (!pad->priv->contentname)
1247 			return;
1248 	}
1249 
1250 	if (GTK_IS_TEXT_VIEW(GTK_TEXT_VIEW (pad->priv->textview))) {
1251 		buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1252 		content = xpad_text_buffer_get_text_with_tags (buffer);
1253 	} else {
1254 		g_warning("There is a problem in the program Xpad. In function 'xpad_pad_save_content' the variable 'pad->priv->textview' is not of type textview. Please send a bugreport to https://bugs.launchpad.net/xpad/+filebug to help improve Xpad.");
1255 	}
1256 
1257 	fio_set_file (pad->priv->contentname, content);
1258 
1259 	pad->priv->unsaved_content = FALSE;
1260 	g_free (content);
1261 }
1262 
1263 /* Extract all the metadata of a single pad from its info-xxxxx file and store it in the pad object */
1264 static void
xpad_pad_load_info(XpadPad * pad,gboolean * show)1265 xpad_pad_load_info (XpadPad *pad, gboolean *show)
1266 {
1267 	gboolean locked = FALSE, follow_font = TRUE, follow_color = TRUE, hidden = FALSE;
1268 	gboolean has_toolbar, autohide_toolbar;
1269 	gchar *fontname = NULL, *text_color_string = NULL, *background_color_string = NULL;
1270 	GdkRGBA text_color = {0, 0, 0, 0}, back_color = {0, 0, 0, 0};
1271 
1272 	if (!pad->priv->infoname)
1273 		return;
1274 
1275 	if (fio_get_values_from_file (pad->priv->infoname,
1276 		"i|width", &pad->priv->width,
1277 		"i|height", &pad->priv->height,
1278 		"i|x", &pad->priv->x,
1279 		"i|y", &pad->priv->y,
1280 		"b|locked", &locked,
1281 		"b|follow_font", &follow_font,
1282 		"b|follow_color", &follow_color,
1283 		"b|sticky", &pad->priv->sticky,
1284 		"b|hidden", &hidden,
1285 		"s|back", &background_color_string,
1286 		"s|text", &text_color_string,
1287 		"s|fontname", &fontname,
1288 		"s|content", &pad->priv->contentname,
1289 		NULL))
1290 		return;
1291 
1292 	pad->priv->unsaved_info = FALSE;
1293 	pad->priv->location_valid = TRUE;
1294 
1295 	g_object_get (pad->priv->settings, "has-toolbar", &has_toolbar, "autohide-toolbar", &autohide_toolbar, NULL);
1296 
1297 	if (has_toolbar && !autohide_toolbar)
1298 	{
1299 		pad->priv->toolbar_height = 0;
1300 		xpad_pad_hide_toolbar (pad);
1301 		xpad_pad_show_toolbar (pad); /* these will resize pad at correct height */
1302 	}
1303 	else
1304 		gtk_window_resize (GTK_WINDOW (pad), (gint) pad->priv->width, (gint) pad->priv->height);
1305 	gtk_window_move (GTK_WINDOW (pad), pad->priv->x, pad->priv->y);
1306 
1307 	g_object_set (XPAD_TEXT_VIEW (pad->priv->textview), "follow-font-style", follow_font, "follow-color-style", follow_color, NULL);
1308 
1309 	if (locked) {
1310 		g_object_set (XPAD_TEXT_VIEW (pad->priv->textview), "follow-font-style", FALSE, "follow-color-style", FALSE, NULL);
1311 	}
1312 
1313 	if (!follow_font)
1314 	{
1315 		PangoFontDescription *font_desc = pango_font_description_from_string (fontname);
1316 		xpad_text_view_set_font(pad->priv->textview, font_desc);
1317 		pango_font_description_free (font_desc);
1318 	}
1319 
1320 	if (!follow_color)
1321 	{
1322 		/*
1323 		 * If, for some reason, one of the colors could not be retrieved
1324 		 * (for example due to the migration to the new GdkRGBA colors),
1325 		 * set the color to the default.
1326 		 */
1327 		if (text_color_string == NULL || background_color_string == NULL) {
1328 			text_color = (GdkRGBA) {0, 0, 0, 1};
1329 			back_color = (GdkRGBA) {1, 0.933334350586, 0.6, 1};
1330 		}
1331 		else {
1332 			/* If, for some reason, the parsing of the colors fail, set the color to the default. */
1333 			if (!gdk_rgba_parse (&text_color, text_color_string) || !gdk_rgba_parse (&back_color, background_color_string)) {
1334 				text_color = (GdkRGBA) {0, 0, 0, 1};
1335 				back_color = (GdkRGBA) {1, 0.933334350586, 0.6, 1};
1336 			}
1337 		}
1338 
1339 		/* Set the text and background color for this pad, as stated in its properties file. */
1340 		xpad_text_view_set_colors (pad->priv->textview, &text_color, &back_color);
1341 	}
1342 
1343 	xpad_pad_stick_unstick (pad);
1344 
1345 	if (show)
1346 		*show = !hidden;
1347 
1348 	g_free(fontname);
1349 }
1350 
1351 void
xpad_pad_save_info(XpadPad * pad)1352 xpad_pad_save_info (XpadPad *pad)
1353 {
1354 	gboolean follow_font_style, follow_color_style;
1355 	guint height = 0;
1356 	GtkStyleContext *style = NULL;
1357 	PangoFontDescription *font = NULL;
1358 	GdkRGBA text_color = {0, 0, 0, 0}, back_color = {0, 0, 0, 0};
1359 
1360 	g_return_if_fail (pad);
1361 
1362 	if (!pad->priv->unsaved_info)
1363 		return;
1364 
1365 	/* Must create pad info file if it doesn't exist yet */
1366 	if (!pad->priv->infoname)
1367 	{
1368 		pad->priv->infoname = fio_unique_name ("info-");
1369 		if (!pad->priv->infoname)
1370 			return;
1371 		gtk_window_set_role (GTK_WINDOW (pad), pad->priv->infoname);
1372 	}
1373 
1374 	/* create content file if it doesn't exist yet */
1375 	if (!pad->priv->contentname)
1376 	{
1377 		pad->priv->contentname = fio_unique_name ("content-");
1378 		if (!pad->priv->contentname)
1379 			return;
1380 	}
1381 
1382 	height = pad->priv->height;
1383 	if (gtk_widget_get_visible (pad->priv->toolbar) && pad->priv->toolbar_expanded)
1384 		height -= pad->priv->toolbar_height;
1385 
1386 	style = gtk_widget_get_style_context (pad->priv->textview);
1387 	gtk_style_context_get (style, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_FONT, &font, NULL);
1388 	gtk_style_context_get_color (style, GTK_STATE_FLAG_NORMAL, &text_color);
1389 	get_background_color (style, GTK_STATE_FLAG_NORMAL, &back_color);
1390 
1391 	g_object_get (XPAD_TEXT_VIEW (pad->priv->textview), "follow-font-style", &follow_font_style, "follow-color-style", &follow_color_style, NULL);
1392 
1393 	fio_set_values_to_file (pad->priv->infoname,
1394 		"i|width", pad->priv->width,
1395 		"i|height", height,
1396 		"i|x", pad->priv->x,
1397 		"i|y", pad->priv->y,
1398 		"b|follow_font", follow_font_style,
1399 		"b|follow_color", follow_color_style,
1400 		"b|sticky", pad->priv->sticky,
1401 		"b|hidden", !gtk_widget_get_visible (GTK_WIDGET(pad)),
1402 		"s|back", gdk_rgba_to_string (&back_color),
1403 		"s|text", gdk_rgba_to_string (&text_color),
1404 		"s|fontname", pango_font_description_to_string (font),
1405 		"s|content", pad->priv->contentname,
1406 		NULL);
1407 
1408 	pango_font_description_free (font);
1409 	pad->priv->unsaved_info = FALSE;
1410 }
1411 
1412 static void
menu_about(XpadPad * pad)1413 menu_about (XpadPad *pad)
1414 {
1415 	const gchar *artists[] = {"Michael Terry <mike@mterry.name>", NULL};
1416 	const gchar *authors[] = {"Arthur Borsboom <arthurborsboom@gmail.com>", "Jeroen Vermeulen <jtv@xs4all.nl>", "Michael Terry <mike@mterry.name>", "Paul Ivanov <pivanov@berkeley.edu>", "Sachin Raut <great.sachin@gmail.com>", NULL};
1417 	const gchar *comments = _("Sticky notes");
1418 	const gchar *copyright = "© 2001-2014 Michael Terry";
1419 	/* Translators: please translate this as your own name and optionally email
1420 		like so: "Your Name <your@email.com>" */
1421 	const gchar *translator_credits = _("translator-credits");
1422 	const gchar *website = "https://launchpad.net/xpad";
1423 
1424 	gtk_show_about_dialog (GTK_WINDOW (pad),
1425 		"artists", artists,
1426 		"authors", authors,
1427 		"comments", comments,
1428 		"copyright", copyright,
1429 		"license-type", GTK_LICENSE_GPL_3_0,
1430 		"logo-icon-name", PACKAGE,
1431 		"translator-credits", translator_credits,
1432 		"version", VERSION,
1433 		"website", website,
1434 		NULL);
1435 }
1436 
1437 static void
xpad_pad_cut(XpadPad * pad)1438 xpad_pad_cut (XpadPad *pad)
1439 {
1440 	gtk_text_buffer_cut_clipboard (
1441 		gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)),
1442 		gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
1443 		TRUE);
1444 }
1445 
1446 static void
xpad_pad_copy(XpadPad * pad)1447 xpad_pad_copy (XpadPad *pad)
1448 {
1449 	gtk_text_buffer_copy_clipboard (
1450 		gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)),
1451 		gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1452 }
1453 
1454 static void
xpad_pad_paste(XpadPad * pad)1455 xpad_pad_paste (XpadPad *pad)
1456 {
1457 	gtk_text_buffer_paste_clipboard (
1458 		gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)),
1459 		gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
1460 		NULL,
1461 		TRUE);
1462 }
1463 
1464 static void
xpad_pad_undo(XpadPad * pad)1465 xpad_pad_undo (XpadPad *pad)
1466 {
1467 	g_return_if_fail (pad->priv->textview);
1468 	XpadTextBuffer *buffer = NULL;
1469 	buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1470 	g_return_if_fail (buffer);
1471 	xpad_text_buffer_undo (buffer);
1472 }
1473 
1474 static void
xpad_pad_redo(XpadPad * pad)1475 xpad_pad_redo (XpadPad *pad)
1476 {
1477 	g_return_if_fail (pad->priv->textview);
1478 	XpadTextBuffer *buffer = NULL;
1479 	buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1480 	g_return_if_fail (buffer);
1481 	xpad_text_buffer_redo (buffer);
1482 }
1483 
1484 static void
xpad_pad_show_all(XpadPad * pad)1485 xpad_pad_show_all (XpadPad *pad)
1486 {
1487 	xpad_pad_group_show_all (pad->priv->group);
1488 }
1489 
1490 static void
xpad_pad_close_all(XpadPad * pad)1491 xpad_pad_close_all (XpadPad *pad)
1492 {
1493 	if (!pad->priv->group)
1494 		return;
1495 
1496 	/*
1497 	 * The logic is different here depending on whether the tray is open.
1498 	 * If it is open, we just close each pad individually.  If it isn't
1499 	 * open, we do a quit.  This way, when xpad is run again, only the
1500 	 * pads open during the last 'close all' will open again.
1501 	 */
1502 	if (xpad_tray_is_open ())
1503 		xpad_pad_group_close_all (pad->priv->group);
1504 	else
1505 		xpad_app_quit ();
1506 }
1507 
1508 static void
menu_toggle_tag(XpadPad * pad,const gchar * name)1509 menu_toggle_tag (XpadPad *pad, const gchar *name)
1510 {
1511 	g_return_if_fail (pad->priv->textview);
1512 	XpadTextBuffer *buffer = NULL;
1513 	buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1514 	xpad_text_buffer_toggle_tag (buffer, name);
1515 	xpad_pad_save_content_delayed(pad);
1516 }
1517 
1518 static void
menu_bold(XpadPad * pad)1519 menu_bold (XpadPad *pad)
1520 {
1521 	menu_toggle_tag (pad, "bold");
1522 }
1523 
1524 static void
menu_italic(XpadPad * pad)1525 menu_italic (XpadPad *pad)
1526 {
1527 	menu_toggle_tag (pad, "italic");
1528 }
1529 
1530 static void
menu_underline(XpadPad * pad)1531 menu_underline (XpadPad *pad)
1532 {
1533 	menu_toggle_tag (pad, "underline");
1534 }
1535 
1536 static void
menu_strikethrough(XpadPad * pad)1537 menu_strikethrough (XpadPad *pad)
1538 {
1539 	menu_toggle_tag (pad, "strikethrough");
1540 }
1541 
1542 static gint
menu_title_compare(GtkWindow * a,GtkWindow * b)1543 menu_title_compare (GtkWindow *a, GtkWindow *b)
1544 {
1545 	gchar *title_a = g_utf8_casefold (gtk_window_get_title (a), -1);
1546 	gchar *title_b = g_utf8_casefold (gtk_window_get_title (b), -1);
1547 
1548 	gint rv = g_utf8_collate (title_a, title_b);
1549 
1550 	g_free (title_a);
1551 	g_free (title_b);
1552 
1553 	return rv;
1554 }
1555 
1556 /* FIXME: Accelerators are working but not visible for menu items with an image (icon). */
1557 #define MENU_ADD(mnemonic, image, key, mask, callback) {\
1558 	if (image) {\
1559 		item = gtk_menu_item_new ();\
1560 		GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);\
1561 		gtk_container_add (GTK_CONTAINER (hbox), gtk_image_new_from_icon_name (image, GTK_ICON_SIZE_MENU));\
1562 		gtk_container_add (GTK_CONTAINER (hbox), gtk_label_new_with_mnemonic (mnemonic));\
1563 		gtk_container_add (GTK_CONTAINER (item), hbox);\
1564 	}\
1565 	else\
1566 		item = gtk_menu_item_new_with_mnemonic (mnemonic);\
1567 	g_signal_connect_swapped (item, "activate", G_CALLBACK (callback), pad);\
1568 	if (key)\
1569 		gtk_widget_add_accelerator (item, "activate", accel_group, key, mask, GTK_ACCEL_VISIBLE);\
1570 	gtk_container_add (GTK_CONTAINER (menu), item);\
1571 	}
1572 
1573 #define MENU_ADD_CHECK(mnemonic, active, callback) {\
1574 	item = gtk_check_menu_item_new_with_mnemonic (mnemonic);\
1575 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active);\
1576 	g_signal_connect (item, "toggled", G_CALLBACK (callback), pad);\
1577 	gtk_container_add (GTK_CONTAINER (menu), item);\
1578 	}
1579 
1580 #define MENU_ADD_SEP() {\
1581 	item = gtk_separator_menu_item_new ();\
1582 	gtk_container_add (GTK_CONTAINER (menu), item);\
1583 	}
1584 
1585 static GtkWidget *
menu_get_popup_no_highlight(XpadPad * pad,GtkAccelGroup * accel_group)1586 menu_get_popup_no_highlight (XpadPad *pad, GtkAccelGroup *accel_group)
1587 {
1588 	GtkWidget *uppermenu, *menu, *item;
1589 
1590 	/* Upper menu */
1591 	uppermenu = gtk_menu_new ();
1592 	gtk_menu_set_accel_group (GTK_MENU (uppermenu), accel_group);
1593 	menu = uppermenu;
1594 	MENU_ADD (_("_New"), "document-new", GDK_KEY_N, GDK_CONTROL_MASK, xpad_pad_spawn);
1595 	MENU_ADD (_("_Delete"), "edit-delete", GDK_KEY_Delete, GDK_SHIFT_MASK, xpad_pad_delete);
1596 	MENU_ADD (_("_Reload"), "reload-pad-content", GDK_KEY_F5, 0, xpad_pad_load_content);
1597 	MENU_ADD (_("_Close"), "window-close", GDK_KEY_W, GDK_CONTROL_MASK, xpad_pad_close);
1598 
1599 	/* Edit submenu */
1600 	item = gtk_menu_item_new_with_mnemonic (_("_Edit"));
1601 	gtk_container_add (GTK_CONTAINER (uppermenu), item);
1602 	menu = gtk_menu_new ();
1603 	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
1604 	MENU_ADD (_("_Undo"), "edit-undo", GDK_KEY_Z, GDK_CONTROL_MASK, xpad_pad_undo);
1605 	g_object_set_data (G_OBJECT (uppermenu), "undo", item);
1606 	MENU_ADD (_("_Redo"), "edit-redo", GDK_KEY_Y, GDK_CONTROL_MASK, xpad_pad_redo);
1607 	g_object_set_data (G_OBJECT (uppermenu), "redo", item);
1608 	MENU_ADD_SEP();
1609 	MENU_ADD (_("_Paste"), "edit-paste", 0, 0, xpad_pad_paste);
1610 	g_object_set_data (G_OBJECT (uppermenu), "paste", item);
1611 	MENU_ADD_SEP();
1612 	MENU_ADD (_("_Layout"), "document-properties", 0, 0, xpad_pad_open_properties);
1613 
1614 	menu = uppermenu;
1615 	MENU_ADD_SEP();
1616 
1617 	/* Notes submenu - The list of notes will get added in the prep function below */
1618 	item = gtk_menu_item_new_with_mnemonic (_("_Notes"));
1619 	gtk_container_add (GTK_CONTAINER (uppermenu), item);
1620 	menu = gtk_menu_new ();
1621 	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
1622 	g_object_set_data (G_OBJECT (uppermenu), "notes-menu", menu);
1623 	MENU_ADD (_("_Show All"), NULL, 0, 0, xpad_pad_show_all);
1624 	MENU_ADD (_("_Close All"), NULL, 0, 0, xpad_pad_close_all);
1625 
1626 	/* Help submenu */
1627 	item = gtk_menu_item_new_with_mnemonic (_("_Help"));
1628 	gtk_container_add (GTK_CONTAINER (uppermenu), item);
1629 	menu = gtk_menu_new ();
1630 	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
1631 	MENU_ADD (_("_Help"), "help-browser", GDK_KEY_F1, 0, show_help);
1632 	MENU_ADD (_("_About"), "help-about", 0, 0, menu_about);
1633 
1634 	/* Upper menu */
1635 	menu = uppermenu;
1636 	MENU_ADD_SEP ();
1637 	MENU_ADD (_("_Preferences"), "preferences-system", 0, 0, xpad_pad_open_preferences);
1638 
1639 	gtk_widget_show_all (uppermenu);
1640 
1641 	return uppermenu;
1642 }
1643 
1644 static void
menu_prep_popup_no_highlight(XpadPad * pad,GtkWidget * uppermenu)1645 menu_prep_popup_no_highlight (XpadPad *pad, GtkWidget *uppermenu)
1646 {
1647 	GtkWidget *menu, *item;
1648 
1649 	GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1650 
1651 	XpadTextBuffer *buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1652 
1653 	item = g_object_get_data (G_OBJECT (uppermenu), "paste");
1654 	if (item)
1655 		gtk_widget_set_sensitive (item, gtk_clipboard_wait_is_text_available (clipboard));
1656 
1657 	item = g_object_get_data (G_OBJECT (uppermenu), "undo");
1658 	if (item)
1659 		gtk_widget_set_sensitive (item, xpad_text_buffer_undo_available (buffer));
1660 
1661 	item = g_object_get_data (G_OBJECT (uppermenu), "redo");
1662 	if (item)
1663 		gtk_widget_set_sensitive (item, xpad_text_buffer_redo_available (buffer));
1664 
1665 	menu = g_object_get_data (G_OBJECT (uppermenu), "notes-menu");
1666 	if (menu)
1667 	{
1668 		gint n = 1;
1669 		gchar *key;
1670 
1671 		/* Remove old notes */
1672 		item = g_object_get_data (G_OBJECT (menu), "notes-sep");
1673 		while (item)
1674 		{
1675 			gtk_container_remove (GTK_CONTAINER (menu), item);
1676 			key = g_strdup_printf ("notes-%i", n++);
1677 			item = g_object_get_data (G_OBJECT (menu), key);
1678 			g_free (key);
1679 		}
1680 
1681 		MENU_ADD_SEP ();
1682 		g_object_set_data (G_OBJECT (menu), "notes-sep", item);
1683 
1684 		/* Add new notes */
1685 		xpad_pad_append_pad_titles_to_menu (menu);
1686 	}
1687 	gtk_widget_show_all (menu);
1688 }
1689 
xpad_pad_append_pad_titles_to_menu(GtkWidget * menu)1690 void xpad_pad_append_pad_titles_to_menu (GtkWidget *menu)
1691 {
1692 	GSList *pads, *l;
1693 	GtkWidget *item;
1694 	gint n;
1695 
1696 	pads = xpad_pad_group_get_pads (xpad_app_get_pad_group ());
1697 	/* Order pads according to title. */
1698 	pads = g_slist_sort (pads, (GCompareFunc) menu_title_compare);
1699 	/* Populate list of windows. */
1700 	for (l = pads, n = 1; l; l = l->next, n++)
1701 	{
1702 		gchar *title;
1703 		gchar *tmp_title;
1704 		gchar *key;
1705 
1706 		key = g_strdup_printf ("notes-%i", n);
1707 		tmp_title = g_strndup (gtk_window_get_title (GTK_WINDOW (l->data)), 20);
1708 		str_replace_tokens (&tmp_title, '_', "__");
1709 		if (n < 10)
1710 			title = g_strdup_printf ("_%i. %s", n, tmp_title);
1711 		else
1712 			title = g_strdup_printf ("%i. %s", n, tmp_title);
1713 		g_free (tmp_title);
1714 
1715 		item = gtk_menu_item_new_with_mnemonic (title);
1716 		g_signal_connect_swapped (item, "activate", G_CALLBACK (gtk_window_present), l->data);
1717 		gtk_container_add (GTK_CONTAINER (menu), item);
1718 		g_object_set_data (G_OBJECT (menu), key, item);
1719 
1720 		g_free (title);
1721 	}
1722 	g_slist_free (pads);
1723 }
1724 
1725 static GtkWidget *
menu_get_popup_highlight(XpadPad * pad,GtkAccelGroup * accel_group)1726 menu_get_popup_highlight (XpadPad *pad, GtkAccelGroup *accel_group)
1727 {
1728 	GtkWidget *menu, *item;
1729 
1730 	menu = gtk_menu_new ();
1731 	gtk_menu_set_accel_group (GTK_MENU (menu), accel_group);
1732 
1733 	MENU_ADD (_("Cu_t"), "edit-cut", 0, 0, xpad_pad_cut);
1734 	MENU_ADD (_("_Copy"), "edit-copy", 0, 0, xpad_pad_copy);
1735 	MENU_ADD (_("_Paste"), "edit-paste", 0, 0, xpad_pad_paste);
1736 	g_object_set_data (G_OBJECT (menu), "paste", item);
1737 	MENU_ADD_SEP ();
1738 	MENU_ADD (_("_Bold"), "format-text-bold", GDK_KEY_b, GDK_CONTROL_MASK, menu_bold);
1739 	MENU_ADD (_("_Italic"), "format-text-italic", GDK_KEY_i, GDK_CONTROL_MASK, menu_italic);
1740 	MENU_ADD (_("_Underline"), "format-text-underline", GDK_KEY_u, GDK_CONTROL_MASK, menu_underline);
1741 	MENU_ADD (_("_Strikethrough"), "format-text-strikethrough", 0, 0, menu_strikethrough);
1742 
1743 	gtk_widget_show_all (menu);
1744 
1745 	return menu;
1746 }
1747 
1748 static void
menu_prep_popup_highlight(XpadPad * pad,GtkWidget * menu)1749 menu_prep_popup_highlight (XpadPad *pad, GtkWidget *menu)
1750 {
1751 	/* A dirty way to silence the compiler for these unused variables. */
1752 	(void) pad;
1753 
1754 	GtkWidget *item;
1755 	GtkClipboard *clipboard;
1756 
1757 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1758 
1759 	item = g_object_get_data (G_OBJECT (menu), "paste");
1760 	if (item)
1761 		gtk_widget_set_sensitive (item, gtk_clipboard_wait_is_text_available (clipboard));
1762 }
1763 
1764 static void
menu_popup(XpadPad * pad)1765 menu_popup (XpadPad *pad)
1766 {
1767 	g_signal_handlers_block_matched (pad, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, (gpointer) xpad_pad_leave_notify_event, NULL);
1768 	pad->priv->toolbar_timeout = 0;
1769 }
1770 
1771 static void
menu_popdown(XpadPad * pad)1772 menu_popdown (XpadPad *pad)
1773 {
1774 	cairo_rectangle_int_t rect;
1775 
1776 	/* We must check if we disabled off of pad and start the timeout if so. */
1777 	rect.x = 10;
1778 	rect.y = 10;
1779 	rect.width = 1;
1780 	rect.height = 1;
1781 
1782 	if (!pad->priv->toolbar_timeout && !gtk_widget_intersect (GTK_WIDGET (pad), &rect, NULL))
1783 		pad->priv->toolbar_timeout = g_timeout_add (1000, (GSourceFunc) toolbar_timeout, pad);
1784 
1785 	g_signal_handlers_unblock_matched (pad, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, (gpointer) xpad_pad_leave_notify_event, NULL);
1786 }
1787 
1788 static void
xpad_pad_popup(XpadPad * pad,GdkEventButton * event)1789 xpad_pad_popup (XpadPad *pad, GdkEventButton *event)
1790 {
1791 	GtkSourceBuffer *buffer;
1792 	GtkWidget *menu;
1793 
1794 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
1795 
1796 	if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), NULL, NULL))
1797 	{
1798 		menu = pad->priv->highlight_menu;
1799 		menu_prep_popup_highlight (pad, menu);
1800 	}
1801 	else
1802 	{
1803 		menu = pad->priv->menu;
1804 		menu_prep_popup_no_highlight (pad, menu);
1805 	}
1806 
1807 	if (!menu)
1808 		return;
1809 
1810 	menu_popup (pad);
1811 
1812 	if (event) {
1813 		gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent*) event);
1814 	}
1815 }
1816 
1817 /* These functions below are used to reduce the amounts of writes, hence improve the performance. */
xpad_pad_save_content_delayed(XpadPad * pad)1818 void xpad_pad_save_content_delayed (XpadPad *pad)
1819 {
1820 	pad->priv->unsaved_content = TRUE;
1821 	xpad_periodic_save_content_delayed (pad);
1822 }
1823 
xpad_pad_save_info_delayed(XpadPad * pad)1824 void xpad_pad_save_info_delayed (XpadPad *pad)
1825 {
1826 	pad->priv->unsaved_info = TRUE;
1827 	xpad_periodic_save_info_delayed (pad);
1828 }
1829 
1830 /* Save pad without delay, for example on application shutdown. */
xpad_pad_save_unsaved(XpadPad * pad)1831 void xpad_pad_save_unsaved (XpadPad *pad)
1832 {
1833 	if (pad->priv->unsaved_content)
1834 		xpad_pad_save_content (pad);
1835 	if (pad->priv->unsaved_info)
1836 		xpad_pad_save_info (pad);
1837 }
1838 
xpad_pad_remove_accelerator_group(XpadPad * pad)1839 void xpad_pad_remove_accelerator_group (XpadPad *pad) {
1840 	g_return_if_fail (pad);
1841 
1842 	gtk_widget_add_events (GTK_WIDGET (pad), 0);
1843 
1844 	if (pad->priv->toolbar) {
1845 		gtk_widget_add_events (pad->priv->toolbar, 0);
1846 	}
1847 
1848 	if (pad->priv->accel_group) {
1849 		gtk_window_remove_accel_group (GTK_WINDOW(pad), pad->priv->accel_group);
1850 		g_clear_object (&pad->priv->accel_group);
1851 	}
1852 }
1853