]> www.fi.muni.cz Git - evince.git/blob - libview/ev-annotation-window.c
abbff69b36d8c06bf13ae77115f4890fcb6da5d4
[evince.git] / libview / ev-annotation-window.c
1 /* ev-annotation-window.c
2  *  this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
5  * Copyright (C) 2007 IƱigo Martinez <inigomartinez@gmail.com>
6  *
7  * Evince is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * Evince is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include "ev-annotation-window.h"
27 #include "ev-stock-icons.h"
28 #include "ev-view-marshal.h"
29 #include "ev-document-misc.h"
30
31 enum {
32         PROP_0,
33         PROP_ANNOTATION,
34         PROP_PARENT
35 };
36
37 enum {
38         CLOSED,
39         MOVED,
40         N_SIGNALS
41 };
42
43 struct _EvAnnotationWindow {
44         GtkWindow     base_instance;
45
46         EvAnnotation *annotation;
47         GtkWindow    *parent;
48
49         GtkWidget    *title;
50         GtkWidget    *close_button;
51         GtkWidget    *text_view;
52         GtkWidget    *resize_se;
53         GtkWidget    *resize_sw;
54
55         gboolean      is_open;
56         EvRectangle  *rect;
57
58         gboolean      in_move;
59         gint          x;
60         gint          y;
61         gint          orig_x;
62         gint          orig_y;
63 };
64
65 struct _EvAnnotationWindowClass {
66         GtkWindowClass base_class;
67
68         void (* closed) (EvAnnotationWindow *window);
69         void (* moved)  (EvAnnotationWindow *window,
70                          gint                x,
71                          gint                y);
72 };
73
74 static guint signals[N_SIGNALS];
75
76 G_DEFINE_TYPE (EvAnnotationWindow, ev_annotation_window, GTK_TYPE_WINDOW)
77
78 #define EV_ICON_SIZE_ANNOT_WINDOW (ev_annotation_window_get_icon_size())
79
80 /* Cut and paste from gtkwindow.c */
81 static void
82 send_focus_change (GtkWidget *widget,
83                    gboolean   in)
84 {
85         GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
86
87         fevent->focus_change.type = GDK_FOCUS_CHANGE;
88         fevent->focus_change.window = gtk_widget_get_window (widget);
89         fevent->focus_change.in = in;
90         if (fevent->focus_change.window)
91                 g_object_ref (fevent->focus_change.window);
92
93         gtk_widget_send_focus_change (widget, fevent);
94
95         gdk_event_free (fevent);
96 }
97
98 static gdouble
99 get_screen_dpi (EvAnnotationWindow *window)
100 {
101         GdkScreen *screen;
102
103         screen = gtk_window_get_screen (GTK_WINDOW (window));
104         return ev_document_misc_get_screen_dpi (screen);
105 }
106
107 static GtkIconSize
108 ev_annotation_window_get_icon_size (void)
109 {
110         static GtkIconSize icon_size = 0;
111
112         if (G_UNLIKELY (icon_size == 0))
113                 icon_size = gtk_icon_size_register ("ev-icon-size-annot-window", 8, 8);
114
115         return icon_size;
116 }
117
118 static void
119 ev_annotation_window_check_contents_modified (EvAnnotationWindow *window)
120 {
121         gchar         *contents;
122         GtkTextIter    start, end;
123         GtkTextBuffer *buffer;
124         EvAnnotation  *annot = window->annotation;
125
126         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (window->text_view));
127         gtk_text_buffer_get_bounds (buffer, &start, &end);
128         contents = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
129
130         if (contents && annot->contents) {
131                 if (strcasecmp (contents, annot->contents) != 0) {
132                         g_free (annot->contents);
133                         annot->contents = contents;
134                         annot->changed = TRUE;
135                 } else {
136                         g_free (contents);
137                 }
138         } else if (annot->contents) {
139                 g_free (annot->contents);
140                 annot->contents = NULL;
141                 annot->changed = TRUE;
142         } else if (contents) {
143                 annot->contents = contents;
144                 annot->changed = TRUE;
145         }
146 }
147
148 static void
149 ev_annotation_window_set_color (EvAnnotationWindow *window,
150                                 GdkColor           *color)
151 {
152         GtkRcStyle *rc_style;
153         GdkColor    gcolor;
154
155         gcolor = *color;
156
157         /* Allocate these colors */
158         gdk_colormap_alloc_color (gtk_widget_get_colormap (GTK_WIDGET (window)),
159                                   &gcolor, FALSE, TRUE);
160
161         /* Apply colors to style */
162         rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (window));
163         rc_style->base[GTK_STATE_NORMAL] = gcolor;
164         rc_style->bg[GTK_STATE_PRELIGHT] = gcolor;
165         rc_style->bg[GTK_STATE_NORMAL] = gcolor;
166         rc_style->bg[GTK_STATE_ACTIVE] = gcolor;
167         rc_style->color_flags[GTK_STATE_PRELIGHT] = GTK_RC_BG;
168         rc_style->color_flags[GTK_STATE_NORMAL] = GTK_RC_BG | GTK_RC_BASE;
169         rc_style->color_flags[GTK_STATE_ACTIVE] = GTK_RC_BG;
170
171         /* Apply the style to the widgets */
172         g_object_ref (rc_style);
173         gtk_widget_modify_style (GTK_WIDGET (window), rc_style);
174         gtk_widget_modify_style (window->close_button, rc_style);
175         gtk_widget_modify_style (window->resize_se, rc_style);
176         gtk_widget_modify_style (window->resize_sw, rc_style);
177         g_object_unref (rc_style);
178 }
179
180 static void
181 ev_annotation_window_dispose (GObject *object)
182 {
183         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (object);
184
185         if (window->annotation) {
186                 ev_annotation_window_check_contents_modified (window);
187                 g_object_unref (window->annotation);
188                 window->annotation = NULL;
189         }
190
191         if (window->rect) {
192                 ev_rectangle_free (window->rect);
193                 window->rect = NULL;
194         }
195
196         (* G_OBJECT_CLASS (ev_annotation_window_parent_class)->dispose) (object);
197 }
198
199 static void
200 ev_annotation_window_set_property (GObject      *object,
201                                    guint         prop_id,
202                                    const GValue *value,
203                                    GParamSpec   *pspec)
204 {
205         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (object);
206
207         switch (prop_id) {
208         case PROP_ANNOTATION:
209                 window->annotation = g_value_dup_object (value);
210                 break;
211         case PROP_PARENT:
212                 window->parent = g_value_get_object (value);
213                 break;
214         default:
215                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
216         }
217 }
218
219 static gboolean
220 ev_annotation_window_resize (EvAnnotationWindow *window,
221                              GdkEventButton     *event,
222                              GtkWidget          *ebox)
223 {
224         if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
225                 gtk_window_begin_resize_drag (GTK_WINDOW (window),
226                                               window->resize_sw == ebox ?
227                                               GDK_WINDOW_EDGE_SOUTH_WEST :
228                                               GDK_WINDOW_EDGE_SOUTH_EAST,
229                                               event->button, event->x_root,
230                                               event->y_root, event->time);
231                 return TRUE;
232         }
233
234         return FALSE;
235 }
236
237 static void
238 ev_annotation_window_set_resize_cursor (GtkWidget          *widget,
239                                         EvAnnotationWindow *window)
240 {
241         GdkWindow *gdk_window = gtk_widget_get_window (widget);
242
243         if (!gdk_window)
244                 return;
245
246         if (gtk_widget_is_sensitive (widget)) {
247                 GdkDisplay *display = gtk_widget_get_display (widget);
248                 GdkCursor  *cursor;
249
250                 cursor = gdk_cursor_new_for_display (display,
251                                                      widget == window->resize_sw ?
252                                                      GDK_BOTTOM_LEFT_CORNER :
253                                                      GDK_BOTTOM_RIGHT_CORNER);
254                 gdk_window_set_cursor (gdk_window, cursor);
255                 gdk_cursor_unref (cursor);
256         } else {
257                 gdk_window_set_cursor (gdk_window, NULL);
258         }
259 }
260
261 static gboolean
262 text_view_button_press (GtkWidget          *widget,
263                         GdkEventButton     *event,
264                         EvAnnotationWindow *window)
265 {
266         ev_annotation_window_grab_focus (window);
267
268         return FALSE;
269 }
270
271 static void
272 ev_annotation_window_close (EvAnnotationWindow *window)
273 {
274         gtk_widget_hide (GTK_WIDGET (window));
275         g_signal_emit (window, signals[CLOSED], 0);
276 }
277
278 static void
279 ev_annotation_window_init (EvAnnotationWindow *window)
280 {
281         GtkWidget *vbox, *hbox;
282         GtkWidget *icon;
283         GtkWidget *swindow;
284
285         gtk_widget_set_can_focus (GTK_WIDGET (window), TRUE);
286
287         vbox = gtk_vbox_new (FALSE, 0);
288
289         /* Title bar */
290         hbox = gtk_hbox_new (FALSE, 0);
291
292         icon = gtk_image_new (); /* FIXME: use the annot icon */
293         gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
294         gtk_widget_show (icon);
295
296         window->title = gtk_label_new (NULL);
297         gtk_box_pack_start (GTK_BOX (hbox), window->title, TRUE, TRUE, 0);
298         gtk_widget_show (window->title);
299
300         window->close_button = gtk_button_new ();
301         gtk_button_set_relief (GTK_BUTTON (window->close_button), GTK_RELIEF_NONE);
302         gtk_container_set_border_width (GTK_CONTAINER (window->close_button), 0);
303         g_signal_connect_swapped (window->close_button, "clicked",
304                                   G_CALLBACK (ev_annotation_window_close),
305                                   window);
306         icon = gtk_image_new_from_stock (EV_STOCK_CLOSE, EV_ICON_SIZE_ANNOT_WINDOW);
307         gtk_container_add (GTK_CONTAINER (window->close_button), icon);
308         gtk_widget_show (icon);
309
310         gtk_box_pack_start (GTK_BOX (hbox), window->close_button, FALSE, FALSE, 0);
311         gtk_widget_show (window->close_button);
312
313         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
314         gtk_widget_show (hbox);
315
316         /* Contents */
317         swindow = gtk_scrolled_window_new (NULL, NULL);
318         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
319                                         GTK_POLICY_AUTOMATIC,
320                                         GTK_POLICY_AUTOMATIC);
321         window->text_view = gtk_text_view_new ();
322         g_signal_connect (window->text_view, "button_press_event",
323                           G_CALLBACK (text_view_button_press),
324                           window);
325         gtk_container_add (GTK_CONTAINER (swindow), window->text_view);
326         gtk_widget_show (window->text_view);
327
328         gtk_box_pack_start (GTK_BOX (vbox), swindow, TRUE, TRUE, 0);
329         gtk_widget_show (swindow);
330
331         /* Resize bar */
332         hbox = gtk_hbox_new (FALSE, 0);
333
334         window->resize_sw = gtk_event_box_new ();
335         gtk_widget_add_events (window->resize_sw, GDK_BUTTON_PRESS_MASK);
336         g_signal_connect_swapped (window->resize_sw, "button-press-event",
337                                   G_CALLBACK (ev_annotation_window_resize),
338                                   window);
339         g_signal_connect (window->resize_sw, "realize",
340                           G_CALLBACK (ev_annotation_window_set_resize_cursor),
341                           window);
342
343         icon = gtk_image_new_from_stock (EV_STOCK_RESIZE_SW, EV_ICON_SIZE_ANNOT_WINDOW);
344         gtk_container_add (GTK_CONTAINER (window->resize_sw), icon);
345         gtk_widget_show (icon);
346         gtk_box_pack_start (GTK_BOX (hbox), window->resize_sw, FALSE, FALSE, 0);
347         gtk_widget_show (window->resize_sw);
348
349         window->resize_se = gtk_event_box_new ();
350         gtk_widget_add_events (window->resize_se, GDK_BUTTON_PRESS_MASK);
351         g_signal_connect_swapped (window->resize_se, "button-press-event",
352                                   G_CALLBACK (ev_annotation_window_resize),
353                                   window);
354         g_signal_connect (window->resize_se, "realize",
355                           G_CALLBACK (ev_annotation_window_set_resize_cursor),
356                           window);
357
358         icon = gtk_image_new_from_stock (EV_STOCK_RESIZE_SE, EV_ICON_SIZE_ANNOT_WINDOW);
359         gtk_container_add (GTK_CONTAINER (window->resize_se), icon);
360         gtk_widget_show (icon);
361         gtk_box_pack_end (GTK_BOX (hbox), window->resize_se, FALSE, FALSE, 0);
362         gtk_widget_show (window->resize_se);
363
364         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
365         gtk_widget_show (hbox);
366
367         gtk_container_add (GTK_CONTAINER (window), vbox);
368         gtk_container_set_border_width (GTK_CONTAINER (window), 0);
369         gtk_widget_show (vbox);
370
371         gtk_widget_add_events (GTK_WIDGET (window),
372                                GDK_BUTTON_PRESS_MASK |
373                                GDK_KEY_PRESS_MASK);
374         gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
375
376         gtk_container_set_border_width (GTK_CONTAINER (window), 2);
377
378         gtk_window_set_accept_focus (GTK_WINDOW (window), TRUE);
379         gtk_window_set_decorated (GTK_WINDOW (window), FALSE);
380         gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE);
381         gtk_window_set_skip_pager_hint (GTK_WINDOW (window), TRUE);
382         gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
383 }
384
385 static GObject *
386 ev_annotation_window_constructor (GType                  type,
387                                   guint                  n_construct_properties,
388                                   GObjectConstructParam *construct_params)
389 {
390         GObject            *object;
391         EvAnnotationWindow *window;
392         EvAnnotation       *annot;
393         gchar              *label;
394         gdouble             opacity;
395         EvRectangle        *rect;
396         gdouble             scale;
397
398         object = G_OBJECT_CLASS (ev_annotation_window_parent_class)->constructor (type,
399                                                                                   n_construct_properties,
400                                                                                   construct_params);
401         window = EV_ANNOTATION_WINDOW (object);
402         annot = window->annotation;
403
404         gtk_window_set_transient_for (GTK_WINDOW (window), window->parent);
405         gtk_window_set_destroy_with_parent (GTK_WINDOW (window), FALSE);
406
407         g_object_get (annot,
408                       "label", &label,
409                       "opacity", &opacity,
410                       "is_open", &window->is_open,
411                       "rectangle", &window->rect,
412                       NULL);
413         rect = window->rect;
414
415         /* Rectangle is at doc resolution (72.0) */
416         scale = get_screen_dpi (window) / 72.0;
417         gtk_window_resize (GTK_WINDOW (window),
418                            (gint)((rect->x2 - rect->x1) * scale),
419                            (gint)((rect->y2 - rect->y1) * scale));
420         ev_annotation_window_set_color (window, &annot->color);
421         gtk_widget_set_name (GTK_WIDGET (window), annot->name);
422         gtk_window_set_title (GTK_WINDOW (window), label);
423         gtk_label_set_text (GTK_LABEL (window->title), label);
424         gtk_window_set_opacity (GTK_WINDOW (window), opacity);
425         g_free (label);
426
427         if (annot->contents) {
428                 GtkTextBuffer *buffer;
429
430                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (window->text_view));
431                 gtk_text_buffer_set_text (buffer, annot->contents, -1);
432         }
433
434         return object;
435 }
436
437 static gboolean
438 ev_annotation_window_button_press_event (GtkWidget      *widget,
439                                          GdkEventButton *event)
440 {
441         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (widget);
442
443         if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
444                 window->in_move = TRUE;
445                 window->x = event->x;
446                 window->y = event->y;
447                 gtk_window_begin_move_drag (GTK_WINDOW (widget),
448                                             event->button,
449                                             event->x_root,
450                                             event->y_root,
451                                             event->time);
452                 return TRUE;
453         }
454
455         return FALSE;
456 }
457
458 static gboolean
459 ev_annotation_window_configure_event (GtkWidget         *widget,
460                                       GdkEventConfigure *event)
461 {
462         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (widget);
463
464         if (window->in_move &&
465             (window->x != event->x || window->y != event->y)) {
466                 window->x = event->x;
467                 window->y = event->y;
468         }
469
470         return GTK_WIDGET_CLASS (ev_annotation_window_parent_class)->configure_event (widget, event);
471 }
472
473 static gboolean
474 ev_annotation_window_focus_in_event (GtkWidget     *widget,
475                                      GdkEventFocus *event)
476 {
477         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (widget);
478
479         if (window->in_move) {
480                 window->orig_x = window->x;
481                 window->orig_y = window->y;
482         }
483
484         return FALSE;
485 }
486
487 static gboolean
488 ev_annotation_window_focus_out_event (GtkWidget     *widget,
489                                       GdkEventFocus *event)
490 {
491         EvAnnotationWindow *window = EV_ANNOTATION_WINDOW (widget);
492
493         if (window->in_move &&
494             (window->orig_x != window->x || window->orig_y != window->y)) {
495                 window->in_move = FALSE;
496                 g_signal_emit (window, signals[MOVED], 0, window->x, window->y);
497         }
498
499         return FALSE;
500 }
501
502 static void
503 ev_annotation_window_class_init (EvAnnotationWindowClass *klass)
504 {
505         GObjectClass   *g_object_class = G_OBJECT_CLASS (klass);
506         GtkWidgetClass *gtk_widget_class = GTK_WIDGET_CLASS (klass);
507
508         g_object_class->constructor = ev_annotation_window_constructor;
509         g_object_class->set_property = ev_annotation_window_set_property;
510         g_object_class->dispose = ev_annotation_window_dispose;
511
512         gtk_widget_class->button_press_event = ev_annotation_window_button_press_event;
513         gtk_widget_class->configure_event = ev_annotation_window_configure_event;
514         gtk_widget_class->focus_in_event = ev_annotation_window_focus_in_event;
515         gtk_widget_class->focus_out_event = ev_annotation_window_focus_out_event;
516
517         g_object_class_install_property (g_object_class,
518                                          PROP_ANNOTATION,
519                                          g_param_spec_object ("annotation",
520                                                               "Annotation",
521                                                               "The annotation associated to the window",
522                                                               EV_TYPE_ANNOTATION_MARKUP,
523                                                               G_PARAM_WRITABLE |
524                                                               G_PARAM_CONSTRUCT_ONLY));
525         g_object_class_install_property (g_object_class,
526                                          PROP_PARENT,
527                                          g_param_spec_object ("parent",
528                                                               "Parent",
529                                                               "The parent window",
530                                                               GTK_TYPE_WINDOW,
531                                                               G_PARAM_WRITABLE |
532                                                               G_PARAM_CONSTRUCT_ONLY));
533         signals[CLOSED] =
534                 g_signal_new ("closed",
535                               G_TYPE_FROM_CLASS (g_object_class),
536                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
537                               G_STRUCT_OFFSET (EvAnnotationWindowClass, closed),
538                               NULL, NULL,
539                               g_cclosure_marshal_VOID__VOID,
540                               G_TYPE_NONE, 0, G_TYPE_NONE);
541         signals[MOVED] =
542                 g_signal_new ("moved",
543                               G_TYPE_FROM_CLASS (g_object_class),
544                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
545                               G_STRUCT_OFFSET (EvAnnotationWindowClass, moved),
546                               NULL, NULL,
547                               ev_view_marshal_VOID__INT_INT,
548                               G_TYPE_NONE, 2,
549                               G_TYPE_INT, G_TYPE_INT);
550 }
551
552 /* Public methods */
553 GtkWidget *
554 ev_annotation_window_new (EvAnnotation *annot,
555                           GtkWindow    *parent)
556 {
557         GtkWidget *window;
558
559         g_return_val_if_fail (EV_IS_ANNOTATION_MARKUP (annot), NULL);
560         g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
561
562         window = g_object_new (EV_TYPE_ANNOTATION_WINDOW,
563                                "annotation", annot,
564                                "parent", parent,
565                                NULL);
566         return window;
567 }
568
569 EvAnnotation *
570 ev_annotation_window_get_annotation (EvAnnotationWindow *window)
571 {
572         g_return_val_if_fail (EV_IS_ANNOTATION_WINDOW (window), NULL);
573
574         return window->annotation;
575 }
576
577 void
578 ev_annotation_window_set_annotation (EvAnnotationWindow *window,
579                                      EvAnnotation       *annot)
580 {
581         g_return_if_fail (EV_IS_ANNOTATION_WINDOW (window));
582         g_return_if_fail (EV_IS_ANNOTATION (annot));
583
584         if (annot == window->annotation)
585                 return;
586
587         g_object_unref (window->annotation);
588         window->annotation = g_object_ref (annot);
589         ev_annotation_window_check_contents_modified (window);
590         g_object_notify (G_OBJECT (window), "annotation");
591 }
592
593 gboolean
594 ev_annotation_window_is_open (EvAnnotationWindow *window)
595 {
596         g_return_val_if_fail (EV_IS_ANNOTATION_WINDOW (window), FALSE);
597
598         return window->is_open;
599 }
600
601 const EvRectangle *
602 ev_annotation_window_get_rectangle (EvAnnotationWindow *window)
603 {
604         g_return_val_if_fail (EV_IS_ANNOTATION_WINDOW (window), NULL);
605
606         return window->rect;
607 }
608
609 void
610 ev_annotation_window_set_rectangle (EvAnnotationWindow *window,
611                                     EvRectangle        *rect)
612 {
613         g_return_if_fail (EV_IS_ANNOTATION_WINDOW (window));
614         g_return_if_fail (rect != NULL);
615
616         *window->rect = *rect;
617 }
618
619 void
620 ev_annotation_window_grab_focus (EvAnnotationWindow *window)
621 {
622         g_return_if_fail (EV_IS_ANNOTATION_WINDOW (window));
623
624         if (!gtk_widget_has_focus (window->text_view)) {
625                 gtk_widget_grab_focus (GTK_WIDGET (window));
626                 send_focus_change (window->text_view, TRUE);
627         }
628 }
629
630 void
631 ev_annotation_window_ungrab_focus (EvAnnotationWindow *window)
632 {
633         g_return_if_fail (EV_IS_ANNOTATION_WINDOW (window));
634
635         if (gtk_widget_has_focus (window->text_view)) {
636                 send_focus_change (window->text_view, FALSE);
637         }
638
639         ev_annotation_window_check_contents_modified (window);
640 }