1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimp-tool-options-manager.c
5  * Copyright (C) 2018 Michael Natterer <mitch@gimp.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 
26 #include "libgimpconfig/gimpconfig.h"
27 
28 #include "tools-types.h"
29 
30 #include "config/gimpcoreconfig.h"
31 
32 #include "core/gimp.h"
33 #include "core/gimpcontext.h"
34 #include "core/gimptoolinfo.h"
35 
36 #include "paint/gimppaintoptions.h"
37 
38 #include "widgets/gimpwidgets-utils.h"
39 
40 #include "gimp-tool-options-manager.h"
41 
42 
43 typedef struct _GimpToolOptionsManager GimpToolOptionsManager;
44 
45 struct _GimpToolOptionsManager
46 {
47   Gimp                *gimp;
48   GimpPaintOptions    *global_paint_options;
49   GimpContextPropMask  global_props;
50 
51   GimpToolInfo        *active_tool;
52 };
53 
54 
55 static GQuark manager_quark = 0;
56 
57 
58 /*  local function prototypes  */
59 
60 static GimpContextPropMask
61               tool_options_manager_get_global_props
62                                                  (GimpCoreConfig         *config);
63 
64 static void   tool_options_manager_global_notify (GimpCoreConfig         *config,
65                                                   const GParamSpec       *pspec,
66                                                   GimpToolOptionsManager *manager);
67 static void   tool_options_manager_paint_options_notify
68                                                  (GimpPaintOptions       *src,
69                                                   const GParamSpec       *pspec,
70                                                   GimpPaintOptions       *dest);
71 
72 static void   tool_options_manager_copy_paint_props
73                                                  (GimpPaintOptions       *src,
74                                                   GimpPaintOptions       *dest,
75                                                   GimpContextPropMask     prop_mask);
76 
77 static void   tool_options_manager_tool_changed  (GimpContext            *user_context,
78                                                   GimpToolInfo           *tool_info,
79                                                   GimpToolOptionsManager *manager);
80 
81 
82 /*  public functions  */
83 
84 void
gimp_tool_options_manager_init(Gimp * gimp)85 gimp_tool_options_manager_init (Gimp *gimp)
86 {
87   GimpToolOptionsManager *manager;
88   GimpContext            *user_context;
89   GimpCoreConfig         *config;
90   GList                  *list;
91 
92   g_return_if_fail (GIMP_IS_GIMP (gimp));
93   g_return_if_fail (manager_quark == 0);
94 
95   manager_quark = g_quark_from_static_string ("gimp-tool-options-manager");
96 
97   config = gimp->config;
98 
99   manager = g_slice_new0 (GimpToolOptionsManager);
100 
101   manager->gimp = gimp;
102 
103   manager->global_paint_options =
104     g_object_new (GIMP_TYPE_PAINT_OPTIONS,
105                   "gimp", gimp,
106                   "name", "tool-options-manager-global-paint-options",
107                   NULL);
108 
109   manager->global_props = tool_options_manager_get_global_props (config);
110 
111   g_object_set_qdata (G_OBJECT (gimp), manager_quark, manager);
112 
113   user_context = gimp_get_user_context (gimp);
114 
115   for (list = gimp_get_tool_info_iter (gimp);
116        list;
117        list = g_list_next (list))
118     {
119       GimpToolInfo *tool_info = list->data;
120 
121       /*  the global props that are actually used by the tool are
122        *  always shared with the user context by undefining them...
123        */
124       gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
125                                       manager->global_props &
126                                       tool_info->context_props,
127                                       FALSE);
128 
129       /*  ...and setting the user context as parent
130        */
131       gimp_context_set_parent (GIMP_CONTEXT (tool_info->tool_options),
132                                user_context);
133 
134       /*  make sure paint tools also share their brush, dynamics,
135        *  gradient properties if the resp. context properties are
136        *  global
137        */
138       if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
139         {
140           g_signal_connect (tool_info->tool_options, "notify",
141                             G_CALLBACK (tool_options_manager_paint_options_notify),
142                             manager->global_paint_options);
143 
144           g_signal_connect (manager->global_paint_options, "notify",
145                             G_CALLBACK (tool_options_manager_paint_options_notify),
146                             tool_info->tool_options);
147 
148           tool_options_manager_copy_paint_props (manager->global_paint_options,
149                                                  GIMP_PAINT_OPTIONS (tool_info->tool_options),
150                                                  tool_info->context_props &
151                                                  manager->global_props);
152         }
153     }
154 
155   g_signal_connect (gimp->config, "notify::global-brush",
156                     G_CALLBACK (tool_options_manager_global_notify),
157                     manager);
158   g_signal_connect (gimp->config, "notify::global-dynamics",
159                     G_CALLBACK (tool_options_manager_global_notify),
160                     manager);
161   g_signal_connect (gimp->config, "notify::global-pattern",
162                     G_CALLBACK (tool_options_manager_global_notify),
163                     manager);
164   g_signal_connect (gimp->config, "notify::global-palette",
165                     G_CALLBACK (tool_options_manager_global_notify),
166                     manager);
167   g_signal_connect (gimp->config, "notify::global-gradient",
168                     G_CALLBACK (tool_options_manager_global_notify),
169                     manager);
170   g_signal_connect (gimp->config, "notify::global-font",
171                     G_CALLBACK (tool_options_manager_global_notify),
172                     manager);
173 
174   g_signal_connect (user_context, "tool-changed",
175                     G_CALLBACK (tool_options_manager_tool_changed),
176                     manager);
177 
178   tool_options_manager_tool_changed (user_context,
179                                      gimp_context_get_tool (user_context),
180                                      manager);
181 }
182 
183 void
gimp_tool_options_manager_exit(Gimp * gimp)184 gimp_tool_options_manager_exit (Gimp *gimp)
185 {
186   GimpToolOptionsManager *manager;
187   GimpContext            *user_context;
188   GList                  *list;
189 
190   g_return_if_fail (GIMP_IS_GIMP (gimp));
191 
192   manager = g_object_get_qdata (G_OBJECT (gimp), manager_quark);
193 
194   g_return_if_fail (manager != NULL);
195 
196   user_context = gimp_get_user_context (gimp);
197 
198   g_signal_handlers_disconnect_by_func (user_context,
199                                         tool_options_manager_tool_changed,
200                                         manager);
201 
202   g_signal_handlers_disconnect_by_func (gimp->config,
203                                         tool_options_manager_global_notify,
204                                         manager);
205 
206   for (list = gimp_get_tool_info_iter (gimp);
207        list;
208        list = g_list_next (list))
209     {
210       GimpToolInfo *tool_info = list->data;
211 
212       gimp_context_set_parent (GIMP_CONTEXT (tool_info->tool_options), NULL);
213 
214       if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
215         {
216           g_signal_handlers_disconnect_by_func (tool_info->tool_options,
217                                                 tool_options_manager_paint_options_notify,
218                                                 manager->global_paint_options);
219 
220           g_signal_handlers_disconnect_by_func (manager->global_paint_options,
221                                                 tool_options_manager_paint_options_notify,
222                                                 tool_info->tool_options);
223         }
224     }
225 
226   g_clear_object (&manager->global_paint_options);
227 
228   g_slice_free (GimpToolOptionsManager, manager);
229 
230   g_object_set_qdata (G_OBJECT (gimp), manager_quark, NULL);
231 }
232 
233 
234 /*  private functions  */
235 
236 static GimpContextPropMask
tool_options_manager_get_global_props(GimpCoreConfig * config)237 tool_options_manager_get_global_props (GimpCoreConfig *config)
238 {
239   GimpContextPropMask global_props = 0;
240 
241   /*  FG and BG are always shared between all tools  */
242   global_props |= GIMP_CONTEXT_PROP_MASK_FOREGROUND;
243   global_props |= GIMP_CONTEXT_PROP_MASK_BACKGROUND;
244 
245   if (config->global_brush)
246     global_props |= GIMP_CONTEXT_PROP_MASK_BRUSH;
247   if (config->global_dynamics)
248     global_props |= GIMP_CONTEXT_PROP_MASK_DYNAMICS;
249   if (config->global_pattern)
250     global_props |= GIMP_CONTEXT_PROP_MASK_PATTERN;
251   if (config->global_palette)
252     global_props |= GIMP_CONTEXT_PROP_MASK_PALETTE;
253   if (config->global_gradient)
254     global_props |= GIMP_CONTEXT_PROP_MASK_GRADIENT;
255   if (config->global_font)
256     global_props |= GIMP_CONTEXT_PROP_MASK_FONT;
257 
258   return global_props;
259 }
260 
261 static void
tool_options_manager_global_notify(GimpCoreConfig * config,const GParamSpec * pspec,GimpToolOptionsManager * manager)262 tool_options_manager_global_notify (GimpCoreConfig         *config,
263                                     const GParamSpec       *pspec,
264                                     GimpToolOptionsManager *manager)
265 {
266   GimpContextPropMask  global_props;
267   GimpContextPropMask  enabled_global_props;
268   GimpContextPropMask  disabled_global_props;
269   GList               *list;
270 
271   global_props = tool_options_manager_get_global_props (config);
272 
273   enabled_global_props  = global_props & ~manager->global_props;
274   disabled_global_props = manager->global_props & ~global_props;
275 
276   /*  copy the newly enabled global props to all tool options, and
277    *  disconnect the newly disabled ones from the user context
278    */
279   for (list = gimp_get_tool_info_iter (manager->gimp);
280        list;
281        list = g_list_next (list))
282     {
283       GimpToolInfo *tool_info = list->data;
284 
285       /*  don't change the active tool, it is always fully connected
286        *  to the user_context anyway because we set its
287        *  defined/undefined context props in tool_changed()
288        */
289       if (tool_info == manager->active_tool)
290         continue;
291 
292       /*  defining the newly disabled ones disconnects them from the
293        *  parent user context
294        */
295       gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
296                                       tool_info->context_props &
297                                       disabled_global_props,
298                                       TRUE);
299 
300       /*  undefining the newly enabled ones copies the value from the
301        *  parent user context
302        */
303       gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
304                                       tool_info->context_props &
305                                       enabled_global_props,
306                                       FALSE);
307 
308       if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
309         tool_options_manager_copy_paint_props (manager->global_paint_options,
310                                                GIMP_PAINT_OPTIONS (tool_info->tool_options),
311                                                tool_info->context_props &
312                                                enabled_global_props);
313     }
314 
315   manager->global_props = global_props;
316 }
317 
318 static void
tool_options_manager_paint_options_notify(GimpPaintOptions * src,const GParamSpec * pspec,GimpPaintOptions * dest)319 tool_options_manager_paint_options_notify (GimpPaintOptions *src,
320                                            const GParamSpec *pspec,
321                                            GimpPaintOptions *dest)
322 {
323   Gimp                   *gimp   = GIMP_CONTEXT (src)->gimp;
324   GimpCoreConfig         *config = gimp->config;
325   GimpToolOptionsManager *manager;
326   GimpToolInfo           *tool_info;
327   GimpContextPropMask     prop_mask = 0;
328   gboolean                active    = FALSE;
329 
330   manager = g_object_get_qdata (G_OBJECT (gimp), manager_quark);
331 
332   /*  one of the options is the global one, the other is the tool's,
333    *  get the tool_info from the tool's options
334    */
335   if (manager->global_paint_options == src)
336     tool_info = gimp_context_get_tool (GIMP_CONTEXT (dest));
337   else
338     tool_info = gimp_context_get_tool (GIMP_CONTEXT (src));
339 
340   if (tool_info == manager->active_tool)
341     active = TRUE;
342 
343   if ((active || config->global_brush) &&
344       tool_info->context_props & GIMP_CONTEXT_PROP_MASK_BRUSH)
345     {
346       prop_mask |= GIMP_CONTEXT_PROP_MASK_BRUSH;
347     }
348 
349   if ((active || config->global_dynamics) &&
350       tool_info->context_props & GIMP_CONTEXT_PROP_MASK_DYNAMICS)
351     {
352       prop_mask |= GIMP_CONTEXT_PROP_MASK_DYNAMICS;
353     }
354 
355   if ((active || config->global_gradient) &&
356       tool_info->context_props & GIMP_CONTEXT_PROP_MASK_GRADIENT)
357     {
358       prop_mask |= GIMP_CONTEXT_PROP_MASK_GRADIENT;
359     }
360 
361   if (gimp_paint_options_is_prop (pspec->name, prop_mask))
362     {
363       GValue value = G_VALUE_INIT;
364 
365       g_value_init (&value, pspec->value_type);
366 
367       g_object_get_property (G_OBJECT (src), pspec->name, &value);
368 
369       g_signal_handlers_block_by_func (dest,
370                                        tool_options_manager_paint_options_notify,
371                                        src);
372 
373       g_object_set_property (G_OBJECT (dest), pspec->name, &value);
374 
375       g_signal_handlers_unblock_by_func (dest,
376                                          tool_options_manager_paint_options_notify,
377                                          src);
378 
379       g_value_unset (&value);
380     }
381 }
382 
383 static void
tool_options_manager_copy_paint_props(GimpPaintOptions * src,GimpPaintOptions * dest,GimpContextPropMask prop_mask)384 tool_options_manager_copy_paint_props (GimpPaintOptions    *src,
385                                        GimpPaintOptions    *dest,
386                                        GimpContextPropMask  prop_mask)
387 {
388   g_signal_handlers_block_by_func (dest,
389                                    tool_options_manager_paint_options_notify,
390                                    src);
391 
392   gimp_paint_options_copy_props (src, dest, prop_mask);
393 
394   g_signal_handlers_unblock_by_func (dest,
395                                      tool_options_manager_paint_options_notify,
396                                      src);
397 }
398 
399 static void
tool_options_manager_tool_changed(GimpContext * user_context,GimpToolInfo * tool_info,GimpToolOptionsManager * manager)400 tool_options_manager_tool_changed (GimpContext            *user_context,
401                                    GimpToolInfo           *tool_info,
402                                    GimpToolOptionsManager *manager)
403 {
404   if (tool_info == manager->active_tool)
405     return;
406 
407   /*  FIXME: gimp_busy HACK
408    *  the tool manager will stop the emission, so simply return
409    */
410   if (user_context->gimp->busy)
411     return;
412 
413   if (manager->active_tool)
414     {
415       GimpToolInfo *active = manager->active_tool;
416 
417       /*  disconnect the old active tool from all context properties
418        *  it uses, but are not currently global
419        */
420       gimp_context_define_properties (GIMP_CONTEXT (active->tool_options),
421                                       active->context_props &
422                                       ~manager->global_props,
423                                       TRUE);
424     }
425 
426   manager->active_tool = tool_info;
427 
428   if (manager->active_tool)
429     {
430       GimpToolInfo *active = manager->active_tool;
431 
432       /*  make sure the tool options GUI always exists, this call
433        *  creates it if needed, so tools always have their option GUI
434        *  available even if the tool options dockable is not open, see
435        *  for example issue #3435
436        */
437       gimp_tools_get_tool_options_gui (active->tool_options);
438 
439       /*  copy the new tool's context properties that are not
440        *  currently global to the user context, so they get used by
441        *  everything
442        */
443       gimp_context_copy_properties (GIMP_CONTEXT (active->tool_options),
444                                     gimp_get_user_context (manager->gimp),
445                                     active->context_props &
446                                     ~manager->global_props);
447 
448       if (GIMP_IS_PAINT_OPTIONS (active->tool_options))
449         tool_options_manager_copy_paint_props (GIMP_PAINT_OPTIONS (active->tool_options),
450                                                manager->global_paint_options,
451                                                active->context_props &
452                                                ~manager->global_props);
453 
454       /*  then, undefine these properties so the tool syncs with the
455        *  user context automatically
456        */
457       gimp_context_define_properties (GIMP_CONTEXT (active->tool_options),
458                                       active->context_props &
459                                       ~manager->global_props,
460                                       FALSE);
461     }
462 }
463