diff options
author | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2014-05-12 19:39:04 +0300 |
---|---|---|
committer | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2014-06-06 17:26:03 +0300 |
commit | 0eb770fe5b96144e9c79487bf13cb8448df72f32 (patch) | |
tree | b196f4c5ac0f82b27cde91e8dc9cbe3a147bd0da | |
parent | 5be337ce674fe6cd955bc5c2077e86ad6c642601 (diff) | |
download | metacity-0eb770fe5b96144e9c79487bf13cb8448df72f32.tar.gz |
Add side-by-side tiling
1. Manually applied this patch:
https://github.com/SolusOS-discontinued/consortium/commit/b463e03f5bdeab307ceee6b969c681f29537c76d
2. Ported tile-preview.c to gtk3.
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/core/boxes.c | 125 | ||||
-rw-r--r-- | src/core/constraints.c | 70 | ||||
-rw-r--r-- | src/core/core.c | 30 | ||||
-rw-r--r-- | src/core/display-private.h | 12 | ||||
-rw-r--r-- | src/core/display.c | 27 | ||||
-rw-r--r-- | src/core/frame.c | 6 | ||||
-rw-r--r-- | src/core/keybindings.c | 43 | ||||
-rw-r--r-- | src/core/prefs.c | 18 | ||||
-rw-r--r-- | src/core/screen-private.h | 9 | ||||
-rw-r--r-- | src/core/screen.c | 96 | ||||
-rw-r--r-- | src/core/testboxes.c | 8 | ||||
-rw-r--r-- | src/core/window-private.h | 23 | ||||
-rw-r--r-- | src/core/window.c | 289 | ||||
-rw-r--r-- | src/core/workspace.c | 3 | ||||
-rw-r--r-- | src/include/boxes.h | 1 | ||||
-rw-r--r-- | src/include/common.h | 4 | ||||
-rw-r--r-- | src/include/core.h | 4 | ||||
-rw-r--r-- | src/include/prefs.h | 2 | ||||
-rw-r--r-- | src/include/tile-preview.h | 37 | ||||
-rw-r--r-- | src/include/ui.h | 1 | ||||
-rw-r--r-- | src/metacity-schemas.convert | 1 | ||||
-rw-r--r-- | src/org.gnome.metacity.gschema.xml.in | 9 | ||||
-rw-r--r-- | src/ui/theme-parser.c | 44 | ||||
-rw-r--r-- | src/ui/theme.c | 63 | ||||
-rw-r--r-- | src/ui/theme.h | 8 | ||||
-rw-r--r-- | src/ui/tile-preview.c | 245 |
27 files changed, 1037 insertions, 143 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 29a7de2f..b11bb44a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -91,6 +91,8 @@ metacity_SOURCES= \ include/resizepopup.h \ ui/tabpopup.c \ include/tabpopup.h \ + ui/tile-preview.c \ + include/tile-preview.h \ ui/theme-parser.c \ ui/theme-parser.h \ ui/theme.c \ diff --git a/src/core/boxes.c b/src/core/boxes.c index 147bd80a..55fd7b3d 100644 --- a/src/core/boxes.c +++ b/src/core/boxes.c @@ -1793,6 +1793,7 @@ meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect, GList* meta_rectangle_find_nonintersected_xinerama_edges ( + const MetaRectangle *screen_rect, const GList *xinerama_rects, const GSList *all_struts) { @@ -1815,99 +1816,41 @@ meta_rectangle_find_nonintersected_xinerama_edges ( while (cur) { MetaRectangle *cur_rect = cur->data; - const GList *compare = xinerama_rects; - while (compare) - { - MetaRectangle *compare_rect = compare->data; - - /* Check if cur might be horizontally adjacent to compare */ - if (meta_rectangle_vert_overlap(cur_rect, compare_rect)) - { - MetaSide side_type; - int y = MAX (cur_rect->y, compare_rect->y); - int height = MIN (BOX_BOTTOM (*cur_rect) - y, - BOX_BOTTOM (*compare_rect) - y); - int width = 0; - int x; - - if (BOX_LEFT (*cur_rect) == BOX_RIGHT (*compare_rect)) - { - /* compare_rect is to the left of cur_rect */ - x = BOX_LEFT (*cur_rect); - side_type = META_SIDE_LEFT; - } - else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect)) - { - /* compare_rect is to the right of cur_rect */ - x = BOX_RIGHT (*cur_rect); - side_type = META_SIDE_RIGHT; - } - else - /* These rectangles aren't adjacent after all */ - x = INT_MIN; + MetaEdge *new_edge; - /* If the rectangles really are adjacent */ - if (x != INT_MIN) - { - /* We need a left edge for the xinerama on the right, and - * a right edge for the xinerama on the left. Just fill - * up the edges and stick 'em on the list. - */ - MetaEdge *new_edge = g_new (MetaEdge, 1); - - new_edge->rect = meta_rect (x, y, width, height); - new_edge->side_type = side_type; - new_edge->edge_type = META_EDGE_XINERAMA; - - ret = g_list_prepend (ret, new_edge); - } - } - - /* Check if cur might be vertically adjacent to compare */ - if (meta_rectangle_horiz_overlap(cur_rect, compare_rect)) - { - MetaSide side_type; - int x = MAX (cur_rect->x, compare_rect->x); - int width = MIN (BOX_RIGHT (*cur_rect) - x, - BOX_RIGHT (*compare_rect) - x); - int height = 0; - int y; - - if (BOX_TOP (*cur_rect) == BOX_BOTTOM (*compare_rect)) - { - /* compare_rect is to the top of cur_rect */ - y = BOX_TOP (*cur_rect); - side_type = META_SIDE_TOP; - } - else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect)) - { - /* compare_rect is to the bottom of cur_rect */ - y = BOX_BOTTOM (*cur_rect); - side_type = META_SIDE_BOTTOM; - } - else - /* These rectangles aren't adjacent after all */ - y = INT_MIN; - - /* If the rectangles really are adjacent */ - if (y != INT_MIN) - { - /* We need a top edge for the xinerama on the bottom, and - * a bottom edge for the xinerama on the top. Just fill - * up the edges and stick 'em on the list. - */ - MetaEdge *new_edge = g_new (MetaEdge, 1); - - new_edge->rect = meta_rect (x, y, width, height); - new_edge->side_type = side_type; - new_edge->edge_type = META_EDGE_XINERAMA; - - ret = g_list_prepend (ret, new_edge); - } - } - - compare = compare->next; + if (BOX_LEFT(*cur_rect) != BOX_LEFT(*screen_rect)) + { + new_edge = g_new (MetaEdge, 1); + new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_TOP (*cur_rect), 0, cur_rect->height); + new_edge->side_type = META_SIDE_LEFT; + new_edge->edge_type = META_EDGE_XINERAMA; + ret = g_list_prepend (ret, new_edge); } + if (BOX_RIGHT(*cur_rect) != BOX_RIGHT(*screen_rect)) + { + new_edge = g_new (MetaEdge, 1); + new_edge->rect = meta_rect (BOX_RIGHT (*cur_rect), BOX_TOP (*cur_rect), 0, cur_rect->height); + new_edge->side_type = META_SIDE_RIGHT; + new_edge->edge_type = META_EDGE_XINERAMA; + ret = g_list_prepend (ret, new_edge); + } + if (BOX_TOP(*cur_rect) != BOX_TOP(*screen_rect)) + { + new_edge = g_new (MetaEdge, 1); + new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_TOP (*cur_rect), cur_rect->width, 0); + new_edge->side_type = META_SIDE_TOP; + new_edge->edge_type = META_EDGE_XINERAMA; + ret = g_list_prepend (ret, new_edge); + } + if (BOX_BOTTOM(*cur_rect) != BOX_BOTTOM(*screen_rect)) + { + new_edge = g_new (MetaEdge, 1); + new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_BOTTOM (*cur_rect), cur_rect->width, 0); + new_edge->side_type = META_SIDE_BOTTOM; + new_edge->edge_type = META_EDGE_XINERAMA; + ret = g_list_prepend (ret, new_edge); + } + cur = cur->next; } diff --git a/src/core/constraints.c b/src/core/constraints.c index 7c2960cc..c7838472 100644 --- a/src/core/constraints.c +++ b/src/core/constraints.c @@ -96,6 +96,7 @@ typedef enum PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1, PRIORITY_SIZE_HINTS_INCREMENTS = 1, PRIORITY_MAXIMIZATION = 2, + PRIORITY_TILING = 2, PRIORITY_FULLSCREEN = 2, PRIORITY_SIZE_HINTS_LIMITS = 3, PRIORITY_TITLEBAR_VISIBLE = 4, @@ -143,6 +144,10 @@ static gboolean constrain_maximization (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, gboolean check_only); +static gboolean constrain_tiling (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); static gboolean constrain_fullscreen (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, @@ -209,6 +214,7 @@ typedef struct { static const Constraint all_constraints[] = { {constrain_maximization, "constrain_maximization"}, + {constrain_tiling, "constrain_tiling"}, {constrain_fullscreen, "constrain_fullscreen"}, {constrain_size_increments, "constrain_size_increments"}, {constrain_size_limits, "constrain_size_limits"}, @@ -742,12 +748,15 @@ constrain_maximization (MetaWindow *window, return TRUE; /* Determine whether constraint applies; exit if it doesn't */ - if (!window->maximized_horizontally && !window->maximized_vertically) + if ((!window->maximized_horizontally && !window->maximized_vertically) || + META_WINDOW_TILED_SIDE_BY_SIDE (window)) return TRUE; /* Calculate target_size = maximized size of (window + frame) */ - if (window->maximized_horizontally && window->maximized_vertically) - target_size = info->work_area_xinerama; + if (META_WINDOW_MAXIMIZED (window)) + { + target_size = info->work_area_xinerama; + } else { /* Amount of maximization possible in a single direction depends @@ -810,6 +819,59 @@ constrain_maximization (MetaWindow *window, return TRUE; } + +static gboolean +constrain_tiling (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle target_size; + MetaRectangle min_size, max_size; + gboolean hminbad, vminbad; + gboolean horiz_equal, vert_equal; + gboolean constraint_already_satisfied; + + if (priority > PRIORITY_TILING) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + return TRUE; + + /* Calculate target_size - as the tile previews need this as well, we + * use an external function for the actual calculation + */ + meta_window_get_current_tile_area (window, &target_size); + unextend_by_frame (&target_size, info->fgeom); + + /* Check min size constraints; max size constraints are ignored as for + * maximized windows. + */ + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + hminbad = target_size.width < min_size.width; + vminbad = target_size.height < min_size.height; + if (hminbad || vminbad) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + horiz_equal = target_size.x == info->current.x && + target_size.width == info->current.width; + vert_equal = target_size.y == info->current.y && + target_size.height == info->current.height; + constraint_already_satisfied = horiz_equal && vert_equal; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + info->current.x = target_size.x; + info->current.width = target_size.width; + info->current.y = target_size.y; + info->current.height = target_size.height; + + return TRUE; +} + static gboolean constrain_fullscreen (MetaWindow *window, ConstraintInfo *info, @@ -861,6 +923,7 @@ constrain_size_increments (MetaWindow *window, /* Determine whether constraint applies; exit if it doesn't */ if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || + META_WINDOW_TILED_SIDE_BY_SIDE (window) || info->action_type == ACTION_MOVE) return TRUE; @@ -992,6 +1055,7 @@ constrain_aspect_ratio (MetaWindow *window, constraints_are_inconsistent = minr > maxr; if (constraints_are_inconsistent || META_WINDOW_MAXIMIZED (window) || window->fullscreen || + META_WINDOW_TILED_SIDE_BY_SIDE (window) || info->action_type == ACTION_MOVE) return TRUE; diff --git a/src/core/core.c b/src/core/core.c index b7806d4e..509b3520 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -26,6 +26,7 @@ #include "frame-private.h" #include "workspace.h" #include "prefs.h" +#include "errors.h" /* Looks up the MetaWindow representing the frame of the given X window. * Used as a helper function by a bunch of the functions below. @@ -295,6 +296,35 @@ meta_core_user_lower_and_unfocus (Display *xdisplay, } void +meta_core_lower_beneath_focus_window (Display *xdisplay, + Window xwindow, + guint32 timestamp) +{ + XWindowChanges changes; + MetaDisplay *display; + MetaScreen *screen; + MetaWindow *focus_window; + + display = meta_display_for_x_display (xdisplay); + screen = meta_display_screen_for_xwindow (display, xwindow); + focus_window = meta_stack_get_top (screen->stack); + + if (focus_window == NULL) + return; + + changes.stack_mode = Below; + changes.sibling = focus_window->frame ? focus_window->frame->xwindow + : focus_window->xwindow; + + meta_error_trap_push (display); + XConfigureWindow (xdisplay, + xwindow, + CWSibling | CWStackMode, + &changes); + meta_error_trap_pop (display, FALSE); +} + +void meta_core_user_focus (Display *xdisplay, Window frame_xwindow, guint32 timestamp) diff --git a/src/core/display-private.h b/src/core/display-private.h index 6bf2c4db..31cc9280 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -70,6 +70,13 @@ typedef void (* MetaWindowPingFunc) (MetaDisplay *display, */ #define N_IGNORED_SERIALS 4 +typedef enum { + META_TILE_NONE, + META_TILE_LEFT, + META_TILE_RIGHT, + META_TILE_MAXIMIZED /* only used for previews */ +} MetaTileMode; + struct _MetaDisplay { char *name; @@ -160,6 +167,8 @@ struct _MetaDisplay int grab_anchor_root_x; int grab_anchor_root_y; MetaRectangle grab_anchor_window_pos; + MetaTileMode grab_tile_mode; + int grab_tile_monitor_number; int grab_latest_motion_x; int grab_latest_motion_y; gulong grab_mask; @@ -170,6 +179,9 @@ struct _MetaDisplay guint grab_frame_action : 1; MetaRectangle grab_wireframe_rect; MetaRectangle grab_wireframe_last_xor_rect; + /* During a resize operation, the directions in which we've broken + * out of the initial maximization state */ + guint grab_resize_unmaximize : 2; /* MetaMaximizeFlags */ MetaRectangle grab_initial_window_pos; int grab_initial_x, grab_initial_y; /* These are only relevant for */ gboolean grab_threshold_movement_reached; /* raise_on_click == FALSE. */ diff --git a/src/core/display.c b/src/core/display.c index 7619e79f..f4075838 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -457,6 +457,8 @@ meta_display_open (void) the_display->grab_window = NULL; the_display->grab_screen = NULL; the_display->grab_resize_popup = NULL; + the_display->grab_tile_mode = META_TILE_NONE; + the_display->grab_tile_monitor_number = -1; the_display->grab_edge_resistance_data = NULL; @@ -3308,6 +3310,16 @@ meta_display_begin_grab_op (MetaDisplay *display, display->grab_xwindow = grab_xwindow; display->grab_button = button; display->grab_mask = modmask; + if (window) + { + display->grab_tile_mode = window->tile_mode; + display->grab_tile_monitor_number = window->tile_monitor_number; + } + else + { + display->grab_tile_mode = META_TILE_NONE; + display->grab_tile_monitor_number = -1; + } display->grab_anchor_root_x = root_x; display->grab_anchor_root_y = root_y; display->grab_latest_motion_x = root_x; @@ -3493,7 +3505,12 @@ meta_display_end_grab_op (MetaDisplay *display, if (display->grab_window != NULL) display->grab_window->shaken_loose = FALSE; - + + /*if(display->grab_window != NULL && display->grab_window->tile_mode == META_TILE_MAXIMIZED) + { + display->grab_window->tile_mode = META_TILE_NONE; + }*/ + if (display->grab_window != NULL && !meta_prefs_get_raise_on_click () && (meta_grab_op_is_moving (display->grab_op) || @@ -3595,10 +3612,16 @@ meta_display_end_grab_op (MetaDisplay *display, display->grab_sync_request_alarm = None; } #endif /* HAVE_XSYNC */ - + + /* Hide the tile preview if it exists */ + if (display->grab_screen->tile_preview) + meta_tile_preview_hide (display->grab_screen->tile_preview); + display->grab_window = NULL; display->grab_screen = NULL; display->grab_xwindow = None; + display->grab_tile_mode = META_TILE_NONE; + display->grab_tile_monitor_number = -1; display->grab_op = META_GRAB_OP_NONE; if (display->grab_resize_popup) diff --git a/src/core/frame.c b/src/core/frame.c index 0459bb6a..b5749480 100644 --- a/src/core/frame.c +++ b/src/core/frame.c @@ -280,6 +280,12 @@ meta_frame_get_flags (MetaFrame *frame) if (META_WINDOW_MAXIMIZED (frame->window)) flags |= META_FRAME_MAXIMIZED; + if (META_WINDOW_TILED_LEFT (frame->window)) + flags |= META_FRAME_TILED_LEFT; + + if (META_WINDOW_TILED_RIGHT (frame->window)) + flags |= META_FRAME_TILED_RIGHT; + if (frame->window->fullscreen) flags |= META_FRAME_FULLSCREEN; diff --git a/src/core/keybindings.c b/src/core/keybindings.c index a2a46599..e0ff5f68 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -1444,6 +1444,10 @@ process_mouse_move_resize_grab (MetaDisplay *display, if (keysym == XK_Escape) { + /* Restore the original tile mode */ + window->tile_mode = display->grab_tile_mode; + window->tile_monitor_number = display->grab_tile_monitor_number; + /* End move or resize and restore to original state. If the * window was a maximized window that had been "shaken loose" we * need to remaximize it. In normal cases, we need to do a @@ -1455,6 +1459,8 @@ process_mouse_move_resize_grab (MetaDisplay *display, meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); + else if (window->tile_mode == META_TILE_LEFT || window->tile_mode == META_TILE_RIGHT) + meta_window_tile (window); else if (!display->grab_wireframe_active) meta_window_move_resize (display->grab_window, TRUE, @@ -2918,6 +2924,43 @@ handle_toggle_above (MetaDisplay *display, meta_window_make_above (window); } +/* TODO: actually use this keybinding, without messing up the existing keybinding schema */ +static void +handle_toggle_tiled (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + MetaTileMode mode = binding->handler->data; + + if ((META_WINDOW_TILED_LEFT (window) && mode == META_TILE_LEFT) || + (META_WINDOW_TILED_RIGHT (window) && mode == META_TILE_RIGHT)) + { + window->tile_mode = META_TILE_NONE; + + if (window->saved_maximize) + meta_window_maximize (window, META_MAXIMIZE_VERTICAL | + META_MAXIMIZE_HORIZONTAL); + else + meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL | + META_MAXIMIZE_HORIZONTAL); + } + else if (meta_window_can_tile_side_by_side (window)) + { + window->tile_mode = mode; + window->tile_monitor_number = meta_screen_get_xinerama_for_window (window->screen, window)->number; + /* Maximization constraints beat tiling constraints, so if the window + * is maximized, tiling won't have any effect unless we unmaximize it + * horizontally first; rather than calling meta_window_unmaximize(), + * we just set the flag and rely on meta_window_tile() syncing it to + * save an additional roundtrip. + */ + window->maximized_horizontally = FALSE; + meta_window_tile (window); + } +} + static void handle_toggle_maximized (MetaDisplay *display, MetaScreen *screen, diff --git a/src/core/prefs.c b/src/core/prefs.c index 3e931406..a76fbc46 100644 --- a/src/core/prefs.c +++ b/src/core/prefs.c @@ -88,6 +88,7 @@ static char *cursor_theme = NULL; static int cursor_size = 24; static gboolean compositing_manager = FALSE; static gboolean resize_with_right_button = FALSE; +static gboolean edge_tiling = FALSE; static gboolean force_fullscreen = TRUE; static GDesktopVisualBellType visual_bell_type = G_DESKTOP_VISUAL_BELL_FULLSCREEN_FLASH; @@ -353,6 +354,14 @@ static MetaBoolPreference preferences_bool[] = &resize_with_right_button, FALSE, }, + { + { "edge-tiling", + SCHEMA_METACITY, + META_PREF_EDGE_TILING, + }, + &edge_tiling, + FALSE, + }, { { NULL, 0, 0 }, NULL, FALSE }, }; @@ -1416,6 +1425,9 @@ meta_preference_to_string (MetaPreference pref) case META_PREF_RESIZE_WITH_RIGHT_BUTTON: return "RESIZE_WITH_RIGHT_BUTTON"; + case META_PREF_EDGE_TILING: + return "EDGE_TILING"; + case META_PREF_FORCE_FULLSCREEN: return "FORCE_FULLSCREEN"; @@ -1746,6 +1758,12 @@ meta_prefs_get_gnome_animations () return gnome_animations; } +gboolean +meta_prefs_get_edge_tiling () +{ + return edge_tiling; +} + MetaKeyBindingAction meta_prefs_get_keybinding_action (const char *name) { diff --git a/src/core/screen-private.h b/src/core/screen-private.h index 326cf61b..f85e0a83 100644 --- a/src/core/screen-private.h +++ b/src/core/screen-private.h @@ -77,7 +77,10 @@ struct _MetaScreen MetaRectangle rect; /* Size of screen; rect.x & rect.y are always 0 */ MetaUI *ui; MetaTabPopup *tab_popup; - + MetaTilePreview *tile_preview; + + guint tile_preview_timeout_id; + MetaWorkspace *active_workspace; /* This window holds the focus when we don't want to focus @@ -159,6 +162,10 @@ void meta_screen_ensure_tab_popup (MetaScreen *scree MetaTabShowType show_type); void meta_screen_ensure_workspace_popup (MetaScreen *screen); +void meta_screen_tile_preview_update (MetaScreen *screen, + gboolean delay); +void meta_screen_tile_preview_hide (MetaScreen *screen); + MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen, MetaWindow *not_this_one); diff --git a/src/core/screen.c b/src/core/screen.c index a471e1a7..c1e886b0 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -587,7 +587,10 @@ meta_screen_new (MetaDisplay *display, screen->xscreen); screen->tab_popup = NULL; - + screen->tile_preview = NULL; + + screen->tile_preview_timeout_id = 0; + screen->stack = meta_stack_new (screen); meta_prefs_add_listener (prefs_changed_callback, screen); @@ -696,7 +699,13 @@ meta_screen_free (MetaScreen *screen, if (screen->xinerama_infos) g_free (screen->xinerama_infos); - + + if (screen->tile_preview_timeout_id) + g_source_remove (screen->tile_preview_timeout_id); + + if (screen->tile_preview) + meta_tile_preview_free (screen->tile_preview); + g_free (screen->screen_name); g_free (screen); @@ -1389,6 +1398,89 @@ meta_screen_ensure_workspace_popup (MetaScreen *screen) /* don't show tab popup, since proper space isn't selected yet */ } +static gboolean +meta_screen_tile_preview_update_timeout (gpointer data) +{ + MetaScreen *screen = data; + MetaWindow *window = screen->display->grab_window; + gboolean composited = screen->display->compositor != NULL; + gboolean needs_preview = FALSE; + + screen->tile_preview_timeout_id = 0; + + if (!screen->tile_preview) + screen->tile_preview = meta_tile_preview_new (screen->number, + composited); + + if (window) + { + switch (window->tile_mode) + { + case META_TILE_LEFT: + case META_TILE_RIGHT: + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + needs_preview = TRUE; + break; + + case META_TILE_MAXIMIZED: + if (!META_WINDOW_MAXIMIZED (window)) + needs_preview = TRUE; + break; + + default: + needs_preview = FALSE; + break; + } + } + + if (needs_preview) + { + MetaRectangle tile_rect; + + meta_window_get_current_tile_area (window, &tile_rect); + meta_tile_preview_show (screen->tile_preview, &tile_rect); + } + else + meta_tile_preview_hide (screen->tile_preview); + + return FALSE; +} + +#define TILE_PREVIEW_TIMEOUT_MS 200 + +void +meta_screen_tile_preview_update (MetaScreen *screen, + gboolean delay) +{ + if (delay) + { + if (screen->tile_preview_timeout_id > 0) + return; + + screen->tile_preview_timeout_id = + g_timeout_add (TILE_PREVIEW_TIMEOUT_MS, + meta_screen_tile_preview_update_timeout, + screen); + } + else + { + if (screen->tile_preview_timeout_id > 0) + g_source_remove (screen->tile_preview_timeout_id); + + meta_screen_tile_preview_update_timeout ((gpointer)screen); + } +} + +void +meta_screen_tile_preview_hide (MetaScreen *screen) +{ + if (screen->tile_preview_timeout_id > 0) + g_source_remove (screen->tile_preview_timeout_id); + + if (screen->tile_preview) + meta_tile_preview_hide (screen->tile_preview); +} + MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen, MetaWindow *not_this_one) diff --git a/src/core/testboxes.c b/src/core/testboxes.c index 953a66a7..670731ea 100644 --- a/src/core/testboxes.c +++ b/src/core/testboxes.c @@ -345,8 +345,14 @@ get_xinerama_edges (int which_xinerama_set, int which_strut_set) ret = NULL; + MetaRectangle screenrect; + screenrect.x = 0; + screenrect.y = 0; + screenrect.width = 1600; + screenrect.height = 1200; + struts = get_strut_list (which_strut_set); - ret = meta_rectangle_find_nonintersected_xinerama_edges (xins, struts); + ret = meta_rectangle_find_nonintersected_xinerama_edges (&screenrect, xins, struts); free_strut_list (struts); meta_rectangle_free_list_and_elements (xins); diff --git a/src/core/window-private.h b/src/core/window-private.h index 85e3c5bc..30f410d2 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -136,6 +136,15 @@ struct _MetaWindow guint maximize_vertically_after_placement : 1; guint minimize_after_placement : 1; + /* The current or requested tile mode. If maximized_vertically is true, + * this is the current mode. If not, it is the mode which will be + * requested after the window grab is released */ + guint tile_mode : 2; + /* The last "full" maximized/unmaximized state. We need to keep track of + * that to toggle between normal/tiled or maximized/tiled states. */ + guint saved_maximize : 1; + int tile_monitor_number; + /* Whether we're shaded */ guint shaded : 1; @@ -381,8 +390,15 @@ struct _MetaWindow (w)->maximized_vertically) #define META_WINDOW_MAXIMIZED_VERTICALLY(w) ((w)->maximized_vertically) #define META_WINDOW_MAXIMIZED_HORIZONTALLY(w) ((w)->maximized_horizontally) +#define META_WINDOW_TILED_SIDE_BY_SIDE(w) ((w)->maximized_vertically && \ + !(w)->maximized_horizontally && \ + (w)->tile_mode != META_TILE_NONE) +#define META_WINDOW_TILED_LEFT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_LEFT) +#define META_WINDOW_TILED_RIGHT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_RIGHT) #define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen) -#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen && !(w)->shaded) +#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !META_WINDOW_TILED_SIDE_BY_SIDE(w) && !(w)->fullscreen && !(w)->shaded) #define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \ (((w)->size_hints.min_width < (w)->size_hints.max_width) || \ ((w)->size_hints.min_height < (w)->size_hints.max_height))) @@ -401,6 +417,7 @@ void meta_window_free (MetaWindow *window, void meta_window_calc_showing (MetaWindow *window); void meta_window_queue (MetaWindow *window, guint queuebits); +void meta_window_tile (MetaWindow *window); void meta_window_minimize (MetaWindow *window); void meta_window_unminimize (MetaWindow *window); void meta_window_maximize (MetaWindow *window, @@ -573,6 +590,8 @@ void meta_window_get_work_area_for_xinerama (MetaWindow *window, void meta_window_get_work_area_all_xineramas (MetaWindow *window, MetaRectangle *area); +void meta_window_get_current_tile_area (MetaWindow *window, + MetaRectangle *tile_area); gboolean meta_window_same_application (MetaWindow *window, MetaWindow *other_window); @@ -637,4 +656,6 @@ void meta_window_update_icon_now (MetaWindow *window); void meta_window_update_role (MetaWindow *window); void meta_window_update_net_wm_type (MetaWindow *window); +gboolean meta_window_can_tile_side_by_side (MetaWindow *window); + #endif diff --git a/src/core/window.c b/src/core/window.c index f24d8af3..42231509 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -468,6 +468,8 @@ meta_window_new_with_attrs (MetaDisplay *display, window->require_on_single_xinerama = TRUE; window->require_titlebar_visible = TRUE; window->on_all_workspaces = FALSE; + window->tile_mode = META_TILE_NONE; + window->tile_monitor_number = -1; window->shaded = FALSE; window->initially_iconic = FALSE; window->minimized = FALSE; @@ -2493,7 +2495,7 @@ ensure_size_hints_satisfied (MetaRectangle *rect, static void meta_window_save_rect (MetaWindow *window) { - if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) + if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen)) { /* save size/pos as appropriate args for move_resize */ if (!window->maximized_horizontally) @@ -2535,7 +2537,7 @@ force_save_user_window_placement (MetaWindow *window) static void save_user_window_placement (MetaWindow *window) { - if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) + if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen)) { MetaRectangle user_rect; @@ -2576,7 +2578,10 @@ meta_window_maximize_internal (MetaWindow *window, window->saved_rect = *saved_rect; else meta_window_save_rect (window); - + + if (maximize_horizontally && maximize_vertically) + window->saved_maximize = TRUE; + window->maximized_horizontally = window->maximized_horizontally || maximize_horizontally; window->maximized_vertically = @@ -2598,6 +2603,8 @@ void meta_window_maximize (MetaWindow *window, MetaMaximizeFlags directions) { + MetaRectangle *saved_rect = NULL; + /* At least one of the two directions ought to be set */ gboolean maximize_horizontally, maximize_vertically; maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; @@ -2623,19 +2630,21 @@ meta_window_maximize (MetaWindow *window, /* if the window hasn't been placed yet, we'll maximize it then */ if (!window->placed) - { - window->maximize_horizontally_after_placement = - window->maximize_horizontally_after_placement || - maximize_horizontally; - window->maximize_vertically_after_placement = - window->maximize_vertically_after_placement || - maximize_vertically; - return; - } + { + window->maximize_horizontally_after_placement = window->maximize_horizontally_after_placement || maximize_horizontally; + window->maximize_vertically_after_placement = window->maximize_vertically_after_placement || maximize_vertically; + return; + } + + if (window->tile_mode != META_TILE_NONE) + { + saved_rect = &window->saved_rect; + window->maximized_vertically = FALSE; + } meta_window_maximize_internal (window, directions, - NULL); + saved_rect); /* move_resize with new maximization constraints */ @@ -2676,15 +2685,80 @@ unmaximize_window_before_freeing (MetaWindow *window) } void +meta_window_tile (MetaWindow *window) +{ + /* Don't do anything if no tiling is requested */ + if (window->tile_mode == META_TILE_NONE) + return; + + meta_window_maximize_internal (window, META_MAXIMIZE_VERTICAL, NULL); + + /* move_resize with new tiling constraints */ + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +static gboolean +meta_window_can_tile_maximized (MetaWindow *window) +{ + return window->has_maximize_func; +} + +gboolean +meta_window_can_tile_side_by_side (MetaWindow *window) +{ + const MetaXineramaScreenInfo *monitor; + MetaRectangle tile_area; + + /*if (!META_WINDOW_ALLOWS_RESIZE (window))*/ + if (!meta_window_can_tile_maximized (window)) + return FALSE; + + monitor = meta_screen_get_current_xinerama (window->screen); + meta_window_get_work_area_for_xinerama (window, monitor->number, &tile_area); + + /* Do not allow tiling in portrait orientation */ + if (tile_area.height > tile_area.width) + return FALSE; + + tile_area.width /= 2; + + if (window->frame) + { + MetaFrameGeometry fgeom; + + meta_frame_calc_geometry (window->frame, &fgeom); + + tile_area.width -= (fgeom.left_width + fgeom.right_width); + tile_area.height -= (fgeom.top_height + fgeom.bottom_height); + } + + return tile_area.width >= window->size_hints.min_width && + tile_area.height >= window->size_hints.min_height; +} + +void meta_window_unmaximize (MetaWindow *window, MetaMaximizeFlags directions) { /* At least one of the two directions ought to be set */ gboolean unmaximize_horizontally, unmaximize_vertically; + + /* Restore tiling if necessary */ + if (window->tile_mode == META_TILE_LEFT || + window->tile_mode == META_TILE_RIGHT) + { + window->maximized_horizontally = FALSE; + meta_window_tile (window); + return; + } + unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL; g_assert (unmaximize_horizontally || unmaximize_vertically); + if (unmaximize_horizontally && unmaximize_vertically) + window->saved_maximize = FALSE; + /* Only do something if the window isn't already maximized in the * given direction(s). */ @@ -2725,17 +2799,6 @@ meta_window_unmaximize (MetaWindow *window, */ ensure_size_hints_satisfied (&target_rect, &window->size_hints); - /* When we unmaximize, if we're doing a mouse move also we could - * get the window suddenly jumping to the upper left corner of - * the workspace, since that's where it was when the grab op - * started. So we need to update the grab state. - */ - if (meta_grab_op_is_moving (window->display->grab_op) && - window->display->grab_window == window) - { - window->display->grab_anchor_window_pos = target_rect; - } - meta_window_move_resize (window, FALSE, target_rect.x, @@ -2747,6 +2810,19 @@ meta_window_unmaximize (MetaWindow *window, */ force_save_user_window_placement (window); + /* When we unmaximize, if we're doing a mouse move also we could + * get the window suddenly jumping to the upper left corner of + * the workspace, since that's where it was when the grab op + * started. So we need to update the grab state. We have to do + * it after the actual operation, as the window may have been moved + * by constraints. + */ + if (meta_grab_op_is_moving (window->display->grab_op) && + window->display->grab_window == window) + { + window->display->grab_anchor_window_pos = window->user_rect; + } + if (window->display->grab_wireframe_active) { window->display->grab_wireframe_rect = target_rect; @@ -6918,21 +6994,84 @@ update_move (MetaWindow *window, if (dx == 0 && dy == 0) return; - /* shake loose (unmaximize) maximized window if dragged beyond the threshold - * in the Y direction. You can't pull a window loose via X motion. + /* Originally for detaching maximized windows, but we use this + * for the zones at the sides of the monitor where trigger tiling + * because it's about the right size */ #define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6 shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) * DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR; - if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) + if (snap) + { + /* We don't want to tile while snapping. Also, clear any previous tile + request. */ + window->tile_mode = META_TILE_NONE; + window->tile_monitor_number = -1; + } + else if (meta_prefs_get_edge_tiling () && + !META_WINDOW_MAXIMIZED (window) && + !META_WINDOW_TILED_SIDE_BY_SIDE (window)) + { + const MetaXineramaScreenInfo *monitor; + MetaRectangle work_area; + + /* For side-by-side tiling we are interested in the inside vertical + * edges of the work area of the monitor where the pointer is located, + * and in the outside top edge for maximized tiling. + * + * For maximized tiling we use the outside edge instead of the + * inside edge, because we don't want to force users to maximize + * windows they are placing near the top of their screens. + * + * The "current" idea of meta_window_get_work_area_current_monitor() and + * meta_screen_get_current_monitor() is slightly different: the former + * refers to the monitor which contains the largest part of the window, + * the latter to the one where the pointer is located. + */ + monitor = meta_screen_get_current_xinerama (window->screen); + meta_window_get_work_area_for_xinerama (window, + monitor->number, + &work_area); + + /* Check if the cursor is in a position which triggers tiling + * and set tile_mode accordingly. + */ + if (meta_window_can_tile_side_by_side (window) && + x >= monitor->rect.x && x < (work_area.x + shake_threshold)) + window->tile_mode = META_TILE_LEFT; + else if (meta_window_can_tile_side_by_side (window) && + x >= work_area.x + work_area.width - shake_threshold && + x < (monitor->rect.x + monitor->rect.width)) + window->tile_mode = META_TILE_RIGHT; + else if (meta_window_can_tile_maximized (window) && + y >= monitor->rect.y && y <= work_area.y) + window->tile_mode = META_TILE_MAXIMIZED; + else + window->tile_mode = META_TILE_NONE; + + if (window->tile_mode != META_TILE_NONE) + window->tile_monitor_number = monitor->number; + } + + /* shake loose (unmaximize) maximized or tiled window if dragged beyond + * the threshold in the Y direction. Tiled windows can also be pulled + * loose via X motion. + */ + + if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) || + (META_WINDOW_TILED_SIDE_BY_SIDE (window) && (MAX (ABS (dx), ABS (dy)) >= shake_threshold))) { double prop; - /* Shake loose */ - window->shaken_loose = TRUE; - + /* Shake loose, so that the window snaps back to maximized + * when dragged near the top; do not snap back if the window + * was tiled. + */ + window->shaken_loose = META_WINDOW_MAXIMIZED (window); + window->tile_mode = META_TILE_NONE; + /* move the unmaximized window to the cursor */ prop = ((double)(x - display->grab_initial_window_pos.x)) / @@ -7005,7 +7144,9 @@ update_move (MetaWindow *window, display->grab_anchor_root_x = x; display->grab_anchor_root_y = y; window->shaken_loose = FALSE; - + + window->tile_mode = META_TILE_NONE; + meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); @@ -7015,13 +7156,20 @@ update_move (MetaWindow *window, } } + /* Delay showing the tile preview slightly to make it more unlikely to + * trigger it unwittingly, e.g. when shaking loose the window or moving + * it to another monitor. + */ + meta_screen_tile_preview_update (window->screen, + window->tile_mode != META_TILE_NONE); + if (display->grab_wireframe_active) old = display->grab_wireframe_rect; else meta_window_get_client_root_coords (window, &old); - /* Don't allow movement in the maximized directions */ - if (window->maximized_horizontally) + /* Don't allow movement in the maximized directions or while tiled */ + if (window->maximized_horizontally || META_WINDOW_TILED_SIDE_BY_SIDE (window)) new_x = old.x; if (window->maximized_vertically) new_y = old.y; @@ -7044,7 +7192,7 @@ update_move (MetaWindow *window, meta_compositor_update_move (display->compositor, window, root_x, root_y); } - + if (display->grab_wireframe_active) meta_window_update_wireframe (window, new_x, new_y, display->grab_wireframe_rect.width, @@ -7369,6 +7517,19 @@ check_use_this_motion_notify (MetaWindow *window, } } +static void +update_tile_mode (MetaWindow *window) +{ + switch (window->tile_mode) + { + case META_TILE_LEFT: + case META_TILE_RIGHT: + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + window->tile_mode = META_TILE_NONE; + break; + } +} + void meta_window_handle_mouse_grab_op_event (MetaWindow *window, XEvent *event) @@ -7437,7 +7598,17 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, { if (meta_grab_op_is_moving (window->display->grab_op)) { - if (event->xbutton.root == window->screen->xroot) + if (window->tile_mode == META_TILE_MAXIMIZED) + { + meta_window_maximize (window, META_MAXIMIZE_VERTICAL | + META_MAXIMIZE_HORIZONTAL); + window->tile_mode = META_TILE_NONE; + } + else if (window->tile_mode != META_TILE_NONE) + { + meta_window_tile (window); + } + else if (event->xbutton.root == window->screen->xroot) update_move (window, event->xbutton.state & ShiftMask, event->xbutton.x_root, event->xbutton.y_root); } @@ -7449,8 +7620,17 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, event->xbutton.x_root, event->xbutton.y_root, TRUE); - if (window->display->compositor) - meta_compositor_set_updates (window->display->compositor, window, TRUE); + if (window->display->compositor) + meta_compositor_set_updates (window->display->compositor, window, TRUE); + + /* If a tiled window has been dragged free with a + * mouse resize without snapping back to the tiled + * state, it will end up with an inconsistent tile + * mode on mouse release; cleaning the mode earlier + * would break the ability to snap back to the tiled + * state, so we wait until mouse release. + */ + update_tile_mode (window); } } @@ -7595,6 +7775,41 @@ meta_window_get_work_area_all_xineramas (MetaWindow *window, window->desc, area->x, area->y, area->width, area->height); } +void +meta_window_get_current_tile_area (MetaWindow *window, + MetaRectangle *tile_area) +{ + int tile_monitor_number; + + g_return_if_fail (window->tile_mode != META_TILE_NONE); + + /* I don't know how to detect monitor configuration changes, so I have to take into account that + * tile_monitor_number might be invalid. If this happens, I replace it with whatever monitor + * the window is currently on. This is usually the correct monitor anyway, only in some special + * cases is the real monitor number actually required (e.g. the window is being moved with the mouse but + * is still mostly on the wrong monitor). + */ + if (window->tile_monitor_number >= window->screen->n_xinerama_infos) + { + window->tile_monitor_number = meta_screen_get_xinerama_for_window (window->screen, window)->number; + } + + tile_monitor_number = window->tile_monitor_number; + if (tile_monitor_number < 0) + { + meta_warning ("%s called with an invalid monitor number; using 0 instead\n", G_STRFUNC); + tile_monitor_number = 0; + } + + meta_window_get_work_area_for_xinerama (window, tile_monitor_number, tile_area); + + if (window->tile_mode == META_TILE_LEFT || + window->tile_mode == META_TILE_RIGHT) + tile_area->width /= 2; + + if (window->tile_mode == META_TILE_RIGHT) + tile_area->x += tile_area->width; +} gboolean meta_window_same_application (MetaWindow *window, diff --git a/src/core/workspace.c b/src/core/workspace.c index 8ba66739..e03a7e73 100644 --- a/src/core/workspace.c +++ b/src/core/workspace.c @@ -741,8 +741,7 @@ ensure_work_areas_validated (MetaWorkspace *workspace) for (i = 0; i < workspace->screen->n_xinerama_infos; i++) tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect); workspace->xinerama_edges = - meta_rectangle_find_nonintersected_xinerama_edges (tmp, - workspace->all_struts); + meta_rectangle_find_nonintersected_xinerama_edges (&workspace->screen->rect, tmp, workspace->all_struts); g_list_free (tmp); /* We're all done, YAAY! Record that everything has been validated. */ diff --git a/src/include/boxes.h b/src/include/boxes.h index 9ae87dc4..9efaa3da 100644 --- a/src/include/boxes.h +++ b/src/include/boxes.h @@ -282,6 +282,7 @@ GList* meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect, * struts. */ GList* meta_rectangle_find_nonintersected_xinerama_edges ( + const MetaRectangle *screen_rect, const GList *xinerama_rects, const GSList *all_struts); diff --git a/src/include/common.h b/src/include/common.h index d408748d..c5171d15 100644 --- a/src/include/common.h +++ b/src/include/common.h @@ -48,7 +48,9 @@ typedef enum META_FRAME_ALLOWS_MOVE = 1 << 11, META_FRAME_FULLSCREEN = 1 << 12, META_FRAME_IS_FLASHING = 1 << 13, - META_FRAME_ABOVE = 1 << 14 + META_FRAME_ABOVE = 1 << 14, + META_FRAME_TILED_LEFT = 1 << 15, + META_FRAME_TILED_RIGHT = 1 << 16 } MetaFrameFlags; typedef enum diff --git a/src/include/core.h b/src/include/core.h index 94302f05..ef20ee28 100644 --- a/src/include/core.h +++ b/src/include/core.h @@ -114,6 +114,10 @@ void meta_core_user_focus (Display *xdisplay, Window frame_xwindow, guint32 timestamp); +void meta_core_lower_beneath_focus_window (Display *xdisplay, + Window xwindow, + guint32 timestamp); + void meta_core_minimize (Display *xdisplay, Window frame_xwindow); void meta_core_toggle_maximize (Display *xdisplay, diff --git a/src/include/prefs.h b/src/include/prefs.h index c251aba9..33430c18 100644 --- a/src/include/prefs.h +++ b/src/include/prefs.h @@ -58,6 +58,7 @@ typedef enum META_PREF_CURSOR_SIZE, META_PREF_COMPOSITING_MANAGER, META_PREF_RESIZE_WITH_RIGHT_BUTTON, + META_PREF_EDGE_TILING, META_PREF_FORCE_FULLSCREEN, META_PREF_PLACEMENT_MODE } MetaPreference; @@ -99,6 +100,7 @@ int meta_prefs_get_auto_raise_delay (void); gboolean meta_prefs_get_reduced_resources (void); gboolean meta_prefs_get_gnome_accessibility (void); gboolean meta_prefs_get_gnome_animations (void); +gboolean meta_prefs_get_edge_tiling (void); const char* meta_prefs_get_screenshot_command (void); diff --git a/src/include/tile-preview.h b/src/include/tile-preview.h new file mode 100644 index 00000000..79312ac3 --- /dev/null +++ b/src/include/tile-preview.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Meta tile preview */ + +/* + * Copyright (C) 2010 Florian Müllner + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#ifndef META_TILE_PREVIEW_H +#define META_TILE_PREVIEW_H + +#include "boxes.h" + +typedef struct _MetaTilePreview MetaTilePreview; + +MetaTilePreview *meta_tile_preview_new (int screen_number, + gboolean composited); +void meta_tile_preview_free (MetaTilePreview *preview); +void meta_tile_preview_show (MetaTilePreview *preview, + MetaRectangle *rect); +void meta_tile_preview_hide (MetaTilePreview *preview); + +#endif /* META_TILE_PREVIEW_H */ diff --git a/src/include/ui.h b/src/include/ui.h index e2bb9ede..0ca0f262 100644 --- a/src/include/ui.h +++ b/src/include/ui.h @@ -180,5 +180,6 @@ int meta_ui_get_drag_threshold (MetaUI *ui); MetaUIDirection meta_ui_get_direction (void); #include "tabpopup.h" +#include "tile-preview.h" #endif diff --git a/src/metacity-schemas.convert b/src/metacity-schemas.convert index 46f3104b..9823cfde 100644 --- a/src/metacity-schemas.convert +++ b/src/metacity-schemas.convert @@ -1,3 +1,4 @@ [org.gnome.metacity] compositing-manager = /apps/metacity/general/compositing_manager reduced-resources = /apps/metacity/general/reduced_resources +side-by-side-tiling = /apps/metacity/general/side_by_side_tiling diff --git a/src/org.gnome.metacity.gschema.xml.in b/src/org.gnome.metacity.gschema.xml.in index d1fd336c..6b4eb0d7 100644 --- a/src/org.gnome.metacity.gschema.xml.in +++ b/src/org.gnome.metacity.gschema.xml.in @@ -30,6 +30,15 @@ However, the wireframe feature is disabled when accessibility is on. </_description> </key> + <key name="edge-tiling" type="b"> + <default>true</default> + <_summary>Enable edge tiling when dropping windows on screen edges</_summary> + <_description> + If enabled, dropping windows on vertical screen edges maximizes them + vertically and resizes them horizontally to cover half of the available + area. Dropping windows on the top screen edge maximizes them completely. + </_description> + </key> <key name="placement-mode" enum="org.gnome.metacity.MetaPlacementMode"> <default>'smart'</default> <_summary>Window placement behavior</_summary> diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c index 77d80ca6..774986cf 100644 --- a/src/ui/theme-parser.c +++ b/src/ui/theme-parser.c @@ -3181,6 +3181,28 @@ parse_style_set_element (GMarkupParseContext *context, meta_frame_style_ref (frame_style); info->style_set->maximized_styles[frame_focus] = frame_style; break; + case META_FRAME_STATE_TILED_LEFT: + if (info->style_set->tiled_left_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->tiled_left_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_TILED_RIGHT: + if (info->style_set->tiled_right_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->tiled_right_styles[frame_focus] = frame_style; + break; case META_FRAME_STATE_SHADED: if (info->style_set->shaded_styles[frame_resize][frame_focus]) { @@ -3203,6 +3225,28 @@ parse_style_set_element (GMarkupParseContext *context, meta_frame_style_ref (frame_style); info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style; break; + case META_FRAME_STATE_TILED_LEFT_AND_SHADED: + if (info->style_set->tiled_left_and_shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->tiled_left_and_shaded_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: + if (info->style_set->tiled_right_and_shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->tiled_right_and_shaded_styles[frame_focus] = frame_style; + break; case META_FRAME_STATE_LAST: g_assert_not_reached (); break; diff --git a/src/ui/theme.c b/src/ui/theme.c index de039a5d..ed25877d 100644 --- a/src/ui/theme.c +++ b/src/ui/theme.c @@ -865,7 +865,9 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, rect->visible.width = button_width; rect->visible.height = button_height; - if (flags & META_FRAME_MAXIMIZED) + if (flags & META_FRAME_MAXIMIZED || + flags & META_FRAME_TILED_LEFT || + flags & META_FRAME_TILED_RIGHT) { rect->clickable.x = rect->visible.x; rect->clickable.y = 0; @@ -4733,7 +4735,11 @@ meta_frame_style_set_unref (MetaFrameStyleSet *style_set) } free_focus_styles (style_set->maximized_styles); + free_focus_styles (style_set->tiled_left_styles); + free_focus_styles (style_set->tiled_right_styles); free_focus_styles (style_set->maximized_and_shaded_styles); + free_focus_styles (style_set->tiled_left_and_shaded_styles); + free_focus_styles (style_set->tiled_right_and_shaded_styles); if (style_set->parent) meta_frame_style_set_unref (style_set->parent); @@ -4785,9 +4791,21 @@ get_style (MetaFrameStyleSet *style_set, case META_FRAME_STATE_MAXIMIZED: styles = style_set->maximized_styles; break; + case META_FRAME_STATE_TILED_LEFT: + styles = style_set->tiled_left_styles; + break; + case META_FRAME_STATE_TILED_RIGHT: + styles = style_set->tiled_right_styles; + break; case META_FRAME_STATE_MAXIMIZED_AND_SHADED: styles = style_set->maximized_and_shaded_styles; break; + case META_FRAME_STATE_TILED_LEFT_AND_SHADED: + styles = style_set->tiled_left_and_shaded_styles; + break; + case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: + styles = style_set->tiled_right_and_shaded_styles; + break; case META_FRAME_STATE_NORMAL: case META_FRAME_STATE_SHADED: case META_FRAME_STATE_LAST: @@ -4797,6 +4815,19 @@ get_style (MetaFrameStyleSet *style_set, style = styles[focus]; + /* Tiled states are optional, try falling back to non-tiled states */ + if (style == NULL) + { + if (state == META_FRAME_STATE_TILED_LEFT || + state == META_FRAME_STATE_TILED_RIGHT) + style = get_style (style_set, META_FRAME_STATE_NORMAL, + resize, focus); + else if (state == META_FRAME_STATE_TILED_LEFT_AND_SHADED || + state == META_FRAME_STATE_TILED_RIGHT_AND_SHADED) + style = get_style (style_set, META_FRAME_STATE_SHADED, + resize, focus); + } + /* Try parent if we failed here */ if (style == NULL && style_set->parent) style = get_style (style_set->parent, state, resize, focus); @@ -5142,7 +5173,7 @@ theme_get_style (MetaTheme *theme, if (style_set == NULL) return NULL; - switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED)) + switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED | META_FRAME_TILED_LEFT | META_FRAME_TILED_RIGHT)) { case 0: state = META_FRAME_STATE_NORMAL; @@ -5150,12 +5181,24 @@ theme_get_style (MetaTheme *theme, case META_FRAME_MAXIMIZED: state = META_FRAME_STATE_MAXIMIZED; break; + case META_FRAME_TILED_LEFT: + state = META_FRAME_STATE_TILED_LEFT; + break; + case META_FRAME_TILED_RIGHT: + state = META_FRAME_STATE_TILED_RIGHT; + break; case META_FRAME_SHADED: state = META_FRAME_STATE_SHADED; break; case (META_FRAME_MAXIMIZED | META_FRAME_SHADED): state = META_FRAME_STATE_MAXIMIZED_AND_SHADED; break; + case (META_FRAME_TILED_LEFT | META_FRAME_SHADED): + state = META_FRAME_STATE_TILED_LEFT_AND_SHADED; + break; + case (META_FRAME_TILED_RIGHT | META_FRAME_SHADED): + state = META_FRAME_STATE_TILED_RIGHT_AND_SHADED; + break; default: g_assert_not_reached (); state = META_FRAME_STATE_LAST; /* compiler */ @@ -5852,10 +5895,18 @@ meta_frame_state_from_string (const char *str) return META_FRAME_STATE_NORMAL; else if (strcmp ("maximized", str) == 0) return META_FRAME_STATE_MAXIMIZED; + else if (strcmp ("tiled_left", str) == 0) + return META_FRAME_STATE_TILED_LEFT; + else if (strcmp ("tiled_right", str) == 0) + return META_FRAME_STATE_TILED_RIGHT; else if (strcmp ("shaded", str) == 0) return META_FRAME_STATE_SHADED; else if (strcmp ("maximized_and_shaded", str) == 0) return META_FRAME_STATE_MAXIMIZED_AND_SHADED; + else if (strcmp ("tiled_left_and_shaded", str) == 0) + return META_FRAME_STATE_TILED_LEFT_AND_SHADED; + else if (strcmp ("tiled_right_and_shaded", str) == 0) + return META_FRAME_STATE_TILED_RIGHT_AND_SHADED; else return META_FRAME_STATE_LAST; } @@ -5869,10 +5920,18 @@ meta_frame_state_to_string (MetaFrameState state) return "normal"; case META_FRAME_STATE_MAXIMIZED: return "maximized"; + case META_FRAME_STATE_TILED_LEFT: + return "tiled_left"; + case META_FRAME_STATE_TILED_RIGHT: + return "tiled_right"; case META_FRAME_STATE_SHADED: return "shaded"; case META_FRAME_STATE_MAXIMIZED_AND_SHADED: return "maximized_and_shaded"; + case META_FRAME_STATE_TILED_LEFT_AND_SHADED: + return "tiled_left_and_shaded"; + case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: + return "tiled_right_and_shaded"; case META_FRAME_STATE_LAST: break; } diff --git a/src/ui/theme.h b/src/ui/theme.h index f1b290ed..cea966c6 100644 --- a/src/ui/theme.h +++ b/src/ui/theme.h @@ -771,8 +771,12 @@ typedef enum { META_FRAME_STATE_NORMAL, META_FRAME_STATE_MAXIMIZED, + META_FRAME_STATE_TILED_LEFT, + META_FRAME_STATE_TILED_RIGHT, META_FRAME_STATE_SHADED, META_FRAME_STATE_MAXIMIZED_AND_SHADED, + META_FRAME_STATE_TILED_LEFT_AND_SHADED, + META_FRAME_STATE_TILED_RIGHT_AND_SHADED, META_FRAME_STATE_LAST } MetaFrameState; @@ -809,8 +813,12 @@ struct _MetaFrameStyleSet MetaFrameStyleSet *parent; MetaFrameStyle *normal_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST]; MetaFrameStyle *maximized_styles[META_FRAME_FOCUS_LAST]; + MetaFrameStyle *tiled_left_styles[META_FRAME_FOCUS_LAST]; + MetaFrameStyle *tiled_right_styles[META_FRAME_FOCUS_LAST]; MetaFrameStyle *shaded_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST]; MetaFrameStyle *maximized_and_shaded_styles[META_FRAME_FOCUS_LAST]; + MetaFrameStyle *tiled_left_and_shaded_styles[META_FRAME_FOCUS_LAST]; + MetaFrameStyle *tiled_right_and_shaded_styles[META_FRAME_FOCUS_LAST]; }; /** diff --git a/src/ui/tile-preview.c b/src/ui/tile-preview.c new file mode 100644 index 00000000..c898b2dd --- /dev/null +++ b/src/ui/tile-preview.c @@ -0,0 +1,245 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Metacity tile-preview marks the area a window will *ehm* snap to */ + +/* + * Copyright (C) 2010 Florian Müllner + * + * 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 <gtk/gtk.h> +#include <cairo.h> + +#include "tile-preview.h" +#include "core.h" + +#define OUTLINE_WIDTH 5 /* frame width in non-composite case */ + + +struct _MetaTilePreview { + GtkWidget *preview_window; + + GdkColor *preview_color; + guchar preview_alpha; + + MetaRectangle tile_rect; + + gboolean has_alpha: 1; +}; + +static gboolean +meta_tile_preview_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + MetaTilePreview *preview = user_data; + GdkRGBA preview_color; + + preview_color.red = (double)preview->preview_color->red / 0xFFFF; + preview_color.green = (double)preview->preview_color->green / 0xFFFF; + preview_color.blue = (double)preview->preview_color->blue / 0xFFFF; + preview_color.alpha = (double)preview->preview_alpha / 0xFF; + + cairo_set_line_width (cr, 1.0); + + if (preview->has_alpha) + { + /* Fill the preview area with a transparent color */ + gdk_cairo_set_source_rgba (cr, &preview_color); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + + /* Use the opaque color for the border */ + preview_color.alpha = 1.0; + gdk_cairo_set_source_rgba (cr, &preview_color); + } + else + { + GdkRGBA white = {1.0, 1.0, 1.0, 1.0}; + + gdk_cairo_set_source_rgba (cr, &white); + + cairo_rectangle (cr, + OUTLINE_WIDTH - 0.5, OUTLINE_WIDTH - 0.5, + preview->tile_rect.width - 2 * (OUTLINE_WIDTH - 1) - 1, + preview->tile_rect.height - 2 * (OUTLINE_WIDTH - 1) - 1); + cairo_stroke (cr); + } + + cairo_rectangle (cr, + 0.5, 0.5, + preview->tile_rect.width - 1, + preview->tile_rect.height - 1); + cairo_stroke (cr); + + return FALSE; +} + +static void +on_preview_window_style_set (GtkWidget *widget, + GtkStyle *previous, + gpointer user_data) +{ + MetaTilePreview *preview = user_data; + GtkStyle *style; + + style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (widget), + "GtkWindow.GtkIconView", + "GtkWindow.GtkIconView", + GTK_TYPE_ICON_VIEW); + + if (style != NULL) + g_object_ref (style); + else + style = gtk_style_new (); + + gtk_style_get (style, GTK_TYPE_ICON_VIEW, + "selection-box-color", &preview->preview_color, + "selection-box-alpha", &preview->preview_alpha, + NULL); + if (!preview->preview_color) + { + GdkColor selection = style->base[GTK_STATE_SELECTED]; + preview->preview_color = gdk_color_copy (&selection); + } + + g_object_unref (style); +} + +MetaTilePreview * +meta_tile_preview_new (int screen_number, + gboolean composited) +{ + MetaTilePreview *preview; + GdkVisual *visual; + GdkScreen *screen; + + screen = gdk_display_get_screen (gdk_display_get_default (), screen_number); + visual = gdk_screen_get_rgba_visual (screen); + + preview = g_new (MetaTilePreview, 1); + + preview->preview_window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (preview->preview_window), screen); + gtk_widget_set_app_paintable (preview->preview_window, TRUE); + + preview->preview_color = NULL; + preview->preview_alpha = 0xFF; + + preview->tile_rect.x = preview->tile_rect.y = 0; + preview->tile_rect.width = preview->tile_rect.height = 0; + + preview->has_alpha = visual && composited; + + if (preview->has_alpha) + { + gtk_widget_set_visual (preview->preview_window, visual); + + g_signal_connect (preview->preview_window, "style-set", + G_CALLBACK (on_preview_window_style_set), preview); + } + + gtk_widget_realize (preview->preview_window); + /*gdk_window_set_back_pixmap (gtk_widget_get_window (preview->preview_window), + NULL, FALSE);*/ + + g_signal_connect (preview->preview_window, "draw", + G_CALLBACK (meta_tile_preview_draw), preview); + + return preview; +} + +void +meta_tile_preview_free (MetaTilePreview *preview) +{ + gtk_widget_destroy (preview->preview_window); + + if (preview->preview_color) + gdk_color_free (preview->preview_color); + + g_free (preview); +} + +void +meta_tile_preview_show (MetaTilePreview *preview, + MetaRectangle *tile_rect) +{ + GdkWindow *window; + GdkRectangle old_rect; + + if (gtk_widget_get_visible (preview->preview_window) + && preview->tile_rect.x == tile_rect->x + && preview->tile_rect.y == tile_rect->y + && preview->tile_rect.width == tile_rect->width + && preview->tile_rect.height == tile_rect->height) + return; /* nothing to do */ + + gtk_widget_show (preview->preview_window); + window = gtk_widget_get_window (preview->preview_window); + meta_core_lower_beneath_focus_window (gdk_x11_get_default_xdisplay(), + GDK_WINDOW_XID (window), + gtk_get_current_event_time ()); + + old_rect.x = old_rect.y = 0; + old_rect.width = preview->tile_rect.width; + old_rect.height = preview->tile_rect.height; + + gdk_window_invalidate_rect (window, &old_rect, FALSE); + + preview->tile_rect = *tile_rect; + + gdk_window_move_resize (window, + preview->tile_rect.x, preview->tile_rect.y, + preview->tile_rect.width, preview->tile_rect.height); + + if (!preview->has_alpha) + { + cairo_rectangle_int_t outer_rect, inner_rect; + cairo_region_t *outer_region, *inner_region; + GdkRGBA black = {.0, .0, .0, 1.0}; + + gdk_window_set_background_rgba (window, &black); + + outer_rect.x = outer_rect.y = 0; + outer_rect.width = preview->tile_rect.width; + outer_rect.height = preview->tile_rect.height; + + inner_rect.x = OUTLINE_WIDTH; + inner_rect.y = OUTLINE_WIDTH; + inner_rect.width = outer_rect.width - 2 * OUTLINE_WIDTH; + inner_rect.height = outer_rect.height - 2 * OUTLINE_WIDTH; + + outer_region = cairo_region_create_rectangle (&outer_rect); + inner_region = cairo_region_create_rectangle (&inner_rect); + + cairo_region_subtract (outer_region, inner_region); + cairo_region_destroy (inner_region); + + gtk_widget_shape_combine_region (preview->preview_window, outer_region); + cairo_region_destroy (outer_region); + } +} + +void +meta_tile_preview_hide (MetaTilePreview *preview) +{ + gtk_widget_hide (preview->preview_window); +} |