gnome-control-center/panels/display/cc-display-arrangement.c
Emilia Daria Majewska 0aede82e58 display: Try snapping all the displays after a config change
This issue was originally addressed in !257 ("display snap after changes").
However it only dealt with a two-monitor setup, so the user is still
unable to rotate the middle display with three or more monitors.

This commit tries to snap the displays until all are adjacent to
another.
2023-03-23 15:52:15 +00:00

1004 lines
30 KiB
C

/* cc-display-arrangement.c
*
* Copyright (C) 2007, 2008, 2017 Red Hat, Inc.
* Copyright (C) 2013 Intel, Inc.
*
* Written by: Benjamin Berg <bberg@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include "cc-display-arrangement.h"
#include "cc-display-config.h"
struct _CcDisplayArrangement
{
GtkDrawingArea object;
CcDisplayConfig *config;
cairo_matrix_t to_widget;
cairo_matrix_t to_actual;
gboolean drag_active;
CcDisplayMonitor *selected_output;
CcDisplayMonitor *prelit_output;
/* Starting position of cursor inside the monitor. */
gdouble drag_anchor_x;
gdouble drag_anchor_y;
guint major_snap_distance;
};
typedef struct _CcDisplayArrangement CcDisplayArrangement;
enum {
PROP_0,
PROP_CONFIG,
PROP_SELECTED_OUTPUT,
PROP_LAST
};
typedef enum {
SNAP_DIR_NONE = 0,
SNAP_DIR_X = 1 << 0,
SNAP_DIR_Y = 1 << 1,
SNAP_DIR_BOTH = (SNAP_DIR_X | SNAP_DIR_Y),
} SnapDirection;
typedef struct {
cairo_matrix_t to_widget;
guint major_snap_distance;
gdouble dist_x;
gdouble dist_y;
gint mon_x;
gint mon_y;
SnapDirection snapped;
} SnapData;
#define MARGIN_PX 0
#define MARGIN_MON 0.66
#define MAJOR_SNAP_DISTANCE 25
#define MINOR_SNAP_DISTANCE 5
#define MIN_OVERLAP 25
G_DEFINE_TYPE (CcDisplayArrangement, cc_display_arrangement, GTK_TYPE_DRAWING_AREA)
static GParamSpec *props[PROP_LAST];
static void
apply_rotation_to_geometry (CcDisplayMonitor *output,
int *w,
int *h)
{
CcDisplayRotation rotation;
rotation = cc_display_monitor_get_rotation (output);
if ((rotation == CC_DISPLAY_ROTATION_90) || (rotation == CC_DISPLAY_ROTATION_270))
{
int tmp;
tmp = *h;
*h = *w;
*w = tmp;
}
}
/* get_geometry */
static void
get_scaled_geometry (CcDisplayConfig *config,
CcDisplayMonitor *output,
int *x,
int *y,
int *w,
int *h)
{
if (cc_display_monitor_is_active (output))
{
cc_display_monitor_get_geometry (output, x, y, w, h);
}
else
{
cc_display_monitor_get_geometry (output, x, y, NULL, NULL);
cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (output), w, h);
}
if (cc_display_config_is_layout_logical (config))
{
double scale = cc_display_monitor_get_scale (output);
*w = round (*w / scale);
*h = round (*h / scale);
}
apply_rotation_to_geometry (output, w, h);
}
static void
get_bounding_box (CcDisplayConfig *config,
gint *x1,
gint *y1,
gint *x2,
gint *y2,
gint *max_w,
gint *max_h)
{
GList *outputs, *l;
g_assert (x1 && y1 && x2 && y2);
*x1 = *y1 = G_MAXINT;
*x2 = *y2 = G_MININT;
*max_w = 0;
*max_h = 0;
outputs = cc_display_config_get_monitors (config);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
int x, y, w, h;
if (!cc_display_monitor_is_useful (output))
continue;
get_scaled_geometry (config, output, &x, &y, &w, &h);
*x1 = MIN (*x1, x);
*y1 = MIN (*y1, y);
*x2 = MAX (*x2, x + w);
*y2 = MAX (*y2, y + h);
*max_w = MAX (*max_w, w);
*max_h = MAX (*max_h, h);
}
}
static void
monitor_get_drawing_rect (CcDisplayArrangement *self,
CcDisplayMonitor *output,
gint *x1,
gint *y1,
gint *x2,
gint *y2)
{
gdouble x, y;
get_scaled_geometry (self->config, output, x1, y1, x2, y2);
/* get_scaled_geometry returns the width and height */
*x2 = *x1 + *x2;
*y2 = *y1 + *y2;
x = *x1; y = *y1;
cairo_matrix_transform_point (&self->to_widget, &x, &y);
*x1 = round (x);
*y1 = round (y);
x = *x2; y = *y2;
cairo_matrix_transform_point (&self->to_widget, &x, &y);
*x2 = round (x);
*y2 = round (y);
}
static void
get_snap_distance (SnapData *snap_data,
gint mon_x,
gint mon_y,
gint new_x,
gint new_y,
gdouble *dist_x,
gdouble *dist_y)
{
gdouble local_dist_x, local_dist_y;
local_dist_x = ABS (mon_x - new_x);
local_dist_y = ABS (mon_y - new_y);
cairo_matrix_transform_distance (&snap_data->to_widget, &local_dist_x, &local_dist_y);
if (dist_x)
*dist_x = local_dist_x;
if (dist_y)
*dist_y = local_dist_y;
}
static void
maybe_update_snap (SnapData *snap_data,
gint mon_x,
gint mon_y,
gint new_x,
gint new_y,
SnapDirection snapped,
SnapDirection major_axis,
gint minor_unlimited)
{
SnapDirection update_snap = SNAP_DIR_NONE;
gdouble dist_x, dist_y;
gdouble dist;
get_snap_distance (snap_data, mon_x, mon_y, new_x, new_y, &dist_x, &dist_y);
dist = MAX (dist_x, dist_y);
/* Snap by the variable max snap distance on the major axis, ensure the
* minor axis is below the minimum snapping distance (often just zero). */
switch (major_axis)
{
case SNAP_DIR_X:
if (dist_x > snap_data->major_snap_distance)
return;
if (dist_y > MINOR_SNAP_DISTANCE)
{
if (new_y > mon_y && minor_unlimited <= 0)
return;
if (new_y < mon_y && minor_unlimited >= 0)
return;
}
break;
case SNAP_DIR_Y:
if (dist_y > snap_data->major_snap_distance)
return;
if (dist_x > MINOR_SNAP_DISTANCE)
{
if (new_x > mon_x && minor_unlimited <= 0)
return;
if (new_x < mon_x && minor_unlimited >= 0)
return;
}
break;
default:
g_assert_not_reached();
}
if (snapped == SNAP_DIR_BOTH)
{
if (snap_data->snapped == SNAP_DIR_NONE)
update_snap = SNAP_DIR_BOTH;
/* Update, if this is closer on the main axis. */
if (((major_axis == SNAP_DIR_X) && (dist_x < snap_data->dist_x)) ||
((major_axis == SNAP_DIR_Y) && (dist_y < snap_data->dist_y)))
{
update_snap = SNAP_DIR_BOTH;
}
/* Also update if we were only snapping in one direction earlier and it
* is better or equally good. */
if ((snap_data->snapped == SNAP_DIR_X && (dist <= snap_data->dist_x)) ||
(snap_data->snapped == SNAP_DIR_Y && (dist <= snap_data->dist_y)))
{
update_snap = SNAP_DIR_BOTH;
}
/* Also allow a minor axis to be added if the first axis remains identical. */
if (((snap_data->snapped == SNAP_DIR_X) && (major_axis == SNAP_DIR_X) && (new_x == snap_data->mon_x)) ||
((snap_data->snapped == SNAP_DIR_Y) && (major_axis == SNAP_DIR_Y) && (new_y == snap_data->mon_y)))
{
update_snap = SNAP_DIR_BOTH;
}
}
else if (snapped == SNAP_DIR_X)
{
if (dist_x < snap_data->dist_x || (snap_data->snapped & SNAP_DIR_X) == SNAP_DIR_NONE)
update_snap = SNAP_DIR_X;
}
else if (snapped == SNAP_DIR_Y)
{
if (dist_y < snap_data->dist_y || (snap_data->snapped & SNAP_DIR_Y) == SNAP_DIR_NONE)
update_snap = SNAP_DIR_Y;
}
else
{
g_assert_not_reached ();
}
if (update_snap & SNAP_DIR_X)
{
snap_data->dist_x = dist_x;
snap_data->mon_x = new_x;
snap_data->snapped = snap_data->snapped | SNAP_DIR_X;
}
if (update_snap & SNAP_DIR_Y)
{
snap_data->dist_y = dist_y;
snap_data->mon_y = new_y;
snap_data->snapped = snap_data->snapped | SNAP_DIR_Y;
}
}
static void
find_best_snapping (CcDisplayConfig *config,
CcDisplayMonitor *snap_output,
SnapData *snap_data)
{
GList *outputs, *l;
gint x1, y1, x2, y2;
gint w, h;
g_assert (snap_data != NULL);
get_scaled_geometry (config, snap_output, &x1, &y1, &w, &h);
x2 = x1 + w;
y2 = y1 + h;
#define OVERLAP(_s1, _s2, _t1, _t2) ((_s1) <= (_t2) && (_t1) <= (_s2))
outputs = cc_display_config_get_monitors (config);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
gint _x1, _y1, _x2, _y2, _h, _w;
gint bottom_snap_pos;
gint top_snap_pos;
gint left_snap_pos;
gint right_snap_pos;
gdouble dist_x, dist_y;
gdouble tmp;
if (output == snap_output)
continue;
if (!cc_display_monitor_is_useful (output))
continue;
get_scaled_geometry (config, output, &_x1, &_y1, &_w, &_h);
_x2 = _x1 + _w;
_y2 = _y1 + _h;
top_snap_pos = _y1 - h;
bottom_snap_pos = _y2;
left_snap_pos = _x1 - w;
right_snap_pos = _x2;
dist_y = 9999;
/* overlap on the X axis */
if (OVERLAP (x1, x2, _x1, _x2))
{
get_snap_distance (snap_data, x1, y1, x1, top_snap_pos, NULL, &dist_y);
get_snap_distance (snap_data, x1, y1, x1, bottom_snap_pos, NULL, &tmp);
dist_y = MIN(dist_y, tmp);
}
dist_x = 9999;
/* overlap on the Y axis */
if (OVERLAP (y1, y2, _y1, _y2))
{
get_snap_distance (snap_data, x1, y1, left_snap_pos, y1, &dist_x, NULL);
get_snap_distance (snap_data, x1, y1, right_snap_pos, y1, &tmp, NULL);
dist_x = MIN(dist_x, tmp);
}
/* We only snap horizontally or vertically to an edge of the same monitor */
if (dist_y < dist_x)
{
maybe_update_snap (snap_data, x1, y1, x1, top_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0);
maybe_update_snap (snap_data, x1, y1, x1, bottom_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0);
}
else if (dist_x < 9999)
{
maybe_update_snap (snap_data, x1, y1, left_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0);
maybe_update_snap (snap_data, x1, y1, right_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0);
}
/* Left/right edge identical on the top */
maybe_update_snap (snap_data, x1, y1, _x1, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0);
maybe_update_snap (snap_data, x1, y1, _x2 - w, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0);
/* Left/right edge identical on the bottom */
maybe_update_snap (snap_data, x1, y1, _x1, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0);
maybe_update_snap (snap_data, x1, y1, _x2 - w, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0);
/* Top/bottom edge identical on the left */
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0);
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0);
/* Top/bottom edge identical on the right */
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0);
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0);
/* If snapping is infinite, then add snapping points with minimal overlap
* to prevent detachment.
* This is similar to the above but simply re-defines the snapping pos
* to have only minimal overlap */
if (snap_data->major_snap_distance == G_MAXUINT)
{
/* Hanging over the left/right edge on the top */
maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1);
maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1);
/* Left/right edge identical on the bottom */
maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1);
maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1);
/* Top/bottom edge identical on the left */
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1);
maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1);
/* Top/bottom edge identical on the right */
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1);
maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1);
}
}
#undef OVERLAP
}
static void
cc_display_arrangement_update_matrices (CcDisplayArrangement *self)
{
GtkAllocation allocation;
gdouble scale, scale_h, scale_w;
gint x1, y1, x2, y2, max_w, max_h;
g_assert (self->config);
/* Do not update the matrices while the user is dragging things around. */
if (self->drag_active)
return;
get_bounding_box (self->config, &x1, &y1, &x2, &y2, &max_w, &max_h);
gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
scale_h = (gdouble) (allocation.width - 2 * MARGIN_PX) / (x2 - x1 + max_w * 2 * MARGIN_MON);
scale_w = (gdouble) (allocation.height - 2 * MARGIN_PX) / (y2 - y1 + max_h * 2 * MARGIN_MON);
scale = MIN (scale_h, scale_w);
cairo_matrix_init_identity (&self->to_widget);
cairo_matrix_translate (&self->to_widget, allocation.width / 2.0, allocation.height / 2.0);
cairo_matrix_scale (&self->to_widget, scale, scale);
cairo_matrix_translate (&self->to_widget, - (x1 + x2) / 2.0, - (y1 + y2) / 2.0);
self->to_actual = self->to_widget;
cairo_matrix_invert (&self->to_actual);
}
static CcDisplayMonitor*
cc_display_arrangement_find_monitor_at (CcDisplayArrangement *self,
gint x,
gint y)
{
g_autoptr(GList) outputs = NULL;
GList *l;
outputs = g_list_copy (cc_display_config_get_monitors (self->config));
if (self->selected_output)
outputs = g_list_prepend (outputs, self->selected_output);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
gint x1, y1, x2, y2;
if (!cc_display_monitor_is_useful (output))
continue;
monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2);
if (x >= x1 && x <= x2 && y >= y1 && y <= y2)
return output;
}
return NULL;
}
static void
on_output_changed_cb (CcDisplayArrangement *self,
CcDisplayMonitor *output)
{
if (cc_display_config_count_useful_monitors (self->config) > 2)
self->major_snap_distance = MAJOR_SNAP_DISTANCE;
else
self->major_snap_distance = G_MAXUINT;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
cc_display_arrangement_draw (GtkDrawingArea *drawing_area,
cairo_t *cr,
gint width,
gint height,
gpointer user_data)
{
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (drawing_area);
GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
g_autoptr(GList) outputs = NULL;
GList *l;
if (!self->config)
return;
cc_display_arrangement_update_matrices (self);
/* Draw in reverse order so that hit detection matches visual. Also pull
* the selected output to the back. */
outputs = g_list_copy (cc_display_config_get_monitors (self->config));
outputs = g_list_remove (outputs, self->selected_output);
if (self->selected_output != NULL)
outputs = g_list_prepend (outputs, self->selected_output);
outputs = g_list_reverse (outputs);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
GtkBorder border, padding, margin;
gint x1, y1, x2, y2;
gint w, h;
gint num;
if (!cc_display_monitor_is_useful (output))
continue;
gtk_style_context_save (context);
cairo_save (cr);
gtk_style_context_add_class (context, "monitor");
if (output == self->selected_output)
state |= GTK_STATE_FLAG_SELECTED;
if (output == self->prelit_output)
state |= GTK_STATE_FLAG_PRELIGHT;
gtk_style_context_set_state (context, state);
if (cc_display_monitor_is_primary (output) || cc_display_config_is_cloning (self->config))
gtk_style_context_add_class (context, "primary");
/* Set in cc-display-panel.c */
num = cc_display_monitor_get_ui_number (output);
monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2);
w = x2 - x1;
h = y2 - y1;
cairo_translate (cr, x1, y1);
gtk_style_context_get_margin (context, &margin);
cairo_translate (cr, margin.left, margin.top);
w -= margin.left + margin.right;
h -= margin.top + margin.bottom;
gtk_render_background (context, cr, 0, 0, w, h);
gtk_render_frame (context, cr, 0, 0, w, h);
gtk_style_context_get_border (context, &border);
gtk_style_context_get_padding (context, &padding);
w -= border.left + border.right + padding.left + padding.right;
h -= border.top + border.bottom + padding.top + padding.bottom;
cairo_translate (cr, border.left + padding.left, border.top + padding.top);
if (num > 0)
{
PangoLayout *layout;
g_autofree gchar *number_str = NULL;
PangoRectangle extents;
GdkRGBA color;
gdouble text_width, text_padding;
gtk_style_context_add_class (context, "monitor-label");
gtk_style_context_remove_class (context, "monitor");
gtk_style_context_get_border (context, &border);
gtk_style_context_get_padding (context, &padding);
cairo_translate (cr, w / 2, h / 2);
number_str = g_strdup_printf ("%d", num);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), number_str);
pango_layout_get_extents (layout, NULL, &extents);
h = (extents.height - extents.y) / PANGO_SCALE;
text_width = (extents.width - extents.x) / PANGO_SCALE;
w = MAX (text_width, h - padding.left - padding.right);
text_padding = w - text_width;
w += border.left + border.right + padding.left + padding.right;
h += border.top + border.bottom + padding.top + padding.bottom;
/* Enforce evenness */
if ((w % 2) != 0)
w++;
if ((h % 2) != 0)
h++;
cairo_translate (cr, - w / 2, - h / 2);
gtk_render_background (context, cr, 0, 0, w, h);
gtk_render_frame (context, cr, 0, 0, w, h);
cairo_translate (cr, border.left + padding.left, border.top + padding.top);
cairo_translate (cr, extents.x + text_padding / 2, 0);
gtk_style_context_get_color (context, &color);
gdk_cairo_set_source_rgba (cr, &color);
gtk_render_layout (context, cr, 0, 0, layout);
g_object_unref (layout);
}
gtk_style_context_restore (context);
cairo_restore (cr);
}
}
static gboolean
on_click_gesture_pressed_cb (GtkGestureClick *click_gesture,
gint n_press,
gdouble x,
gdouble y,
CcDisplayArrangement *self)
{
CcDisplayMonitor *output;
gdouble event_x, event_y;
gint mon_x, mon_y;
if (!self->config)
return FALSE;
g_return_val_if_fail (self->drag_active == FALSE, FALSE);
output = cc_display_arrangement_find_monitor_at (self, x, y);
if (!output)
return FALSE;
event_x = x;
event_y = y;
cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y);
cc_display_monitor_get_geometry (output, &mon_x, &mon_y, NULL, NULL);
cc_display_arrangement_set_selected_output (self, output);
if (cc_display_config_count_useful_monitors (self->config) > 1)
{
self->drag_active = TRUE;
self->drag_anchor_x = event_x - mon_x;
self->drag_anchor_y = event_y - mon_y;
}
return TRUE;
}
static gboolean
on_click_gesture_released_cb (GtkGestureClick *click_gesture,
gint n_press,
gdouble x,
gdouble y,
CcDisplayArrangement *self)
{
CcDisplayMonitor *output;
if (!self->config)
return FALSE;
if (!self->drag_active)
return FALSE;
self->drag_active = FALSE;
output = cc_display_arrangement_find_monitor_at (self, x, y);
gtk_widget_set_cursor_from_name (GTK_WIDGET (self),
output != NULL ? "fleur" : NULL);
/* And queue a redraw to recenter everything */
gtk_widget_queue_draw (GTK_WIDGET (self));
g_signal_emit_by_name (G_OBJECT (self), "updated");
return TRUE;
}
static gboolean
on_motion_controller_motion_cb (GtkEventControllerMotion *motion_controller,
gdouble x,
gdouble y,
CcDisplayArrangement *self)
{
gdouble event_x, event_y;
gint mon_x, mon_y;
SnapData snap_data;
if (!self->config)
return FALSE;
if (cc_display_config_count_useful_monitors (self->config) <= 1)
return FALSE;
if (!self->drag_active)
{
CcDisplayMonitor *output;
output = cc_display_arrangement_find_monitor_at (self, x, y);
gtk_widget_set_cursor_from_name (GTK_WIDGET (self),
output != NULL ? "fleur" : NULL);
if (self->prelit_output != output)
gtk_widget_queue_draw (GTK_WIDGET (self));
self->prelit_output = output;
return FALSE;
}
g_assert (self->selected_output);
event_x = x;
event_y = y;
cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y);
mon_x = round (event_x - self->drag_anchor_x);
mon_y = round (event_y - self->drag_anchor_y);
/* The monitor is now at the location as if there was no snapping whatsoever. */
snap_data.snapped = SNAP_DIR_NONE;
snap_data.mon_x = mon_x;
snap_data.mon_y = mon_y;
snap_data.dist_x = 0;
snap_data.dist_y = 0;
snap_data.to_widget = self->to_widget;
snap_data.major_snap_distance = self->major_snap_distance;
cc_display_monitor_set_position (self->selected_output, mon_x, mon_y);
find_best_snapping (self->config, self->selected_output, &snap_data);
cc_display_monitor_set_position (self->selected_output, snap_data.mon_x, snap_data.mon_y);
return TRUE;
}
static void
cc_display_arrangement_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object);
switch (prop_id)
{
case PROP_CONFIG:
g_value_set_object (value, self->config);
break;
case PROP_SELECTED_OUTPUT:
g_value_set_object (value, self->selected_output);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
cc_display_arrangement_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcDisplayArrangement *obj = CC_DISPLAY_ARRANGEMENT (object);
switch (prop_id)
{
case PROP_CONFIG:
cc_display_arrangement_set_config (obj, g_value_get_object (value));
break;
case PROP_SELECTED_OUTPUT:
cc_display_arrangement_set_selected_output (obj, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
cc_display_arrangement_finalize (GObject *object)
{
CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object);
g_clear_object (&self->config);
G_OBJECT_CLASS (cc_display_arrangement_parent_class)->finalize (object);
}
static void
cc_display_arrangement_class_init (CcDisplayArrangementClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = cc_display_arrangement_finalize;
gobject_class->get_property = cc_display_arrangement_get_property;
gobject_class->set_property = cc_display_arrangement_set_property;
props[PROP_CONFIG] = g_param_spec_object ("config", "Display Config",
"The display configuration to work with",
CC_TYPE_DISPLAY_CONFIG,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
props[PROP_SELECTED_OUTPUT] = g_param_spec_object ("selected-output", "Selected Output",
"The output that is currently selected on the configuration",
CC_TYPE_DISPLAY_MONITOR,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class,
PROP_LAST,
props);
g_signal_new ("updated",
CC_TYPE_DISPLAY_ARRANGEMENT,
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "display-arrangement");
}
static void
cc_display_arrangement_init (CcDisplayArrangement *self)
{
GtkEventController *motion_controller;
GtkGesture *click_gesture;
click_gesture = gtk_gesture_click_new ();
g_signal_connect (click_gesture, "pressed", G_CALLBACK (on_click_gesture_pressed_cb), self);
g_signal_connect (click_gesture, "released", G_CALLBACK (on_click_gesture_released_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click_gesture));
motion_controller = gtk_event_controller_motion_new ();
g_signal_connect (motion_controller, "motion", G_CALLBACK (on_motion_controller_motion_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), motion_controller);
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self),
cc_display_arrangement_draw,
self,
NULL);
self->major_snap_distance = MAJOR_SNAP_DISTANCE;
}
CcDisplayArrangement*
cc_display_arrangement_new (CcDisplayConfig *config)
{
return g_object_new (CC_TYPE_DISPLAY_ARRANGEMENT, "config", config, NULL);
}
CcDisplayConfig*
cc_display_arrangement_get_config (CcDisplayArrangement *self)
{
return self->config;
}
void
cc_display_arrangement_set_config (CcDisplayArrangement *self,
CcDisplayConfig *config)
{
const gchar *signals[] = { "rotation", "mode", "primary", "active", "scale", "position-changed", "is-usable" };
GList *outputs, *l;
guint i;
if (self->config)
{
outputs = cc_display_config_get_monitors (self->config);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
g_signal_handlers_disconnect_by_data (output, self);
}
}
g_clear_object (&self->config);
self->drag_active = FALSE;
/* Listen to all the signals */
if (config)
{
self->config = g_object_ref (config);
outputs = cc_display_config_get_monitors (self->config);
for (l = outputs; l; l = l->next)
{
CcDisplayMonitor *output = l->data;
for (i = 0; i < G_N_ELEMENTS (signals); ++i)
g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED);
}
}
cc_display_arrangement_set_selected_output (self, NULL);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]);
}
CcDisplayMonitor*
cc_display_arrangement_get_selected_output (CcDisplayArrangement *self)
{
return self->selected_output;
}
void
cc_display_arrangement_set_selected_output (CcDisplayArrangement *self,
CcDisplayMonitor *output)
{
g_return_if_fail (self->drag_active == FALSE);
/* XXX: Could check that it actually belongs to the right config object. */
self->selected_output = output;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]);
}
static gboolean
try_snap_output (CcDisplayConfig *config,
CcDisplayMonitor *output)
{
SnapData snap_data;
gint x, y, w, h;
if (!cc_display_monitor_is_useful (output))
return FALSE;
get_scaled_geometry (config, output, &x, &y, &w, &h);
snap_data.snapped = SNAP_DIR_NONE;
snap_data.mon_x = x;
snap_data.mon_y = y;
snap_data.dist_x = 0;
snap_data.dist_y = 0;
cairo_matrix_init_identity (&snap_data.to_widget);
snap_data.major_snap_distance = G_MAXUINT;
find_best_snapping (config, output, &snap_data);
if (x != snap_data.mon_x || y != snap_data.mon_y)
{
cc_display_monitor_set_position (output, snap_data.mon_x, snap_data.mon_y);
return TRUE;
}
return FALSE;
}
void
cc_display_config_snap_output (CcDisplayConfig *config,
CcDisplayMonitor *output)
{
gboolean changed;
GList *l;
if (cc_display_config_count_useful_monitors (config) <= 1)
return;
(void) (try_snap_output (config, output));
do
{
changed = FALSE;
for (l = cc_display_config_get_monitors (config); l; l = l->next)
{
changed |= try_snap_output (config, l->data);
}
}
while (changed);
}