1 /** @file
2 * @brief A simple dialog for previewing icon representation.
3 */
4 /* Authors:
5 * Jon A. Cruz
6 * Bob Jamison
7 * Other dudes from The Inkscape Organization
8 *
9 * Copyright (C) 2004 Bob Jamison
10 * Copyright (C) 2005,2010 Jon A. Cruz
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtk.h>
19 #include <glib/gmem.h>
20 #include <glibmm/i18n.h>
21 #include <gtkmm/alignment.h>
22 #include <gtkmm/buttonbox.h>
23 #include <gtkmm/stock.h>
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "display/nr-arena.h"
28 #include "document.h"
29 #include "inkscape.h"
30 #include "preferences.h"
31 #include "selection.h"
32 #include "sp-root.h"
33 #include "xml/repr.h"
35 #include "icon-preview.h"
37 extern "C" {
38 // takes doc, root, icon, and icon name to produce pixels
39 guchar *
40 sp_icon_doc_icon( SPDocument *doc, NRArenaItem *root,
41 const gchar *name, unsigned int psize );
42 }
44 #define noICON_VERBOSE 1
46 namespace Inkscape {
47 namespace UI {
48 namespace Dialog {
51 IconPreviewPanel &IconPreviewPanel::getInstance()
52 {
53 IconPreviewPanel *instance = new IconPreviewPanel();
55 instance->refreshPreview();
57 return *instance;
58 }
60 //#########################################################################
61 //## E V E N T S
62 //#########################################################################
64 void IconPreviewPanel::on_button_clicked(int which)
65 {
66 if ( hot != which ) {
67 buttons[hot]->set_active( false );
69 hot = which;
70 updateMagnify();
71 _getContents()->queue_draw();
72 }
73 }
78 //#########################################################################
79 //## C O N S T R U C T O R / D E S T R U C T O R
80 //#########################################################################
81 /**
82 * Constructor
83 */
84 IconPreviewPanel::IconPreviewPanel() :
85 UI::Widget::Panel("", "/dialogs/iconpreview", SP_VERB_VIEW_ICON_PREVIEW),
86 deskTrack(),
87 desktop(0),
88 document(0),
89 timer(0),
90 renderTimer(0),
91 pending(false),
92 minDelay(0.1),
93 targetId(),
94 hot(1),
95 selectionButton(0),
96 desktopChangeConn(),
97 docReplacedConn(),
98 docModConn(),
99 selChangedConn()
100 {
101 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
102 numEntries = 0;
104 bool pack = prefs->getBool("/iconpreview/pack", true);
106 std::vector<Glib::ustring> pref_sizes = prefs->getAllDirs("/iconpreview/sizes/default");
107 std::vector<int> rawSizes;
109 for (std::vector<Glib::ustring>::iterator i = pref_sizes.begin(); i != pref_sizes.end(); ++i) {
110 if (prefs->getBool(*i + "/show", true)) {
111 int sizeVal = prefs->getInt(*i + "/value", -1);
112 if (sizeVal > 0) {
113 rawSizes.push_back(sizeVal);
114 }
115 }
116 }
118 if ( !rawSizes.empty() ) {
119 numEntries = rawSizes.size();
120 sizes = new int[numEntries];
121 int i = 0;
122 for ( std::vector<int>::iterator it = rawSizes.begin(); it != rawSizes.end(); ++it, ++i ) {
123 sizes[i] = *it;
124 }
125 }
127 if ( numEntries < 1 )
128 {
129 numEntries = 5;
130 sizes = new int[numEntries];
131 sizes[0] = 16;
132 sizes[1] = 24;
133 sizes[2] = 32;
134 sizes[3] = 48;
135 sizes[4] = 128;
136 }
138 pixMem = new guchar*[numEntries];
139 images = new Gtk::Image*[numEntries];
140 labels = new Glib::ustring*[numEntries];
141 buttons = new Gtk::ToggleToolButton*[numEntries];
144 for ( int i = 0; i < numEntries; i++ ) {
145 char *label = g_strdup_printf(_("%d x %d"), sizes[i], sizes[i]);
146 labels[i] = new Glib::ustring(label);
147 g_free(label);
148 pixMem[i] = 0;
149 images[i] = 0;
150 }
153 magLabel.set_label( *labels[hot] );
155 Gtk::VBox* magBox = new Gtk::VBox();
157 Gtk::Frame *magFrame = Gtk::manage(new Gtk::Frame(_("Magnified:")));
158 magFrame->add( magnified );
160 magBox->pack_start( *magFrame, Gtk::PACK_EXPAND_WIDGET );
161 magBox->pack_start( magLabel, Gtk::PACK_SHRINK );
164 Gtk::VBox *verts = new Gtk::VBox();
165 Gtk::HBox *horiz = 0;
166 int previous = 0;
167 int avail = 0;
168 for ( int i = numEntries - 1; i >= 0; --i ) {
169 pixMem[i] = new guchar[4 * sizes[i] * sizes[i]];
170 memset( pixMem[i], 0x00, 4 * sizes[i] * sizes[i] );
172 GdkPixbuf *pb = gdk_pixbuf_new_from_data( pixMem[i], GDK_COLORSPACE_RGB, TRUE, 8, sizes[i], sizes[i], sizes[i] * 4, /*(GdkPixbufDestroyNotify)g_free*/NULL, NULL );
173 GtkImage* img = GTK_IMAGE( gtk_image_new_from_pixbuf( pb ) );
174 images[i] = Glib::wrap(img);
175 Glib::ustring label(*labels[i]);
176 buttons[i] = new Gtk::ToggleToolButton(label);
177 buttons[i]->set_active( i == hot );
178 if ( prefs->getBool("/iconpreview/showFrames", true) ) {
179 Gtk::Frame *frame = new Gtk::Frame();
180 frame->set_shadow_type(Gtk::SHADOW_ETCHED_IN);
181 frame->add(*images[i]);
182 buttons[i]->set_icon_widget(*Gtk::manage(frame));
183 } else {
184 buttons[i]->set_icon_widget(*images[i]);
185 }
187 tips.set_tip((*buttons[i]), label);
189 buttons[i]->signal_clicked().connect( sigc::bind<int>( sigc::mem_fun(*this, &IconPreviewPanel::on_button_clicked), i) );
192 Gtk::Alignment *align = Gtk::manage(new Gtk::Alignment(0.5, 0.5, 0, 0));
193 align->add(*buttons[i]);
195 int pad = 12;
196 if ( !pack || ( (avail == 0) && (previous == 0) ) ) {
197 verts->pack_end(*align, Gtk::PACK_SHRINK);
198 previous = sizes[i];
199 avail = sizes[i];
200 } else {
201 if ((avail < pad) || ((sizes[i] > avail) && (sizes[i] < previous))) {
202 horiz = 0;
203 }
204 if ((horiz == 0) && (sizes[i] <= previous)) {
205 avail = previous;
206 }
207 if (sizes[i] <= avail) {
208 if (!horiz) {
209 horiz = Gtk::manage(new Gtk::HBox());
210 avail = previous;
211 verts->pack_end(*horiz, Gtk::PACK_SHRINK);
212 }
213 horiz->pack_start(*align, Gtk::PACK_EXPAND_WIDGET);
214 avail -= sizes[i];
215 avail -= pad; // a little extra for padding
216 } else {
217 horiz = 0;
218 verts->pack_end(*align, Gtk::PACK_SHRINK);
219 }
220 }
221 }
223 iconBox.pack_start(splitter);
224 splitter.pack1( *magBox, true, true );
225 Gtk::Frame *actuals = Gtk::manage(new Gtk::Frame(_("Actual Size:")));
226 actuals->add(*verts);
227 splitter.pack2( *actuals, false, false );
230 selectionButton = new Gtk::CheckButton(C_("Icon preview window", "Sele_ction"), true);//selectionButton = (Gtk::ToggleButton*) gtk_check_button_new_with_mnemonic(_("_Selection")); // , GTK_RESPONSE_APPLY
231 magBox->pack_start( *selectionButton, Gtk::PACK_SHRINK );
232 tips.set_tip((*selectionButton), _("Selection only or whole document"));
233 selectionButton->signal_clicked().connect( sigc::mem_fun(*this, &IconPreviewPanel::modeToggled) );
235 gint val = prefs->getBool("/iconpreview/selectionOnly");
236 selectionButton->set_active( val != 0 );
239 _getContents()->pack_start(iconBox, Gtk::PACK_SHRINK);
241 show_all_children();
243 // Connect this up last
244 desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &IconPreviewPanel::setDesktop) );
245 deskTrack.connect(GTK_WIDGET(gobj()));
246 }
248 IconPreviewPanel::~IconPreviewPanel()
249 {
250 setDesktop(0);
251 if (timer) {
252 timer->stop();
253 delete timer;
254 timer = 0;
255 }
256 if ( renderTimer ) {
257 renderTimer->stop();
258 delete renderTimer;
259 renderTimer = 0;
260 }
262 selChangedConn.disconnect();
263 docModConn.disconnect();
264 docReplacedConn.disconnect();
265 desktopChangeConn.disconnect();
266 deskTrack.disconnect();
267 }
269 //#########################################################################
270 //## M E T H O D S
271 //#########################################################################
274 #if ICON_VERBOSE
275 static Glib::ustring getTimestr()
276 {
277 Glib::ustring str;
278 GTimeVal now = {0, 0};
279 g_get_current_time(&now);
280 glong secs = now.tv_sec % 60;
281 glong mins = (now.tv_sec / 60) % 60;
282 gchar *ptr = g_strdup_printf(":%02ld:%02ld.%06ld", mins, secs, now.tv_usec);
283 str = ptr;
284 g_free(ptr);
285 ptr = 0;
286 return str;
287 }
288 #endif // ICON_VERBOSE
290 void IconPreviewPanel::setDesktop( SPDesktop* desktop )
291 {
292 Panel::setDesktop(desktop);
294 SPDocument *newDoc = (desktop) ? desktop->doc() : 0;
296 if ( desktop != this->desktop ) {
297 docReplacedConn.disconnect();
298 selChangedConn.disconnect();
300 this->desktop = Panel::getDesktop();
301 if ( this->desktop ) {
302 docReplacedConn = this->desktop->connectDocumentReplaced(sigc::hide<0>(sigc::mem_fun(this, &IconPreviewPanel::setDocument)));
303 if ( this->desktop->selection && Inkscape::Preferences::get()->getBool("/iconpreview/autoRefresh", true) ) {
304 selChangedConn = desktop->selection->connectChanged(sigc::hide(sigc::mem_fun(this, &IconPreviewPanel::queueRefresh)));
305 }
306 }
307 }
308 setDocument(newDoc);
309 deskTrack.setBase(desktop);
310 }
312 void IconPreviewPanel::setDocument( SPDocument *document )
313 {
314 if (this->document != document) {
315 docModConn.disconnect();
317 this->document = document;
318 if (this->document) {
319 if ( Inkscape::Preferences::get()->getBool("/iconpreview/autoRefresh", true) ) {
320 docModConn = this->document->connectModified(sigc::hide(sigc::mem_fun(this, &IconPreviewPanel::queueRefresh)));
321 }
322 queueRefresh();
323 }
324 }
325 }
327 void IconPreviewPanel::refreshPreview()
328 {
329 SPDesktop *desktop = getDesktop();
330 if (!timer) {
331 timer = new Glib::Timer();
332 }
333 if (timer->elapsed() < minDelay) {
334 #if ICON_VERBOSE
335 g_message( "%s Deferring refresh as too soon. calling queueRefresh()", getTimestr().c_str() );
336 #endif //ICON_VERBOSE
337 // Do not refresh too quickly
338 queueRefresh();
339 } else if ( desktop ) {
340 #if ICON_VERBOSE
341 g_message( "%s Refreshing preview.", getTimestr().c_str() );
342 #endif // ICON_VERBOSE
343 bool hold = Inkscape::Preferences::get()->getBool("/iconpreview/selectionHold", true);
344 SPObject *target = 0;
345 if ( selectionButton && selectionButton->get_active() )
346 {
347 target = (hold && !targetId.empty()) ? desktop->doc()->getObjectById( targetId.c_str() ) : 0;
348 if ( !target ) {
349 targetId.clear();
350 Inkscape::Selection * sel = sp_desktop_selection(desktop);
351 if ( sel ) {
352 //g_message("found a selection to play with");
354 GSList const *items = sel->itemList();
355 while ( items && !target ) {
356 SPItem* item = SP_ITEM( items->data );
357 SPObject * obj = SP_OBJECT(item);
358 gchar const *id = obj->getId();
359 if ( id ) {
360 targetId = id;
361 target = obj;
362 }
364 items = g_slist_next(items);
365 }
366 }
367 }
368 } else {
369 target = desktop->currentRoot();
370 }
371 if ( target ) {
372 renderPreview(target);
373 }
374 #if ICON_VERBOSE
375 g_message( "%s resetting timer", getTimestr().c_str() );
376 #endif // ICON_VERBOSE
377 timer->reset();
378 }
379 }
381 bool IconPreviewPanel::refreshCB()
382 {
383 bool callAgain = true;
384 if (!timer) {
385 timer = new Glib::Timer();
386 }
387 if ( timer->elapsed() > minDelay ) {
388 #if ICON_VERBOSE
389 g_message( "%s refreshCB() timer has progressed", getTimestr().c_str() );
390 #endif // ICON_VERBOSE
391 callAgain = false;
392 refreshPreview();
393 #if ICON_VERBOSE
394 g_message( "%s refreshCB() setting pending false", getTimestr().c_str() );
395 #endif // ICON_VERBOSE
396 pending = false;
397 }
398 return callAgain;
399 }
401 void IconPreviewPanel::queueRefresh()
402 {
403 if (!pending) {
404 pending = true;
405 #if ICON_VERBOSE
406 g_message( "%s queueRefresh() Setting pending true", getTimestr().c_str() );
407 #endif // ICON_VERBOSE
408 if (!timer) {
409 timer = new Glib::Timer();
410 }
411 Glib::signal_idle().connect( sigc::mem_fun(this, &IconPreviewPanel::refreshCB), Glib::PRIORITY_DEFAULT_IDLE );
412 }
413 }
415 void IconPreviewPanel::modeToggled()
416 {
417 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
418 bool selectionOnly = (selectionButton && selectionButton->get_active());
419 prefs->setBool("/iconpreview/selectionOnly", selectionOnly);
420 if ( !selectionOnly ) {
421 targetId.clear();
422 }
424 refreshPreview();
425 }
427 void IconPreviewPanel::renderPreview( SPObject* obj )
428 {
429 SPDocument * doc = SP_OBJECT_DOCUMENT(obj);
430 gchar const * id = obj->getId();
431 if ( !renderTimer ) {
432 renderTimer = new Glib::Timer();
433 }
434 renderTimer->reset();
436 #if ICON_VERBOSE
437 g_message("%s setting up to render '%s' as the icon", getTimestr().c_str(), id );
438 #endif // ICON_VERBOSE
440 NRArenaItem *root = NULL;
442 /* Create new arena */
443 NRArena *arena = NRArena::create();
445 /* Create ArenaItem and set transform */
446 unsigned int visionkey = sp_item_display_key_new(1);
448 root = sp_item_invoke_show ( SP_ITEM( SP_DOCUMENT_ROOT(doc) ),
449 arena, visionkey, SP_ITEM_SHOW_DISPLAY );
451 for ( int i = 0; i < numEntries; i++ ) {
452 guchar * px = sp_icon_doc_icon( doc, root, id, sizes[i] );
453 // g_message( " size %d %s", sizes[i], (px ? "worked" : "failed") );
454 if ( px ) {
455 memcpy( pixMem[i], px, sizes[i] * sizes[i] * 4 );
456 g_free( px );
457 px = 0;
458 } else {
459 memset( pixMem[i], 0, sizes[i] * sizes[i] * 4 );
460 }
461 images[i]->queue_draw();
462 }
463 updateMagnify();
465 sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), visionkey);
466 nr_object_unref((NRObject *) arena);
467 renderTimer->stop();
468 minDelay = std::max( 0.1, renderTimer->elapsed() * 3.0 );
469 #if ICON_VERBOSE
470 g_message(" render took %f seconds.", renderTimer->elapsed());
471 #endif // ICON_VERBOSE
472 }
474 void IconPreviewPanel::updateMagnify()
475 {
476 Glib::RefPtr<Gdk::Pixbuf> buf = images[hot]->get_pixbuf()->scale_simple( 128, 128, Gdk::INTERP_NEAREST );
477 magLabel.set_label( *labels[hot] );
478 magnified.set( buf );
479 magnified.queue_draw();
480 magnified.get_parent()->queue_draw();
481 }
483 } //namespace Dialogs
484 } //namespace UI
485 } //namespace Inkscape
487 /*
488 Local Variables:
489 mode:c++
490 c-file-style:"stroustrup"
491 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
492 indent-tabs-mode:nil
493 fill-column:99
494 End:
495 */
496 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :