d9aad2bdce6795b6a806a86a424f8d588e09f577
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"
33 #include "eek-preview.h"
35 namespace Inkscape {
36 namespace UI {
37 namespace Dialogs {
39 SwatchesPanel* SwatchesPanel::instance = 0;
42 ColorItem::ColorItem( unsigned int r, unsigned int g, unsigned int b, Glib::ustring& name ) :
43 def( r, g, b, name )
44 {
45 }
47 ColorItem::~ColorItem()
48 {
49 }
51 ColorItem::ColorItem(ColorItem const &other) :
52 Inkscape::UI::Previewable()
53 {
54 if ( this != &other ) {
55 *this = other;
56 }
57 }
59 ColorItem &ColorItem::operator=(ColorItem const &other)
60 {
61 if ( this != &other ) {
62 def = other.def;
63 }
64 return *this;
65 }
67 typedef enum {
68 XCOLOR_DATA = 0,
69 TEXT_DATA
70 } colorFlavorType;
72 static const GtkTargetEntry color_entries[] = {
73 {"application/x-color", 0, XCOLOR_DATA},
74 {"text/plain", 0, TEXT_DATA},
75 };
77 static void dragGetColorData( GtkWidget *widget,
78 GdkDragContext *drag_context,
79 GtkSelectionData *data,
80 guint info,
81 guint time,
82 gpointer user_data)
83 {
84 static GdkAtom typeXColor = gdk_atom_intern("application/x-color", FALSE);
85 static GdkAtom typeText = gdk_atom_intern("text/plain", FALSE);
87 ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
88 if ( info == 1 ) {
89 gchar* tmp = g_strdup_printf("#%02x%02x%02x", item->def.r, item->def.g, item->def.b);
91 gtk_selection_data_set( data,
92 typeText,
93 8, // format
94 (guchar*)tmp,
95 strlen((const char*)tmp) + 1);
96 g_free(tmp);
97 tmp = 0;
98 } else {
99 guint16 tmp[4];
100 tmp[0] = (item->def.r << 8) | item->def.r;
101 tmp[1] = (item->def.g << 8) | item->def.g;
102 tmp[2] = (item->def.b << 8) | item->def.b;
103 tmp[3] = 0xffff;
104 gtk_selection_data_set( data,
105 typeXColor,
106 16, // format
107 reinterpret_cast<const guchar*>(tmp),
108 (3+1) * 2);
109 }
110 }
112 static void dragBegin( GtkWidget *widget, GdkDragContext* dc, gpointer data )
113 {
114 ColorItem* item = reinterpret_cast<ColorItem*>(data);
115 if ( item )
116 {
117 Glib::RefPtr<Gdk::Pixbuf> thumb = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, 32, 24 );
118 guint32 fillWith = (0xff000000 & (item->def.r << 24))
119 | (0x00ff0000 & (item->def.g << 16))
120 | (0x0000ff00 & (item->def.b << 8));
121 thumb->fill( fillWith );
122 gtk_drag_set_icon_pixbuf( dc, thumb->gobj(), 0, 0 );
123 }
125 }
127 //"drag-drop"
128 gboolean dragDropColorData( GtkWidget *widget,
129 GdkDragContext *drag_context,
130 gint x,
131 gint y,
132 guint time,
133 gpointer user_data)
134 {
135 // TODO finish
136 return TRUE;
137 }
139 static void bouncy( GtkWidget* widget, gpointer callback_data ) {
140 ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
141 if ( item ) {
142 item->buttonClicked(false);
143 }
144 }
146 static void bouncy2( GtkWidget* widget, gint arg1, gpointer callback_data ) {
147 ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
148 if ( item ) {
149 item->buttonClicked(true);
150 }
151 }
153 Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, Gtk::BuiltinIconSize size)
154 {
155 Gtk::Widget* widget = 0;
156 if ( style == PREVIEW_STYLE_BLURB ) {
157 Gtk::Label *lbl = new Gtk::Label(def.descr);
158 lbl->set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER);
159 widget = lbl;
160 } else {
161 Glib::ustring blank(" ");
162 if ( size == Gtk::ICON_SIZE_MENU ) {
163 blank = " ";
164 }
166 GtkWidget* eekWidget = eek_preview_new();
167 EekPreview * preview = EEK_PREVIEW(eekWidget);
168 Gtk::Widget* newBlot = Glib::wrap(eekWidget);
170 eek_preview_set_color( preview, (def.r << 8) | def.r, (def.g << 8) | def.g, (def.b << 8) | def.b);
172 eek_preview_set_details( preview, (::PreviewStyle)style, (::ViewType)view, (::GtkIconSize)size );
174 GValue val = {0, {{0}, {0}}};
175 g_value_init( &val, G_TYPE_BOOLEAN );
176 g_value_set_boolean( &val, FALSE );
177 g_object_set_property( G_OBJECT(preview), "focus-on-click", &val );
179 /*
180 Gtk::Button *btn = new Gtk::Button(blank);
181 Gdk::Color color;
182 color.set_rgb((_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b);
183 btn->modify_bg(Gtk::STATE_NORMAL, color);
184 btn->modify_bg(Gtk::STATE_ACTIVE, color);
185 btn->modify_bg(Gtk::STATE_PRELIGHT, color);
186 btn->modify_bg(Gtk::STATE_SELECTED, color);
188 Gtk::Widget* newBlot = btn;
189 */
191 tips.set_tip((*newBlot), def.descr);
193 /*
194 newBlot->signal_clicked().connect( sigc::mem_fun(*this, &ColorItem::buttonClicked) );
196 sigc::signal<void> type_signal_something;
197 */
198 g_signal_connect( G_OBJECT(newBlot->gobj()),
199 "clicked",
200 G_CALLBACK(bouncy),
201 this);
203 g_signal_connect( G_OBJECT(newBlot->gobj()),
204 "alt-clicked",
205 G_CALLBACK(bouncy2),
206 this);
208 gtk_drag_source_set( GTK_WIDGET(newBlot->gobj()),
209 GDK_BUTTON1_MASK,
210 color_entries,
211 G_N_ELEMENTS(color_entries),
212 GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY) );
214 g_signal_connect( G_OBJECT(newBlot->gobj()),
215 "drag-data-get",
216 G_CALLBACK(dragGetColorData),
217 this);
219 g_signal_connect( G_OBJECT(newBlot->gobj()),
220 "drag-begin",
221 G_CALLBACK(dragBegin),
222 this );
224 g_signal_connect( G_OBJECT(newBlot->gobj()),
225 "drag-drop",
226 G_CALLBACK(dragDropColorData),
227 this);
229 widget = newBlot;
230 }
232 return widget;
233 }
235 void ColorItem::buttonClicked(bool secondary)
236 {
237 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
238 if (desktop) {
239 char const * attrName = secondary ? "stroke" : "fill";
240 guint32 rgba = (def.r << 24) | (def.g << 16) | (def.b << 8) | 0xff;
241 gchar c[64];
242 sp_svg_write_color(c, 64, rgba);
244 SPCSSAttr *css = sp_repr_css_attr_new();
245 sp_repr_css_set_property( css, attrName, c );
246 sp_desktop_set_style(desktop, css);
248 sp_repr_css_attr_unref(css);
249 sp_document_done (SP_DT_DOCUMENT (desktop));
250 }
251 }
256 static char* trim( char* str ) {
257 char* ret = str;
258 while ( *str && (*str == ' ' || *str == '\t') ) {
259 str++;
260 }
261 ret = str;
262 while ( *str ) {
263 str++;
264 }
265 str--;
266 while ( str > ret && ( *str == ' ' || *str == '\t' ) || *str == '\r' || *str == '\n' ) {
267 *str-- = 0;
268 }
269 return ret;
270 }
272 void skipWhitespace( char*& str ) {
273 while ( *str == ' ' || *str == '\t' ) {
274 str++;
275 }
276 }
278 bool parseNum( char*& str, int& val ) {
279 val = 0;
280 while ( '0' <= *str && *str <= '9' ) {
281 val = val * 10 + (*str - '0');
282 str++;
283 }
284 bool retval = !(*str == 0 || *str == ' ' || *str == '\t' || *str == '\r' || *str == '\n');
285 return retval;
286 }
289 class JustForNow
290 {
291 public:
292 JustForNow() : _prefWidth(0) {}
294 Glib::ustring _name;
295 int _prefWidth;
296 std::vector<ColorItem*> _colors;
297 };
299 static std::vector<JustForNow*> possible;
301 static void loadPaletteFile( gchar const *filename )
302 {
303 char block[1024];
304 FILE *f = Inkscape::IO::fopen_utf8name( filename, "r" );
305 if ( f ) {
306 char* result = fgets( block, sizeof(block), f );
307 if ( result ) {
308 if ( strncmp( "GIMP Palette", block, 12 ) == 0 ) {
309 bool inHeader = true;
310 bool hasErr = false;
312 JustForNow *onceMore = new JustForNow();
314 do {
315 result = fgets( block, sizeof(block), f );
316 block[sizeof(block) - 1] = 0;
317 if ( result ) {
318 if ( block[0] == '#' ) {
319 // ignore comment
320 } else {
321 char *ptr = block;
322 // very simple check for header versus entry
323 while ( *ptr == ' ' || *ptr == '\t' ) {
324 ptr++;
325 }
326 if ( *ptr == 0 ) {
327 // blank line. skip it.
328 } else if ( '0' <= *ptr && *ptr <= '9' ) {
329 // should be an entry link
330 inHeader = false;
331 ptr = block;
332 Glib::ustring name("");
333 int r = 0;
334 int g = 0;
335 int b = 0;
336 skipWhitespace(ptr);
337 if ( *ptr ) {
338 hasErr = parseNum(ptr, r);
339 if ( !hasErr ) {
340 skipWhitespace(ptr);
341 hasErr = parseNum(ptr, g);
342 }
343 if ( !hasErr ) {
344 skipWhitespace(ptr);
345 hasErr = parseNum(ptr, b);
346 }
347 if ( !hasErr && *ptr ) {
348 char* n = trim(ptr);
349 if (n != NULL) {
350 name = n;
351 }
352 }
353 if ( !hasErr ) {
354 // Add the entry now
355 Glib::ustring nameStr(name);
356 ColorItem* item = new ColorItem( r, g, b, nameStr );
357 onceMore->_colors.push_back(item);
358 }
359 } else {
360 hasErr = true;
361 }
362 } else {
363 if ( !inHeader ) {
364 // Hmmm... probably bad. Not quite the format we want?
365 hasErr = true;
366 } else {
367 char* sep = strchr(result, ':');
368 if ( sep ) {
369 *sep = 0;
370 char* val = trim(sep + 1);
371 char* name = trim(result);
372 if ( *name ) {
373 if ( strcmp( "Name", name ) == 0 )
374 {
375 onceMore->_name = val;
376 }
377 else if ( strcmp( "Columns", name ) == 0 )
378 {
379 gchar* endPtr = 0;
380 guint64 numVal = g_ascii_strtoull( val, &endPtr, 10 );
381 if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) {
382 // overflow
383 } else if ( (numVal == 0) && (endPtr == val) ) {
384 // failed conversion
385 } else {
386 onceMore->_prefWidth = numVal;
387 }
388 }
389 } else {
390 // error
391 hasErr = true;
392 }
393 } else {
394 // error
395 hasErr = true;
396 }
397 }
398 }
399 }
400 }
401 } while ( result && !hasErr );
402 if ( !hasErr ) {
403 possible.push_back(onceMore);
404 } else {
405 delete onceMore;
406 }
407 }
408 }
410 fclose(f);
411 }
412 }
414 static void loadEmUp()
415 {
416 static bool beenHere = false;
417 if ( !beenHere ) {
418 beenHere = true;
420 std::list<gchar *> sources;
421 sources.push_back( profile_path("palettes") );
422 sources.push_back( g_strdup(INKSCAPE_PALETTESDIR) );
424 // Use this loop to iterate through a list of possible document locations.
425 while (!sources.empty()) {
426 gchar *dirname = sources.front();
428 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
429 GError *err = 0;
430 GDir *directory = g_dir_open(dirname, 0, &err);
431 if (!directory) {
432 gchar *safeDir = Inkscape::IO::sanitizeString(dirname);
433 g_warning(_("Palettes directory (%s) is unavailable."), safeDir);
434 g_free(safeDir);
435 } else {
436 gchar *filename = 0;
437 while ((filename = (gchar *)g_dir_read_name(directory)) != NULL) {
438 gchar* full = g_build_filename(dirname, filename, NULL);
439 if ( !Inkscape::IO::file_test( full, (GFileTest)(G_FILE_TEST_IS_DIR ) ) ) {
440 loadPaletteFile(full);
441 }
442 g_free(full);
443 }
444 g_dir_close(directory);
445 }
446 }
448 // toss the dirname
449 g_free(dirname);
450 sources.pop_front();
451 }
452 }
453 }
463 SwatchesPanel& SwatchesPanel::getInstance()
464 {
465 if ( !instance ) {
466 instance = new SwatchesPanel();
467 }
469 return *instance;
470 }
474 /**
475 * Constructor
476 */
477 SwatchesPanel::SwatchesPanel() :
478 Inkscape::UI::Widget::Panel ("dialogs.swatches"),
479 _holder(0)
480 {
481 _holder = new PreviewHolder();
482 loadEmUp();
484 if ( !possible.empty() ) {
485 JustForNow* first = possible.front();
486 if ( first->_prefWidth > 0 ) {
487 _holder->setColumnPref( first->_prefWidth );
488 }
489 _holder->freezeUpdates();
490 for ( std::vector<ColorItem*>::iterator it = first->_colors.begin(); it != first->_colors.end(); it++ ) {
491 _holder->addPreview(*it);
492 }
493 _holder->thawUpdates();
495 Gtk::RadioMenuItem::Group groupOne;
496 int i = 0;
497 for ( std::vector<JustForNow*>::iterator it = possible.begin(); it != possible.end(); it++ ) {
498 JustForNow* curr = *it;
499 Gtk::RadioMenuItem* single = manage(new Gtk::RadioMenuItem(groupOne, curr->_name));
500 _regItem( single, 3, i );
501 i++;
502 }
504 }
507 _getContents()->pack_start(*_holder, Gtk::PACK_EXPAND_WIDGET);
508 _setTargetFillable(_holder);
510 show_all_children();
512 restorePanelPrefs();
513 }
515 SwatchesPanel::~SwatchesPanel()
516 {
517 }
519 void SwatchesPanel::setOrientation( Gtk::AnchorType how )
520 {
521 // Must call the parent class or bad things might happen
522 Inkscape::UI::Widget::Panel::setOrientation( how );
524 if ( _holder )
525 {
526 _holder->setOrientation( Gtk::ANCHOR_SOUTH );
527 }
528 }
530 void SwatchesPanel::_handleAction( int setId, int itemId )
531 {
532 switch( setId ) {
533 case 3:
534 {
535 if ( itemId >= 0 && itemId < static_cast<int>(possible.size()) ) {
536 _holder->clear();
537 JustForNow* curr = possible[itemId];
538 if ( curr->_prefWidth > 0 ) {
539 _holder->setColumnPref( curr->_prefWidth );
540 }
541 _holder->freezeUpdates();
542 for ( std::vector<ColorItem*>::iterator it = curr->_colors.begin(); it != curr->_colors.end(); it++ ) {
543 _holder->addPreview(*it);
544 }
545 _holder->thawUpdates();
546 }
547 }
548 break;
549 }
550 }
552 } //namespace Dialogs
553 } //namespace UI
554 } //namespace Inkscape
557 /*
558 Local Variables:
559 mode:c++
560 c-file-style:"stroustrup"
561 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
562 indent-tabs-mode:nil
563 fill-column:99
564 End:
565 */
566 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :