1 // SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Eek Preview Stuffs.
16 *
17 * The Initial Developer of the Original Code is
18 * Jon A. Cruz.
19 * Portions created by the Initial Developer are Copyright (C) 2005
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38 #include <algorithm>
39 #include <gdkmm/general.h>
40 #include "preview.h"
41 #include "preferences.h"
42
43 namespace Inkscape {
44 namespace UI {
45 namespace Widget {
46
47 #define PRIME_BUTTON_MAGIC_NUMBER 1
48
49 /* Keep in sync with last value in eek-preview.h */
50 #define PREVIEW_SIZE_LAST PREVIEW_SIZE_HUGE
51 #define PREVIEW_SIZE_NEXTFREE (PREVIEW_SIZE_HUGE + 1)
52
53 #define PREVIEW_MAX_RATIO 500
54
55 void
set_color(int r,int g,int b)56 Preview::set_color(int r, int g, int b )
57 {
58 _r = r;
59 _g = g;
60 _b = b;
61
62 queue_draw();
63 }
64
65
66 void
set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> & pixbuf)67 Preview::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf)
68 {
69 _previewPixbuf = pixbuf;
70
71 queue_draw();
72
73 if (_scaled)
74 {
75 _scaled.reset();
76 }
77
78 _scaledW = _previewPixbuf->get_width();
79 _scaledH = _previewPixbuf->get_height();
80 }
81
82 static gboolean setupDone = FALSE;
83 static GtkRequisition sizeThings[PREVIEW_SIZE_NEXTFREE];
84
85 void
set_size_mappings(guint count,GtkIconSize const * sizes)86 Preview::set_size_mappings( guint count, GtkIconSize const* sizes )
87 {
88 gint width = 0;
89 gint height = 0;
90 gint smallest = 512;
91 gint largest = 0;
92 guint i = 0;
93 guint delta = 0;
94
95 for ( i = 0; i < count; ++i ) {
96 gboolean worked = gtk_icon_size_lookup( sizes[i], &width, &height );
97 if ( worked ) {
98 if ( width < smallest ) {
99 smallest = width;
100 }
101 if ( width > largest ) {
102 largest = width;
103 }
104 }
105 }
106
107 smallest = (smallest * 3) / 4;
108
109 delta = largest - smallest;
110
111 for ( i = 0; i < G_N_ELEMENTS(sizeThings); ++i ) {
112 guint val = smallest + ( (i * delta) / (G_N_ELEMENTS(sizeThings) - 1) );
113 sizeThings[i].width = val;
114 sizeThings[i].height = val;
115 }
116
117 setupDone = TRUE;
118 }
119
120 void
size_request(GtkRequisition * req) const121 Preview::size_request(GtkRequisition* req) const
122 {
123 int width = 0;
124 int height = 0;
125
126 if ( !setupDone ) {
127 GtkIconSize sizes[] = {
128 GTK_ICON_SIZE_MENU,
129 GTK_ICON_SIZE_SMALL_TOOLBAR,
130 GTK_ICON_SIZE_LARGE_TOOLBAR,
131 GTK_ICON_SIZE_BUTTON,
132 GTK_ICON_SIZE_DIALOG
133 };
134 set_size_mappings( G_N_ELEMENTS(sizes), sizes );
135 }
136
137 width = sizeThings[_size].width;
138 height = sizeThings[_size].height;
139
140 if ( _view == VIEW_TYPE_LIST ) {
141 width *= 3;
142 }
143
144 if ( _ratio != 100 ) {
145 width = (width * _ratio) / 100;
146 if ( width < 0 ) {
147 width = 1;
148 }
149 }
150
151 req->width = width;
152 req->height = height;
153 }
154
155 void
get_preferred_width_vfunc(int & minimal_width,int & natural_width) const156 Preview::get_preferred_width_vfunc(int &minimal_width, int &natural_width) const
157 {
158 GtkRequisition requisition;
159 size_request(&requisition);
160 minimal_width = natural_width = requisition.width;
161 }
162
163 void
get_preferred_height_vfunc(int & minimal_height,int & natural_height) const164 Preview::get_preferred_height_vfunc(int &minimal_height, int &natural_height) const
165 {
166 GtkRequisition requisition;
167 size_request(&requisition);
168 minimal_height = natural_height = requisition.height;
169 }
170
171 bool
on_draw(const Cairo::RefPtr<Cairo::Context> & cr)172 Preview::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
173 {
174 auto allocation = get_allocation();
175
176 gint insetTop = 0, insetBottom = 0;
177 gint insetLeft = 0, insetRight = 0;
178
179 if (_border == BORDER_SOLID) {
180 insetTop = 1;
181 insetLeft = 1;
182 }
183 if (_border == BORDER_SOLID_LAST_ROW) {
184 insetTop = insetBottom = 1;
185 insetLeft = 1;
186 }
187 if (_border == BORDER_WIDE) {
188 insetTop = insetBottom = 1;
189 insetLeft = insetRight = 1;
190 }
191
192 auto context = get_style_context();
193
194 context->render_frame(cr,
195 0, 0,
196 allocation.get_width(), allocation.get_height());
197
198 context->render_background(cr,
199 0, 0,
200 allocation.get_width(), allocation.get_height());
201
202 // Border
203 if (_border != BORDER_NONE) {
204 cr->set_source_rgb(0.0, 0.0, 0.0);
205 cr->rectangle(0, 0, allocation.get_width(), allocation.get_height());
206 cr->fill();
207 }
208
209 cr->set_source_rgb(_r/65535.0, _g/65535.0, _b/65535.0 );
210 cr->rectangle(insetLeft, insetTop, allocation.get_width() - (insetLeft + insetRight), allocation.get_height() - (insetTop + insetBottom));
211 cr->fill();
212
213 if (_previewPixbuf )
214 {
215 if ((allocation.get_width() != _scaledW) || (allocation.get_height() != _scaledH)) {
216 if (_scaled)
217 {
218 _scaled.reset();
219 }
220
221 _scaledW = allocation.get_width() - (insetLeft + insetRight);
222 _scaledH = allocation.get_height() - (insetTop + insetBottom);
223
224 _scaled = _previewPixbuf->scale_simple(_scaledW,
225 _scaledH,
226 Gdk::INTERP_BILINEAR);
227 }
228
229 Glib::RefPtr<Gdk::Pixbuf> pix = (_scaled) ? _scaled : _previewPixbuf;
230
231 // Border
232 if (_border != BORDER_NONE) {
233 cr->set_source_rgb(0.0, 0.0, 0.0);
234 cr->rectangle(0, 0, allocation.get_width(), allocation.get_height());
235 cr->fill();
236 }
237
238 Gdk::Cairo::set_source_pixbuf(cr, pix, insetLeft, insetTop);
239 cr->paint();
240 }
241
242 if (_linked)
243 {
244 /* Draw arrow */
245 GdkRectangle possible = {insetLeft,
246 insetTop,
247 (allocation.get_width() - (insetLeft + insetRight)),
248 (allocation.get_height() - (insetTop + insetBottom))
249 };
250
251 GdkRectangle area = {possible.x,
252 possible.y,
253 possible.width / 2,
254 possible.height / 2 };
255
256 /* Make it square */
257 if ( area.width > area.height )
258 area.width = area.height;
259 if ( area.height > area.width )
260 area.height = area.width;
261
262 /* Center it horizontally */
263 if ( area.width < possible.width ) {
264 int diff = (possible.width - area.width) / 2;
265 area.x += diff;
266 }
267
268 if (_linked & PREVIEW_LINK_IN)
269 {
270 context->render_arrow(cr,
271 G_PI, // Down-pointing arrow
272 area.x, area.y,
273 std::min(area.width, area.height)
274 );
275 }
276
277 if (_linked & PREVIEW_LINK_OUT)
278 {
279 GdkRectangle otherArea = {area.x, area.y, area.width, area.height};
280 if ( otherArea.height < possible.height ) {
281 otherArea.y = possible.y + (possible.height - otherArea.height);
282 }
283
284 context->render_arrow(cr,
285 G_PI, // Down-pointing arrow
286 otherArea.x, otherArea.y,
287 std::min(otherArea.width, otherArea.height)
288 );
289 }
290
291 if (_linked & PREVIEW_LINK_OTHER)
292 {
293 GdkRectangle otherArea = {insetLeft, area.y, area.width, area.height};
294 if ( otherArea.height < possible.height ) {
295 otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
296 }
297
298 context->render_arrow(cr,
299 1.5*G_PI, // Left-pointing arrow
300 otherArea.x, otherArea.y,
301 std::min(otherArea.width, otherArea.height)
302 );
303 }
304
305
306 if (_linked & PREVIEW_FILL)
307 {
308 GdkRectangle otherArea = {possible.x + ((possible.width / 4) - (area.width / 2)),
309 area.y,
310 area.width, area.height};
311 if ( otherArea.height < possible.height ) {
312 otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
313 }
314 context->render_check(cr,
315 otherArea.x, otherArea.y,
316 otherArea.width, otherArea.height );
317 }
318
319 if (_linked & PREVIEW_STROKE)
320 {
321 GdkRectangle otherArea = {possible.x + (((possible.width * 3) / 4) - (area.width / 2)),
322 area.y,
323 area.width, area.height};
324 if ( otherArea.height < possible.height ) {
325 otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
326 }
327 // This should be a diamond too?
328 context->render_check(cr,
329 otherArea.x, otherArea.y,
330 otherArea.width, otherArea.height );
331 }
332 }
333
334
335 if ( has_focus() ) {
336 allocation = get_allocation();
337
338 context->render_focus(cr,
339 0 + 1, 0 + 1,
340 allocation.get_width() - 2, allocation.get_height() - 2 );
341 }
342
343 return false;
344 }
345
346
347 bool
on_enter_notify_event(GdkEventCrossing * event)348 Preview::on_enter_notify_event(GdkEventCrossing* event )
349 {
350 _within = true;
351 set_state_flags(_hot ? Gtk::STATE_FLAG_ACTIVE : Gtk::STATE_FLAG_PRELIGHT, false);
352
353 return false;
354 }
355
356 bool
on_leave_notify_event(GdkEventCrossing * event)357 Preview::on_leave_notify_event(GdkEventCrossing* event)
358 {
359 _within = false;
360 set_state_flags(Gtk::STATE_FLAG_NORMAL, false);
361
362 return false;
363 }
364
365 bool
on_button_press_event(GdkEventButton * event)366 Preview::on_button_press_event(GdkEventButton *event)
367 {
368 if (_takesFocus && !has_focus() )
369 {
370 grab_focus();
371 }
372
373 if ( event->button == PRIME_BUTTON_MAGIC_NUMBER ||
374 event->button == 2 )
375 {
376 _hot = true;
377
378 if ( _within )
379 {
380 set_state_flags(Gtk::STATE_FLAG_ACTIVE, false);
381 }
382 }
383
384 return false;
385 }
386
387 bool
on_button_release_event(GdkEventButton * event)388 Preview::on_button_release_event(GdkEventButton* event)
389 {
390 _hot = false;
391 set_state_flags(Gtk::STATE_FLAG_NORMAL, false);
392
393 if (_within &&
394 (event->button == PRIME_BUTTON_MAGIC_NUMBER ||
395 event->button == 2))
396 {
397 gboolean isAlt = ( ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) ||
398 (event->button == 2));
399
400 if ( isAlt )
401 {
402 _signal_alt_clicked(2);
403 }
404 else
405 {
406 _signal_clicked.emit();
407 }
408 }
409
410 return false;
411 }
412
413 void
set_linked(LinkType link)414 Preview::set_linked(LinkType link)
415 {
416 link = (LinkType)(link & PREVIEW_LINK_ALL);
417
418 if (link != _linked)
419 {
420 _linked = link;
421
422 queue_draw();
423 }
424 }
425
426 LinkType
get_linked() const427 Preview::get_linked() const
428 {
429 return (LinkType)_linked;
430 }
431
432 void
set_details(ViewType view,PreviewSize size,guint ratio,guint border)433 Preview::set_details(ViewType view,
434 PreviewSize size,
435 guint ratio,
436 guint border)
437 {
438 _view = view;
439
440 if ( size > PREVIEW_SIZE_LAST )
441 {
442 size = PREVIEW_SIZE_LAST;
443 }
444
445 _size = size;
446
447 if ( ratio > PREVIEW_MAX_RATIO )
448 {
449 ratio = PREVIEW_MAX_RATIO;
450 }
451
452 _ratio = ratio;
453 _border = border;
454
455 queue_draw();
456 }
457
Preview()458 Preview::Preview()
459 : _r(0x80),
460 _g(0x80),
461 _b(0xcc),
462 _scaledW(0),
463 _scaledH(0),
464 _hot(false),
465 _within(false),
466 _takesFocus(false),
467 _view(VIEW_TYPE_LIST),
468 _size(PREVIEW_SIZE_SMALL),
469 _ratio(100),
470 _border(BORDER_NONE),
471 _previewPixbuf(nullptr),
472 _scaled(nullptr),
473 _linked(PREVIEW_LINK_NONE)
474 {
475 set_can_focus(true);
476 set_receives_default(true);
477
478 set_sensitive(true);
479
480 add_events(Gdk::BUTTON_PRESS_MASK
481 |Gdk::BUTTON_RELEASE_MASK
482 |Gdk::KEY_PRESS_MASK
483 |Gdk::KEY_RELEASE_MASK
484 |Gdk::FOCUS_CHANGE_MASK
485 |Gdk::ENTER_NOTIFY_MASK
486 |Gdk::LEAVE_NOTIFY_MASK );
487 }
488
489 } // namespace Widget
490 } // namespace UI
491 } // namespace Inkscape
492
493 /*
494 Local Variables:
495 mode:c++
496 c-file-style:"stroustrup"
497 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
498 indent-tabs-mode:nil
499 fill-column:99
500 End:
501 */
502 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
503