1 /*
2  * Copyright © 2009-2018 Siyan Panayotov <contact@siyanpanayotov.com>
3  *
4  * Based on code by (see README for details):
5  * - Björn Lindqvist <bjourne@gmail.com>
6  *
7  * This file is part of Viewnior.
8  *
9  * Viewnior is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Viewnior is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Viewnior.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <libintl.h>
24 #define _(String) gettext (String)
25 
26 #include "uni-scroll-win.h"
27 #include "uni-image-view.h"
28 #include "uni-nav.h"
29 
30 /*************************************************************/
31 /***** PRIVATE DATA ******************************************/
32 /*************************************************************/
33 static const char *nav_button[] =
34 {
35     /* columns rows colors chars-per-pixel */
36     "14 14 2 1 ",
37     "  c black",
38     ". c None",
39     /* pixels */
40     "......  ......",
41     ".....    .....",
42     "....      ....",
43     "......  ......",
44     ".. ...  ... ..",
45     ".  ...  ...  .",
46     "              ",
47     "              ",
48     ".  ...  ...  .",
49     ".. ...  ... ..",
50     "......  ......",
51     "....      ....",
52     ".....    .....",
53     "......  ......"
54 };
55 
56 G_DEFINE_TYPE (UniScrollWin, uni_scroll_win, GTK_TYPE_TABLE);
57 
58 /*************************************************************/
59 /***** Static stuff ******************************************/
60 /*************************************************************/
61 static void
uni_scroll_win_show_scrollbar(UniScrollWin * window,gboolean show)62 uni_scroll_win_show_scrollbar (UniScrollWin * window, gboolean show)
63 {
64     if (show)
65     {
66         gtk_widget_show_now (window->vscroll);
67         gtk_widget_show_now (window->hscroll);
68         gtk_widget_show_now (window->nav_box);
69     }
70     else
71     {
72         gtk_widget_hide (window->vscroll);
73         gtk_widget_hide (window->hscroll);
74         gtk_widget_hide (window->nav_box);
75     }
76 }
77 
78 
79 static void
uni_scroll_win_adjustment_changed(GtkAdjustment * adj,UniScrollWin * window)80 uni_scroll_win_adjustment_changed (GtkAdjustment * adj, UniScrollWin * window)
81 {
82     uni_scroll_win_show_scrollbar (window,
83         window->show_scrollbar && !uni_scroll_win_image_fits (window));
84 }
85 
86 static void
uni_scroll_win_nav_btn_clicked(UniScrollWin * window,GdkEventButton * ev)87 uni_scroll_win_nav_btn_clicked (UniScrollWin * window, GdkEventButton * ev)
88 {
89     uni_nav_show_and_grab (UNI_NAV (window->nav), ev->x_root, ev->y_root);
90 }
91 
92 static void
uni_scroll_win_set_view(UniScrollWin * window,UniImageView * view)93 uni_scroll_win_set_view (UniScrollWin * window, UniImageView * view)
94 {
95     // Setup the scrollbars
96     GtkAdjustment *hadj;
97     hadj = (GtkAdjustment *) g_object_new (GTK_TYPE_ADJUSTMENT, NULL);
98 
99     GtkAdjustment *vadj;
100     vadj = (GtkAdjustment *) g_object_new (GTK_TYPE_ADJUSTMENT, NULL);
101 
102     window->hscroll = gtk_hscrollbar_new (hadj);
103     window->vscroll = gtk_vscrollbar_new (vadj);
104 
105     // We want to be notified when the adjustments change.
106     g_signal_connect (hadj, "changed",
107                       G_CALLBACK (uni_scroll_win_adjustment_changed), window);
108     g_signal_connect (vadj, "changed",
109                       G_CALLBACK (uni_scroll_win_adjustment_changed), window);
110 
111     // Output the adjustments to the widget.
112     gtk_widget_set_scroll_adjustments (GTK_WIDGET (view), hadj, vadj);
113 
114     // Add the widgets to the table.
115     gtk_widget_push_composite_child ();
116     gtk_table_attach (GTK_TABLE (window), GTK_WIDGET (view), 0, 1, 0, 1,
117                       GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
118     gtk_table_attach (GTK_TABLE (window), window->vscroll, 1, 2, 0, 1,
119                       GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
120     gtk_table_attach (GTK_TABLE (window), window->hscroll, 0, 1, 1, 2,
121                       GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
122     gtk_table_attach (GTK_TABLE (window), window->nav_box, 1, 2, 1, 2,
123                       GTK_SHRINK, GTK_SHRINK, 0, 0);
124     gtk_widget_pop_composite_child ();
125 
126     // Create the UniNav popup.
127     window->nav = uni_nav_new (view);
128 }
129 
130 /*************************************************************/
131 /***** Private signal handlers *******************************/
132 /*************************************************************/
133 /* The size request signal needs to be implemented and return two
134    constant dummy values, otherwise an infinite loop may occur when
135    UniScrollWin is placed in a non-bounded container.
136 
137    When the scroll adjustments of UniImageView changes,
138    uni_scroll_win_adjustment_changed will be called which may
139    instruct GTK to show the scrollbars. When the scrollbars are shown
140    the size has to be renegotiated. Because UniScrollWin now
141    shows the widgets which it didn't before, it will be allocated a
142    bigger space.
143 
144    The bigger space allocation is propagated down to UniImageView
145    which updates its adjustments accordingly. Because of the bigger
146    space, the scrollbars might not be needed
147    anymore. uni_scroll_win_adjustment_changed is invoked again
148    which may hide the scrollbars. Because UniScrollWin now hides
149    widgets it previously showed, the size has to be
150    renegotiated. UniScrollWin finds out that the size is now to
151    small so the scrollbars has to be shown after all.
152 
153    And so it continues.
154  */
155 static void
uni_scroll_win_size_request(GtkWidget * widget,GtkRequisition * req)156 uni_scroll_win_size_request (GtkWidget * widget, GtkRequisition * req)
157 {
158     /* Chain up. */
159     GTK_WIDGET_CLASS (uni_scroll_win_parent_class)->size_request
160         (widget, req);
161     req->width = req->height = 200;
162 }
163 
164 /*************************************************************/
165 /***** Stuff that deals with the type ************************/
166 /*************************************************************/
167 static void
uni_scroll_win_init(UniScrollWin * window)168 uni_scroll_win_init (UniScrollWin * window)
169 {
170     window->hscroll = NULL;
171     window->vscroll = NULL;
172     window->nav_box = NULL;
173     window->nav = NULL;
174     window->show_scrollbar = FALSE;
175 
176     // Setup the navigator button.
177     window->nav_button = gdk_pixbuf_new_from_xpm_data (nav_button);
178     window->nav_image = gtk_image_new_from_pixbuf (window->nav_button);
179 
180     window->nav_box = gtk_event_box_new ();
181     gtk_container_add (GTK_CONTAINER (window->nav_box), window->nav_image);
182     g_signal_connect_swapped (G_OBJECT (window->nav_box),
183                               "button_press_event",
184                               G_CALLBACK (uni_scroll_win_nav_btn_clicked),
185                               window);
186 
187     gtk_widget_set_tooltip_text (window->nav_box,
188                                  _("Open the navigator window"));
189 }
190 
191 static void
uni_scroll_win_finalize(GObject * object)192 uni_scroll_win_finalize (GObject * object)
193 {
194     UniScrollWin *window = UNI_SCROLL_WIN (object);
195     g_object_unref (window->nav_button);
196     /* Maybe window->nav should be unreferenced here.. But uh I don't
197        know how. */
198     gtk_widget_destroy (window->nav);
199 
200     /* Chain up. */
201     G_OBJECT_CLASS (uni_scroll_win_parent_class)->finalize (object);
202 }
203 
204 enum {
205     PROP_IMAGE_VIEW = 1
206 };
207 
208 static void
uni_scroll_win_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)209 uni_scroll_win_set_property (GObject * object,
210                              guint prop_id,
211                              const GValue * value, GParamSpec * pspec)
212 {
213     UniScrollWin *window = UNI_SCROLL_WIN (object);
214     if (prop_id == PROP_IMAGE_VIEW)
215         uni_scroll_win_set_view (window, g_value_get_object (value));
216     else
217         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
218 }
219 
220 static void
uni_scroll_win_class_init(UniScrollWinClass * klass)221 uni_scroll_win_class_init (UniScrollWinClass * klass)
222 {
223     GObjectClass *object_class = G_OBJECT_CLASS (klass);
224     object_class->finalize = uni_scroll_win_finalize;
225     object_class->set_property = uni_scroll_win_set_property;
226 
227     GParamSpec *pspec = g_param_spec_object ("view",
228                                              "Image View",
229                                              "Image View to navigate",
230                                              UNI_TYPE_IMAGE_VIEW,
231                                              G_PARAM_CONSTRUCT_ONLY |
232                                              G_PARAM_WRITABLE);
233     g_object_class_install_property (object_class, PROP_IMAGE_VIEW, pspec);
234 
235     GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
236     widget_class->size_request = uni_scroll_win_size_request;
237 }
238 
239 /**
240  * uni_scroll_win_new:
241  * @view: a #UniImageView to show.
242  * @returns: A new #UniScrollWin.
243  *
244  * Creates a new #UniScrollWin containing the #UniImageView.
245  *
246  * The widget is built using four subwidgets arranged inside a
247  * #GtkTable with two columns and two rows. Two scrollbars, one
248  * navigator button (the decorations) and one #UniImageView.
249  *
250  * When the #UniImageView fits inside the window, the decorations are
251  * hidden.
252  **/
253 GtkWidget *
uni_scroll_win_new(UniImageView * view)254 uni_scroll_win_new (UniImageView * view)
255 {
256     gpointer data = g_object_new (UNI_TYPE_SCROLL_WIN,
257                                   "n-columns", 2,
258                                   "n-rows", 2,
259                                   "homogeneous", FALSE,
260                                   "view", view,
261                                   NULL);
262     return GTK_WIDGET (data);
263 }
264 
265 
266 /**
267  * uni_scroll_win_image_fits:
268  * @window: the #UniScrollWin to inspect.
269  * @returns: A boolean indicating if the image fits in the window
270  *
271  * Check if the current image fits in the window without the need to scoll.
272  * The check is performed as if scrollbars are not visible.
273  **/
274 gboolean
uni_scroll_win_image_fits(UniScrollWin * window)275 uni_scroll_win_image_fits (UniScrollWin * window)
276 {
277     GtkAdjustment *hadj, *vadj;
278     GtkAllocation allocation;
279 
280     hadj = gtk_range_get_adjustment (GTK_RANGE (window->hscroll));
281     vadj = gtk_range_get_adjustment (GTK_RANGE (window->vscroll));
282     gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
283 
284     /* We compare with the allocation size for the window instead of
285        hadj->page_size and vadj->page_size. If the scrollbars are
286        shown the views size is about 15 pixels shorter and thinner,
287        which makes the page sizes inaccurate. The scroll windows
288        allocation, on the other hand, always gives the correct number
289        of pixels that COULD be shown if the scrollbars weren't
290        there.
291      */
292     return gtk_adjustment_get_upper (hadj) <= allocation.width &&
293            gtk_adjustment_get_upper (vadj) <= allocation.height;
294 }
295 
296 /**
297  * uni_scroll_win_set_show_scrollbar:
298  * @window: the #UniScrollWin to adjust.
299  *
300  * Show or hide the scrollbar.
301  * The scrollbar will only be shown if the image doesn't fit in the window.
302  **/
303 void
uni_scroll_win_set_show_scrollbar(UniScrollWin * window,gboolean show)304 uni_scroll_win_set_show_scrollbar (UniScrollWin * window, gboolean show)
305 {
306     window->show_scrollbar = show;
307     uni_scroll_win_show_scrollbar (window,
308         window->show_scrollbar && !uni_scroll_win_image_fits (window));
309 }
310