1 /*
2 * A simple panel for color swatches
3 *
4 * Authors:
5 * Jon A. Cruz
6 *
7 * Copyright (C) 2005 Jon A. Cruz
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 */
11 #ifdef HAVE_CONFIG_H
12 # include <config.h>
13 #endif
15 #include <errno.h>
17 #include <gtk/gtkdialog.h> //for GTK_RESPONSE* types
18 #include <gtk/gtkdnd.h>
20 #include <glibmm/i18n.h>
21 #include <gdkmm/pixbuf.h>
22 #include "inkscape.h"
23 #include "document.h"
24 #include "desktop-handles.h"
25 #include "extension/db.h"
26 #include "inkscape.h"
27 #include "svg/svg-color.h"
28 #include "desktop-style.h"
29 #include "io/sys.h"
30 #include "path-prefix.h"
31 #include "swatches.h"
32 #include "sp-item.h"
34 #include "eek-preview.h"
36 namespace Inkscape {
37 namespace UI {
38 namespace Dialogs {
40 SwatchesPanel* SwatchesPanel::instance = 0;
43 ColorItem::ColorItem( unsigned int r, unsigned int g, unsigned int b, Glib::ustring& name ) :
44 def( r, g, b, name ),
45 _linkIsTone(false),
46 _linkPercent(0),
47 _linkGray(0),
48 _linkSrc(0)
49 {
50 }
52 ColorItem::~ColorItem()
53 {
54 }
56 ColorItem::ColorItem(ColorItem const &other) :
57 Inkscape::UI::Previewable()
58 {
59 if ( this != &other ) {
60 *this = other;
61 }
62 }
64 ColorItem &ColorItem::operator=(ColorItem const &other)
65 {
66 if ( this != &other ) {
67 def = other.def;
69 // TODO - correct linkage
70 _linkSrc = other._linkSrc;
71 g_message("Erk!");
72 }
73 return *this;
74 }
77 class JustForNow
78 {
79 public:
80 JustForNow() : _prefWidth(0) {}
82 Glib::ustring _name;
83 int _prefWidth;
84 std::vector<ColorItem*> _colors;
85 };
87 static std::vector<JustForNow*> possible;
91 typedef enum {
92 APP_X_INKY_COLOR_ID = 0,
93 APP_X_INKY_COLOR = 0,
94 APP_X_COLOR,
95 TEXT_DATA
96 } colorFlavorType;
98 static const GtkTargetEntry sourceColorEntries[] = {
99 // {"application/x-inkscape-color-id", GTK_TARGET_SAME_APP, APP_X_INKY_COLOR_ID},
100 {"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
101 {"application/x-color", 0, APP_X_COLOR},
102 {"text/plain", 0, TEXT_DATA},
103 };
105 static void dragGetColorData( GtkWidget *widget,
106 GdkDragContext *drag_context,
107 GtkSelectionData *data,
108 guint info,
109 guint time,
110 gpointer user_data)
111 {
112 static GdkAtom typeXColor = gdk_atom_intern("application/x-color", FALSE);
113 static GdkAtom typeText = gdk_atom_intern("text/plain", FALSE);
115 ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
116 if ( info == TEXT_DATA ) {
117 gchar* tmp = g_strdup_printf("#%02x%02x%02x", item->def.getR(), item->def.getG(), item->def.getB() );
119 gtk_selection_data_set( data,
120 typeText,
121 8, // format
122 (guchar*)tmp,
123 strlen((const char*)tmp) + 1);
124 g_free(tmp);
125 tmp = 0;
126 } else if ( info == APP_X_INKY_COLOR ) {
127 Glib::ustring paletteName;
129 // Find where this thing came from
130 bool found = false;
131 int index = 0;
132 for ( std::vector<JustForNow*>::iterator it = possible.begin(); it != possible.end() && !found; ++it ) {
133 JustForNow* curr = *it;
134 index = 0;
135 for ( std::vector<ColorItem*>::iterator zz = curr->_colors.begin(); zz != curr->_colors.end(); ++zz ) {
136 if ( item == *zz ) {
137 found = true;
138 paletteName = curr->_name;
139 break;
140 } else {
141 index++;
142 }
143 }
144 }
146 // if ( found ) {
147 // g_message("Found the color at entry %d in palette '%s'", index, paletteName.c_str() );
148 // } else {
149 // g_message("Unable to find the color");
150 // }
151 int itemCount = 4 + 1 + 1 + paletteName.length();
153 guint16* tmp = new guint16[itemCount]
154 ;
155 tmp[0] = (item->def.getR() << 8) | item->def.getR();
156 tmp[1] = (item->def.getG() << 8) | item->def.getG();
157 tmp[2] = (item->def.getB() << 8) | item->def.getB();
158 tmp[3] = 0xffff;
160 tmp[4] = index;
161 tmp[5] = paletteName.length();
162 for ( unsigned int i = 0; i < paletteName.length(); i++ ) {
163 tmp[6 + i] = paletteName[i];
164 }
165 gtk_selection_data_set( data,
166 typeXColor,
167 16, // format
168 reinterpret_cast<const guchar*>(tmp),
169 itemCount * 2);
170 delete[] tmp;
171 } else {
172 guint16 tmp[4];
173 tmp[0] = (item->def.getR() << 8) | item->def.getR();
174 tmp[1] = (item->def.getG() << 8) | item->def.getG();
175 tmp[2] = (item->def.getB() << 8) | item->def.getB();
176 tmp[3] = 0xffff;
177 gtk_selection_data_set( data,
178 typeXColor,
179 16, // format
180 reinterpret_cast<const guchar*>(tmp),
181 (3+1) * 2);
182 }
183 }
185 static void dragBegin( GtkWidget *widget, GdkDragContext* dc, gpointer data )
186 {
187 ColorItem* item = reinterpret_cast<ColorItem*>(data);
188 if ( item )
189 {
190 Glib::RefPtr<Gdk::Pixbuf> thumb = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, 32, 24 );
191 guint32 fillWith = (0xff000000 & (item->def.getR() << 24))
192 | (0x00ff0000 & (item->def.getG() << 16))
193 | (0x0000ff00 & (item->def.getB() << 8));
194 thumb->fill( fillWith );
195 gtk_drag_set_icon_pixbuf( dc, thumb->gobj(), 0, 0 );
196 }
198 }
200 //"drag-drop"
201 // gboolean dragDropColorData( GtkWidget *widget,
202 // GdkDragContext *drag_context,
203 // gint x,
204 // gint y,
205 // guint time,
206 // gpointer user_data)
207 // {
208 // // TODO finish
210 // return TRUE;
211 // }
213 static void bouncy( GtkWidget* widget, gpointer callback_data ) {
214 ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
215 if ( item ) {
216 item->buttonClicked(false);
217 }
218 }
220 static void bouncy2( GtkWidget* widget, gint arg1, gpointer callback_data ) {
221 ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
222 if ( item ) {
223 item->buttonClicked(true);
224 }
225 }
227 static void dieDieDie( GtkObject *obj, gpointer user_data )
228 {
229 g_message("die die die %p %p", obj, user_data );
230 }
232 static const GtkTargetEntry destColorTargets[] = {
233 // {"application/x-inkscape-color-id", GTK_TARGET_SAME_APP, APP_X_INKY_COLOR_ID},
234 {"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
235 {"application/x-color", 0, APP_X_COLOR},
236 };
238 #include "color.h" // for SP_RGBA32_U_COMPOSE
240 void ColorItem::_dropDataIn( GtkWidget *widget,
241 GdkDragContext *drag_context,
242 gint x, gint y,
243 GtkSelectionData *data,
244 guint info,
245 guint event_time,
246 gpointer user_data)
247 {
248 // g_message(" droppy droppy %d", info);
249 switch (info) {
250 case APP_X_INKY_COLOR:
251 {
252 if ( data->length >= 8 ) {
253 // Careful about endian issues.
254 guint16* dataVals = (guint16*)data->data;
255 if ( user_data ) {
256 ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
257 if ( item->def.isEditable() ) {
258 // Shove on in the new value
259 item->def.setRGB( 0x0ff & (dataVals[0] >> 8), 0x0ff & (dataVals[1] >> 8), 0x0ff & (dataVals[2] >> 8) );
260 }
261 }
262 }
263 break;
264 }
265 case APP_X_COLOR:
266 {
267 if ( data->length == 8 ) {
268 // Careful about endian issues.
269 guint16* dataVals = (guint16*)data->data;
270 // {
271 // gchar c[64] = {0};
272 // sp_svg_write_color( c, 64,
273 // SP_RGBA32_U_COMPOSE(
274 // 0x0ff & (dataVals[0] >> 8),
275 // 0x0ff & (dataVals[1] >> 8),
276 // 0x0ff & (dataVals[2] >> 8),
277 // 0xff // can't have transparency in the color itself
278 // //0x0ff & (data->data[3] >> 8),
279 // ));
280 // }
281 if ( user_data ) {
282 ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
283 if ( item->def.isEditable() ) {
284 // Shove on in the new value
285 item->def.setRGB( 0x0ff & (dataVals[0] >> 8), 0x0ff & (dataVals[1] >> 8), 0x0ff & (dataVals[2] >> 8) );
286 }
287 }
288 }
289 break;
290 }
291 default:
292 g_message("unknown drop type");
293 }
295 }
297 static bool bruteForce( SPDocument* document, Inkscape::XML::Node* node, Glib::ustring const& match, int r, int g, int b )
298 {
299 bool changed = false;
301 if ( node ) {
302 gchar const * val = node->attribute("HOTFill");
303 if ( val && (match == val) ) {
304 SPObject *obj = document->getObjectByRepr( node );
306 gchar c[64] = {0};
307 sp_svg_write_color( c, 64, SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
308 SPCSSAttr *css = sp_repr_css_attr_new();
309 sp_repr_css_set_property( css, "fill", c );
311 sp_desktop_apply_css_recursive( (SPItem*)obj, css, true );
312 ((SPItem*)obj)->updateRepr();
314 changed = true;
315 }
317 val = node->attribute("HOTStroke");
318 if ( val && (match == val) ) {
319 SPObject *obj = document->getObjectByRepr( node );
321 gchar c[64] = {0};
322 sp_svg_write_color( c, 64, SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
323 SPCSSAttr *css = sp_repr_css_attr_new();
324 sp_repr_css_set_property( css, "stroke", c );
326 sp_desktop_apply_css_recursive( (SPItem*)obj, css, true );
327 ((SPItem*)obj)->updateRepr();
329 changed = true;
330 }
332 Inkscape::XML::Node* first = node->firstChild();
333 changed |= bruteForce( document, first, match, r, g, b );
335 changed |= bruteForce( document, node->next(), match, r, g, b );
336 }
338 return changed;
339 }
341 void ColorItem::_colorDefChanged(void* data)
342 {
343 ColorItem* item = reinterpret_cast<ColorItem*>(data);
344 if ( item ) {
345 for ( std::vector<Gtk::Widget*>::iterator it = item->_previews.begin(); it != item->_previews.end(); ++it ) {
346 Gtk::Widget* widget = *it;
347 if ( IS_EEK_PREVIEW(widget->gobj()) ) {
348 EekPreview * preview = EEK_PREVIEW(widget->gobj());
349 eek_preview_set_color( preview,
350 (item->def.getR() << 8) | item->def.getR(),
351 (item->def.getG() << 8) | item->def.getG(),
352 (item->def.getB() << 8) | item->def.getB() );
354 eek_preview_set_linked( preview, (LinkType)((item->_linkSrc ? PREVIEW_LINK_IN:0) | (item->_listeners.empty() ? 0:PREVIEW_LINK_OUT)) );
356 widget->queue_draw();
357 }
358 }
360 for ( std::vector<ColorItem*>::iterator it = item->_listeners.begin(); it != item->_listeners.end(); ++it ) {
361 guint r = item->def.getR();
362 guint g = item->def.getG();
363 guint b = item->def.getB();
365 if ( (*it)->_linkIsTone ) {
366 r = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * r) ) / 100;
367 g = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * g) ) / 100;
368 b = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * b) ) / 100;
369 } else {
370 r = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * r) ) / 100;
371 g = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * g) ) / 100;
372 b = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * b) ) / 100;
373 }
375 (*it)->def.setRGB( r, g, b );
376 }
379 // Look for objects using this color
380 {
381 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
382 if ( desktop ) {
383 SPDocument* document = SP_DT_DOCUMENT( desktop );
384 Inkscape::XML::Node *rroot = sp_document_repr_root( document );
385 if ( rroot ) {
387 // Find where this thing came from
388 Glib::ustring paletteName;
389 bool found = false;
390 int index = 0;
391 for ( std::vector<JustForNow*>::iterator it2 = possible.begin(); it2 != possible.end() && !found; ++it2 ) {
392 JustForNow* curr = *it2;
393 index = 0;
394 for ( std::vector<ColorItem*>::iterator zz = curr->_colors.begin(); zz != curr->_colors.end(); ++zz ) {
395 if ( item == *zz ) {
396 found = true;
397 paletteName = curr->_name;
398 break;
399 } else {
400 index++;
401 }
402 }
403 }
405 if ( !paletteName.empty() ) {
406 gchar* str = g_strdup_printf("%d|", index);
407 paletteName.insert( 0, str );
408 g_free(str);
409 str = 0;
411 if ( bruteForce( document, rroot, paletteName, item->def.getR(), item->def.getG(), item->def.getB() ) ) {
412 sp_document_done( document );
413 }
414 }
415 }
416 }
417 }
418 }
419 }
422 Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, Gtk::BuiltinIconSize size)
423 {
424 Gtk::Widget* widget = 0;
425 if ( style == PREVIEW_STYLE_BLURB ) {
426 Gtk::Label *lbl = new Gtk::Label(def.descr);
427 lbl->set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER);
428 widget = lbl;
429 } else {
430 Glib::ustring blank(" ");
431 if ( size == Gtk::ICON_SIZE_MENU ) {
432 blank = " ";
433 }
435 GtkWidget* eekWidget = eek_preview_new();
436 EekPreview * preview = EEK_PREVIEW(eekWidget);
437 Gtk::Widget* newBlot = Glib::wrap(eekWidget);
439 eek_preview_set_color( preview, (def.getR() << 8) | def.getR(), (def.getG() << 8) | def.getG(), (def.getB() << 8) | def.getB());
441 eek_preview_set_details( preview, (::PreviewStyle)style, (::ViewType)view, (::GtkIconSize)size );
442 eek_preview_set_linked( preview, (LinkType)((_linkSrc ? PREVIEW_LINK_IN:0) | (_listeners.empty() ? 0:PREVIEW_LINK_OUT)) );
444 def.addCallback( _colorDefChanged, this );
446 GValue val = {0, {{0}, {0}}};
447 g_value_init( &val, G_TYPE_BOOLEAN );
448 g_value_set_boolean( &val, FALSE );
449 g_object_set_property( G_OBJECT(preview), "focus-on-click", &val );
451 /*
452 Gtk::Button *btn = new Gtk::Button(blank);
453 Gdk::Color color;
454 color.set_rgb((_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b);
455 btn->modify_bg(Gtk::STATE_NORMAL, color);
456 btn->modify_bg(Gtk::STATE_ACTIVE, color);
457 btn->modify_bg(Gtk::STATE_PRELIGHT, color);
458 btn->modify_bg(Gtk::STATE_SELECTED, color);
460 Gtk::Widget* newBlot = btn;
461 */
463 tips.set_tip((*newBlot), def.descr);
465 /*
466 newBlot->signal_clicked().connect( sigc::mem_fun(*this, &ColorItem::buttonClicked) );
468 sigc::signal<void> type_signal_something;
469 */
470 g_signal_connect( G_OBJECT(newBlot->gobj()),
471 "clicked",
472 G_CALLBACK(bouncy),
473 this);
475 g_signal_connect( G_OBJECT(newBlot->gobj()),
476 "alt-clicked",
477 G_CALLBACK(bouncy2),
478 this);
480 gtk_drag_source_set( GTK_WIDGET(newBlot->gobj()),
481 GDK_BUTTON1_MASK,
482 sourceColorEntries,
483 G_N_ELEMENTS(sourceColorEntries),
484 GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY) );
486 g_signal_connect( G_OBJECT(newBlot->gobj()),
487 "drag-data-get",
488 G_CALLBACK(dragGetColorData),
489 this);
491 g_signal_connect( G_OBJECT(newBlot->gobj()),
492 "drag-begin",
493 G_CALLBACK(dragBegin),
494 this );
496 // g_signal_connect( G_OBJECT(newBlot->gobj()),
497 // "drag-drop",
498 // G_CALLBACK(dragDropColorData),
499 // this);
501 if ( def.isEditable() )
502 {
503 gtk_drag_dest_set( GTK_WIDGET(newBlot->gobj()),
504 GTK_DEST_DEFAULT_ALL,
505 destColorTargets,
506 G_N_ELEMENTS(destColorTargets),
507 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) );
510 g_signal_connect( G_OBJECT(newBlot->gobj()),
511 "drag-data-received",
512 G_CALLBACK(_dropDataIn),
513 this );
514 }
516 g_signal_connect( G_OBJECT(newBlot->gobj()),
517 "destroy",
518 G_CALLBACK(dieDieDie),
519 this);
522 widget = newBlot;
523 }
525 _previews.push_back( widget );
527 return widget;
528 }
530 void ColorItem::buttonClicked(bool secondary)
531 {
532 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
533 if (desktop) {
534 char const * attrName = secondary ? "stroke" : "fill";
535 guint32 rgba = (def.getR() << 24) | (def.getG() << 16) | (def.getB() << 8) | 0xff;
536 gchar c[64];
537 sp_svg_write_color(c, 64, rgba);
539 SPCSSAttr *css = sp_repr_css_attr_new();
540 sp_repr_css_set_property( css, attrName, c );
541 sp_desktop_set_style(desktop, css);
543 sp_repr_css_attr_unref(css);
544 sp_document_done (SP_DT_DOCUMENT (desktop));
545 }
546 }
551 static char* trim( char* str ) {
552 char* ret = str;
553 while ( *str && (*str == ' ' || *str == '\t') ) {
554 str++;
555 }
556 ret = str;
557 while ( *str ) {
558 str++;
559 }
560 str--;
561 while ( str > ret && ( *str == ' ' || *str == '\t' ) || *str == '\r' || *str == '\n' ) {
562 *str-- = 0;
563 }
564 return ret;
565 }
567 void skipWhitespace( char*& str ) {
568 while ( *str == ' ' || *str == '\t' ) {
569 str++;
570 }
571 }
573 bool parseNum( char*& str, int& val ) {
574 val = 0;
575 while ( '0' <= *str && *str <= '9' ) {
576 val = val * 10 + (*str - '0');
577 str++;
578 }
579 bool retval = !(*str == 0 || *str == ' ' || *str == '\t' || *str == '\r' || *str == '\n');
580 return retval;
581 }
584 static bool getBlock( std::string& dst, guchar ch, std::string const str )
585 {
586 bool good = false;
587 size_t pos = str.find(ch);
588 if ( pos != std::string::npos )
589 {
590 size_t pos2 = str.find( '(', pos );
591 if ( pos2 != std::string::npos ) {
592 size_t endPos = str.find( ')', pos2 );
593 if ( endPos != std::string::npos ) {
594 dst = str.substr( pos2 + 1, (endPos - pos2 - 1) );
595 good = true;
596 }
597 }
598 }
599 return good;
600 }
602 static bool popVal( guint64& numVal, std::string& str )
603 {
604 bool good = false;
605 size_t endPos = str.find(',');
606 if ( endPos == std::string::npos ) {
607 endPos = str.length();
608 }
610 if ( endPos != std::string::npos && endPos > 0 ) {
611 std::string xxx = str.substr( 0, endPos );
612 const gchar* ptr = xxx.c_str();
613 gchar* endPtr = 0;
614 numVal = g_ascii_strtoull( ptr, &endPtr, 10 );
615 if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) {
616 // overflow
617 } else if ( (numVal == 0) && (endPtr == ptr) ) {
618 // failed conversion
619 } else {
620 good = true;
621 str.erase( 0, endPos + 1 );
622 }
623 }
625 return good;
626 }
628 void ColorItem::_wireMagicColors( void* p )
629 {
630 JustForNow* onceMore = reinterpret_cast<JustForNow*>(p);
631 if ( onceMore )
632 {
633 for ( std::vector<ColorItem*>::iterator it = onceMore->_colors.begin(); it != onceMore->_colors.end(); ++it )
634 {
635 size_t pos = (*it)->def.descr.find("*{");
636 if ( pos != std::string::npos )
637 {
638 std::string subby = (*it)->def.descr.substr( pos + 2 );
639 size_t endPos = subby.find("}*");
640 if ( endPos != std::string::npos )
641 {
642 subby.erase( endPos );
643 //g_message("FOUND MAGIC at '%s'", (*it)->def.descr.c_str());
644 //g_message(" '%s'", subby.c_str());
646 if ( subby.find('E') != std::string::npos )
647 {
648 //g_message(" HOT!");
649 (*it)->def.setEditable( true );
650 }
652 std::string part;
653 // Tint. index + 1 more val.
654 if ( getBlock( part, 'T', subby ) ) {
655 guint64 colorIndex = 0;
656 if ( popVal( colorIndex, part ) ) {
657 guint64 percent = 0;
658 if ( popVal( percent, part ) ) {
659 (*it)->_linkTint( *(onceMore->_colors[colorIndex]), percent );
660 }
661 }
662 }
664 // Shade/tone. index + 1 or 2 more val.
665 if ( getBlock( part, 'S', subby ) ) {
666 guint64 colorIndex = 0;
667 if ( popVal( colorIndex, part ) ) {
668 guint64 percent = 0;
669 if ( popVal( percent, part ) ) {
670 guint64 grayLevel = 0;
671 if ( !popVal( grayLevel, part ) ) {
672 grayLevel = 0;
673 }
674 (*it)->_linkTone( *(onceMore->_colors[colorIndex]), percent, grayLevel );
675 }
676 }
677 }
679 }
680 }
681 }
682 }
683 }
686 void ColorItem::_linkTint( ColorItem& other, int percent )
687 {
688 if ( !_linkSrc )
689 {
690 other._listeners.push_back(this);
691 _linkIsTone = false;
692 _linkPercent = percent;
693 if ( _linkPercent > 100 )
694 _linkPercent = 100;
695 if ( _linkPercent < 0 )
696 _linkPercent = 0;
697 _linkGray = 0;
698 _linkSrc = &other;
700 ColorItem::_colorDefChanged(&other);
701 }
702 }
704 void ColorItem::_linkTone( ColorItem& other, int percent, int grayLevel )
705 {
706 if ( !_linkSrc )
707 {
708 other._listeners.push_back(this);
709 _linkIsTone = true;
710 _linkPercent = percent;
711 if ( _linkPercent > 100 )
712 _linkPercent = 100;
713 if ( _linkPercent < 0 )
714 _linkPercent = 0;
715 _linkGray = grayLevel;
716 _linkSrc = &other;
718 ColorItem::_colorDefChanged(&other);
719 }
720 }
723 void _loadPaletteFile( gchar const *filename )
724 {
725 char block[1024];
726 FILE *f = Inkscape::IO::fopen_utf8name( filename, "r" );
727 if ( f ) {
728 char* result = fgets( block, sizeof(block), f );
729 if ( result ) {
730 if ( strncmp( "GIMP Palette", block, 12 ) == 0 ) {
731 bool inHeader = true;
732 bool hasErr = false;
734 JustForNow *onceMore = new JustForNow();
736 do {
737 result = fgets( block, sizeof(block), f );
738 block[sizeof(block) - 1] = 0;
739 if ( result ) {
740 if ( block[0] == '#' ) {
741 // ignore comment
742 } else {
743 char *ptr = block;
744 // very simple check for header versus entry
745 while ( *ptr == ' ' || *ptr == '\t' ) {
746 ptr++;
747 }
748 if ( *ptr == 0 ) {
749 // blank line. skip it.
750 } else if ( '0' <= *ptr && *ptr <= '9' ) {
751 // should be an entry link
752 inHeader = false;
753 ptr = block;
754 Glib::ustring name("");
755 int r = 0;
756 int g = 0;
757 int b = 0;
758 skipWhitespace(ptr);
759 if ( *ptr ) {
760 hasErr = parseNum(ptr, r);
761 if ( !hasErr ) {
762 skipWhitespace(ptr);
763 hasErr = parseNum(ptr, g);
764 }
765 if ( !hasErr ) {
766 skipWhitespace(ptr);
767 hasErr = parseNum(ptr, b);
768 }
769 if ( !hasErr && *ptr ) {
770 char* n = trim(ptr);
771 if (n != NULL) {
772 name = n;
773 }
774 }
775 if ( !hasErr ) {
776 // Add the entry now
777 Glib::ustring nameStr(name);
778 ColorItem* item = new ColorItem( r, g, b, nameStr );
779 onceMore->_colors.push_back(item);
780 }
781 } else {
782 hasErr = true;
783 }
784 } else {
785 if ( !inHeader ) {
786 // Hmmm... probably bad. Not quite the format we want?
787 hasErr = true;
788 } else {
789 char* sep = strchr(result, ':');
790 if ( sep ) {
791 *sep = 0;
792 char* val = trim(sep + 1);
793 char* name = trim(result);
794 if ( *name ) {
795 if ( strcmp( "Name", name ) == 0 )
796 {
797 onceMore->_name = val;
798 }
799 else if ( strcmp( "Columns", name ) == 0 )
800 {
801 gchar* endPtr = 0;
802 guint64 numVal = g_ascii_strtoull( val, &endPtr, 10 );
803 if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) {
804 // overflow
805 } else if ( (numVal == 0) && (endPtr == val) ) {
806 // failed conversion
807 } else {
808 onceMore->_prefWidth = numVal;
809 }
810 }
811 } else {
812 // error
813 hasErr = true;
814 }
815 } else {
816 // error
817 hasErr = true;
818 }
819 }
820 }
821 }
822 }
823 } while ( result && !hasErr );
824 if ( !hasErr ) {
825 possible.push_back(onceMore);
826 ColorItem::_wireMagicColors( onceMore );
827 } else {
828 delete onceMore;
829 }
830 }
831 }
833 fclose(f);
834 }
835 }
837 static void loadEmUp()
838 {
839 static bool beenHere = false;
840 if ( !beenHere ) {
841 beenHere = true;
843 std::list<gchar *> sources;
844 sources.push_back( profile_path("palettes") );
845 sources.push_back( g_strdup(INKSCAPE_PALETTESDIR) );
847 // Use this loop to iterate through a list of possible document locations.
848 while (!sources.empty()) {
849 gchar *dirname = sources.front();
851 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
852 GError *err = 0;
853 GDir *directory = g_dir_open(dirname, 0, &err);
854 if (!directory) {
855 gchar *safeDir = Inkscape::IO::sanitizeString(dirname);
856 g_warning(_("Palettes directory (%s) is unavailable."), safeDir);
857 g_free(safeDir);
858 } else {
859 gchar *filename = 0;
860 while ((filename = (gchar *)g_dir_read_name(directory)) != NULL) {
861 gchar* lower = g_ascii_strdown( filename, -1 );
862 if ( g_str_has_suffix(lower, ".gpl") ) {
863 gchar* full = g_build_filename(dirname, filename, NULL);
864 if ( !Inkscape::IO::file_test( full, (GFileTest)(G_FILE_TEST_IS_DIR ) ) ) {
865 _loadPaletteFile(full);
866 }
867 g_free(full);
868 }
869 g_free(lower);
870 }
871 g_dir_close(directory);
872 }
873 }
875 // toss the dirname
876 g_free(dirname);
877 sources.pop_front();
878 }
879 }
880 }
890 SwatchesPanel& SwatchesPanel::getInstance()
891 {
892 if ( !instance ) {
893 instance = new SwatchesPanel();
894 }
896 return *instance;
897 }
901 /**
902 * Constructor
903 */
904 SwatchesPanel::SwatchesPanel() :
905 Inkscape::UI::Widget::Panel ("dialogs.swatches"),
906 _holder(0)
907 {
908 _holder = new PreviewHolder();
909 loadEmUp();
911 if ( !possible.empty() ) {
912 JustForNow* first = possible.front();
913 if ( first->_prefWidth > 0 ) {
914 _holder->setColumnPref( first->_prefWidth );
915 }
916 _holder->freezeUpdates();
917 for ( std::vector<ColorItem*>::iterator it = first->_colors.begin(); it != first->_colors.end(); it++ ) {
918 _holder->addPreview(*it);
919 }
920 _holder->thawUpdates();
922 Gtk::RadioMenuItem::Group groupOne;
923 int i = 0;
924 for ( std::vector<JustForNow*>::iterator it = possible.begin(); it != possible.end(); it++ ) {
925 JustForNow* curr = *it;
926 Gtk::RadioMenuItem* single = manage(new Gtk::RadioMenuItem(groupOne, curr->_name));
927 _regItem( single, 3, i );
928 i++;
929 }
931 }
934 _getContents()->pack_start(*_holder, Gtk::PACK_EXPAND_WIDGET);
935 _setTargetFillable(_holder);
937 show_all_children();
939 restorePanelPrefs();
940 }
942 SwatchesPanel::~SwatchesPanel()
943 {
944 }
946 void SwatchesPanel::setOrientation( Gtk::AnchorType how )
947 {
948 // Must call the parent class or bad things might happen
949 Inkscape::UI::Widget::Panel::setOrientation( how );
951 if ( _holder )
952 {
953 _holder->setOrientation( Gtk::ANCHOR_SOUTH );
954 }
955 }
957 void SwatchesPanel::_handleAction( int setId, int itemId )
958 {
959 switch( setId ) {
960 case 3:
961 {
962 if ( itemId >= 0 && itemId < static_cast<int>(possible.size()) ) {
963 _holder->clear();
964 JustForNow* curr = possible[itemId];
965 if ( curr->_prefWidth > 0 ) {
966 _holder->setColumnPref( curr->_prefWidth );
967 }
968 _holder->freezeUpdates();
969 for ( std::vector<ColorItem*>::iterator it = curr->_colors.begin(); it != curr->_colors.end(); it++ ) {
970 _holder->addPreview(*it);
971 }
972 _holder->thawUpdates();
973 }
974 }
975 break;
976 }
977 }
979 } //namespace Dialogs
980 } //namespace UI
981 } //namespace Inkscape
984 /*
985 Local Variables:
986 mode:c++
987 c-file-style:"stroustrup"
988 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
989 indent-tabs-mode:nil
990 fill-column:99
991 End:
992 */
993 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :