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,