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*/ )
123 {
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*/ )
139 {
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 }
146 }
148 static void
149 sp_fill_style_widget_change_subselection( Inkscape::Application */*inkscape*/,
150 SPDesktop */*desktop*/,
151 SPWidget *spw )
152 {
153 sp_fill_style_widget_update (spw);
154 }
156 static void
157 sp_fill_style_widget_change_selection( SPWidget *spw,
158 Inkscape::Selection */*selection*/,
159 SPPaintSelector */*psel*/ )
160 {
161 sp_fill_style_widget_update (spw);
162 }
164 /**
165 * \param sel Selection to use, or NULL.
166 */
167 static void
168 sp_fill_style_widget_update (SPWidget *spw)
169 {
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));
246 }
249 static void
250 sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
251 SPPaintSelectorMode /*mode*/,
252 SPWidget *spw )
253 {
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);
261 }
263 static void
264 sp_fill_style_widget_fillrule_changed ( SPPaintSelector */*psel*/,
265 SPPaintSelectorFillRule mode,
266 SPWidget *spw )
267 {
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"));
282 }
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)
296 {
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));
333 }
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 )
346 {
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));
538 }
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 :