diff --git a/src/Makefile.am b/src/Makefile.am index 750f795d..17bf17e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,7 @@ src_tilda_SOURCES = \ src/tilda_terminal.h src/tilda_terminal.c \ src/tilda-url-spawner.h src/tilda-url-spawner.c \ src/tilda_window.h src/tilda_window.c \ + src/window-position.h src/window-position.c \ src/tomboykeybinder.h src/tomboykeybinder.c \ src/wizard.h src/wizard.c \ $(NULL) @@ -113,6 +114,29 @@ src_tilda_LDADD = \ -lm \ $(NULL) +check_PROGRAMS = tests/test-window-position +TESTS = $(check_PROGRAMS) + +tests_test_window_position_SOURCES = \ + tests/test-window-position.c \ + src/window-position.c \ + $(NULL) + +tests_test_window_position_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src \ + $(NULL) + +tests_test_window_position_CFLAGS = \ + $(AM_CFLAGS) \ + $(GTK_CFLAGS) \ + $(NULL) + +tests_test_window_position_LDADD = \ + $(AM_LDADD) \ + $(GTK_LIBS) \ + $(NULL) + EXTRA_DIST += \ src/glade-resources.gresource.xml \ src/tilda-dbus-actions.xml \ diff --git a/src/key_grabber.c b/src/key_grabber.c index 66faf110..8a4bf49a 100644 --- a/src/key_grabber.c +++ b/src/key_grabber.c @@ -79,14 +79,13 @@ void generate_animation_positions (struct tilda_window_ *tw) DEBUG_ASSERT (tw != NULL); gint i; - gint last_pos_x = config_getint ("x_pos"); - gint last_pos_y = config_getint ("y_pos"); + GdkRectangle geometry; + tilda_window_get_effective_geometry (tw, 0, 0, &geometry); - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - gint last_width = rectangle.width; - gint last_height = rectangle.height; + gint last_pos_x = geometry.x; + gint last_pos_y = geometry.y; + gint last_width = geometry.width; + gint last_height = geometry.height; gint screen_width; gint screen_height; screen_size_get_dimensions (&screen_width, &screen_height); @@ -160,7 +159,7 @@ void tilda_window_set_active (tilda_window *tw) XEvent event; long mask = SubstructureRedirectMask | SubstructureNotifyMask; - gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos")); + tilda_window_update_window_position (tw); if (gdk_x11_screen_supports_net_wm_hint (screen, gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW"))) { @@ -392,8 +391,6 @@ static void pull_down (struct tilda_window_ *tw) { (guchar *) &atom, 1); gdk_x11_display_error_trap_pop_ignored(gdk_display_get_default()); } - } else { - gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos")); } /* Nasty code to make metacity behave. Starting at metacity-2.22 they "fixed" the @@ -434,4 +431,3 @@ void tilda_keygrabber_unbind (const gchar *keystr) /* vim: set ts=4 sts=4 sw=4 expandtab: */ - diff --git a/src/tilda_window.c b/src/tilda_window.c index f8e73afc..e37f84c2 100644 --- a/src/tilda_window.c +++ b/src/tilda_window.c @@ -21,6 +21,7 @@ #include "tilda_window.h" #include "tilda_terminal.h" #include "key_grabber.h" +#include "window-position.h" #include #include @@ -1046,7 +1047,7 @@ gboolean tilda_window_init (const gchar *config_file, const gint instance, tilda tw->current_state = STATE_UP; GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); + tilda_window_get_effective_geometry (tw, 0, 0, &rectangle); gint width = rectangle.width; gint height = rectangle.height; @@ -1059,6 +1060,7 @@ gboolean tilda_window_init (const gchar *config_file, const gint instance, tilda /* Create GDK resources now, to prevent crashes later on */ gtk_widget_realize (tw->window); + tilda_window_update_window_position (tw); generate_animation_positions (tw); /* Initialize wizard window reference to NULL */ @@ -1286,59 +1288,93 @@ gint tilda_window_find_centering_coordinate (tilda_window *tw, { DEBUG_FUNCTION ("tilda_window_find_centering_coordinate"); - gdouble monitor_dimension = 0; - gdouble tilda_dimension = 0; - GdkMonitor *monitor = tilda_window_find_monitor_number (tw); - GdkRectangle rectangle; - gdk_monitor_get_workarea (monitor, &rectangle); + GdkRectangle geometry; + tilda_window_get_effective_geometry (tw, 0, 0, &geometry); + + return dimension == HEIGHT ? geometry.y : geometry.x; +} + +void +tilda_window_get_effective_geometry (tilda_window *tw, + gint width, + gint height, + GdkRectangle *geometry) +{ + DEBUG_FUNCTION ("tilda_window_get_effective_geometry"); + DEBUG_ASSERT (tw != NULL); + g_return_if_fail (geometry != NULL); - GdkRectangle tilda_rectangle; - config_get_configured_window_size (&tilda_rectangle); + GdkMonitor *monitor = tilda_window_find_monitor_number (tw); + GdkRectangle workarea; + gdk_monitor_get_workarea (monitor, &workarea); - if (dimension == HEIGHT) { - monitor_dimension = rectangle.height; - tilda_dimension = tilda_rectangle.height; - } else if (dimension == WIDTH) { - monitor_dimension = rectangle.width; - tilda_dimension = tilda_rectangle.width; + if (width <= 0) { + const gdouble relative_width = + GLONG_TO_DOUBLE (config_getint ("width_percentage")); + width = pixels_ratio_to_absolute (relative_width, workarea.width); } - const gdouble screen_center = monitor_dimension / 2.0; - const gdouble tilda_center = tilda_dimension / 2.0; - gint center = (int) (screen_center - tilda_center); - - if(dimension == HEIGHT) { - center += rectangle.y; - } else if (dimension == WIDTH) { - center += rectangle.x; + + if (height <= 0) { + const gdouble relative_height = + GLONG_TO_DOUBLE (config_getint ("height_percentage")); + height = pixels_ratio_to_absolute (relative_height, workarea.height); } - return center; + + gint x = (gint) config_getint ("x_pos"); + gint y = (gint) config_getint ("y_pos"); + + if (config_getbool ("centered_horizontally")) + x = workarea.x + (workarea.width - width) / 2; + + if (config_getbool ("centered_vertically")) + y = workarea.y + (workarea.height - height) / 2; + + tilda_window_constrain_position (&workarea, width, height, &x, &y); + + geometry->x = x; + geometry->y = y; + geometry->width = width; + geometry->height = height; +} + +static void +tilda_window_apply_position (tilda_window *tw, + const GdkRectangle *geometry) +{ + if (config_getbool ("centered_horizontally")) + config_setint ("x_pos", geometry->x); + + if (config_getbool ("centered_vertically")) + config_setint ("y_pos", geometry->y); + + gtk_window_move (GTK_WINDOW (tw->window), geometry->x, geometry->y); +} + +void +tilda_window_update_window_geometry (tilda_window *tw, + gint width, + gint height) +{ + DEBUG_FUNCTION ("tilda_window_update_window_geometry"); + + GdkRectangle geometry; + tilda_window_get_effective_geometry (tw, width, height, &geometry); + + gtk_window_resize (GTK_WINDOW (tw->window), + geometry.width, + geometry.height); + tilda_window_apply_position (tw, &geometry); } void tilda_window_update_window_position (tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_update_window_position"); - /** - * If the screen size changed we might also need to recenter the - * tilda window. - */ - gint pos_x, pos_y; - gboolean centered_horizontally = config_getbool ("centered_horizontally"); - gboolean centered_vertically = config_getbool ("centered_vertically"); - - if (centered_horizontally) { - pos_x = tilda_window_find_centering_coordinate (tw, WIDTH); - config_setint ("x_pos", pos_x); - pos_y = (gint) config_getint ("y_pos"); - gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y); - } - if (centered_vertically) { - pos_y = tilda_window_find_centering_coordinate (tw, HEIGHT); - config_setint ("y_pos", pos_y); - pos_x = (gint) config_getint ("x_pos"); - gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y); - } + GdkRectangle geometry; + tilda_window_get_effective_geometry (tw, 0, 0, &geometry); + + tilda_window_apply_position (tw, &geometry); } static gboolean update_tilda_window_size (gpointer user_data) @@ -1348,32 +1384,12 @@ static gboolean update_tilda_window_size (gpointer user_data) g_debug ("Updating tilda window size in idle handler to " "match new size of workarea."); - /* 1. Get current tilda window size */ - int windowHeight = gtk_widget_get_allocated_height (GTK_WIDGET (tw->window)); - int windowWidth = gtk_widget_get_allocated_width (GTK_WIDGET (tw->window)); - - gint newWidth = windowWidth; - gint newHeight = windowHeight; - - /* 2. Get the desired size and update the tilda window size if necessary. */ - GdkRectangle configured_geometry; - config_get_configured_window_size (&configured_geometry); - - if (configured_geometry.width - windowWidth >= 1) { - newWidth = configured_geometry.width; - } - - if (configured_geometry.height - windowHeight >= 1) { - newHeight = configured_geometry.height; - } - - gtk_window_resize (GTK_WINDOW (tw->window), - newWidth, - newHeight); - - tilda_window_update_window_position (tw); + /* Reapply both configured dimensions so the window also shrinks when the + * workarea becomes smaller. */ + tilda_window_update_window_geometry (tw, 0, 0); + generate_animation_positions (tw); - /* 3. Returning G_SOURCE_REMOVE below will clear the event source in Gtk. + /* Returning G_SOURCE_REMOVE below will clear the event source in Gtk. * Thus, we need to reset the ID such that a new event source can be * registered if the workarea changes again. */ tw->size_update_event_source = 0; diff --git a/src/tilda_window.h b/src/tilda_window.h index 107b711e..b8af8237 100644 --- a/src/tilda_window.h +++ b/src/tilda_window.h @@ -236,6 +236,25 @@ GdkMonitor* tilda_window_find_monitor_number(tilda_window *tw); */ gint tilda_window_find_centering_coordinate (tilda_window *tw, enum dimensions dimension); +/** + * Gets the configured window geometry constrained to the selected monitor's + * workarea. A non-positive width or height is derived from the corresponding + * configured percentage. + */ +void tilda_window_get_effective_geometry (tilda_window *tw, + gint width, + gint height, + GdkRectangle *geometry); + +/** + * Resizes the window and moves it to its effective configured position. + * A non-positive width or height is derived from the corresponding configured + * percentage. + */ +void tilda_window_update_window_geometry (tilda_window *tw, + gint width, + gint height); + void tilda_window_update_window_position (tilda_window *tw); #define TILDA_WINDOW(data) ((tilda_window *)(data)) diff --git a/src/window-position.c b/src/window-position.c new file mode 100644 index 00000000..e650d0c2 --- /dev/null +++ b/src/window-position.c @@ -0,0 +1,40 @@ +/* + * This is free software; you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, 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 Library General Public + * License along with this library. If not, see . + */ + +#include "window-position.h" + +void +tilda_window_constrain_position (const GdkRectangle *workarea, + gint window_width, + gint window_height, + gint *x, + gint *y) +{ + g_return_if_fail (workarea != NULL); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + gint max_x = workarea->x; + gint max_y = workarea->y; + + if (window_width < workarea->width) + max_x += workarea->width - window_width; + + if (window_height < workarea->height) + max_y += workarea->height - window_height; + + *x = CLAMP (*x, workarea->x, max_x); + *y = CLAMP (*y, workarea->y, max_y); +} diff --git a/src/window-position.h b/src/window-position.h new file mode 100644 index 00000000..d426142f --- /dev/null +++ b/src/window-position.h @@ -0,0 +1,31 @@ +/* + * This is free software; you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, 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 Library General Public + * License along with this library. If not, see . + */ + +#ifndef TILDA_WINDOW_POSITION_H +#define TILDA_WINDOW_POSITION_H + +#include + +G_BEGIN_DECLS + +void tilda_window_constrain_position (const GdkRectangle *workarea, + gint window_width, + gint window_height, + gint *x, + gint *y); + +G_END_DECLS + +#endif diff --git a/src/wizard.c b/src/wizard.c index bee4bf0e..50393379 100644 --- a/src/wizard.c +++ b/src/wizard.c @@ -319,7 +319,7 @@ combo_monitor_selection_changed_cb (GtkWidget* widget, tilda_window *tw) * to store the size of the screen. */ GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); + tilda_window_get_effective_geometry (tw, 0, 0, &rectangle); if(selected_monitor_rectangle.width != original_monitor_rectangle.width) { @@ -327,10 +327,6 @@ combo_monitor_selection_changed_cb (GtkWidget* widget, tilda_window *tw) SPIN_BUTTON_SET_RANGE ("spin_width_pixels", 0, new_max_width); SPIN_BUTTON_SET_VALUE ("spin_width_pixels", rectangle.width); - - gtk_window_resize (GTK_WINDOW(tw->window), - rectangle.width, - rectangle.height); } if(selected_monitor_rectangle.height != original_monitor_rectangle.height) @@ -339,10 +335,6 @@ combo_monitor_selection_changed_cb (GtkWidget* widget, tilda_window *tw) SPIN_BUTTON_SET_RANGE ("spin_height_pixels", 0, new_max_height); SPIN_BUTTON_SET_VALUE ("spin_height_pixels", rectangle.height); - - gtk_window_resize (GTK_WINDOW(tw->window), - rectangle.width, - rectangle.height); } gint screen_width, screen_height; @@ -352,7 +344,10 @@ combo_monitor_selection_changed_cb (GtkWidget* widget, tilda_window *tw) SPIN_BUTTON_SET_RANGE ("spin_y_position", 0, screen_height); SPIN_BUTTON_SET_VALUE("spin_y_position", selected_monitor_rectangle.y); - tilda_window_update_window_position (tw); + tilda_window_update_window_geometry (tw, + rectangle.width, + rectangle.height); + generate_animation_positions (tw); return GDK_EVENT_STOP; } @@ -914,19 +909,7 @@ static void spin_height_percentage_value_changed_cb (GtkWidget *spin_height_perc &spin_height_pixels_value_changed_cb, height_pixels, tw); - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - gtk_window_resize (GTK_WINDOW(tw->window), rectangle.width, height_pixels); - - if (config_getbool ("centered_vertically")) - { - config_setint ("y_pos", tilda_window_find_centering_coordinate (tw, HEIGHT)); - - gtk_window_move (GTK_WINDOW(tw->window), - config_getint ("x_pos"), - config_getint ("y_pos")); - } + tilda_window_update_window_geometry (tw, 0, height_pixels); /* Always regenerate animation positions when changing x or y position! * Otherwise you get VERY strange things going on :) */ @@ -950,19 +933,7 @@ static void spin_height_pixels_value_changed_cb (GtkWidget *spin_height_pixels, &spin_height_percentage_value_changed_cb, height_percentage * 100, tw); - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - gtk_window_resize (GTK_WINDOW(tw->window), rectangle.width, height_pixels); - - if (config_getbool ("centered_vertically")) - { - config_setint ("y_pos", tilda_window_find_centering_coordinate (tw, HEIGHT)); - - gtk_window_move (GTK_WINDOW(tw->window), - config_getint ("x_pos"), - config_getint ("y_pos")); - } + tilda_window_update_window_geometry (tw, 0, height_pixels); /* Always regenerate animation positions when changing x or y position! * Otherwise you get VERY strange things going on :) */ @@ -986,19 +957,7 @@ static void spin_width_percentage_value_changed_cb (GtkWidget *spin_width_percen &spin_width_pixels_value_changed_cb, width_pixels, tw); - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - gtk_window_resize (GTK_WINDOW(tw->window), width_pixels, rectangle.height); - - if (config_getbool ("centered_horizontally")) - { - config_setint ("x_pos", tilda_window_find_centering_coordinate (tw, WIDTH)); - - gtk_window_move (GTK_WINDOW(tw->window), - config_getint ("x_pos"), - config_getint ("y_pos")); - } + tilda_window_update_window_geometry (tw, width_pixels, 0); /* Always regenerate animation positions when changing x or y position! * Otherwise you get VERY strange things going on :) */ @@ -1021,19 +980,7 @@ static void spin_width_pixels_value_changed_cb (GtkWidget *spin_width_pixels, ti &spin_width_percentage_value_changed_cb, width_percentage * 100, tw); - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - gtk_window_resize (GTK_WINDOW(tw->window), width_pixels, rectangle.height); - - if (config_getbool ("centered_horizontally")) - { - config_setint ("x_pos", tilda_window_find_centering_coordinate (tw, WIDTH)); - - gtk_window_move (GTK_WINDOW(tw->window), - config_getint ("x_pos"), - config_getint ("y_pos")); - } + tilda_window_update_window_geometry (tw, width_pixels, 0); /* Always regenerate animation positions when changing x or y position! * Otherwise you get VERY strange things going on :) */ @@ -1255,14 +1202,8 @@ static void check_animated_pulldown_toggled_cb (GtkWidget *w, tilda_window *tw) * than show and place the window. */ if (!status) { - GdkRectangle rectangle; - config_get_configured_window_size (&rectangle); - - guint width = rectangle.width; - guint height = rectangle.height; - - gtk_window_resize (GTK_WINDOW(tw->window), width, height); - gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos")); + tilda_window_update_window_geometry (tw, 0, 0); + generate_animation_positions (tw); } /* Avoids a nasty looking glitch if you switch on animation while the window is @@ -1794,7 +1735,7 @@ static void initialize_geometry_spinners(tilda_window *tw) { /* Update range and value of height spinners */ GdkRectangle tilda_rectangle; - config_get_configured_window_size (&tilda_rectangle); + tilda_window_get_effective_geometry (tw, 0, 0, &tilda_rectangle); gint width = tilda_rectangle.width; gint height = tilda_rectangle.height; diff --git a/tests/test-window-position.c b/tests/test-window-position.c new file mode 100644 index 00000000..4dd55780 --- /dev/null +++ b/tests/test-window-position.c @@ -0,0 +1,129 @@ +/* + * This is free software; you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, 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 Library General Public + * License along with this library. If not, see . + */ + +#include "window-position.h" + +typedef struct { + GdkRectangle workarea; + gint window_width; + gint window_height; + gint requested_x; + gint requested_y; + gint expected_x; + gint expected_y; +} PositionCase; + +static void +test_position_case (gconstpointer user_data) +{ + const PositionCase *test = user_data; + gint x = test->requested_x; + gint y = test->requested_y; + + tilda_window_constrain_position (&test->workarea, + test->window_width, + test->window_height, + &x, + &y); + + g_assert_cmpint (x, ==, test->expected_x); + g_assert_cmpint (y, ==, test->expected_y); +} + +int +main (int argc, char **argv) +{ + static const PositionCase cases[] = { + { + .workarea = { .x = 0, .y = 32, .width = 3840, .height = 2128 }, + .window_width = 1535, + .window_height = 2106, + .requested_x = 2300, + .requested_y = 37, + .expected_x = 2300, + .expected_y = 37, + }, + { + .workarea = { .x = 0, .y = 32, .width = 3840, .height = 2128 }, + .window_width = 1535, + .window_height = 2128, + .requested_x = 2300, + .requested_y = 37, + .expected_x = 2300, + .expected_y = 32, + }, + { + .workarea = { .x = 100, .y = 200, .width = 800, .height = 600 }, + .window_width = 400, + .window_height = 300, + .requested_x = 0, + .requested_y = 900, + .expected_x = 100, + .expected_y = 500, + }, + { + .workarea = { .x = 100, .y = 200, .width = 800, .height = 600 }, + .window_width = 400, + .window_height = 300, + .requested_x = 1000, + .requested_y = 0, + .expected_x = 500, + .expected_y = 200, + }, + { + .workarea = { .x = -1920, .y = 24, .width = 1920, .height = 1056 }, + .window_width = 1535, + .window_height = 900, + .requested_x = -500, + .requested_y = 100, + .expected_x = -1535, + .expected_y = 100, + }, + { + .workarea = { .x = 1920, .y = 24, .width = 1920, .height = 1056 }, + .window_width = 2000, + .window_height = 1200, + .requested_x = 2400, + .requested_y = 100, + .expected_x = 1920, + .expected_y = 24, + }, + { + .workarea = { .x = -800, .y = -600, .width = 800, .height = 600 }, + .window_width = 800, + .window_height = 600, + .requested_x = -700, + .requested_y = -500, + .expected_x = -800, + .expected_y = -600, + }, + }; + static const gchar *names[] = { + "/window-position/preserves-valid-position", + "/window-position/full-height-aligns-to-workarea", + "/window-position/constrains-left-and-bottom", + "/window-position/constrains-right-and-top", + "/window-position/handles-negative-origin", + "/window-position/aligns-oversized-window", + "/window-position/aligns-exact-size", + }; + + g_test_init (&argc, &argv, NULL); + + for (guint i = 0; i < G_N_ELEMENTS (cases); i++) + g_test_add_data_func (names[i], &cases[i], test_position_case); + + return g_test_run (); +}