Code

Refactoring SPColor to C++ and removing legacy CMYK implementation
[inkscape.git] / src / dialogs / fill-style.cpp
1 #define __SP_FILL_STYLE_C__
3 /**
4  * \brief  Fill style widget
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 #define noSP_FS_VERBOSE
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
24 #include <widgets/sp-widget.h>
25 #include <sp-linear-gradient.h>
26 #include <sp-pattern.h>
27 #include <sp-radial-gradient.h>
28 #include <widgets/paint-selector.h>
29 #include <style.h>
30 #include <gradient-chemistry.h>
31 #include <desktop-style.h>
32 #include <desktop-handles.h>
33 #include <selection.h>
34 #include <inkscape.h>
35 #include <document-private.h>
36 #include <xml/repr.h>
37 #include <glibmm/i18n.h>
38 #include <display/sp-canvas.h>
41 // These can be deleted once we sort out the libart dependence.
43 #define ART_WIND_RULE_NONZERO 0
45 static void sp_fill_style_widget_construct          ( SPWidget *spw,
46                                                       SPPaintSelector *psel );
48 static void sp_fill_style_widget_modify_selection   ( SPWidget *spw,
49                                                       Inkscape::Selection *selection,
50                                                       guint flags,
51                                                       SPPaintSelector *psel );
53 static void sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw );
55 static void sp_fill_style_widget_change_selection   ( SPWidget *spw,
56                                                       Inkscape::Selection *selection,
57                                                       SPPaintSelector *psel );
59 static void sp_fill_style_widget_update (SPWidget *spw);
61 static void sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
62                                                       SPPaintSelectorMode mode,
63                                                       SPWidget *spw );
64 static void sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel,
65                                           SPPaintSelectorFillRule mode,
66                                                     SPWidget *spw );
68 static void sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw );
69 static void sp_fill_style_widget_paint_changed (SPPaintSelector *psel, SPWidget *spw );
71 GtkWidget *
72 sp_fill_style_widget_new (void)
73 {
74     GtkWidget *spw = sp_widget_new_global (INKSCAPE);
76     GtkWidget *vb = gtk_vbox_new (FALSE, 0);
77     gtk_widget_show (vb);
78     gtk_container_add (GTK_CONTAINER (spw), vb);
80     GtkWidget *psel = sp_paint_selector_new (true); // with fillrule selector
81     gtk_widget_show (psel);
82     gtk_box_pack_start (GTK_BOX (vb), psel, TRUE, TRUE, 0);
83     g_object_set_data (G_OBJECT (spw), "paint-selector", psel);
85     g_signal_connect ( G_OBJECT (psel), "mode_changed",
86                        G_CALLBACK (sp_fill_style_widget_paint_mode_changed),
87                        spw );
89     g_signal_connect ( G_OBJECT (psel), "dragged",
90                        G_CALLBACK (sp_fill_style_widget_paint_dragged),
91                        spw );
93     g_signal_connect ( G_OBJECT (psel), "changed",
94                        G_CALLBACK (sp_fill_style_widget_paint_changed),
95                        spw );
97     g_signal_connect ( G_OBJECT (psel), "fillrule_changed",
98                        G_CALLBACK (sp_fill_style_widget_fillrule_changed),
99                        spw );
102     g_signal_connect ( G_OBJECT (spw), "construct",
103                        G_CALLBACK (sp_fill_style_widget_construct), psel);
105 //FIXME: switch these from spw signals to global inkscape object signals; spw just retranslates
106 //those anyway; then eliminate spw
107     g_signal_connect ( G_OBJECT (spw), "modify_selection",
108                        G_CALLBACK (sp_fill_style_widget_modify_selection), psel);
110     g_signal_connect ( G_OBJECT (spw), "change_selection",
111                        G_CALLBACK (sp_fill_style_widget_change_selection), psel);
113     g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_fill_style_widget_change_subselection), spw);
115     sp_fill_style_widget_update (SP_WIDGET (spw));
117     return spw;
119 } // end of sp_fill_style_widget_new()
123 static void
124 sp_fill_style_widget_construct ( SPWidget *spw, SPPaintSelector *psel )
126     (void)psel;
128 #ifdef SP_FS_VERBOSE
129     g_print ( "Fill style widget constructed: inkscape %p repr %p\n",
130               spw->inkscape, spw->repr );
131 #endif
132     if (spw->inkscape) {
133         sp_fill_style_widget_update (spw);
134     }
136 } // end of sp_fill_style_widget_construct()
138 static void
139 sp_fill_style_widget_modify_selection ( SPWidget *spw,
140                                         Inkscape::Selection *selection,
141                                         guint flags,
142                                         SPPaintSelector *psel )
144     (void)selection;
145     (void)psel;
146     if (flags & ( SP_OBJECT_MODIFIED_FLAG |
147                   SP_OBJECT_PARENT_MODIFIED_FLAG |
148                   SP_OBJECT_STYLE_MODIFIED_FLAG) )
149     {
150         sp_fill_style_widget_update (spw);
151     }
154 static void
155 sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape,
156                                         SPDesktop *desktop,
157                                         SPWidget *spw )
159     (void)inkscape;
160     sp_fill_style_widget_update (spw);
163 static void
164 sp_fill_style_widget_change_selection ( SPWidget *spw,
165                                         Inkscape::Selection *selection,
166                                         SPPaintSelector *psel )
168     (void)selection;
169     sp_fill_style_widget_update (spw);
172 /**
173 * \param sel Selection to use, or NULL.
174 */
175 static void
176 sp_fill_style_widget_update (SPWidget *spw)
178     if (g_object_get_data (G_OBJECT (spw), "update"))
179         return;
181     if (g_object_get_data (G_OBJECT (spw), "local")) {
182         g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (FALSE)); // local change; do nothing, but reset the flag
183         return;
184     }
186     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
188     SPPaintSelector *psel = SP_PAINT_SELECTOR (g_object_get_data (G_OBJECT (spw), "paint-selector"));
190     // create temporary style
191     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
192     // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
193     int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FILL); 
195     switch (result) {
196         case QUERY_STYLE_NOTHING:
197         {
198             /* No paint at all */
199             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
200             break;
201         }
203         case QUERY_STYLE_SINGLE:
204         case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
205         case QUERY_STYLE_MULTIPLE_SAME: 
206         {
207             SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, true);
208             sp_paint_selector_set_mode (psel, pselmode);
210             sp_paint_selector_set_fillrule (psel, query->fill_rule.computed == ART_WIND_RULE_NONZERO? 
211                                      SP_PAINT_SELECTOR_FILLRULE_NONZERO : SP_PAINT_SELECTOR_FILLRULE_EVENODD);
213             if (query->fill.set && query->fill.isColor()) {
214                 gfloat d[3];
215                 sp_color_get_rgb_floatv (&query->fill.value.color, d);
216                 SPColor color( d[0], d[1], d[2] );
217                 sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value));
219             } else if (query->fill.set && query->fill.isPaintserver()) {
221                 SPPaintServer *server = SP_STYLE_FILL_SERVER (query);
223                 if (SP_IS_LINEARGRADIENT (server)) {
224                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
225                     sp_paint_selector_set_gradient_linear (psel, vector);
227                     SPLinearGradient *lg = SP_LINEARGRADIENT (server);
228                     sp_paint_selector_set_gradient_properties (psel,
229                                                        SP_GRADIENT_UNITS (lg),
230                                                        SP_GRADIENT_SPREAD (lg));
231                 } else if (SP_IS_RADIALGRADIENT (server)) {
232                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
233                     sp_paint_selector_set_gradient_radial (psel, vector);
235                     SPRadialGradient *rg = SP_RADIALGRADIENT (server);
236                     sp_paint_selector_set_gradient_properties (psel,
237                                                        SP_GRADIENT_UNITS (rg),
238                                                        SP_GRADIENT_SPREAD (rg));
239                 } else if (SP_IS_PATTERN (server)) {
240                     SPPattern *pat = pattern_getroot (SP_PATTERN (server));
241                     sp_update_pattern_list (psel, pat);
242                 }
243             }
244             break;
245         }
247         case QUERY_STYLE_MULTIPLE_DIFFERENT:
248         {
249             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
250             break;
251         }
252     }
254     sp_style_unref(query);
256     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
261 static void
262 sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
263                                           SPPaintSelectorMode mode,
264                                           SPWidget *spw )
266     (void)mode;
267     if (g_object_get_data (G_OBJECT (spw), "update"))
268         return;
270     /* TODO: Does this work? */
271     /* TODO: Not really, here we have to get old color back from object */
272     /* Instead of relying on paint widget having meaningful colors set */
273     sp_fill_style_widget_paint_changed (psel, spw);
276 static void
277 sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel,
278                                           SPPaintSelectorFillRule mode,
279                                           SPWidget *spw )
281     (void)psel;
282     if (g_object_get_data (G_OBJECT (spw), "update"))
283         return;
285     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
287     SPCSSAttr *css = sp_repr_css_attr_new ();
288     sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero");
290     sp_desktop_set_style (desktop, css);
292     sp_repr_css_attr_unref (css);
294     sp_document_done (SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILL_STROKE, 
295                       _("Change fill rule"));
298 static gchar *undo_label_1 = "fill:flatcolor:1";
299 static gchar *undo_label_2 = "fill:flatcolor:2";
300 static gchar *undo_label = undo_label_1;
302 /**
303 This is called repeatedly while you are dragging a color slider, only for flat color
304 modes. Previously it set the color in style but did not update the repr for efficiency, however
305 this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
306 lumps all its changes for undo.
307  */
308 static void
309 sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw)
311     if (!spw->inkscape) {
312         return;
313     }
315     if (g_object_get_data (G_OBJECT (spw), "update")) {
316         return;
317     }
319     if (g_object_get_data (G_OBJECT (spw), "local")) {
320         // previous local flag not cleared yet; 
321         // this means dragged events come too fast, so we better skip this one to speed up display 
322         // (it's safe to do this in any case)
323         return;
324     }
326     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
328     switch (psel->mode) {
330         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
331         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
332         {
333             sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity");
334             sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE, 
335                                     _("Set fill color"));
336             g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection
337             break;
338         }
340         default:
341             g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'",
342                         __FILE__, __LINE__, psel->mode );
343             break;
345     }
346     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
350 /**
351 This is called (at least) when:
352 1  paint selector mode is switched (e.g. flat color -> gradient)
353 2  you finished dragging a gradient node and released mouse
354 3  you changed a gradient selector parameter (e.g. spread)
355 Must update repr.
356  */
357 static void
358 sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
359                                      SPWidget *spw )
361     if (g_object_get_data (G_OBJECT (spw), "update")) {
362         return;
363     }
364     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
366     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
367     if (!desktop) {
368         return;
369     }
370     SPDocument *document = sp_desktop_document (desktop);
371     Inkscape::Selection *selection = sp_desktop_selection (desktop);
373     GSList const *items = selection->itemList();
375     switch (psel->mode) {
377         case SP_PAINT_SELECTOR_MODE_EMPTY:
378             // This should not happen.
379             g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
380                         __FILE__, __LINE__, psel->mode);
381             break;
382         case SP_PAINT_SELECTOR_MODE_MULTIPLE:
383             // This happens when you switch multiple objects with different gradients to flat color;
384             // nothing to do here.
385             break;
387         case SP_PAINT_SELECTOR_MODE_NONE:
388         {
389             SPCSSAttr *css = sp_repr_css_attr_new ();
390             sp_repr_css_set_property (css, "fill", "none");
392             sp_desktop_set_style (desktop, css);
394             sp_repr_css_attr_unref (css);
396             sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
397                               _("Remove fill"));
398             break;
399         }
401         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
402         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
403         {
404             // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
405             sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
407             sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity");
408             sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
409                                     _("Set fill color"));
410             // resume interruptibility
411             sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
413             // on release, toggle undo_label so that the next drag will not be lumped with this one
414             if (undo_label == undo_label_1)
415                 undo_label = undo_label_2;
416             else
417                 undo_label = undo_label_1;
419             break;
420         }
422         case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
423         case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
424             if (items) {
425                 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
426                                                        ? SP_GRADIENT_TYPE_LINEAR
427                                                        : SP_GRADIENT_TYPE_RADIAL );
429                 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
430                 SPCSSAttr *css = sp_repr_css_attr_new();
431                 sp_repr_css_set_property(css, "fill-opacity", "1.0");
433                 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
434                 if (!vector) {
435                     /* No vector in paint selector should mean that we just changed mode */
437                     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
438                     int result = objects_query_fillstroke ((GSList *) items, query, true);
439                     guint32 common_rgb = 0;
440                     if (result == QUERY_STYLE_MULTIPLE_SAME) {
441                         if (!query->fill.isColor()) {
442                             common_rgb = sp_desktop_get_color(desktop, true);
443                         } else {
444                             common_rgb = query->fill.value.color.toRGBA32( 0xff );
445                         }
446                         vector = sp_document_default_gradient_vector(document, common_rgb);
447                     }
448                     sp_style_unref(query);
450                     for (GSList const *i = items; i != NULL; i = i->next) {
451                         //FIXME: see above
452                         sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
454                         if (!vector) {
455                             sp_item_set_gradient(SP_ITEM(i->data),
456                                                  sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true),
457                                                  gradient_type, true);
458                         } else {
459                             sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
460                         }
461                     }
462                 } else {
463                     /* We have changed from another gradient type, or modified spread/units within
464                      * this gradient type. */
465                     vector = sp_gradient_ensure_vector_normalized (vector);
466                     for (GSList const *i = items; i != NULL; i = i->next) {
467                         //FIXME: see above
468                         sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style");
470                         SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
471                         sp_gradient_selector_attrs_to_gradient (gr, psel);
472                     }
473                 }
475                 sp_repr_css_attr_unref (css);
477                 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
478                                   _("Set gradient on fill"));
479             }
480             break;
482         case SP_PAINT_SELECTOR_MODE_PATTERN:
484             if (items) {
486                 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
487                 if (!pattern) {
489                     /* No Pattern in paint selector should mean that we just
490                      * changed mode - dont do jack.
491                      */
493                 } else {
494                     Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
495                     SPCSSAttr *css = sp_repr_css_attr_new ();
496                     gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
497                     sp_repr_css_set_property (css, "fill", urltext);
499                     // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
500                     sp_repr_css_set_property(css, "fill-opacity", "1.0");
502                     // cannot just call sp_desktop_set_style, because we don't want to touch those
503                     // objects who already have the same root pattern but through a different href
504                     // chain. FIXME: move this to a sp_item_set_pattern
505                     for (GSList const *i = items; i != NULL; i = i->next) {
506                          SPObject *selobj = SP_OBJECT (i->data);
508                          SPStyle *style = SP_OBJECT_STYLE (selobj);
509                          if (style && style->fill.isPaintserver()) {
510                              SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj);
511                              if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
512                                 // only if this object's pattern is not rooted in our selected pattern, apply
513                                  continue;
514                          }
516                          sp_desktop_apply_css_recursive (selobj, css, true);
517                      }
519                     sp_repr_css_attr_unref (css);
520                     g_free (urltext);
522                 } // end if
524                 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
525                                   _("Set pattern on fill"));
527             } // end if
529             break;
531         case SP_PAINT_SELECTOR_MODE_UNSET:
532             if (items) {
533                     SPCSSAttr *css = sp_repr_css_attr_new ();
534                     sp_repr_css_unset_property (css, "fill");
536                     sp_desktop_set_style (desktop, css);
537                     sp_repr_css_attr_unref (css);
539                     sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
540                                       _("Unset fill"));
541             }
542             break;
544         default:
545             g_warning ( "file %s: line %d: Paint selector should not be in "
546                         "mode %d",
547                         __FILE__, __LINE__, psel->mode );
548             break;
549     }
551     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
555 /*
556   Local Variables:
557   mode:c++
558   c-file-style:"stroustrup"
559   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
560   indent-tabs-mode:nil
561   fill-column:99
562   End:
563 */
564 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :