Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
icon-preview.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Jon A. Cruz
8 * Bob Jamison
9 * Other dudes from The Inkscape Organization
10 * Abhishek Sharma
11 *
12 * Copyright (C) 2004 Bob Jamison
13 * Copyright (C) 2005,2010 Jon A. Cruz
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "icon-preview.h"
19
20#include <glibmm/i18n.h>
21#include <gtkmm/checkbutton.h>
22#include <gtkmm/image.h>
23#include <gtkmm/snapshot.h>
24#include <gtkmm/togglebutton.h>
25
26#include "desktop.h"
27#include "page-manager.h"
28#include "selection.h"
30#include "display/drawing.h"
31#include "object/sp-root.h"
32#include "ui/util.h"
33#include "ui/widget/frame.h"
34
35#define noICON_VERBOSE 1
36
37namespace Inkscape::UI::Dialog {
38
39//#########################################################################
40//## E V E N T S
41//#########################################################################
42
44{
45 if ( hot != which ) {
46 buttons[hot]->set_active( false );
47
48 hot = which;
50 queue_draw();
51 }
52}
53
54//#########################################################################
55//## C O N S T R U C T O R / D E S T R U C T O R
56//#########################################################################
57
59 : DialogBase("/dialogs/iconpreview", "IconPreview")
60 , drawing(nullptr)
61 , drawing_doc(nullptr)
62 , visionkey(0)
63 , timer(nullptr)
64 , renderTimer(nullptr)
65 , pending(false)
66 , minDelay(0.1)
67 , hot(1)
68 , selectionButton(nullptr)
69{
71
72 bool pack = prefs->getBool("/iconpreview/pack", true);
73
74 std::vector<Glib::ustring> pref_sizes = prefs->getAllDirs("/iconpreview/sizes/default");
75
76 for (auto & pref_size : pref_sizes) {
77 if (prefs->getBool(pref_size + "/show", true)) {
78 int sizeVal = prefs->getInt(pref_size + "/value", -1);
79 if (sizeVal > 0) {
80 sizes.push_back(sizeVal);
81 }
82 }
83 }
84
85 if (sizes.empty()) {
86 sizes = {16, 24, 32, 48, 128};
87 }
88 images .resize(sizes.size());
89 labels .resize(sizes.size());
90 buttons.resize(sizes.size());
91 textures.resize(sizes.size());
92
93 for (std::size_t i = 0; i < sizes.size(); ++i) {
94 labels[i] = Glib::ustring::compose("%1 x %1", sizes[i]);
95 }
96
97 magLabel.set_label(labels[hot]);
98
99 auto const magBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
100
101 magnified.set_size_request(128, 128);
102 magnified.set_halign(Gtk::Align::CENTER);
103 magnified.set_valign(Gtk::Align::CENTER);
104
105 auto const magFrame = Gtk::make_managed<UI::Widget::Frame>(_("Magnified:"));
106 magFrame->add(magnified);
107 magFrame->add_css_class("icon-preview");
108 magFrame->set_vexpand();
109
110 magBox->append(*magFrame);
111 magBox->append(magLabel);
112
113 auto const verts = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
114
115 Gtk::Box *horiz = nullptr;
116 int previous = 0;
117 int avail = 0;
118 for (auto i = sizes.size(); i-- > 0;) {
119 images[i] = Gtk::make_managed<Gtk::Image>();
120 images[i]->set_size_request(sizes[i], sizes[i]);
121
122 auto const &label = labels[i];
123
124 buttons[i] = Gtk::make_managed<Gtk::ToggleButton>();
125 buttons[i]->add_css_class("icon-preview");
126 buttons[i]->set_has_frame(false);
127 buttons[i]->set_active(i == hot);
128
129 if (prefs->getBool("/iconpreview/showFrames", true)) {
130 auto const frame = Gtk::make_managed<Gtk::Frame>();
131 frame->set_child(*images[i]);
132 frame->add_css_class("icon-preview");
133 buttons[i]->set_child(*frame);
134 } else {
135 buttons[i]->set_child(*images[i]);
136 }
137
138 buttons[i]->set_tooltip_text(label);
139 buttons[i]->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &IconPreviewPanel::on_button_clicked), i));
140 buttons[i]->set_halign(Gtk::Align::CENTER);
141 buttons[i]->set_valign(Gtk::Align::CENTER);
142
143 if (!pack || (avail == 0 && previous == 0)) {
144 verts->prepend(*buttons[i]);
145 previous = sizes[i];
146 avail = sizes[i];
147 } else {
148 static constexpr int pad = 12;
149
150 if (avail < pad || (sizes[i] > avail && sizes[i] < previous)) {
151 horiz = nullptr;
152 }
153
154 if (!horiz && sizes[i] <= previous) {
155 avail = previous;
156 }
157
158 if (sizes[i] <= avail) {
159 if (!horiz) {
160 horiz = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
161 horiz->set_halign(Gtk::Align::CENTER);
162 avail = previous;
163 verts->prepend(*horiz);
164 }
165
166 horiz->prepend(*buttons[i]);
167
168 avail -= sizes[i];
169 avail -= pad; // a little extra for padding
170 } else {
171 horiz = nullptr;
172 verts->prepend(*buttons[i]);
173 }
174 }
175 }
176
178 splitter.set_valign(Gtk::Align::START);
179 splitter.set_start_child(*magBox);
180 splitter.set_shrink_start_child(false);
181 auto const actuals = Gtk::make_managed<UI::Widget::Frame>(_("Actual Size:"));
182 actuals->add(*verts);
183 splitter.set_end_child(*actuals);
184 splitter.set_resize_end_child(false);
185 splitter.set_shrink_end_child(false);
186
187 selectionButton = Gtk::make_managed<Gtk::CheckButton>(C_("Icon preview window", "Sele_ction"), true);
188 magBox->append(*selectionButton);
189 selectionButton->set_tooltip_text(_("Selection only or whole document"));
190 selectionButton->signal_toggled().connect(sigc::mem_fun(*this, &IconPreviewPanel::modeToggled));
191
192 bool const val = prefs->getBool("/iconpreview/selectionOnly");
193 selectionButton->set_active(val);
194
196}
197
199{
201
202 if (timer) {
203 timer->stop();
204 timer.reset(); // Reset the unique_ptr, not the Timer!
205 }
206
207 if ( renderTimer ) {
208 renderTimer->stop();
209 renderTimer.reset(); // Reset the unique_ptr, not the Timer!
210 }
211}
212
213//#########################################################################
214//## M E T H O D S
215//#########################################################################
216
217#if ICON_VERBOSE
218static Glib::ustring getTimestr()
219{
220 Glib::ustring str;
221 gint64 micr = g_get_monotonic_time();
222 gint64 mins = ((int)round(micr / 60000000)) % 60;
223 gdouble dsecs = micr / 1000000;
224 gchar *ptr = g_strdup_printf(":%02u:%f", mins, dsecs);
225 str = ptr;
226 g_free(ptr);
227 ptr = 0;
228 return str;
229}
230#endif // ICON_VERBOSE
231
233{
234 if (getDesktop() && Inkscape::Preferences::get()->getBool("/iconpreview/autoRefresh", true)) {
235 queueRefresh();
236 }
237}
238
243
248
250{
252
254
255 if (drawing_doc) {
256 drawing = std::make_unique<Inkscape::Drawing>();
258 drawing->setRoot(drawing_doc->getRoot()->invoke_show(*drawing, visionkey, SP_ITEM_SHOW_DISPLAY));
260 queueRefresh();
261 }
262}
263
266{
267 docDesConn.disconnect();
268
269 if (!drawing) {
270 return;
271 }
272
274 drawing.reset();
275
276 drawing_doc = nullptr;
277}
278
280{
281 auto document = getDocument();
282 if (!timer) {
283 timer = std::make_unique<Glib::Timer>();
284 }
285 if (timer->elapsed() < minDelay) {
286#if ICON_VERBOSE
287 g_message( "%s Deferring refresh as too soon. calling queueRefresh()", getTimestr().c_str() );
288#endif //ICON_VERBOSE
289 // Do not refresh too quickly
290 queueRefresh();
291 } else if (document) {
292#if ICON_VERBOSE
293 g_message( "%s Refreshing preview.", getTimestr().c_str() );
294#endif // ICON_VERBOSE
295 bool hold = Inkscape::Preferences::get()->getBool("/iconpreview/selectionHold", true);
296 SPObject *target = nullptr;
297 if ( selectionButton && selectionButton->get_active() )
298 {
299 target = (hold && !targetId.empty()) ? document->getObjectById( targetId.c_str() ) : nullptr;
300 if ( !target ) {
301 targetId.clear();
302 if (auto selection = getSelection()) {
303 for (auto item : selection->items()) {
304 if (gchar const *id = item->getId()) {
305 targetId = id;
306 target = item;
307 }
308 }
309 }
310 }
311 } else {
312 target = getDesktop()->getDocument()->getRoot();
313 }
314 if (target) {
315 renderPreview(target);
316 }
317#if ICON_VERBOSE
318 g_message( "%s resetting timer", getTimestr().c_str() );
319#endif // ICON_VERBOSE
320 timer->reset();
321 }
322}
323
325{
326 bool callAgain = true;
327 if (!timer) {
328 timer = std::make_unique<Glib::Timer>();
329 }
330 if ( timer->elapsed() > minDelay ) {
331#if ICON_VERBOSE
332 g_message( "%s refreshCB() timer has progressed", getTimestr().c_str() );
333#endif // ICON_VERBOSE
334 callAgain = false;
336#if ICON_VERBOSE
337 g_message( "%s refreshCB() setting pending false", getTimestr().c_str() );
338#endif // ICON_VERBOSE
339 pending = false;
340 }
341 return callAgain;
342}
343
345{
346 if (!pending) {
347 pending = true;
348#if ICON_VERBOSE
349 g_message( "%s queueRefresh() Setting pending true", getTimestr().c_str() );
350#endif // ICON_VERBOSE
351 if (!timer) {
352 timer = std::make_unique<Glib::Timer>();
353 }
354 Glib::signal_idle().connect( sigc::mem_fun(*this, &IconPreviewPanel::refreshCB), Glib::PRIORITY_DEFAULT_IDLE );
355 }
356}
357
359{
361 bool selectionOnly = (selectionButton && selectionButton->get_active());
362 prefs->setBool("/iconpreview/selectionOnly", selectionOnly);
363 if ( !selectionOnly ) {
364 targetId.clear();
365 }
366
368}
369
370static void overlayPixels(unsigned char *px, int width, int height, int stride, unsigned r, unsigned g, unsigned b)
371{
372 int bytesPerPixel = 4;
373 int spacing = 4;
374 for ( int y = 0; y < height; y += spacing ) {
375 auto ptr = px + y * stride;
376 for ( int x = 0; x < width; x += spacing ) {
377 *(ptr++) = 0xff;
378 *(ptr++) = r;
379 *(ptr++) = g;
380 *(ptr++) = b;
381
382 ptr += bytesPerPixel * (spacing - 1);
383 }
384 }
385
386 if ( width > 1 && height > 1 ) {
387 // point at the last pixel
388 auto ptr = px + ((height-1) * stride) + ((width - 1) * bytesPerPixel);
389
390 if ( width > 2 ) {
391 px[4] = 0xff;
392 px[5] = r;
393 px[6] = g;
394 px[7] = b;
395
396 ptr[-12] = 0xff;
397 ptr[-11] = r;
398 ptr[-10] = g;
399 ptr[-9] = b;
400 }
401
402 ptr[-4] = 0xff;
403 ptr[-3] = r;
404 ptr[-2] = g;
405 ptr[-1] = b;
406
407 px[0 + stride] = 0xff;
408 px[1 + stride] = r;
409 px[2 + stride] = g;
410 px[3 + stride] = b;
411
412 ptr[0 - stride] = 0xff;
413 ptr[1 - stride] = r;
414 ptr[2 - stride] = g;
415 ptr[3 - stride] = b;
416
417 if ( height > 2 ) {
418 ptr[0 - stride * 3] = 0xff;
419 ptr[1 - stride * 3] = r;
420 ptr[2 - stride * 3] = g;
421 ptr[3 - stride * 3] = b;
422 }
423 }
424}
425
426// takes doc, drawing, icon, and icon name to produce pixels
427static Cairo::RefPtr<Cairo::ImageSurface> sp_icon_doc_icon(SPDocument *doc, Drawing &drawing, char const *name, unsigned psize)
428{
429 if (!doc) {
430 return nullptr;
431 }
432
433 auto const item = cast<SPItem>(doc->getObjectById(name));
434 if (!item) {
435 return nullptr;
436 }
437
438 // Find bbox in document. This is in document coordinates, i.e. pixels.
439 auto const dbox = item->parent
441 : *doc->preferredBounds();
442 if (!dbox) {
443 return nullptr;
444 }
445
446 bool const dump = Inkscape::Preferences::get()->getBool("/debug/icons/dumpSvg");
447
448 // Update to renderable state.
449 double sf = 1.0;
450 drawing.root()->setTransform(Geom::Scale(sf));
451 drawing.update();
452 // Item integer bbox in points.
453 // NOTE: previously, each rect coordinate was rounded using floor(c + 0.5)
454 Geom::IntRect ibox = dbox->roundOutwards();
455
456 if (dump) {
457 g_message(" box --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), (double)ibox.right(), (double)ibox.bottom());
458 }
459
460 // Find button visible area.
461 int width = ibox.width();
462 int height = ibox.height();
463
464 if (dump) {
465 g_message(" vis --'%s' (%d,%d)", name, width, height);
466 }
467
468 if (int block = std::max(width, height); block != static_cast<int>(psize)) {
469 if (dump) {
470 g_message(" resizing");
471 }
472 sf = (double)psize / (double)block;
473
474 drawing.root()->setTransform(Geom::Scale(sf));
475 drawing.update();
476
477 auto scaled_box = *dbox * Geom::Scale(sf);
478 ibox = scaled_box.roundOutwards();
479 if (dump) {
480 g_message(" box2 --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), (double)ibox.right(), (double)ibox.bottom());
481 }
482
483 // Find button visible area.
484 width = ibox.width();
485 height = ibox.height();
486 if (dump) {
487 g_message(" vis2 --'%s' (%d,%d)", name, width, height);
488 }
489 }
490
491 auto pdim = Geom::IntPoint(psize, psize);
492 int dx, dy;
493 //dx = (psize - width) / 2;
494 //dy = (psize - height) / 2;
495 dx=dy=psize;
496 dx=(dx-width)/2; // watch out for psize, since 'unsigned'-'signed' can cause problems if the result is negative
497 dy=(dy-height)/2;
499 // Actual renderable area.
500 Geom::IntRect ua = *Geom::intersect(ibox, area);
501
502 if (dump) {
503 g_message(" area --'%s' (%f,%f)-(%f,%f)", name, (double)area.left(), (double)area.top(), (double)area.right(), (double)area.bottom());
504 g_message(" ua --'%s' (%f,%f)-(%f,%f)", name, (double)ua.left(), (double)ua.top(), (double)ua.right(), (double)ua.bottom());
505 }
506
507 // Render.
508 auto s = Cairo::ImageSurface::create(Cairo::ImageSurface::Format::ARGB32, psize, psize);
509 auto dc = DrawingContext(s->cobj(), ua.min());
510
511 auto bg = doc->getPageManager().getDefaultBackgroundColor();
512
513 auto cr = Cairo::Context::create(s);
514 cr->set_source_rgba(bg[0], bg[1], bg[2], bg[3]);
515 cr->rectangle(0, 0, psize, psize);
516 cr->fill();
517 cr->save();
518 cr.reset();
519
520 drawing.render(dc, ua);
521
522 if (Preferences::get()->getBool("/debug/icons/overlaySvg")) {
523 s->flush();
524 overlayPixels(s->get_data(), psize, psize, s->get_stride(), 0x00, 0x00, 0xff);
525 s->mark_dirty();
526 }
527
528 return s;
529}
530
532{
533 SPDocument * doc = obj->document;
534 gchar const * id = obj->getId();
535 if ( !renderTimer ) {
536 renderTimer = std::make_unique<Glib::Timer>();
537 }
538 renderTimer->reset(); // Reset the Timer, not the unique_ptr!
539
540#if ICON_VERBOSE
541 g_message("%s setting up to render '%s' as the icon", getTimestr().c_str(), id );
542#endif // ICON_VERBOSE
543
544 for (std::size_t i = 0; i < sizes.size(); ++i) {
545 textures[i] = to_texture(sp_icon_doc_icon(doc, *drawing, id, sizes[i]));
546 images[i]->set(textures[i]);
547 }
549
550 renderTimer->stop();
551 minDelay = std::max( 0.1, renderTimer->elapsed() * 3.0 );
552#if ICON_VERBOSE
553 g_message(" render took %f seconds.", renderTimer->elapsed());
554#endif // ICON_VERBOSE
555}
556
562
563void Magnifier::snapshot_vfunc(Glib::RefPtr<Gtk::Snapshot> const &snapshot)
564{
565 if (!_texture) {
566 snapshot->append_color(Gdk::RGBA{0, 0, 0}, Gdk::Rectangle{0, 0, get_width(), get_height()});
567 return;
568 }
569 auto node = gsk_texture_scale_node_new(_texture->gobj(), Gdk::Graphene::Rect{0, 0, 128, 128}.gobj(), GSK_SCALING_FILTER_NEAREST);
570 gtk_snapshot_append_node(snapshot->gobj(), node);
571 gsk_render_node_unref(node);
572}
573
574} // namespace Inkscape::UI::Dialog
575
576/*
577 Local Variables:
578 mode:c++
579 c-file-style:"stroustrup"
580 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
581 indent-tabs-mode:nil
582 fill-column:99
583 End:
584*/
585// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Axis aligned, non-empty, generic rectangle.
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
Two-dimensional point with integer coordinates.
Definition int-point.h:57
Scaling from the origin.
Definition transforms.h:150
Minimal wrapper over Cairo.
void setTransform(Geom::Affine const &trans)
DrawingItem * root()
Definition drawing.h:47
void update(Geom::IntRect const &area=Geom::IntRect::infinite(), Geom::Affine const &affine=Geom::identity(), unsigned flags=DrawingItem::STATE_ALL, unsigned reset=0)
Definition drawing.cpp:231
void render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags=0) const
Definition drawing.cpp:242
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:230
Colors::Color const & getDefaultBackgroundColor() const
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
std::vector< Glib::ustring > getAllDirs(Glib::ustring const &path)
Get all subdirectories of the specified directory.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
DialogBase is the base class for the dialog system.
Definition dialog-base.h:40
Selection * getSelection() const
Definition dialog-base.h:82
SPDocument * getDocument() const
Definition dialog-base.h:81
SPDesktop * getDesktop() const
Definition dialog-base.h:77
std::vector< Gtk::Image * > images
void selectionModified(Selection *selection, guint flags) override
void removeDrawing()
Safely delete the Inkscape::Drawing and references to it.
std::vector< Glib::RefPtr< Gdk::Texture > > textures
std::unique_ptr< Glib::Timer > timer
void selectionChanged(Selection *selection) override
std::unique_ptr< Drawing > drawing
std::unique_ptr< Glib::Timer > renderTimer
std::vector< Gtk::ToggleButton * > buttons
std::vector< Glib::ustring > labels
Glib::RefPtr< Gdk::Texture > _texture
void snapshot_vfunc(Glib::RefPtr< Gtk::Snapshot > const &snapshot) override
void set(Glib::RefPtr< Gdk::Texture > const &texture)
SPDocument * getDocument() const
Definition desktop.h:189
Typed SVG document implementation.
Definition document.h:101
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
SPObject * getObjectById(std::string const &id) const
Geom::OptRect preferredBounds() const
Definition document.cpp:969
Inkscape::PageManager & getPageManager()
Definition document.h:162
sigc::connection connectDestroy(sigc::signal< void()>::slot_type slot)
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1026
Inkscape::DrawingItem * invoke_show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags)
Definition sp-item.cpp:1277
void invoke_hide(unsigned int key)
Definition sp-item.cpp:1328
static unsigned int display_key_new(unsigned numkeys)
Allocates unique integer keys.
Definition sp-item.cpp:1254
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPObject * parent
Definition sp-object.h:189
Editable view implementation.
Cairo drawing context with Inkscape extensions.
SVG drawing for display.
A simple dialog for previewing icon representation.
SPItem * item
Inkscape::XML::Node * node
Glib::ustring label
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
Dialog code.
Definition desktop.h:117
static void overlayPixels(unsigned char *px, int width, int height, int stride, unsigned r, unsigned g, unsigned b)
static Cairo::RefPtr< Cairo::ImageSurface > sp_icon_doc_icon(SPDocument *doc, Drawing &drawing, char const *name, unsigned psize)
static Glib::ustring getTimestr()
static constexpr int height
static void pack(PackType const pack_type, Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Definition pack.cpp:121
static void append(std::vector< T > &target, std::vector< T > &&source)
int stride
SPRoot: SVG <svg> implementation.
double width
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gdk::Texture > to_texture(Cairo::RefPtr< Cairo::Surface > const &surface)
Convert an image surface in ARGB32 format to a texture.
Definition util.cpp:495