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