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