mutter/src/constraints.c
Elijah Newren a7201d27d1 Merge of all the changes on the constraints_experiments branch. This is
2005-11-18  Elijah Newren  <newren@gmail.com>

	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-19 14:58:50 +00:00

1084 lines
42 KiB
C

/* Metacity size/position constraints */
/*
* 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
* 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 <config.h>
#include "constraints.h"
#include "workspace.h"
#include "place.h"
#include <stdlib.h>
#include <math.h>
#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
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;
typedef enum
{
ACTION_MOVE,
ACTION_RESIZE,
ACTION_MOVE_AND_RESIZE
} ActionType;
typedef struct
{
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;
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);
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 (* ConstraintFunc) (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
gboolean check_only);
typedef struct {
ConstraintFunc func;
const char* name;
} Constraint;
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}
};
static gboolean
do_all_constraints (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
gboolean check_only)
{
const Constraint *constraint;
gboolean satisfied;
constraint = &all_constraints[0];
satisfied = TRUE;
while (constraint->func != NULL)
{
satisfied = satisfied &&
(*constraint->func) (window, info, priority, check_only);
if (!check_only)
{
/* 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);
}
else if (!satisfied)
{
/* Log which constraint was not satisfied */
meta_topic (META_DEBUG_GEOMETRY,
"constraint %s not satisfied.\n",
constraint->name);
return FALSE;
}
++constraint;
}
return TRUE;
}
void
meta_window_constrain (MetaWindow *window,
MetaFrameGeometry *orig_fgeom,
MetaMoveResizeFlags flags,
int resize_gravity,
const MetaRectangle *orig,
MetaRectangle *new)
{
ConstraintInfo info;
/* 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 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;
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->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);
const MetaXineramaScreenInfo *xinerama_info =
meta_screen_get_xinerama_for_window (window->screen, window);
info->entire_xinerama = xinerama_info->rect;
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
* maximized/fullscreen windows until they are unmaximized
* and unfullscreened
*/
did_placement = FALSE;
if (!window->placed &&
window->calc_placement &&
!META_WINDOW_MAXIMIZED (window) &&
!window->fullscreen)
{
MetaRectangle placed_rect = info->orig;
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
*/
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,
xinerama_info->number,
&info->work_area_xinerama);
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_horizontally_after_placement ||
window->maximize_vertically_after_placement) &&
(window->placed || did_placement))
{
/* 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 (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)
{
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;
}
}
static void
update_onscreen_requirements (MetaWindow *window,
ConstraintInfo *info)
{
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:
fudge = 2;
break;
case NorthWestGravity:
case SouthWestGravity:
case CenterGravity:
case NorthEastGravity:
case SouthEastGravity:
case StaticGravity:
default:
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;
/*** 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 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 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:
/* 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;
}