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 #ifdef SP_FS_VERBOSE
127 g_print ( "Fill style widget constructed: inkscape %p repr %p\n",
128 spw->inkscape, spw->repr );
129 #endif
130 if (spw->inkscape) {
131 sp_fill_style_widget_update (spw);
132 }
134 } // end of sp_fill_style_widget_construct()
136 static void
137 sp_fill_style_widget_modify_selection( SPWidget *spw,
138 Inkscape::Selection */*selection*/,
139 guint flags,
140 SPPaintSelector */*psel*/ )
141 {
142 if (flags & ( SP_OBJECT_MODIFIED_FLAG |
143 SP_OBJECT_PARENT_MODIFIED_FLAG |
144 SP_OBJECT_STYLE_MODIFIED_FLAG) )
145 {
146 sp_fill_style_widget_update (spw);
147 }
148 }
150 static void
151 sp_fill_style_widget_change_subselection( Inkscape::Application */*inkscape*/,
152 SPDesktop */*desktop*/,
153 SPWidget *spw )
154 {
155 sp_fill_style_widget_update (spw);
156 }
158 static void
159 sp_fill_style_widget_change_selection( SPWidget *spw,
160 Inkscape::Selection */*selection*/,
161 SPPaintSelector */*psel*/ )
162 {
163 sp_fill_style_widget_update (spw);
164 }
166 /**
167 * \param sel Selection to use, or NULL.
168 */
169 static void
170 sp_fill_style_widget_update (SPWidget *spw)
171 {
172 if (g_object_get_data (G_OBJECT (spw), "update"))
173 return;
175 if (g_object_get_data (G_OBJECT (spw), "local")) {
176 g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (FALSE)); // local change; do nothing, but reset the flag
177 return;
178 }
180 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
182 SPPaintSelector *psel = SP_PAINT_SELECTOR (g_object_get_data (G_OBJECT (spw), "paint-selector"));
184 // create temporary style
185 SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
186 // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
187 int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FILL);
189 switch (result) {
190 case QUERY_STYLE_NOTHING:
191 {
192 /* No paint at all */
193 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
194 break;
195 }
197 case QUERY_STYLE_SINGLE:
198 case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
199 case QUERY_STYLE_MULTIPLE_SAME:
200 {
201 SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, true);
202 sp_paint_selector_set_mode (psel, pselmode);
204 sp_paint_selector_set_fillrule (psel, query->fill_rule.computed == ART_WIND_RULE_NONZERO?
205 SP_PAINT_SELECTOR_FILLRULE_NONZERO : SP_PAINT_SELECTOR_FILLRULE_EVENODD);
207 if (query->fill.set && query->fill.isColor()) {
208 sp_paint_selector_set_color_alpha (psel, &query->fill.value.color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value));
209 } else if (query->fill.set && query->fill.isPaintserver()) {
211 SPPaintServer *server = SP_STYLE_FILL_SERVER (query);
213 if (SP_IS_LINEARGRADIENT (server)) {
214 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
215 sp_paint_selector_set_gradient_linear (psel, vector);
217 SPLinearGradient *lg = SP_LINEARGRADIENT (server);
218 sp_paint_selector_set_gradient_properties (psel,
219 SP_GRADIENT_UNITS (lg),
220 SP_GRADIENT_SPREAD (lg));
221 } else if (SP_IS_RADIALGRADIENT (server)) {
222 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
223 sp_paint_selector_set_gradient_radial (psel, vector);
225 SPRadialGradient *rg = SP_RADIALGRADIENT (server);
226 sp_paint_selector_set_gradient_properties (psel,
227 SP_GRADIENT_UNITS (rg),
228 SP_GRADIENT_SPREAD (rg));
229 } else if (SP_IS_PATTERN (server)) {
230 SPPattern *pat = pattern_getroot (SP_PATTERN (server));
231 sp_update_pattern_list (psel, pat);
232 }
233 }
234 break;
235 }
237 case QUERY_STYLE_MULTIPLE_DIFFERENT:
238 {
239 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
240 break;
241 }
242 }
244 sp_style_unref(query);
246 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
248 }
251 static void
252 sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
253 SPPaintSelectorMode /*mode*/,
254 SPWidget *spw )
255 {
256 if (g_object_get_data (G_OBJECT (spw), "update"))
257 return;
259 /* TODO: Does this work? */
260 /* TODO: Not really, here we have to get old color back from object */
261 /* Instead of relying on paint widget having meaningful colors set */
262 sp_fill_style_widget_paint_changed (psel, spw);
263 }
265 static void
266 sp_fill_style_widget_fillrule_changed ( SPPaintSelector */*psel*/,
267 SPPaintSelectorFillRule mode,
268 SPWidget *spw )
269 {
270 if (g_object_get_data (G_OBJECT (spw), "update"))
271 return;
273 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
275 SPCSSAttr *css = sp_repr_css_attr_new ();
276 sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero");
278 sp_desktop_set_style (desktop, css);
280 sp_repr_css_attr_unref (css);
282 sp_document_done (SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILL_STROKE,
283 _("Change fill rule"));
284 }
286 static gchar *undo_label_1 = "fill:flatcolor:1";
287 static gchar *undo_label_2 = "fill:flatcolor:2";
288 static gchar *undo_label = undo_label_1;
290 /**
291 This is called repeatedly while you are dragging a color slider, only for flat color
292 modes. Previously it set the color in style but did not update the repr for efficiency, however
293 this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
294 lumps all its changes for undo.
295 */
296 static void
297 sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw)
298 {
299 if (!spw->inkscape) {
300 return;
301 }
303 if (g_object_get_data (G_OBJECT (spw), "update")) {
304 return;
305 }
307 if (g_object_get_data (G_OBJECT (spw), "local")) {
308 // previous local flag not cleared yet;
309 // this means dragged events come too fast, so we better skip this one to speed up display
310 // (it's safe to do this in any case)
311 return;
312 }
314 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
316 switch (psel->mode) {
318 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
319 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
320 {
321 sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity");
322 sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE,
323 _("Set fill color"));
324 g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection
325 break;
326 }
328 default:
329 g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'",
330 __FILE__, __LINE__, psel->mode );
331 break;
333 }
334 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
335 }
338 /**
339 This is called (at least) when:
340 1 paint selector mode is switched (e.g. flat color -> gradient)
341 2 you finished dragging a gradient node and released mouse
342 3 you changed a gradient selector parameter (e.g. spread)
343 Must update repr.
344 */
345 static void
346 sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
347 SPWidget *spw )
348 {
349 if (g_object_get_data (G_OBJECT (spw), "update")) {
350 return;
351 }
352 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
354 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
355 if (!desktop) {
356 return;
357 }
358 SPDocument *document = sp_desktop_document (desktop);
359 Inkscape::Selection *selection = sp_desktop_selection (desktop);
361 GSList const *items = selection->itemList();
363 switch (psel->mode) {
365 case SP_PAINT_SELECTOR_MODE_EMPTY:
366 // This should not happen.
367 g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
368 __FILE__, __LINE__, psel->mode);
369 break;
370 case SP_PAINT_SELECTOR_MODE_MULTIPLE:
371 // This happens when you switch multiple objects with different gradients to flat color;
372 // nothing to do here.
373 break;
375 case SP_PAINT_SELECTOR_MODE_NONE:
376 {
377 SPCSSAttr *css = sp_repr_css_attr_new ();
378 sp_repr_css_set_property (css, "fill", "none");
380 sp_desktop_set_style (desktop, css);
382 sp_repr_css_attr_unref (css);
384 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
385 _("Remove fill"));
386 break;
387 }
389 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
390 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
391 {
392 // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
393 sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
395 sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity");
396 sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
397 _("Set fill color"));
398 // resume interruptibility
399 sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
401 // on release, toggle undo_label so that the next drag will not be lumped with this one
402 if (undo_label == undo_label_1)
403 undo_label = undo_label_2;
404 else
405 undo_label = undo_label_1;
407 break;
408 }
410 case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
411 case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
412 if (items) {
413 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
414 ? SP_GRADIENT_TYPE_LINEAR
415 : SP_GRADIENT_TYPE_RADIAL );
417 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
418 SPCSSAttr *css = sp_repr_css_attr_new();
419 sp_repr_css_set_property(css, "fill-opacity", "1.0");
421 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
422 if (!vector) {
423 /* No vector in paint selector should mean that we just changed mode */
425 SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
426 int result = objects_query_fillstroke ((GSList *) items, query, true);
427 guint32 common_rgb = 0;
428 if (result == QUERY_STYLE_MULTIPLE_SAME) {
429 if (!query->fill.isColor()) {
430 common_rgb = sp_desktop_get_color(desktop, true);
431 } else {
432 common_rgb = query->fill.value.color.toRGBA32( 0xff );
433 }
434 vector = sp_document_default_gradient_vector(document, common_rgb);
435 }
436 sp_style_unref(query);
438 for (GSList const *i = items; i != NULL; i = i->next) {
439 //FIXME: see above
440 sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
442 if (!vector) {
443 sp_item_set_gradient(SP_ITEM(i->data),
444 sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true),
445 gradient_type, true);
446 } else {
447 sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
448 }
449 }
450 } else {
451 /* We have changed from another gradient type, or modified spread/units within
452 * this gradient type. */
453 vector = sp_gradient_ensure_vector_normalized (vector);
454 for (GSList const *i = items; i != NULL; i = i->next) {
455 //FIXME: see above
456 sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style");
458 SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
459 sp_gradient_selector_attrs_to_gradient (gr, psel);
460 }
461 }
463 sp_repr_css_attr_unref (css);
465 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
466 _("Set gradient on fill"));
467 }
468 break;
470 case SP_PAINT_SELECTOR_MODE_PATTERN:
472 if (items) {
474 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
475 if (!pattern) {
477 /* No Pattern in paint selector should mean that we just
478 * changed mode - dont do jack.
479 */
481 } else {
482 Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
483 SPCSSAttr *css = sp_repr_css_attr_new ();
484 gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
485 sp_repr_css_set_property (css, "fill", urltext);
487 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
488 sp_repr_css_set_property(css, "fill-opacity", "1.0");
490 // cannot just call sp_desktop_set_style, because we don't want to touch those
491 // objects who already have the same root pattern but through a different href
492 // chain. FIXME: move this to a sp_item_set_pattern
493 for (GSList const *i = items; i != NULL; i = i->next) {
494 SPObject *selobj = SP_OBJECT (i->data);
496 SPStyle *style = SP_OBJECT_STYLE (selobj);
497 if (style && style->fill.isPaintserver()) {
498 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj);
499 if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
500 // only if this object's pattern is not rooted in our selected pattern, apply
501 continue;
502 }
504 sp_desktop_apply_css_recursive (selobj, css, true);
505 }
507 sp_repr_css_attr_unref (css);
508 g_free (urltext);
510 } // end if
512 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
513 _("Set pattern on fill"));
515 } // end if
517 break;
519 case SP_PAINT_SELECTOR_MODE_UNSET:
520 if (items) {
521 SPCSSAttr *css = sp_repr_css_attr_new ();
522 sp_repr_css_unset_property (css, "fill");
524 sp_desktop_set_style (desktop, css);
525 sp_repr_css_attr_unref (css);
527 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
528 _("Unset fill"));
529 }
530 break;
532 default:
533 g_warning ( "file %s: line %d: Paint selector should not be in "
534 "mode %d",
535 __FILE__, __LINE__, psel->mode );
536 break;
537 }
539 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
540 }
543 /*
544 Local Variables:
545 mode:c++
546 c-file-style:"stroustrup"
547 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
548 indent-tabs-mode:nil
549 fill-column:99
550 End:
551 */
552 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :