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 )
125 {
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 )
143 {
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 }
152 }
154 static void
155 sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape,
156 SPDesktop *desktop,
157 SPWidget *spw )
158 {
159 (void)inkscape;
160 sp_fill_style_widget_update (spw);
161 }
163 static void
164 sp_fill_style_widget_change_selection ( SPWidget *spw,
165 Inkscape::Selection *selection,
166 SPPaintSelector *psel )
167 {
168 (void)selection;
169 sp_fill_style_widget_update (spw);
170 }
172 /**
173 * \param sel Selection to use, or NULL.
174 */
175 static void
176 sp_fill_style_widget_update (SPWidget *spw)
177 {
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 sp_paint_selector_set_color_alpha (psel, &query->fill.value.color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value));
215 } else if (query->fill.set && query->fill.isPaintserver()) {
217 SPPaintServer *server = SP_STYLE_FILL_SERVER (query);
219 if (SP_IS_LINEARGRADIENT (server)) {
220 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
221 sp_paint_selector_set_gradient_linear (psel, vector);
223 SPLinearGradient *lg = SP_LINEARGRADIENT (server);
224 sp_paint_selector_set_gradient_properties (psel,
225 SP_GRADIENT_UNITS (lg),
226 SP_GRADIENT_SPREAD (lg));
227 } else if (SP_IS_RADIALGRADIENT (server)) {
228 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
229 sp_paint_selector_set_gradient_radial (psel, vector);
231 SPRadialGradient *rg = SP_RADIALGRADIENT (server);
232 sp_paint_selector_set_gradient_properties (psel,
233 SP_GRADIENT_UNITS (rg),
234 SP_GRADIENT_SPREAD (rg));
235 } else if (SP_IS_PATTERN (server)) {
236 SPPattern *pat = pattern_getroot (SP_PATTERN (server));
237 sp_update_pattern_list (psel, pat);
238 }
239 }
240 break;
241 }
243 case QUERY_STYLE_MULTIPLE_DIFFERENT:
244 {
245 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
246 break;
247 }
248 }
250 sp_style_unref(query);
252 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
254 }
257 static void
258 sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
259 SPPaintSelectorMode mode,
260 SPWidget *spw )
261 {
262 (void)mode;
263 if (g_object_get_data (G_OBJECT (spw), "update"))
264 return;
266 /* TODO: Does this work? */
267 /* TODO: Not really, here we have to get old color back from object */
268 /* Instead of relying on paint widget having meaningful colors set */
269 sp_fill_style_widget_paint_changed (psel, spw);
270 }
272 static void
273 sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel,
274 SPPaintSelectorFillRule mode,
275 SPWidget *spw )
276 {
277 (void)psel;
278 if (g_object_get_data (G_OBJECT (spw), "update"))
279 return;
281 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
283 SPCSSAttr *css = sp_repr_css_attr_new ();
284 sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero");
286 sp_desktop_set_style (desktop, css);
288 sp_repr_css_attr_unref (css);
290 sp_document_done (SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILL_STROKE,
291 _("Change fill rule"));
292 }
294 static gchar *undo_label_1 = "fill:flatcolor:1";
295 static gchar *undo_label_2 = "fill:flatcolor:2";
296 static gchar *undo_label = undo_label_1;
298 /**
299 This is called repeatedly while you are dragging a color slider, only for flat color
300 modes. Previously it set the color in style but did not update the repr for efficiency, however
301 this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
302 lumps all its changes for undo.
303 */
304 static void
305 sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw)
306 {
307 if (!spw->inkscape) {
308 return;
309 }
311 if (g_object_get_data (G_OBJECT (spw), "update")) {
312 return;
313 }
315 if (g_object_get_data (G_OBJECT (spw), "local")) {
316 // previous local flag not cleared yet;
317 // this means dragged events come too fast, so we better skip this one to speed up display
318 // (it's safe to do this in any case)
319 return;
320 }
322 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
324 switch (psel->mode) {
326 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
327 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
328 {
329 sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity");
330 sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE,
331 _("Set fill color"));
332 g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection
333 break;
334 }
336 default:
337 g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'",
338 __FILE__, __LINE__, psel->mode );
339 break;
341 }
342 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
343 }
346 /**
347 This is called (at least) when:
348 1 paint selector mode is switched (e.g. flat color -> gradient)
349 2 you finished dragging a gradient node and released mouse
350 3 you changed a gradient selector parameter (e.g. spread)
351 Must update repr.
352 */
353 static void
354 sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
355 SPWidget *spw )
356 {
357 if (g_object_get_data (G_OBJECT (spw), "update")) {
358 return;
359 }
360 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
362 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
363 if (!desktop) {
364 return;
365 }
366 SPDocument *document = sp_desktop_document (desktop);
367 Inkscape::Selection *selection = sp_desktop_selection (desktop);
369 GSList const *items = selection->itemList();
371 switch (psel->mode) {
373 case SP_PAINT_SELECTOR_MODE_EMPTY:
374 // This should not happen.
375 g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
376 __FILE__, __LINE__, psel->mode);
377 break;
378 case SP_PAINT_SELECTOR_MODE_MULTIPLE:
379 // This happens when you switch multiple objects with different gradients to flat color;
380 // nothing to do here.
381 break;
383 case SP_PAINT_SELECTOR_MODE_NONE:
384 {
385 SPCSSAttr *css = sp_repr_css_attr_new ();
386 sp_repr_css_set_property (css, "fill", "none");
388 sp_desktop_set_style (desktop, css);
390 sp_repr_css_attr_unref (css);
392 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
393 _("Remove fill"));
394 break;
395 }
397 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
398 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
399 {
400 // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
401 sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
403 sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity");
404 sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
405 _("Set fill color"));
406 // resume interruptibility
407 sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
409 // on release, toggle undo_label so that the next drag will not be lumped with this one
410 if (undo_label == undo_label_1)
411 undo_label = undo_label_2;
412 else
413 undo_label = undo_label_1;
415 break;
416 }
418 case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
419 case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
420 if (items) {
421 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
422 ? SP_GRADIENT_TYPE_LINEAR
423 : SP_GRADIENT_TYPE_RADIAL );
425 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
426 SPCSSAttr *css = sp_repr_css_attr_new();
427 sp_repr_css_set_property(css, "fill-opacity", "1.0");
429 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
430 if (!vector) {
431 /* No vector in paint selector should mean that we just changed mode */
433 SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
434 int result = objects_query_fillstroke ((GSList *) items, query, true);
435 guint32 common_rgb = 0;
436 if (result == QUERY_STYLE_MULTIPLE_SAME) {
437 if (!query->fill.isColor()) {
438 common_rgb = sp_desktop_get_color(desktop, true);
439 } else {
440 common_rgb = query->fill.value.color.toRGBA32( 0xff );
441 }
442 vector = sp_document_default_gradient_vector(document, common_rgb);
443 }
444 sp_style_unref(query);
446 for (GSList const *i = items; i != NULL; i = i->next) {
447 //FIXME: see above
448 sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
450 if (!vector) {
451 sp_item_set_gradient(SP_ITEM(i->data),
452 sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true),
453 gradient_type, true);
454 } else {
455 sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
456 }
457 }
458 } else {
459 /* We have changed from another gradient type, or modified spread/units within
460 * this gradient type. */
461 vector = sp_gradient_ensure_vector_normalized (vector);
462 for (GSList const *i = items; i != NULL; i = i->next) {
463 //FIXME: see above
464 sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style");
466 SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
467 sp_gradient_selector_attrs_to_gradient (gr, psel);
468 }
469 }
471 sp_repr_css_attr_unref (css);
473 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
474 _("Set gradient on fill"));
475 }
476 break;
478 case SP_PAINT_SELECTOR_MODE_PATTERN:
480 if (items) {
482 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
483 if (!pattern) {
485 /* No Pattern in paint selector should mean that we just
486 * changed mode - dont do jack.
487 */
489 } else {
490 Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
491 SPCSSAttr *css = sp_repr_css_attr_new ();
492 gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
493 sp_repr_css_set_property (css, "fill", urltext);
495 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
496 sp_repr_css_set_property(css, "fill-opacity", "1.0");
498 // cannot just call sp_desktop_set_style, because we don't want to touch those
499 // objects who already have the same root pattern but through a different href
500 // chain. FIXME: move this to a sp_item_set_pattern
501 for (GSList const *i = items; i != NULL; i = i->next) {
502 SPObject *selobj = SP_OBJECT (i->data);
504 SPStyle *style = SP_OBJECT_STYLE (selobj);
505 if (style && style->fill.isPaintserver()) {
506 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj);
507 if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
508 // only if this object's pattern is not rooted in our selected pattern, apply
509 continue;
510 }
512 sp_desktop_apply_css_recursive (selobj, css, true);
513 }
515 sp_repr_css_attr_unref (css);
516 g_free (urltext);
518 } // end if
520 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
521 _("Set pattern on fill"));
523 } // end if
525 break;
527 case SP_PAINT_SELECTOR_MODE_UNSET:
528 if (items) {
529 SPCSSAttr *css = sp_repr_css_attr_new ();
530 sp_repr_css_unset_property (css, "fill");
532 sp_desktop_set_style (desktop, css);
533 sp_repr_css_attr_unref (css);
535 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
536 _("Unset fill"));
537 }
538 break;
540 default:
541 g_warning ( "file %s: line %d: Paint selector should not be in "
542 "mode %d",
543 __FILE__, __LINE__, psel->mode );
544 break;
545 }
547 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
548 }
551 /*
552 Local Variables:
553 mode:c++
554 c-file-style:"stroustrup"
555 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
556 indent-tabs-mode:nil
557 fill-column:99
558 End:
559 */
560 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :