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