GCC Code Coverage Report


Directory: ./
File: panels/display/cc-display-arrangement.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 432 0.0%
Functions: 0 29 0.0%
Branches: 0 240 0.0%

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