Line |
Branch |
Exec |
Source |
1 |
|
|
/* cc-display-arrangement.c |
2 |
|
|
* |
3 |
|
|
* Copyright (C) 2007, 2008, 2017 Red Hat, Inc. |
4 |
|
|
* Copyright (C) 2013 Intel, Inc. |
5 |
|
|
* |
6 |
|
|
* Written by: Benjamin Berg <bberg@redhat.com> |
7 |
|
|
* |
8 |
|
|
* This program is free software; you can redistribute it and/or modify |
9 |
|
|
* it under the terms of the GNU General Public License as published by |
10 |
|
|
* the Free Software Foundation; either version 2, or (at your option) |
11 |
|
|
* any later version. |
12 |
|
|
* |
13 |
|
|
* This program is distributed in the hope that it will be useful, |
14 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 |
|
|
* GNU General Public License for more details. |
17 |
|
|
* |
18 |
|
|
* You should have received a copy of the GNU General Public License |
19 |
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
20 |
|
|
*/ |
21 |
|
|
|
22 |
|
|
#include <math.h> |
23 |
|
|
#include "cc-display-arrangement.h" |
24 |
|
|
#include "cc-display-config.h" |
25 |
|
|
|
26 |
|
|
struct _CcDisplayArrangement |
27 |
|
|
{ |
28 |
|
|
GtkDrawingArea object; |
29 |
|
|
|
30 |
|
|
CcDisplayConfig *config; |
31 |
|
|
|
32 |
|
|
cairo_matrix_t to_widget; |
33 |
|
|
cairo_matrix_t to_actual; |
34 |
|
|
|
35 |
|
|
gboolean drag_active; |
36 |
|
|
CcDisplayMonitor *selected_output; |
37 |
|
|
CcDisplayMonitor *prelit_output; |
38 |
|
|
/* Starting position of cursor inside the monitor. */ |
39 |
|
|
gdouble drag_anchor_x; |
40 |
|
|
gdouble drag_anchor_y; |
41 |
|
|
|
42 |
|
|
guint major_snap_distance; |
43 |
|
|
}; |
44 |
|
|
|
45 |
|
|
typedef struct _CcDisplayArrangement CcDisplayArrangement; |
46 |
|
|
|
47 |
|
|
enum { |
48 |
|
|
PROP_0, |
49 |
|
|
PROP_CONFIG, |
50 |
|
|
PROP_SELECTED_OUTPUT, |
51 |
|
|
PROP_LAST |
52 |
|
|
}; |
53 |
|
|
|
54 |
|
|
typedef enum { |
55 |
|
|
SNAP_DIR_NONE = 0, |
56 |
|
|
SNAP_DIR_X = 1 << 0, |
57 |
|
|
SNAP_DIR_Y = 1 << 1, |
58 |
|
|
SNAP_DIR_BOTH = (SNAP_DIR_X | SNAP_DIR_Y), |
59 |
|
|
} SnapDirection; |
60 |
|
|
|
61 |
|
|
typedef struct { |
62 |
|
|
cairo_matrix_t to_widget; |
63 |
|
|
guint major_snap_distance; |
64 |
|
|
gdouble dist_x; |
65 |
|
|
gdouble dist_y; |
66 |
|
|
gint mon_x; |
67 |
|
|
gint mon_y; |
68 |
|
|
SnapDirection snapped; |
69 |
|
|
} SnapData; |
70 |
|
|
|
71 |
|
|
#define MARGIN_PX 0 |
72 |
|
|
#define MARGIN_MON 0.66 |
73 |
|
|
#define MAJOR_SNAP_DISTANCE 25 |
74 |
|
|
#define MINOR_SNAP_DISTANCE 5 |
75 |
|
|
#define MIN_OVERLAP 25 |
76 |
|
|
|
77 |
|
✗ |
G_DEFINE_TYPE (CcDisplayArrangement, cc_display_arrangement, GTK_TYPE_DRAWING_AREA) |
78 |
|
|
|
79 |
|
|
static GParamSpec *props[PROP_LAST]; |
80 |
|
|
|
81 |
|
|
static void |
82 |
|
✗ |
apply_rotation_to_geometry (CcDisplayMonitor *output, |
83 |
|
|
int *w, |
84 |
|
|
int *h) |
85 |
|
|
{ |
86 |
|
|
CcDisplayRotation rotation; |
87 |
|
|
|
88 |
|
✗ |
rotation = cc_display_monitor_get_rotation (output); |
89 |
|
✗ |
if ((rotation == CC_DISPLAY_ROTATION_90) || (rotation == CC_DISPLAY_ROTATION_270)) |
90 |
|
|
{ |
91 |
|
|
int tmp; |
92 |
|
✗ |
tmp = *h; |
93 |
|
✗ |
*h = *w; |
94 |
|
✗ |
*w = tmp; |
95 |
|
|
} |
96 |
|
✗ |
} |
97 |
|
|
|
98 |
|
|
/* get_geometry */ |
99 |
|
|
static void |
100 |
|
✗ |
get_scaled_geometry (CcDisplayConfig *config, |
101 |
|
|
CcDisplayMonitor *output, |
102 |
|
|
int *x, |
103 |
|
|
int *y, |
104 |
|
|
int *w, |
105 |
|
|
int *h) |
106 |
|
|
{ |
107 |
|
✗ |
if (cc_display_monitor_is_active (output)) |
108 |
|
|
{ |
109 |
|
✗ |
cc_display_monitor_get_geometry (output, x, y, w, h); |
110 |
|
|
} |
111 |
|
|
else |
112 |
|
|
{ |
113 |
|
✗ |
cc_display_monitor_get_geometry (output, x, y, NULL, NULL); |
114 |
|
✗ |
cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (output), w, h); |
115 |
|
|
} |
116 |
|
|
|
117 |
|
✗ |
if (cc_display_config_is_layout_logical (config)) |
118 |
|
|
{ |
119 |
|
✗ |
double scale = cc_display_monitor_get_scale (output); |
120 |
|
✗ |
*w = round (*w / scale); |
121 |
|
✗ |
*h = round (*h / scale); |
122 |
|
|
} |
123 |
|
|
|
124 |
|
✗ |
apply_rotation_to_geometry (output, w, h); |
125 |
|
✗ |
} |
126 |
|
|
|
127 |
|
|
static void |
128 |
|
✗ |
get_bounding_box (CcDisplayConfig *config, |
129 |
|
|
gint *x1, |
130 |
|
|
gint *y1, |
131 |
|
|
gint *x2, |
132 |
|
|
gint *y2, |
133 |
|
|
gint *max_w, |
134 |
|
|
gint *max_h) |
135 |
|
|
{ |
136 |
|
|
GList *outputs, *l; |
137 |
|
|
|
138 |
|
✗ |
g_assert (x1 && y1 && x2 && y2); |
139 |
|
|
|
140 |
|
✗ |
*x1 = *y1 = G_MAXINT; |
141 |
|
✗ |
*x2 = *y2 = G_MININT; |
142 |
|
✗ |
*max_w = 0; |
143 |
|
✗ |
*max_h = 0; |
144 |
|
|
|
145 |
|
✗ |
outputs = cc_display_config_get_monitors (config); |
146 |
|
✗ |
for (l = outputs; l; l = l->next) |
147 |
|
|
{ |
148 |
|
✗ |
CcDisplayMonitor *output = l->data; |
149 |
|
|
int x, y, w, h; |
150 |
|
|
|
151 |
|
✗ |
if (!cc_display_monitor_is_useful (output)) |
152 |
|
✗ |
continue; |
153 |
|
|
|
154 |
|
✗ |
get_scaled_geometry (config, output, &x, &y, &w, &h); |
155 |
|
|
|
156 |
|
✗ |
*x1 = MIN (*x1, x); |
157 |
|
✗ |
*y1 = MIN (*y1, y); |
158 |
|
✗ |
*x2 = MAX (*x2, x + w); |
159 |
|
✗ |
*y2 = MAX (*y2, y + h); |
160 |
|
✗ |
*max_w = MAX (*max_w, w); |
161 |
|
✗ |
*max_h = MAX (*max_h, h); |
162 |
|
|
} |
163 |
|
✗ |
} |
164 |
|
|
|
165 |
|
|
static void |
166 |
|
✗ |
monitor_get_drawing_rect (CcDisplayArrangement *self, |
167 |
|
|
CcDisplayMonitor *output, |
168 |
|
|
gint *x1, |
169 |
|
|
gint *y1, |
170 |
|
|
gint *x2, |
171 |
|
|
gint *y2) |
172 |
|
|
{ |
173 |
|
|
gdouble x, y; |
174 |
|
|
|
175 |
|
✗ |
get_scaled_geometry (self->config, output, x1, y1, x2, y2); |
176 |
|
|
|
177 |
|
|
/* get_scaled_geometry returns the width and height */ |
178 |
|
✗ |
*x2 = *x1 + *x2; |
179 |
|
✗ |
*y2 = *y1 + *y2; |
180 |
|
|
|
181 |
|
✗ |
x = *x1; y = *y1; |
182 |
|
✗ |
cairo_matrix_transform_point (&self->to_widget, &x, &y); |
183 |
|
✗ |
*x1 = round (x); |
184 |
|
✗ |
*y1 = round (y); |
185 |
|
|
|
186 |
|
✗ |
x = *x2; y = *y2; |
187 |
|
✗ |
cairo_matrix_transform_point (&self->to_widget, &x, &y); |
188 |
|
✗ |
*x2 = round (x); |
189 |
|
✗ |
*y2 = round (y); |
190 |
|
✗ |
} |
191 |
|
|
|
192 |
|
|
|
193 |
|
|
static void |
194 |
|
✗ |
get_snap_distance (SnapData *snap_data, |
195 |
|
|
gint mon_x, |
196 |
|
|
gint mon_y, |
197 |
|
|
gint new_x, |
198 |
|
|
gint new_y, |
199 |
|
|
gdouble *dist_x, |
200 |
|
|
gdouble *dist_y) |
201 |
|
|
{ |
202 |
|
|
gdouble local_dist_x, local_dist_y; |
203 |
|
|
|
204 |
|
✗ |
local_dist_x = ABS (mon_x - new_x); |
205 |
|
✗ |
local_dist_y = ABS (mon_y - new_y); |
206 |
|
|
|
207 |
|
✗ |
cairo_matrix_transform_distance (&snap_data->to_widget, &local_dist_x, &local_dist_y); |
208 |
|
|
|
209 |
|
✗ |
if (dist_x) |
210 |
|
✗ |
*dist_x = local_dist_x; |
211 |
|
✗ |
if (dist_y) |
212 |
|
✗ |
*dist_y = local_dist_y; |
213 |
|
✗ |
} |
214 |
|
|
|
215 |
|
|
static void |
216 |
|
✗ |
maybe_update_snap (SnapData *snap_data, |
217 |
|
|
gint mon_x, |
218 |
|
|
gint mon_y, |
219 |
|
|
gint new_x, |
220 |
|
|
gint new_y, |
221 |
|
|
SnapDirection snapped, |
222 |
|
|
SnapDirection major_axis, |
223 |
|
|
gint minor_unlimited) |
224 |
|
|
{ |
225 |
|
✗ |
SnapDirection update_snap = SNAP_DIR_NONE; |
226 |
|
|
gdouble dist_x, dist_y; |
227 |
|
|
gdouble dist; |
228 |
|
|
|
229 |
|
✗ |
get_snap_distance (snap_data, mon_x, mon_y, new_x, new_y, &dist_x, &dist_y); |
230 |
|
✗ |
dist = MAX (dist_x, dist_y); |
231 |
|
|
|
232 |
|
|
/* Snap by the variable max snap distance on the major axis, ensure the |
233 |
|
|
* minor axis is below the minimum snapping distance (often just zero). */ |
234 |
|
✗ |
switch (major_axis) |
235 |
|
|
{ |
236 |
|
✗ |
case SNAP_DIR_X: |
237 |
|
✗ |
if (dist_x > snap_data->major_snap_distance) |
238 |
|
✗ |
return; |
239 |
|
✗ |
if (dist_y > MINOR_SNAP_DISTANCE) |
240 |
|
|
{ |
241 |
|
✗ |
if (new_y > mon_y && minor_unlimited <= 0) |
242 |
|
✗ |
return; |
243 |
|
✗ |
if (new_y < mon_y && minor_unlimited >= 0) |
244 |
|
✗ |
return; |
245 |
|
|
} |
246 |
|
✗ |
break; |
247 |
|
|
|
248 |
|
✗ |
case SNAP_DIR_Y: |
249 |
|
✗ |
if (dist_y > snap_data->major_snap_distance) |
250 |
|
✗ |
return; |
251 |
|
✗ |
if (dist_x > MINOR_SNAP_DISTANCE) |
252 |
|
|
{ |
253 |
|
✗ |
if (new_x > mon_x && minor_unlimited <= 0) |
254 |
|
✗ |
return; |
255 |
|
✗ |
if (new_x < mon_x && minor_unlimited >= 0) |
256 |
|
✗ |
return; |
257 |
|
|
} |
258 |
|
✗ |
break; |
259 |
|
|
|
260 |
|
✗ |
default: |
261 |
|
✗ |
g_assert_not_reached(); |
262 |
|
|
} |
263 |
|
|
|
264 |
|
✗ |
if (snapped == SNAP_DIR_BOTH) |
265 |
|
|
{ |
266 |
|
✗ |
if (snap_data->snapped == SNAP_DIR_NONE) |
267 |
|
✗ |
update_snap = SNAP_DIR_BOTH; |
268 |
|
|
|
269 |
|
|
/* Update, if this is closer on the main axis. */ |
270 |
|
✗ |
if (((major_axis == SNAP_DIR_X) && (dist_x < snap_data->dist_x)) || |
271 |
|
✗ |
((major_axis == SNAP_DIR_Y) && (dist_y < snap_data->dist_y))) |
272 |
|
|
{ |
273 |
|
✗ |
update_snap = SNAP_DIR_BOTH; |
274 |
|
|
} |
275 |
|
|
|
276 |
|
|
/* Also update if we were only snapping in one direction earlier and it |
277 |
|
|
* is better or equally good. */ |
278 |
|
✗ |
if ((snap_data->snapped == SNAP_DIR_X && (dist <= snap_data->dist_x)) || |
279 |
|
✗ |
(snap_data->snapped == SNAP_DIR_Y && (dist <= snap_data->dist_y))) |
280 |
|
|
{ |
281 |
|
✗ |
update_snap = SNAP_DIR_BOTH; |
282 |
|
|
} |
283 |
|
|
|
284 |
|
|
/* Also allow a minor axis to be added if the first axis remains identical. */ |
285 |
|
✗ |
if (((snap_data->snapped == SNAP_DIR_X) && (major_axis == SNAP_DIR_X) && (new_x == snap_data->mon_x)) || |
286 |
|
✗ |
((snap_data->snapped == SNAP_DIR_Y) && (major_axis == SNAP_DIR_Y) && (new_y == snap_data->mon_y))) |
287 |
|
|
{ |
288 |
|
✗ |
update_snap = SNAP_DIR_BOTH; |
289 |
|
|
} |
290 |
|
|
} |
291 |
|
✗ |
else if (snapped == SNAP_DIR_X) |
292 |
|
|
{ |
293 |
|
✗ |
if (dist_x < snap_data->dist_x || (snap_data->snapped & SNAP_DIR_X) == SNAP_DIR_NONE) |
294 |
|
✗ |
update_snap = SNAP_DIR_X; |
295 |
|
|
} |
296 |
|
✗ |
else if (snapped == SNAP_DIR_Y) |
297 |
|
|
{ |
298 |
|
✗ |
if (dist_y < snap_data->dist_y || (snap_data->snapped & SNAP_DIR_Y) == SNAP_DIR_NONE) |
299 |
|
✗ |
update_snap = SNAP_DIR_Y; |
300 |
|
|
} |
301 |
|
|
else |
302 |
|
|
{ |
303 |
|
✗ |
g_assert_not_reached (); |
304 |
|
|
} |
305 |
|
|
|
306 |
|
✗ |
if (update_snap & SNAP_DIR_X) |
307 |
|
|
{ |
308 |
|
✗ |
snap_data->dist_x = dist_x; |
309 |
|
✗ |
snap_data->mon_x = new_x; |
310 |
|
✗ |
snap_data->snapped = snap_data->snapped | SNAP_DIR_X; |
311 |
|
|
} |
312 |
|
✗ |
if (update_snap & SNAP_DIR_Y) |
313 |
|
|
{ |
314 |
|
✗ |
snap_data->dist_y = dist_y; |
315 |
|
✗ |
snap_data->mon_y = new_y; |
316 |
|
✗ |
snap_data->snapped = snap_data->snapped | SNAP_DIR_Y; |
317 |
|
|
} |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
static void |
321 |
|
✗ |
find_best_snapping (CcDisplayConfig *config, |
322 |
|
|
CcDisplayMonitor *snap_output, |
323 |
|
|
SnapData *snap_data) |
324 |
|
|
{ |
325 |
|
|
GList *outputs, *l; |
326 |
|
|
gint x1, y1, x2, y2; |
327 |
|
|
gint w, h; |
328 |
|
|
|
329 |
|
✗ |
g_assert (snap_data != NULL); |
330 |
|
|
|
331 |
|
✗ |
get_scaled_geometry (config, snap_output, &x1, &y1, &w, &h); |
332 |
|
✗ |
x2 = x1 + w; |
333 |
|
✗ |
y2 = y1 + h; |
334 |
|
|
|
335 |
|
|
#define OVERLAP(_s1, _s2, _t1, _t2) ((_s1) <= (_t2) && (_t1) <= (_s2)) |
336 |
|
|
|
337 |
|
✗ |
outputs = cc_display_config_get_monitors (config); |
338 |
|
✗ |
for (l = outputs; l; l = l->next) |
339 |
|
|
{ |
340 |
|
✗ |
CcDisplayMonitor *output = l->data; |
341 |
|
|
gint _x1, _y1, _x2, _y2, _h, _w; |
342 |
|
|
gint bottom_snap_pos; |
343 |
|
|
gint top_snap_pos; |
344 |
|
|
gint left_snap_pos; |
345 |
|
|
gint right_snap_pos; |
346 |
|
|
gdouble dist_x, dist_y; |
347 |
|
|
gdouble tmp; |
348 |
|
|
|
349 |
|
✗ |
if (output == snap_output) |
350 |
|
✗ |
continue; |
351 |
|
|
|
352 |
|
✗ |
if (!cc_display_monitor_is_useful (output)) |
353 |
|
✗ |
continue; |
354 |
|
|
|
355 |
|
✗ |
get_scaled_geometry (config, output, &_x1, &_y1, &_w, &_h); |
356 |
|
✗ |
_x2 = _x1 + _w; |
357 |
|
✗ |
_y2 = _y1 + _h; |
358 |
|
|
|
359 |
|
✗ |
top_snap_pos = _y1 - h; |
360 |
|
✗ |
bottom_snap_pos = _y2; |
361 |
|
✗ |
left_snap_pos = _x1 - w; |
362 |
|
✗ |
right_snap_pos = _x2; |
363 |
|
|
|
364 |
|
✗ |
dist_y = 9999; |
365 |
|
|
/* overlap on the X axis */ |
366 |
|
✗ |
if (OVERLAP (x1, x2, _x1, _x2)) |
367 |
|
|
{ |
368 |
|
✗ |
get_snap_distance (snap_data, x1, y1, x1, top_snap_pos, NULL, &dist_y); |
369 |
|
✗ |
get_snap_distance (snap_data, x1, y1, x1, bottom_snap_pos, NULL, &tmp); |
370 |
|
✗ |
dist_y = MIN(dist_y, tmp); |
371 |
|
|
} |
372 |
|
|
|
373 |
|
✗ |
dist_x = 9999; |
374 |
|
|
/* overlap on the Y axis */ |
375 |
|
✗ |
if (OVERLAP (y1, y2, _y1, _y2)) |
376 |
|
|
{ |
377 |
|
✗ |
get_snap_distance (snap_data, x1, y1, left_snap_pos, y1, &dist_x, NULL); |
378 |
|
✗ |
get_snap_distance (snap_data, x1, y1, right_snap_pos, y1, &tmp, NULL); |
379 |
|
✗ |
dist_x = MIN(dist_x, tmp); |
380 |
|
|
} |
381 |
|
|
|
382 |
|
|
/* We only snap horizontally or vertically to an edge of the same monitor */ |
383 |
|
✗ |
if (dist_y < dist_x) |
384 |
|
|
{ |
385 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, x1, top_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0); |
386 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, x1, bottom_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0); |
387 |
|
|
} |
388 |
|
✗ |
else if (dist_x < 9999) |
389 |
|
|
{ |
390 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, left_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0); |
391 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, right_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0); |
392 |
|
|
} |
393 |
|
|
|
394 |
|
|
/* Left/right edge identical on the top */ |
395 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x1, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); |
396 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x2 - w, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); |
397 |
|
|
|
398 |
|
|
/* Left/right edge identical on the bottom */ |
399 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x1, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); |
400 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x2 - w, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); |
401 |
|
|
|
402 |
|
|
/* Top/bottom edge identical on the left */ |
403 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0); |
404 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0); |
405 |
|
|
|
406 |
|
|
/* Top/bottom edge identical on the right */ |
407 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0); |
408 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0); |
409 |
|
|
|
410 |
|
|
/* If snapping is infinite, then add snapping points with minimal overlap |
411 |
|
|
* to prevent detachment. |
412 |
|
|
* This is similar to the above but simply re-defines the snapping pos |
413 |
|
|
* to have only minimal overlap */ |
414 |
|
✗ |
if (snap_data->major_snap_distance == G_MAXUINT) |
415 |
|
|
{ |
416 |
|
|
/* Hanging over the left/right edge on the top */ |
417 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1); |
418 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1); |
419 |
|
|
|
420 |
|
|
/* Left/right edge identical on the bottom */ |
421 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1); |
422 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1); |
423 |
|
|
|
424 |
|
|
/* Top/bottom edge identical on the left */ |
425 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1); |
426 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1); |
427 |
|
|
|
428 |
|
|
/* Top/bottom edge identical on the right */ |
429 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1); |
430 |
|
✗ |
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1); |
431 |
|
|
} |
432 |
|
|
} |
433 |
|
|
|
434 |
|
|
#undef OVERLAP |
435 |
|
✗ |
} |
436 |
|
|
|
437 |
|
|
static void |
438 |
|
✗ |
cc_display_arrangement_update_matrices (CcDisplayArrangement *self) |
439 |
|
|
{ |
440 |
|
|
GtkAllocation allocation; |
441 |
|
|
gdouble scale, scale_h, scale_w; |
442 |
|
|
gint x1, y1, x2, y2, max_w, max_h; |
443 |
|
|
|
444 |
|
✗ |
g_assert (self->config); |
445 |
|
|
|
446 |
|
|
/* Do not update the matrices while the user is dragging things around. */ |
447 |
|
✗ |
if (self->drag_active) |
448 |
|
✗ |
return; |
449 |
|
|
|
450 |
|
✗ |
get_bounding_box (self->config, &x1, &y1, &x2, &y2, &max_w, &max_h); |
451 |
|
✗ |
gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); |
452 |
|
|
|
453 |
|
✗ |
scale_h = (gdouble) (allocation.width - 2 * MARGIN_PX) / (x2 - x1 + max_w * 2 * MARGIN_MON); |
454 |
|
✗ |
scale_w = (gdouble) (allocation.height - 2 * MARGIN_PX) / (y2 - y1 + max_h * 2 * MARGIN_MON); |
455 |
|
|
|
456 |
|
✗ |
scale = MIN (scale_h, scale_w); |
457 |
|
|
|
458 |
|
✗ |
cairo_matrix_init_identity (&self->to_widget); |
459 |
|
✗ |
cairo_matrix_translate (&self->to_widget, allocation.width / 2.0, allocation.height / 2.0); |
460 |
|
✗ |
cairo_matrix_scale (&self->to_widget, scale, scale); |
461 |
|
✗ |
cairo_matrix_translate (&self->to_widget, - (x1 + x2) / 2.0, - (y1 + y2) / 2.0); |
462 |
|
|
|
463 |
|
✗ |
self->to_actual = self->to_widget; |
464 |
|
✗ |
cairo_matrix_invert (&self->to_actual); |
465 |
|
|
} |
466 |
|
|
|
467 |
|
|
static CcDisplayMonitor* |
468 |
|
✗ |
cc_display_arrangement_find_monitor_at (CcDisplayArrangement *self, |
469 |
|
|
gint x, |
470 |
|
|
gint y) |
471 |
|
|
{ |
472 |
|
✗ |
g_autoptr(GList) outputs = NULL; |
473 |
|
|
GList *l; |
474 |
|
|
|
475 |
|
✗ |
outputs = g_list_copy (cc_display_config_get_monitors (self->config)); |
476 |
|
|
|
477 |
|
✗ |
if (self->selected_output) |
478 |
|
✗ |
outputs = g_list_prepend (outputs, self->selected_output); |
479 |
|
|
|
480 |
|
✗ |
for (l = outputs; l; l = l->next) |
481 |
|
|
{ |
482 |
|
✗ |
CcDisplayMonitor *output = l->data; |
483 |
|
|
gint x1, y1, x2, y2; |
484 |
|
|
|
485 |
|
✗ |
if (!cc_display_monitor_is_useful (output)) |
486 |
|
✗ |
continue; |
487 |
|
|
|
488 |
|
✗ |
monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2); |
489 |
|
|
|
490 |
|
✗ |
if (x >= x1 && x <= x2 && y >= y1 && y <= y2) |
491 |
|
✗ |
return output; |
492 |
|
|
} |
493 |
|
|
|
494 |
|
✗ |
return NULL; |
495 |
|
|
} |
496 |
|
|
|
497 |
|
|
static void |
498 |
|
✗ |
on_output_changed_cb (CcDisplayArrangement *self, |
499 |
|
|
CcDisplayMonitor *output) |
500 |
|
|
{ |
501 |
|
✗ |
if (cc_display_config_count_useful_monitors (self->config) > 2) |
502 |
|
✗ |
self->major_snap_distance = MAJOR_SNAP_DISTANCE; |
503 |
|
|
else |
504 |
|
✗ |
self->major_snap_distance = G_MAXUINT; |
505 |
|
|
|
506 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (self)); |
507 |
|
✗ |
} |
508 |
|
|
|
509 |
|
|
static void |
510 |
|
✗ |
cc_display_arrangement_draw (GtkDrawingArea *drawing_area, |
511 |
|
|
cairo_t *cr, |
512 |
|
|
gint width, |
513 |
|
|
gint height, |
514 |
|
|
gpointer user_data) |
515 |
|
|
{ |
516 |
|
✗ |
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (drawing_area); |
517 |
|
✗ |
GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self)); |
518 |
|
✗ |
g_autoptr(GList) outputs = NULL; |
519 |
|
|
GList *l; |
520 |
|
|
|
521 |
|
✗ |
if (!self->config) |
522 |
|
✗ |
return; |
523 |
|
|
|
524 |
|
✗ |
cc_display_arrangement_update_matrices (self); |
525 |
|
|
|
526 |
|
|
/* Draw in reverse order so that hit detection matches visual. Also pull |
527 |
|
|
* the selected output to the back. */ |
528 |
|
✗ |
outputs = g_list_copy (cc_display_config_get_monitors (self->config)); |
529 |
|
✗ |
outputs = g_list_remove (outputs, self->selected_output); |
530 |
|
✗ |
if (self->selected_output != NULL) |
531 |
|
✗ |
outputs = g_list_prepend (outputs, self->selected_output); |
532 |
|
✗ |
outputs = g_list_reverse (outputs); |
533 |
|
|
|
534 |
|
✗ |
for (l = outputs; l; l = l->next) |
535 |
|
|
{ |
536 |
|
✗ |
CcDisplayMonitor *output = l->data; |
537 |
|
✗ |
GtkStateFlags state = GTK_STATE_FLAG_NORMAL; |
538 |
|
|
GtkBorder border, padding, margin; |
539 |
|
|
gint x1, y1, x2, y2; |
540 |
|
|
gint w, h; |
541 |
|
|
gint num; |
542 |
|
|
|
543 |
|
✗ |
if (!cc_display_monitor_is_useful (output)) |
544 |
|
✗ |
continue; |
545 |
|
|
|
546 |
|
✗ |
gtk_style_context_save (context); |
547 |
|
✗ |
cairo_save (cr); |
548 |
|
|
|
549 |
|
✗ |
gtk_style_context_add_class (context, "monitor"); |
550 |
|
|
|
551 |
|
✗ |
if (output == self->selected_output) |
552 |
|
✗ |
state |= GTK_STATE_FLAG_SELECTED; |
553 |
|
✗ |
if (output == self->prelit_output) |
554 |
|
✗ |
state |= GTK_STATE_FLAG_PRELIGHT; |
555 |
|
|
|
556 |
|
✗ |
gtk_style_context_set_state (context, state); |
557 |
|
✗ |
if (cc_display_monitor_is_primary (output) || cc_display_config_is_cloning (self->config)) |
558 |
|
✗ |
gtk_style_context_add_class (context, "primary"); |
559 |
|
|
|
560 |
|
|
/* Set in cc-display-panel.c */ |
561 |
|
✗ |
num = cc_display_monitor_get_ui_number (output); |
562 |
|
|
|
563 |
|
✗ |
monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2); |
564 |
|
✗ |
w = x2 - x1; |
565 |
|
✗ |
h = y2 - y1; |
566 |
|
|
|
567 |
|
✗ |
cairo_translate (cr, x1, y1); |
568 |
|
|
|
569 |
|
✗ |
gtk_style_context_get_margin (context, &margin); |
570 |
|
|
|
571 |
|
✗ |
cairo_translate (cr, margin.left, margin.top); |
572 |
|
|
|
573 |
|
✗ |
w -= margin.left + margin.right; |
574 |
|
✗ |
h -= margin.top + margin.bottom; |
575 |
|
|
|
576 |
|
✗ |
gtk_render_background (context, cr, 0, 0, w, h); |
577 |
|
✗ |
gtk_render_frame (context, cr, 0, 0, w, h); |
578 |
|
|
|
579 |
|
✗ |
gtk_style_context_get_border (context, &border); |
580 |
|
✗ |
gtk_style_context_get_padding (context, &padding); |
581 |
|
|
|
582 |
|
✗ |
w -= border.left + border.right + padding.left + padding.right; |
583 |
|
✗ |
h -= border.top + border.bottom + padding.top + padding.bottom; |
584 |
|
|
|
585 |
|
✗ |
cairo_translate (cr, border.left + padding.left, border.top + padding.top); |
586 |
|
|
|
587 |
|
✗ |
if (num > 0) |
588 |
|
|
{ |
589 |
|
|
PangoLayout *layout; |
590 |
|
✗ |
g_autofree gchar *number_str = NULL; |
591 |
|
|
PangoRectangle extents; |
592 |
|
|
GdkRGBA color; |
593 |
|
|
gdouble text_width, text_padding; |
594 |
|
|
|
595 |
|
✗ |
gtk_style_context_add_class (context, "monitor-label"); |
596 |
|
✗ |
gtk_style_context_remove_class (context, "monitor"); |
597 |
|
|
|
598 |
|
✗ |
gtk_style_context_get_border (context, &border); |
599 |
|
✗ |
gtk_style_context_get_padding (context, &padding); |
600 |
|
|
|
601 |
|
✗ |
cairo_translate (cr, w / 2, h / 2); |
602 |
|
|
|
603 |
|
✗ |
number_str = g_strdup_printf ("%d", num); |
604 |
|
✗ |
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), number_str); |
605 |
|
✗ |
pango_layout_get_extents (layout, NULL, &extents); |
606 |
|
|
|
607 |
|
✗ |
h = (extents.height - extents.y) / PANGO_SCALE; |
608 |
|
✗ |
text_width = (extents.width - extents.x) / PANGO_SCALE; |
609 |
|
✗ |
w = MAX (text_width, h - padding.left - padding.right); |
610 |
|
✗ |
text_padding = w - text_width; |
611 |
|
|
|
612 |
|
✗ |
w += border.left + border.right + padding.left + padding.right; |
613 |
|
✗ |
h += border.top + border.bottom + padding.top + padding.bottom; |
614 |
|
|
|
615 |
|
|
/* Enforce evenness */ |
616 |
|
✗ |
if ((w % 2) != 0) |
617 |
|
✗ |
w++; |
618 |
|
✗ |
if ((h % 2) != 0) |
619 |
|
✗ |
h++; |
620 |
|
|
|
621 |
|
✗ |
cairo_translate (cr, - w / 2, - h / 2); |
622 |
|
|
|
623 |
|
✗ |
gtk_render_background (context, cr, 0, 0, w, h); |
624 |
|
✗ |
gtk_render_frame (context, cr, 0, 0, w, h); |
625 |
|
|
|
626 |
|
✗ |
cairo_translate (cr, border.left + padding.left, border.top + padding.top); |
627 |
|
✗ |
cairo_translate (cr, extents.x + text_padding / 2, 0); |
628 |
|
|
|
629 |
|
✗ |
gtk_style_context_get_color (context, &color); |
630 |
|
✗ |
gdk_cairo_set_source_rgba (cr, &color); |
631 |
|
|
|
632 |
|
✗ |
gtk_render_layout (context, cr, 0, 0, layout); |
633 |
|
✗ |
g_object_unref (layout); |
634 |
|
|
} |
635 |
|
|
|
636 |
|
✗ |
gtk_style_context_restore (context); |
637 |
|
✗ |
cairo_restore (cr); |
638 |
|
|
} |
639 |
|
|
} |
640 |
|
|
|
641 |
|
|
static gboolean |
642 |
|
✗ |
on_click_gesture_pressed_cb (CcDisplayArrangement *self, |
643 |
|
|
gint n_press, |
644 |
|
|
gdouble x, |
645 |
|
|
gdouble y) |
646 |
|
|
{ |
647 |
|
|
CcDisplayMonitor *output; |
648 |
|
|
gdouble event_x, event_y; |
649 |
|
|
gint mon_x, mon_y; |
650 |
|
|
|
651 |
|
✗ |
if (!self->config) |
652 |
|
✗ |
return FALSE; |
653 |
|
|
|
654 |
|
✗ |
g_return_val_if_fail (self->drag_active == FALSE, FALSE); |
655 |
|
|
|
656 |
|
✗ |
output = cc_display_arrangement_find_monitor_at (self, x, y); |
657 |
|
✗ |
if (!output) |
658 |
|
✗ |
return FALSE; |
659 |
|
|
|
660 |
|
✗ |
event_x = x; |
661 |
|
✗ |
event_y = y; |
662 |
|
|
|
663 |
|
✗ |
cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y); |
664 |
|
✗ |
cc_display_monitor_get_geometry (output, &mon_x, &mon_y, NULL, NULL); |
665 |
|
|
|
666 |
|
✗ |
cc_display_arrangement_set_selected_output (self, output); |
667 |
|
|
|
668 |
|
✗ |
if (cc_display_config_count_useful_monitors (self->config) > 1) |
669 |
|
|
{ |
670 |
|
✗ |
self->drag_active = TRUE; |
671 |
|
✗ |
self->drag_anchor_x = event_x - mon_x; |
672 |
|
✗ |
self->drag_anchor_y = event_y - mon_y; |
673 |
|
|
} |
674 |
|
|
|
675 |
|
✗ |
return TRUE; |
676 |
|
|
} |
677 |
|
|
|
678 |
|
|
static gboolean |
679 |
|
✗ |
on_click_gesture_released_cb (CcDisplayArrangement *self, |
680 |
|
|
gint n_press, |
681 |
|
|
gdouble x, |
682 |
|
|
gdouble y) |
683 |
|
|
{ |
684 |
|
|
CcDisplayMonitor *output; |
685 |
|
|
|
686 |
|
✗ |
if (!self->config) |
687 |
|
✗ |
return FALSE; |
688 |
|
|
|
689 |
|
✗ |
if (!self->drag_active) |
690 |
|
✗ |
return FALSE; |
691 |
|
|
|
692 |
|
✗ |
self->drag_active = FALSE; |
693 |
|
|
|
694 |
|
✗ |
output = cc_display_arrangement_find_monitor_at (self, x, y); |
695 |
|
✗ |
gtk_widget_set_cursor_from_name (GTK_WIDGET (self), |
696 |
|
|
output != NULL ? "fleur" : NULL); |
697 |
|
|
|
698 |
|
|
/* And queue a redraw to recenter everything */ |
699 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (self)); |
700 |
|
|
|
701 |
|
✗ |
g_signal_emit_by_name (G_OBJECT (self), "updated"); |
702 |
|
|
|
703 |
|
✗ |
return TRUE; |
704 |
|
|
} |
705 |
|
|
|
706 |
|
|
static gboolean |
707 |
|
✗ |
on_motion_controller_motion_cb (CcDisplayArrangement *self, |
708 |
|
|
gdouble x, |
709 |
|
|
gdouble y) |
710 |
|
|
{ |
711 |
|
|
gdouble event_x, event_y; |
712 |
|
|
gint mon_x, mon_y; |
713 |
|
|
SnapData snap_data; |
714 |
|
|
|
715 |
|
✗ |
if (!self->config) |
716 |
|
✗ |
return FALSE; |
717 |
|
|
|
718 |
|
✗ |
if (cc_display_config_count_useful_monitors (self->config) <= 1) |
719 |
|
✗ |
return FALSE; |
720 |
|
|
|
721 |
|
✗ |
if (!self->drag_active) |
722 |
|
|
{ |
723 |
|
|
CcDisplayMonitor *output; |
724 |
|
✗ |
output = cc_display_arrangement_find_monitor_at (self, x, y); |
725 |
|
|
|
726 |
|
✗ |
gtk_widget_set_cursor_from_name (GTK_WIDGET (self), |
727 |
|
|
output != NULL ? "fleur" : NULL); |
728 |
|
✗ |
if (self->prelit_output != output) |
729 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (self)); |
730 |
|
|
|
731 |
|
✗ |
self->prelit_output = output; |
732 |
|
|
|
733 |
|
✗ |
return FALSE; |
734 |
|
|
} |
735 |
|
|
|
736 |
|
✗ |
g_assert (self->selected_output); |
737 |
|
|
|
738 |
|
✗ |
event_x = x; |
739 |
|
✗ |
event_y = y; |
740 |
|
|
|
741 |
|
✗ |
cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y); |
742 |
|
|
|
743 |
|
✗ |
mon_x = round (event_x - self->drag_anchor_x); |
744 |
|
✗ |
mon_y = round (event_y - self->drag_anchor_y); |
745 |
|
|
|
746 |
|
|
/* The monitor is now at the location as if there was no snapping whatsoever. */ |
747 |
|
✗ |
snap_data.snapped = SNAP_DIR_NONE; |
748 |
|
✗ |
snap_data.mon_x = mon_x; |
749 |
|
✗ |
snap_data.mon_y = mon_y; |
750 |
|
✗ |
snap_data.dist_x = 0; |
751 |
|
✗ |
snap_data.dist_y = 0; |
752 |
|
✗ |
snap_data.to_widget = self->to_widget; |
753 |
|
✗ |
snap_data.major_snap_distance = self->major_snap_distance; |
754 |
|
|
|
755 |
|
✗ |
cc_display_monitor_set_position (self->selected_output, mon_x, mon_y); |
756 |
|
|
|
757 |
|
✗ |
find_best_snapping (self->config, self->selected_output, &snap_data); |
758 |
|
|
|
759 |
|
✗ |
cc_display_monitor_set_position (self->selected_output, snap_data.mon_x, snap_data.mon_y); |
760 |
|
|
|
761 |
|
✗ |
return TRUE; |
762 |
|
|
} |
763 |
|
|
|
764 |
|
|
static void |
765 |
|
✗ |
cc_display_arrangement_get_property (GObject *object, |
766 |
|
|
guint prop_id, |
767 |
|
|
GValue *value, |
768 |
|
|
GParamSpec *pspec) |
769 |
|
|
{ |
770 |
|
✗ |
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object); |
771 |
|
|
|
772 |
|
✗ |
switch (prop_id) |
773 |
|
|
{ |
774 |
|
✗ |
case PROP_CONFIG: |
775 |
|
✗ |
g_value_set_object (value, self->config); |
776 |
|
✗ |
break; |
777 |
|
|
|
778 |
|
✗ |
case PROP_SELECTED_OUTPUT: |
779 |
|
✗ |
g_value_set_object (value, self->selected_output); |
780 |
|
✗ |
break; |
781 |
|
|
|
782 |
|
✗ |
default: |
783 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
784 |
|
|
} |
785 |
|
✗ |
} |
786 |
|
|
|
787 |
|
|
static void |
788 |
|
✗ |
cc_display_arrangement_set_property (GObject *object, |
789 |
|
|
guint prop_id, |
790 |
|
|
const GValue *value, |
791 |
|
|
GParamSpec *pspec) |
792 |
|
|
{ |
793 |
|
✗ |
CcDisplayArrangement *obj = CC_DISPLAY_ARRANGEMENT (object); |
794 |
|
|
|
795 |
|
✗ |
switch (prop_id) |
796 |
|
|
{ |
797 |
|
✗ |
case PROP_CONFIG: |
798 |
|
✗ |
cc_display_arrangement_set_config (obj, g_value_get_object (value)); |
799 |
|
✗ |
break; |
800 |
|
|
|
801 |
|
✗ |
case PROP_SELECTED_OUTPUT: |
802 |
|
✗ |
cc_display_arrangement_set_selected_output (obj, g_value_get_object (value)); |
803 |
|
✗ |
break; |
804 |
|
|
|
805 |
|
✗ |
default: |
806 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
807 |
|
|
} |
808 |
|
✗ |
} |
809 |
|
|
|
810 |
|
|
static void |
811 |
|
✗ |
cc_display_arrangement_finalize (GObject *object) |
812 |
|
|
{ |
813 |
|
✗ |
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object); |
814 |
|
|
|
815 |
|
✗ |
g_clear_object (&self->config); |
816 |
|
|
|
817 |
|
✗ |
G_OBJECT_CLASS (cc_display_arrangement_parent_class)->finalize (object); |
818 |
|
✗ |
} |
819 |
|
|
|
820 |
|
|
static void |
821 |
|
✗ |
cc_display_arrangement_class_init (CcDisplayArrangementClass *klass) |
822 |
|
|
{ |
823 |
|
✗ |
GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
824 |
|
|
|
825 |
|
✗ |
gobject_class->finalize = cc_display_arrangement_finalize; |
826 |
|
✗ |
gobject_class->get_property = cc_display_arrangement_get_property; |
827 |
|
✗ |
gobject_class->set_property = cc_display_arrangement_set_property; |
828 |
|
|
|
829 |
|
✗ |
props[PROP_CONFIG] = g_param_spec_object ("config", "Display Config", |
830 |
|
|
"The display configuration to work with", |
831 |
|
|
CC_TYPE_DISPLAY_CONFIG, |
832 |
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); |
833 |
|
|
|
834 |
|
✗ |
props[PROP_SELECTED_OUTPUT] = g_param_spec_object ("selected-output", "Selected Output", |
835 |
|
|
"The output that is currently selected on the configuration", |
836 |
|
|
CC_TYPE_DISPLAY_MONITOR, |
837 |
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); |
838 |
|
|
|
839 |
|
✗ |
g_object_class_install_properties (gobject_class, |
840 |
|
|
PROP_LAST, |
841 |
|
|
props); |
842 |
|
|
|
843 |
|
✗ |
g_signal_new ("updated", |
844 |
|
|
CC_TYPE_DISPLAY_ARRANGEMENT, |
845 |
|
|
G_SIGNAL_RUN_LAST, |
846 |
|
|
0, NULL, NULL, NULL, |
847 |
|
|
G_TYPE_NONE, 0); |
848 |
|
|
|
849 |
|
✗ |
gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "display-arrangement"); |
850 |
|
✗ |
} |
851 |
|
|
|
852 |
|
|
static void |
853 |
|
✗ |
cc_display_arrangement_init (CcDisplayArrangement *self) |
854 |
|
|
{ |
855 |
|
|
GtkEventController *motion_controller; |
856 |
|
|
GtkGesture *click_gesture; |
857 |
|
|
|
858 |
|
✗ |
click_gesture = gtk_gesture_click_new (); |
859 |
|
✗ |
g_signal_connect_swapped (click_gesture, "pressed", G_CALLBACK (on_click_gesture_pressed_cb), self); |
860 |
|
✗ |
g_signal_connect_swapped (click_gesture, "released", G_CALLBACK (on_click_gesture_released_cb), self); |
861 |
|
✗ |
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click_gesture)); |
862 |
|
|
|
863 |
|
✗ |
motion_controller = gtk_event_controller_motion_new (); |
864 |
|
✗ |
g_signal_connect_swapped (motion_controller, "motion", G_CALLBACK (on_motion_controller_motion_cb), self); |
865 |
|
✗ |
gtk_widget_add_controller (GTK_WIDGET (self), motion_controller); |
866 |
|
|
|
867 |
|
✗ |
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self), |
868 |
|
|
cc_display_arrangement_draw, |
869 |
|
|
self, |
870 |
|
|
NULL); |
871 |
|
|
|
872 |
|
✗ |
self->major_snap_distance = MAJOR_SNAP_DISTANCE; |
873 |
|
✗ |
} |
874 |
|
|
|
875 |
|
|
CcDisplayArrangement* |
876 |
|
✗ |
cc_display_arrangement_new (CcDisplayConfig *config) |
877 |
|
|
{ |
878 |
|
✗ |
return g_object_new (CC_TYPE_DISPLAY_ARRANGEMENT, "config", config, NULL); |
879 |
|
|
} |
880 |
|
|
|
881 |
|
|
CcDisplayConfig* |
882 |
|
✗ |
cc_display_arrangement_get_config (CcDisplayArrangement *self) |
883 |
|
|
{ |
884 |
|
✗ |
return self->config; |
885 |
|
|
} |
886 |
|
|
|
887 |
|
|
void |
888 |
|
✗ |
cc_display_arrangement_set_config (CcDisplayArrangement *self, |
889 |
|
|
CcDisplayConfig *config) |
890 |
|
|
{ |
891 |
|
✗ |
const gchar *signals[] = { "rotation", "mode", "primary", "active", "scale", "position-changed", "is-usable" }; |
892 |
|
|
GList *outputs, *l; |
893 |
|
|
guint i; |
894 |
|
|
|
895 |
|
✗ |
if (self->config) |
896 |
|
|
{ |
897 |
|
✗ |
outputs = cc_display_config_get_monitors (self->config); |
898 |
|
✗ |
for (l = outputs; l; l = l->next) |
899 |
|
|
{ |
900 |
|
✗ |
CcDisplayMonitor *output = l->data; |
901 |
|
|
|
902 |
|
✗ |
g_signal_handlers_disconnect_by_data (output, self); |
903 |
|
|
} |
904 |
|
|
} |
905 |
|
✗ |
g_clear_object (&self->config); |
906 |
|
|
|
907 |
|
✗ |
self->drag_active = FALSE; |
908 |
|
|
|
909 |
|
|
/* Listen to all the signals */ |
910 |
|
✗ |
if (config) |
911 |
|
|
{ |
912 |
|
✗ |
self->config = g_object_ref (config); |
913 |
|
|
|
914 |
|
✗ |
outputs = cc_display_config_get_monitors (self->config); |
915 |
|
✗ |
for (l = outputs; l; l = l->next) |
916 |
|
|
{ |
917 |
|
✗ |
CcDisplayMonitor *output = l->data; |
918 |
|
|
|
919 |
|
✗ |
for (i = 0; i < G_N_ELEMENTS (signals); ++i) |
920 |
|
✗ |
g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); |
921 |
|
|
} |
922 |
|
|
} |
923 |
|
|
|
924 |
|
✗ |
cc_display_arrangement_set_selected_output (self, NULL); |
925 |
|
|
|
926 |
|
✗ |
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); |
927 |
|
✗ |
} |
928 |
|
|
|
929 |
|
|
CcDisplayMonitor* |
930 |
|
✗ |
cc_display_arrangement_get_selected_output (CcDisplayArrangement *self) |
931 |
|
|
{ |
932 |
|
✗ |
return self->selected_output; |
933 |
|
|
} |
934 |
|
|
|
935 |
|
|
void |
936 |
|
✗ |
cc_display_arrangement_set_selected_output (CcDisplayArrangement *self, |
937 |
|
|
CcDisplayMonitor *output) |
938 |
|
|
{ |
939 |
|
✗ |
g_return_if_fail (self->drag_active == FALSE); |
940 |
|
|
|
941 |
|
|
/* XXX: Could check that it actually belongs to the right config object. */ |
942 |
|
✗ |
self->selected_output = output; |
943 |
|
|
|
944 |
|
✗ |
gtk_widget_queue_draw (GTK_WIDGET (self)); |
945 |
|
|
|
946 |
|
✗ |
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]); |
947 |
|
|
} |
948 |
|
|
|
949 |
|
|
static gboolean |
950 |
|
✗ |
try_snap_output (CcDisplayConfig *config, |
951 |
|
|
CcDisplayMonitor *output) |
952 |
|
|
{ |
953 |
|
|
SnapData snap_data; |
954 |
|
|
gint x, y, w, h; |
955 |
|
|
|
956 |
|
✗ |
if (!cc_display_monitor_is_useful (output)) |
957 |
|
✗ |
return FALSE; |
958 |
|
|
|
959 |
|
✗ |
get_scaled_geometry (config, output, &x, &y, &w, &h); |
960 |
|
|
|
961 |
|
✗ |
snap_data.snapped = SNAP_DIR_NONE; |
962 |
|
✗ |
snap_data.mon_x = x; |
963 |
|
✗ |
snap_data.mon_y = y; |
964 |
|
✗ |
snap_data.dist_x = 0; |
965 |
|
✗ |
snap_data.dist_y = 0; |
966 |
|
✗ |
cairo_matrix_init_identity (&snap_data.to_widget); |
967 |
|
✗ |
snap_data.major_snap_distance = G_MAXUINT; |
968 |
|
|
|
969 |
|
✗ |
find_best_snapping (config, output, &snap_data); |
970 |
|
|
|
971 |
|
✗ |
if (x != snap_data.mon_x || y != snap_data.mon_y) |
972 |
|
|
{ |
973 |
|
✗ |
cc_display_monitor_set_position (output, snap_data.mon_x, snap_data.mon_y); |
974 |
|
✗ |
return TRUE; |
975 |
|
|
} |
976 |
|
|
|
977 |
|
✗ |
return FALSE; |
978 |
|
|
} |
979 |
|
|
|
980 |
|
|
void |
981 |
|
✗ |
cc_display_config_snap_outputs (CcDisplayConfig *config) |
982 |
|
|
{ |
983 |
|
|
GList *l; |
984 |
|
|
|
985 |
|
✗ |
if (cc_display_config_count_useful_monitors (config) <= 1) |
986 |
|
✗ |
return; |
987 |
|
|
|
988 |
|
✗ |
for (l = cc_display_config_get_monitors (config); l; l = l->next) |
989 |
|
|
{ |
990 |
|
✗ |
try_snap_output (config, l->data); |
991 |
|
|
} |
992 |
|
|
} |
993 |
|
|
|