1
0
Fork 0
mutter-performance-source/src/constraints.c
Elijah Newren 9641bc5ee7 compute the frame geometry due to maximization only after actually
2005-11-20  Elijah Newren  <newren@gmail.com>

	* src/constraints.c (place_window_if_needed): compute the frame
	geometry due to maximization only after actually maximizing.
	Fixes #321902.
2005-11-21 06:00:18 +00:00

1083 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;
}
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;
}
/* maximization may have changed frame geometry */
if (window->frame && !window->fullscreen)
meta_frame_calc_geometry (window->frame, info->fgeom);
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;
}