From a7201d27d135fd113778bbde23aa9a8db83b8dfb Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Sat, 19 Nov 2005 14:58:50 +0000 Subject: [PATCH] Merge of all the changes on the constraints_experiments branch. This is 2005-11-18 Elijah Newren Merge of all the changes on the constraints_experiments branch. This is just a summary, to get the full ChangeLog of those changes (approx. 2000 lines): cvs -q -z3 update -Pd -r constraints_experiments cvs -q -z3 diff -pu -r CONSTRAINTS_EXPERIMENTS_BRANCHPOINT ChangeLog Bugs fixed: unfiled - constraints.c is overly complicated[1] unfiled - constraints.c is not robust when all constraints cannot simultaneously be met (constraints need to be prioritized) unfiled - keep-titlebar-onscreen constraint is decoration unaware (since get_outermost_onscreen_positions() forgets to include decorations) unfiled - keyboard snap-moving and snap-resizing snap to hidden edges 109553 - gravity w/ simultaneous move & resize doesn't work 113601 - maximize vertical and horizontal should toggle and be constrained 122196 - windows show up under vertical panels 122670 - jerky/random resizing of window via keyboard[2] 124582 - keyboard and mouse snap-resizing and snap-moving erroneously moves the window multidimensionally 136307 - don't allow apps to resize themselves off the screen (*cough* filechooser *cough*) 142016, 143784 - windows should not span multiple xineramas unless placed there by the user 143145 - clamp new windows to screensize and force them onscreen, if they'll fit 144126 - Handle pathological strut lists sanely[3] 149867 - fixed aspect ratio windows are difficult to resize[4] 152898 - make screen edges consistent; allow easy slamming of windows into the left, right, and bottom edges of the screen too. 154706 - bouncing weirdness at screen edge with keyboard moving or resizing 156699 - avoid struts when placing windows, if possible (nasty a11y blocker) 302456 - dragging offscreen too restrictive 304857 - wireframe moving off the top of the screen is misleading 308521 - make uni-directional resizing easier with alt-middle-drag and prevent the occasional super annoying resize-the-wrong-side(s) behavior 312007 - snap-resize moves windows with a minimum size constraint 312104 - resizing the top of a window can cause the bottom to grow 319351 - don't instantly snap on mouse-move-snapping, remove braindeadedness of having order of releasing shift and releasing button press matter so much [1] fixed in my opinion, anyway. [2] Actually, it's not totally fixed--it's just annoying instead of almost completely unusable. Matthias had a suggestion that may fix the remainder of the problems (see http://tinyurl.com/bwzuu). [3] This bug was originally about not-quite-so-pathological cases but was left open for the worse cases. The code from the branch handles the remainder of the cases mentioned in this bug. [4] Actually, although it's far better there's still some minor issues left: a slight drift that's only noticeable after lots of resizing, and potential problems with partially onscreen constraints due to not clearing any fixed_directions flags (aspect ratio windows get resized in both directions and thus aren't fixed in one of them) New feature: 81704 - edge resistance for user move and resize operations; in particular 3 different kinds of resistance are implemented: Pixel-Distance: window movement is resisted when it aligns with an edge unless the movement is greater than a threshold number of pixels Timeout: window movement past an edge is prevented until a certain amount of time has elapsed during the operation since the first request to move it past that edge Keyboard-Buildup: when moving or resizing with the keyboard, once a window is aligned with a certain edge it cannot move past until the correct direction has been pressed enough times (e.g. 2 or 3 times) Major changes: - constraints.c has been rewritten; very few lines of code from the old version remain. There is a comment near the top of the function explaining the basics of how the new framework works. A more detailed explanation can be found in doc/how-constraints-works.txt - edge-resistance.[ch] are new files implementing edge-resistance. - boxes.[ch] are new files containing low-level error-prone functions used heavily in constraints.c and edge-resistance.c, among various places throughout the code. testboxes.c contains a thorough testsuite for the boxes.[ch] functions compiled into a program, testboxes. - meta_window_move_resize_internal() *must* be told the gravity of the associated operation (if it's just a move operation, the gravity will be ignored, but for resize and move+resize the correct value is needed) - the craziness of different values that meta_window_move_resize_internal() accepts has been documented in a large comment at the beginning of the function. It may be possible to clean this up some, but until then things will remain as they were before--caller beware. - screen and xinerama usable areas (i.e. places not covered by e.g. panels) are cached in the workspace now, as are the screen and xinerama edges. These get updated with the workarea in src/workspace.c:ensure_work_areas_validated() --- .cvsignore | 1 + ChangeLog | 111 ++ doc/how-constraints-works.txt | 283 ++++ src/.cvsignore | 1 + src/Makefile.am | 8 +- src/bell.c | 4 +- src/boxes.c | 1745 +++++++++++++++++++++++ src/boxes.h | 239 ++++ src/common.h | 24 +- src/constraints.c | 2445 +++++++++++++-------------------- src/constraints.h | 33 +- src/core.c | 40 +- src/core.h | 4 + src/display.c | 143 +- src/display.h | 28 +- src/edge-resistance.c | 1278 +++++++++++++++++ src/edge-resistance.h | 46 + src/frame.c | 6 +- src/frames.c | 30 +- src/keybindings.c | 359 ++--- src/menu.c | 2 + src/place.c | 111 +- src/screen.c | 182 +-- src/screen.h | 13 +- src/session.c | 49 +- src/stack.c | 17 +- src/tabpopup.c | 23 +- src/tabpopup.h | 6 +- src/testboxes.c | 1400 +++++++++++++++++++ src/util.c | 42 + src/util.h | 2 + src/window.c | 1083 ++++++++------- src/window.h | 59 +- src/workspace.c | 259 ++-- src/workspace.h | 20 +- 35 files changed, 7358 insertions(+), 2738 deletions(-) create mode 100644 doc/how-constraints-works.txt create mode 100644 src/boxes.c create mode 100644 src/boxes.h create mode 100644 src/edge-resistance.c create mode 100644 src/edge-resistance.h create mode 100644 src/testboxes.c diff --git a/.cvsignore b/.cvsignore index e72ba9bd0..8c231c096 100644 --- a/.cvsignore +++ b/.cvsignore @@ -15,6 +15,7 @@ ltconfig ltmain.sh stamp-h stamp-h.in +stamp-h1 stamp.h version.h config.h.in diff --git a/ChangeLog b/ChangeLog index a99178903..60718ea54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,114 @@ +2005-11-18 Elijah Newren + + Merge of all the changes on the constraints_experiments branch. + This is just a summary, to get the full ChangeLog of those + changes (approx. 2000 lines): + cvs -q -z3 update -Pd -r constraints_experiments + cvs -q -z3 diff -pu -r CONSTRAINTS_EXPERIMENTS_BRANCHPOINT ChangeLog + + Bugs fixed: + unfiled - constraints.c is overly complicated[1] + unfiled - constraints.c is not robust when all constraints + cannot simultaneously be met (constraints need to be + prioritized) + unfiled - keep-titlebar-onscreen constraint is decoration + unaware (since get_outermost_onscreen_positions() + forgets to include decorations) + unfiled - keyboard snap-moving and snap-resizing snap to hidden + edges + 109553 - gravity w/ simultaneous move & resize doesn't work + 113601 - maximize vertical and horizontal should toggle and be + constrained + 122196 - windows show up under vertical panels + 122670 - jerky/random resizing of window via keyboard[2] + 124582 - keyboard and mouse snap-resizing and snap-moving + erroneously moves the window multidimensionally + 136307 - don't allow apps to resize themselves off the screen + (*cough* filechooser *cough*) + 142016, 143784 - windows should not span multiple xineramas + unless placed there by the user + 143145 - clamp new windows to screensize and force them + onscreen, if they'll fit + 144126 - Handle pathological strut lists sanely[3] + 149867 - fixed aspect ratio windows are difficult to resize[4] + 152898 - make screen edges consistent; allow easy slamming of + windows into the left, right, and bottom edges of the + screen too. + 154706 - bouncing weirdness at screen edge with keyboard moving + or resizing + 156699 - avoid struts when placing windows, if possible (nasty + a11y blocker) + 302456 - dragging offscreen too restrictive + 304857 - wireframe moving off the top of the screen is misleading + 308521 - make uni-directional resizing easier with + alt-middle-drag and prevent the occasional super + annoying resize-the-wrong-side(s) behavior + 312007 - snap-resize moves windows with a minimum size + constraint + 312104 - resizing the top of a window can cause the bottom to + grow + 319351 - don't instantly snap on mouse-move-snapping, remove + braindeadedness of having order of releasing shift and + releasing button press matter so much + + [1] fixed in my opinion, anyway. + [2] Actually, it's not totally fixed--it's just annoying + instead of almost completely unusable. Matthias had a + suggestion that may fix the remainder of the problems (see + http://tinyurl.com/bwzuu). + [3] This bug was originally about not-quite-so-pathological + cases but was left open for the worse cases. The code from + the branch handles the remainder of the cases mentioned in + this bug. + [4] Actually, although it's far better there's still some minor + issues left: a slight drift that's only noticeable after + lots of resizing, and potential problems with partially + onscreen constraints due to not clearing any + fixed_directions flags (aspect ratio windows get resized in + both directions and thus aren't fixed in one of them) + + New feature: + 81704 - edge resistance for user move and resize operations; + in particular 3 different kinds of resistance are + implemented: + Pixel-Distance: window movement is resisted when it + aligns with an edge unless the movement is greater than + a threshold number of pixels + Timeout: window movement past an edge is prevented until + a certain amount of time has elapsed during the + operation since the first request to move it past that + edge + Keyboard-Buildup: when moving or resizing with the + keyboard, once a window is aligned with a certain edge + it cannot move past until the correct direction has + been pressed enough times (e.g. 2 or 3 times) + + Major changes: + - constraints.c has been rewritten; very few lines of code from + the old version remain. There is a comment near the top of + the function explaining the basics of how the new framework + works. A more detailed explanation can be found in + doc/how-constraints-works.txt + - edge-resistance.[ch] are new files implementing edge-resistance. + - boxes.[ch] are new files containing low-level error-prone + functions used heavily in constraints.c and edge-resistance.c, + among various places throughout the code. testboxes.c + contains a thorough testsuite for the boxes.[ch] functions + compiled into a program, testboxes. + - meta_window_move_resize_internal() *must* be told the gravity + of the associated operation (if it's just a move operation, + the gravity will be ignored, but for resize and move+resize + the correct value is needed) + - the craziness of different values that + meta_window_move_resize_internal() accepts has been documented + in a large comment at the beginning of the function. It may + be possible to clean this up some, but until then things will + remain as they were before--caller beware. + - screen and xinerama usable areas (i.e. places not covered by + e.g. panels) are cached in the workspace now, as are the + screen and xinerama edges. These get updated with the + workarea in src/workspace.c:ensure_work_areas_validated() + 2005-11-14 Elijah Newren * configure.in: post-release version bump to 2.13.2 diff --git a/doc/how-constraints-works.txt b/doc/how-constraints-works.txt new file mode 100644 index 000000000..327e5fe8e --- /dev/null +++ b/doc/how-constraints-works.txt @@ -0,0 +1,283 @@ +File contents: + Basic Ideas + Important points to remember + Explanation of fields in the ConstraintInfo struct + Gory details of resize_gravity vs. fixed_directions + +IMPORTANT NOTE: There's a big comment at the top of constraints.c +explaining how to add extra constraints or tweak others. Read it. I put +that information there because it may be enough information by itself for +people to hack on constraints.c. I won't duplicate that information in +this file; this file is for deeper details. + + +--------------------------------------------------------------------------- +Basic Ideas +--------------------------------------------------------------------------- +There are a couple basic ideas behind how this constraints.c code works and +why it works that way: + + 1) Split the low-level error-prone operations into a special file + 2) Add robustness by prioritizing constraints + 3) Make use of a minimal spanning set of rectangles for the + "onscreen region" (screen minus struts). + 4) Constraints can be user-action vs app-action oriented + 5) Avoid over-complification ;-) + +Some more details explaining these basic ideas: + + 1) Split tedious operations out + + boxes.[ch] have been added which contain many common, tedious, and + error-prone operations. I find that this separation helps a lot for + managing the complexity and ensuring that things work correctly. + Also, note that testboxes.c thoroughly tests all functionality in + boxes.[ch] and a testboxes program is automatically compiled. + + Note that functions have also been added to this file to handle some + of the tedium necessary for edge resistance as well. + + 2) Prioritize constraints + + In the old code, if each and every constraint could not be + simultaneously satisfied, then it would result in some + difficult-to-predict set of constraints being violated. This was + because constraints were applied in order, with the possibility for + each making changes that violated previous constraints, with no + checking done at the end. + + Now, all constraints have an associated priority, defined in the + ConstraintPriority enum near the top of constraints.c. The + constraints are all applied, and then are all checked; if not all are + satisfied then the least important constraints are dropped and the + process is repeated. This ensures that the most important constraints + are satisfied. + + A special note to make here is that if any one given constraint is + impossible to satisfy even individually (e.g. if minimum size hints + specify a larger window than the screen size, making the + fully-onscreen constraint impossible to satisfy) then we treat the + constraint as being satisfied. This sounds counter-intuitive, but the + idea is that we want to satisfy as many constraints as possible and if + we treat it as a violation then all constraints with a lesser priority + also get dropped along with the impossible to satisfy one. + + 3) Using maximal/spanning rectangles + + The constraints rely heavily on something I call spanning rectangles + (which Soeren referred to as maximal rectangles, a name which I think + I like better but I don't want to go change all the code now). These + spanning rectangles have the property that a window will fit on the + screen if and only if it fits within at least one of the rectangles. + Soeren had an alternative way of describing these rectangles, namely + that they were rectangles with the property that if you made any of + them larger in any direction, they would overlap with struts or be + offscreen (with the implicit assumption that there are enough of these + rectangles that combined they cover all relevant parts of the screen). + Note that, by necessity, these spanning/maximal rectangles will often + overlap each other. + + Such a list makes it relatively easy to define operations like + window-is-onscreen or clamp-window-to-region or + shove-window-into-region. Since we have a on-single-xinerama + constraint in addition to the onscreen constraint(s), we cache + number_xineramas + 1 of these lists in the workspace. These lists + then only need to be updated whenever the workarea is (e.g. when strut + list change or screen or xinerama size changes). + + 4) Constraints can be user-action vs app-action oriented + + Such differentiation requires special care for the constraints to be + consistent; e.g. if the user does something and one constraint + applies, then the app does something you have to be careful that the + constraint on the app action doesn't result in some jarring motion. + + In particular, the constraints currently allow offscreen movement or + resizing for user actions only. The way consistency is handled is + that at the end of the constraints, update_onscreen_requirements() + checks to see if the window is offscreen or split across xineramas and + updates window->require_fully_onscreen and + window->require_on_single_xinerama appropriately. + + 5) Avoid over-complification + + The previous code tried to reform the constraints into terms of a + single variable. This made the code rather difficult to + understand. ("This is a rather complicated fix for an obscure bug + that happened when resizing a window and encountering a constraint + such as the top edge of the screen.") It also failed, even on the + very example for which it used as justification for the complexity + (bug 312104 -- when keyboard resizing the top of the window, + Metacity extends the bottom once the titlebar hits the top panel), + though the reason why it failed is somewhat mysterious as it should + have worked. Further, it didn't really reform the constraints in + terms of a single variable -- there was both an x_move_delta and an + x_resize_delta, and the existence of both caused bug 109553 + (gravity with simultaneous move and resize doesn't work) + + +--------------------------------------------------------------------------- +Important points to remember +--------------------------------------------------------------------------- + + - Inner vs Outer window + + Note that because of how configure requests work and + meta_window_move_resize_internal() and friends are set up, that the + rectangles passed to meta_window_constrain() are with respect to inner + window positions instead of outer window positions (meaning that window + manager decorations are not included in the position/size). For the + constraints that need to be enforced with respect to outer window + positions, you'll need to make use of the extend_by_frame() and + unextend_by_frame() functions. + + - meta_window_move_resize_internal() accepts a really hairy set of + inputs. See the huge comment at the beginning of that function. + constraints gets screwed up if that function can't sanitize the input, + so be very careful about that. It used to be pretty busted. + + +--------------------------------------------------------------------------- +Explanation of fields in the ConstraintInfo strut +--------------------------------------------------------------------------- + +As of the time of this writing, ConstraintInfo had the following fields: + orig + current + fgeom + action_type + is_user_action + resize_gravity + fixed_directions + work_area_xinerama + entire_xinerama + usable_screen_region + usable_xinerama_region + +A brief description of each and/or pointers to more information are found +below: + orig + The previous position and size of the window, ignoring any window + decorations + current + The requested position and size of the window, ignoring any window + decorations. This rectangle gets modified by the various constraints + to specify the allowed position closest to the requested position. + fgeom + The geometry of the window frame (i.e. "decorations"), if it exists. + Otherwise, it's a dummy 0-size frame for convenience (i.e. this pointer + is guaranteed to be non-NULL so you don't have to do the stupid check). + action_type + Whether the action being constrained is a move, resize, or a combined + move and resize. Some constraints can run faster with this information + (e.g. constraining size increment hints or min size hints don't need to + do anything for pure move operations). This may also be used for + providing slightly different behavior (e.g. clip-to-region instead of + shove-into-region for resize vs. moving operations), but doesn't + currently have a lot of use for this. + is_user_action + Used to determine whether the action being constrained is a user + action. If so, certain parts of the constraint may be relaxed. Note + that this requires care to get right; see item 4 of the basic ideas + section for more details. + resize_gravity + The gravity used in the resize operation, used in order to make sure + windows are resized correctly if constraints specify that their size + must be modified. Explained further in the resize_gravity + vs. fixed_directions section. + fixed_directions + There may be multiple solutions to shoving a window back onscreen. + Typically, the shortest distance used is the solution picked, but if + e.g. an application only moved its window in a single direction, it's + more desirable that the window is shoved back in that direction than in + a different one. fixed_directions facilitates that. Explained further + in the resize_gravity vs. fixed_directions section. + work_area_xinerama + This region is defined in the workspace and just cached here for + convenience. It is basically the area obtained by taking the current + xinerama, treating all partial struts as full struts, and then + subtracting all struts from the current xinerama region. Useful + e.g. for enforcing maximization constraints. + entire_xinerama + Just a cache of the rectangle corresponding to the entire current + xinerama, including struts. Useful e.g. for enforcing fullscreen + constraints. + usable_screen_region + The set of maximal/spanning rectangles for the entire screen; this + region doesn't overlap with any struts and helps to enforce + e.g. onscreen constraints. + usable_xinerama_region + The set of maximal/spanning rectangles for the current xinerama; this + region doesn't overlap with any struts on the xinerama and helps to + enforce e.g. the on-single-xinerama constraint. + + +--------------------------------------------------------------------------- +Gory details of resize_gravity vs. fixed_directions +--------------------------------------------------------------------------- + +Note that although resize_gravity and fixed_directions look similar, they +are used for different purposes: + + - resize_gravity is only for resize operations and is used for + constraints unrelated to keeping a window within a certain region + - fixed_directions is for both move and resize operations and is + specifically for keeping a window within a specified region. + +Examples of where each are used: + + - If a window is simultaneously moved and resized to the southeast corner + with SouthEastGravity, but it turns out that the window was sized to + something smaller than the minimum size hint, then the size_hints + constraint should resize the window using the resize_gravity to ensure + that the southeast corner doesn't move. + - If an application resizes itself so that it grows downward only (which + I note could be using any of three different gravities, most likely + NorthWest), and happens to put the southeast part of the window under a + partial strut, then the window needs to be forced back on screen. + (Yes, shoved onscreen and not clipped; see bug 136307). It may be the + case that moving the window to the left results in less movement of the + window than moving the window up, which, in the absence of fixed + directions would cause us to chose moving to the left. But since the + user knows that only the height of the window is changing, they would + find moving to the left weird (especially if this were a dialog that + had been centered on its parent). It'd be better to shove the window + upwards so we make sure to keep the left and right sides fixed in this + case. Note that moving the window upwards (or leftwards) is probably + totally against the gravity in this case; but that's okay because + gravity typically assumes there's more than enough onscreen space for + the resize and we only override the gravity when that assumption is + wrong. + +For the paranoid, a fixed directions might give an impossible to fulfill +constraint (I don't think that's true currently in the code, but I haven't +thought it through in a while). If this ever becomes a problem, it should +be relatively simple to throw out the fixed directions when this happens +and rerun the constraint. Of course, it might be better to rethink things +to just avoid such a problem. + +The nitty gritty of what gets fixed: + User move: + in x direction - y direction fixed + in y direction - x direction fixed + in both dirs. - neither direction fixed + User resize: (note that for clipping, only 1 side ever changed) + in x direction - y direction fixed (technically opposite x side fixed too) + in y direction - x direction fixed (technically opposite y side fixed too) + in both dirs. - neither direction fixed + App move: + in x direction - y direction fixed + in y direction - x direction fixed + in both dirs. - neither direction fixed + App resize + in x direction - y direction fixed + in y direction - x direction fixed + in 2 parallel directions (center side gravity) - other dir. fixed + in 2 orthogonal directions (corner gravity) - neither dir. fixed + in 3 or 4 directions (a center-like gravity) - neither dir. fixed + Move & resize + Treat like resize case though this will usually mean all four sides + change and result in neither direction being fixed + Note that in all cases, if neither direction moves it is likely do to a + change in struts and thus neither direction should be fixed despite the + lack of movement. diff --git a/src/.cvsignore b/src/.cvsignore index d3a91654b..b998675ae 100644 --- a/src/.cvsignore +++ b/src/.cvsignore @@ -5,6 +5,7 @@ Makefile metacity metacity-theme-viewer metacity-dialog +testboxes testgradient inlinepixbufs.h metacity.desktop diff --git a/src/Makefile.am b/src/Makefile.am index 3ed5e1db9..efbc0b251 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,8 @@ metacity_SOURCES= \ async-getprop.h \ bell.h \ bell.c \ + boxes.h \ + boxes.c \ common.h \ compositor.c \ compositor.h \ @@ -25,6 +27,8 @@ metacity_SOURCES= \ display.h \ draw-workspace.c \ draw-workspace.h \ + edge-resistance.c \ + edge-resistance.h \ effects.c \ effects.h \ errors.c \ @@ -133,11 +137,13 @@ metacity_LDADD=@METACITY_LIBS@ $(EFENCE) metacity_theme_viewer_LDADD= @METACITY_LIBS@ libmetacity-private.la metacity_dialog_LDADD=@METACITY_LIBS@ +testboxes_SOURCES=util.h util.c boxes.h boxes.c testboxes.c testgradient_SOURCES=gradient.h gradient.c testgradient.c testasyncgetprop_SOURCES=async-getprop.h async-getprop.c testasyncgetprop.c -noinst_PROGRAMS=testgradient testasyncgetprop +noinst_PROGRAMS=testboxes testgradient testasyncgetprop +testboxes_LDADD= @METACITY_LIBS@ testgradient_LDADD= @METACITY_LIBS@ testasyncgetprop_LDADD= @METACITY_LIBS@ diff --git a/src/bell.c b/src/bell.c index b002170d8..cb1cf353f 100644 --- a/src/bell.c +++ b/src/bell.c @@ -30,8 +30,8 @@ meta_bell_flash_screen (MetaDisplay *display, MetaScreen *screen) { Window root = screen->xroot; - int width = screen->width; - int height = screen->height; + int width = screen->rect.width; + int height = screen->rect.height; if (screen->flash_window == None) { diff --git a/src/boxes.c b/src/boxes.c new file mode 100644 index 000000000..06f23c025 --- /dev/null +++ b/src/boxes.c @@ -0,0 +1,1745 @@ +/* Simple box operations */ + +/* + * Copyright (C) 2005 Elijah Newren + * [meta_rectangle_intersect() is copyright the GTK+ Team according to Havoc, + * see gdkrectangle.c. As far as Havoc knows, he probably wrote + * meta_rectangle_equal(), and I'm guessing it's (C) Red Hat. So...] + * Copyright (C) 1995-2000 GTK+ Team + * Copyright (C) 2002 Red Hat, Inc. + * + * 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 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "boxes.h" +#include "util.h" +#include /* Just for the definition of the various gravities */ +#include /* For snprintf */ + +char* +meta_rectangle_to_string (const MetaRectangle *rect, + char *output) +{ + /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit. + * Should be more than enough space. Note that of this space, the + * trailing \0 will be overwritten for all but the last rectangle. + */ + g_snprintf (output, RECT_LENGTH, "%d,%d +%d,%d", + rect->x, rect->y, rect->width, rect->height); + + return output; +} + +char* +meta_rectangle_region_to_string (GList *region, + const char *separator_string, + char *output) +{ + /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 + * for each digit. Should be more than enough space. Note that of this + * space, the trailing \0 will be overwritten for all but the last + * rectangle. + */ + char rect_string[RECT_LENGTH]; + + if (region == NULL) + snprintf (output, 10, "(EMPTY)"); + + char *cur = output; + GList *tmp = region; + while (tmp) + { + MetaRectangle *rect = tmp->data; + g_snprintf (rect_string, RECT_LENGTH, "[%d,%d +%d,%d]", + rect->x, rect->y, rect->width, rect->height); + cur = g_stpcpy (cur, rect_string); + tmp = tmp->next; + if (tmp) + cur = g_stpcpy (cur, separator_string); + } + + return output; +} + +char* +meta_rectangle_edge_to_string (const MetaEdge *edge, + char *output) +{ + /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit. + * Should be more than enough space. Note that of this space, the + * trailing \0 will be overwritten for all but the last rectangle. + * + * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and + * 2 more spaces, for a total of 10 more. + */ + g_snprintf (output, EDGE_LENGTH, "[%d,%d +%d,%d], %2d, %2d", + edge->rect.x, edge->rect.y, edge->rect.width, edge->rect.height, + edge->side_type, edge->edge_type); + + return output; +} + +char* +meta_rectangle_edge_list_to_string (GList *edge_list, + const char *separator_string, + char *output) +{ + /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 for + * each digit. Should be more than enough space. Note that of this + * space, the trailing \0 will be overwritten for all but the last + * rectangle. + * + * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and + * 2 more spaces, for a total of 10 more. + */ + char rect_string[EDGE_LENGTH]; + + if (edge_list == NULL) + snprintf (output, 10, "(EMPTY)"); + + char *cur = output; + GList *tmp = edge_list; + while (tmp) + { + MetaEdge *edge = tmp->data; + MetaRectangle *rect = &edge->rect; + g_snprintf (rect_string, EDGE_LENGTH, "([%d,%d +%d,%d], %2d, %2d)", + rect->x, rect->y, rect->width, rect->height, + edge->side_type, edge->edge_type); + cur = g_stpcpy (cur, rect_string); + tmp = tmp->next; + if (tmp) + cur = g_stpcpy (cur, separator_string); + } + + return output; +} + +MetaRectangle +meta_rect (int x, int y, int width, int height) +{ + MetaRectangle temporary; + temporary.x = x; + temporary.y = y; + temporary.width = width; + temporary.height = height; + + return temporary; +} + +int +meta_rectangle_area (const MetaRectangle *rect) +{ + g_return_val_if_fail (rect != NULL, 0); + return rect->width * rect->height; +} + +gboolean +meta_rectangle_intersect (const MetaRectangle *src1, + const MetaRectangle *src2, + MetaRectangle *dest) +{ + int dest_x, dest_y; + int dest_w, dest_h; + int return_val; + + g_return_val_if_fail (src1 != NULL, FALSE); + g_return_val_if_fail (src2 != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + return_val = FALSE; + + dest_x = MAX (src1->x, src2->x); + dest_y = MAX (src1->y, src2->y); + dest_w = MIN (src1->x + src1->width, src2->x + src2->width) - dest_x; + dest_h = MIN (src1->y + src1->height, src2->y + src2->height) - dest_y; + + if (dest_w > 0 && dest_h > 0) + { + dest->x = dest_x; + dest->y = dest_y; + dest->width = dest_w; + dest->height = dest_h; + return_val = TRUE; + } + else + { + dest->width = 0; + dest->height = 0; + } + + return return_val; +} + +gboolean +meta_rectangle_equal (const MetaRectangle *src1, + const MetaRectangle *src2) +{ + return ((src1->x == src2->x) && + (src1->y == src2->y) && + (src1->width == src2->width) && + (src1->height == src2->height)); +} + +gboolean +meta_rectangle_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + g_return_val_if_fail (rect1 != NULL, FALSE); + g_return_val_if_fail (rect2 != NULL, FALSE); + + return !((rect1->x + rect1->width <= rect2->x) || + (rect2->x + rect2->width <= rect1->x) || + (rect1->y + rect1->height <= rect2->y) || + (rect2->y + rect2->height <= rect1->y)); +} + +gboolean +meta_rectangle_vert_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + return (rect1->y < rect2->y + rect2->height && + rect2->y < rect1->y + rect1->height); +} + +gboolean +meta_rectangle_horiz_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + return (rect1->x < rect2->x + rect2->width && + rect2->x < rect1->x + rect1->width); +} + +gboolean +meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect) +{ + return (outer_rect->width >= inner_rect->width && + outer_rect->height >= inner_rect->height); +} + +gboolean +meta_rectangle_contains_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect) +{ + return + inner_rect->x >= outer_rect->x && + inner_rect->y >= outer_rect->y && + inner_rect->x + inner_rect->width <= outer_rect->x + outer_rect->width && + inner_rect->y + inner_rect->height <= outer_rect->y + outer_rect->height; +} + +void +meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect, + MetaRectangle *rect, + int gravity, + int new_width, + int new_height) +{ + /* FIXME: I'm too deep into this to know whether the below comment is + * still clear or not now that I've moved it out of constraints.c. + * boxes.h has a good comment, but I'm not sure if the below info is also + * helpful on top of that (or whether it has superfluous info). + */ + + /* These formulas may look overly simplistic at first but you can work + * everything out with a left_frame_with, right_frame_width, + * border_width, and old and new client area widths (instead of old total + * width and new total width) and you come up with the same formulas. + * + * Also, note that the reason we can treat NorthWestGravity and + * StaticGravity the same is because we're not given a location at + * which to place the window--the window was already placed + * appropriately before. So, NorthWestGravity for this function + * means to just leave the upper left corner of the outer window + * where it already is, and StaticGravity for this function means to + * just leave the upper left corner of the inner window where it + * already is. But leaving either of those two corners where they + * already are will ensure that the other corner is fixed as well + * (since frame size doesn't change)--thus making the two + * equivalent. + */ + + /* First, the x direction */ + int adjust = 0; + switch (gravity) + { + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + rect->x = old_rect->x; + break; + + case NorthGravity: + case CenterGravity: + case SouthGravity: + /* FIXME: Needing to adjust new_width kind of sucks, but not doing so + * would cause drift. + */ + new_width -= (old_rect->width - new_width) % 2; + rect->x = old_rect->x + (old_rect->width - new_width)/2; + break; + + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + rect->x = old_rect->x + (old_rect->width - new_width); + break; + + case StaticGravity: + default: + rect->x = old_rect->x; + break; + } + rect->width = new_width; + + /* Next, the y direction */ + adjust = 0; + switch (gravity) + { + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + rect->y = old_rect->y; + break; + + case WestGravity: + case CenterGravity: + case EastGravity: + /* FIXME: Needing to adjust new_height kind of sucks, but not doing so + * would cause drift. + */ + new_height -= (old_rect->height - new_height) % 2; + rect->y = old_rect->y + (old_rect->height - new_height)/2; + break; + + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + rect->y = old_rect->y + (old_rect->height - new_height); + break; + + case StaticGravity: + default: + rect->y = old_rect->y; + break; + } + rect->height = new_height; +} + +/* Not so simple helper function for get_minimal_spanning_set_for_region() */ +static GList* +merge_spanning_rects_in_region (GList *region) +{ + /* NOTE FOR ANY OPTIMIZATION PEOPLE OUT THERE: Please see the + * documentation of get_minimal_spanning_set_for_region() for performance + * considerations that also apply to this function. + */ + + GList* compare; + compare = region; + + if (region == NULL) + { + meta_warning ("Region to merge was empty! Either you have a some " + "pathological STRUT list or there's a bug somewhere!\n"); + return NULL; + } + + while (compare && compare->next) + { + MetaRectangle *a = compare->data; + GList *other = compare->next; + + g_assert (a->width > 0 && a->height > 0); + + while (other) + { + MetaRectangle *b = other->data; + GList *delete_me = NULL; + + g_assert (b->width > 0 && b->height > 0); + + /* If a contains b, just remove b */ + if (meta_rectangle_contains_rect (a, b)) + { + delete_me = other; + } + /* If b contains a, just remove a */ + else if (meta_rectangle_contains_rect (a, b)) + { + delete_me = compare; + } + /* If a and b might be mergeable horizontally */ + else if (a->y == b->y && a->height == b->height) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + } + /* If a and b are adjacent */ + else if (a->x + a->width == b->x || a->x == b->x + b->width) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + } + } + /* If a and b might be mergeable vertically */ + else if (a->x == b->x && a->width == b->width) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + } + /* If a and b are adjacent */ + else if (a->y + a->height == b->y || a->y == b->y + b->height) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + } + } + + other = other->next; + + /* Delete any rectangle in the list that is no longer wanted */ + if (delete_me != NULL) + { + /* Deleting the rect we compare others to is a little tricker */ + if (compare == delete_me) + { + compare = compare->next; + other = compare->next; + a = compare->data; + } + + /* Okay, we can free it now */ + g_free (delete_me->data); + region = g_list_delete_link (region, delete_me); + } + + } + + compare = compare->next; + } + + return region; +} + +/* Simple helper function for get_minimal_spanning_set_for_region()... */ +static gint +compare_rect_areas (gconstpointer a, gconstpointer b) +{ + const MetaRectangle *a_rect = (gconstpointer) a; + const MetaRectangle *b_rect = (gconstpointer) b; + + int a_area = meta_rectangle_area (a_rect); + int b_area = meta_rectangle_area (b_rect); + + return b_area - a_area; /* positive ret value denotes b > a, ... */ +} + +/* This function is trying to find a "minimal spanning set (of rectangles)" + * for a given region. + * + * The region is given by taking basic_rect, then removing the areas + * covered by all the rectangles in the all_struts list, and then expanding + * the resulting region by the given number of pixels in each direction. + * + * A "minimal spanning set (of rectangles)" is the best name I could come + * up with for the concept I had in mind. Basically, for a given region, I + * want a set of rectangles with the property that a window is contained in + * the region if and only if it is contained within at least one of the + * rectangles. + * + * The GList* returned will be a list of (allocated) MetaRectangles. + * The list will need to be freed by calling + * meta_rectangle_free_spanning_set() on it (or by manually + * implementing that function...) + */ +GList* +meta_rectangle_get_minimal_spanning_set_for_region ( + const MetaRectangle *basic_rect, + const GSList *all_struts) +{ + /* NOTE FOR OPTIMIZERS: This function *might* be somewhat slow, + * especially due to the call to merge_spanning_rects_in_region() (which + * is O(n^2) where n is the size of the list generated in this function). + * This is made more onerous due to the fact that it involves a fair + * number of memory allocation and deallocation calls. However, n is 1 + * for default installations of Gnome (because partial struts aren't used + * by default and only partial struts increase the size of the spanning + * set generated). With one partial strut, n will be 2 or 3. With 2 + * partial struts, n will probably be 4 or 5. So, n probably isn't large + * enough to make this worth bothering. Further, it is only called from + * workspace.c:ensure_work_areas_validated (at least as of the time of + * writing this comment), which in turn should only be called if the + * strut list changes or the screen or xinerama size changes. If it ever + * does show up on profiles (most likely because people start using + * ridiculously huge numbers of partial struts), possible optimizations + * include: + * + * (1) rewrite merge_spanning_rects_in_region() to be O(n) or O(nlogn). + * I'm not totally sure it's possible, but with a couple copies of + * the list and sorting them appropriately, I believe it might be. + * (2) only call merge_spanning_rects_in_region() with a subset of the + * full list of rectangles. I believe from some of my preliminary + * debugging and thinking about it that it is possible to figure out + * apriori groups of rectangles which are only merge candidates with + * each other. (See testboxes.c:get_screen_region() when which==2 + * and track the steps of this function carefully to see what gave + * me the hint that this might work) + * (3) figure out how to avoid merge_spanning_rects_in_region(). I think + * it might be possible to modify this function to make that + * possible, and I spent just a little while thinking about it, but n + * wasn't large enough to convince me to care yet. + * (4) Some of the stuff Rob mentioned at http://mail.gnome.org/archives\ + * /metacity-devel-list/2005-November/msg00028.html. (Sorry for the + * URL splitting.) + */ + + GList *ret; + GList *tmp_list; + const GSList *strut_iter; + MetaRectangle *temp_rect; + + /* The algorithm is basically as follows: + * Initialize rectangle_set to basic_rect + * Foreach strut: + * Foreach rectangle in rectangle_set: + * - Split the rectangle into new rectangles that don't overlap the + * strut (but which are as big as possible otherwise) + * - Remove the old (pre-split) rectangle from the rectangle_set, + * and replace it with the new rectangles generated from the + * splitting + */ + + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *basic_rect; + ret = g_list_prepend (NULL, temp_rect); + + strut_iter = all_struts; + while (strut_iter) + { + GList *rect_iter; + MetaRectangle *strut = (MetaRectangle*) strut_iter->data; + + tmp_list = ret; + ret = NULL; + rect_iter = tmp_list; + while (rect_iter) + { + MetaRectangle *rect = (MetaRectangle*) rect_iter->data; + if (!meta_rectangle_overlap (rect, strut)) + ret = g_list_prepend (ret, rect); + else + { + /* If there is area in rect left of strut */ + if (rect->x < strut->x) + { + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + temp_rect->width = strut->x - rect->x; + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect right of strut */ + if (rect->x + rect->width > strut->x + strut->width) + { + int new_x; + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + new_x = strut->x + strut->width; + temp_rect->width = rect->x + rect->width - new_x; + temp_rect->x = new_x; + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect above strut */ + if (rect->y < strut->y) + { + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + temp_rect->height = strut->y - rect->y; + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect below strut */ + if (rect->y + rect->height > strut->y + strut->height) + { + int new_y; + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + new_y = strut->y + strut->height; + temp_rect->height = rect->y + rect->height - new_y; + temp_rect->y = new_y; + ret = g_list_prepend (ret, temp_rect); + } + g_free (rect); + } + rect_iter = rect_iter->next; + } + g_list_free (tmp_list); + strut_iter = strut_iter->next; + } + + /* Sort by maximal area, just because I feel like it... */ + ret = g_list_sort (ret, compare_rect_areas); + + /* Merge rectangles if possible so that the list really is minimal */ + ret = merge_spanning_rects_in_region (ret); + + return ret; +} + +GList* +meta_rectangle_expand_region (GList *region, + const int left_expand, + const int right_expand, + const int top_expand, + const int bottom_expand) +{ + /* Now it's time to do the directional expansion */ + GList *tmp_list = region; + while (tmp_list) + { + MetaRectangle *rect = (MetaRectangle*) tmp_list->data; + rect->x -= left_expand; + rect->width += (left_expand + right_expand); + rect->y -= top_expand; + rect->height += (top_expand + bottom_expand); + tmp_list = tmp_list->next; + } + + return region; +} + +void +meta_rectangle_free_list_and_elements (GList *filled_list) +{ + g_list_foreach (filled_list, + (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ + NULL); + g_list_free (filled_list); +} + +gboolean +meta_rectangle_could_fit_in_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + const GList *temp; + gboolean could_fit; + + temp = spanning_rects; + could_fit = FALSE; + while (!could_fit && temp != NULL) + { + could_fit = could_fit || meta_rectangle_could_fit_rect (temp->data, rect); + temp = temp->next; + } + + return could_fit; +} + +gboolean +meta_rectangle_contained_in_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + const GList *temp; + gboolean contained; + + temp = spanning_rects; + contained = FALSE; + while (!contained && temp != NULL) + { + contained = contained || meta_rectangle_contains_rect (temp->data, rect); + temp = temp->next; + } + + return contained; +} + +void +meta_rectangle_clamp_to_fit_into_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect, + const MetaRectangle *min_size) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + + /* First, find best rectangle from spanning_rects to which we can clamp + * rect to fit into. + */ + temp = spanning_rects; + while (temp) + { + int factor = 1; + MetaRectangle *compare_rect = temp->data; + int maximal_overlap_amount_for_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + factor = 0; + + /* If y is fixed and the entire height of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + factor = 0; + + /* If compare can't hold the min_size window, set factor to 0 */ + if (compare_rect->width < min_size->width || + compare_rect->height < min_size->height) + factor = 0; + + /* Determine maximal overlap amount */ + maximal_overlap_amount_for_compare = + MIN (rect->width, compare_rect->width) * + MIN (rect->height, compare_rect->height); + maximal_overlap_amount_for_compare *= factor; + + /* See if this is the best rect so far */ + if (maximal_overlap_amount_for_compare > best_overlap) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + } + + temp = temp->next; + } + + /* Clamp rect appropriately */ + if (best_rect == NULL) + { + meta_warning ("No rect whose size to clamp to found!\n"); + + /* If it doesn't fit, at least make it no bigger than it has to be */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + rect->width = min_size->width; + if (!(fixed_directions & FIXED_DIRECTION_Y)) + rect->height = min_size->height; + } + else + { + rect->width = MIN (rect->width, best_rect->width); + rect->height = MIN (rect->height, best_rect->height); + } +} + +void +meta_rectangle_clip_to_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + + /* First, find best rectangle from spanning_rects to which we will clip + * rect into. + */ + temp = spanning_rects; + while (temp) + { + int factor = 1; + MetaRectangle *compare_rect = temp->data; + MetaRectangle overlap; + int maximal_overlap_amount_for_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + factor = 0; + + /* If y is fixed and the entire height of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + factor = 0; + + /* Determine maximal overlap amount */ + meta_rectangle_intersect (rect, compare_rect, &overlap); + maximal_overlap_amount_for_compare = meta_rectangle_area (&overlap); + maximal_overlap_amount_for_compare *= factor; + + /* See if this is the best rect so far */ + if (maximal_overlap_amount_for_compare > best_overlap) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + } + + temp = temp->next; + } + + /* Clip rect appropriately */ + if (best_rect == NULL) + meta_warning ("No rect to clip to found!\n"); + else + { + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + { + /* Find the new left and right */ + int new_x = MAX (rect->x, best_rect->x); + rect->width = MIN ((rect->x + rect->width) - new_x, + (best_rect->x + best_rect->width) - new_x); + rect->x = new_x; + } + + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_Y)) + { + /* Clip the top, if needed */ + int new_y = MAX (rect->y, best_rect->y); + rect->height = MIN ((rect->y + rect->height) - new_y, + (best_rect->y + best_rect->height) - new_y); + rect->y = new_y; + } + } +} + +void +meta_rectangle_shove_into_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + int shortest_distance = G_MAXINT; + + /* First, find best rectangle from spanning_rects to which we will shove + * rect into. + */ + temp = spanning_rects; + while (temp) + { + int factor = 1; + MetaRectangle *compare_rect = temp->data; + int maximal_overlap_amount_for_compare; + int dist_to_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + factor = 0; + + /* If y is fixed and the entire height of rect doesn't fit in compare, set + * factor to 0. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + factor = 0; + + /* Determine maximal overlap amount between rect & compare_rect */ + maximal_overlap_amount_for_compare = + MIN (rect->width, compare_rect->width) * + MIN (rect->height, compare_rect->height); + + /* Determine distance necessary to put rect into comapre_rect */ + dist_to_compare = 0; + if (compare_rect->x > rect->x) + dist_to_compare += compare_rect->x - rect->x; + if (compare_rect->x + compare_rect->width < rect->x + rect->width) + dist_to_compare += (rect->x + rect->width) - + (compare_rect->x + compare_rect->width); + if (compare_rect->y > rect->y) + dist_to_compare += compare_rect->y - rect->y; + if (compare_rect->y + compare_rect->height < rect->y + rect->height) + dist_to_compare += (rect->y + rect->height) - + (compare_rect->y + compare_rect->height); + + /* If we'd have to move in the wrong direction, disqualify compare_rect */ + if (factor == 0) + { + maximal_overlap_amount_for_compare = 0; + dist_to_compare = G_MAXINT; + } + + /* See if this is the best rect so far */ + if ((maximal_overlap_amount_for_compare > best_overlap) || + (maximal_overlap_amount_for_compare == best_overlap && + dist_to_compare < shortest_distance)) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + shortest_distance = dist_to_compare; + } + + temp = temp->next; + } + + /* Shove rect appropriately */ + if (best_rect == NULL) + meta_warning ("No rect to shove into found!\n"); + else + { + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + { + /* Shove to the right, if needed */ + if (best_rect->x > rect->x) + rect->x = best_rect->x; + + /* Shove to the left, if needed */ + if (best_rect->x + best_rect->width < rect->x + rect->width) + rect->x = (best_rect->x + best_rect->width) - rect->width; + } + + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_Y)) + { + /* Shove down, if needed */ + if (best_rect->y > rect->y) + rect->y = best_rect->y; + + /* Shove up, if needed */ + if (best_rect->y + best_rect->height < rect->y + rect->height) + rect->y = (best_rect->y + best_rect->height) - rect->height; + } + } +} + +void +meta_rectangle_find_linepoint_closest_to_point (double x1, + double y1, + double x2, + double y2, + double px, + double py, + double *valx, + double *valy) +{ + /* I'll use the shorthand rx, ry for the return values, valx & valy. + * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2). + * For that to happen, we first need the slope of the line from (x1,y1) + * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.: + * (ry-y1) (y2-y1) + * ------- = ------- + * (rx-x1) (x2-x1) + * If x1==x2, though, this gives divide by zero errors, so we want to + * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1): + * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) + * This is a valid requirement even when x1==x2 (when x1==x2, this latter + * equation will basically just mean that rx must be equal to both x1 and + * x2) + * + * The other requirement that we have is that the line from (rx,ry) to + * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2). So + * we just need to get a vector in the direction of each line, take the + * dot product of the two, and ensure that the result is 0: + * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. + * + * This gives us two equations and two unknowns: + * + * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) + * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. + * + * This particular pair of equations is always solvable so long as + * (x1,y1) and (x2,y2) are not the same point (and note that anyone who + * calls this function that way is braindead because it means that they + * really didn't specify a line after all). However, the caller should + * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like + * 10^{-8} apart in each coordinate), otherwise roundoff error could + * cause issues. Solving these equations by hand (or using Maple(TM) or + * Mathematica(TM) or whatever) results in slightly messy expressions, + * but that's all the below few lines do. + */ + + double diffx, diffy, den; + diffx = x2 - x1; + diffy = y2 - y1; + den = diffx * diffx + diffy * diffy; + + *valx = (py * diffx * diffy + px * diffx * diffx + + y2 * x1 * diffy - y1 * x2 * diffy) / den; + *valy = (px * diffx * diffy + py * diffy * diffy + + x2 * y1 * diffx - x1 * y2 * diffx) / den; +} + +/***************************************************************************/ +/* */ +/* Switching gears to code for edges instead of just rectangles */ +/* */ +/***************************************************************************/ + +static GList* +get_rect_minus_overlap (const GList *rect_in_list, + MetaRectangle *overlap) +{ + MetaRectangle *temp; + MetaRectangle *rect = rect_in_list->data; + GList *ret = NULL; + + if (BOX_LEFT (*rect) < BOX_LEFT (*overlap)) + { + temp = g_new (MetaRectangle, 1); + *temp = *rect; + temp->width = BOX_LEFT (*overlap) - BOX_LEFT (*rect); + ret = g_list_prepend (ret, temp); + } + if (BOX_RIGHT (*rect) > BOX_RIGHT (*overlap)) + { + temp = g_new (MetaRectangle, 1); + *temp = *rect; + temp->x = BOX_RIGHT (*overlap); + temp->width = BOX_RIGHT (*rect) - BOX_RIGHT (*overlap); + ret = g_list_prepend (ret, temp); + } + if (BOX_TOP (*rect) < BOX_TOP (*overlap)) + { + temp = g_new (MetaRectangle, 1); + temp->x = overlap->x; + temp->width = overlap->width; + temp->y = BOX_TOP (*rect); + temp->height = BOX_TOP (*overlap) - BOX_TOP (*rect); + ret = g_list_prepend (ret, temp); + } + if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*overlap)) + { + temp = g_new (MetaRectangle, 1); + temp->x = overlap->x; + temp->width = overlap->width; + temp->y = BOX_BOTTOM (*overlap); + temp->height = BOX_BOTTOM (*rect) - BOX_BOTTOM (*overlap); + ret = g_list_prepend (ret, temp); + } + + return ret; +} + +static GList* +replace_rect_with_list (GList *old_element, + GList *new_list) +{ + GList *ret; + g_assert (old_element != NULL); + + if (!new_list) + { + /* If there is no new list, just remove the old_element */ + ret = old_element->next; + g_list_remove_link (old_element, old_element); + } + else + { + /* Fix up the prev and next pointers everywhere */ + ret = new_list; + if (old_element->prev) + { + old_element->prev->next = new_list; + new_list->prev = old_element->prev; + } + if (old_element->next) + { + GList *tmp = g_list_last (new_list); + old_element->next->prev = tmp; + tmp->next = old_element->next; + } + } + + /* Free the old_element and return the appropriate "next" point */ + g_free (old_element->data); + g_list_free_1 (old_element); + return ret; +} + +/* Make a copy of the strut list, make sure that copy only contains parts + * of the old_struts that intersect with the rection rect, and then do some + * magic to make all the new struts disjoint (okay, we we break up struts + * that aren't disjoint in a way that the overlapping part is only included + * once, so it's not really magic...). + */ +static GList* +get_disjoint_strut_list_in_region (const GSList *old_struts, + const MetaRectangle *region) +{ + GList *struts; + GList *tmp; + + /* First, copy the list */ + struts = NULL; + while (old_struts) + { + MetaRectangle *cur = old_struts->data; + MetaRectangle *copy = g_new (MetaRectangle, 1); + *copy = *cur; + if (meta_rectangle_intersect (copy, region, copy)) + struts = g_list_prepend (struts, copy); + else + g_free (copy); + + old_struts = old_struts->next; + } + + /* Now, loop over the list and check for intersections, fixing things up + * where they do intersect. + */ + tmp = struts; + while (tmp) + { + GList *compare; + + MetaRectangle *cur = tmp->data; + + compare = tmp->next; + while (compare) + { + MetaRectangle *comp = compare->data; + MetaRectangle overlap; + + if (meta_rectangle_intersect (cur, comp, &overlap)) + { + /* Get a list of rectangles for each strut that don't overlap + * the intersection region. + */ + GList *cur_leftover = get_rect_minus_overlap (tmp, &overlap); + GList *comp_leftover = get_rect_minus_overlap (compare, &overlap); + + /* Add the intersection region to cur_leftover */ + MetaRectangle *overlap_allocated = g_new (MetaRectangle, 1); + *overlap_allocated = overlap; + cur_leftover = g_list_prepend (cur_leftover, overlap_allocated); + + /* Fix up tmp, compare, and cur -- maybe struts too */ + if (struts == tmp) + { + struts = replace_rect_with_list (tmp, cur_leftover); + tmp = struts; + } + else + tmp = replace_rect_with_list (tmp, cur_leftover); + compare = replace_rect_with_list (compare, comp_leftover); + + if (compare == NULL) + break; + + cur = tmp->data; + } + + compare = compare->next; + } + + tmp = tmp->next; + } + + return struts; +} + +/* To make things easily testable, provide a nice way of sorting edges */ +gint +meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b) +{ + const MetaEdge *a_edge_rect = (gconstpointer) a; + const MetaEdge *b_edge_rect = (gconstpointer) b; + + int a_compare, b_compare; + + a_compare = a_edge_rect->side_type; + b_compare = b_edge_rect->side_type; + + if (a_compare == b_compare) + { + if (a_edge_rect->side_type == META_DIRECTION_LEFT || + a_edge_rect->side_type == META_DIRECTION_RIGHT) + { + a_compare = a_edge_rect->rect.x; + b_compare = b_edge_rect->rect.x; + if (a_compare == b_compare) + { + a_compare = a_edge_rect->rect.y; + b_compare = b_edge_rect->rect.y; + } + } + else if (a_edge_rect->side_type == META_DIRECTION_TOP || + a_edge_rect->side_type == META_DIRECTION_BOTTOM) + { + a_compare = a_edge_rect->rect.y; + b_compare = b_edge_rect->rect.y; + if (a_compare == b_compare) + { + a_compare = a_edge_rect->rect.x; + b_compare = b_edge_rect->rect.x; + } + } + else + g_assert ("Some idiot wanted to sort sides of different types.\n"); + } + + return a_compare - b_compare; /* positive value denotes a > b ... */ +} + +/* Determine whether two given edges overlap */ +static gboolean +edges_overlap (const MetaEdge *edge1, + const MetaEdge *edge2) +{ + if (edge1->rect.width == 0 && edge2->rect.width == 0) + { + return meta_rectangle_vert_overlap (&edge1->rect, &edge2->rect) && + edge1->rect.x == edge2->rect.x; + } + else if (edge1->rect.height == 0 && edge2->rect.height == 0) + { + return meta_rectangle_horiz_overlap (&edge1->rect, &edge2->rect) && + edge1->rect.y == edge2->rect.y; + } + else + { + return FALSE; + } +} + +static gboolean +rectangle_and_edge_intersection (const MetaRectangle *rect, + const MetaEdge *edge, + MetaEdge *overlap, + int *handle_type) +{ + const MetaRectangle *rect2 = &edge->rect; + MetaRectangle *result = &overlap->rect; + gboolean intersect = TRUE; + + /* We don't know how to set these, so set them to invalid values */ + overlap->edge_type = -1; + overlap->side_type = -1; + + /* Figure out what the intersection is */ + result->x = MAX (rect->x, rect2->x); + result->y = MAX (rect->y, rect2->y); + result->width = MIN (BOX_RIGHT (*rect), BOX_RIGHT (*rect2)) - result->x; + result->height = MIN (BOX_BOTTOM (*rect), BOX_BOTTOM (*rect2)) - result->y; + + /* Find out if the intersection is empty; have to do it this way since + * edges have a thickness of 0 + */ + if ((result->width < 0 || result->height < 0) || + (result->width == 0 && result->height == 0)) + { + result->width = 0; + result->height = 0; + intersect = FALSE; + } + else + { + /* Need to figure out the handle_type, a somewhat weird quantity: + * 0 - overlap is in middle of rect + * -1 - overlap is at the side of rect, and is on the opposite side + * of rect than the edge->side_type side + * 1 - overlap is at the side of rect, and the side of rect it is + * on is the edge->side_type side + */ + switch (edge->side_type) + { + case META_DIRECTION_LEFT: + if (result->x == rect->x) + *handle_type = 1; + else if (result->x == BOX_RIGHT (*rect)) + *handle_type = -1; + else + *handle_type = 0; + break; + case META_DIRECTION_RIGHT: + if (result->x == rect->x) + *handle_type = -1; + else if (result->x == BOX_RIGHT (*rect)) + *handle_type = 1; + else + *handle_type = 0; + break; + case META_DIRECTION_TOP: + if (result->y == rect->y) + *handle_type = 1; + else if (result->y == BOX_BOTTOM (*rect)) + *handle_type = -1; + else + *handle_type = 0; + break; + case META_DIRECTION_BOTTOM: + if (result->y == rect->y) + *handle_type = -1; + else if (result->y == BOX_BOTTOM (*rect)) + *handle_type = 1; + else + *handle_type = 0; + break; + } + } + return intersect; +} + +/* Add all edges of the given rect to cur_edges and return the result. If + * rect_is_internal is false, the side types are switched (LEFT<->RIGHT and + * TOP<->BOTTOM). + */ +static GList* +add_edges (GList *cur_edges, + const MetaRectangle *rect, + gboolean rect_is_internal) +{ + MetaEdge *temp_edge; + int i; + + for (i=0; i<4; i++) + { + temp_edge = g_new (MetaEdge, 1); + temp_edge->rect = *rect; + switch (i) + { + case 0: + temp_edge->side_type = + rect_is_internal ? META_DIRECTION_LEFT : META_DIRECTION_RIGHT; + temp_edge->rect.width = 0; + break; + case 1: + temp_edge->side_type = + rect_is_internal ? META_DIRECTION_RIGHT : META_DIRECTION_LEFT; + temp_edge->rect.x += temp_edge->rect.width; + temp_edge->rect.width = 0; + break; + case 2: + temp_edge->side_type = + rect_is_internal ? META_DIRECTION_TOP : META_DIRECTION_BOTTOM; + temp_edge->rect.height = 0; + break; + case 3: + temp_edge->side_type = + rect_is_internal ? META_DIRECTION_BOTTOM : META_DIRECTION_TOP; + temp_edge->rect.y += temp_edge->rect.height; + temp_edge->rect.height = 0; + break; + } + temp_edge->edge_type = META_EDGE_SCREEN; + cur_edges = g_list_prepend (cur_edges, temp_edge); + } + + return cur_edges; +} + +/* Remove any part of old_edge that intersects remove and add any resulting + * edges to cur_list. Return cur_list when finished. + */ +static GList* +split_edge (GList *cur_list, + const MetaEdge *old_edge, + const MetaEdge *remove) +{ + MetaEdge *temp_edge; + switch (old_edge->side_type) + { + case META_DIRECTION_LEFT: + case META_DIRECTION_RIGHT: + g_assert (meta_rectangle_vert_overlap (&old_edge->rect, &remove->rect)); + if (BOX_TOP (old_edge->rect) < BOX_TOP (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.height = BOX_TOP (remove->rect) + - BOX_TOP (old_edge->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + if (BOX_BOTTOM (old_edge->rect) > BOX_BOTTOM (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.y = BOX_BOTTOM (remove->rect); + temp_edge->rect.height = BOX_BOTTOM (old_edge->rect) + - BOX_BOTTOM (remove->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + break; + case META_DIRECTION_TOP: + case META_DIRECTION_BOTTOM: + g_assert (meta_rectangle_horiz_overlap (&old_edge->rect, &remove->rect)); + if (BOX_LEFT (old_edge->rect) < BOX_LEFT (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.width = BOX_LEFT (remove->rect) + - BOX_LEFT (old_edge->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + if (BOX_RIGHT (old_edge->rect) > BOX_RIGHT (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.x = BOX_RIGHT (remove->rect); + temp_edge->rect.width = BOX_RIGHT (old_edge->rect) + - BOX_RIGHT (remove->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + break; + } + + return cur_list; +} + +/* Split up edge and remove preliminary edges from strut_edges depending on + * if and how strut and edge intersect. + */ +static void +fix_up_edges (MetaRectangle *strut, MetaEdge *edge, + GList **strut_edges, GList **edge_splits, + gboolean *edge_needs_removal) +{ + MetaEdge overlap; + int handle_type; + + if (!rectangle_and_edge_intersection (strut, edge, &overlap, &handle_type)) + return; + + if (handle_type == 0 || handle_type == 1) + { + /* Put the result of removing overlap from edge into edge_splits */ + *edge_splits = split_edge (*edge_splits, edge, &overlap); + *edge_needs_removal = TRUE; + } + + if (handle_type == -1 || handle_type == 1) + { + /* Remove the overlap from strut_edges */ + /* First, loop over the edges of the strut */ + GList *tmp = *strut_edges; + while (tmp) + { + MetaEdge *cur = tmp->data; + /* If this is the edge that overlaps, then we need to split it */ + if (edges_overlap (cur, &overlap)) + { + /* Split this edge into some new ones */ + *strut_edges = split_edge (*strut_edges, cur, &overlap); + + /* Delete the old one */ + GList *delete_me = tmp; + tmp = tmp->next; + g_free (cur); + *strut_edges = g_list_delete_link (*strut_edges, delete_me); + } + else + tmp = tmp->next; + } + } +} + +/* This function removes intersections of edges with the rectangles from the + * list of edges. + */ +GList* +meta_rectangle_remove_intersections_with_boxes_from_edges ( + GList *edges, + const GSList *rectangles) +{ + const GSList *rect_iter; + const int opposing = 1; + + /* Now remove all intersections of rectangles with the edge list */ + rect_iter = rectangles; + while (rect_iter) + { + MetaRectangle *rect = rect_iter->data; + GList *edge_iter = edges; + while (edge_iter) + { + MetaEdge *edge = edge_iter->data; + MetaEdge overlap; + int handle; + gboolean edge_iter_advanced = FALSE; + + /* If this edge overlaps with this rect... */ + if (rectangle_and_edge_intersection (rect, edge, &overlap, &handle)) + { + + /* "Intersections" where the edges touch but are opposite + * sides (e.g. a left edge against the right edge) should not + * be split. Note that the comments in + * rectangle_and_edge_intersection() say that opposing edges + * occur when handle is -1, BUT you need to remember that we + * treat the left side of a window as a right edge because + * it's what the right side of the window being moved should + * be-resisted-by/snap-to. So opposing is really 1. Anyway, + * we just keep track of it in the opposing constant set up + * above and if handle isn't equal to that, then we know the + * edge should be split. + */ + if (handle != opposing) + { + /* Keep track of this edge so we can delete it below */ + GList *delete_me = edge_iter; + edge_iter = edge_iter->next; + edge_iter_advanced = TRUE; + + /* Split the edge and add the result to beginning of edges */ + edges = split_edge (edges, edge, &overlap); + + /* Now free the edge... */ + g_free (edge); + edges = g_list_delete_link (edges, delete_me); + } + } + + if (!edge_iter_advanced) + edge_iter = edge_iter->next; + } + + rect_iter = rect_iter->next; + } + + return edges; +} + +/* This function is trying to find all the edges of an onscreen region. */ +GList* +meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect, + const GSList *all_struts) +{ + GList *ret; + GList *fixed_struts; + GList *edge_iter; + const GList *strut_iter; + + /* The algorithm is basically as follows: + * Make sure the struts are disjoint + * Initialize the edge_set to the edges of basic_rect + * Foreach strut: + * Put together a preliminary new edge from the edges of the strut + * Foreach edge in edge_set: + * - Split the edge if it is partially contained inside the strut + * - If the edge matches an edge of the strut (i.e. a strut just + * against the edge of the screen or a not-next-to-edge-of-screen + * strut adjacent to another), then both the edge from the + * edge_set and the preliminary edge for the strut will need to + * be split + * Add any remaining "preliminary" strut edges to the edge_set + */ + + /* Make sure the struts are disjoint */ + fixed_struts = get_disjoint_strut_list_in_region (all_struts, basic_rect); + + /* Start off the list with the edges of basic_rect */ + ret = add_edges (NULL, basic_rect, TRUE); + + strut_iter = fixed_struts; + while (strut_iter) + { + MetaRectangle *strut = (MetaRectangle*) strut_iter->data; + + /* Get the new possible edges we may need to add from the strut */ + GList *new_strut_edges = add_edges (NULL, strut, FALSE); + + edge_iter = ret; + while (edge_iter) + { + MetaEdge *cur_edge = edge_iter->data; + GList *splits_of_cur_edge = NULL; + gboolean edge_needs_removal = FALSE; + + fix_up_edges (strut, cur_edge, + &new_strut_edges, &splits_of_cur_edge, + &edge_needs_removal); + + if (edge_needs_removal) + { + /* Delete the old edge */ + GList *delete_me = edge_iter; + edge_iter = edge_iter->next; + g_free (cur_edge); + ret = g_list_delete_link (ret, delete_me); + + /* Add the new split parts of the edge */ + ret = g_list_concat (splits_of_cur_edge, ret); + } + else + { + edge_iter = edge_iter->next; + } + + /* edge_iter was already advanced above */ + } + + ret = g_list_concat (new_strut_edges, ret); + strut_iter = strut_iter->next; + } + + /* Sort the list */ + ret = g_list_sort (ret, meta_rectangle_edge_cmp); + + /* Free the fixed struts list */ + meta_rectangle_free_list_and_elements (fixed_struts); + + return ret; +} + +GList* +meta_rectangle_find_nonintersected_xinerama_edges ( + const GList *xinerama_rects, + const GSList *all_struts) +{ + /* This function cannot easily be merged with + * meta_rectangle_find_onscreen_edges() because real screen edges + * and strut edges both are of the type "there ain't anything + * immediately on the other side"; xinerama edges are different. + */ + GList *ret; + const GList *cur; + + /* Initialize the return list to be empty */ + ret = NULL; + + /* start of ret with all the edges of xineramas that are adjacent to + * another xinerama. + */ + cur = xinerama_rects; + while (cur) + { + MetaRectangle *cur_rect = cur->data; + const GList *compare = xinerama_rects; + while (compare) + { + MetaRectangle *compare_rect = compare->data; + + /* Check if cur might be horizontally adjacent to compare */ + if (meta_rectangle_vert_overlap(cur_rect, compare_rect)) + { + MetaDirection side_type; + int y = MAX (cur_rect->y, compare_rect->y); + int height = MIN (BOX_BOTTOM (*cur_rect) - y, + BOX_BOTTOM (*compare_rect) - y); + int width = 0; + int x; + + if (BOX_LEFT (*cur_rect) == BOX_RIGHT (*compare_rect)) + { + /* compare_rect is to the left of cur_rect */ + x = BOX_LEFT (*cur_rect); + side_type = META_DIRECTION_LEFT; + } + else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect)) + { + /* compare_rect is to the right of cur_rect */ + x = BOX_RIGHT (*cur_rect); + side_type = META_DIRECTION_RIGHT; + } + else + /* These rectangles aren't adjacent after all */ + x = INT_MIN; + + /* If the rectangles really are adjacent */ + if (x != INT_MIN) + { + /* We need a left edge for the xinerama on the right, and + * a right edge for the xinerama on the left. Just fill + * up the edges and stick 'em on the list. + */ + MetaEdge *new_edge = g_new (MetaEdge, 1); + + new_edge->rect = meta_rect (x, y, width, height); + new_edge->side_type = side_type; + new_edge->edge_type = META_EDGE_XINERAMA; + + ret = g_list_prepend (ret, new_edge); + } + } + + /* Check if cur might be vertically adjacent to compare */ + if (meta_rectangle_horiz_overlap(cur_rect, compare_rect)) + { + MetaDirection side_type; + int x = MAX (cur_rect->x, compare_rect->x); + int width = MIN (BOX_RIGHT (*cur_rect) - x, + BOX_RIGHT (*compare_rect) - x); + int height = 0; + int y; + + if (BOX_TOP (*cur_rect) == BOX_BOTTOM (*compare_rect)) + { + /* compare_rect is to the top of cur_rect */ + y = BOX_TOP (*cur_rect); + side_type = META_DIRECTION_TOP; + } + else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect)) + { + /* compare_rect is to the bottom of cur_rect */ + y = BOX_BOTTOM (*cur_rect); + side_type = META_DIRECTION_BOTTOM; + } + else + /* These rectangles aren't adjacent after all */ + y = INT_MIN; + + /* If the rectangles really are adjacent */ + if (y != INT_MIN) + { + /* We need a top edge for the xinerama on the bottom, and + * a bottom edge for the xinerama on the top. Just fill + * up the edges and stick 'em on the list. + */ + MetaEdge *new_edge = g_new (MetaEdge, 1); + + new_edge->rect = meta_rect (x, y, width, height); + new_edge->side_type = side_type; + new_edge->edge_type = META_EDGE_XINERAMA; + + ret = g_list_prepend (ret, new_edge); + } + } + + compare = compare->next; + } + cur = cur->next; + } + + ret = meta_rectangle_remove_intersections_with_boxes_from_edges (ret, + all_struts); + + /* Sort the list */ + ret = g_list_sort (ret, meta_rectangle_edge_cmp); + + return ret; +} diff --git a/src/boxes.h b/src/boxes.h new file mode 100644 index 000000000..cea7cf32f --- /dev/null +++ b/src/boxes.h @@ -0,0 +1,239 @@ +/* Simple box operations */ + +/* + * Copyright (C) 2005 Elijah Newren + * + * 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 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_BOXES_H +#define META_BOXES_H + +#include +#include "common.h" + +typedef struct _MetaRectangle MetaRectangle; + +struct _MetaRectangle +{ + int x; + int y; + int width; + int height; +}; + +#define BOX_LEFT(box) ((box).x) /* Leftmost pixel of rect */ +#define BOX_RIGHT(box) ((box).x + (box).width) /* One pixel past right */ +#define BOX_TOP(box) ((box).y) /* Topmost pixel of rect */ +#define BOX_BOTTOM(box) ((box).y + (box).height) /* One pixel past bottom */ + +typedef enum +{ + FIXED_DIRECTION_X = 1 << 0, + FIXED_DIRECTION_Y = 1 << 1, +} FixedDirections; + +typedef enum +{ + META_EDGE_WINDOW, + META_EDGE_XINERAMA, + META_EDGE_SCREEN +} MetaEdgeType; + +typedef struct _MetaEdge MetaEdge; +struct _MetaEdge +{ + MetaRectangle rect; /* width or height should be 1 */ + MetaDirection side_type; /* should only have 1 of the 4 directions set */ + MetaEdgeType edge_type; +}; + +/* Output functions -- note that the output buffer had better be big enough: + * rect_to_string: RECT_LENGTH + * region_to_string: (RECT_LENGTH+strlen(separator_string)) * + * g_list_length (region) + * edge_to_string: EDGE_LENGTH + * edge_list_to_...: (EDGE_LENGTH+strlen(separator_string)) * + * g_list_length (edge_list) + */ +#define RECT_LENGTH 27 +#define EDGE_LENGTH 37 +char* meta_rectangle_to_string (const MetaRectangle *rect, + char *output); +char* meta_rectangle_region_to_string (GList *region, + const char *separator_string, + char *output); +char* meta_rectangle_edge_to_string (const MetaEdge *edge, + char *output); +char* meta_rectangle_edge_list_to_string ( + GList *edge_list, + const char *separator_string, + char *output); + +/* Function to make initializing a rect with a single line of code easy */ +MetaRectangle meta_rect (int x, int y, int width, int height); + +/* Basic comparison functions */ +int meta_rectangle_area (const MetaRectangle *rect); +gboolean meta_rectangle_intersect (const MetaRectangle *src1, + const MetaRectangle *src2, + MetaRectangle *dest); +gboolean meta_rectangle_equal (const MetaRectangle *src1, + const MetaRectangle *src2); + +/* overlap is similar to intersect but doesn't provide location of + * intersection information. + */ +gboolean meta_rectangle_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2); + +/* vert_overlap means ignore the horizontal location and ask if the + * vertical parts overlap. An alternate way to think of it is "Does there + * exist a way to shift either rect horizontally so that the two rects + * overlap?" horiz_overlap is similar. + */ +gboolean meta_rectangle_vert_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2); +gboolean meta_rectangle_horiz_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2); + +/* could_fit_rect determines whether "outer_rect" is big enough to contain + * inner_rect. contains_rect checks whether it actually contains it. + */ +gboolean meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect); +gboolean meta_rectangle_contains_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect); + +/* Resize old_rect to the given new_width and new_height, but store the + * result in rect. NOTE THAT THIS IS RESIZE ONLY SO IT CANNOT BE USED FOR + * A MOVERESIZE OPERATION (that simplies the routine a little bit as it + * means there's no difference between NorthWestGravity and StaticGravity. + * Also, I lied a little bit--technically, you could use it in a MoveResize + * operation if you muck with old_rect just right). + */ +void meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect, + MetaRectangle *rect, + int gravity, + int new_width, + int new_height); + +/* find a list of rectangles with the property that a window is contained + * in the given region if and only if it is contained in one of the + * rectangles in the list. + * + * In this case, the region is given by taking basic_rect, removing from + * it the intersections with all the rectangles in the all_struts list, + * then expanding all the rectangles in the resulting list by the given + * amounts on each side. + * + * See boxes.c for more details. + */ +GList* meta_rectangle_get_minimal_spanning_set_for_region ( + const MetaRectangle *basic_rect, + const GSList *all_struts); + +GList* meta_rectangle_expand_region (GList *region, + const int left_expand, + const int right_expand, + const int top_expand, + const int bottom_expand); + +/* Free the list created by + * meta_rectangle_get_minimal_spanning_set_for_region() + * or + * meta_rectangle_find_onscreen_edges () + * or + * meta_rectangle_find_nonintersected_xinerama_edges() + */ +void meta_rectangle_free_list_and_elements (GList *filled_list); + +/* could_fit_in_region determines whether one of the spanning_rects is + * big enough to contain rect. contained_in_region checks whether one + * actually contains it. + */ +gboolean meta_rectangle_could_fit_in_region ( + const GList *spanning_rects, + const MetaRectangle *rect); +gboolean meta_rectangle_contained_in_region ( + const GList *spanning_rects, + const MetaRectangle *rect); + +/* Make the rectangle small enough to fit into one of the spanning_rects, + * but make it no smaller than min_size. + */ +void meta_rectangle_clamp_to_fit_into_region ( + const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect, + const MetaRectangle *min_size); + +/* Clip the rectangle so that it fits into one of the spanning_rects, assuming + * it overlaps with at least one of them + */ +void meta_rectangle_clip_to_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect); + +/* Shove the rectangle into one of the spanning_rects, assuming it fits in + * one of them. + */ +void meta_rectangle_shove_into_region( + const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect); + +/* Finds the point on the line connecting (x1,y1) to (x2,y2) which is closest + * to (px, py). Useful for finding an optimal rectangle size when given a + * range between two sizes that are all candidates. + */ +void meta_rectangle_find_linepoint_closest_to_point (double x1, double y1, + double x2, double y2, + double px, double py, + double *valx, double *valy); + +/***************************************************************************/ +/* */ +/* Switching gears to code for edges instead of just rectangles */ +/* */ +/***************************************************************************/ + +/* Compare two edges, so that sorting functions can put a list of edges in + * canonical order. + */ +gint meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b); + +/* Removes an parts of edges in the given list that intersect any box in the + * given rectangle list. Returns the result. + */ +GList* meta_rectangle_remove_intersections_with_boxes_from_edges ( + GList *edges, + const GSList *rectangles); + +/* Finds all the edges of an onscreen region, returning a GList* of + * MetaEdgeRect's. + */ +GList* meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect, + const GSList *all_struts); + +/* Finds edges between adjacent xineramas which are not covered by the given + * struts. + */ +GList* meta_rectangle_find_nonintersected_xinerama_edges ( + const GList *xinerama_rects, + const GSList *all_struts); + +#endif /* META_BOXES_H */ diff --git a/src/common.h b/src/common.h index c7c8108e0..391601cdd 100644 --- a/src/common.h +++ b/src/common.h @@ -2,6 +2,7 @@ /* * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -64,7 +65,8 @@ typedef enum META_MENU_OP_MOVE_LEFT = 1 << 13, META_MENU_OP_MOVE_RIGHT = 1 << 14, META_MENU_OP_MOVE_UP = 1 << 15, - META_MENU_OP_MOVE_DOWN = 1 << 16 + META_MENU_OP_MOVE_DOWN = 1 << 16, + META_MENU_OP_RECOVER = 1 << 17 } MetaMenuOp; typedef struct _MetaWindowMenu MetaWindowMenu; @@ -184,6 +186,22 @@ typedef enum META_VIRTUAL_MOD5_MASK = 1 << 14 } MetaVirtualModifier; +/* Relative directions or sides seem to come up all over the place... */ +/* FIXME: Replace + * place.[ch]:MetaWindowEdgePosition, + * screen.[ch]:MetaScreenDirection, + * workspace.[ch]:MetaMotionDirection, + * with the use of MetaDirection. + */ +typedef enum +{ + META_DIRECTION_LEFT = 1 << 0, + META_DIRECTION_RIGHT = 1 << 1, + META_DIRECTION_TOP = 1 << 2, + META_DIRECTION_BOTTOM = 1 << 3, + META_DIRECTION_UP = 1 << 2, /* Alternate name for TOP */ + META_DIRECTION_DOWN = 1 << 3 /* Alternate name for BOTTOM */ +} MetaDirection; /* Function a window button can have. Note, you can't add stuff here * without extending the theme format to draw a new function and @@ -226,7 +244,3 @@ struct _MetaButtonLayout (ycoord) < ((rect).y + (rect).height)) #endif - - - - diff --git a/src/constraints.c b/src/constraints.c index ca36522d9..4e8802808 100644 --- a/src/constraints.c +++ b/src/constraints.c @@ -3,6 +3,7 @@ /* * Copyright (C) 2002, 2003 Red Hat, Inc. * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -22,1260 +23,397 @@ #include #include "constraints.h" -#include "window.h" #include "workspace.h" #include "place.h" -/* The way this code works was suggested by Owen Taylor. - * - * For any move_resize, we determine which variables are "free - * variables" and apply constraints in terms of those. During the move - * resize, we only want to modify those variables; otherwise the - * constraint process can have peculiar side effects when the size and - * position constraints interact. For example, resizing a window from - * the top might go wrong when position constraints apply to the top - * edge, and result in the bottom edge moving downward while the top - * stays fixed. - * - * After selecting the variables we plan to vary, we define - * each constraint on the window in terms of those variables. - * - * Trivial example, say we are resizing vertically from the top of the - * window. In that case we are applying the user's mouse motion delta - * to an original size and position, note that dy is positive to - * resize downward: - * - * new_height = orig_height - dy; - * new_y = orig_y + dy; - * - * A constraint that the position can't go above the top panel would - * look like this: - * - * new_y >= screen_top_bound - * - * Substitute: - * - * orig_y + dy >= screen_top_bound - * - * Find the "boundary point" by changing to an equality: - * - * orig_y + dy = screen_top_bound - * - * Solve: - * - * dy = screen_top_bound - orig_y - * - * This dy is now the _maximum_ dy and you constrain dy with that - * value, applying it to both the move and the resize: - * - * new_height = orig_height - dy; - * new_y = orig_y + dy; - * - * This way the constraint is applied simultaneously to size/position, - * so you aren't running the risk of constraining one but still - * changing the other. i.e. we've converted an operation that may - * modify both the Y position and the height of the window into an - * operation that modifies a single variable, dy. That variable is - * then constrained, rather than the constraining the Y pos and height - * separately. This is a rather complicated fix for an obscure bug - * that happened when resizing a window and encountering a constraint - * such as the top edge of the screen. - * - */ +#include +#include +#if 0 + // This is the short and sweet version of how to hack on this file; see + // doc/how-constraints-works.txt for the gory details. The basics of + // understanding this file can be shown by the steps needed to add a new + // constraint, which are: + // 1) Add a new entry in the ConstraintPriority enum; higher values + // have higher priority + // 2) Write a new function following the format of the example below, + // "constrain_whatever". + // 3) Add your function to the all_constraints and all_constraint_names + // arrays (the latter of which is for debugging purposes) + // + // An example constraint function, constrain_whatever: + // + // /* constrain_whatever does the following: + // * Quits (returning true) if priority is higher than PRIORITY_WHATEVER + // * If check_only is TRUE + // * Returns whether the constraint is satisfied or not + // * otherwise + // * Enforces the constraint + // * Note that the value of PRIORITY_WHATEVER is centralized with the + // * priorities of other constraints in the definition of ConstrainPriority + // * for easier maintenance and shuffling of priorities. + // */ + // static gboolean + // constrain_whatever (MetaWindow *window, + // ConstraintInfo *info, + // ConstraintPriority priority, + // gboolean check_only) + // { + // if (priority > PRIORITY_WHATEVER) + // return TRUE; + // + // /* Determine whether constraint applies; note that if the constraint + // * cannot possibly be satisfied, constraint_applies should be set to + // * false. If we don't do this, all constraints with a lesser priority + // * will be dropped along with this one, and we'd rather apply as many as + // * possible. + // */ + // if (!constraint_applies) + // return TRUE; + // + // /* Determine whether constraint is already satisfied; if we're only + // * checking the status of whether the constraint is satisfied, we end + // * here. + // */ + // if (check_only || constraint_already_satisfied) + // return constraint_already_satisfied; + // + // /* Enforce constraints */ + // return TRUE; /* Note that we exited early if check_only is FALSE; also, + // * we know we can return TRUE here because we exited early + // * if the constraint could not be satisfied; not that the + // * return value is heeded in this case... + // */ + // } +#endif -/* To adjust for window gravity, such as a client moving itself to the - * southeast corner, we want to compute the gravity reference point - * - (screen_width,screen_height) in the SE corner case - using the - * size the client had in its configure request. But then we want - * to compute the actual position we intend to land on using - * the real constrained dimensions of the window. - * - * So for a window being placed in the SE corner and simultaneously - * resized, we get the gravity reference point, then compute where the - * window should go to maintain that ref. point at its current size - * instead of at the requested size, and conceptually move the window - * to the requested ref. point but at its current size, without - * applying any constraints. Then we constrain it with the top and - * left edges as the edges that vary, with a dx/dy that are the delta - * from the current size to the requested size. - * - * This method applies to any ConfigureRequest that does a simultaneous - * move/resize. - * - * We use the same method to e.g. maximize a window; if the window is - * maximized, we want to MOVE_VERTICAL/MOVE_HORIZONTAL to the top - * center of the screen, then RESIZE_BOTTOM and - * RESIZE_HORIZONTAL_CENTER. i.e. essentially NorthGravity. - * - */ +typedef enum +{ + PRIORITY_MINIMUM = 0, // Dummy value used for loop start = min(all priorities) + PRIORITY_ASPECT_RATIO = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1, + PRIORITY_SIZE_HINTS_INCREMENTS = 1, + PRIORITY_MAXIMIZATION = 2, + PRIORITY_FULLSCREEN = 2, + PRIORITY_SIZE_HINTS_LIMITS = 3, + PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4, + PRIORITY_MAXIMUM = 4 // Dummy value used for loop end = max(all priorities) +} ConstraintPriority; -#define FLOOR(value, base) ( ((int) ((value) / (base))) * (base) ) +typedef enum +{ + ACTION_MOVE, + ACTION_RESIZE, + ACTION_MOVE_AND_RESIZE +} ActionType; typedef struct { - MetaWindow *window; - MetaFrameGeometry fgeom; - const MetaXineramaScreenInfo *xinerama; - MetaRectangle work_area_xinerama; - MetaRectangle work_area_screen; - int nw_x, nw_y, se_x, se_y; /* these are whole-screen not xinerama */ + MetaRectangle orig; + MetaRectangle current; + MetaFrameGeometry *fgeom; + ActionType action_type; + gboolean is_user_action; + + /* I know that these two things probably look similar at first, but they + * have much different uses. See doc/how-constraints-works.txt for for + * explanation of the differences and similarity between resize_gravity + * and fixed_directions + */ + int resize_gravity; + FixedDirections fixed_directions; + + /* work_area_xinerama - current xinerama region minus struts + * entire_xinerama - current xienrama, including strut regions + */ + MetaRectangle work_area_xinerama; + MetaRectangle entire_xinerama; + + /* Spanning rectangles for the non-covered (by struts) region of the + * screen and also for just the current xinerama + */ + GList *usable_screen_region; + GList *usable_xinerama_region; } ConstraintInfo; -/* (FIXME instead of TITLEBAR_LENGTH_ONSCREEN, get the actual - * size of the menu control?). - */ +static gboolean constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); -#define TITLEBAR_LENGTH_ONSCREEN 75 +static void setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new); +static void place_window_if_needed (MetaWindow *window, + ConstraintInfo *info); +static void update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info); +static void extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static void unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static inline void get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size); -typedef gboolean (* MetaConstraintAppliesFunc) (MetaWindow *window); +typedef gboolean (* ConstraintFunc) (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); -/* There's a function for each case with a different "free variable" */ -typedef void (* MetaConstrainTopFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta); -typedef void (* MetaConstrainBottomFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta); -typedef void (* MetaConstrainVCenterFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta); -typedef void (* MetaConstrainLeftFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta); -typedef void (* MetaConstrainRightFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta); -typedef void (* MetaConstrainHCenterFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta); -typedef void (* MetaConstrainMoveFunc) (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta, - int *y_delta); - -typedef struct -{ - const char *name; - MetaConstraintAppliesFunc applies_func; - MetaConstrainTopFunc top_func; - MetaConstrainBottomFunc bottom_func; - MetaConstrainVCenterFunc vcenter_func; - MetaConstrainLeftFunc left_func; - MetaConstrainRightFunc right_func; - MetaConstrainHCenterFunc hcenter_func; - MetaConstrainMoveFunc move_func; +typedef struct { + ConstraintFunc func; + const char* name; } Constraint; -/* "Is the desktop window" constraint: - * - * new_x = 0; - * new_y = 0; - * new_w = orig_width; - * new_h = orig_height; - * - * Note that if we are applying a resize constraint, - * e.g. constraint_desktop_top_func, this is kind of broken since we - * end up resizing the window in order to get its position right. But - * that case shouldn't happen in practice. - */ -static gboolean -constraint_desktop_applies_func (MetaWindow *window) -{ - return window->type == META_WINDOW_DESKTOP; -} - -static void -constraint_desktop_top_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - *y_delta = 0 - orig->y; -} - -static void -constraint_desktop_bottom_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - /* nothing */ -} - -static void -constraint_desktop_vcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - *y_delta = 0 - orig->y; -} - -static void -constraint_desktop_left_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - *x_delta = 0 - orig->x; -} - -static void -constraint_desktop_right_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - /* nothing */ -} - -static void -constraint_desktop_hcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - *x_delta = 0 - orig->x; -} - -static void -constraint_desktop_move_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta, - int *y_delta) -{ - *x_delta = 0 - orig->x; - *y_delta = 0 - orig->y; -} - -static const Constraint constraint_desktop = { - "Desktop", - constraint_desktop_applies_func, - constraint_desktop_top_func, - constraint_desktop_bottom_func, - constraint_desktop_vcenter_func, - constraint_desktop_left_func, - constraint_desktop_right_func, - constraint_desktop_hcenter_func, - constraint_desktop_move_func +static const Constraint all_constraints[] = { + {constrain_maximization, "constrain_maximization"}, + {constrain_fullscreen, "constrain_fullscreen"}, + {constrain_size_increments, "constrain_size_increments"}, + {constrain_size_limits, "constrain_size_limits"}, + {constrain_aspect_ratio, "constrain_aspect_ratio"}, + {constrain_to_single_xinerama, "constrain_to_single_xinerama"}, + {constrain_fully_onscreen, "constrain_fully_onscreen"}, + {constrain_partially_onscreen, "constrain_partially_onscreen"}, + {NULL, NULL} }; -/* Titlebar is onscreen constraint: - * - * Constants: - * titlebar_width_onscreen = amount of titlebar width that has to be onscreen - * nw_x, nw_y = left/top edges that titlebar can't go outside - * se_x, se_y = right/bottom edges - * - * NW limit has priority over SE, since titlebar is on NW - * - * Left resize - * === - * - * new_width = orig_width - dx - * new_x = orig_x + dx - * - * Amount of window+frame that doesn't fit in the work area: - * - * offscreen_width = left_width + new_width + right_width - (se_x - nw_x) - * - * If we keep the old metacity rule where a window can be offscreen by - * offscreen_width, then the math works out that left/top resizes are not - * constrained. If we instead have a rule where the window can never be offscreen, - * you get the following: - * - * new_x >= nw_x + left_width + titlebar_width_offscreen - * orig_x + dx >= nw_x + left_width + titlebar_width_onscreen - * dx >= nw_x + left_width + titlebar_width_onscreen - orig_x - * - * i.e. the minimum dx is: nw_x + left_width + titlebar_width_onscreen - orig_x - * - * We could have a more complicated rule that constrains only if the current - * offscreen width is positive, thus allowing something more like the old - * behavior, but not doing that for now. - * - * Top resize works the same as left resize. Right/bottom resize don't have a limit - * because the constraint is designed to keep the top left corner of the - * window or its titlebar on the screen, and right/bottom resize will never move that - * area. Center resize is almost like left/top but dx has the opposite sign - * and new_width = orig_width + 2dx. - * - * For right/bottom we can try to handle windows that aren't in a valid - * location to begin with: - * - * new_x <= se_x - titlebar_width_onscreen - * dx <= se_x - titlebar_width_onscreen - orig_x - * - * but in principle this constraint is never triggered. - * - * Vertical move - * === - * - * new_height = orig_height - * new_y = orig_y + dy - * - * new_y >= nw_y + top_height - * - * Min negative dy (nw_y + top_height - orig_y) just as with top resize. - * Max positive dy has to be computed from se_y and given less priority than the - * min negative: - * - * new_y < se_y - * orig_y + dy = se_y - * so max dy is (se_y - orig_y) - * - * Horizontal move is equivalent to vertical. - * - */ - static gboolean -constraint_onscreen_applies_func (MetaWindow *window) +do_all_constraints (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) { - return - !window->fullscreen && - window->type != META_WINDOW_DESKTOP && - window->type != META_WINDOW_DOCK; -} + const Constraint *constraint; + gboolean satisfied; -static void -get_outermost_onscreen_positions (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int delta_x, - int delta_y, - int *leftmost_x_p, - int *rightmost_x_p, - int *topmost_y_p, - int *bottommost_y_p) -{ - GList *workspaces; - GList *tmp; - GSList *stmp; - MetaRectangle current; - int bottommost_y; - - /* to handle struts, we get the list of workspaces for the window - * and traverse all the struts in each of the cached strut lists for - * the workspaces. Note that because the workarea has already been - * computed, these strut lists should already be up to date. This function - * should have good performance since we call it a lot. - */ - - current = *orig; - current.x += delta_x; - current.y += delta_y; - - workspaces = meta_window_get_workspaces (window); - tmp = workspaces; - - if (leftmost_x_p) + constraint = &all_constraints[0]; + satisfied = TRUE; + while (constraint->func != NULL) { - *leftmost_x_p = info->nw_x; - while (tmp) + satisfied = satisfied && + (*constraint->func) (window, info, priority, check_only); + + if (!check_only) { - stmp = ((MetaWorkspace*) tmp->data)->left_struts; - while (stmp) - { - MetaRectangle *rect = (MetaRectangle*) stmp->data; - /* the strut only matters if the title bar is - * overlapping the strut rect. - */ - if (((current.y - info->fgeom.top_height >= rect->y) && - (current.y - info->fgeom.top_height < rect->y + rect->height)) || - ((current.y >= rect->y) && - (current.y < rect->y + rect->height))) - { - *leftmost_x_p = MAX (*leftmost_x_p, rect->width); - } - - stmp = stmp->next; - } - - tmp = tmp->next; + /* Log how the constraint modified the position */ + meta_topic (META_DEBUG_GEOMETRY, + "info->current is %d,%d +%d,%d after %s\n", + info->current.x, info->current.y, + info->current.width, info->current.height, + constraint->name); } - - *leftmost_x_p = *leftmost_x_p - current.width + - MIN (TITLEBAR_LENGTH_ONSCREEN, current.width); - } - - tmp = workspaces; - if (rightmost_x_p) - { - *rightmost_x_p = info->se_x; - while (tmp) + else if (!satisfied) { - stmp = ((MetaWorkspace*) tmp->data)->right_struts; - while (stmp) - { - MetaRectangle *rect = (MetaRectangle*) stmp->data; - /* the strut only matters if the title bar is - * overlapping the strut rect. - */ - if (((current.y - info->fgeom.top_height >= rect->y) && - (current.y - info->fgeom.top_height < rect->y + rect->height)) || - ((current.y >= rect->y) && - (current.y < rect->y + rect->height))) - { - *rightmost_x_p = MIN (*rightmost_x_p, rect->x); - } - - stmp = stmp->next; - } - - tmp = tmp->next; + /* Log which constraint was not satisfied */ + meta_topic (META_DEBUG_GEOMETRY, + "constraint %s not satisfied.\n", + constraint->name); + return FALSE; } - - *rightmost_x_p = *rightmost_x_p - - MIN (TITLEBAR_LENGTH_ONSCREEN, current.width); + ++constraint; } - tmp = workspaces; - if (topmost_y_p) - { - *topmost_y_p = info->nw_y; - while (tmp) - { - stmp = ((MetaWorkspace*) tmp->data)->top_struts; - while (stmp) - { - MetaRectangle *rect = (MetaRectangle*) stmp->data; - /* here the strut matters if the titlebar is overlapping - * the window horizontally - */ - if ((current.x < rect->x + rect->width) && - (current.x + current.width > rect->x)) - { - *topmost_y_p = MAX (*topmost_y_p, rect->height); - } - - stmp = stmp->next; - } - - tmp = tmp->next; - } - - *topmost_y_p = *topmost_y_p + info->fgeom.top_height; - } - - tmp = workspaces; - bottommost_y = G_MAXUSHORT; - if (bottommost_y_p || topmost_y_p) - { - bottommost_y = info->se_y; - while (tmp) - { - stmp = ((MetaWorkspace*) tmp->data)->bottom_struts; - while (stmp) - { - MetaRectangle *rect = (MetaRectangle*) stmp->data; - /* here the strut matters if the titlebar is overlapping - * the window horizontally - */ - if ((current.x < rect->x + rect->width) && - (current.x + current.width > rect->x)) - { - bottommost_y = MIN (bottommost_y, rect->y); - } - - stmp = stmp->next; - } - - tmp = tmp->next; - } - } - - if (bottommost_y_p) - { - *bottommost_y_p = bottommost_y; - - /* If no frame, keep random TITLEBAR_LENGTH_ONSCREEN pixels on the - * screen. - */ - if (!window->frame) - *bottommost_y_p = *bottommost_y_p - - MIN (TITLEBAR_LENGTH_ONSCREEN, current.height); - } - - /* if the window has a minimum size too big for the "effective" work - * area let it "cheat" a little by allowing a user to move it up so - * that you can see the bottom of the window. - */ - if (topmost_y_p) - { - int minheight; - - if (window->frame) - { - /* this is the "normal" case of, e.g. a dialog that's - * just too big for the work area - */ - minheight = window->frame->bottom_height + - window->size_hints.min_height; - } - else - { - /* let frameless windows move offscreen is too large for the - * effective work area. This may include windows that try - * to make themselves full screen by removing the - * decorations and repositioning themselves. - */ - minheight = orig->height; - } - - if (minheight > (bottommost_y - *topmost_y_p)) - *topmost_y_p = bottommost_y - minheight; - } + return TRUE; } -static void -constraint_onscreen_top_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - int min_dy; - int topmost_y; - - get_outermost_onscreen_positions (window, info, orig, 0, *y_delta, - NULL, NULL, &topmost_y, NULL); - - min_dy = topmost_y - orig->y; - - if (*y_delta < min_dy) - *y_delta = min_dy; -} - -static void -constraint_onscreen_bottom_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - /* no way to resize off the bottom so that constraints are - violated */ - return; -} - -static void -constraint_onscreen_vcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - int max_dy; - int topmost_y; - - get_outermost_onscreen_positions (window, info, orig, 0, *y_delta, - NULL, NULL, &topmost_y, NULL); - - max_dy = orig->y - topmost_y; - - if (*y_delta > max_dy) - *y_delta = max_dy; -} - -static void -constraint_onscreen_left_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - /* no way to resize off the sides so that constraints are violated - */ - return; -} - -static void -constraint_onscreen_right_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - /* no way to resize off the sides so that constraints are violated - */ - return; -} - -static void -constraint_onscreen_hcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - /* no way to resize off the sides so that constraints are violated - */ - return; -} - -static void -constraint_onscreen_move_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta, - int *y_delta) -{ - int min_delta; - int max_delta; - int leftmost_x, rightmost_x, topmost_y, bottommost_y; - - get_outermost_onscreen_positions (window, info, orig, *x_delta, *y_delta, - &leftmost_x, &rightmost_x, - &topmost_y, &bottommost_y); - - min_delta = topmost_y - orig->y; - max_delta = bottommost_y - orig->y; - - /* Note that min delta (top left) has priority over - * max delta (bottom right) to facilitate keeping - * titlebar on the screen - */ - if (*y_delta > max_delta) - *y_delta = max_delta; - if (*y_delta < min_delta) - *y_delta = min_delta; - - min_delta = leftmost_x - orig->x; - max_delta = rightmost_x - orig->x; - - if (*x_delta > max_delta) - *x_delta = max_delta; - if (*x_delta < min_delta) - *x_delta = min_delta; -} - -static const Constraint constraint_onscreen = { - "Onscreen", - constraint_onscreen_applies_func, - constraint_onscreen_top_func, - constraint_onscreen_bottom_func, - constraint_onscreen_vcenter_func, - constraint_onscreen_left_func, - constraint_onscreen_right_func, - constraint_onscreen_hcenter_func, - constraint_onscreen_move_func -}; - - -/* Size hints constraints: - * - * For min/max size we just clamp to those, and for resize increment - * we clamp to the one at or below the requested place. - * - * For aspect ratio, we special-case it at the end of - * meta_window_constrain, because it involves both dimensions, and - * thus messes up our generic framework. - * - * Left resize can be solved for dx like this: - * new_width = orig_width - dx - * new_x = orig_x + dx - * - * new_width >= min_width - * orig_width - dx >= min_width - * - dx >= min_width - orig_width - * dx <= orig_width - min_width - * - * new_width <= max_width - * orig_width - dx <= max_width - * - dx <= max_width - orig_width - * dx >= orig_width - max_width - * - */ - -#define USE_HINTS_FOR_WINDOW_STATE(window) (!((window)->fullscreen || (window)->maximized)) - -static gboolean -constraint_hints_applies_func (MetaWindow *window) -{ - return USE_HINTS_FOR_WINDOW_STATE (window); -} - -static void -constraint_hints_top_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - int min_dy; - int max_dy; - int height; - - max_dy = orig->height - window->size_hints.min_height; - min_dy = orig->height - window->size_hints.max_height; - - g_assert (max_dy >= min_dy); - - if (*y_delta > max_dy) - *y_delta = max_dy; - if (*y_delta < min_dy) - *y_delta = min_dy; - - /* shrink to base + N * inc - */ - height = orig->height - *y_delta; - height = window->size_hints.base_height + - FLOOR (height - window->size_hints.base_height, window->size_hints.height_inc); - - *y_delta = orig->height - height; -} - -static void -constraint_hints_bottom_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - int min_dy; - int max_dy; - int height; - - min_dy = window->size_hints.min_height - orig->height; - max_dy = window->size_hints.max_height - orig->height; - - g_assert (max_dy >= min_dy); - - if (*y_delta > max_dy) - *y_delta = max_dy; - if (*y_delta < min_dy) - *y_delta = min_dy; - - /* shrink to base + N * inc - */ - height = orig->height + *y_delta; - height = window->size_hints.base_height + - FLOOR (height - window->size_hints.base_height, window->size_hints.height_inc); - - *y_delta = height - orig->height; -} - -static void -constraint_hints_vcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *y_delta) -{ - int min_dy; - int max_dy; - int height; - - /* Remember our delta is negative to shrink window, positive to - * grow it, and the actual resize is y_delta * 2 (which is broken, - * but that's how it currently is) - */ - - min_dy = (window->size_hints.min_height - orig->height) / 2; - max_dy = (window->size_hints.max_height - orig->height) / 2; - - g_assert (max_dy >= min_dy); - - if (*y_delta > max_dy) - *y_delta = max_dy; - if (*y_delta < min_dy) - *y_delta = min_dy; - - /* shrink to base + N * inc - */ - height = orig->height + *y_delta * 2; - height = window->size_hints.base_height + - FLOOR (height - window->size_hints.base_height, window->size_hints.height_inc); - - *y_delta = (height - orig->height) / 2; -} - -static void -constraint_hints_left_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - int min_dx; - int max_dx; - int width; - - max_dx = orig->width - window->size_hints.min_width; - min_dx = orig->width - window->size_hints.max_width; - - g_assert (max_dx >= min_dx); - - if (*x_delta > max_dx) - *x_delta = max_dx; - if (*x_delta < min_dx) - *x_delta = min_dx; - - /* shrink to base + N * inc - */ - width = orig->width - *x_delta; - width = window->size_hints.base_width + - FLOOR (width - window->size_hints.base_width, window->size_hints.width_inc); - - *x_delta = orig->width - width; -} - -static void -constraint_hints_right_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - int min_dx; - int max_dx; - int width; - - min_dx = window->size_hints.min_width - orig->width; - max_dx = window->size_hints.max_width - orig->width; - - g_assert (max_dx >= min_dx); - - if (*x_delta > max_dx) - *x_delta = max_dx; - if (*x_delta < min_dx) - *x_delta = min_dx; - - /* shrink to base + N * inc - */ - width = orig->width + *x_delta; - width = window->size_hints.base_width + - FLOOR (width - window->size_hints.base_width, window->size_hints.width_inc); - - *x_delta = width - orig->width; -} - -static void -constraint_hints_hcenter_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta) -{ - int min_dx; - int max_dx; - int width; - - /* Remember our delta is negative to shrink window, positive to - * grow it, and the actual resize is x_delta * 2 (which is broken, - * but that's how it currently is) - */ - - min_dx = (window->size_hints.min_width - orig->width) / 2; - max_dx = (window->size_hints.max_width - orig->width) / 2; - - g_assert (max_dx >= min_dx); - - if (*x_delta > max_dx) - *x_delta = max_dx; - if (*x_delta < min_dx) - *x_delta = min_dx; - - /* shrink to base + N * inc - */ - width = orig->width + *x_delta * 2; - width = window->size_hints.base_width + - FLOOR (width - window->size_hints.base_width, window->size_hints.width_inc); - - *x_delta = (width - orig->width) / 2; -} - -static void -constraint_hints_move_func (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int *x_delta, - int *y_delta) -{ - /* nothing */ -} - -static const Constraint constraint_hints = { - "Hints", - constraint_hints_applies_func, - constraint_hints_top_func, - constraint_hints_bottom_func, - constraint_hints_vcenter_func, - constraint_hints_left_func, - constraint_hints_right_func, - constraint_hints_hcenter_func, - constraint_hints_move_func -}; - -/* Array of all constraints at once */ -static const Constraint *all_constraints[] = { - &constraint_desktop, - &constraint_onscreen, - &constraint_hints, - NULL -}; - -/* Move with no accompanying change to window size */ -static void -constrain_move (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int x_delta, - int y_delta, - MetaRectangle *new) -{ - const Constraint **cp; - int old_x, old_y; - int paranoia; - - /* Evidence that we can't actually prove this algorithm is right */ -#define MAX_ITERATIONS 10 - paranoia = 0; - - do { - old_x = x_delta; - old_y = y_delta; - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d %d (Move constraint '%s')\n", - x_delta, y_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->move_func) (window, info, orig, - &x_delta, &y_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d %d (Move constraint '%s')\n", - x_delta, y_delta, (*cp)->name); - - ++cp; - } - - ++paranoia; - } while (((old_x != x_delta) || (old_y != y_delta)) && paranoia < MAX_ITERATIONS); - - new->x = orig->x + x_delta; - new->y = orig->y + y_delta; - - if (paranoia >= MAX_ITERATIONS) - meta_topic (META_DEBUG_GEOMETRY, - "Constraints were never satisfied for window %s\n", - window->desc); -} - -static void -constrain_resize_left (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int x_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (Left constraint '%s')\n", - x_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->left_func) (window, info, orig, - &x_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (Left constraint '%s')\n", - x_delta, (*cp)->name); - - ++cp; - } - - /* Moving mouse from 10 to 5 means current - orig means 5 - 10 means - * a delta of -5 - */ - new->x = orig->x + x_delta; - new->width = orig->width - x_delta; -} - -static void -constrain_resize_hcenter (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int x_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (HCenter constraint '%s')\n", - x_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->hcenter_func) (window, info, orig, - &x_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (HCenter constraint '%s')\n", - x_delta, (*cp)->name); - - ++cp; - } - - /* center deltas are positive to grow the window and negative to - * shrink it. - */ - new->x = orig->x - x_delta; - new->width = orig->width + x_delta * 2; - /* FIXME above implies that with center gravity you have to grow - * in increments of two - */ -} - -static void -constrain_resize_right (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int x_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (Right constraint '%s')\n", - x_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->right_func) (window, info, orig, - &x_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (Right constraint '%s')\n", - x_delta, (*cp)->name); - - ++cp; - } - - new->width = orig->width + x_delta; -} - -static void -constrain_resize_top (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int y_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (Top constraint '%s')\n", - y_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->top_func) (window, info, orig, - &y_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (Top constraint '%s')\n", - y_delta, (*cp)->name); - - ++cp; - } - - new->y = orig->y + y_delta; - new->height = orig->height - y_delta; -} - -static void -constrain_resize_vcenter (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int y_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (VCenter constraint '%s')\n", - y_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->vcenter_func) (window, info, orig, - &y_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (VCenter constraint '%s')\n", - y_delta, (*cp)->name); - - ++cp; - } - - /* center deltas are positive to grow the window and negative to - * shrink it. - */ - new->y = orig->y - y_delta; - new->height = orig->height + y_delta * 2; - /* FIXME above implies that with center gravity you have to grow - * in increments of two - */ -} - -static void -constrain_resize_bottom (MetaWindow *window, - const ConstraintInfo *info, - const MetaRectangle *orig, - int y_delta, - MetaRectangle *new) -{ - const Constraint **cp; - - cp = &all_constraints[0]; - - while (*cp) - { - meta_topic (META_DEBUG_GEOMETRY, - "Before: %d (Bottom constraint '%s')\n", - y_delta, (*cp)->name); - - if ((* (*cp)->applies_func) (window)) - (* (*cp)->bottom_func) (window, info, orig, - &y_delta); - - meta_topic (META_DEBUG_GEOMETRY, - "After: %d (Bottom constraint '%s')\n", - y_delta, (*cp)->name); - - ++cp; - } - - new->height = orig->height + y_delta; -} - -static void -update_position_limits (MetaWindow *window, - ConstraintInfo *info) -{ - int nw_x, nw_y; - int se_x, se_y; - - /* For maximized windows the limits are the work area, for - * other windows we see which struts apply based on the - * window's position later on - */ - if (window->maximized) - { - nw_x = MIN (info->work_area_xinerama.x, info->work_area_screen.x); - nw_y = MIN (info->work_area_xinerama.y, info->work_area_screen.y); - - /* find bottom-right corner of workarea */ - se_x = MAX (info->work_area_xinerama.x + info->work_area_xinerama.width, - info->work_area_screen.x + info->work_area_screen.width); - se_y = MAX (info->work_area_xinerama.y + info->work_area_xinerama.height, - info->work_area_screen.y + info->work_area_screen.height); - } - else - { - nw_x = 0; - nw_y = 0; - se_x = window->screen->width; - se_y = window->screen->height; - } - - /* If we have a micro-screen or huge frames maybe nw/se got - * swapped - */ - if (nw_x > se_x) - { - int tmp = nw_x; - nw_x = se_x; - se_x = tmp; - } - - if (nw_y > se_y) - { - int tmp = nw_y; - nw_y = se_y; - se_y = tmp; - } - - info->nw_x = nw_x; - info->nw_y = nw_y; - info->se_x = se_x; - info->se_y = se_y; -} - -/* The delta values are the mouse motion distance deltas, - * i.e. mouse_current_pos - mouse_orig_pos, for resizing on - * the sides, or moving. For center resize, the delta - * value is positive to grow the window and negative to - * shrink it (while the sign of the mouse delta - * depends on which side of the window you are center resizing - * from) - */ void meta_window_constrain (MetaWindow *window, MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, const MetaRectangle *orig, - int x_move_delta, - int y_move_delta, - MetaResizeDirection x_direction, - int x_delta, - MetaResizeDirection y_direction, - int y_delta, MetaRectangle *new) { ConstraintInfo info; - MetaRectangle current; - gboolean did_placement; - -#define OUTER_WIDTH(rect) ((rect).width + info.fgeom.left_width + info.fgeom.right_width) -#define OUTER_HEIGHT(rect) ((rect).height + info.fgeom.top_height + info.fgeom.bottom_height) + /* WARNING: orig and new specify positions and sizes of the inner window, + * not the outer. This is a common gotcha since half the constraints + * deal with inner window position/size and half deal with outer. See + * doc/how-constraints-works.txt for more information. + */ meta_topic (META_DEBUG_GEOMETRY, - "Constraining %s x_move_delta = %d y_move_delta = %d x_direction = %d y_direction = %d x_delta = %d y_delta = %d orig %d,%d %dx%d\n", - window->desc, x_move_delta, y_move_delta, - x_direction, y_direction, x_delta, y_delta, - orig->x, orig->y, orig->width, orig->height); + "Constraining %s in move from %d,%d %dx%d to %d,%d %dx%d\n", + window->desc, + orig->x, orig->y, orig->width, orig->height, + new->x, new->y, new->width, new->height); + + setup_constraint_info (&info, + window, + orig_fgeom, + flags, + resize_gravity, + orig, + new); + place_window_if_needed (window, &info); + + ConstraintPriority priority = PRIORITY_MINIMUM; + gboolean satisfied = FALSE; + while (!satisfied && priority <= PRIORITY_MAXIMUM) { + gboolean check_only = TRUE; + + /* Individually enforce all the high-enough priority constraints */ + do_all_constraints (window, &info, priority, !check_only); + + /* Check if all high-enough priority constraints are simultaneously + * satisfied + */ + satisfied = do_all_constraints (window, &info, priority, check_only); + + /* Drop the least important constraints if we can't satisfy them all */ + priority++; + } + + /* Make sure we use the constrained position */ + *new = info.current; + + /* We may need to update window->require_fully_onscreen, + * window->require_on_single_xinerama, and perhaps other quantities + * if this was a user move or user move-and-resize operation. + */ + update_onscreen_requirements (window, &info); + + /* Ew, what an ugly way to do things. Destructors (in a real OOP language, + * not gobject-style--gobject would be more pain than it's worth) or + * smart pointers would be so much nicer here. *shrug* + */ + if (!orig_fgeom) + g_free (info.fgeom); +} + +static void +setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new) +{ + info->orig = *orig; + info->current = *new; /* Create a fake frame geometry if none really exists */ if (orig_fgeom && !window->fullscreen) - info.fgeom = *orig_fgeom; + info->fgeom = orig_fgeom; else + info->fgeom = g_new0 (MetaFrameGeometry, 1); + + if (flags & META_IS_MOVE_ACTION && flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_MOVE_AND_RESIZE; + else if (flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_RESIZE; + else if (flags & META_IS_MOVE_ACTION) + info->action_type = ACTION_MOVE_AND_RESIZE; + else + g_error ("BAD, BAD developer! No treat for you! (Fix your calls to " + "meta_window_move_resize_internal()).\n"); + + info->is_user_action = (flags & META_IS_USER_ACTION); + + info->resize_gravity = resize_gravity; + + info->fixed_directions = 0; + /* If x directions don't change but either y direction does */ + if ( orig->x == new->x && orig->x + orig->width == new->x + new->width && + (orig->y != new->y || orig->y + orig->height != new->y + new->height)) { - info.fgeom.top_height = 0; - info.fgeom.bottom_height = 0; - info.fgeom.left_width = 0; - info.fgeom.right_width = 0; + info->fixed_directions = FIXED_DIRECTION_X; + } + /* If y directions don't change but either x direction does */ + if ( orig->y == new->y && orig->y + orig->height == new->y + new->height && + (orig->x != new->x || orig->x + orig->width != new->x + new->width )) + { + info->fixed_directions = FIXED_DIRECTION_Y; } - meta_window_get_work_area_current_xinerama (window, &info.work_area_xinerama); - meta_window_get_work_area_all_xineramas (window, &info.work_area_screen); + meta_window_get_work_area_current_xinerama (window, &info->work_area_xinerama); - info.window = window; - info.xinerama = meta_screen_get_xinerama_for_window (window->screen, - window); - /* Init info->nw_x etc. */ - update_position_limits (window, &info); + const MetaXineramaScreenInfo *xinerama_info = + meta_screen_get_xinerama_for_window (window->screen, window); + info->entire_xinerama = xinerama_info->rect; - current = *orig; - *new = current; + MetaWorkspace *cur_workspace = window->screen->active_workspace; + info->usable_screen_region = + meta_workspace_get_onscreen_region (cur_workspace); + info->usable_xinerama_region = + meta_workspace_get_onxinerama_region (cur_workspace, + xinerama_info->number); + + /* Log all this information for debugging */ + meta_topic (META_DEBUG_GEOMETRY, + "Setting up constraint info:\n" + " orig: %d,%d +%d,%d\n" + " new : %d,%d +%d,%d\n" + " fgeom: %d,%d,%d,%d\n" + " action_type : %s\n" + " is_user_action : %s\n" + " resize_gravity : %s\n" + " fixed_directions: %s\n" + " work_area_xinerama: %d,%d +%d,%d\n" + " entire_xinerama : %d,%d +%d,%d\n", + info->orig.x, info->orig.y, info->orig.width, info->orig.height, + info->current.x, info->current.y, + info->current.width, info->current.height, + info->fgeom->left_width, info->fgeom->right_width, + info->fgeom->top_height, info->fgeom->bottom_height, + (info->action_type == ACTION_MOVE) ? "Move" : + (info->action_type == ACTION_RESIZE) ? "Resize" : + (info->action_type == ACTION_MOVE_AND_RESIZE) ? "Move&Resize" : + "Freakin' Invalid Stupid", + (info->is_user_action) ? "true" : "false", + meta_gravity_to_string (info->resize_gravity), + (info->fixed_directions == 0) ? "None" : + (info->fixed_directions == FIXED_DIRECTION_X) ? "X fixed" : + (info->fixed_directions == FIXED_DIRECTION_Y) ? "Y fixed" : + "Freakin' Invalid Stupid", + info->work_area_xinerama.x, info->work_area_xinerama.y, + info->work_area_xinerama.width, + info->work_area_xinerama.height, + info->entire_xinerama.x, info->entire_xinerama.y, + info->entire_xinerama.width, info->entire_xinerama.height); +} + +static void +place_window_if_needed(MetaWindow *window, + ConstraintInfo *info) +{ + gboolean did_placement; /* Do placement if any, so we go ahead and apply position * constraints in a move-only context. Don't place @@ -1285,328 +423,661 @@ meta_window_constrain (MetaWindow *window, did_placement = FALSE; if (!window->placed && window->calc_placement && - !window->maximized && + !META_WINDOW_MAXIMIZED (window) && !window->fullscreen) { - MetaRectangle placed_rect = current; + MetaRectangle placed_rect = info->orig; - meta_window_place (window, orig_fgeom, current.x, current.y, + meta_window_place (window, info->fgeom, info->orig.x, info->orig.y, &placed_rect.x, &placed_rect.y); did_placement = TRUE; /* placing the window may have changed the xinerama. Find the * new xinerama and update the ConstraintInfo */ - info.xinerama = meta_screen_get_xinerama_for_rect (window->screen, - &placed_rect); + const MetaXineramaScreenInfo *xinerama_info = + meta_screen_get_xinerama_for_rect (window->screen, &placed_rect); + info->entire_xinerama = xinerama_info->rect; meta_window_get_work_area_for_xinerama (window, - info.xinerama->number, - &info.work_area_xinerama); - update_position_limits (window, &info); + xinerama_info->number, + &info->work_area_xinerama); - constrain_move (window, &info, ¤t, - placed_rect.x - current.x, - placed_rect.y - current.y, - new); - current = *new; - - /* Ignore any non-placement movement */ - x_move_delta = 0; - y_move_delta = 0; + info->current.x = placed_rect.x; + info->current.y = placed_rect.y; + /* Since we just barely placed the window, there's no reason to + * consider any of the directions fixed. + */ + info->fixed_directions = 0; } - if (window->maximize_after_placement && + if ((window->maximize_horizontally_after_placement || + window->maximize_vertically_after_placement) && (window->placed || did_placement)) { - window->maximize_after_placement = FALSE; - - if (OUTER_WIDTH (*new) >= info.work_area_xinerama.width && - OUTER_HEIGHT (*new) >= info.work_area_xinerama.height) - { - /* define a sane saved_rect so that the user can unmaximize - * to something reasonable. - */ - new->width = .75 * info.work_area_xinerama.width; - new->height = .75 * info.work_area_xinerama.height; - new->x = info.work_area_xinerama.x + .125 * info.work_area_xinerama.width; - new->y = info.work_area_xinerama.y + .083 * info.work_area_xinerama.height; - } - - meta_window_maximize_internal (window, new); + /* define a sane saved_rect so that the user can unmaximize to + * something reasonable. + */ + if (info->current.width >= info->work_area_xinerama.width) + { + info->current.width = .75 * info->work_area_xinerama.width; + info->current.x = info->work_area_xinerama.x + + .125 * info->work_area_xinerama.width; + } + if (info->current.height >= info->work_area_xinerama.height) + { + info->current.height = .75 * info->work_area_xinerama.height; + info->current.y = info->work_area_xinerama.y + + .083 * info->work_area_xinerama.height; + } /* maximization may have changed frame geometry */ - if (orig_fgeom && !window->fullscreen) + if (window->frame && !window->fullscreen) + meta_frame_calc_geometry (window->frame, info->fgeom); + + if (window->maximize_horizontally_after_placement && + window->maximize_vertically_after_placement) + meta_window_maximize_internal (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL, + &info->current); + else if (window->maximize_horizontally_after_placement) { - meta_frame_calc_geometry (window->frame, - orig_fgeom); - info.fgeom = *orig_fgeom; + info->current.x = info->work_area_xinerama.x + + info->fgeom->left_width; + info->current.width = info->work_area_xinerama.width + - info->fgeom->left_width - info->fgeom->right_width; } + else if (window->maximize_vertically_after_placement); + { + info->current.y = info->work_area_xinerama.y + + info->fgeom->top_height; + info->current.height = info->work_area_xinerama.height + - info->fgeom->top_height - info->fgeom->bottom_height; + } + + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; } - - /* Maximization, fullscreen, etc. are defined as a resize followed by - * a move, as explained in one of the big comments at the top of - * this file. - */ - if (window->fullscreen) - { - current = *new; - constrain_resize_bottom (window, &info, ¤t, - (info.xinerama->height - OUTER_HEIGHT (current)), - new); - - current = *new; - - constrain_resize_right (window, &info, ¤t, - info.xinerama->width - OUTER_WIDTH (current), - new); - current = *new; - - constrain_move (window, &info, ¤t, - info.xinerama->x_origin - current.x + info.fgeom.left_width, - info.xinerama->y_origin - current.y + info.fgeom.top_height, - new); - } - else if (window->maximized) - { - constrain_resize_bottom (window, &info, ¤t, - (info.work_area_xinerama.height - OUTER_HEIGHT (current)), - new); - - current = *new; - - constrain_resize_right (window, &info, ¤t, - info.work_area_xinerama.width - OUTER_WIDTH (current), - new); - current = *new; - - constrain_move (window, &info, ¤t, - info.work_area_xinerama.x - current.x + info.fgeom.left_width, - info.work_area_xinerama.y - current.y + info.fgeom.top_height, - new); - - current = *new; - } - else - { - switch (x_direction) - { - case META_RESIZE_LEFT_OR_TOP: - constrain_resize_left (window, &info, ¤t, - x_delta, new); - break; - case META_RESIZE_CENTER: - constrain_resize_hcenter (window, &info, ¤t, - x_delta, new); - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - constrain_resize_right (window, &info, ¤t, - x_delta, new); - break; - } - - switch (y_direction) - { - case META_RESIZE_LEFT_OR_TOP: - constrain_resize_top (window, &info, ¤t, - y_delta, new); - break; - case META_RESIZE_CENTER: - constrain_resize_vcenter (window, &info, ¤t, - y_delta, new); - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - constrain_resize_bottom (window, &info, ¤t, - y_delta, new); - break; - } - - current = *new; - - constrain_move (window, &info, ¤t, - x_move_delta, y_move_delta, - new); - - current = *new; - } - - /* Now we have to sort out the aspect ratio */ - if (!window->fullscreen) - { - /* - * width - * min_aspect <= -------- <= max_aspect - * height - */ - double min_aspect, max_aspect; - int width, height; - - min_aspect = window->size_hints.min_aspect.x / (double) window->size_hints.min_aspect.y; - max_aspect = window->size_hints.max_aspect.x / (double) window->size_hints.max_aspect.y; - - width = current.width; - height = current.height; - - if (min_aspect * height > width) - { - int delta; - - if (y_direction == META_RESIZE_CENTER) - { - delta = FLOOR (height * min_aspect - width, window->size_hints.width_inc); - if (width + delta <= window->size_hints.max_width) - width += delta; - else - { - delta = FLOOR (height - width / min_aspect, window->size_hints.height_inc); - if (height - delta >= window->size_hints.min_height) - height -= delta; - } - } - else - { - delta = FLOOR (height - width / min_aspect, window->size_hints.height_inc); - if (height - delta >= window->size_hints.min_height) - height -= delta; - else - { - delta = FLOOR (height * min_aspect - width, window->size_hints.width_inc); - if (width + delta <= window->size_hints.max_width) - width += delta; - } - } - } - - if (max_aspect * height < width) - { - int delta; - - if (x_direction == META_RESIZE_CENTER) - { - delta = FLOOR (width / max_aspect - height, window->size_hints.height_inc); - if (height + delta <= window->size_hints.max_height) - height += delta; - else - { - delta = FLOOR (width - height * max_aspect, window->size_hints.width_inc); - if (width - delta >= window->size_hints.min_width) - width -= delta; - } - } - else - { - delta = FLOOR (width - height * max_aspect, window->size_hints.width_inc); - if (width - delta >= window->size_hints.min_width) - width -= delta; - else - { - delta = FLOOR (width / max_aspect - height, window->size_hints.height_inc); - if (height + delta <= window->size_hints.max_height) - height += delta; - } - } - } - - /* Convert into terms of the direction of resize and reapply the - * earlier constraints; this means aspect ratio becomes the - * least-important of the constraints. If we wanted aspect to be - * the most important, we could just not do this next bit. - */ - - if (current.width != width) - { - x_delta = width - current.width; /* positive delta to increase width */ - switch (x_direction) - { - case META_RESIZE_LEFT_OR_TOP: - constrain_resize_left (window, &info, ¤t, - - x_delta, new); - break; - case META_RESIZE_CENTER: - constrain_resize_hcenter (window, &info, ¤t, - x_delta, new); - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - constrain_resize_right (window, &info, ¤t, - x_delta, new); - break; - } - } - - if (current.height != height) - { - y_delta = height - current.height; /* positive to increase height */ - - switch (y_direction) - { - case META_RESIZE_LEFT_OR_TOP: - constrain_resize_top (window, &info, ¤t, - - y_delta, new); - break; - case META_RESIZE_CENTER: - constrain_resize_vcenter (window, &info, ¤t, - y_delta, new); - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - constrain_resize_bottom (window, &info, ¤t, - y_delta, new); - break; - } - } - - current = *new; - } - - meta_topic (META_DEBUG_GEOMETRY, - "Constrained %s new %d,%d %dx%d old %d,%d %dx%d\n", - window->desc, - new->x, new->y, new->width, new->height, - orig->x, orig->y, orig->width, orig->height); } -MetaResizeDirection -meta_x_direction_from_gravity (int gravity) +static void +update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info) { - switch (gravity) + gboolean old; + + /* We only apply the various onscreen requirements to normal windows */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return; + + /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen and + * require_on_single_xinerama flags to *become false* due to user + * interactions (which is allowed since certain constraints are ignored + * for user interactions regardless of the setting of these flags). + * However, whether to make these flags *become true* due to just an + * application interaction is a little trickier. It's possible that + * users may find not doing that strange since two application + * interactions that resize in opposite ways don't necessarily end up + * cancelling--but it may also be strange for the user to have an + * application resize the window so that it's onscreen, the user forgets + * about it, and then later the app is able to resize itself off the + * screen. Anyway, for now, I'm think the latter is the more problematic + * case but this may need to be revisited. + */ + + /* The require onscreen/on-single-xinerama stuff is relative to the + * outer window, not the inner + */ + extend_by_frame (&info->current, info->fgeom); + + /* Update whether we want future constraint runs to require the + * window to be on fully onscreen. + */ + old = window->require_fully_onscreen; + window->require_fully_onscreen = + meta_rectangle_contained_in_region (info->usable_screen_region, + &info->current); + if (old ^ window->require_fully_onscreen) + meta_topic (META_DEBUG_GEOMETRY, + "require_fully_onscreen for %s toggled to %s\n", + window->desc, + window->require_fully_onscreen ? "TRUE" : "FALSE"); + + /* Update whether we want future constraint runs to require the + * window to be on a single xinerama. + */ + old = window->require_on_single_xinerama; + window->require_on_single_xinerama = + meta_rectangle_contained_in_region (info->usable_xinerama_region, + &info->current); + if (old ^ window->require_on_single_xinerama) + meta_topic (META_DEBUG_GEOMETRY, + "require_on_single_xinerama for %s toggled to %s\n", + window->desc, + window->require_on_single_xinerama ? "TRUE" : "FALSE"); + + /* Don't forget to restore the position of the window */ + unextend_by_frame (&info->current, info->fgeom); +} + +static void +extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x -= fgeom->left_width; + rect->y -= fgeom->top_height; + rect->width += fgeom->left_width + fgeom->right_width; + rect->height += fgeom->top_height + fgeom->bottom_height; +} + +static void +unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x += fgeom->left_width; + rect->y += fgeom->top_height; + rect->width -= fgeom->left_width + fgeom->right_width; + rect->height -= fgeom->top_height + fgeom->bottom_height; +} + +static inline void +get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size) +{ + /* We pack the results into MetaRectangle structs just for convienience; we + * don't actually use the position of those rects. + */ + min_size->width = window->size_hints.min_width; + min_size->height = window->size_hints.min_height; + max_size->width = window->size_hints.max_width; + max_size->height = window->size_hints.max_height; + + if (include_frame) { + int fw = fgeom->left_width + fgeom->right_width; + int fh = fgeom->top_height + fgeom->bottom_height; + + min_size->width += fw; + min_size->height += fh; + max_size->width += fw; + max_size->height += fh; + } +} + +static gboolean +constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_MAXIMIZATION) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->maximized_horizontally && !window->maximized_vertically) + return TRUE; + + MetaRectangle min_size, max_size; + MetaRectangle work_area = info->work_area_xinerama; + unextend_by_frame (&work_area, info->fgeom); + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + + gboolean hminbad, vminbad, hmaxbad, vmaxbad; + hminbad = work_area.width < min_size.width && window->maximized_horizontally; + vminbad = work_area.height < min_size.height && window->maximized_vertically; + hmaxbad = work_area.width > max_size.width && window->maximized_horizontally; + vmaxbad = work_area.height > max_size.height && window->maximized_vertically; + if (hminbad || vminbad || hmaxbad || vmaxbad) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + gboolean horiz_equal, vert_equal; + horiz_equal = work_area.x == info->current.x && + work_area.width == info->current.width; + vert_equal = work_area.y == info->current.y && + work_area.height == info->current.height; + gboolean constraint_already_satisfied = + (horiz_equal || !window->maximized_horizontally) && + (vert_equal || !window->maximized_vertically); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + if (window->maximized_horizontally) + { + info->current.x = work_area.x; + info->current.width = work_area.width; + } + if (window->maximized_vertically) + { + info->current.y = work_area.y; + info->current.height = work_area.height; + } + return TRUE; +} + +static gboolean +constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_FULLSCREEN) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->fullscreen) + return TRUE; + MetaRectangle min_size, max_size; + MetaRectangle xinerama = info->entire_xinerama; + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + gboolean too_big = !meta_rectangle_could_fit_rect (&xinerama, &min_size); + gboolean too_small = !meta_rectangle_could_fit_rect (&max_size, &xinerama); + if (too_big || too_small) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + gboolean constraint_already_satisfied = + meta_rectangle_equal (&info->current, &xinerama); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + info->current = xinerama; + return TRUE; +} + +static gboolean +constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_SIZE_HINTS_INCREMENTS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + int bh, hi, bw, wi, extra_height, extra_width; + bh = window->size_hints.base_height; + hi = window->size_hints.height_inc; + bw = window->size_hints.base_width; + wi = window->size_hints.width_inc; + extra_height = (info->current.height - bh) % hi; + extra_width = (info->current.width - bw) % wi; + if (window->maximized_horizontally) + extra_width *= 0; + if (window->maximized_vertically) + extra_height *= 0; + gboolean constraint_already_satisfied = + (extra_height == 0 && extra_width == 0); + + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + /* Shrink to base + N * inc */ + meta_rectangle_resize_with_gravity (&info->orig, + &info->current, + info->resize_gravity, + info->current.width - extra_width, + info->current.height - extra_height); + return TRUE; +} + +static gboolean +constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_SIZE_HINTS_LIMITS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. + * + * Note: The old code didn't apply this constraint for fullscreen or + * maximized windows--but that seems odd to me. *shrug* + */ + if (info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + MetaRectangle min_size, max_size; + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + gboolean too_big = + !meta_rectangle_could_fit_rect (&info->current, &min_size); + gboolean too_small = + !meta_rectangle_could_fit_rect (&max_size, &info->current); + gboolean constraint_already_satisfied = !too_big && !too_small; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + int new_width, new_height; + new_width = CLAMP (info->current.width, min_size.width, max_size.width); + new_height = CLAMP (info->current.height, min_size.height, max_size.height); + meta_rectangle_resize_with_gravity (&info->orig, + &info->current, + info->resize_gravity, + new_width, + new_height); + return TRUE; +} + +static gboolean +constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ASPECT_RATIO) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. */ + double minr, maxr; + minr = window->size_hints.min_aspect.x / + (double)window->size_hints.min_aspect.y; + maxr = window->size_hints.max_aspect.x / + (double)window->size_hints.max_aspect.y; + gboolean constraints_are_inconsistent = minr > maxr; + if (constraints_are_inconsistent || + META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is. We + * need the following to hold: + * + * width + * minr <= ------ <= maxr + * height + * + * But we need to allow for some slight fudging since width and height + * are integers instead of floating point numbers (this is particularly + * important when minr == maxr), so we allow width and height to be off + * a little bit from strictly satisfying these equations. For just one + * sided resizing, we have to make the fudge factor a little bigger + * because of how meta_rectangle_resize_with_gravity treats those as + * being a resize increment (FIXME: I should handle real resize + * increments better here...) + */ + int fudge; + switch (info->resize_gravity) + { + case WestGravity: + case NorthGravity: + case SouthGravity: case EastGravity: - case NorthEastGravity: - case SouthEastGravity: - return META_RESIZE_LEFT_OR_TOP; + fudge = 2; break; - case WestGravity: case NorthWestGravity: case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: case StaticGravity: - return META_RESIZE_RIGHT_OR_BOTTOM; - break; - default: - return META_RESIZE_CENTER; + fudge = 1; break; } -} + gboolean constraint_already_satisfied = + info->current.width - (info->current.height * minr ) > -minr*fudge && + info->current.width - (info->current.height * maxr ) < maxr*fudge; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; -MetaResizeDirection -meta_y_direction_from_gravity (int gravity) -{ - switch (gravity) + /*** Enforce constraint ***/ + int new_width, new_height; + double best_width, best_height; + double alt_width, alt_height; + new_width = info->current.width; + new_height = info->current.height; + + switch (info->resize_gravity) { - case SouthGravity: - case SouthWestGravity: - case SouthEastGravity: - return META_RESIZE_LEFT_OR_TOP; + case WestGravity: + case EastGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_height = CLAMP (new_height, new_width / maxr, new_width / minr); break; case NorthGravity: - case NorthWestGravity: - case NorthEastGravity: - case StaticGravity: - return META_RESIZE_RIGHT_OR_BOTTOM; + case SouthGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = CLAMP (new_width, new_height * minr, new_height * maxr); break; + case NorthWestGravity: + case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: + case StaticGravity: default: - return META_RESIZE_CENTER; + /* Find what width would correspond to new_height, and what height would + * correspond to new_width */ + alt_width = CLAMP (new_width, new_height * minr, new_height * maxr); + alt_height = CLAMP (new_height, new_width / maxr, new_width / minr); + + /* The line connecting the points (alt_width, new_height) and + * (new_width, alt_height) provide a range of + * valid-for-the-aspect-ratio-constraint sizes. We want the + * size in that range closest to the value requested, i.e. the + * point on the line which is closest to the point (new_width, + * new_height) + */ + meta_rectangle_find_linepoint_closest_to_point (alt_width, new_height, + new_width, alt_height, + new_width, new_height, + &best_width, &best_height); + + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = best_width; + new_height = best_height; + + break; } + + meta_rectangle_resize_with_gravity (&info->orig, + &info->current, + info->resize_gravity, + new_width, + new_height); + + return TRUE; +} + +static gboolean +do_screen_and_xinerama_relative_constraints ( + MetaWindow *window, + GList *region_spanning_rectangles, + ConstraintInfo *info, + gboolean check_only) +{ + gboolean exit_early = FALSE; + + /* First, log some debugging information */ + char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)]; + (void) spanning_region; /* Avoid stupid & incorrect compiler warnings... */ + meta_topic (META_DEBUG_GEOMETRY, + "screen/xinerama constraint; region_spanning_rectangles: %s\n", + meta_rectangle_region_to_string (region_spanning_rectangles, ", ", + spanning_region)); + + /* Determine whether constraint applies; exit if it doesn't */ + MetaRectangle how_far_it_can_be_smushed, min_size, max_size; + how_far_it_can_be_smushed = info->current; + get_size_limits (window, info->fgeom, TRUE, &min_size, &max_size); + extend_by_frame (&info->current, info->fgeom); + + if (info->action_type != ACTION_MOVE) + { + if (!(info->fixed_directions & FIXED_DIRECTION_X)) + how_far_it_can_be_smushed.width = min_size.width; + + if (!(info->fixed_directions & FIXED_DIRECTION_Y)) + how_far_it_can_be_smushed.height = min_size.height; + } + if (!meta_rectangle_could_fit_in_region (region_spanning_rectangles, + &how_far_it_can_be_smushed)) + exit_early = TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + gboolean constraint_satisfied = + meta_rectangle_contained_in_region (region_spanning_rectangles, + &info->current); + if (exit_early || constraint_satisfied || check_only) + { + unextend_by_frame (&info->current, info->fgeom); + return constraint_satisfied; + } + + /* Enforce constraint */ + + /* Clamp rectangle size for resize or move+resize actions */ + if (info->action_type != ACTION_MOVE) + meta_rectangle_clamp_to_fit_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current, + &min_size); + + if (info->is_user_action && info->action_type == ACTION_RESIZE) + /* For user resize, clip to the relevant region */ + meta_rectangle_clip_to_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + else + /* For everything else, shove the rectangle into the relevant region */ + meta_rectangle_shove_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + + unextend_by_frame (&info->current, info->fgeom); + return TRUE; +} + +static gboolean +constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut) and we can't apply it to frameless windows + * or else users will be unable to move windows such as XMMS across xineramas. + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->screen->n_xinerama_infos == 1 || + !window->require_on_single_xinerama || + !window->frame || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + gboolean retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_xinerama_region, + info, + check_only); + + /* Free up the data we allocated */ + return retval; +} + +static gboolean +constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + !window->require_fully_onscreen || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + gboolean retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + + return retval; +} + +static gboolean +constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return TRUE; + + /* Determine how much offscreen things are allowed. We first need to + * figure out how much must remain on the screen. For that, we use 25% + * window width/height but clamp to the range of (10,75) pixels. This is + * somewhat of a seat of my pants random guess at what might look good. + * Then, the amount that is allowed off is just the window size minus + * this amount. + */ + int horiz_amount = info->current.width / 4; + int vert_amount = info->current.height / 4; + horiz_amount = CLAMP (horiz_amount, 10, 75); + vert_amount = CLAMP (vert_amount, 10, 75); + horiz_amount = info->current.width - horiz_amount; + vert_amount = info->current.height - vert_amount; + + /* Extend the region, have a helper function handle the constraint, + * then return the region to its original size. + */ + meta_rectangle_expand_region (info->usable_screen_region, + horiz_amount, + horiz_amount, + vert_amount, + vert_amount); + gboolean retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + meta_rectangle_expand_region (info->usable_screen_region, + -horiz_amount, + -horiz_amount, + -vert_amount, + -vert_amount); + + return retval; } diff --git a/src/constraints.h b/src/constraints.h index de8555b27..031b95056 100644 --- a/src/constraints.h +++ b/src/constraints.h @@ -2,6 +2,7 @@ /* * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -28,32 +29,18 @@ typedef enum { - META_RESIZE_LEFT_OR_TOP, - META_RESIZE_CENTER, - META_RESIZE_RIGHT_OR_BOTTOM -} MetaResizeDirection; + META_IS_CONFIGURE_REQUEST = 1 << 0, + META_DO_GRAVITY_ADJUST = 1 << 1, + META_IS_USER_ACTION = 1 << 2, + META_IS_MOVE_ACTION = 1 << 3, + META_IS_RESIZE_ACTION = 1 << 4 +} MetaMoveResizeFlags; void meta_window_constrain (MetaWindow *window, - MetaFrameGeometry *fgeom, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, const MetaRectangle *orig, - int x_move_delta, - int y_move_delta, - MetaResizeDirection x_direction, - int x_delta, - MetaResizeDirection y_direction, - int y_delta, MetaRectangle *new); -MetaResizeDirection meta_x_direction_from_gravity (int gravity); -MetaResizeDirection meta_y_direction_from_gravity (int gravity); - #endif /* META_CONSTRAINTS_H */ - - - - - - - - - diff --git a/src/core.c b/src/core.c index b3f6cd715..b4b6a5f89 100644 --- a/src/core.c +++ b/src/core.c @@ -3,7 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2003 Rob Adams - * Copyright (C) 2004 Elijah Newren + * Copyright (C) 2004, 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -48,6 +48,23 @@ meta_core_get_client_size (Display *xdisplay, *height = window->rect.height; } +gboolean +meta_core_titlebar_is_onscreen (Display *xdisplay, + Window frame_xwindow) +{ + MetaDisplay *display; + MetaWindow *window; + + display = meta_display_for_x_display (xdisplay); + window = meta_display_lookup_x_window (display, frame_xwindow); + + if (window == NULL || window->frame == NULL) + meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); + + return meta_window_titlebar_is_onscreen (window); +} + + Window meta_core_get_client_xwindow (Display *xdisplay, Window frame_xwindow) @@ -371,7 +388,8 @@ meta_core_maximize (Display *xdisplay, if (window == NULL || window->frame == NULL) meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); } void @@ -387,10 +405,12 @@ meta_core_toggle_maximize (Display *xdisplay, if (window == NULL || window->frame == NULL) meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); - if (window->maximized) - meta_window_unmaximize (window); + if (META_WINDOW_MAXIMIZED (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); else - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); } void @@ -406,7 +426,8 @@ meta_core_unmaximize (Display *xdisplay, if (window == NULL || window->frame == NULL) meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); - meta_window_unmaximize (window); + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); } void @@ -691,6 +712,9 @@ meta_core_get_menu_accelerator (MetaMenuOp menu_op, case META_MENU_OP_MOVE_DOWN: name = META_KEYBINDING_MOVE_WORKSPACE_DOWN; break; + case META_MENU_OP_RECOVER: + /* No keybinding for this one */ + break; } if (name) @@ -852,9 +876,9 @@ meta_core_get_screen_size (Display *xdisplay, meta_bug ("No such frame window 0x%lx!\n", frame_on_screen); if (width) - *width = window->screen->width; + *width = window->screen->rect.width; if (height) - *height = window->screen->height; + *height = window->screen->rect.height; } void diff --git a/src/core.h b/src/core.h index 2c7c4e68c..b361550e9 100644 --- a/src/core.h +++ b/src/core.h @@ -2,6 +2,7 @@ /* * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,6 +32,9 @@ void meta_core_get_client_size (Display *xdisplay, int *width, int *height); +gboolean meta_core_titlebar_is_onscreen (Display *xdisplay, + Window frame_xwindow); + Window meta_core_get_client_xwindow (Display *xdisplay, Window frame_xwindow); diff --git a/src/display.c b/src/display.c index 3ede1f1cf..2d2d20f83 100644 --- a/src/display.c +++ b/src/display.c @@ -519,6 +519,8 @@ meta_display_open (const char *name) display->grab_screen = NULL; display->grab_resize_popup = NULL; + display->grab_edge_resistance_data = NULL; + #ifdef HAVE_XSYNC { int major, minor; @@ -1336,6 +1338,15 @@ handle_net_moveresize_window (MetaDisplay* display, if (window) { + /* FIXME!!!! I'm pretty sure this is wrong except _maybe_ for the + * resize-only case; see comment at beginning of + * meta_window_move_resize_internal(). Basically, this should act + * like a configure request--meaning that it should count as an app + * specified change instead of a user one, and the position needs to + * be fixed up with adjust_for_gravity(). In particular, + * meta_window_resize_with_gravity(), meta_window_resize(), and + * meta_window_move_resize() should probably NOT be called. + */ meta_window_get_gravity_position (window, &x, &y); width = window->rect.width; height = window->rect.height; @@ -1391,10 +1402,14 @@ handle_net_restack_window (MetaDisplay* display, if (window) { - /* - * The EWMH includes a sibling for the restack request, but we - * don't currently support these types of raises. + /* FIXME: The EWMH includes a sibling for the restack request, but we + * (stupidly) don't currently support these types of raises. * + * Also, unconditionally following these is REALLY stupid--we should + * combine this code with the stuff in + * meta_window_configure_request() which is smart about whether to + * follow the request or do something else (though not smart enough + * and is also too stupid to handle the sibling stuff). */ switch (event->xclient.data.l[2]) { @@ -1670,36 +1685,49 @@ event_callback (XEvent *event, { if (window->has_resize_func) { - gboolean north; - gboolean west; + gboolean north, south; + gboolean west, east; int root_x, root_y; MetaGrabOp op; meta_window_get_position (window, &root_x, &root_y); - west = event->xbutton.x_root < (root_x + window->rect.width / 2); - north = event->xbutton.y_root < (root_y + window->rect.height / 2); + west = event->xbutton.x_root < (root_x + 1 * window->rect.width / 3); + east = event->xbutton.x_root > (root_x + 2 * window->rect.width / 3); + north = event->xbutton.y_root < (root_y + 1 * window->rect.height / 3); + south = event->xbutton.y_root > (root_y + 2 * window->rect.height / 3); - if (west && north) + if (north && west) op = META_GRAB_OP_RESIZING_NW; - else if (west) - op = META_GRAB_OP_RESIZING_SW; - else if (north) + else if (north && east) op = META_GRAB_OP_RESIZING_NE; - else + else if (south && west) + op = META_GRAB_OP_RESIZING_SW; + else if (south && east) op = META_GRAB_OP_RESIZING_SE; + else if (north) + op = META_GRAB_OP_RESIZING_N; + else if (west) + op = META_GRAB_OP_RESIZING_W; + else if (east) + op = META_GRAB_OP_RESIZING_E; + else if (south) + op = META_GRAB_OP_RESIZING_S; + else /* Middle region is no-op to avoid user triggering wrong action */ + op = META_GRAB_OP_NONE; - meta_display_begin_grab_op (display, - window->screen, - window, - op, - TRUE, - event->xbutton.serial, - event->xbutton.button, - 0, - event->xbutton.time, - event->xbutton.x_root, - event->xbutton.y_root); + if (op != META_GRAB_OP_NONE) + meta_display_begin_grab_op (display, + window->screen, + window, + op, + TRUE, + event->xbutton.serial, + event->xbutton.button, + 0, + event->xbutton.time, + event->xbutton.x_root, + event->xbutton.y_root); } } else if (event->xbutton.button == 3) @@ -3244,6 +3272,7 @@ meta_display_begin_grab_op (MetaDisplay *display, display->grab_old_window_stacking = NULL; #ifdef HAVE_XSYNC display->grab_sync_request_alarm = None; + display->grab_last_user_action_was_snap = FALSE; #endif display->grab_was_cancelled = FALSE; @@ -3335,6 +3364,18 @@ meta_display_begin_grab_op (MetaDisplay *display, g_assert (display->grab_window != NULL || display->grab_screen != NULL); g_assert (display->grab_op != META_GRAB_OP_NONE); + /* If this is a move or resize, cache the window edges for + * resistance/snapping + */ + if (meta_grab_op_is_resizing (display->grab_op) || + meta_grab_op_is_moving (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Computing edges to resist-movement or snap-to for %s.\n", + window->desc); + meta_display_compute_resistance_and_snapping_edges (display); + } + /* Save the old stacking */ if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) { @@ -3408,6 +3449,15 @@ meta_display_end_grab_op (MetaDisplay *display, display->ungrab_should_not_cause_focus_window = display->grab_xwindow; } + /* If this was a move or resize clear out the edge cache */ + if (meta_grab_op_is_resizing (display->grab_op) || + meta_grab_op_is_moving (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Clearing out the edges for resistance/snapping"); + meta_display_cleanup_edges (display); + } + if (display->grab_old_window_stacking != NULL) { meta_topic (META_DEBUG_WINDOW_OPS, @@ -4276,53 +4326,6 @@ meta_resize_gravity_from_grab_op (MetaGrabOp op) return gravity; } -gboolean -meta_rectangle_intersect (MetaRectangle *src1, - MetaRectangle *src2, - MetaRectangle *dest) -{ - int dest_x, dest_y; - int dest_w, dest_h; - int return_val; - - g_return_val_if_fail (src1 != NULL, FALSE); - g_return_val_if_fail (src2 != NULL, FALSE); - g_return_val_if_fail (dest != NULL, FALSE); - - return_val = FALSE; - - dest_x = MAX (src1->x, src2->x); - dest_y = MAX (src1->y, src2->y); - dest_w = MIN (src1->x + src1->width, src2->x + src2->width) - dest_x; - dest_h = MIN (src1->y + src1->height, src2->y + src2->height) - dest_y; - - if (dest_w > 0 && dest_h > 0) - { - dest->x = dest_x; - dest->y = dest_y; - dest->width = dest_w; - dest->height = dest_h; - return_val = TRUE; - } - else - { - dest->width = 0; - dest->height = 0; - } - - return return_val; -} - -gboolean -meta_rectangle_equal (const MetaRectangle *src1, - const MetaRectangle *src2) -{ - return ((src1->x == src2->x) && - (src1->y == src2->y) && - (src1->width == src2->width) && - (src1->height == src2->height)); -} - static MetaScreen* find_screen_for_selection (MetaDisplay *display, Window owner, diff --git a/src/display.h b/src/display.h index 6031533c6..c651c5e97 100644 --- a/src/display.h +++ b/src/display.h @@ -33,6 +33,7 @@ #include #include "eventqueue.h" #include "common.h" +#include "boxes.h" #ifdef HAVE_STARTUP_NOTIFICATION #include @@ -44,17 +45,6 @@ #define meta_XFree(p) do { if ((p)) XFree ((p)); } while (0) -/* this doesn't really belong here, oh well. */ -typedef struct _MetaRectangle MetaRectangle; - -struct _MetaRectangle -{ - int x; - int y; - int width; - int height; -}; - typedef struct MetaCompositor MetaCompositor; typedef struct _MetaDisplay MetaDisplay; typedef struct _MetaFrame MetaFrame; @@ -68,6 +58,8 @@ typedef struct _MetaWorkspace MetaWorkspace; typedef struct _MetaWindowPropHooks MetaWindowPropHooks; typedef struct _MetaGroupPropHooks MetaGroupPropHooks; +typedef struct MetaEdgeResistanceData MetaEdgeResistanceData; + typedef void (* MetaWindowPingFunc) (MetaDisplay *display, Window xwindow, Time timestamp, @@ -280,6 +272,7 @@ struct _MetaDisplay int grab_wireframe_last_display_width; int grab_wireframe_last_display_height; GList* grab_old_window_stacking; + MetaEdgeResistanceData *grab_edge_resistance_data; /* we use property updates as sentinels for certain window focus events * to avoid some race conditions on EnterNotify events @@ -350,6 +343,7 @@ struct _MetaDisplay int render_error_base; #endif #ifdef HAVE_XSYNC + unsigned int grab_last_user_action_was_snap; unsigned int have_xsync : 1; #define META_DISPLAY_HAS_XSYNC(display) ((display)->have_xsync) #else @@ -459,6 +453,10 @@ void meta_display_grab_focus_window_button (MetaDisplay *display, void meta_display_ungrab_focus_window_button (MetaDisplay *display, MetaWindow *window); +/* Next two functions are defined in edge-resistance.c */ +void meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display); +void meta_display_cleanup_edges (MetaDisplay *display); + /* make a request to ensure the event serial has changed */ void meta_display_increment_event_serial (MetaDisplay *display); @@ -490,8 +488,6 @@ typedef enum { META_TAB_LIST_NORMAL, META_TAB_LIST_DOCKS - - } MetaTabList; GList* meta_display_get_tab_list (MetaDisplay *display, @@ -516,12 +512,6 @@ int meta_resize_gravity_from_grab_op (MetaGrabOp op); gboolean meta_grab_op_is_moving (MetaGrabOp op); gboolean meta_grab_op_is_resizing (MetaGrabOp op); -gboolean meta_rectangle_intersect (MetaRectangle *src1, - MetaRectangle *src2, - MetaRectangle *dest); -gboolean meta_rectangle_equal (const MetaRectangle *src1, - const MetaRectangle *src2); - void meta_display_devirtualize_modifiers (MetaDisplay *display, MetaVirtualModifier modifiers, unsigned int *mask); diff --git a/src/edge-resistance.c b/src/edge-resistance.c new file mode 100644 index 000000000..784581d17 --- /dev/null +++ b/src/edge-resistance.c @@ -0,0 +1,1278 @@ +/* Edge resistance for move/resize operations */ + +/* + * Copyright (C) 2005 Elijah Newren + * + * 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 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "edge-resistance.h" +#include "boxes.h" +#include "display.h" +#include "workspace.h" + +/* A simple macro for whether a given window's edges are potentially + * relevant for resistance/snapping during a move/resize operation + */ +#define WINDOW_EDGES_RELEVANT(window, display) \ + meta_window_should_be_showing (window) && \ + window->screen == display->grab_screen && \ + window != display->grab_window && \ + window->type != META_WINDOW_DESKTOP && \ + window->type != META_WINDOW_MENU && \ + window->type != META_WINDOW_SPLASHSCREEN + +struct ResistanceDataForAnEdge +{ + gboolean timeout_setup; + guint timeout_id; + int timeout_edge_pos; + gboolean timeout_over; + GSourceFunc timeout_func; + MetaWindow *window; + int keyboard_buildup; + gboolean allow_past_screen_edge; +}; +typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge; + +struct MetaEdgeResistanceData +{ + GArray *left_edges; + GArray *right_edges; + GArray *top_edges; + GArray *bottom_edges; + + ResistanceDataForAnEdge left_data; + ResistanceDataForAnEdge right_data; + ResistanceDataForAnEdge top_data; + ResistanceDataForAnEdge bottom_data; +}; + +static int +find_index_of_edge_near_position (const GArray *edges, + int position, + gboolean want_interval_min, + gboolean horizontal) +{ + /* This is basically like a binary search, except that we're trying to + * find a range instead of an exact value. So, if we have in our array + * Value: 3 27 316 316 316 505 522 800 1213 + * Index: 0 1 2 3 4 5 6 7 8 + * and we call this function with position=500 & want_interval_min=TRUE + * then we should get 5 (because 505 is the first value bigger than 500). + * If we call this function with position=805 and want_interval_min=FALSE + * then we should get 7 (because 800 is the last value smaller than 800). + * A couple more, to make things clear: + * position want_interval_min correct_answer + * 316 TRUE 2 + * 316 FALSE 4 + * 2 FALSE -1 + * 2000 TRUE 9 + */ + int low, high, mid; + int compare; + MetaEdge *edge; + + /* Initialize mid, edge, & compare in the off change that the array only + * has one element. + */ + mid = 0; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + /* Begin the search... */ + low = 0; + high = edges->len - 1; + while (low < high) + { + mid = low + (high - low)/2; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + if (compare == position) + break; + + if (compare > position) + high = mid - 1; + else + low = mid + 1; + } + + /* mid should now be _really_ close to the index we want, so we start + * linearly searching. However, note that we don't know if mid is less + * than or greater than what we need and it's possible that there are + * several equal values equal to what we were searching for and we ended + * up in the middle of them instead of at the end. So we may need to + * move mid multiple locations over. + */ + if (want_interval_min) + { + while (compare >= position && mid > 0) + { + mid--; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + while (compare < position && mid < (int)edges->len - 1) + { + mid++; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + + /* Special case for no values in array big enough */ + if (compare < position) + return edges->len; + + /* Return the found value */ + return mid; + } + else + { + while (compare <= position && mid < (int)edges->len - 1) + { + mid++; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + while (compare > position && mid > 0) + { + mid--; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + + /* Special case for no values in array small enough */ + if (compare > position) + return -1; + + /* Return the found value */ + return mid; + } +} + +static gboolean +points_on_same_side (int ref, int pt1, int pt2) +{ + return (pt1 - ref) * (pt2 - ref) > 0; +} + +static int +find_nearest_position (const GArray *edges, + int position, + int old_position, + const MetaRectangle *new_rect, + gboolean horizontal, + gboolean only_forward) +{ + /* This is basically just a binary search except that we're looking + * for the value closest to position, rather than finding that + * actual value. Also, we ignore any edges that aren't relevant + * given the horizontal/vertical position of new_rect. + */ + int low, high, mid; + int compare; + MetaEdge *edge; + int best, best_dist, i; + + /* Initialize mid, edge, & compare in the off change that the array only + * has one element. + */ + mid = 0; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + /* Begin the search... */ + low = 0; + high = edges->len - 1; + while (low < high) + { + mid = low + (high - low)/2; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + if (compare == position) + break; + + if (compare > position) + high = mid - 1; + else + low = mid + 1; + } + + /* mid should now be _really_ close to the index we want, so we + * start searching nearby for something that overlaps and is closer + * than the original position. + */ + best = old_position; + best_dist = INT_MAX; + + /* Start the search at mid */ + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + gboolean edges_align = horizontal ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + if (edges_align && + (!only_forward || !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + } + + /* Now start searching higher than mid */ + for (i = mid + 1; i < (int)edges->len; i++) + { + edge = g_array_index (edges, MetaEdge*, i); + compare = horizontal ? edge->rect.x : edge->rect.y; + + gboolean edges_align = horizontal ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + + if (edges_align && + (!only_forward || + !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + break; + } + } + + /* Now start searching lower than mid */ + for (i = mid-1; i >= 0; i--) + { + edge = g_array_index (edges, MetaEdge*, i); + compare = horizontal ? edge->rect.x : edge->rect.y; + + gboolean edges_align = horizontal ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + + if (edges_align && + (!only_forward || + !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + break; + } + } + + /* Return the best one found */ + return best; +} + +static gboolean +movement_towards_edge (MetaDirection side, int increment) +{ + switch (side) + { + case META_DIRECTION_LEFT: + case META_DIRECTION_TOP: + return increment < 0; + case META_DIRECTION_RIGHT: + case META_DIRECTION_BOTTOM: + return increment > 0; + } + + g_assert_not_reached (); +} + +static gboolean +edge_resistance_timeout (gpointer data) +{ + ResistanceDataForAnEdge *resistance_data = data; + + resistance_data->timeout_over = TRUE; + resistance_data->timeout_id = 0; + (*resistance_data->timeout_func)(resistance_data->window); + + return FALSE; +} + +static int +apply_edge_resistance (MetaWindow *window, + int old_pos, + int new_pos, + const MetaRectangle *new_rect, + GArray *edges, + ResistanceDataForAnEdge *resistance_data, + GSourceFunc timeout_func, + gboolean xdir, + gboolean keyboard_op) +{ + int i, begin, end; + gboolean okay_to_clear_keyboard_buildup = FALSE; + int keyboard_buildup_edge = G_MAXINT; + gboolean increasing = new_pos > old_pos; + int increment = increasing ? 1 : -1; + + const int PIXEL_DISTANCE_THRESHOLD_WINDOW = 16; + const int PIXEL_DISTANCE_THRESHOLD_XINERAMA = 32; + const int PIXEL_DISTANCE_THRESHOLD_SCREEN = 32; + const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW = 0; + const int TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA = 100; + const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN = 750; + const int KEYBOARD_BUILDUP_THRESHOLD_WINDOW = 16; + const int KEYBOARD_BUILDUP_THRESHOLD_XINERAMA = 24; + const int KEYBOARD_BUILDUP_THRESHOLD_SCREEN = 32; + + /* Quit if no movement was specified */ + if (old_pos == new_pos) + return new_pos; + + /* Remove the old timeout if it's no longer relevant */ + if (resistance_data->timeout_setup && + ((resistance_data->timeout_edge_pos > old_pos && + resistance_data->timeout_edge_pos > new_pos) || + (resistance_data->timeout_edge_pos < old_pos && + resistance_data->timeout_edge_pos < new_pos))) + { + resistance_data->timeout_setup = FALSE; + if (resistance_data->timeout_id != 0) + { + g_source_remove (resistance_data->timeout_id); + resistance_data->timeout_id = 0; + } + } + + /* Get the range of indices in the edge array that we move past/to. */ + begin = find_index_of_edge_near_position (edges, old_pos, increasing, xdir); + end = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir); + + /* Loop over all these edges we're moving past/to. */ + i = begin; + while ((increasing && i <= end) || + (!increasing && i >= end)) + { + gboolean edges_align; + MetaEdge *edge = g_array_index (edges, MetaEdge*, i); + int compare = xdir ? edge->rect.x : edge->rect.y; + + /* Find out if this edge is relevant */ + edges_align = xdir ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + + /* Nothing to do unless the edges align */ + if (!edges_align) + { + /* Go to the next edge in the range */ + i += increment; + continue; + } + + /* Rest is easier to read if we split on keyboard vs. mouse op */ + if (keyboard_op) + { + /* KEYBOARD ENERGY BUILDUP RESISTANCE: If the user has is moving + * fast enough or has already built up enough "energy", then let + * the user past the edge, otherwise stop at this edge. If the + * user was previously stopped at this edge, add movement amount + * to the built up energy. + */ + + /* First, determine the amount of the resistance */ + int resistance = 0; + switch (edge->edge_type) + { + case META_EDGE_WINDOW: + resistance = KEYBOARD_BUILDUP_THRESHOLD_WINDOW; + break; + case META_EDGE_XINERAMA: + resistance = KEYBOARD_BUILDUP_THRESHOLD_XINERAMA; + break; + case META_EDGE_SCREEN: + resistance = KEYBOARD_BUILDUP_THRESHOLD_SCREEN; + break; + } + + /* Clear any previous buildup if we've run into an edge at a + * different location than what we were building up on before. + * See below for more details where these get set. + */ + if (okay_to_clear_keyboard_buildup && + compare != keyboard_buildup_edge) + { + okay_to_clear_keyboard_buildup = FALSE; + resistance_data->keyboard_buildup = 0; + } + + /* Determine the threshold */ + int threshold = resistance - resistance_data->keyboard_buildup; + + /* See if threshold hasn't been met yet or not */ + if (ABS (compare - new_pos) < threshold) + { + if (resistance_data->keyboard_buildup != 0) + resistance_data->keyboard_buildup += ABS (new_pos - compare); + else + resistance_data->keyboard_buildup = 1; /* 0 causes stuckage */ + return compare; + } + else + { + /* It may be the case that there are two windows with edges + * at the same location. If so, the buildup ought to count + * towards both edges. So we just not that it's okay to + * clear the buildup once we find an edge at a different + * location. + */ + okay_to_clear_keyboard_buildup = TRUE; + keyboard_buildup_edge = compare; + } + } + else /* mouse op */ + { + /* INFINITE RESISTANCE for screen edges under certain cases; If + * the edge is relevant and we're moving towards it and it's a + * screen edge and infinite resistance has been requested for + * this particular grab op then don't allow movement past it. + */ + if (edge->edge_type == META_EDGE_SCREEN && + !resistance_data->allow_past_screen_edge && + movement_towards_edge (edge->side_type, increment)) + { + return compare; + } + + /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving + * towards it, then we may want to have some kind of time delay + * before the user can move past this edge. + */ + if (movement_towards_edge (edge->side_type, increment)) + { + /* First, determine the length of time for the resistance */ + int timeout_length_ms = 0; + switch (edge->edge_type) + { + case META_EDGE_WINDOW: + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW; + break; + case META_EDGE_XINERAMA: + if (window->require_on_single_xinerama) + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA; + break; + case META_EDGE_SCREEN: + if (window->require_fully_onscreen) + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN; + break; + } + + if (!resistance_data->timeout_setup && + timeout_length_ms != 0) + { + resistance_data->timeout_id = + g_timeout_add (timeout_length_ms, + edge_resistance_timeout, + resistance_data); + resistance_data->timeout_setup = TRUE; + resistance_data->timeout_edge_pos = compare; + resistance_data->timeout_over = FALSE; + resistance_data->timeout_func = timeout_func; + resistance_data->window = window; + } + if (!resistance_data->timeout_over && + timeout_length_ms != 0) + return compare; + } + + /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the + * user hasn't moved at least threshold pixels past this edge, + * stop movement at this edge. (Note that this is different from + * keyboard resistance precisely because keyboard move ops are + * relative to previous positions, whereas mouse move ops are + * relative to differences in mouse position and mouse position + * is an absolute quantity rather than a relative quantity) + */ + + /* First, determine the threshold */ + int threshold = 0; + switch (edge->edge_type) + { + case META_EDGE_WINDOW: + threshold = PIXEL_DISTANCE_THRESHOLD_WINDOW; + break; + case META_EDGE_XINERAMA: + threshold = PIXEL_DISTANCE_THRESHOLD_XINERAMA; + break; + case META_EDGE_SCREEN: + threshold = PIXEL_DISTANCE_THRESHOLD_SCREEN; + break; + } + + if (ABS (compare - new_pos) < threshold) + return compare; + } + + /* Go to the next edge in the range */ + i += increment; + } + + /* If we didn't run into any new edges in keyboard buildup but had moved + * far enough to get past the last one, clear the buildup + */ + if (okay_to_clear_keyboard_buildup && new_pos != keyboard_buildup_edge) + resistance_data->keyboard_buildup = 0; + + return new_pos; +} + +static int +apply_edge_snapping (int old_pos, + int new_pos, + const MetaRectangle *new_rect, + GArray *edges1, + GArray *edges2, + gboolean xdir, + gboolean keyboard_op) +{ + int pos1, pos2; + int best; + + if (old_pos == new_pos) + return new_pos; + + /* We look at two sets of edges (e.g. left and right) individually + * finding the nearest position among each set of edges and then later + * finding the better of these two bests. + */ + pos1 = find_nearest_position (edges1, + new_pos, + old_pos, + new_rect, + xdir, + keyboard_op); + pos2 = find_nearest_position (edges2, + new_pos, + old_pos, + new_rect, + xdir, + keyboard_op); + + /* For keyboard snapping, ignore either pos1 or pos2 if they aren't in the + * right direction. + */ + if (keyboard_op) + { + if (!points_on_same_side (old_pos, pos1, new_pos)) + return pos2; + if (!points_on_same_side (old_pos, pos2, new_pos)) + return pos1; + } + + /* Find the better of pos1 and pos2 and return it */ + if (ABS (pos1 - new_pos) < ABS (pos2 - new_pos)) + best = pos1; + else + best = pos2; + + /* If mouse snap-moving, the user could easily accidentally move just a + * couple pixels in a direction they didn't mean to move; so ignore snap + * movement in those cases unless it's only a small number of pixels + * anyway. + */ + if (!keyboard_op && + ABS (best - old_pos) >= 8 && + ABS (new_pos - old_pos) < 8) + return old_pos; + else + /* Otherwise, return the best of the snapping positions found */ + return best; +} + +/* This function takes the position (including any frame) of the window and + * a proposed new position (ignoring edge resistance/snapping), and then + * applies edge resistance to EACH edge (separately) updating new_outer. + * It returns true if new_outer is modified, false otherwise. + * + * display->grab_edge_resistance_data MUST already be setup or calling this + * function will cause a crash. + */ +static gboolean +apply_edge_resistance_to_each_side (MetaDisplay *display, + MetaWindow *window, + const MetaRectangle *old_outer, + MetaRectangle *new_outer, + GSourceFunc timeout_func, + gboolean auto_snap, + gboolean keyboard_op) +{ + MetaEdgeResistanceData *edge_data; + MetaRectangle modified_rect; + gboolean modified; + int new_left, new_right, new_top, new_bottom; + + g_assert (display->grab_edge_resistance_data != NULL); + edge_data = display->grab_edge_resistance_data; + + if (auto_snap) + { + /* Do the auto snapping instead of normal edge resistance; in all + * cases, we allow snapping to opposite kinds of edges (e.g. left + * sides of windows to both left and right edges. + */ + + new_left = apply_edge_snapping (BOX_LEFT (*old_outer), + BOX_LEFT (*new_outer), + new_outer, + edge_data->left_edges, + edge_data->right_edges, + TRUE, + keyboard_op); + + new_right = apply_edge_snapping (BOX_RIGHT (*old_outer), + BOX_RIGHT (*new_outer), + new_outer, + edge_data->left_edges, + edge_data->right_edges, + TRUE, + keyboard_op); + + new_top = apply_edge_snapping (BOX_TOP (*old_outer), + BOX_TOP (*new_outer), + new_outer, + edge_data->top_edges, + edge_data->bottom_edges, + FALSE, + keyboard_op); + + new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer), + BOX_BOTTOM (*new_outer), + new_outer, + edge_data->top_edges, + edge_data->bottom_edges, + FALSE, + keyboard_op); + } + else + { + /* Now, apply the normal edge resistance */ + new_left = apply_edge_resistance (window, + BOX_LEFT (*old_outer), + BOX_LEFT (*new_outer), + new_outer, + edge_data->left_edges, + &edge_data->left_data, + timeout_func, + TRUE, + keyboard_op); + new_right = apply_edge_resistance (window, + BOX_RIGHT (*old_outer), + BOX_RIGHT (*new_outer), + new_outer, + edge_data->right_edges, + &edge_data->right_data, + timeout_func, + TRUE, + keyboard_op); + new_top = apply_edge_resistance (window, + BOX_TOP (*old_outer), + BOX_TOP (*new_outer), + new_outer, + edge_data->top_edges, + &edge_data->top_data, + timeout_func, + FALSE, + keyboard_op); + new_bottom = apply_edge_resistance (window, + BOX_BOTTOM (*old_outer), + BOX_BOTTOM (*new_outer), + new_outer, + edge_data->bottom_edges, + &edge_data->bottom_data, + timeout_func, + FALSE, + keyboard_op); + } + + /* Determine whether anything changed, and save the changes */ + modified_rect = meta_rect (new_left, + new_top, + new_right - new_left, + new_bottom - new_top); + modified = !meta_rectangle_equal (new_outer, &modified_rect); + *new_outer = modified_rect; + return modified; +} + +void +meta_display_cleanup_edges (MetaDisplay *display) +{ + MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; + g_assert (edge_data != NULL); + guint i,j; + + /* We first need to clean out any window edges */ + for (i = 0; i < 4; i++) + { + GArray *tmp = NULL; + switch (i) + { + case 0: + tmp = edge_data->left_edges; + break; + case 1: + tmp = edge_data->right_edges; + break; + case 2: + tmp = edge_data->top_edges; + break; + case 3: + tmp = edge_data->bottom_edges; + break; + default: + g_assert_not_reached (); + } + + for (j = 0; j < tmp->len; j++) + { + MetaEdge *edge = g_array_index (tmp, MetaEdge*, j); + if (edge->edge_type == META_EDGE_WINDOW) + g_free (edge); + } + } + + /* Now free the arrays and data */ + g_array_free (edge_data->left_edges, TRUE); + g_array_free (edge_data->right_edges, TRUE); + g_array_free (edge_data->top_edges, TRUE); + g_array_free (edge_data->bottom_edges, TRUE); + + /* Cleanup the timeouts */ + if (edge_data->left_data.timeout_setup && + edge_data->left_data.timeout_id != 0) + g_source_remove (edge_data->left_data.timeout_id); + if (edge_data->right_data.timeout_setup && + edge_data->right_data.timeout_id != 0) + g_source_remove (edge_data->right_data.timeout_id); + if (edge_data->top_data.timeout_setup && + edge_data->top_data.timeout_id != 0) + g_source_remove (edge_data->top_data.timeout_id); + if (edge_data->bottom_data.timeout_setup && + edge_data->bottom_data.timeout_id != 0) + g_source_remove (edge_data->bottom_data.timeout_id); + + g_free (display->grab_edge_resistance_data); + display->grab_edge_resistance_data = NULL; +} + +static int +stupid_sort_requiring_extra_pointer_dereference (gconstpointer a, + gconstpointer b) +{ + const MetaEdge * const *a_edge = a; + const MetaEdge * const *b_edge = b; + return meta_rectangle_edge_cmp (*a_edge, *b_edge); +} + +static void +cache_edges (MetaDisplay *display, + GList *window_edges, + GList *xinerama_edges, + GList *screen_edges) +{ + MetaEdgeResistanceData *edge_data; + GList *tmp; + int num_left, num_right, num_top, num_bottom; + int i; + + /* + * 1st: Get the total number of each kind of edge + */ + num_left = num_right = num_top = num_bottom = 0; + for (i = 0; i < 3; i++) + { + tmp = NULL; + switch (i) + { + case 0: + tmp = window_edges; + break; + case 1: + tmp = xinerama_edges; + break; + case 2: + tmp = screen_edges; + break; + default: + g_assert_not_reached (); + } + + while (tmp) + { + MetaEdge *edge = tmp->data; + switch (edge->side_type) + { + case META_DIRECTION_LEFT: + num_left++; + break; + case META_DIRECTION_RIGHT: + num_right++; + break; + case META_DIRECTION_TOP: + num_top++; + break; + case META_DIRECTION_BOTTOM: + num_bottom++; + break; + default: + g_assert_not_reached (); + } + tmp = tmp->next; + } + } + + /* + * 2nd: Allocate the edges + */ + g_assert (display->grab_edge_resistance_data == NULL); + display->grab_edge_resistance_data = g_new (MetaEdgeResistanceData, 1); + edge_data = display->grab_edge_resistance_data; + edge_data->left_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_left); + edge_data->right_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_right); + edge_data->top_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_top); + edge_data->bottom_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_bottom); + + /* + * 3rd: Add the edges to the arrays + */ + num_left = num_right = num_top = num_bottom = 0; + for (i = 0; i < 3; i++) + { + tmp = NULL; + switch (i) + { + case 0: + tmp = window_edges; + break; + case 1: + tmp = xinerama_edges; + break; + case 2: + tmp = screen_edges; + break; + default: + g_assert_not_reached (); + } + + while (tmp) + { + MetaEdge *edge = tmp->data; + switch (edge->side_type) + { + case META_DIRECTION_LEFT: + g_array_append_val (edge_data->left_edges, edge); + break; + case META_DIRECTION_RIGHT: + g_array_append_val (edge_data->right_edges, edge); + break; + case META_DIRECTION_TOP: + g_array_append_val (edge_data->top_edges, edge); + break; + case META_DIRECTION_BOTTOM: + g_array_append_val (edge_data->bottom_edges, edge); + break; + default: + g_assert_not_reached (); + } + tmp = tmp->next; + } + } + + /* + * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were + * individually sorted earlier and we could have done this faster and + * avoided this sort by sticking them into the array with some simple + * merging of the lists). + */ + g_array_sort (display->grab_edge_resistance_data->left_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->right_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->top_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->bottom_edges, + stupid_sort_requiring_extra_pointer_dereference); +} + +static void +initialize_grab_edge_resistance_data (MetaDisplay *display) +{ + MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; + + edge_data->left_data.timeout_setup = FALSE; + edge_data->right_data.timeout_setup = FALSE; + edge_data->top_data.timeout_setup = FALSE; + edge_data->bottom_data.timeout_setup = FALSE; + + edge_data->left_data.keyboard_buildup = 0; + edge_data->right_data.keyboard_buildup = 0; + edge_data->top_data.keyboard_buildup = 0; + edge_data->bottom_data.keyboard_buildup = 0; + + edge_data->left_data.allow_past_screen_edge = TRUE; + edge_data->right_data.allow_past_screen_edge = TRUE; + edge_data->bottom_data.allow_past_screen_edge = TRUE; + edge_data->top_data.allow_past_screen_edge = + display->grab_anchor_root_y >= display->grab_initial_window_pos.y; +} + +void +meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display) +{ + GList *stacked_windows; + GList *cur_window_iter; + GList *edges; + /* Lists of window positions (rects) and their relative stacking positions */ + int stack_position; + GSList *obscuring_windows, *window_stacking; + /* The portions of the above lists that still remain at the stacking position + * in the layer that we are working on + */ + GSList *rem_windows, *rem_win_stacking; + + /* + * 1st: Get the list of relevant windows, from bottom to top + */ + stacked_windows = + meta_stack_list_windows (display->grab_screen->stack, + display->grab_screen->active_workspace); + + /* + * 2nd: we need to separate that stacked list into a list of windows that + * can obscure other edges. To make sure we only have windows obscuring + * those below it instead of going both ways, we also need to keep a + * counter list. Messy, I know. + */ + obscuring_windows = window_stacking = NULL; + cur_window_iter = stacked_windows; + stack_position = 0; + while (cur_window_iter != NULL) + { + MetaWindow *cur_window = cur_window_iter->data; + if (WINDOW_EDGES_RELEVANT (cur_window, display)) + { + MetaRectangle *new_rect; + new_rect = g_new (MetaRectangle, 1); + meta_window_get_outer_rect (cur_window, new_rect); + obscuring_windows = g_slist_prepend (obscuring_windows, new_rect); + window_stacking = + g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position)); + } + + stack_position++; + cur_window_iter = cur_window_iter->next; + } + /* Put 'em in bottom to top order */ + rem_windows = g_slist_reverse (obscuring_windows); + rem_win_stacking = g_slist_reverse (window_stacking); + + /* + * 3rd: loop over the windows again, this time getting the edges from + * them and removing intersections with the relevant obscuring_windows & + * obscuring_docks. + */ + edges = NULL; + stack_position = 0; + cur_window_iter = stacked_windows; + while (cur_window_iter != NULL) + { + MetaRectangle cur_rect; + MetaWindow *cur_window = cur_window_iter->data; + meta_window_get_outer_rect (cur_window, &cur_rect); + + /* Check if we want to use this window's edges for edge + * resistance (note that dock edges are considered screen edges + * which are handled separately + */ + if (WINDOW_EDGES_RELEVANT (cur_window, display) && + cur_window->type != META_WINDOW_DOCK) + { + GList *new_edges; + MetaEdge *new_edge; + MetaRectangle reduced; + + /* We don't care about snapping to any portion of the window that + * is offscreen (we also don't care about parts of edges covered + * by other windows or DOCKS, but that's handled below). + */ + meta_rectangle_intersect (&cur_rect, + &display->grab_screen->rect, + &reduced); + + new_edges = NULL; + + /* Left side of this window is resistance for the right edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.width = 0; + new_edge->side_type = META_DIRECTION_RIGHT; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Right side of this window is resistance for the left edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.x += new_edge->rect.width; + new_edge->rect.width = 0; + new_edge->side_type = META_DIRECTION_LEFT; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Top side of this window is resistance for the bottom edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.height = 0; + new_edge->side_type = META_DIRECTION_BOTTOM; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Top side of this window is resistance for the bottom edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.y += new_edge->rect.height; + new_edge->rect.height = 0; + new_edge->side_type = META_DIRECTION_TOP; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Update the remaining windows to only those at a higher + * stacking position than this one. + */ + while (rem_win_stacking && + stack_position >= (int)rem_win_stacking->data) + { + rem_windows = rem_windows->next; + rem_win_stacking = rem_win_stacking->next; + } + + /* Remove edge portions overlapped by rem_windows and rem_docks */ + new_edges = + meta_rectangle_remove_intersections_with_boxes_from_edges ( + new_edges, + rem_windows); + + /* Save the new edges */ + edges = g_list_concat (new_edges, edges); + } + + stack_position++; + cur_window_iter = cur_window_iter->next; + } + + /* + * 4th: Free the extra memory not needed and sort the list + */ + /* Free the memory used by the obscuring windows/docks lists */ + g_slist_free (window_stacking); + /* FIXME: Shouldn't there be a helper function to make this one line of code + * to free a list instead of four ugly ones? + */ + g_slist_foreach (obscuring_windows, + (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ + NULL); + g_slist_free (obscuring_windows); + + /* Sort the list. FIXME: Should I bother with this sorting? I just + * sort again later in cache_edges() anyway... + */ + edges = g_list_sort (edges, meta_rectangle_edge_cmp); + + /* + * 5th: Cache the combination of these edges with the onscreen and + * xinerama edges in an array for quick access. Free the edges since + * they've been cached elsewhere. + */ + cache_edges (display, + edges, + display->grab_screen->active_workspace->xinerama_edges, + display->grab_screen->active_workspace->screen_edges); + g_list_free (edges); + + /* + * 6th: Initialize the resistance timeouts and buildups + */ + initialize_grab_edge_resistance_data (display); +} + +/* Note that old_[xy] and new_[xy] are with respect to inner positions of + * the window. + */ +void +meta_window_edge_resistance_for_move (MetaWindow *window, + int old_x, + int old_y, + int *new_x, + int *new_y, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op) +{ + MetaRectangle old_outer, proposed_outer, new_outer; + + if (window == window->display->grab_window && + window->display->grab_wireframe_active) + { + meta_window_get_xor_rect (window, + &window->display->grab_wireframe_rect, + &old_outer); + } + else + { + meta_window_get_outer_rect (window, &old_outer); + } + proposed_outer = old_outer; + proposed_outer.x += (*new_x - old_x); + proposed_outer.y += (*new_y - old_y); + new_outer = proposed_outer; + + window->display->grab_last_user_action_was_snap = snap; + if (apply_edge_resistance_to_each_side (window->display, + window, + &old_outer, + &new_outer, + timeout_func, + snap, + is_keyboard_op)) + { + /* apply_edge_resistance_to_each_side independently applies + * resistance to both the right and left edges of new_outer as both + * could meet areas of resistance. But we don't want a resize, so we + * just have both edges move according to the stricter of the + * resistances. Same thing goes for top & bottom edges. + */ + MetaRectangle *reference; + int left_change, right_change, smaller_x_change; + int top_change, bottom_change, smaller_y_change; + + if (snap && !is_keyboard_op) + reference = &proposed_outer; + else + reference = &old_outer; + + left_change = BOX_LEFT (new_outer) - BOX_LEFT (*reference); + right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference); + if ( snap && is_keyboard_op && left_change == 0) + smaller_x_change = right_change; + else if (snap && is_keyboard_op && right_change == 0) + smaller_x_change = left_change; + else if (ABS (left_change) < ABS (right_change)) + smaller_x_change = left_change; + else + smaller_x_change = right_change; + + top_change = BOX_TOP (new_outer) - BOX_TOP (*reference); + bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference); + if ( snap && is_keyboard_op && top_change == 0) + smaller_y_change = bottom_change; + else if (snap && is_keyboard_op && bottom_change == 0) + smaller_y_change = top_change; + else if (ABS (top_change) < ABS (bottom_change)) + smaller_y_change = top_change; + else + smaller_y_change = bottom_change; + + *new_x = old_x + smaller_x_change + + (BOX_LEFT (*reference) - BOX_LEFT (old_outer)); + *new_y = old_y + smaller_y_change + + (BOX_TOP (*reference) - BOX_TOP (old_outer)); + } +} + +/* Note that old_(width|height) and new_(width|height) are with respect to + * sizes of the inner window. + */ +void +meta_window_edge_resistance_for_resize (MetaWindow *window, + int old_width, + int old_height, + int *new_width, + int *new_height, + int gravity, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op) +{ + MetaRectangle old_outer, new_outer; + int new_outer_width, new_outer_height; + + if (window == window->display->grab_window && + window->display->grab_wireframe_active) + { + meta_window_get_xor_rect (window, + &window->display->grab_wireframe_rect, + &old_outer); + } + else + { + meta_window_get_outer_rect (window, &old_outer); + } + new_outer_width = old_outer.width + (*new_width - old_width); + new_outer_height = old_outer.height + (*new_height - old_height); + meta_rectangle_resize_with_gravity (&old_outer, + &new_outer, + gravity, + new_outer_width, + new_outer_height); + + window->display->grab_last_user_action_was_snap = snap; + if (apply_edge_resistance_to_each_side (window->display, + window, + &old_outer, + &new_outer, + timeout_func, + snap, + is_keyboard_op)) + { + *new_width = old_width + (new_outer.width - old_outer.width); + *new_height = old_height + (new_outer.height - old_outer.height); + } +} diff --git a/src/edge-resistance.h b/src/edge-resistance.h new file mode 100644 index 000000000..f327c83ed --- /dev/null +++ b/src/edge-resistance.h @@ -0,0 +1,46 @@ +/* Edge resistance for move/resize operations */ + +/* + * Copyright (C) 2005 Elijah Newren + * + * 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 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_EDGE_RESISTANCE_H +#define META_EDGE_RESISTANCE_H + +#include "window.h" + +void meta_window_edge_resistance_for_move (MetaWindow *window, + int old_x, + int old_y, + int *new_x, + int *new_y, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op); +void meta_window_edge_resistance_for_resize (MetaWindow *window, + int old_width, + int old_height, + int *new_width, + int *new_height, + int gravity, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op); + +#endif /* META_EDGE_RESISTANCE_H */ + diff --git a/src/frame.c b/src/frame.c index 76e629666..445f89c76 100644 --- a/src/frame.c +++ b/src/frame.c @@ -3,6 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2003, 2004 Red Hat, Inc. + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -264,7 +265,10 @@ meta_frame_get_flags (MetaFrame *frame) if (frame->window->on_all_workspaces) flags |= META_FRAME_STUCK; - if (frame->window->maximized) + /* FIXME: Should we have some kind of UI for windows that are just vertically + * maximized or just horizontally maximized? + */ + if (META_WINDOW_MAXIMIZED (frame->window)) flags |= META_FRAME_MAXIMIZED; if (frame->window->fullscreen) diff --git a/src/frames.c b/src/frames.c index 03e11d3d5..c5f1b9c9f 100644 --- a/src/frames.c +++ b/src/frames.c @@ -3,6 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -1441,16 +1442,25 @@ meta_frames_button_press_event (GtkWidget *widget, break; } - meta_core_begin_grab_op (gdk_display, - frame->xwindow, - op, - TRUE, - meta_ui_get_last_event_serial (gdk_display), - event->button, - 0, - event->time, - event->x_root, - event->y_root); + if (!meta_core_titlebar_is_onscreen (gdk_display, + frame->xwindow)) + meta_core_show_window_menu (gdk_display, + frame->xwindow, + event->x_root, + event->y_root, + event->button, + event->time); + else + meta_core_begin_grab_op (gdk_display, + frame->xwindow, + op, + TRUE, + meta_ui_get_last_event_serial (gdk_display), + event->button, + 0, + event->time, + event->x_root, + event->y_root); } else if (control == META_FRAME_CONTROL_TITLE && event->button == 1) diff --git a/src/keybindings.c b/src/keybindings.c index be448c2db..e1872a0e3 100644 --- a/src/keybindings.c +++ b/src/keybindings.c @@ -26,6 +26,7 @@ #include "keybindings.h" #include "workspace.h" #include "errors.h" +#include "edge-resistance.h" #include "ui.h" #include "frame.h" #include "place.h" @@ -1690,8 +1691,6 @@ process_keyboard_move_grab (MetaDisplay *display, int x, y; int incr; gboolean smart_snap; - int edge; - int candidate_position; handled = FALSE; @@ -1719,7 +1718,7 @@ process_keyboard_move_grab (MetaDisplay *display, #define NORMAL_INCREMENT 10 if (smart_snap) - incr = 0; + incr = 1; else if (event->xkey.state & ControlMask) incr = SMALL_INCREMENT; else @@ -1754,18 +1753,6 @@ process_keyboard_move_grab (MetaDisplay *display, case XK_Up: case XK_KP_Up: y -= incr; - - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_TOP, - FALSE); - if (window->frame) - candidate_position = edge + window->frame->child_y; - else - candidate_position = edge; - - if (smart_snap || ((candidate_position > y) && ABS (candidate_position - y) < incr)) - y = candidate_position; - handled = TRUE; break; case XK_KP_End: @@ -1773,18 +1760,6 @@ process_keyboard_move_grab (MetaDisplay *display, case XK_Down: case XK_KP_Down: y += incr; - - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_BOTTOM, - TRUE); - if (window->frame) - candidate_position = edge - window->frame->bottom_height - window->rect.height; - else - candidate_position = edge - window->rect.height; - - if (smart_snap || ((candidate_position < y) && ABS (candidate_position - y) < incr)) - y = candidate_position; - handled = TRUE; break; } @@ -1796,19 +1771,6 @@ process_keyboard_move_grab (MetaDisplay *display, case XK_Left: case XK_KP_Left: x -= incr; - - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_LEFT, - FALSE); - if (window->frame) - candidate_position = edge + window->frame->child_x; - else - candidate_position = edge; - - if (smart_snap || - ((candidate_position > x) && ABS (candidate_position - x) < incr)) - x = candidate_position; - handled = TRUE; break; case XK_KP_Prior: @@ -1816,27 +1778,34 @@ process_keyboard_move_grab (MetaDisplay *display, case XK_Right: case XK_KP_Right: x += incr; - - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_RIGHT, - TRUE); - if (window->frame) - candidate_position = edge - window->frame->right_width - window->rect.width; - else - candidate_position = edge - window->rect.width; - - if (smart_snap || ((candidate_position < x) && ABS (candidate_position - x) < incr)) - x = candidate_position; - handled = TRUE; break; } if (handled) { + MetaRectangle old_rect; meta_topic (META_DEBUG_KEYBINDINGS, "Computed new window location %d,%d due to keypress\n", x, y); + + if (display->grab_wireframe_active) + old_rect = display->grab_wireframe_rect; + else + { + old_rect = window->rect; + meta_window_get_position (window, &old_rect.x, &old_rect.y); + } + + meta_window_edge_resistance_for_move (window, + old_rect.x, + old_rect.y, + &x, + &y, + NULL, + smart_snap, + TRUE); + if (display->grab_wireframe_active) { meta_window_update_wireframe (window, x, y, @@ -1986,13 +1955,9 @@ process_keyboard_resize_grab (MetaDisplay *display, gboolean handled; int height_inc; int width_inc; - int x, y; - int orig_x, orig_y; int width, height; gboolean smart_snap; - int edge; int gravity; - int candidate_position; handled = FALSE; @@ -2029,21 +1994,15 @@ process_keyboard_resize_grab (MetaDisplay *display, if (display->grab_wireframe_active) { - orig_x = display->grab_wireframe_rect.x; - orig_y = display->grab_wireframe_rect.y; width = display->grab_wireframe_rect.width; height = display->grab_wireframe_rect.height; } else { - meta_window_get_position (window, &orig_x, &orig_y); width = window->rect.width; height = window->rect.height; } - x = orig_x; - y = orig_y; - gravity = meta_resize_gravity_from_grab_op (display->grab_op); smart_snap = (event->xkey.state & ShiftMask) != 0; @@ -2053,40 +2012,28 @@ process_keyboard_resize_grab (MetaDisplay *display, if (smart_snap) { - height_inc = 0; - width_inc = 0; + height_inc = 1; + width_inc = 1; } else if (event->xkey.state & ControlMask) { - if (window->size_hints.width_inc > 1) - width_inc = window->size_hints.width_inc; - else - width_inc = SMALL_INCREMENT; - - if (window->size_hints.height_inc > 1) - height_inc = window->size_hints.height_inc; - else - height_inc = SMALL_INCREMENT; + width_inc = SMALL_INCREMENT; + height_inc = SMALL_INCREMENT; } else { - if (window->size_hints.width_inc > 1) - width_inc = window->size_hints.width_inc; - else - width_inc = NORMAL_INCREMENT; - - if (window->size_hints.height_inc > 1) - height_inc = window->size_hints.height_inc; - else - height_inc = NORMAL_INCREMENT; + width_inc = NORMAL_INCREMENT; + height_inc = NORMAL_INCREMENT; } - - /* When moving by increments, we still snap to edges if the move - * to the edge is smaller than the increment. This is because - * Shift + arrow to snap is sort of a hidden feature. This way - * people using just arrows shouldn't get too frustrated. + + /* If this is a resize increment window, make the amount we resize + * the window by match that amount (well, unless snap resizing...) */ - + if (window->size_hints.width_inc > 1) + width_inc = window->size_hints.width_inc; + if (window->size_hints.height_inc > 1) + height_inc = window->size_hints.height_inc; + switch (keysym) { case XK_Up: @@ -2097,49 +2044,14 @@ process_keyboard_resize_grab (MetaDisplay *display, case NorthWestGravity: case NorthEastGravity: /* Move bottom edge up */ - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_BOTTOM, - FALSE); - - if (window->frame) - candidate_position = edge - window->frame->bottom_height; - else - candidate_position = edge; - - if (smart_snap || - ((candidate_position > (y + (height - height_inc))) && - ABS (candidate_position - (y + (height - height_inc))) < height_inc)) - { - if (candidate_position - y > 0) - height = candidate_position - y; - } - else if (height - height_inc > 0) - { - height -= height_inc; - } - - handled = TRUE; + height -= height_inc; break; case SouthGravity: case SouthWestGravity: case SouthEastGravity: /* Move top edge up */ - y -= height_inc; - - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_TOP, - FALSE); - - if (window->frame) - candidate_position = edge + window->frame->child_y; - else - candidate_position = edge; - - if (smart_snap || ((candidate_position > y) && ABS (candidate_position - y) < height_inc)) - y = candidate_position; - - height += (orig_y - y); + height += height_inc; break; case EastGravity: @@ -2161,49 +2073,13 @@ process_keyboard_resize_grab (MetaDisplay *display, case NorthEastGravity: /* Move bottom edge down */ height += height_inc; - - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_BOTTOM, - TRUE); - - if (window->frame) - candidate_position = edge - window->frame->bottom_height; - else - candidate_position = edge; - - if (smart_snap || ((candidate_position < (y+height)) && - ABS (candidate_position - (y+height)) < height_inc)) - height = candidate_position - y; break; case SouthGravity: case SouthWestGravity: case SouthEastGravity: /* Move top edge down */ - edge = meta_window_find_next_horizontal_edge (window, - META_WINDOW_EDGE_TOP, - TRUE); - - if (window->frame) - candidate_position = edge + window->frame->child_y; - else - candidate_position = edge; - - if (smart_snap || - ((candidate_position < (y + height_inc)) && - ABS (candidate_position - (y + height_inc)) < height_inc)) - { - if (height - (candidate_position - orig_y) > 0) - { - y = candidate_position; - height -= (y - orig_y); - } - } - else if (height - ((y + height_inc) - orig_y) > 0) - { - y += height_inc; - height -= (y - orig_y); - } + height -= height_inc; break; case EastGravity: @@ -2223,50 +2099,15 @@ process_keyboard_resize_grab (MetaDisplay *display, case EastGravity: case SouthEastGravity: case NorthEastGravity: - x -= width_inc; - /* Move left edge left */ - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_LEFT, - FALSE); - - if (window->frame) - candidate_position = edge + window->frame->child_x; - else - candidate_position = edge; - - if (smart_snap || ((candidate_position > x) && ABS (candidate_position - x) < width_inc)) - x = candidate_position; - - width += (orig_x - x); + width += width_inc; break; case WestGravity: case SouthWestGravity: case NorthWestGravity: /* Move right edge left */ - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_RIGHT, - FALSE); - - if (window->frame) - candidate_position = edge - window->frame->right_width; - else - candidate_position = edge; - - if (smart_snap || - ((candidate_position > (x + (width - width_inc))) && - ABS (candidate_position - (x + (width - width_inc))) < width_inc)) - { - if (candidate_position - x > 0) - width = candidate_position - x; - } - else if (width - width_inc > 0) - { - width -= width_inc; - } - - handled = TRUE; + width -= width_inc; break; case NorthGravity: @@ -2287,30 +2128,7 @@ process_keyboard_resize_grab (MetaDisplay *display, case SouthEastGravity: case NorthEastGravity: /* Move left edge right */ - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_LEFT, - TRUE); - - if (window->frame) - candidate_position = edge + window->frame->child_x; - else - candidate_position = edge; - - if (smart_snap || - ((candidate_position < (x + width_inc)) && - ABS (candidate_position - (x + width_inc)) < width_inc)) - { - if (width - (candidate_position - orig_x) > 0) - { - x = candidate_position; - width -= (x - orig_x); - } - } - else if (width - ((x + width_inc) - orig_x) > 0) - { - x += width_inc; - width -= (x - orig_x); - } + width -= width_inc; break; case WestGravity: @@ -2318,21 +2136,6 @@ process_keyboard_resize_grab (MetaDisplay *display, case NorthWestGravity: /* Move right edge right */ width += width_inc; - - edge = meta_window_find_next_vertical_edge (window, - META_WINDOW_EDGE_RIGHT, - TRUE); - - if (window->frame) - candidate_position = edge - window->frame->right_width; - else - candidate_position = edge; - - if (smart_snap || ((candidate_position > (x+width)) && - ABS (candidate_position - (x+width)) < width_inc)) - width = candidate_position - x; - - handled = TRUE; break; case NorthGravity: @@ -2357,17 +2160,53 @@ process_keyboard_resize_grab (MetaDisplay *display, if (handled) { + MetaRectangle old_rect; meta_topic (META_DEBUG_KEYBINDINGS, - "Computed new window location %d,%d %dx%d due to keypress\n", - x, y, width, height); + "Computed new window size due to keypress: " + "%dx%d, gravity %s\n", + width, height, meta_gravity_to_string (gravity)); + if (display->grab_wireframe_active) + old_rect = display->grab_wireframe_rect; + else + old_rect = window->rect; /* Don't actually care about x,y */ + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_resize (window, + old_rect.width, + old_rect.height, + &width, + &height, + gravity, + NULL, + smart_snap, + TRUE); + if (display->grab_wireframe_active) { - meta_window_update_wireframe (window, x, y, width, height); + MetaRectangle new_position; + meta_rectangle_resize_with_gravity (&display->grab_wireframe_rect, + &new_position, + gravity, + width, + height); + meta_window_update_wireframe (window, + new_position.x, + new_position.y, + new_position.width, + new_position.height); } else { - meta_window_move_resize (window, TRUE, x, y, width, height); + /* We don't need to update unless the specified width and height + * are actually different from what we had before. + */ + if (window->rect.width != width || window->rect.height != height) + meta_window_resize_with_gravity (window, + TRUE, + width, + height, + gravity); } meta_window_update_keyboard_resize (window, FALSE); } @@ -2782,10 +2621,12 @@ handle_maximize_vert (MetaDisplay *display, XEvent *event, MetaKeyBinding *binding) { - if (window) + if (window && window->has_resize_func) { - if (window->has_resize_func) - meta_window_fill_vertical (window); + if (window->maximized_vertically) + meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL); + else + meta_window_maximize (window, META_MAXIMIZE_VERTICAL); } } @@ -2796,10 +2637,12 @@ handle_maximize_horiz (MetaDisplay *display, XEvent *event, MetaKeyBinding *binding) { - if (window) + if (window && window->has_resize_func) { - if (window->has_resize_func) - meta_window_fill_horizontal (window); + if (window->maximized_horizontally) + meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL); + else + meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL); } } @@ -3230,10 +3073,14 @@ handle_toggle_maximize (MetaDisplay *display, { if (window) { - if (window->maximized) - meta_window_unmaximize (window); + if (META_WINDOW_MAXIMIZED (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); else if (window->has_maximize_func) - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); } } @@ -3247,7 +3094,9 @@ handle_maximize (MetaDisplay *display, if (window) { if (window->has_maximize_func) - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); } } @@ -3260,8 +3109,10 @@ handle_unmaximize (MetaDisplay *display, { if (window) { - if (window->maximized) - meta_window_unmaximize (window); + if (window->maximized_vertically || window->maximized_horizontally) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); } } diff --git a/src/menu.c b/src/menu.c index 2d7031488..65f37508b 100644 --- a/src/menu.c +++ b/src/menu.c @@ -3,6 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2004 Rob Adams + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -60,6 +61,7 @@ static MenuItem menuitems[] = { { META_MENU_OP_UNABOVE, NULL, TRUE, N_("On _Top") }, { META_MENU_OP_MOVE, NULL, FALSE, N_("_Move") }, { META_MENU_OP_RESIZE, NULL, FALSE, N_("_Resize") }, + { META_MENU_OP_RECOVER, NULL, FALSE, N_("Move Titlebar On_screen") }, { 0, NULL, FALSE, NULL }, /* separator */ { META_MENU_OP_DELETE, METACITY_STOCK_DELETE, FALSE, N_("_Close") }, { META_MENU_OP_WORKSPACES, NULL, FALSE, NULL }, /* separator */ diff --git a/src/place.c b/src/place.c index 05426f5db..befcbe576 100644 --- a/src/place.c +++ b/src/place.c @@ -529,16 +529,6 @@ center_tile_rect_in_area (MetaRectangle *rect, rect->y = work_area->y + fluff; } -static gboolean -rect_fits_in_work_area (MetaRectangle *work_area, - MetaRectangle *rect) -{ - return ((rect->x >= work_area->x) && - (rect->y >= work_area->y) && - (rect->x + rect->width <= work_area->x + work_area->width) && - (rect->y + rect->height <= work_area->y + work_area->height)); -} - /* Find the leftmost, then topmost, empty area on the workspace * that can contain the new window. * @@ -597,13 +587,15 @@ find_first_fit (MetaWindow *window, for (i = 0; i < n_xineramas; i++) { +#ifdef WITH_VERBOSE_MODE + char xinerama_location_string[RECT_LENGTH]; + meta_rectangle_to_string (&window->screen->xinerama_infos[xineramas_list[i]].rect, + xinerama_location_string); meta_topic (META_DEBUG_XINERAMA, - "Natural xinerama %d is %d,%d %dx%d\n", + "Natural xinerama %d is %s\n", i, - window->screen->xinerama_infos[xineramas_list[i]].x_origin, - window->screen->xinerama_infos[xineramas_list[i]].y_origin, - window->screen->xinerama_infos[xineramas_list[i]].width, - window->screen->xinerama_infos[xineramas_list[i]].height); + xinerama_location_string); +#endif } /* try each xinerama in the natural ordering in turn */ @@ -614,7 +606,7 @@ find_first_fit (MetaWindow *window, center_tile_rect_in_area (&rect, &work_area); - if (rect_fits_in_work_area (&work_area, &rect) && + if (meta_rectangle_contains_rect (&work_area, &rect) && !rectangle_overlaps_some_window (&rect, windows)) { *new_x = rect.x; @@ -642,7 +634,7 @@ find_first_fit (MetaWindow *window, rect.x = outer_rect.x; rect.y = outer_rect.y + outer_rect.height; - if (rect_fits_in_work_area (&work_area, &rect) && + if (meta_rectangle_contains_rect (&work_area, &rect) && !rectangle_overlaps_some_window (&rect, below_sorted)) { *new_x = rect.x; @@ -673,7 +665,7 @@ find_first_fit (MetaWindow *window, rect.x = outer_rect.x + outer_rect.width; rect.y = outer_rect.y; - if (rect_fits_in_work_area (&work_area, &rect) && + if (meta_rectangle_contains_rect (&work_area, &rect) && !rectangle_overlaps_some_window (&rect, right_sorted)) { *new_x = rect.x; @@ -817,7 +809,6 @@ meta_window_place (MetaWindow *window, if (parent) { int w; - MetaRectangle area; meta_window_get_position (parent, &x, &y); w = parent->rect.width; @@ -836,6 +827,16 @@ meta_window_place (MetaWindow *window, if (fgeom) y += fgeom->top_height; +#if 0 + /* FIXME: If no one has complained about this within a couple + * months, it means that the constraints.c rewrite fixed this + * correctly and this hack is no longer necessary. Feel free to + * submit a patch removing this #ifdef'd out code. (Comment + * written on 2005-10-28). + */ + + MetaRectangle area; + /* clip to xinerama of parent*/ meta_window_get_work_area_current_xinerama (parent, &area); @@ -845,6 +846,7 @@ meta_window_place (MetaWindow *window, y = area.y + area.height - window->rect.height; if (x < area.x) x = area.x; if (y < area.y) y = area.y; +#endif meta_topic (META_DEBUG_PLACEMENT, "Centered window %s over transient parent\n", window->desc); @@ -867,20 +869,20 @@ meta_window_place (MetaWindow *window, window->type == META_WINDOW_MODAL_DIALOG || window->type == META_WINDOW_SPLASHSCREEN) { - /* Center on screen */ + /* Center on current xinerama (i.e. on current monitor) */ int w, h; /* Warning, this function is a round trip! */ xi = meta_screen_get_current_xinerama (window->screen); - w = xi->width; - h = xi->height; + w = xi->rect.width; + h = xi->rect.height; x = (w - window->rect.width) / 2; y = (h - window->rect.height) / 2; - x += xi->x_origin; - y += xi->y_origin; + x += xi->rect.x; + y += xi->rect.y; meta_topic (META_DEBUG_PLACEMENT, "Centered window %s on screen %d xinerama %d\n", window->desc, window->screen->number, xi->number); @@ -919,8 +921,8 @@ meta_window_place (MetaWindow *window, xi = meta_screen_get_current_xinerama (window->screen); /* "Origin" placement algorithm */ - x = xi->x_origin; - y = xi->y_origin; + x = xi->rect.x; + y = xi->rect.y; if (find_first_fit (window, fgeom, windows, xineramas_list, n_xineramas, @@ -986,11 +988,11 @@ meta_window_place (MetaWindow *window, &workarea); meta_window_get_outer_rect (window, &outer); - if (outer.width >= workarea.width && - outer.height >= workarea.height) - { - window->maximize_after_placement = TRUE; - } + if (outer.width >= workarea.width) + window->maximize_horizontally_after_placement = TRUE; + + if (outer.height >= workarea.height) + window->maximize_vertically_after_placement = TRUE; } done_check_denied_focus: @@ -1022,8 +1024,8 @@ meta_window_place (MetaWindow *window, focus_window_list = g_list_prepend (NULL, focus_window); /* Reset x and y ("origin" placement algorithm) */ - x = xi->x_origin; - y = xi->y_origin; + x = xi->rect.x; + y = xi->rect.y; found_fit = find_first_fit (window, fgeom, focus_window_list, xineramas_list, n_xineramas, @@ -1094,33 +1096,6 @@ get_windows_showing_on_same_screen (MetaWindow *window, return windows; } -static gboolean -rects_overlap_vertically (const MetaRectangle *a, - const MetaRectangle *b) -{ - /* if they don't overlap, then either a is above b - * or b is above a - */ - if ((a->y + a->height) < b->y) - return FALSE; - else if ((b->y + b->height) < a->y) - return FALSE; - else - return TRUE; -} - -static gboolean -rects_overlap_horizontally (const MetaRectangle *a, - const MetaRectangle *b) -{ - if ((a->x + a->width) < b->x) - return FALSE; - else if ((b->x + b->width) < a->x) - return FALSE; - else - return TRUE; -} - static void get_vertical_edges (MetaWindow *window, int **edges_p, @@ -1149,13 +1124,13 @@ get_vertical_edges (MetaWindow *window, g_array_append_val (edges, edge); edge = 0; g_array_append_val (edges, edge); - g_array_append_val (edges, window->screen->width); + g_array_append_val (edges, window->screen->rect.width); /* Now get the xinerama screen edges */ for (i = 0; i < window->screen->n_xinerama_infos - 1; i++) { - edge = window->screen->xinerama_infos[i].x_origin + - window->screen->xinerama_infos[i].width; + edge = window->screen->xinerama_infos[i].rect.x + + window->screen->xinerama_infos[i].rect.width; g_array_append_val (edges, edge); } @@ -1185,7 +1160,7 @@ get_vertical_edges (MetaWindow *window, meta_window_get_outer_rect (w, &w_rect); - if (rects_overlap_vertically (&rect, &w_rect)) + if (meta_rectangle_vert_overlap (&rect, &w_rect)) { g_array_append_val (edges, w_rect.x); edge = w_rect.x + w_rect.width; @@ -1231,13 +1206,13 @@ get_horizontal_edges (MetaWindow *window, g_array_append_val (edges, edge); edge = 0; g_array_append_val (edges, edge); - g_array_append_val (edges, window->screen->height); + g_array_append_val (edges, window->screen->rect.height); /* Now get the xinerama screen edges */ for (i = 0; i < window->screen->n_xinerama_infos - 1; i++) { - edge = window->screen->xinerama_infos[i].y_origin + - window->screen->xinerama_infos[i].height; + edge = window->screen->xinerama_infos[i].rect.y + + window->screen->xinerama_infos[i].rect.height; g_array_append_val (edges, edge); } @@ -1266,7 +1241,7 @@ get_horizontal_edges (MetaWindow *window, meta_window_get_outer_rect (w, &w_rect); - if (rects_overlap_horizontally (&rect, &w_rect)) + if (meta_rectangle_horiz_overlap (&rect, &w_rect)) { g_array_append_val (edges, w_rect.y); edge = w_rect.y + w_rect.height; diff --git a/src/screen.c b/src/screen.c index c59df378f..62a0f87a1 100644 --- a/src/screen.c +++ b/src/screen.c @@ -6,7 +6,7 @@ * Some ICCCM manager selection code derived from fvwm2, * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team * Copyright (C) 2003 Rob Adams - * Copyright (C) 2004 Elijah Newren + * Copyright (C) 2004, 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -221,18 +221,18 @@ reload_xinerama_infos (MetaScreen *screen) while (i < n_infos) { screen->xinerama_infos[i].number = infos[i].screen_number; - screen->xinerama_infos[i].x_origin = infos[i].x_org; - screen->xinerama_infos[i].y_origin = infos[i].y_org; - screen->xinerama_infos[i].width = infos[i].width; - screen->xinerama_infos[i].height = infos[i].height; + screen->xinerama_infos[i].rect.x = infos[i].x_org; + screen->xinerama_infos[i].rect.y = infos[i].y_org; + screen->xinerama_infos[i].rect.width = infos[i].width; + screen->xinerama_infos[i].rect.height = infos[i].height; meta_topic (META_DEBUG_XINERAMA, "Xinerama %d is %d,%d %d x %d\n", screen->xinerama_infos[i].number, - screen->xinerama_infos[i].x_origin, - screen->xinerama_infos[i].y_origin, - screen->xinerama_infos[i].width, - screen->xinerama_infos[i].height); + screen->xinerama_infos[i].rect.x, + screen->xinerama_infos[i].rect.y, + screen->xinerama_infos[i].rect.width, + screen->xinerama_infos[i].rect.height); ++i; } @@ -282,18 +282,18 @@ reload_xinerama_infos (MetaScreen *screen) while (i < n_monitors) { screen->xinerama_infos[i].number = i; - screen->xinerama_infos[i].x_origin = monitors[i].x; - screen->xinerama_infos[i].y_origin = monitors[i].y; - screen->xinerama_infos[i].width = monitors[i].width; - screen->xinerama_infos[i].height = monitors[i].height; + screen->xinerama_infos[i].rect.x = monitors[i].x; + screen->xinerama_infos[i].rect.y = monitors[i].y; + screen->xinerama_infos[i].rect.width = monitors[i].width; + screen->xinerama_infos[i].rect.height = monitors[i].height; meta_topic (META_DEBUG_XINERAMA, "Xinerama %d is %d,%d %d x %d\n", screen->xinerama_infos[i].number, - screen->xinerama_infos[i].x_origin, - screen->xinerama_infos[i].y_origin, - screen->xinerama_infos[i].width, - screen->xinerama_infos[i].height); + screen->xinerama_infos[i].rect.x, + screen->xinerama_infos[i].rect.y, + screen->xinerama_infos[i].rect.width, + screen->xinerama_infos[i].rect.height); ++i; } @@ -325,16 +325,13 @@ reload_xinerama_infos (MetaScreen *screen) screen->n_xinerama_infos = 2; screen->xinerama_infos[0].number = 0; - screen->xinerama_infos[0].x_origin = 0; - screen->xinerama_infos[0].y_origin = 0; - screen->xinerama_infos[0].width = screen->width / 2; - screen->xinerama_infos[0].height = screen->height; + screen->xinerama_infos[0].rect = screen->rect; + screen->xinerama_infos[0].rect.width = screen->rect.width / 2; screen->xinerama_infos[1].number = 1; - screen->xinerama_infos[1].x_origin = screen->width / 2; - screen->xinerama_infos[1].y_origin = 0; - screen->xinerama_infos[1].width = screen->width / 2 + screen->width % 2; - screen->xinerama_infos[1].height = screen->height; + screen->xinerama_infos[1].rect = screen->rect; + screen->xinerama_infos[1].rect.x = screen->rect.width / 2; + screen->xinerama_infos[1].rect.width = screen->rect.width / 2; } else { @@ -345,10 +342,7 @@ reload_xinerama_infos (MetaScreen *screen) screen->n_xinerama_infos = 1; screen->xinerama_infos[0].number = 0; - screen->xinerama_infos[0].x_origin = 0; - screen->xinerama_infos[0].y_origin = 0; - screen->xinerama_infos[0].width = screen->width; - screen->xinerama_infos[0].height = screen->height; + screen->xinerama_infos[0].rect = screen->rect; } } @@ -520,8 +514,9 @@ meta_screen_new (MetaDisplay *display, screen->screen_name = get_screen_name (display, number); screen->xscreen = ScreenOfDisplay (xdisplay, number); screen->xroot = xroot; - screen->width = WidthOfScreen (screen->xscreen); - screen->height = HeightOfScreen (screen->xscreen); + screen->rect.x = screen->rect.y = 0; + screen->rect.width = WidthOfScreen (screen->xscreen); + screen->rect.height = HeightOfScreen (screen->xscreen); screen->current_cursor = -1; /* invalid/unset */ screen->default_xvisual = DefaultVisualOfScreen (screen->xscreen); screen->default_depth = DefaultDepthOfScreen (screen->xscreen); @@ -1015,8 +1010,8 @@ set_desktop_geometry_hint (MetaScreen *screen) if (screen->closing > 0) return; - data[0] = screen->width; - data[1] = screen->height; + data[0] = screen->rect.width; + data[1] = screen->rect.height; meta_verbose ("Setting _NET_DESKTOP_GEOMETRY to %ld, %ld\n", data[0], data[1]); @@ -1212,10 +1207,7 @@ meta_screen_ensure_tab_popup (MetaScreen *screen, if (!window->minimized || !meta_window_get_icon_geometry (window, &r)) meta_window_get_outer_rect (window, &r); - entries[i].x = r.x; - entries[i].y = r.y; - entries[i].width = r.width; - entries[i].height = r.height; + entries[i].rect = r; /* Find inside of highlight rectangle to be used * when window is outlined for tabbing. @@ -1229,19 +1221,19 @@ meta_screen_ensure_tab_popup (MetaScreen *screen, int south = window->frame->rect.height - window->frame->child_y - window->rect.height; int east = window->frame->child_x; - entries[i].inner_x = east; - entries[i].inner_y = south; - entries[i].inner_width = window->rect.width; - entries[i].inner_height = window->frame->rect.height - south * 2; + entries[i].inner_rect.x = east; + entries[i].inner_rect.y = south; + entries[i].inner_rect.width = window->rect.width; + entries[i].inner_rect.height = window->frame->rect.height - south * 2; } else { /* Use an arbitrary border size */ #define OUTLINE_WIDTH 5 - entries[i].inner_x = OUTLINE_WIDTH; - entries[i].inner_y = OUTLINE_WIDTH; - entries[i].inner_width = window->rect.width - OUTLINE_WIDTH * 2; - entries[i].inner_height = window->rect.height - OUTLINE_WIDTH * 2; + entries[i].inner_rect.x = OUTLINE_WIDTH; + entries[i].inner_rect.y = OUTLINE_WIDTH; + entries[i].inner_rect.width = window->rect.width - OUTLINE_WIDTH * 2; + entries[i].inner_rect.height = window->rect.height - OUTLINE_WIDTH * 2; } ++i; @@ -1376,26 +1368,20 @@ meta_screen_get_xinerama_for_rect (MetaScreen *screen, best_xinerama = 0; xinerama_score = 0; - i = 0; - while (i < screen->n_xinerama_infos) + for (i = 0; i < screen->n_xinerama_infos; i++) { - MetaRectangle dest, screen_info; - - screen_info.x = screen->xinerama_infos[i].x_origin; - screen_info.y = screen->xinerama_infos[i].y_origin; - screen_info.width = screen->xinerama_infos[i].width; - screen_info.height = screen->xinerama_infos[i].height; - - if (meta_rectangle_intersect (&screen_info, rect, &dest)) + MetaRectangle dest; + if (meta_rectangle_intersect (&screen->xinerama_infos[i].rect, + rect, + &dest)) { - if (dest.width * dest.height > xinerama_score) + int cur = meta_rectangle_area (&dest); + if (cur > xinerama_score) { - xinerama_score = dest.width * dest.height; + xinerama_score = cur; best_xinerama = i; } } - - ++i; } return &screen->xinerama_infos[best_xinerama]; @@ -1425,22 +1411,18 @@ meta_screen_get_xinerama_neighbor (MetaScreen *screen, { current = screen->xinerama_infos + i; - if (((direction == META_SCREEN_RIGHT) && - (current->x_origin == input->x_origin + input->width) && - (current->y_origin >= input->y_origin) && - (current->y_origin <= input->y_origin+input->height)) || - ((direction == META_SCREEN_LEFT) && - (input->x_origin == current->x_origin + current->width) && - (current->y_origin >= input->y_origin) && - (current->y_origin <= input->y_origin + input->height)) || - ((direction == META_SCREEN_UP) && - (input->y_origin == current->y_origin + current->height) && - (current->x_origin >= input->x_origin) && - (current->x_origin <= input->x_origin + input->width)) || - ((direction == META_SCREEN_DOWN) && - (current->y_origin == input->y_origin + input->height) && - (current->x_origin >= input->x_origin) && - (current->x_origin <= input->x_origin + input->width))) + if ((direction == META_SCREEN_RIGHT && + current->rect.x == input->rect.x + input->rect.width && + meta_rectangle_vert_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_LEFT && + input->rect.x == current->rect.x + current->rect.width && + meta_rectangle_vert_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_UP && + input->rect.y == current->rect.y + current->rect.height && + meta_rectangle_horiz_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_DOWN && + current->rect.y == input->rect.y + input->rect.height && + meta_rectangle_horiz_overlap(¤t->rect, &input->rect))) { return current; } @@ -1544,24 +1526,6 @@ meta_screen_get_natural_xinerama_list (MetaScreen *screen, g_queue_free (xinerama_queue); } -gboolean -meta_screen_rect_intersects_xinerama (MetaScreen *screen, - MetaRectangle *rect, - int which_xinerama) -{ - MetaRectangle dest, screen_rect; - - screen_rect.x = screen->xinerama_infos[which_xinerama].x_origin; - screen_rect.y = screen->xinerama_infos[which_xinerama].y_origin; - screen_rect.width = screen->xinerama_infos[which_xinerama].width; - screen_rect.height = screen->xinerama_infos[which_xinerama].height; - - if (meta_rectangle_intersect (&screen_rect, rect, &dest)) - return TRUE; - - return FALSE; -} - const MetaXineramaScreenInfo* meta_screen_get_current_xinerama (MetaScreen *screen) { @@ -1574,39 +1538,33 @@ meta_screen_get_current_xinerama (MetaScreen *screen) if (screen->display->xinerama_cache_invalidated) { Window root_return, child_return; - int root_x_return, root_y_return; int win_x_return, win_y_return; unsigned int mask_return; int i; + MetaRectangle pointer_position; screen->display->xinerama_cache_invalidated = FALSE; + pointer_position.width = pointer_position.height = 1; XQueryPointer (screen->display->xdisplay, screen->xroot, &root_return, &child_return, - &root_x_return, - &root_y_return, + &pointer_position.x, + &pointer_position.y, &win_x_return, &win_y_return, &mask_return); screen->last_xinerama_index = 0; - i = 0; - while (i < screen->n_xinerama_infos) + for (i = 0; i < screen->n_xinerama_infos; i++) { - if ((root_x_return >= screen->xinerama_infos[i].x_origin && - root_x_return < (screen->xinerama_infos[i].x_origin + - screen->xinerama_infos[i].width) && - root_y_return >= screen->xinerama_infos[i].y_origin && - root_y_return < (screen->xinerama_infos[i].y_origin + - screen->xinerama_infos[i].height))) - { - screen->last_xinerama_index = i; - break; - } - - ++i; + if (meta_rectangle_contains_rect (&screen->xinerama_infos[i].rect, + &pointer_position)) + { + screen->last_xinerama_index = i; + break; + } } meta_topic (META_DEBUG_XINERAMA, @@ -2201,8 +2159,8 @@ meta_screen_resize (MetaScreen *screen, int width, int height) { - screen->width = width; - screen->height = height; + screen->rect.width = width; + screen->rect.height = height; reload_xinerama_infos (screen); set_desktop_geometry_hint (screen); diff --git a/src/screen.h b/src/screen.h index 96ad9c425..02c680734 100644 --- a/src/screen.h +++ b/src/screen.h @@ -3,7 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2003 Rob Adams - * Copyright (C) 2004 Elijah Newren + * Copyright (C) 2004, 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,10 +33,7 @@ typedef struct _MetaXineramaScreenInfo MetaXineramaScreenInfo; struct _MetaXineramaScreenInfo { int number; - int x_origin; - int y_origin; - int width; - int height; + MetaRectangle rect; }; typedef void (* MetaScreenWindowFunc) (MetaScreen *screen, MetaWindow *window, @@ -69,8 +66,7 @@ struct _MetaScreen Window xroot; int default_depth; Visual *default_xvisual; - int width; - int height; + MetaRectangle rect; /* Size of screen; rect.x & rect.y are always 0 */ MetaUI *ui; MetaTabPopup *tab_popup; @@ -158,9 +154,6 @@ const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_rect (MetaScreen const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_window (MetaScreen *screen, MetaWindow *window); -gboolean meta_screen_rect_intersects_xinerama (MetaScreen *screen, - MetaRectangle *window, - int which_xinerama); const MetaXineramaScreenInfo* meta_screen_get_xinerama_neighbor (MetaScreen *screen, int which_xinerama, diff --git a/src/session.c b/src/session.c index 7550dc368..e4a08b340 100644 --- a/src/session.c +++ b/src/session.c @@ -3,7 +3,7 @@ /* * Copyright (C) 2001 Havoc Pennington (some code in here from * libgnomeui, (C) Tom Tromey, Carsten Schaar) - * Copyright (C) 2004 Elijah Newren + * Copyright (C) 2004, 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -728,47 +728,6 @@ window_type_from_string (const char *str) return META_WINDOW_NORMAL; } -static const char* -window_gravity_to_string (int gravity) -{ - switch (gravity) - { - case NorthWestGravity: - return "NorthWestGravity"; - break; - case NorthGravity: - return "NorthGravity"; - break; - case NorthEastGravity: - return "NorthEastGravity"; - break; - case WestGravity: - return "WestGravity"; - break; - case CenterGravity: - return "CenterGravity"; - break; - case EastGravity: - return "EastGravity"; - break; - case SouthWestGravity: - return "SouthWestGravity"; - break; - case SouthGravity: - return "SouthGravity"; - break; - case SouthEastGravity: - return "SouthEastGravity"; - break; - case StaticGravity: - return "StaticGravity"; - break; - default: - return "NorthWestGravity"; - break; - } -} - static int window_gravity_from_string (const char *str) { @@ -995,7 +954,7 @@ save_state (void) fputs (" \n", outfile); /* Maximized */ - if (window->maximized) + if (META_WINDOW_MAXIMIZED (window)) { fprintf (outfile, " \n", @@ -1021,7 +980,7 @@ save_state (void) fprintf (outfile, " \n", x, y, w, h, - window_gravity_to_string (window->size_hints.win_gravity)); + meta_gravity_to_string (window->size_hints.win_gravity)); } fputs (" \n", outfile); @@ -1480,7 +1439,7 @@ start_element_handler (GMarkupParseContext *context, pd->info->rect.y, pd->info->rect.width, pd->info->rect.height, - window_gravity_to_string (pd->info->gravity)); + meta_gravity_to_string (pd->info->gravity)); } else { diff --git a/src/stack.c b/src/stack.c index f9ab1129c..80ebfb95b 100644 --- a/src/stack.c +++ b/src/stack.c @@ -201,8 +201,7 @@ window_is_fullscreen_size (MetaWindow *window) { int i; - if (window->rect.width >= window->screen->width && - window->rect.height >= window->screen->height) + if (meta_rectangle_could_fit_rect (&window->rect, &window->screen->rect)) { /* we use the work area since windows that try to * position at 0,0 will get pushed down by menu panel @@ -210,26 +209,20 @@ window_is_fullscreen_size (MetaWindow *window) MetaRectangle workarea; meta_window_get_work_area_current_xinerama (window, &workarea); - if (window->rect.x <= workarea.x && - window->rect.y <= workarea.y && - window->rect.x + window->rect.width >= workarea.x + workarea.width && - window->rect.y + window->rect.height >= workarea.y + workarea.height) + if (meta_rectangle_contains_rect (&window->rect, &workarea)) return TRUE; } i = 0; while (i < window->screen->n_xinerama_infos) { - if (window->rect.width >= window->screen->xinerama_infos[i].width && - window->rect.height >= window->screen->xinerama_infos[i].height) + if (meta_rectangle_could_fit_rect (&window->rect, + &window->screen->xinerama_infos[i].rect)) { MetaRectangle workarea; meta_window_get_work_area_current_xinerama (window, &workarea); - if (window->rect.x <= workarea.x && - window->rect.y <= workarea.y && - window->rect.x + window->rect.width >= workarea.x + workarea.width && - window->rect.y + window->rect.height >= workarea.y + workarea.height) + if (meta_rectangle_contains_rect (&window->rect, &workarea)) return TRUE; } diff --git a/src/tabpopup.c b/src/tabpopup.c index 427bb7fdf..f71e65de7 100644 --- a/src/tabpopup.c +++ b/src/tabpopup.c @@ -241,15 +241,15 @@ meta_ui_tab_popup_new (const MetaTabEntry *entries, if (outline) { - te->rect.x = entries[i].x; - te->rect.y = entries[i].y; - te->rect.width = entries[i].width; - te->rect.height = entries[i].height; + te->rect.x = entries[i].rect.x; + te->rect.y = entries[i].rect.y; + te->rect.width = entries[i].rect.width; + te->rect.height = entries[i].rect.height; - te->inner_rect.x = entries[i].inner_x; - te->inner_rect.y = entries[i].inner_y; - te->inner_rect.width = entries[i].inner_width; - te->inner_rect.height = entries[i].inner_height; + te->inner_rect.x = entries[i].inner_rect.x; + te->inner_rect.y = entries[i].inner_rect.y; + te->inner_rect.width = entries[i].inner_rect.width; + te->inner_rect.height = entries[i].inner_rect.height; } tab_entries = g_list_prepend (tab_entries, te); @@ -739,7 +739,8 @@ selectable_workspace_new (MetaWorkspace *workspace) widget = g_object_new (meta_select_workspace_get_type (), NULL); - screen_aspect = (double) workspace->screen->height / (double) workspace->screen->width; + screen_aspect = (double) workspace->screen->rect.height / + (double) workspace->screen->rect.width; /* account for select rect */ gtk_widget_set_size_request (widget, @@ -893,8 +894,8 @@ meta_select_workspace_expose_event (GtkWidget *widget, SELECT_OUTLINE_WIDTH, widget->allocation.width - SELECT_OUTLINE_WIDTH * 2, widget->allocation.height - SELECT_OUTLINE_WIDTH * 2, - workspace->screen->width, - workspace->screen->height, + workspace->screen->rect.width, + workspace->screen->rect.height, NULL, (workspace->screen->active_workspace == workspace), windows, diff --git a/src/tabpopup.h b/src/tabpopup.h index 74c883cfb..6695c9d26 100644 --- a/src/tabpopup.h +++ b/src/tabpopup.h @@ -2,6 +2,7 @@ /* * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -24,6 +25,7 @@ /* Don't include gtk.h or gdk.h here */ #include "common.h" +#include "boxes.h" #include #include #include @@ -37,8 +39,8 @@ struct _MetaTabEntry MetaTabEntryKey key; const char *title; GdkPixbuf *icon; - int x, y, width, height; - int inner_x, inner_y, inner_width, inner_height; + MetaRectangle rect; + MetaRectangle inner_rect; guint blank : 1; guint hidden : 1; guint demands_attention : 1; diff --git a/src/testboxes.c b/src/testboxes.c new file mode 100644 index 000000000..80425a663 --- /dev/null +++ b/src/testboxes.c @@ -0,0 +1,1400 @@ +/* Metacity box operation testing program */ + +/* + * Copyright (C) 2005 Elijah Newren + * + * 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 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "boxes.h" +#include +#include +#include +#include /* Just for the definition of the various gravities */ +#include /* To initialize random seed */ + +#define NUM_RANDOM_RUNS 10000 + +static void +init_random_ness () +{ + srand(time(NULL)); +} + +static void +get_random_rect (MetaRectangle *rect) +{ + rect->x = rand () % 1600; + rect->y = rand () % 1200; + rect->width = rand () % 1600 + 1; + rect->height = rand () % 1200 + 1; +} + +static MetaRectangle* +new_meta_rect (int x, int y, int width, int height) +{ + MetaRectangle* temporary; + temporary = g_new (MetaRectangle, 1); + temporary->x = x; + temporary->y = y; + temporary->width = width; + temporary->height = height; + + return temporary; +} + +static MetaEdge* +new_screen_edge (int x, int y, int width, int height, int side_type) +{ + MetaEdge* temporary; + temporary = g_new (MetaEdge, 1); + temporary->rect.x = x; + temporary->rect.y = y; + temporary->rect.width = width; + temporary->rect.height = height; + temporary->side_type = side_type; + temporary->edge_type = META_EDGE_SCREEN; + + return temporary; +} + +static MetaEdge* +new_xinerama_edge (int x, int y, int width, int height, int side_type) +{ + MetaEdge* temporary; + temporary = g_new (MetaEdge, 1); + temporary->rect.x = x; + temporary->rect.y = y; + temporary->rect.width = width; + temporary->rect.height = height; + temporary->side_type = side_type; + temporary->edge_type = META_EDGE_XINERAMA; + + return temporary; +} + +static void +test_area () +{ + MetaRectangle temp; + int i; + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp); + g_assert (meta_rectangle_area (&temp) == temp.width * temp.height); + } + + temp = meta_rect (0, 0, 5, 7); + g_assert (meta_rectangle_area (&temp) == 35); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_intersect () +{ + MetaRectangle a = {100, 200, 50, 40}; + MetaRectangle b = { 0, 50, 110, 152}; + MetaRectangle c = { 0, 0, 10, 10}; + MetaRectangle d = {100, 100, 50, 50}; + MetaRectangle b_intersect_d = {100, 100, 10, 50}; + MetaRectangle temp; + MetaRectangle temp2; + + meta_rectangle_intersect (&a, &b, &temp); + temp2 = meta_rect (100, 200, 10, 2); + g_assert (meta_rectangle_equal (&temp, &temp2)); + g_assert (meta_rectangle_area (&temp) == 20); + + meta_rectangle_intersect (&a, &c, &temp); + g_assert (meta_rectangle_area (&temp) == 0); + + meta_rectangle_intersect (&a, &d, &temp); + g_assert (meta_rectangle_area (&temp) == 0); + + meta_rectangle_intersect (&b, &d, &b); + g_assert (meta_rectangle_equal (&b, &b_intersect_d)); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_equal () +{ + MetaRectangle a = {10, 12, 4, 18}; + MetaRectangle b = a; + MetaRectangle c = {10, 12, 4, 19}; + MetaRectangle d = {10, 12, 7, 18}; + MetaRectangle e = {10, 62, 4, 18}; + MetaRectangle f = {27, 12, 4, 18}; + + g_assert ( meta_rectangle_equal (&a, &b)); + g_assert (!meta_rectangle_equal (&a, &c)); + g_assert (!meta_rectangle_equal (&a, &d)); + g_assert (!meta_rectangle_equal (&a, &e)); + g_assert (!meta_rectangle_equal (&a, &f)); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_overlap_funcs () +{ + MetaRectangle temp1, temp2; + int i; + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp1); + get_random_rect (&temp2); + g_assert (meta_rectangle_overlap (&temp1, &temp2) == + (meta_rectangle_horiz_overlap (&temp1, &temp2) && + meta_rectangle_vert_overlap (&temp1, &temp2))); + } + + temp1 = meta_rect ( 0, 0, 10, 10); + temp2 = meta_rect (20, 0, 10, 5); + g_assert (!meta_rectangle_overlap (&temp1, &temp2)); + g_assert (!meta_rectangle_horiz_overlap (&temp1, &temp2)); + g_assert ( meta_rectangle_vert_overlap (&temp1, &temp2)); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_basic_fitting () +{ + MetaRectangle temp1, temp2, temp3; + int i; + /* Four cases: + * case temp1 fits temp2 temp1 could fit temp2 + * 1 Y Y + * 2 N Y + * 3 Y N + * 4 N N + * Of the four cases, case 3 is impossible. An alternate way of looking + * at this table is that either the middle column must be no, or the last + * column must be yes. So we test that. Also, we can repeat the test + * reversing temp1 and temp2. + */ + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp1); + get_random_rect (&temp2); + g_assert (meta_rectangle_contains_rect (&temp1, &temp2) == FALSE || + meta_rectangle_could_fit_rect (&temp1, &temp2) == TRUE); + g_assert (meta_rectangle_contains_rect (&temp2, &temp1) == FALSE || + meta_rectangle_could_fit_rect (&temp2, &temp1) == TRUE); + } + + temp1 = meta_rect ( 0, 0, 10, 10); + temp2 = meta_rect ( 5, 5, 5, 5); + temp3 = meta_rect ( 8, 2, 3, 7); + g_assert ( meta_rectangle_contains_rect (&temp1, &temp2)); + g_assert (!meta_rectangle_contains_rect (&temp2, &temp1)); + g_assert (!meta_rectangle_contains_rect (&temp1, &temp3)); + g_assert ( meta_rectangle_could_fit_rect (&temp1, &temp3)); + g_assert (!meta_rectangle_could_fit_rect (&temp3, &temp2)); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +free_strut_list (GSList *struts) +{ + GSList *tmp = struts; + while (tmp) + { + g_free (tmp->data); + tmp = tmp->next; + } + g_slist_free (struts); +} + +static GSList* +get_strut_list (int which) +{ + GSList *struts; + + struts = NULL; + + g_assert (which >=0 && which <= 6); + switch (which) + { + case 0: + break; + case 1: + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 1600, 20)); + struts = g_slist_prepend (struts, new_meta_rect ( 400, 1160, 1600, 40)); + break; + case 2: + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 1600, 20)); + struts = g_slist_prepend (struts, new_meta_rect ( 800, 1100, 400, 100)); + struts = g_slist_prepend (struts, new_meta_rect ( 300, 1150, 150, 50)); + break; + case 3: + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 1600, 20)); + struts = g_slist_prepend (struts, new_meta_rect ( 800, 1100, 400, 100)); + struts = g_slist_prepend (struts, new_meta_rect ( 300, 1150, 80, 50)); + struts = g_slist_prepend (struts, new_meta_rect ( 700, 525, 200, 150)); + break; + case 4: + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 800, 1200)); + struts = g_slist_prepend (struts, new_meta_rect ( 800, 0, 1600, 20)); + break; + case 5: + struts = g_slist_prepend (struts, new_meta_rect ( 800, 0, 1600, 20)); + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 800, 1200)); + struts = g_slist_prepend (struts, new_meta_rect ( 800, 10, 800, 1200)); + break; + case 6: + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 1600, 40)); + struts = g_slist_prepend (struts, new_meta_rect ( 0, 0, 1600, 20)); + break; + } + + return struts; +} + +static GList* +get_screen_region (int which) +{ + GList *ret; + GSList *struts; + MetaRectangle basic_rect; + + basic_rect = meta_rect (0, 0, 1600, 1200); + ret = NULL; + + struts = get_strut_list (which); + ret = meta_rectangle_get_minimal_spanning_set_for_region (&basic_rect, struts); + free_strut_list (struts); + + return ret; +} + +static GList* +get_screen_edges (int which) +{ + GList *ret; + GSList *struts; + MetaRectangle basic_rect; + + basic_rect = meta_rect (0, 0, 1600, 1200); + ret = NULL; + + struts = get_strut_list (which); + ret = meta_rectangle_find_onscreen_edges (&basic_rect, struts); + free_strut_list (struts); + + return ret; +} + +static GList* +get_xinerama_edges (int which_xinerama_set, int which_strut_set) +{ + GList *ret; + GSList *struts; + GList *xins; + + xins = NULL; + g_assert (which_xinerama_set >=0 && which_xinerama_set <= 3); + switch (which_xinerama_set) + { + case 0: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 1200)); + break; + case 1: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 800, 1200)); + xins = g_list_prepend (xins, new_meta_rect (800, 0, 800, 1200)); + break; + case 2: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600)); + xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 1600, 600)); + break; + case 3: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600)); + xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 800, 600)); + xins = g_list_prepend (xins, new_meta_rect (800, 600, 800, 600)); + break; + } + + ret = NULL; + + struts = get_strut_list (which_strut_set); + ret = meta_rectangle_find_nonintersected_xinerama_edges (xins, struts); + + free_strut_list (struts); + meta_rectangle_free_list_and_elements (xins); + + return ret; +} + +#if 0 +static void +test_merge_regions () +{ + /* logarithmically distributed random number of struts (range?) + * logarithmically distributed random size of struts (up to screen size???) + * uniformly distributed location of center of struts (within screen) + * merge all regions that are possible + * print stats on problem setup + * number of (non-completely-occluded?) struts + * percentage of screen covered + * length of resulting non-minimal spanning set + * length of resulting minimal spanning set + * print stats on merged regions: + * number boxes merged + * number of those merges that were of the form A contains B + * number of those merges that were of the form A partially contains B + * number of those merges that were of the form A is adjacent to B + */ + + GList* region; + GList* compare; + int num_contains, num_merged, num_part_contains, num_adjacent; + + num_contains = num_merged = num_part_contains = num_adjacent = 0; + compare = region = get_screen_region (2); + g_assert (region); + + printf ("Merging stats:\n"); + printf (" Length of initial list: %d\n", g_list_length (region)); +#ifdef PRINT_DEBUG + char rect1[RECT_LENGTH], rect2[RECT_LENGTH]; + char region_list[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list); + printf (" Initial rectangles: %s\n", region_list); +#endif + + while (compare && compare->next) + { + MetaRectangle *a = compare->data; + GList *other = compare->next; + + g_assert (a->width > 0 && a->height > 0); + + while (other) + { + MetaRectangle *b = other->data; + GList *delete_me = NULL; + + g_assert (b->width > 0 && b->height > 0); + +#ifdef PRINT_DEBUG + printf (" -- Comparing %s to %s --\n", + meta_rectangle_to_string (a, rect1), + meta_rectangle_to_string (b, rect2)); +#endif + + /* If a contains b, just remove b */ + if (meta_rectangle_contains_rect (a, b)) + { + delete_me = other; + num_contains++; + num_merged++; + } + /* If b contains a, just remove a */ + else if (meta_rectangle_contains_rect (a, b)) + { + delete_me = compare; + num_contains++; + num_merged++; + } + /* If a and b might be mergeable horizontally */ + else if (a->y == b->y && a->height == b->height) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + num_part_contains++; + num_merged++; + } + /* If a and b are adjacent */ + else if (a->x + a->width == b->x || a->x == b->x + b->width) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + num_adjacent++; + num_merged++; + } + } + /* If a and b might be mergeable vertically */ + else if (a->x == b->x && a->width == b->width) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + num_part_contains++; + num_merged++; + } + /* If a and b are adjacent */ + else if (a->y + a->height == b->y || a->y == b->y + b->height) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + num_adjacent++; + num_merged++; + } + } + + other = other->next; + + /* Delete any rectangle in the list that is no longer wanted */ + if (delete_me != NULL) + { +#ifdef PRINT_DEBUG + MetaRectangle *bla = delete_me->data; + printf (" Deleting rect %s\n", + meta_rectangle_to_string (bla, rect1)); +#endif + + /* Deleting the rect we're compare others to is a little tricker */ + if (compare == delete_me) + { + compare = compare->next; + other = compare->next; + a = compare->data; + } + + /* Okay, we can free it now */ + g_free (delete_me->data); + region = g_list_delete_link (region, delete_me); + } + +#ifdef PRINT_DEBUG + char region_list[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list); + printf (" After comparison, new list is: %s\n", region_list); +#endif + } + + compare = compare->next; + } + + printf (" Num rectangles contained in others : %d\n", + num_contains); + printf (" Num rectangles partially contained in others: %d\n", + num_part_contains); + printf (" Num rectangles adjacent to others : %d\n", + num_adjacent); + printf (" Num rectangles merged with others : %d\n", + num_merged); +#ifdef PRINT_DEBUG + char region_list2[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list2); + printf (" Final rectangles: %s\n", region_list2); +#endif + + meta_rectangle_free_spanning_set (region); + region = NULL; + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} +#endif + +static void +verify_lists_are_equal (GList *code, GList *answer) +{ + int which = 0; + + while (code && answer) + { + MetaRectangle *a = code->data; + MetaRectangle *b = answer->data; + + if (a->x != b->x || + a->y != b->y || + a->width != b->width || + a->height != b->height) + { + g_error ("%dth item in code answer answer lists do not match; " + "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n", + which, + a->x, a->y, a->width, a->height, + b->x, b->y, b->width, b->height); + } + + code = code->next; + answer = answer->next; + + which++; + } + + /* Ought to be at the end of both lists; check if we aren't */ + if (code) + { + MetaRectangle *tmp = code->data; + g_error ("code list longer than answer list by %d items; " + "first extra item: %d,%d +%d,%d\n", + g_list_length (code), + tmp->x, tmp->y, tmp->width, tmp->height); + } + + if (answer) + { + MetaRectangle *tmp = answer->data; + g_error ("answer list longer than code list by %d items; " + "first extra item: %d,%d +%d,%d\n", + g_list_length (answer), + tmp->x, tmp->y, tmp->width, tmp->height); + } +} + +static void +test_regions_okay () +{ + GList* region; + GList* tmp; + + /*************************************************************/ + /* Make sure test region 0 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (0); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect (0, 0, 1600, 1200)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 1 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 400, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 1600, 1140)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 2 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect ( 450, 20, 350, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 800, 1130)); + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 1080)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 3 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 380, 675, 420, 525)); // 220500 + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180)); // 354000 + tmp = g_list_prepend (tmp, new_meta_rect ( 380, 20, 320, 1180)); // 377600 + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 800, 475)); // 380000 + tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180)); // 472000 + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 1600, 425)); // 680000 + tmp = g_list_prepend (tmp, new_meta_rect ( 900, 20, 700, 1080)); // 756000 + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 700, 1130)); // 791000 + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 505)); // 808000 +#if 0 + printf ("Got to here...\n"); + char region_list[(RECT_LENGTH+2) * g_list_length (region)]; + char tmp_list[ (RECT_LENGTH+2) * g_list_length (tmp)]; + meta_rectangle_region_to_string (region, ", ", region_list); + meta_rectangle_region_to_string (region, ", ", tmp_list); + printf ("%s vs. %s\n", region_list, tmp_list); +#endif + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 4 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 800, 20, 800, 1180)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 5 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (5); + verify_lists_are_equal (region, NULL); + + /* FIXME: Still to do: + * - Create random struts and check the regions somehow + */ + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_region_fitting () +{ + GList* region; + MetaRectangle rect; + + /* See test_basic_fitting() for how/why these automated random tests work */ + int i; + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + g_assert (meta_rectangle_contained_in_region (region, &rect) == FALSE || + meta_rectangle_could_fit_in_region (region, &rect) == TRUE); + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (1); + + rect = meta_rect (50, 50, 400, 400); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (meta_rectangle_contained_in_region (region, &rect)); + + rect = meta_rect (250, 0, 500, 1150); + g_assert (!meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + rect = meta_rect (250, 0, 400, 400); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + meta_rectangle_free_list_and_elements (region); + + region = get_screen_region (2); + rect = meta_rect (1000, 50, 600, 1100); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_clamping_to_region () +{ + GList* region; + MetaRectangle rect; + MetaRectangle min_size; + FixedDirections fixed_directions; + + min_size.height = min_size.width = 1; + fixed_directions = 0; + + int i; + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + MetaRectangle temp; + get_random_rect (&rect); + temp = rect; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (meta_rectangle_could_fit_in_region (region, &rect) == TRUE); + g_assert (rect.x == temp.x && rect.y == temp.y); + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (1); + + rect = meta_rect (50, 50, 10000, 10000); + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 1600 && rect.height == 1140); + + rect = meta_rect (275, -50, 410, 10000); + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1180); + + rect = meta_rect (50, 50, 10000, 10000); + min_size.height = 1170; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1180); + + rect = meta_rect (50, 50, 10000, 10000); + min_size.width = 600; min_size.height = 1170; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 600 && rect.height == 1170); + + rect = meta_rect (350, 50, 100, 1100); + min_size.width = 1; min_size.height = 1; + fixed_directions = FIXED_DIRECTION_X; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 100 && rect.height == 1100); + + rect = meta_rect (300, 70, 500, 1100); + min_size.width = 1; min_size.height = 1; + fixed_directions = FIXED_DIRECTION_Y; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1100); + + rect = meta_rect (300, 70, 999999, 999999); + min_size.width = 100; min_size.height = 200; + fixed_directions = FIXED_DIRECTION_Y; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 100 && rect.height == 999999); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static gboolean +rect_overlaps_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + /* FIXME: Should I move this to boxes.[ch]? */ + const GList *temp; + gboolean overlaps; + + temp = spanning_rects; + overlaps = FALSE; + while (!overlaps && temp != NULL) + { + overlaps = overlaps || meta_rectangle_overlap (temp->data, rect); + temp = temp->next; + } + + return overlaps; +} + +gboolean time_to_print = FALSE; + +static void +test_clipping_to_region () +{ + GList* region; + MetaRectangle rect, temp; + FixedDirections fixed_directions; + + fixed_directions = 0; + + int i; + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + if (rect_overlaps_region (region, &rect)) + { + meta_rectangle_clip_to_region (region, 0, &rect); + g_assert (meta_rectangle_contained_in_region (region, &rect) == TRUE); + } + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (2); + + rect = meta_rect (-50, -10, 10000, 10000); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (region->data, &rect)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 1000, 400, 150); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (450, 1000, 250, 200); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (400, 1000, 300, 150); + meta_rectangle_clip_to_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (400, 1000, 300, 150); + meta_rectangle_clip_to_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_shoving_into_region () +{ + GList* region; + MetaRectangle rect, temp; + FixedDirections fixed_directions; + + fixed_directions = 0; + + int i; + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + if (meta_rectangle_could_fit_in_region (region, &rect)) + { + meta_rectangle_shove_into_region (region, 0, &rect); + g_assert (meta_rectangle_contained_in_region (region, &rect)); + } + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (2); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 950, 400, 200); + meta_rectangle_shove_into_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (425, 1000, 300, 200); + temp = meta_rect (450, 1000, 300, 200); + meta_rectangle_shove_into_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (425, 1000, 300, 200); + temp = meta_rect (425, 950, 300, 200); + meta_rectangle_shove_into_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 300, 1000, 400, 200); + temp = meta_rect (1200, 1000, 400, 200); + meta_rectangle_shove_into_region (region, + FIXED_DIRECTION_Y, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 800, 1150, 400, 50); /* Completely "offscreen" :) */ + temp = meta_rect ( 800, 1050, 400, 50); + meta_rectangle_shove_into_region (region, + 0, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (-1000, 0, 400, 150); /* Offscreen in 2 directions */ + temp = meta_rect ( 0, 20, 400, 150); + meta_rectangle_shove_into_region (region, + 0, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +verify_edge_lists_are_equal (GList *code, GList *answer) +{ + int which = 0; + + while (code && answer) + { + MetaEdge *a = code->data; + MetaEdge *b = answer->data; + + if (!meta_rectangle_equal (&a->rect, &b->rect) || + a->side_type != b->side_type || + a->edge_type != b->edge_type) + { + g_error ("%dth item in code answer answer lists do not match; " + "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n", + which, + a->rect.x, a->rect.y, a->rect.width, a->rect.height, + b->rect.x, b->rect.y, b->rect.width, b->rect.height); + } + + code = code->next; + answer = answer->next; + + which++; + } + + /* Ought to be at the end of both lists; check if we aren't */ + if (code) + { + MetaEdge *tmp = code->data; + g_error ("code list longer than answer list by %d items; " + "first extra item rect: %d,%d +%d,%d\n", + g_list_length (code), + tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height); + } + + if (answer) + { + MetaEdge *tmp = answer->data; + g_error ("answer list longer than code list by %d items; " + "first extra item rect: %d,%d +%d,%d\n", + g_list_length (answer), + tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height); + } +} + +static void +test_find_onscreen_edges () +{ + GList* edges; + GList* tmp; + + int left = META_DIRECTION_LEFT; + int right = META_DIRECTION_RIGHT; + int top = META_DIRECTION_TOP; + int bottom = META_DIRECTION_BOTTOM; + + /*************************************************/ + /* Make sure test region 0 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (0); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 0, 0, 1200, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 0, 1200, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 1 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 1200, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1140, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 0, 40, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 2 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1200, 350, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 150, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right)); + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1150, 0, 50, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 3 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1200, 420, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 80, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 200, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 675, 200, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 0, 150, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right)); + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 900, 525, 0, 150, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1150, 0, 50, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + +#if 0 + #define FUDGE 50 /* number of edges */ + char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE]; + meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1); + meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2); + printf("Generated edge list:\n %s\nComparison edges list:\n %s\n", + big_buffer1, big_buffer2); +#endif + + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 4 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1200, 800, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 800, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 5 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (5); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 6 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (6); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 40, 0, 1160, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 0, 1160, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_find_nonintersected_xinerama_edges () +{ + GList* edges; + GList* tmp; + + int left = META_DIRECTION_LEFT; + int right = META_DIRECTION_RIGHT; + int top = META_DIRECTION_TOP; + int bottom = META_DIRECTION_BOTTOM; + + /*************************************************************************/ + /* Make sure test xinerama set 0 for with region 0 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (0, 0); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 2 for with region 1 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (2, 1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, top)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 1 for with region 2 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (1, 2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1080, right)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1180, left)); +#if 0 + #define FUDGE 50 + char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE]; + meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1); + meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2); + printf("Generated edge list:\n %s\nComparison edges list:\n %s\n", + big_buffer1, big_buffer2); +#endif + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 3 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 425, right)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 525, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 4 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 0, 600, right)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 5has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 5); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_gravity_resize () +{ + MetaRectangle oldrect, rect, temp; + + rect.x = -500; /* Some random amount not equal to oldrect.x to ensure that + * the resize is done with respect to oldrect instead of rect + */ + oldrect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect ( 50, 300, 20, 5); + meta_rectangle_resize_with_gravity (&oldrect, + &rect, + NorthWestGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (165, 300, 20, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + NorthGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (280, 300, 20, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + NorthEastGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect ( 50, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthWestGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (150, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (250, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthEastGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (167, 738, 237, 843); + temp = meta_rect (167, 1113, 832, 93); + meta_rectangle_resize_with_gravity (&rect, + &rect, + WestGravity, + 832, + 93); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 167, 738, 237, 843); + temp = meta_rect (-131, 1113, 833, 93); + meta_rectangle_resize_with_gravity (&rect, + &rect, + CenterGravity, + 832, + 93); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (270, 994, 430, 212); + meta_rectangle_resize_with_gravity (&rect, + &rect, + EastGravity, + 430, + 211); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 1000, 430, 211); + meta_rectangle_resize_with_gravity (&rect, + &rect, + StaticGravity, + 430, + 211); + g_assert (meta_rectangle_equal (&rect, &temp)); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +static void +test_find_closest_point_to_line () +{ + double x1, y1, x2, y2, px, py, rx, ry; + double answer_x, answer_y; + + x1 = 3.0; y1 = 49.0; + x2 = 2.0; y2 = - 1.0; + px = -2.6; py = 19.1; + answer_x = 2.4; answer_y = 19; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Special test for x1 == x2, so that slop of line is infinite */ + x1 = 3.0; y1 = 49.0; + x2 = 3.0; y2 = - 1.0; + px = -2.6; py = 19.1; + answer_x = 3.0; answer_y = 19.1; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Special test for y1 == y2, so perp line has slope of infinity */ + x1 = 3.14; y1 = 7.0; + x2 = 2.718; y2 = 7.0; + px = -2.6; py = 19.1; + answer_x = -2.6; answer_y = 7; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Test when we the point we want to be closest to is actually on the line */ + x1 = 3.0; y1 = 49.0; + x2 = 2.0; y2 = - 1.0; + px = 2.4; py = 19.0; + answer_x = 2.4; answer_y = 19; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + printf ("%s passed.\n", __PRETTY_FUNCTION__); +} + +int +main() +{ + init_random_ness (); + test_area (); + test_intersect (); + test_equal (); + test_overlap_funcs (); + test_basic_fitting (); + + test_regions_okay (); + test_region_fitting (); + + test_clamping_to_region (); + test_clipping_to_region (); + test_shoving_into_region (); + + /* And now the functions dealing with edges more than boxes */ + test_find_onscreen_edges (); + test_find_nonintersected_xinerama_edges (); + + /* And now the misfit functions that don't quite fit in anywhere else... */ + test_gravity_resize (); + test_find_closest_point_to_line (); + + printf ("All tests passed.\n"); + return 0; +} diff --git a/src/util.c b/src/util.c index fbdfa2466..a7d03ea85 100644 --- a/src/util.c +++ b/src/util.c @@ -29,6 +29,7 @@ #include #include #include +#include /* Just for the definition of the various gravities */ #ifdef HAVE_BACKTRACE #include @@ -464,3 +465,44 @@ meta_unsigned_long_hash (gconstpointer v) return val; #endif } + +const char* +meta_gravity_to_string (int gravity) +{ + switch (gravity) + { + case NorthWestGravity: + return "NorthWestGravity"; + break; + case NorthGravity: + return "NorthGravity"; + break; + case NorthEastGravity: + return "NorthEastGravity"; + break; + case WestGravity: + return "WestGravity"; + break; + case CenterGravity: + return "CenterGravity"; + break; + case EastGravity: + return "EastGravity"; + break; + case SouthWestGravity: + return "SouthWestGravity"; + break; + case SouthGravity: + return "SouthGravity"; + break; + case SouthEastGravity: + return "SouthEastGravity"; + break; + case StaticGravity: + return "StaticGravity"; + break; + default: + return "NorthWestGravity"; + break; + } +} diff --git a/src/util.h b/src/util.h index 86e8dfd3b..67c7b0150 100644 --- a/src/util.h +++ b/src/util.h @@ -85,6 +85,8 @@ guint meta_unsigned_long_hash (gconstpointer v); void meta_print_backtrace (void); +const char* meta_gravity_to_string (int gravity); + #include #define _(x) dgettext (GETTEXT_PACKAGE, x) #define N_(x) x diff --git a/src/window.c b/src/window.c index 4624157ad..6e0ab93dd 100644 --- a/src/window.c +++ b/src/window.c @@ -24,6 +24,7 @@ #include #include "window.h" +#include "edge-resistance.h" #include "util.h" #include "frame.h" #include "errors.h" @@ -48,13 +49,6 @@ #include #endif -typedef enum -{ - META_IS_CONFIGURE_REQUEST = 1 << 0, - META_DO_GRAVITY_ADJUST = 1 << 1, - META_USER_MOVE_RESIZE = 1 << 2 -} MetaMoveResizeFlags; - static int destroying_windows_disallowed = 0; @@ -95,6 +89,19 @@ static void ensure_mru_position_after (MetaWindow *window, void meta_window_move_resize_now (MetaWindow *window); +static void update_move (MetaWindow *window, + gboolean snap, + int x, + int y); +static gboolean update_move_timeout (gpointer data); +static void update_resize (MetaWindow *window, + gboolean snap, + int x, + int y, + gboolean force); +static gboolean update_resize_timeout (gpointer data); + + /* FIXME we need an abstraction that covers all these queues. */ void meta_window_unqueue_calc_showing (MetaWindow *window); @@ -395,7 +402,6 @@ meta_window_new_with_attrs (MetaDisplay *display, window->has_shape = has_shape; - /* Remember this rect is the actual window size */ window->rect.x = attrs->x; window->rect.y = attrs->y; window->rect.width = attrs->width; @@ -431,9 +437,13 @@ meta_window_new_with_attrs (MetaDisplay *display, window->user_has_move_resized = FALSE; - window->maximized = FALSE; - window->maximize_after_placement = FALSE; + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; window->fullscreen = FALSE; + window->require_fully_onscreen = TRUE; + window->require_on_single_xinerama = TRUE; window->on_all_workspaces = FALSE; window->shaded = FALSE; window->initially_iconic = FALSE; @@ -688,9 +698,11 @@ meta_window_new_with_attrs (MetaDisplay *display, * passing TRUE for is_configure_request, ICCCM says * initial map is handled same as configure request */ + MetaMoveResizeFlags flags = + META_IS_CONFIGURE_REQUEST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; meta_window_move_resize_internal (window, - META_IS_CONFIGURE_REQUEST, - NorthWestGravity, + flags, + window->size_hints.win_gravity, window->size_hints.x, window->size_hints.y, window->size_hints.width, @@ -769,8 +781,10 @@ meta_window_apply_session_info (MetaWindow *window, info->maximized, window->desc); if (window->has_maximize_func && info->maximized) - { - meta_window_maximize (window); + { + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); if (info->saved_rect_set) { @@ -866,9 +880,11 @@ meta_window_apply_session_info (MetaWindow *window, "Restoring pos %d,%d size %d x %d for %s\n", x, y, w, h, window->desc); + MetaMoveResizeFlags flags = + META_DO_GRAVITY_ADJUST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; meta_window_move_resize_internal (window, - META_DO_GRAVITY_ADJUST, - NorthWestGravity, + flags, + window->size_hints.win_gravity, x, y, w, h); } } @@ -961,8 +977,9 @@ meta_window_free (MetaWindow *window) if (window->display->focus_window == window) window->display->focus_window = NULL; - if (window->maximized) - meta_window_unmaximize (window); + if (window->maximized_horizontally || window->maximized_vertically) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); meta_window_unqueue_calc_showing (window); meta_window_unqueue_move_resize (window); @@ -1130,10 +1147,13 @@ set_net_wm_state (MetaWindow *window) data[i] = window->display->atom_net_wm_state_skip_taskbar; ++i; } - if (window->maximized) + if (window->maximized_horizontally) { data[i] = window->display->atom_net_wm_state_maximized_horz; ++i; + } + if (window->maximized_vertically) + { data[i] = window->display->atom_net_wm_state_maximized_vert; ++i; } @@ -1326,8 +1346,8 @@ implement_showing (MetaWindow *window, /* just animate into the corner somehow - maybe * not a good idea... */ - icon_rect.x = window->screen->width; - icon_rect.y = window->screen->height; + icon_rect.x = window->screen->rect.width; + icon_rect.y = window->screen->rect.height; icon_rect.width = 1; icon_rect.height = 1; } @@ -1984,53 +2004,93 @@ meta_window_unminimize (MetaWindow *window) static void meta_window_save_rect (MetaWindow *window) { - if (!(window->maximized || window->fullscreen)) + if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) { /* save size/pos as appropriate args for move_resize */ - window->saved_rect = window->rect; - if (window->frame) + if (!window->maximized_horizontally) { - window->saved_rect.x += window->frame->rect.x; - window->saved_rect.y += window->frame->rect.y; + window->saved_rect.x = window->rect.x; + window->saved_rect.width = window->rect.width; + if (window->frame) + window->saved_rect.x += window->frame->rect.x; + } + if (!window->maximized_vertically) + { + window->saved_rect.y = window->rect.y; + window->saved_rect.height = window->rect.height; + if (window->frame) + window->saved_rect.y += window->frame->rect.y; } } } void -meta_window_maximize_internal (MetaWindow *window, - MetaRectangle *saved_rect) +meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect) { + /* At least one of the two directions ought to be set */ + gboolean maximize_horizontally, maximize_vertically; + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + meta_topic (META_DEBUG_WINDOW_OPS, - "Maximizing %s\n", window->desc); + "Maximizing %s%s\n", + window->desc, + maximize_horizontally && maximize_vertically ? "" : + maximize_horizontally ? " horizontally" : + maximize_vertically ? " vertically" : "BUGGGGG"); if (saved_rect != NULL) window->saved_rect = *saved_rect; else meta_window_save_rect (window); - window->maximized = TRUE; + window->maximized_horizontally = + window->maximized_horizontally || maximize_horizontally; + window->maximized_vertically = + window->maximized_vertically || maximize_vertically; recalc_window_features (window); set_net_wm_state (window); } void -meta_window_maximize (MetaWindow *window) +meta_window_maximize (MetaWindow *window, + MetaMaximizeFlags directions) { - if (!window->maximized) + /* At least one of the two directions ought to be set */ + gboolean maximize_horizontally, maximize_vertically; + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((maximize_horizontally && !window->maximized_horizontally) || + (maximize_vertically && !window->maximized_vertically)) { - if (window->shaded) + if (window->shaded && maximize_vertically) meta_window_unshade (window); /* if the window hasn't been placed yet, we'll maximize it then */ if (!window->placed) { - window->maximize_after_placement = TRUE; + window->maximize_horizontally_after_placement = + window->maximize_horizontally_after_placement || + maximize_horizontally; + window->maximize_vertically_after_placement = + window->maximize_vertically_after_placement || + maximize_vertically; return; } - meta_window_maximize_internal (window, NULL); + meta_window_maximize_internal (window, + directions, + NULL); /* move_resize with new maximization constraints */ @@ -2039,14 +2099,32 @@ meta_window_maximize (MetaWindow *window) } void -meta_window_unmaximize (MetaWindow *window) +meta_window_unmaximize (MetaWindow *window, + MetaMaximizeFlags directions) { - if (window->maximized) + /* At least one of the two directions ought to be set */ + gboolean unmaximize_horizontally, unmaximize_vertically; + unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (unmaximize_horizontally || unmaximize_vertically); + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((unmaximize_horizontally && window->maximized_horizontally) || + (unmaximize_vertically && window->maximized_vertically)) { meta_topic (META_DEBUG_WINDOW_OPS, - "Unmaximizing %s\n", window->desc); + "Unmaximizing %s%s\n", + window->desc, + unmaximize_horizontally && unmaximize_vertically ? "" : + unmaximize_horizontally ? " horizontally" : + unmaximize_vertically ? " vertically" : "BUGGGGG"); - window->maximized = FALSE; + window->maximized_horizontally = + window->maximized_horizontally && !unmaximize_horizontally; + window->maximized_vertically = + window->maximized_vertically && !unmaximize_vertically; /* When we unmaximize, if we're doing a mouse move also we could * get the window suddenly jumping to the upper left corner of @@ -2283,19 +2361,17 @@ meta_window_activate (MetaWindow *window, meta_window_focus (window, timestamp); } -/* returns values suitable for meta_window_move - * i.e. static gravity +/* Manually fix all the weirdness explained in the big comment at the + * beginning of meta_window_move_resize_internal() giving positions + * expected by meta_window_constrain (i.e. positions & sizes of the + * internal or client window). */ static void adjust_for_gravity (MetaWindow *window, MetaFrameGeometry *fgeom, gboolean coords_assume_border, - int x, - int y, - int width, - int height, - int *xp, - int *yp) + int gravity, + MetaRectangle *rect) { int ref_x, ref_y; int bw; @@ -2311,15 +2387,15 @@ adjust_for_gravity (MetaWindow *window, { child_x = fgeom->left_width; child_y = fgeom->top_height; - frame_width = child_x + width + fgeom->right_width; - frame_height = child_y + height + fgeom->bottom_height; + frame_width = child_x + rect->width + fgeom->right_width; + frame_height = child_y + rect->height + fgeom->bottom_height; } else { child_x = 0; child_y = 0; - frame_width = width; - frame_height = height; + frame_width = rect->width; + frame_height = rect->height; } /* We're computing position to pass to window_move, which is @@ -2329,93 +2405,93 @@ adjust_for_gravity (MetaWindow *window, * their formulas assume we're honoring the border width, rather * than compensating for having turned it off) */ - switch (window->size_hints.win_gravity) + switch (gravity) { case NorthWestGravity: - ref_x = x; - ref_y = y; + ref_x = rect->x; + ref_y = rect->y; break; case NorthGravity: - ref_x = x + width / 2 + bw; - ref_y = y; + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y; break; case NorthEastGravity: - ref_x = x + width + bw * 2; - ref_y = y; + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y; break; case WestGravity: - ref_x = x; - ref_y = y + height / 2 + bw; + ref_x = rect->x; + ref_y = rect->y + rect->height / 2 + bw; break; case CenterGravity: - ref_x = x + width / 2 + bw; - ref_y = y + height / 2 + bw; + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y + rect->height / 2 + bw; break; case EastGravity: - ref_x = x + width + bw * 2; - ref_y = y + height / 2 + bw; + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y + rect->height / 2 + bw; break; case SouthWestGravity: - ref_x = x; - ref_y = y + height + bw * 2; + ref_x = rect->x; + ref_y = rect->y + rect->height + bw * 2; break; case SouthGravity: - ref_x = x + width / 2 + bw; - ref_y = y + height + bw * 2; + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y + rect->height + bw * 2; break; case SouthEastGravity: - ref_x = x + width + bw * 2; - ref_y = y + height + bw * 2; + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y + rect->height + bw * 2; break; case StaticGravity: default: - ref_x = x; - ref_y = y; + ref_x = rect->x; + ref_y = rect->y; break; } - switch (window->size_hints.win_gravity) + switch (gravity) { case NorthWestGravity: - *xp = ref_x + child_x; - *yp = ref_y + child_y; + rect->x = ref_x + child_x; + rect->y = ref_y + child_y; break; case NorthGravity: - *xp = ref_x - frame_width / 2 + child_x; - *yp = ref_y + child_y; + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y + child_y; break; case NorthEastGravity: - *xp = ref_x - frame_width + child_x; - *yp = ref_y + child_y; + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y + child_y; break; case WestGravity: - *xp = ref_x + child_x; - *yp = ref_y - frame_height / 2 + child_y; + rect->x = ref_x + child_x; + rect->y = ref_y - frame_height / 2 + child_y; break; case CenterGravity: - *xp = ref_x - frame_width / 2 + child_x; - *yp = ref_y - frame_height / 2 + child_y; + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y - frame_height / 2 + child_y; break; case EastGravity: - *xp = ref_x - frame_width + child_x; - *yp = ref_y - frame_height / 2 + child_y; + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y - frame_height / 2 + child_y; break; case SouthWestGravity: - *xp = ref_x + child_x; - *yp = ref_y - frame_height + child_y; + rect->x = ref_x + child_x; + rect->y = ref_y - frame_height + child_y; break; case SouthGravity: - *xp = ref_x - frame_width / 2 + child_x; - *yp = ref_y - frame_height + child_y; + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y - frame_height + child_y; break; case SouthEastGravity: - *xp = ref_x - frame_width + child_x; - *yp = ref_y - frame_height + child_y; + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y - frame_height + child_y; break; case StaticGravity: default: - *xp = ref_x; - *yp = ref_y; + rect->x = ref_x; + rect->y = ref_y; break; } } @@ -2426,47 +2502,6 @@ static_gravity_works (MetaDisplay *display) return display->static_gravity_works; } -static void -get_mouse_deltas_for_resize (MetaWindow *window, - int resize_gravity, - int w, - int h, - int *x_delta, - int *y_delta) -{ - switch (meta_x_direction_from_gravity (resize_gravity)) - { - case META_RESIZE_LEFT_OR_TOP: - *x_delta = window->rect.width - w; - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - *x_delta = w - window->rect.width; - break; - case META_RESIZE_CENTER: - /* FIXME this implies that with center gravity you have to grow - * in increments of two - */ - *x_delta = (w - window->rect.width) / 2; - break; - } - - switch (meta_y_direction_from_gravity (resize_gravity)) - { - case META_RESIZE_LEFT_OR_TOP: - *y_delta = window->rect.height - h; - break; - case META_RESIZE_RIGHT_OR_BOTTOM: - *y_delta = h - window->rect.height; - break; - case META_RESIZE_CENTER: - /* FIXME this implies that with center gravity you have to grow - * in increments of two - */ - *y_delta = (h - window->rect.height) / 2; - break; - } -} - #ifdef HAVE_XSYNC static void send_sync_request (MetaWindow *window) @@ -2506,6 +2541,41 @@ meta_window_move_resize_internal (MetaWindow *window, int w, int h) { + /* meta_window_move_resize_internal gets called with very different + * meanings for root_x_nw and root_y_nw. w & h are always the area of + * the inner or client window (i.e. excluding the frame) and the + * resize_gravity is always the gravity associated with the resize or + * move_resize request (the gravity is ignored for move-only operations). + * But the location is different because of how this function gets + * called; note that in all cases what we want to find out is the upper + * left corner of the position of the inner window: + * + * Case | Called from (flags; resize_gravity) + * -----+----------------------------------------------- + * 1 | A resize only ConfigureRequest + * 1 | meta_window_resize + * 1 | meta_window_resize_with_gravity + * 2 | New window + * 2 | Session restore + * 2 | A not-resize-only ConfigureRequest + * 3 | meta_window_move + * 3 | meta_window_move_resize + * 4 | various functions via handle_net_moveresize_window() in display.c + * + * For each of the cases, root_x_nw and root_y_nw must be treated as follows: + * + * (1) They should be entirely ignored; instead the previous position + * and size of the window should be resized according to the given + * gravity in order to determine the new position of the window. + * (2) Needs to be fixed up by adjust_for_gravity() as these + * coordinates are relative to some corner or side of the outer + * window (except for the case of StaticGravity) and we want to + * know the location of the upper left corner of the inner window. + * (3) These values are already the desired positon of the NW corner + * of the inner window + * (4) The place that calls this function this way must be fixed; it is + * wrong. + */ XWindowChanges values; unsigned int mask; gboolean need_configure_notify; @@ -2528,14 +2598,15 @@ meta_window_move_resize_internal (MetaWindow *window, */ int client_move_x; int client_move_y; - int x_delta; - int y_delta; MetaRectangle new_rect; MetaRectangle old_rect; is_configure_request = (flags & META_IS_CONFIGURE_REQUEST) != 0; do_gravity_adjust = (flags & META_DO_GRAVITY_ADJUST) != 0; - is_user_action = (flags & META_USER_MOVE_RESIZE) != 0; + is_user_action = (flags & META_IS_USER_ACTION) != 0; + + /* The action has to be a move or a resize or both... */ + g_assert (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)); /* We don't need it in the idle queue anymore. */ meta_window_unqueue_move_resize (window); @@ -2553,8 +2624,30 @@ meta_window_move_resize_internal (MetaWindow *window, if (window->frame) meta_frame_calc_geometry (window->frame, &fgeom); - - if (is_configure_request || do_gravity_adjust) + + new_rect.x = root_x_nw; + new_rect.y = root_y_nw; + new_rect.width = w; + new_rect.height = h; + + /* If this is a resize only, the position should be ignored and + * instead obtained by resizing the old rectangle according to the + * relevant gravity. + */ + if ((flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)) == + META_IS_RESIZE_ACTION) + { + meta_rectangle_resize_with_gravity (&old_rect, + &new_rect, + resize_gravity, + new_rect.width, + new_rect.height); + + meta_topic (META_DEBUG_GEOMETRY, + "Compensated for gravity in resize action; new pos %d,%d\n", + new_rect.x, new_rect.y); + } + else if (is_configure_request || do_gravity_adjust) { adjust_for_gravity (window, window->frame ? &fgeom : NULL, @@ -2562,29 +2655,20 @@ meta_window_move_resize_internal (MetaWindow *window, * the border width existed */ is_configure_request, - root_x_nw, - root_y_nw, - w, h, - &root_x_nw, - &root_y_nw); - + window->size_hints.win_gravity, + &new_rect); + meta_topic (META_DEBUG_GEOMETRY, - "Compensated position for gravity, new pos %d,%d\n", - root_x_nw, root_y_nw); + "Compensated for configure_request/do_gravity_adjust needing " + "weird positioning; new pos %d,%d\n", + new_rect.x, new_rect.y); } - get_mouse_deltas_for_resize (window, resize_gravity, w, h, - &x_delta, &y_delta); - meta_window_constrain (window, window->frame ? &fgeom : NULL, + flags, + resize_gravity, &old_rect, - root_x_nw - old_rect.x, - root_y_nw - old_rect.y, - meta_x_direction_from_gravity (resize_gravity), - x_delta, - meta_y_direction_from_gravity (resize_gravity), - y_delta, &new_rect); w = new_rect.width; @@ -2908,8 +2992,10 @@ meta_window_resize (MetaWindow *window, meta_window_get_position (window, &x, &y); + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION; meta_window_move_resize_internal (window, - user_op ? META_USER_MOVE_RESIZE : 0, + flags, NorthWestGravity, x, y, w, h); } @@ -2920,8 +3006,10 @@ meta_window_move (MetaWindow *window, int root_x_nw, int root_y_nw) { + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | META_IS_MOVE_ACTION; meta_window_move_resize_internal (window, - user_op ? META_USER_MOVE_RESIZE : 0, + flags, NorthWestGravity, root_x_nw, root_y_nw, window->rect.width, @@ -2936,8 +3024,11 @@ meta_window_move_resize (MetaWindow *window, int w, int h) { + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | + META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; meta_window_move_resize_internal (window, - user_op ? META_USER_MOVE_RESIZE : 0, + flags, NorthWestGravity, root_x_nw, root_y_nw, w, h); @@ -2954,8 +3045,10 @@ meta_window_resize_with_gravity (MetaWindow *window, meta_window_get_position (window, &x, &y); + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION; meta_window_move_resize_internal (window, - user_op ? META_USER_MOVE_RESIZE : 0, + flags, gravity, x, y, w, h); } @@ -2981,94 +3074,6 @@ meta_window_move_resize_now (MetaWindow *window) window->rect.height); } -static void -check_maximize_to_work_area (MetaWindow *window, - const MetaRectangle *work_area) -{ - /* If we now fill the screen, maximize. - * the point here is that fill horz + fill vert = maximized - */ - MetaRectangle rect; - - if (!window->has_maximize_func) - return; - - meta_window_get_outer_rect (window, &rect); - - /* The logic in this if is basically: - * if window's left side is at far left or offscreen AND - * window's bottom side is far top or offscreen AND - * window's right side is at far right or offscreen AND - * window's bottom side is at far bottom or offscreen - * except that we maximize windows with a size increment hint (e.g. - * terminals) should be maximized if they are "sufficiently close" - * to the above criteria... - */ - if ( rect.x <= work_area->x && - rect.y <= work_area->y && - (((work_area->width + work_area->x) - (rect.width + rect.x)) < - window->size_hints.width_inc) && - (((work_area->height + work_area->y) - (rect.height + rect.y)) < - window->size_hints.height_inc) ) - meta_window_maximize (window); -} - -void -meta_window_fill_horizontal (MetaWindow *window) -{ - MetaRectangle work_area; - int x, y, w, h; - - meta_window_get_user_position (window, &x, &y); - - w = window->rect.width; - h = window->rect.height; - - meta_window_get_work_area_current_xinerama (window, &work_area); - - x = work_area.x; - w = work_area.width; - - if (window->frame != NULL) - { - x += window->frame->child_x; - w -= (window->frame->child_x + window->frame->right_width); - } - - meta_window_move_resize (window, TRUE, - x, y, w, h); - - check_maximize_to_work_area (window, &work_area); -} - -void -meta_window_fill_vertical (MetaWindow *window) -{ - MetaRectangle work_area; - int x, y, w, h; - - meta_window_get_user_position (window, &x, &y); - - w = window->rect.width; - h = window->rect.height; - - meta_window_get_work_area_current_xinerama (window, &work_area); - - y = work_area.y; - h = work_area.height; - - if (window->frame != NULL) - { - y += window->frame->child_y; - h -= (window->frame->child_y + window->frame->bottom_height); - } - - meta_window_move_resize (window, TRUE, - x, y, w, h); - - check_maximize_to_work_area (window, &work_area); -} - static guint move_resize_idle = 0; static GSList *move_resize_pending = NULL; @@ -3380,12 +3385,9 @@ meta_window_begin_wireframe (MetaWindow *window) int display_width, display_height; window->display->grab_wireframe_rect = window->rect; - - if (window->frame) - { - window->display->grab_wireframe_rect.x += window->frame->rect.x; - window->display->grab_wireframe_rect.y += window->frame->rect.y; - } + meta_window_get_position (window, + &window->display->grab_wireframe_rect.x, + &window->display->grab_wireframe_rect.y); meta_window_get_xor_rect (window, &window->display->grab_wireframe_rect, &new_xor); @@ -4046,15 +4048,28 @@ meta_window_configure_request (MetaWindow *window, * don't make too much sense. I think I am doing the math in a couple * places and could do it in only one function, and remove some of the * move_resize_internal arguments. + * + * UPDATE (2005-09-17): See the huge comment at the beginning of + * meta_window_move_resize_internal() which explains why the current + * setup requires the only_resize thing. Yeah, it'd be much better to + * have a different setup for meta_window_move_resize_internal()... */ - meta_window_move_resize_internal (window, META_IS_CONFIGURE_REQUEST, - only_resize ? - window->size_hints.win_gravity : NorthWestGravity, - window->size_hints.x, - window->size_hints.y, - window->size_hints.width, - window->size_hints.height); + MetaMoveResizeFlags flags = + META_IS_CONFIGURE_REQUEST; + if (event->xconfigurerequest.value_mask & (CWX | CWY)) + flags |= META_IS_MOVE_ACTION; + if (event->xconfigurerequest.value_mask & (CWWidth | CWHeight)) + flags |= META_IS_RESIZE_ACTION; + + if (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)) + meta_window_move_resize_internal (window, + flags, + window->size_hints.win_gravity, + window->size_hints.x, + window->size_hints.y, + window->size_hints.width, + window->size_hints.height); /* Handle stacking. We only handle raises/lowers, mostly because * stack.c really can't deal with anything else. I guess we'll fix @@ -4259,18 +4274,31 @@ meta_window_client_message (MetaWindow *window, } if (first == display->atom_net_wm_state_maximized_horz || - second == display->atom_net_wm_state_maximized_horz || - first == display->atom_net_wm_state_maximized_vert || + second == display->atom_net_wm_state_maximized_horz) + { + gboolean max; + + max = (action == _NET_WM_STATE_ADD || + (action == _NET_WM_STATE_TOGGLE && + !window->maximized_horizontally)); + if (max && window->has_maximize_func) + meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL); + else + meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL); + } + + if (first == display->atom_net_wm_state_maximized_vert || second == display->atom_net_wm_state_maximized_vert) { gboolean max; max = (action == _NET_WM_STATE_ADD || - (action == _NET_WM_STATE_TOGGLE && !window->maximized)); + (action == _NET_WM_STATE_TOGGLE && + !window->maximized_vertically)); if (max && window->has_maximize_func) - meta_window_maximize (window); + meta_window_maximize (window, META_MAXIMIZE_VERTICAL); else - meta_window_unmaximize (window); + meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL); } if (first == display->atom_net_wm_state_modal || @@ -4886,7 +4914,8 @@ update_net_wm_state (MetaWindow *window) Atom *atoms; window->shaded = FALSE; - window->maximized = FALSE; + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; window->wm_state_modal = FALSE; window->wm_state_skip_taskbar = FALSE; window->wm_state_skip_pager = FALSE; @@ -4906,9 +4935,9 @@ update_net_wm_state (MetaWindow *window) if (atoms[i] == window->display->atom_net_wm_state_shaded) window->shaded = TRUE; else if (atoms[i] == window->display->atom_net_wm_state_maximized_horz) - window->maximize_after_placement = TRUE; + window->maximize_horizontally_after_placement = TRUE; else if (atoms[i] == window->display->atom_net_wm_state_maximized_vert) - window->maximize_after_placement = TRUE; + window->maximize_vertically_after_placement = TRUE; else if (atoms[i] == window->display->atom_net_wm_state_modal) window->wm_state_modal = TRUE; else if (atoms[i] == window->display->atom_net_wm_state_skip_taskbar) @@ -5526,15 +5555,6 @@ meta_window_update_struts (MetaWindow *window) MetaRectangle new_top; MetaRectangle new_bottom; - /** - * This gap must be kept to at least 75 pixels, since otherwise - * struts on opposite sides of the screen left/right could interfere - * in each other in a way that makes it so there is no feasible - * solution to the constraint satisfaction problem. See - * constraints.c. - */ -#define MIN_EMPTY (76) - meta_verbose ("Updating struts for %s\n", window->desc); if (window->struts) @@ -5551,25 +5571,19 @@ meta_window_update_struts (MetaWindow *window) } new_has_struts = FALSE; + new_left = window->screen->rect; new_left.width = 0; - new_left.x = 0; - new_left.y = 0; - new_left.height = window->screen->height; + new_right = window->screen->rect; new_right.width = 0; - new_right.x = window->screen->width; - new_right.y = 0; - new_right.height = window->screen->height; + new_right.x = window->screen->rect.width; + new_top = window->screen->rect; new_top.height = 0; - new_top.y = 0; - new_top.x = 0; - new_top.width = window->screen->width; + new_bottom = window->screen->rect; new_bottom.height = 0; - new_bottom.y = window->screen->height; - new_bottom.x = 0; - new_bottom.width = window->screen->width; + new_bottom.y = window->screen->rect.height; if (meta_prop_get_cardinal_list (window->display, window->xwindow, @@ -5583,20 +5597,13 @@ meta_window_update_struts (MetaWindow *window) } else { - int gap; - gap = window->screen->width - struts[0] - struts[1]; - gap -= MIN_EMPTY; new_has_struts = TRUE; - new_left.width = (int) struts[0] + MIN (0, gap/2); - new_right.width = (int) struts[1] + MIN (0, gap/2); - gap = window->screen->height - struts[2] - struts[3]; - gap -= MIN_EMPTY; - new_top.height = (int)struts[2] + MIN (0, gap/2); - new_bottom.height = (int)struts[3] + MIN (0, gap/2); - new_right.x = window->screen->width - - new_right.width; - new_bottom.y = window->screen->height - - new_bottom.height; + new_left.width = (int) struts[0]; + new_right.width = (int) struts[1]; + new_top.height = (int)struts[2]; + new_bottom.height = (int)struts[3]; + new_right.x = window->screen->rect.width - new_right.width; + new_bottom.y = window->screen->rect.height - new_bottom.height; new_left.y = struts[4]; new_left.height = struts[5] - new_left.y + 1; new_right.y = struts[6]; @@ -5636,22 +5643,13 @@ meta_window_update_struts (MetaWindow *window) } else { - int gap; - gap = window->screen->width - struts[0] - struts[1]; - gap -= MIN_EMPTY; new_has_struts = TRUE; - new_left.width = (int) struts[0] + MIN (0, gap/2); - new_right.width = (int) struts[1] + MIN (0, gap/2); - gap = window->screen->height - struts[2] - struts[3]; - gap -= MIN_EMPTY; - new_top.height = (int)struts[2] + MIN (0, gap/2); - new_bottom.height = (int)struts[3] + MIN (0, gap/2); - new_left.x = 0; - new_right.x = window->screen->width - - new_right.width; - new_top.y = 0; - new_bottom.y = window->screen->height - - new_bottom.height; + new_left.width = (int) struts[0]; + new_right.width = (int) struts[1]; + new_top.height = (int)struts[2]; + new_bottom.height = (int)struts[3]; + new_right.x = window->screen->rect.width - new_right.width; + new_bottom.y = window->screen->rect.height - new_bottom.height; meta_verbose ("_NET_WM_STRUT struts %d %d %d %d for window %s\n", new_left.width, @@ -5933,8 +5931,8 @@ recalc_window_features (MetaWindow *window) * is entire screen size (kind of broken, because we * actually fullscreen to xinerama head size not screen size) */ - if (window->size_hints.min_width == window->screen->width && - window->size_hints.min_height == window->screen->height && + if (window->size_hints.min_width == window->screen->rect.width && + window->size_hints.min_height == window->screen->rect.height && !window->decorated) ; /* leave fullscreen available */ else @@ -6070,11 +6068,15 @@ menu_callback (MetaWindowMenu *menu, break; case META_MENU_OP_UNMAXIMIZE: - meta_window_unmaximize (window); + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); break; case META_MENU_OP_MAXIMIZE: - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); break; case META_MENU_OP_UNSHADE: @@ -6137,6 +6139,10 @@ menu_callback (MetaWindowMenu *menu, META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN, meta_display_get_current_time (window->display)); break; + + case META_MENU_OP_RECOVER: + meta_window_shove_titlebar_onscreen (window); + break; case 0: /* nothing */ @@ -6196,6 +6202,11 @@ meta_window_show_menu (MetaWindow *window, ops |= (META_MENU_OP_DELETE | META_MENU_OP_MINIMIZE | META_MENU_OP_MOVE | META_MENU_OP_RESIZE); + if (!meta_window_titlebar_is_onscreen (window) && + window->type != META_WINDOW_DOCK && + window->type != META_WINDOW_DESKTOP) + ops |= META_MENU_OP_RECOVER; + n_workspaces = meta_screen_get_n_workspaces (window->screen); if (n_workspaces > 1) @@ -6223,7 +6234,7 @@ meta_window_show_menu (MetaWindow *window, meta_screen_free_workspace_layout (&layout); - if (window->maximized) + if (META_WINDOW_MAXIMIZED (window)) ops |= META_MENU_OP_UNMAXIMIZE; else ops |= META_MENU_OP_MAXIMIZE; @@ -6295,6 +6306,98 @@ meta_window_show_menu (MetaWindow *window, meta_ui_window_menu_popup (menu, root_x, root_y, button, timestamp); } +void +meta_window_shove_titlebar_onscreen (MetaWindow *window) +{ + MetaRectangle outer_rect; + GList *onscreen_region; + int horiz_amount, vert_amount; + int newx, newy; + + /* If there's no titlebar, don't bother */ + if (!window->frame) + return; + + /* Get the basic info we need */ + meta_window_get_outer_rect (window, &outer_rect); + onscreen_region = window->screen->active_workspace->screen_region; + + /* Extend the region (just in case the window is too big to fit on the + * screen), then shove the window on screen, then return the region to + * normal. + */ + horiz_amount = outer_rect.width; + vert_amount = outer_rect.height; + meta_rectangle_expand_region (onscreen_region, + horiz_amount, + horiz_amount, + 0, + vert_amount); + meta_rectangle_shove_into_region(onscreen_region, + FIXED_DIRECTION_X, + &outer_rect); + meta_rectangle_expand_region (onscreen_region, + -horiz_amount, + -horiz_amount, + 0, + -vert_amount); + + newx = outer_rect.x + window->frame->child_x; + newy = outer_rect.y + window->frame->child_y; + meta_window_move_resize (window, + TRUE, + newx, + newy, + window->rect.width, + window->rect.height); +} + +gboolean +meta_window_titlebar_is_onscreen (MetaWindow *window) +{ + MetaRectangle titlebar_rect; + GList *onscreen_region; + int titlebar_size; + gboolean is_onscreen; + + const int min_height_needed = 8; + const int min_width_percent = 0.5; + const int min_width_absolute = 50; + + /* Titlebar can't be offscreen if there is no titlebar... */ + if (!window->frame) + return FALSE; + + /* Get the rectangle corresponding to the titlebar */ + meta_window_get_outer_rect (window, &titlebar_rect); + titlebar_rect.height = window->frame->child_y; + titlebar_size = meta_rectangle_area (&titlebar_rect); + + /* Run through the spanning rectangles for the screen and see if one of + * them overlaps with the titlebar sufficiently to consider it onscreen. + */ + is_onscreen = FALSE; + onscreen_region = window->screen->active_workspace->screen_region; + while (onscreen_region) + { + MetaRectangle *spanning_rect = onscreen_region->data; + MetaRectangle overlap; + + meta_rectangle_intersect (&titlebar_rect, spanning_rect, &overlap); + if (overlap.height > MIN (titlebar_rect.height, min_height_needed) && + overlap.width > MIN (titlebar_rect.width * min_width_percent, + min_width_absolute)) + { + is_onscreen = TRUE; + break; + } + + onscreen_region = onscreen_region->next; + } + + return is_onscreen; +} + static double timeval_to_ms (const GTimeVal *timeval) { @@ -6385,14 +6488,28 @@ check_moveresize_frequency (MetaWindow *window, } } +static gboolean +update_move_timeout (gpointer data) +{ + MetaWindow *window = data; + + update_move (window, + window->display->grab_last_user_action_was_snap, + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y); + + return FALSE; +} + static void update_move (MetaWindow *window, - unsigned int mask, + gboolean snap, int x, int y) { int dx, dy; int new_x, new_y; + MetaRectangle old; int shake_threshold; window->display->grab_latest_motion_x = x; @@ -6411,7 +6528,14 @@ update_move (MetaWindow *window, window->display->grab_anchor_window_pos.x, window->display->grab_anchor_window_pos.y, dx, dy); - + + /* Don't bother doing anything if no move has been specified. (This + * happens often, even in keyboard moving, due to the warping of the + * pointer. + */ + if (dx == 0 && dy == 0) + return; + /* shake loose (unmaximize) maximized window if dragged beyond the threshold * in the Y direction. You can't pull a window loose via X motion. */ @@ -6420,7 +6544,7 @@ update_move (MetaWindow *window, shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) * DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR; - if (window->maximized && ABS (dy) >= shake_threshold) + if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) { double prop; @@ -6446,14 +6570,16 @@ update_move (MetaWindow *window, window->display->grab_anchor_root_x = x; window->display->grab_anchor_root_y = y; - meta_window_unmaximize (window); + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); return; } /* remaximize window on an other xinerama monitor if window has * been shaken loose or it is still maximized (then move straight) */ - else if (window->shaken_loose || window->maximized) + else if (window->shaken_loose || META_WINDOW_MAXIMIZED (window)) { const MetaXineramaScreenInfo *wxinerama; MetaRectangle work_area; @@ -6485,7 +6611,9 @@ update_move (MetaWindow *window, window->saved_rect.y += window->frame->child_y; } - meta_window_unmaximize (window); + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); } window->display->grab_initial_window_pos = work_area; @@ -6493,27 +6621,39 @@ update_move (MetaWindow *window, window->display->grab_anchor_root_y = y; window->shaken_loose = FALSE; - meta_window_maximize (window); + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); return; } } } - /* don't allow a maximized window to move */ - if (window->maximized) - return; - - if (mask & ShiftMask) + if (window->display->grab_wireframe_active) + old = window->display->grab_wireframe_rect; + else { - /* snap to edges */ - if (dy != 0) - new_x = meta_window_find_nearest_vertical_edge (window, new_x); - - if (dx != 0) - new_y = meta_window_find_nearest_horizontal_edge (window, new_y); + old = window->rect; + meta_window_get_position (window, &old.x, &old.y); } + /* Don't allow movement in the maximized directions */ + if (window->maximized_horizontally) + new_x = old.x; + if (window->maximized_vertically) + new_y = old.y; + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_move (window, + old.x, + old.y, + &new_x, + &new_y, + update_move_timeout, + snap, + FALSE); + if (window->display->grab_wireframe_active) meta_window_update_wireframe (window, new_x, new_y, window->display->grab_wireframe_rect.width, @@ -6522,25 +6662,22 @@ update_move (MetaWindow *window, meta_window_move (window, TRUE, new_x, new_y); } -static void update_resize (MetaWindow *window, - int x, - int y, - gboolean force); - static gboolean update_resize_timeout (gpointer data) { MetaWindow *window = data; update_resize (window, - window->display->grab_latest_motion_x, - window->display->grab_latest_motion_y, - TRUE); + window->display->grab_last_user_action_was_snap, + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y, + TRUE); return FALSE; } static void update_resize (MetaWindow *window, + gboolean snap, int x, int y, gboolean force) { @@ -6560,6 +6697,13 @@ update_resize (MetaWindow *window, new_w = window->display->grab_anchor_window_pos.width; new_h = window->display->grab_anchor_window_pos.height; + /* Don't bother doing anything if no move has been specified. (This + * happens often, even in keyboard resizing, due to the warping of the + * pointer. + */ + if (dx == 0 && dy == 0) + return; + /* FIXME this is only used in wireframe mode */ new_x = window->display->grab_anchor_window_pos.x; new_y = window->display->grab_anchor_window_pos.y; @@ -6608,6 +6752,11 @@ update_resize (MetaWindow *window, } } + /* FIXME: This stupidity only needed because of wireframe mode and + * the fact that wireframe isn't making use of + * meta_rectangle_resize_with_gravity(). If we were to use that, we + * could just increment new_w and new_h by dx and dy in all cases. + */ switch (window->display->grab_op) { case META_GRAB_OP_RESIZING_SE: @@ -6680,12 +6829,46 @@ update_resize (MetaWindow *window, window->display->grab_resize_timeout_id = 0; } - old = window->rect; + if (window->display->grab_wireframe_active) + old = window->display->grab_wireframe_rect; + else + old = window->rect; /* Don't actually care about x,y */ + + /* One sided resizing ought to actually be one-sided, despite the fact that + * aspect ratio windows don't interact nicely with the above stuff. So, + * to avoid some nasty flicker, we enforce that. + */ + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_N: + new_w = old.width; + break; + + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_RESIZING_W: + new_h = old.height; + break; + + default: + break; + } /* compute gravity of client during operation */ gravity = meta_resize_gravity_from_grab_op (window->display->grab_op); g_assert (gravity >= 0); + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_resize (window, + old.width, + old.height, + &new_w, + &new_h, + gravity, + update_resize_timeout, + snap, + FALSE); + if (window->display->grab_wireframe_active) { if ((new_x + new_w <= new_x) || (new_y + new_h <= new_y)) @@ -6702,7 +6885,11 @@ update_resize (MetaWindow *window, } else { - meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity); + /* We don't need to update unless the specified width and height + * are actually different from what we had before. + */ + if (old.width != new_w || old.height != new_h) + meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity); } /* Store the latest resize time, if we actually resized. */ @@ -6831,6 +7018,7 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, case META_GRAB_OP_KEYBOARD_RESIZING_NW: /* no pointer round trip here, to keep in sync */ update_resize (window, + window->display->grab_last_user_action_was_snap, window->display->grab_latest_motion_x, window->display->grab_latest_motion_y, TRUE); @@ -6844,20 +7032,29 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, switch (event->type) { - case ButtonRelease: - if (meta_grab_op_is_moving (window->display->grab_op)) + case ButtonRelease: + /* If the user was snap moving then ignore the button release + * because they may have let go of shift before releasing the + * mouse button and they almost certainly do not want a + * non-snapped movement to occur from the button release. + */ + if (!window->display->grab_last_user_action_was_snap) { - if (event->xbutton.root == window->screen->xroot) - update_move (window, event->xbutton.state, - event->xbutton.x_root, event->xbutton.y_root); - } - else if (meta_grab_op_is_resizing (window->display->grab_op)) - { - if (event->xbutton.root == window->screen->xroot) - update_resize (window, - event->xbutton.x_root, - event->xbutton.y_root, - TRUE); + if (meta_grab_op_is_moving (window->display->grab_op)) + { + if (event->xbutton.root == window->screen->xroot) + update_move (window, event->xbutton.state & ShiftMask, + event->xbutton.x_root, event->xbutton.y_root); + } + else if (meta_grab_op_is_resizing (window->display->grab_op)) + { + if (event->xbutton.root == window->screen->xroot) + update_resize (window, + event->xbutton.state & ShiftMask, + event->xbutton.x_root, + event->xbutton.y_root, + TRUE); + } } meta_display_end_grab_op (window->display, event->xbutton.time); @@ -6871,7 +7068,7 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, if (check_use_this_motion_notify (window, event)) update_move (window, - event->xmotion.state, + event->xmotion.state & ShiftMask, event->xmotion.x_root, event->xmotion.y_root); } @@ -6883,32 +7080,14 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, if (check_use_this_motion_notify (window, event)) update_resize (window, + event->xmotion.state & ShiftMask, event->xmotion.x_root, event->xmotion.y_root, - FALSE); + FALSE); } } break; - case EnterNotify: - case LeaveNotify: - if (meta_grab_op_is_moving (window->display->grab_op)) - { - if (event->xcrossing.root == window->screen->xroot) - update_move (window, - event->xcrossing.state, - event->xcrossing.x_root, - event->xcrossing.y_root); - } - else if (meta_grab_op_is_resizing (window->display->grab_op)) - { - if (event->xcrossing.root == window->screen->xroot) - update_resize (window, - event->xcrossing.x_root, - event->xcrossing.y_root, - FALSE); - } - break; default: break; } @@ -6939,54 +7118,26 @@ get_work_area_xinerama (MetaWindow *window, MetaRectangle *area, int which_xinerama) { - MetaRectangle space_area; GList *tmp; - int left_strut; - int right_strut; - int top_strut; - int bottom_strut; - int xinerama_origin_x; - int xinerama_origin_y; - int xinerama_width; - int xinerama_height; g_assert (which_xinerama >= 0); - xinerama_origin_x = window->screen->xinerama_infos[which_xinerama].x_origin; - xinerama_origin_y = window->screen->xinerama_infos[which_xinerama].y_origin; - xinerama_width = window->screen->xinerama_infos[which_xinerama].width; - xinerama_height = window->screen->xinerama_infos[which_xinerama].height; - - left_strut = 0; - right_strut = 0; - top_strut = 0; - bottom_strut = 0; + /* Initialize to the whole xinerama */ + *area = window->screen->xinerama_infos[which_xinerama].rect; tmp = meta_window_get_workspaces (window); while (tmp != NULL) { + MetaRectangle workspace_work_area; meta_workspace_get_work_area_for_xinerama (tmp->data, which_xinerama, - &space_area); - - left_strut = MAX (left_strut, space_area.x - xinerama_origin_x); - right_strut = MAX (right_strut, - (xinerama_width - - (space_area.x - xinerama_origin_x) - - space_area.width)); - top_strut = MAX (top_strut, space_area.y - xinerama_origin_y); - bottom_strut = MAX (bottom_strut, - (xinerama_height - - (space_area.y - xinerama_origin_y) - - space_area.height)); + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); tmp = tmp->next; } - area->x = xinerama_origin_x + left_strut; - area->y = xinerama_origin_y + top_strut; - area->width = xinerama_width - left_strut - right_strut; - area->height = xinerama_height - top_strut - bottom_strut; - meta_topic (META_DEBUG_WORKAREA, "Window %s xinerama %d has work area %d,%d %d x %d\n", window->desc, which_xinerama, @@ -7022,50 +7173,22 @@ void meta_window_get_work_area_all_xineramas (MetaWindow *window, MetaRectangle *area) { - MetaRectangle space_area; GList *tmp; - int left_strut; - int right_strut; - int top_strut; - int bottom_strut; - int screen_origin_x; - int screen_origin_y; - int screen_width; - int screen_height; - screen_origin_x = 0; - screen_origin_y = 0; - screen_width = window->screen->width; - screen_height = window->screen->height; - - left_strut = 0; - right_strut = 0; - top_strut = 0; - bottom_strut = 0; + /* Initialize to the whole screen */ + *area = window->screen->rect; tmp = meta_window_get_workspaces (window); while (tmp != NULL) { + MetaRectangle workspace_work_area; meta_workspace_get_work_area_all_xineramas (tmp->data, - &space_area); - - left_strut = MAX (left_strut, space_area.x - screen_origin_x); - right_strut = MAX (right_strut, - (screen_width - - (space_area.x - screen_origin_x) - - space_area.width)); - top_strut = MAX (top_strut, space_area.y - screen_origin_y); - bottom_strut = MAX (bottom_strut, - (screen_height - - (space_area.y - screen_origin_y) - - space_area.height)); + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); tmp = tmp->next; } - - area->x = screen_origin_x + left_strut; - area->y = screen_origin_y + top_strut; - area->width = screen_width - left_strut - right_strut; - area->height = screen_height - top_strut - bottom_strut; meta_topic (META_DEBUG_WORKAREA, "Window %s has whole-screen work area %d,%d %d x %d\n", @@ -7378,12 +7501,35 @@ warp_grab_pointer (MetaWindow *window, *x += rect.x; *y += rect.y; + + /* Avoid weird bouncing at the screen edge; see bug 154706 */ + *x = CLAMP (*x, 0, window->screen->rect.width-1); + *y = CLAMP (*y, 0, window->screen->rect.height-1); meta_error_trap_push_with_return (window->display); meta_topic (META_DEBUG_WINDOW_OPS, "Warping pointer to %d,%d with window at %d,%d\n", *x, *y, rect.x, rect.y); + + /* Need to update the grab positions so that the MotionNotify and other + * events generated by the XWarpPointer() call below don't cause complete + * funkiness. See bug 124582 and bug 122670. + */ + window->display->grab_anchor_root_x = *x; + window->display->grab_anchor_root_y = *y; + window->display->grab_latest_motion_x = *x; + window->display->grab_latest_motion_y = *y; + if (window->display->grab_wireframe_active) + window->display->grab_anchor_window_pos = + window->display->grab_wireframe_rect; + else + { + window->display->grab_anchor_window_pos = window->rect; + meta_window_get_position (window, + &window->display->grab_anchor_window_pos.x, + &window->display->grab_anchor_window_pos.y); + } XWarpPointer (window->display->xdisplay, None, @@ -7443,33 +7589,6 @@ meta_window_update_keyboard_resize (MetaWindow *window, window->display->grab_op, &x, &y); - { - /* As we warped the pointer, we have to reset the anchor state, - * since if the mouse moves we want to use those events to do the - * right thing. Also, this means that the motion notify - * from the pointer warp comes back as a no-op. - */ - int dx, dy; - - dx = x - window->display->grab_anchor_root_x; - dy = y - window->display->grab_anchor_root_y; - - window->display->grab_anchor_root_x += dx; - window->display->grab_anchor_root_y += dy; - if (window->display->grab_wireframe_active) - { - window->display->grab_anchor_window_pos = - window->display->grab_wireframe_rect; - } - else - { - window->display->grab_anchor_window_pos = window->rect; - meta_window_get_position (window, - &window->display->grab_anchor_window_pos.x, - &window->display->grab_anchor_window_pos.y); - } - } - if (update_cursor) { meta_display_set_grab_op_cursor (window->display, diff --git a/src/window.h b/src/window.h index 4253c96d3..54a5308b1 100644 --- a/src/window.h +++ b/src/window.h @@ -52,6 +52,12 @@ typedef enum META_WINDOW_SPLASHSCREEN } MetaWindowType; +typedef enum +{ + META_MAXIMIZE_HORIZONTAL = 1 << 0, + META_MAXIMIZE_VERTICAL = 1 << 1 +} MetaMaximizeFlags; + struct _MetaStruts { /* struts */ @@ -108,8 +114,10 @@ struct _MetaWindow Time initial_timestamp; /* Whether we're maximized */ - guint maximized : 1; - guint maximize_after_placement : 1; + guint maximized_horizontally : 1; + guint maximized_vertically : 1; + guint maximize_horizontally_after_placement : 1; + guint maximize_vertically_after_placement : 1; /* Whether we're shaded */ guint shaded : 1; @@ -117,6 +125,12 @@ struct _MetaWindow /* Whether we're fullscreen */ guint fullscreen : 1; + /* Whether we're trying to constrain the window to be fully onscreen */ + guint require_fully_onscreen : 1; + + /* Whether we're trying to constrain the window to be on a single xinerama */ + guint require_on_single_xinerama : 1; + /* Whether we're sticky in the multi-workspace sense * (vs. the not-scroll-with-viewport sense, we don't * have no stupid viewports) @@ -287,25 +301,29 @@ struct _MetaWindow /* The size we set the window to last (i.e. what we believe * to be its actual size on the server). The x, y are * the actual server-side x,y so are relative to the frame - * or the root window as appropriate. + * (meaning that they just hold the frame width and height) + * or the root window (meaning they specify the location + * of the top left of the inner window) as appropriate. */ MetaRectangle rect; - /* The geometry to restore when we unmaximize. - * The position is in root window coords, even if - * there's a frame, which contrasts with window->rect - * above. + /* The geometry to restore when we unmaximize. The position is in + * root window coords, even if there's a frame, which contrasts with + * window->rect above. Note that this gives the position and size + * of the client window (i.e. ignoring the frame). */ MetaRectangle saved_rect; /* This is the geometry the window had after the last user-initiated * move/resize operations. We use this whenever we are moving the - * implicitly (for example, if we move to avoid a panel, we - * can snap back to this position if the panel moves again) + * implicitly (for example, if we move to avoid a panel, we can snap + * back to this position if the panel moves again). Note that this + * gives the position and size of the client window (i.e. ignoring + * the frame). * * Position valid if user_has_moved, size valid if user_has_resized * - * Position always in root coords, unlike window->rect + * Position always in root coords, unlike window->rect. */ MetaRectangle user_rect; @@ -330,8 +348,10 @@ struct _MetaWindow * the dynamic window state such as "maximized", not just the * window's type */ +#define META_WINDOW_MAXIMIZED(w) ((w)->maximized_horizontally && \ + (w)->maximized_vertically) #define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen) -#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !(w)->maximized && !(w)->fullscreen && !(w)->shaded) +#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen && !(w)->shaded) #define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \ (((w)->size_hints.min_width < (w)->size_hints.max_width) || \ ((w)->size_hints.min_height < (w)->size_hints.max_height))) @@ -350,10 +370,13 @@ void meta_window_calc_showing (MetaWindow *window); void meta_window_queue_calc_showing (MetaWindow *window); void meta_window_minimize (MetaWindow *window); void meta_window_unminimize (MetaWindow *window); -void meta_window_maximize (MetaWindow *window); -void meta_window_maximize_internal (MetaWindow *window, - MetaRectangle *saved_rect); -void meta_window_unmaximize (MetaWindow *window); +void meta_window_maximize (MetaWindow *window, + MetaMaximizeFlags directions); +void meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect); +void meta_window_unmaximize (MetaWindow *window, + MetaMaximizeFlags directions); void meta_window_make_above (MetaWindow *window); void meta_window_unmake_above (MetaWindow *window); void meta_window_shade (MetaWindow *window); @@ -390,9 +413,6 @@ void meta_window_resize_with_gravity (MetaWindow *window, int gravity); -void meta_window_fill_horizontal (MetaWindow *window); -void meta_window_fill_vertical (MetaWindow *window); - /* Return whether the window would be showing if we were on its workspace */ gboolean meta_window_showing_on_its_workspace (MetaWindow *window); @@ -477,6 +497,9 @@ void meta_window_show_menu (MetaWindow *window, int button, Time timestamp); +gboolean meta_window_titlebar_is_onscreen (MetaWindow *window); +void meta_window_shove_titlebar_onscreen (MetaWindow *window); + void meta_window_set_gravity (MetaWindow *window, int gravity); diff --git a/src/workspace.c b/src/workspace.c index 70f6ebdf2..d19e6a8fb 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -57,17 +57,19 @@ meta_workspace_new (MetaScreen *screen) workspace->mru_list = NULL; meta_screen_foreach_window (screen, maybe_add_to_list, &workspace->mru_list); - workspace->work_areas = NULL; workspace->work_areas_invalid = TRUE; - workspace->all_work_areas.x = 0; - workspace->all_work_areas.y = 0; - workspace->all_work_areas.width = 0; - workspace->all_work_areas.height = 0; + workspace->work_area_xinerama = NULL; + workspace->work_area_screen.x = 0; + workspace->work_area_screen.y = 0; + workspace->work_area_screen.width = 0; + workspace->work_area_screen.height = 0; - workspace->left_struts = NULL; - workspace->right_struts = NULL; - workspace->top_struts = NULL; - workspace->bottom_struts = NULL; + workspace->screen_region = NULL; + workspace->xinerama_region = NULL; + workspace->screen_edges = NULL; + workspace->xinerama_edges = NULL; + + workspace->all_struts = NULL; workspace->showing_desktop = FALSE; @@ -79,6 +81,7 @@ meta_workspace_free (MetaWorkspace *workspace) { GList *tmp; MetaScreen *screen; + int i; g_return_if_fail (workspace != workspace->screen->active_workspace); @@ -107,13 +110,17 @@ meta_workspace_free (MetaWorkspace *workspace) workspace->screen->workspaces = g_list_remove (workspace->screen->workspaces, workspace); - g_free (workspace->work_areas); + g_free (workspace->work_area_xinerama); g_list_free (workspace->mru_list); - g_slist_free (workspace->left_struts); - g_slist_free (workspace->right_struts); - g_slist_free (workspace->top_struts); - g_slist_free (workspace->bottom_struts); + g_slist_free (workspace->all_struts); + + for (i = 0; i < screen->n_xinerama_infos; i++) + meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]); + g_free (workspace->xinerama_region); + meta_rectangle_free_list_and_elements (workspace->screen_region); + meta_rectangle_free_list_and_elements (workspace->screen_edges); + meta_rectangle_free_list_and_elements (workspace->xinerama_edges); g_free (workspace); @@ -429,6 +436,7 @@ meta_workspace_invalidate_work_area (MetaWorkspace *workspace) { GList *tmp; GList *windows; + int i; if (workspace->work_areas_invalid) { @@ -442,17 +450,22 @@ meta_workspace_invalidate_work_area (MetaWorkspace *workspace) "Invalidating work area for workspace %d\n", meta_workspace_index (workspace)); - g_free (workspace->work_areas); - workspace->work_areas = NULL; + g_free (workspace->work_area_xinerama); + workspace->work_area_xinerama = NULL; - g_slist_free (workspace->left_struts); - workspace->left_struts = NULL; - g_slist_free (workspace->right_struts); - workspace->right_struts = NULL; - g_slist_free (workspace->top_struts); - workspace->top_struts = NULL; - g_slist_free (workspace->bottom_struts); - workspace->bottom_struts = NULL; + g_slist_free (workspace->all_struts); + workspace->all_struts = NULL; + + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]); + g_free (workspace->xinerama_region); + meta_rectangle_free_list_and_elements (workspace->screen_region); + meta_rectangle_free_list_and_elements (workspace->screen_edges); + meta_rectangle_free_list_and_elements (workspace->xinerama_edges); + workspace->xinerama_region = NULL; + workspace->screen_region = NULL; + workspace->screen_edges = NULL; + workspace->xinerama_edges = NULL; workspace->work_areas_invalid = TRUE; @@ -491,16 +504,17 @@ ensure_work_areas_validated (MetaWorkspace *workspace) if (!workspace->work_areas_invalid) return; - g_assert (workspace->top_struts == NULL); - g_assert (workspace->bottom_struts == NULL); - g_assert (workspace->left_struts == NULL); - g_assert (workspace->right_struts == NULL); + g_assert (workspace->all_struts == NULL); + g_assert (workspace->xinerama_region == NULL); + g_assert (workspace->screen_region == NULL); + g_assert (workspace->screen_edges == NULL); + g_assert (workspace->xinerama_edges == NULL); windows = meta_workspace_list_windows (workspace); - g_free (workspace->work_areas); - workspace->work_areas = g_new (MetaRectangle, - workspace->screen->n_xinerama_infos); + g_free (workspace->work_area_xinerama); + workspace->work_area_xinerama = g_new (MetaRectangle, + workspace->screen->n_xinerama_infos); i = 0; while (i < workspace->screen->n_xinerama_infos) @@ -528,67 +542,63 @@ ensure_work_areas_validated (MetaWorkspace *workspace) if ((i == 0) && (w->struts->left.width > 0)) { - workspace->left_struts = g_slist_prepend (workspace->left_struts, - &w->struts->left); + workspace->all_struts = g_slist_prepend (workspace->all_struts, + &w->struts->left); } - if (meta_screen_rect_intersects_xinerama (w->screen, - &w->struts->left, - i)) + if (meta_rectangle_overlap (&w->screen->xinerama_infos[i].rect, + &w->struts->left)) { left_strut = MAX (left_strut, w->struts->left.width - - workspace->screen->xinerama_infos[i].x_origin); + workspace->screen->xinerama_infos[i].rect.x); all_left_strut = MAX (all_left_strut, w->struts->left.width); } if ((i == 0) && (w->struts->right.width > 0)) { - workspace->right_struts = g_slist_prepend (workspace->right_struts, - &w->struts->right); + workspace->all_struts = g_slist_prepend (workspace->all_struts, + &w->struts->right); } - if (meta_screen_rect_intersects_xinerama (w->screen, - &w->struts->right, - i)) + if (meta_rectangle_overlap (&w->screen->xinerama_infos[i].rect, + &w->struts->right)) { right_strut = MAX (right_strut, w->struts->right.width - - workspace->screen->width + - workspace->screen->xinerama_infos[i].width + - workspace->screen->xinerama_infos[i].x_origin); + workspace->screen->rect.width + + workspace->screen->xinerama_infos[i].rect.width + + workspace->screen->xinerama_infos[i].rect.x); all_right_strut = MAX (all_right_strut, w->struts->right.width); } if ((i == 0) && (w->struts->top.height > 0)) { - workspace->top_struts = g_slist_prepend (workspace->top_struts, + workspace->all_struts = g_slist_prepend (workspace->all_struts, &w->struts->top); } - if (meta_screen_rect_intersects_xinerama (w->screen, - &w->struts->top, - i)) + if (meta_rectangle_overlap (&w->screen->xinerama_infos[i].rect, + &w->struts->top)) { top_strut = MAX (top_strut, w->struts->top.height - - workspace->screen->xinerama_infos[i].y_origin); + workspace->screen->xinerama_infos[i].rect.y); all_top_strut = MAX (all_top_strut, w->struts->top.height); } if ((i == 0) && (w->struts->bottom.height > 0)) { - workspace->bottom_struts = g_slist_prepend (workspace->bottom_struts, - &w->struts->bottom); + workspace->all_struts = g_slist_prepend (workspace->all_struts, + &w->struts->bottom); } - if (meta_screen_rect_intersects_xinerama (w->screen, - &w->struts->bottom, - i)) + if (meta_rectangle_overlap (&w->screen->xinerama_infos[i].rect, + &w->struts->bottom)) { bottom_strut = MAX (bottom_strut, w->struts->bottom.height - - workspace->screen->height + - workspace->screen->xinerama_infos[i].height + - workspace->screen->xinerama_infos[i].y_origin); + workspace->screen->rect.height + + workspace->screen->xinerama_infos[i].rect.height + + workspace->screen->xinerama_infos[i].rect.y); all_bottom_strut = MAX (all_bottom_strut, w->struts->bottom.height); } } @@ -600,36 +610,36 @@ ensure_work_areas_validated (MetaWorkspace *workspace) #define MIN_SANE_AREA 100 if ((left_strut + right_strut) > - (workspace->screen->xinerama_infos[i].width - MIN_SANE_AREA)) + (workspace->screen->xinerama_infos[i].rect.width - MIN_SANE_AREA)) { meta_topic (META_DEBUG_WORKAREA, "Making left/right struts %d %d sane xinerama %d\n", left_strut, right_strut, i); - left_strut = (workspace->screen->xinerama_infos[i].width - + left_strut = (workspace->screen->xinerama_infos[i].rect.width - MIN_SANE_AREA) / 2; right_strut = left_strut; } if ((top_strut + bottom_strut) > - (workspace->screen->xinerama_infos[i].height - MIN_SANE_AREA)) + (workspace->screen->xinerama_infos[i].rect.height - MIN_SANE_AREA)) { meta_topic (META_DEBUG_WORKAREA, "Making top/bottom struts %d %d sane xinerama %d\n", top_strut, bottom_strut, i); - top_strut = (workspace->screen->xinerama_infos[i].height - + top_strut = (workspace->screen->xinerama_infos[i].rect.height - MIN_SANE_AREA) / 2; bottom_strut = top_strut; } - workspace->work_areas[i].x = - left_strut + workspace->screen->xinerama_infos[i].x_origin; - workspace->work_areas[i].y = top_strut + - workspace->screen->xinerama_infos[i].y_origin; - workspace->work_areas[i].width = - workspace->screen->xinerama_infos[i].width - + workspace->work_area_xinerama[i].x = + left_strut + workspace->screen->xinerama_infos[i].rect.x; + workspace->work_area_xinerama[i].y = top_strut + + workspace->screen->xinerama_infos[i].rect.y; + workspace->work_area_xinerama[i].width = + workspace->screen->xinerama_infos[i].rect.width - left_strut - right_strut; - workspace->work_areas[i].height = - workspace->screen->xinerama_infos[i].height - + workspace->work_area_xinerama[i].height = + workspace->screen->xinerama_infos[i].rect.height - top_strut - bottom_strut; meta_topic (META_DEBUG_WORKAREA, @@ -637,10 +647,10 @@ ensure_work_areas_validated (MetaWorkspace *workspace) "xinerama %d: %d,%d %d x %d\n", meta_workspace_index (workspace), i, - workspace->work_areas[i].x, - workspace->work_areas[i].y, - workspace->work_areas[i].width, - workspace->work_areas[i].height); + workspace->work_area_xinerama[i].x, + workspace->work_area_xinerama[i].y, + workspace->work_area_xinerama[i].width, + workspace->work_area_xinerama[i].height); ++i; } @@ -648,41 +658,91 @@ ensure_work_areas_validated (MetaWorkspace *workspace) g_list_free (windows); if ((all_left_strut + all_right_strut) > - (workspace->screen->width - MIN_SANE_AREA)) + (workspace->screen->rect.width - MIN_SANE_AREA)) { meta_topic (META_DEBUG_WORKAREA, "Making screen-wide left/right struts %d %d sane\n", all_left_strut, all_right_strut); - all_left_strut = (workspace->screen->width - MIN_SANE_AREA) / 2; + all_left_strut = (workspace->screen->rect.width - MIN_SANE_AREA) / 2; all_right_strut = all_left_strut; } if ((all_top_strut + all_bottom_strut) > - (workspace->screen->height - MIN_SANE_AREA)) + (workspace->screen->rect.height - MIN_SANE_AREA)) { meta_topic (META_DEBUG_WORKAREA, "Making top/bottom struts %d %d sane\n", all_top_strut, all_bottom_strut); - all_top_strut = (workspace->screen->height - MIN_SANE_AREA) / 2; + all_top_strut = (workspace->screen->rect.height - MIN_SANE_AREA) / 2; all_bottom_strut = all_top_strut; } - workspace->all_work_areas.x = all_left_strut; - workspace->all_work_areas.y = all_top_strut; - workspace->all_work_areas.width = - workspace->screen->width - all_left_strut - all_right_strut; - workspace->all_work_areas.height = - workspace->screen->height - all_top_strut - all_bottom_strut; - + workspace->work_area_screen.x = all_left_strut; + workspace->work_area_screen.y = all_top_strut; + workspace->work_area_screen.width = + workspace->screen->rect.width - all_left_strut - all_right_strut; + workspace->work_area_screen.height = + workspace->screen->rect.height - all_top_strut - all_bottom_strut; + + /* Now cache the spanning rects for the onscreen and + * on-single-xinerama regions + */ + g_assert (workspace->xinerama_region == NULL); + g_assert (workspace->screen_region == NULL); + + workspace->xinerama_region = g_new (GList*, + workspace->screen->n_xinerama_infos); + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + { + workspace->xinerama_region[i] = + meta_rectangle_get_minimal_spanning_set_for_region ( + &workspace->screen->xinerama_infos[i].rect, + workspace->all_struts); + } + workspace->screen_region = + meta_rectangle_get_minimal_spanning_set_for_region ( + &workspace->screen->rect, + workspace->all_struts); + /* Since get_minimal_spanning_set_for_region() doesn't do the + * MIN_SANE_AREA thing, manually account for it. Note that it's okay if + * get_minimal_spanning_set_for_region() returns a very small region--all + * we really need here is a 1x1 region that corresponds to somewhere on + * the monitor for the partially onscreen constraint. If we don't get + * anything, though, we use work_area_screen just for convenience. + */ + if (workspace->screen_region == NULL) + { + MetaRectangle *nonempty_region; + nonempty_region = g_new (MetaRectangle, 1); + *nonempty_region = workspace->work_area_screen; + workspace->screen_region = g_list_prepend (NULL, nonempty_region); + } + + /* Now cache the screen and xinerama edges for edge resistance and snapping */ + g_assert (workspace->screen_edges == NULL); + g_assert (workspace->xinerama_edges == NULL); + workspace->screen_edges = + meta_rectangle_find_onscreen_edges (&workspace->screen->rect, + workspace->all_struts); + tmp = NULL; + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect); + workspace->xinerama_edges = + meta_rectangle_find_nonintersected_xinerama_edges (tmp, + workspace->all_struts); + g_list_free (tmp); + + + /* We're all done, YAAY! Record that everything has been validated. */ workspace->work_areas_invalid = FALSE; meta_topic (META_DEBUG_WORKAREA, "Computed work area for workspace %d: %d,%d %d x %d\n", meta_workspace_index (workspace), - workspace->all_work_areas.x, - workspace->all_work_areas.y, - workspace->all_work_areas.width, - workspace->all_work_areas.height); + workspace->work_area_screen.x, + workspace->work_area_screen.y, + workspace->work_area_screen.width, + workspace->work_area_screen.height); } void @@ -695,7 +755,7 @@ meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace, ensure_work_areas_validated (workspace); g_assert (which_xinerama < workspace->screen->n_xinerama_infos); - *area = workspace->work_areas[which_xinerama]; + *area = workspace->work_area_xinerama[which_xinerama]; } void @@ -704,7 +764,24 @@ meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, { ensure_work_areas_validated (workspace); - *area = workspace->all_work_areas; + *area = workspace->work_area_screen; +} + +GList* +meta_workspace_get_onscreen_region (MetaWorkspace *workspace) +{ + ensure_work_areas_validated (workspace); + + return workspace->screen_region; +} + +GList* +meta_workspace_get_onxinerama_region (MetaWorkspace *workspace, + int which_xinerama) +{ + ensure_work_areas_validated (workspace); + + return workspace->xinerama_region[which_xinerama]; } #ifdef WITH_VERBOSE_MODE diff --git a/src/workspace.h b/src/workspace.h index 311c94d01..3c3e8d81f 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2001 Havoc Pennington - * Copyright (C) 2004 Elijah Newren + * Copyright (C) 2004, 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -43,12 +43,13 @@ struct _MetaWorkspace GList *windows; GList *mru_list; - MetaRectangle all_work_areas; - MetaRectangle *work_areas; - GSList *left_struts; - GSList *right_struts; - GSList *top_struts; - GSList *bottom_struts; + MetaRectangle work_area_screen; + MetaRectangle *work_area_xinerama; + GList *screen_region; + GList **xinerama_region; + GList *screen_edges; + GList *xinerama_edges; + GSList *all_struts; guint work_areas_invalid : 1; guint showing_desktop : 1; @@ -78,6 +79,11 @@ void meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace, MetaRectangle *area); void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, MetaRectangle *area); +GList* meta_workspace_get_onscreen_region (MetaWorkspace *workspace); +GList* meta_workspace_get_onxinerama_region (MetaWorkspace *workspace, + int which_xinerama); +void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, + MetaRectangle *area); void meta_workspace_focus_default_window (MetaWorkspace *workspace, MetaWindow *not_this_one,