Code

Move app-specific logic and file operations up from the lower level.
[inkscape.git] / src / dialogs / fill-style.cpp
1 /** @file
2  * @brief  Fill style widget
3  */
4 /* Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   Frank Felfe <innerspace@iname.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *
9  * Copyright (C) 1999-2005 authors
10  * Copyright (C) 2001-2002 Ximian, Inc.
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #define noSP_FS_VERBOSE
17 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
22 #include "widgets/sp-widget.h"
23 #include "sp-linear-gradient.h"
24 #include "sp-pattern.h"
25 #include "sp-radial-gradient.h"
26 #include "widgets/paint-selector.h"
27 #include "style.h"
28 #include "gradient-chemistry.h"
29 #include "desktop-style.h"
30 #include "desktop-handles.h"
31 #include "selection.h"
32 #include "inkscape.h"
33 #include "document-private.h"
34 #include "xml/repr.h"
35 #include <glibmm/i18n.h>
36 #include "display/sp-canvas.h"
39 // These can be deleted once we sort out the libart dependence.
41 #define ART_WIND_RULE_NONZERO 0
43 static void sp_fill_style_widget_construct          ( SPWidget *spw,
44                                                       SPPaintSelector *psel );
46 static void sp_fill_style_widget_modify_selection   ( SPWidget *spw,
47                                                       Inkscape::Selection *selection,
48                                                       guint flags,
49                                                       SPPaintSelector *psel );
51 static void sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw );
53 static void sp_fill_style_widget_change_selection   ( SPWidget *spw,
54                                                       Inkscape::Selection *selection,
55                                                       SPPaintSelector *psel );
57 static void sp_fill_style_widget_update (SPWidget *spw);
59 static void sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
60                                                       SPPaintSelectorMode mode,
61                                                       SPWidget *spw );
62 static void sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel,
63                                           SPPaintSelectorFillRule mode,
64                                                     SPWidget *spw );
66 static void sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw );
67 static void sp_fill_style_widget_paint_changed (SPPaintSelector *psel, SPWidget *spw );
69 GtkWidget *
70 sp_fill_style_widget_new (void)
71 {
72     GtkWidget *spw = sp_widget_new_global (INKSCAPE);
74     GtkWidget *vb = gtk_vbox_new (FALSE, 0);
75     gtk_widget_show (vb);
76     gtk_container_add (GTK_CONTAINER (spw), vb);
78     GtkWidget *psel = sp_paint_selector_new (true); // with fillrule selector
79     gtk_widget_show (psel);
80     gtk_box_pack_start (GTK_BOX (vb), psel, TRUE, TRUE, 0);
81     g_object_set_data (G_OBJECT (spw), "paint-selector", psel);
83     g_signal_connect ( G_OBJECT (psel), "mode_changed",
84                        G_CALLBACK (sp_fill_style_widget_paint_mode_changed),
85                        spw );
87     g_signal_connect ( G_OBJECT (psel), "dragged",
88                        G_CALLBACK (sp_fill_style_widget_paint_dragged),
89                        spw );
91     g_signal_connect ( G_OBJECT (psel), "changed",
92                        G_CALLBACK (sp_fill_style_widget_paint_changed),
93                        spw );
95     g_signal_connect ( G_OBJECT (psel), "fillrule_changed",
96                        G_CALLBACK (sp_fill_style_widget_fillrule_changed),
97                        spw );
100     g_signal_connect ( G_OBJECT (spw), "construct",
101                        G_CALLBACK (sp_fill_style_widget_construct), psel);
103 //FIXME: switch these from spw signals to global inkscape object signals; spw just retranslates
104 //those anyway; then eliminate spw
105     g_signal_connect ( G_OBJECT (spw), "modify_selection",
106                        G_CALLBACK (sp_fill_style_widget_modify_selection), psel);
108     g_signal_connect ( G_OBJECT (spw), "change_selection",
109                        G_CALLBACK (sp_fill_style_widget_change_selection), psel);
111     g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_fill_style_widget_change_subselection), spw);
113     sp_fill_style_widget_update (SP_WIDGET (spw));
115     return spw;
117 } // end of sp_fill_style_widget_new()
121 static void
122 sp_fill_style_widget_construct( SPWidget *spw, SPPaintSelector */*psel*/ )
124 #ifdef SP_FS_VERBOSE
125     g_print ( "Fill style widget constructed: inkscape %p repr %p\n",
126               spw->inkscape, spw->repr );
127 #endif
128     if (spw->inkscape) {
129         sp_fill_style_widget_update (spw);
130     }
132 } // end of sp_fill_style_widget_construct()
134 static void
135 sp_fill_style_widget_modify_selection( SPWidget *spw,
136                                        Inkscape::Selection */*selection*/,
137                                        guint flags,
138                                        SPPaintSelector */*psel*/ )
140     if (flags & ( SP_OBJECT_MODIFIED_FLAG |
141                   SP_OBJECT_PARENT_MODIFIED_FLAG |
142                   SP_OBJECT_STYLE_MODIFIED_FLAG) )
143     {
144         sp_fill_style_widget_update (spw);
145     }
148 static void
149 sp_fill_style_widget_change_subselection( Inkscape::Application */*inkscape*/,
150                                           SPDesktop */*desktop*/,
151                                           SPWidget *spw )
153     sp_fill_style_widget_update (spw);
156 static void
157 sp_fill_style_widget_change_selection( SPWidget *spw,
158                                        Inkscape::Selection */*selection*/,
159                                        SPPaintSelector */*psel*/ )
161     sp_fill_style_widget_update (spw);
164 /**
165 * \param sel Selection to use, or NULL.
166 */
167 static void
168 sp_fill_style_widget_update (SPWidget *spw)
170     if (g_object_get_data (G_OBJECT (spw), "update"))
171         return;
173     if (g_object_get_data (G_OBJECT (spw), "local")) {
174         g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (FALSE)); // local change; do nothing, but reset the flag
175         return;
176     }
178     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
180     SPPaintSelector *psel = SP_PAINT_SELECTOR (g_object_get_data (G_OBJECT (spw), "paint-selector"));
182     // create temporary style
183     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
184     // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
185     int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FILL); 
187     switch (result) {
188         case QUERY_STYLE_NOTHING:
189         {
190             /* No paint at all */
191             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
192             break;
193         }
195         case QUERY_STYLE_SINGLE:
196         case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
197         case QUERY_STYLE_MULTIPLE_SAME: 
198         {
199             SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, true);
200             sp_paint_selector_set_mode (psel, pselmode);
202             sp_paint_selector_set_fillrule (psel, query->fill_rule.computed == ART_WIND_RULE_NONZERO? 
203                                      SP_PAINT_SELECTOR_FILLRULE_NONZERO : SP_PAINT_SELECTOR_FILLRULE_EVENODD);
205             if (query->fill.set && query->fill.isColor()) {
206                 sp_paint_selector_set_color_alpha (psel, &query->fill.value.color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value));
207             } else if (query->fill.set && query->fill.isPaintserver()) {
209                 SPPaintServer *server = SP_STYLE_FILL_SERVER (query);
211                 if (SP_IS_LINEARGRADIENT (server)) {
212                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
213                     sp_paint_selector_set_gradient_linear (psel, vector);
215                     SPLinearGradient *lg = SP_LINEARGRADIENT (server);
216                     sp_paint_selector_set_gradient_properties (psel,
217                                                        SP_GRADIENT_UNITS (lg),
218                                                        SP_GRADIENT_SPREAD (lg));
219                 } else if (SP_IS_RADIALGRADIENT (server)) {
220                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
221                     sp_paint_selector_set_gradient_radial (psel, vector);
223                     SPRadialGradient *rg = SP_RADIALGRADIENT (server);
224                     sp_paint_selector_set_gradient_properties (psel,
225                                                        SP_GRADIENT_UNITS (rg),
226                                                        SP_GRADIENT_SPREAD (rg));
227                 } else if (SP_IS_PATTERN (server)) {
228                     SPPattern *pat = pattern_getroot (SP_PATTERN (server));
229                     sp_update_pattern_list (psel, pat);
230                 }
231             }
232             break;
233         }
235         case QUERY_STYLE_MULTIPLE_DIFFERENT:
236         {
237             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
238             break;
239         }
240     }
242     sp_style_unref(query);
244     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
249 static void
250 sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
251                                           SPPaintSelectorMode /*mode*/,
252                                           SPWidget *spw )
254     if (g_object_get_data (G_OBJECT (spw), "update"))
255         return;
257     /* TODO: Does this work? */
258     /* TODO: Not really, here we have to get old color back from object */
259     /* Instead of relying on paint widget having meaningful colors set */
260     sp_fill_style_widget_paint_changed (psel, spw);
263 static void
264 sp_fill_style_widget_fillrule_changed ( SPPaintSelector */*psel*/,
265                                           SPPaintSelectorFillRule mode,
266                                           SPWidget *spw )
268     if (g_object_get_data (G_OBJECT (spw), "update"))
269         return;
271     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
273     SPCSSAttr *css = sp_repr_css_attr_new ();
274     sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero");
276     sp_desktop_set_style (desktop, css);
278     sp_repr_css_attr_unref (css);
280     sp_document_done (SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILL_STROKE, 
281                       _("Change fill rule"));
284 static gchar const *undo_label_1 = "fill:flatcolor:1";
285 static gchar const *undo_label_2 = "fill:flatcolor:2";
286 static gchar const *undo_label = undo_label_1;
288 /**
289 This is called repeatedly while you are dragging a color slider, only for flat color
290 modes. Previously it set the color in style but did not update the repr for efficiency, however
291 this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
292 lumps all its changes for undo.
293  */
294 static void
295 sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw)
297     if (!spw->inkscape) {
298         return;
299     }
301     if (g_object_get_data (G_OBJECT (spw), "update")) {
302         return;
303     }
305     if (g_object_get_data (G_OBJECT (spw), "local")) {
306         // previous local flag not cleared yet; 
307         // this means dragged events come too fast, so we better skip this one to speed up display 
308         // (it's safe to do this in any case)
309         return;
310     }
312     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
314     switch (psel->mode) {
316         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
317         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
318         {
319             sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity");
320             sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE, 
321                                     _("Set fill color"));
322             g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection
323             break;
324         }
326         default:
327             g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'",
328                         __FILE__, __LINE__, psel->mode );
329             break;
331     }
332     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
336 /**
337 This is called (at least) when:
338 1  paint selector mode is switched (e.g. flat color -> gradient)
339 2  you finished dragging a gradient node and released mouse
340 3  you changed a gradient selector parameter (e.g. spread)
341 Must update repr.
342  */
343 static void
344 sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
345                                      SPWidget *spw )
347     if (g_object_get_data (G_OBJECT (spw), "update")) {
348         return;
349     }
350     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
352     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
353     if (!desktop) {
354         return;
355     }
356     SPDocument *document = sp_desktop_document (desktop);
357     Inkscape::Selection *selection = sp_desktop_selection (desktop);
359     GSList const *items = selection->itemList();
361     switch (psel->mode) {
363         case SP_PAINT_SELECTOR_MODE_EMPTY:
364             // This should not happen.
365             g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
366                         __FILE__, __LINE__, psel->mode);
367             break;
368         case SP_PAINT_SELECTOR_MODE_MULTIPLE:
369             // This happens when you switch multiple objects with different gradients to flat color;
370             // nothing to do here.
371             break;
373         case SP_PAINT_SELECTOR_MODE_NONE:
374         {
375             SPCSSAttr *css = sp_repr_css_attr_new ();
376             sp_repr_css_set_property (css, "fill", "none");
378             sp_desktop_set_style (desktop, css);
380             sp_repr_css_attr_unref (css);
382             sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
383                               _("Remove fill"));
384             break;
385         }
387         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
388         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
389         {
390             // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
391             sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
393             sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity");
394             sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
395                                     _("Set fill color"));
396             // resume interruptibility
397             sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
399             // on release, toggle undo_label so that the next drag will not be lumped with this one
400             if (undo_label == undo_label_1)
401                 undo_label = undo_label_2;
402             else
403                 undo_label = undo_label_1;
405             break;
406         }
408         case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
409         case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
410             if (items) {
411                 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
412                                                        ? SP_GRADIENT_TYPE_LINEAR
413                                                        : SP_GRADIENT_TYPE_RADIAL );
415                 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
416                 SPCSSAttr *css = sp_repr_css_attr_new();
417                 sp_repr_css_set_property(css, "fill-opacity", "1.0");
419                 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
420                 if (!vector) {
421                     /* No vector in paint selector should mean that we just changed mode */
423                     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
424                     int result = objects_query_fillstroke ((GSList *) items, query, true);
425                     guint32 common_rgb = 0;
426                     if (result == QUERY_STYLE_MULTIPLE_SAME) {
427                         if (!query->fill.isColor()) {
428                             common_rgb = sp_desktop_get_color(desktop, true);
429                         } else {
430                             common_rgb = query->fill.value.color.toRGBA32( 0xff );
431                         }
432                         vector = sp_document_default_gradient_vector(document, common_rgb);
433                     }
434                     sp_style_unref(query);
436                     for (GSList const *i = items; i != NULL; i = i->next) {
437                         //FIXME: see above
438                         sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
440                         if (!vector) {
441                             sp_item_set_gradient(SP_ITEM(i->data),
442                                                  sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true),
443                                                  gradient_type, true);
444                         } else {
445                             sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
446                         }
447                     }
448                 } else {
449                     /* We have changed from another gradient type, or modified spread/units within
450                      * this gradient type. */
451                     vector = sp_gradient_ensure_vector_normalized (vector);
452                     for (GSList const *i = items; i != NULL; i = i->next) {
453                         //FIXME: see above
454                         sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style");
456                         SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
457                         sp_gradient_selector_attrs_to_gradient (gr, psel);
458                     }
459                 }
461                 sp_repr_css_attr_unref (css);
463                 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
464                                   _("Set gradient on fill"));
465             }
466             break;
468         case SP_PAINT_SELECTOR_MODE_PATTERN:
470             if (items) {
472                 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
473                 if (!pattern) {
475                     /* No Pattern in paint selector should mean that we just
476                      * changed mode - dont do jack.
477                      */
479                 } else {
480                     Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
481                     SPCSSAttr *css = sp_repr_css_attr_new ();
482                     gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
483                     sp_repr_css_set_property (css, "fill", urltext);
485                     // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
486                     sp_repr_css_set_property(css, "fill-opacity", "1.0");
488                     // cannot just call sp_desktop_set_style, because we don't want to touch those
489                     // objects who already have the same root pattern but through a different href
490                     // chain. FIXME: move this to a sp_item_set_pattern
491                     for (GSList const *i = items; i != NULL; i = i->next) {
492                          SPObject *selobj = SP_OBJECT (i->data);
494                          SPStyle *style = SP_OBJECT_STYLE (selobj);
495                          if (style && style->fill.isPaintserver()) {
496                              SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj);
497                              if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
498                                 // only if this object's pattern is not rooted in our selected pattern, apply
499                                  continue;
500                          }
502                          sp_desktop_apply_css_recursive (selobj, css, true);
503                      }
505                     sp_repr_css_attr_unref (css);
506                     g_free (urltext);
508                 } // end if
510                 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
511                                   _("Set pattern on fill"));
513             } // end if
515             break;
517         case SP_PAINT_SELECTOR_MODE_UNSET:
518             if (items) {
519                     SPCSSAttr *css = sp_repr_css_attr_new ();
520                     sp_repr_css_unset_property (css, "fill");
522                     sp_desktop_set_style (desktop, css);
523                     sp_repr_css_attr_unref (css);
525                     sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
526                                       _("Unset fill"));
527             }
528             break;
530         default:
531             g_warning ( "file %s: line %d: Paint selector should not be in "
532                         "mode %d",
533                         __FILE__, __LINE__, psel->mode );
534             break;
535     }
537     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
541 /*
542   Local Variables:
543   mode:c++
544   c-file-style:"stroustrup"
545   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
546   indent-tabs-mode:nil
547   fill-column:99
548   End:
549 */
550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :