a7201d27d1
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()
1084 lines
42 KiB
C
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;
|
|
}
|