1 /*
2 * rubberband.c
3 *
4 *
5 * Authors:
6 * Richard Hult <rhult@hem.passagen.se>
7 * Ricardo Markiewicz <rmarkie@fi.uba.ar>
8 * Andres de Barbara <adebarbara@fi.uba.ar>
9 * Marc Lorber <lorber.marc@wanadoo.fr>
10 * Bernhard Schuster <bernhard@ahoi.io>
11 * Guido Trentalancia <guido@trentalancia.com>
12 *
13 * Description: Handles the user interaction when doing area/rubberband
14 *selections.
15 *
16 * Web page: https://ahoi.io/project/oregano
17 *
18 * Copyright (C) 1999-2001 Richard Hult
19 * Copyright (C) 2003,2006 Ricardo Markiewicz
20 * Copyright (C) 2009-2012 Marc Lorber
21 * Copyright (C) 2013 Bernhard Schuster
22 * Copyright (C) 2017 Guido Trentalancia
23 *
24 * This program is free software; you can redistribute it and/or
25 * modify it under the terms of the GNU General Public License as
26 * published by the Free Software Foundation; either version 2 of the
27 * License, or (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32 * General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public
35 * License along with this program; if not, write to the
36 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
37 * Boston, MA 02110-1301, USA.
38 */
39
40 #include <math.h>
41
42 #include <gdk/gdkkeysyms.h>
43
44 #include "rubberband.h"
45 #include "sheet-private.h"
46
47 #include "debug.h"
48
create_stipple(const char * color_name,guchar stipple_data[])49 inline static cairo_pattern_t *create_stipple (const char *color_name, guchar stipple_data[])
50 {
51 cairo_surface_t *surface;
52 cairo_pattern_t *pattern;
53 GdkRGBA color;
54 int stride;
55 const int width = 8;
56 const int height = 8;
57
58 gdk_rgba_parse (&color, color_name);
59 /* stipple_data[2] = stipple_data[14] = color.red >> 8;
60 stipple_data[1] = stipple_data[13] = color.green >> 8;
61 stipple_data[0] = stipple_data[12] = color.blue >> 8;
62 */
63 stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
64 g_assert (stride > 0);
65 NG_DEBUG ("stride = %i", stride);
66 surface = cairo_image_surface_create_for_data (stipple_data, CAIRO_FORMAT_ARGB32, width, height,
67 stride);
68 pattern = cairo_pattern_create_for_surface (surface);
69 cairo_surface_destroy (surface);
70 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
71
72 return pattern;
73 }
74
75 #define COLOR_A 0x3093BA52
76 #define COLOR_B 0x30FFFFFF
77 #define PREMULTIPLY(argb) \
78 ((argb & 0xFF << 24) | \
79 ((((argb & 0xFF << 16) >> 16) * ((argb & 0xFF << 24) >> 24) / 0xFF) << 16) | \
80 ((((argb & 0xFF << 8) >> 8) * ((argb & 0xFF << 24) >> 24) / 0xFF) << 8) | \
81 ((((argb & 0xFF << 0) >> 0) * ((argb & 0xFF << 24) >> 24) / 0xFF) << 0))
82 #define COLOR_A_PRE PREMULTIPLY (COLOR_A)
83 #define COLOR_B_PRE PREMULTIPLY (COLOR_B)
84
rubberband_info_new(Sheet * sheet)85 RubberbandInfo *rubberband_info_new (Sheet *sheet)
86 {
87 RubberbandInfo *rubberband_info;
88 cairo_pattern_t *pattern;
89 cairo_matrix_t matrix;
90
91 NG_DEBUG ("0x%x A", COLOR_A);
92 NG_DEBUG ("0x%x B", COLOR_B);
93 NG_DEBUG ("0x%x A PRE", COLOR_A_PRE);
94 NG_DEBUG ("0x%x B PRE", COLOR_B_PRE);
95 static guint32 stipple_data[8 * 8] = {
96 COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE,
97 COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE,
98 COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE,
99 COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE,
100 COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE,
101
102 COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE,
103 COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE,
104 COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE,
105 COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE, COLOR_A_PRE,
106 COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE, COLOR_B_PRE};
107
108 /* the stipple patten should look like that
109 * 1 1 1 0 0 0 0 1
110 * 1 1 0 0 0 0 1 1
111 * 1 0 0 0 0 1 1 1
112 * 0 0 0 0 1 1 1 1
113 *
114 * 0 0 0 1 1 1 1 0
115 * 0 0 1 1 1 1 0 0
116 * 0 1 1 1 1 0 0 0
117 * 1 1 1 1 0 0 0 0
118 */
119 rubberband_info = g_new (RubberbandInfo, 1);
120 rubberband_info->state = RUBBERBAND_START;
121
122 pattern = create_stipple ("lightgrey", (guchar *)stipple_data);
123
124 // scale 5x, see
125 // http://cairographics.org/manual/cairo-cairo-pattern-t.html#cairo-pattern-t
126 cairo_matrix_init_scale (&matrix, 1.0, 1.0);
127 cairo_pattern_set_matrix (pattern, &matrix);
128
129 rubberband_info->rectangle = GOO_CANVAS_RECT (goo_canvas_rect_new (
130 GOO_CANVAS_ITEM (sheet->object_group), 10.0, 10.0, 10.0, 10.0, "stroke-color", "black",
131 "line-width", 0.2, "fill-pattern", pattern, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL));
132 cairo_pattern_destroy (pattern);
133 return rubberband_info;
134 }
135
rubberband_info_destroy(RubberbandInfo * rubberband_info)136 void rubberband_info_destroy (RubberbandInfo *rubberband_info)
137 {
138 g_return_if_fail (rubberband_info != NULL);
139 goo_canvas_item_remove (GOO_CANVAS_ITEM (rubberband_info->rectangle));
140 g_free (rubberband_info);
141 }
142
rubberband_start(Sheet * sheet,GdkEvent * event)143 gboolean rubberband_start (Sheet *sheet, GdkEvent *event)
144 {
145 GList *list;
146 double x, y;
147 RubberbandInfo *rubberband_info;
148
149 g_assert (event->type == GDK_BUTTON_PRESS);
150 x = event->button.x;
151 y = event->button.y;
152 goo_canvas_convert_from_pixels (GOO_CANVAS (sheet), &x, &y);
153
154 rubberband_info = sheet->priv->rubberband_info;
155 rubberband_info->start.x = x;
156 rubberband_info->start.y = y;
157 rubberband_info->end.x = x;
158 rubberband_info->end.y = y;
159 rubberband_info->state = RUBBERBAND_ACTIVE;
160
161 // FIXME TODO recheck
162 g_assert (rubberband_info->rectangle != NULL);
163 g_object_set (rubberband_info->rectangle, "x", x, "y", y, "width", 0., "height", 0.,
164 "visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
165 #if 1
166 // Mark all the selected objects to preserve their selected state
167 // if SHIFT is pressed while rubberbanding.
168 if (event->button.state & GDK_SHIFT_MASK) {
169 for (list = sheet->priv->selected_objects; list; list = list->next)
170 sheet_item_set_preserve_selection (SHEET_ITEM (list->data), TRUE);
171
172 sheet->priv->preserve_selection_items = g_list_copy (sheet->priv->selected_objects);
173 }
174 #endif
175
176 sheet_pointer_grab (sheet, event);
177 return TRUE;
178 }
179
rubberband_update(Sheet * sheet,GdkEvent * event)180 gboolean rubberband_update (Sheet *sheet, GdkEvent *event)
181 {
182 GList *iter;
183 Coords cur, cmin, cmax;
184 double dx, dy; // TODO maybe keep track of subpixel changes, make em
185 // global/part of the rubberband_info struct and reset on
186 // finish
187 double width, height, width_ng, height_ng;
188 RubberbandInfo *rubberband_info;
189
190 rubberband_info = sheet->priv->rubberband_info;
191
192 g_assert (event->type == GDK_MOTION_NOTIFY);
193 cur.x = event->motion.x;
194 cur.y = event->motion.y;
195 goo_canvas_convert_from_pixels (GOO_CANVAS (sheet), &cur.x, &cur.y);
196
197 width = fabs (rubberband_info->end.x - rubberband_info->start.x);
198 height = fabs (rubberband_info->end.y - rubberband_info->start.y);
199
200 width_ng = fabs (cur.x - rubberband_info->start.x);
201 height_ng = fabs (cur.y - rubberband_info->start.y);
202
203 dx = fabs (width_ng - width);
204 dy = fabs (height_ng - height);
205 NG_DEBUG ("motion :: dx=%lf, dy=%lf :: x=%lf, y=%lf :: w_ng=%lf, h_ng=%lf", dx, dy, cur.x,
206 cur.y, width_ng, height_ng);
207
208 // TODO FIXME scroll window if needed (use
209 // http://developer.gnome.org/goocanvas/stable/GooCanvas.html#goo-canvas-scroll-to)
210
211 if (dx > 0.1 || dy > 0.1) { // a 0.1 change in pixel coords would be the least
212 // visible, silently ignore everything else
213 rubberband_info->end.x = cur.x;
214 rubberband_info->end.y = cur.y;
215 cmin.x = MIN (rubberband_info->start.x, rubberband_info->end.x);
216 cmin.y = MIN (rubberband_info->start.y, rubberband_info->end.y);
217 cmax.x = cmin.x + width_ng;
218 cmax.y = cmin.y + height_ng;
219 #if 1
220 for (iter = sheet->priv->items; iter; iter = iter->next) {
221 sheet_item_select_in_area (iter->data, &cmin, &cmax);
222 }
223 #endif
224
225 g_object_set (GOO_CANVAS_ITEM (rubberband_info->rectangle), "x", cmin.x, "y", cmin.y,
226 "width", width_ng, "height", height_ng, "visibility", GOO_CANVAS_ITEM_VISIBLE,
227 NULL);
228 goo_canvas_item_raise (GOO_CANVAS_ITEM (rubberband_info->rectangle), NULL);
229 }
230 return TRUE;
231 }
232
rubberband_finish(Sheet * sheet,GdkEvent * event)233 gboolean rubberband_finish (Sheet *sheet, GdkEvent *event)
234 {
235 RubberbandInfo *rubberband_info;
236
237 rubberband_info = sheet->priv->rubberband_info;
238 #if 1
239 GList *iter = NULL;
240 if (sheet->priv->preserve_selection_items) {
241 for (iter = sheet->priv->preserve_selection_items; iter; iter = iter->next)
242 sheet_item_set_preserve_selection (SHEET_ITEM (iter->data), FALSE);
243
244 g_list_free (sheet->priv->preserve_selection_items);
245 sheet->priv->preserve_selection_items = NULL;
246 }
247 #endif
248
249 sheet_pointer_ungrab (sheet, event);
250
251 g_object_set (rubberband_info->rectangle, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
252
253 rubberband_info->state = RUBBERBAND_START;
254 return TRUE;
255 }
256