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