Code

Bringing in new color class
[inkscape.git] / src / dialogs / swatches.cpp
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     }
112 static void dragBegin( GtkWidget *widget, GdkDragContext* dc, gpointer data )
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     }
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)
135 // TODO finish
136     return TRUE;
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     }
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     }
153 Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, Gtk::BuiltinIconSize size)
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;
235 void ColorItem::buttonClicked(bool secondary)
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     }
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;
272 void skipWhitespace( char*& str ) {
273     while ( *str == ' ' || *str == '\t' ) {
274         str++;
275     }
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;
289 class JustForNow
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 )
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     }
414 static void loadEmUp()
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     }
463 SwatchesPanel& SwatchesPanel::getInstance()
465     if ( !instance ) {
466         instance = new SwatchesPanel();
467     }
469     return *instance;
474 /**
475  * Constructor
476  */
477 SwatchesPanel::SwatchesPanel() :
478     Inkscape::UI::Widget::Panel ("dialogs.swatches"),
479     _holder(0)
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();
515 SwatchesPanel::~SwatchesPanel()
519 void SwatchesPanel::setOrientation( Gtk::AnchorType how )
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     }
530 void SwatchesPanel::_handleAction( int setId, int itemId )
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     }
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 :