From a87157176ca6e01c8c4047999ee584f00b63c11e Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Fri, 31 May 2013 16:22:39 +0200 Subject: Implement side-by-side tiling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch by Florian Müllner for Metacity https://bugzilla.gnome.org/show_bug.cgi?id=607694 When dragging a window over a screen edge and dropping it there, maximize it vertically and scale it horizontally to cover the corresponding half of the current monitor. Whenever a "hot area" which triggers this behavior is entered, an indication of window's target size is displayed after a short delay to avoid distraction when moving a window between monitors. --- src/Makefile.am | 2 + src/core/constraints.c | 65 ++++++++++- src/core/core.c | 30 +++++ src/core/prefs.c | 16 +++ src/core/screen-private.h | 5 + src/core/screen.c | 66 ++++++++++- src/core/window-private.h | 18 ++- src/core/window.c | 175 ++++++++++++++++++++++++---- src/include/core.h | 4 + src/include/prefs.h | 2 + src/include/tile-preview.h | 37 ++++++ src/include/ui.h | 1 + src/org.mate.marco.gschema.xml | 5 + src/ui/tile-preview.c | 251 +++++++++++++++++++++++++++++++++++++++++ 14 files changed, 650 insertions(+), 27 deletions(-) create mode 100644 src/include/tile-preview.h create mode 100644 src/ui/tile-preview.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index a7b01237..93263611 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,6 +98,8 @@ marco_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/constraints.c b/src/core/constraints.c index 16d9b107..a79f858a 100644 --- a/src/core/constraints.c +++ b/src/core/constraints.c @@ -98,6 +98,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, @@ -145,6 +146,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, @@ -211,6 +216,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"}, @@ -731,7 +737,8 @@ 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 (window)) return TRUE; /* Calculate target_size = maximized size of (window + frame) */ @@ -799,6 +806,58 @@ 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 (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, @@ -850,7 +909,7 @@ constrain_size_increments (MetaWindow *window, /* Determine whether constraint applies; exit if it doesn't */ if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || - info->action_type == ACTION_MOVE) + META_WINDOW_TILED (window) || info->action_type == ACTION_MOVE) return TRUE; /* Determine whether constraint is already satisfied; exit if it is */ @@ -981,7 +1040,7 @@ constrain_aspect_ratio (MetaWindow *window, constraints_are_inconsistent = minr > maxr; if (constraints_are_inconsistent || META_WINDOW_MAXIMIZED (window) || window->fullscreen || - info->action_type == ACTION_MOVE) + META_WINDOW_TILED (window) || info->action_type == ACTION_MOVE) return TRUE; /* Determine whether constraint is already satisfied; exit if it is. We diff --git a/src/core/core.c b/src/core/core.c index 76e5548b..c8fa02b7 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -28,6 +28,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. @@ -296,6 +297,35 @@ meta_core_user_lower_and_unfocus (Display *xdisplay, timestamp); } +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, diff --git a/src/core/prefs.c b/src/core/prefs.c index 116a9bb8..5f46b554 100644 --- a/src/core/prefs.c +++ b/src/core/prefs.c @@ -117,6 +117,7 @@ static gboolean compositing_fast_alt_tab = FALSE; static gboolean resize_with_right_button = FALSE; static gboolean center_new_windows = FALSE; static gboolean force_fullscreen = TRUE; +static gboolean side_by_side_tiling = FALSE; static MetaVisualBellType visual_bell_type = META_VISUAL_BELL_FULLSCREEN_FLASH; static MetaButtonLayout button_layout; @@ -401,6 +402,12 @@ static MetaBoolPreference preferences_bool[] = ¢er_new_windows, FALSE, }, + { "side-by-side-tiling", + KEY_GENERAL_SCHEMA, + META_PREF_SIDE_BY_SIDE_TILING, + &side_by_side_tiling, + FALSE, + }, { NULL, NULL, 0, NULL, FALSE }, }; @@ -1545,6 +1552,9 @@ meta_preference_to_string (MetaPreference pref) case META_PREF_FORCE_FULLSCREEN: return "FORCE_FULLSCREEN"; + + case META_PREF_SIDE_BY_SIDE_TILING: + return "SIDE_BY_SIDE_TILING"; } return "(unknown)"; @@ -2202,6 +2212,12 @@ meta_prefs_get_center_new_windows (void) return center_new_windows; } +gboolean +meta_prefs_get_side_by_side_tiling () +{ + return side_by_side_tiling; +} + guint meta_prefs_get_mouse_button_resize (void) { diff --git a/src/core/screen-private.h b/src/core/screen-private.h index 77ea457f..8eb02d00 100644 --- a/src/core/screen-private.h +++ b/src/core/screen-private.h @@ -79,6 +79,9 @@ 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; @@ -160,6 +163,8 @@ void meta_screen_ensure_tab_popup (MetaScreen *scree MetaTabList list_type, MetaTabShowType show_type); void meta_screen_ensure_workspace_popup (MetaScreen *screen); +void meta_screen_tile_preview_update (MetaScreen *screen, + gboolean delay); 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 e8fce40a..eefe58f2 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -582,7 +582,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); @@ -691,7 +694,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); @@ -1451,6 +1460,59 @@ 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; + + screen->tile_preview_timeout_id = 0; + + if (!screen->tile_preview) + screen->tile_preview = meta_tile_preview_new (screen->number, + composited); + + if (window + && !META_WINDOW_TILED (window) + && window->tile_mode != META_TILE_NONE) + { + 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); + } +} + MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen, MetaWindow *not_this_one) diff --git a/src/core/window-private.h b/src/core/window-private.h index 447a2c4b..02c518e0 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -83,6 +83,12 @@ typedef enum { #define NUMBER_OF_QUEUES 3 +typedef enum { + META_TILE_NONE, + META_TILE_LEFT, + META_TILE_RIGHT +} MetaTileMode; + struct _MetaWindow { MetaDisplay *display; @@ -138,6 +144,11 @@ 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; + /* Whether we're shaded */ guint shaded : 1; @@ -383,8 +394,11 @@ 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(w) ((w)->maximized_vertically && \ + !(w)->maximized_horizontally && \ + (w)->tile_mode != META_TILE_NONE) #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(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))) @@ -575,6 +589,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); diff --git a/src/core/window.c b/src/core/window.c index d997bae7..7a7f6bec 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -470,6 +470,7 @@ 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->shaded = FALSE; window->initially_iconic = FALSE; window->minimized = FALSE; @@ -2489,7 +2490,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 (window) || window->fullscreen)) { /* save size/pos as appropriate args for move_resize */ if (!window->maximized_horizontally) @@ -2531,7 +2532,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 (window) || window->fullscreen)) { MetaRectangle user_rect; @@ -2596,6 +2597,7 @@ 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; @@ -2631,9 +2633,16 @@ meta_window_maximize (MetaWindow *window, 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 */ @@ -2673,12 +2682,64 @@ unmaximize_window_before_freeing (MetaWindow *window) } } +static 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); + meta_screen_tile_preview_update (window->screen, FALSE); + + /* move_resize with new tiling constraints + */ + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +static gboolean +meta_window_can_tile (MetaWindow *window) +{ + const MetaXineramaScreenInfo *monitor; + MetaRectangle tile_area; + + if (!META_WINDOW_ALLOWS_RESIZE (window)) + return FALSE; + + monitor = meta_screen_get_current_xinerama (window->screen); + meta_window_get_work_area_for_xinerama (window, monitor->number, &tile_area); + + 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_NONE) + { + 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); @@ -2723,17 +2784,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, @@ -2745,6 +2795,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; @@ -6898,20 +6961,58 @@ 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 (meta_prefs_get_side_by_side_tiling () && + meta_window_can_tile (window)) + { + const MetaXineramaScreenInfo *monitor; + MetaRectangle work_area; + + /* For tiling we are interested in the work area of the monitor where + * the pointer is located. + * Also see comment in meta_window_get_current_tile_area() + */ + monitor = meta_screen_get_current_xinerama (window->screen); + meta_window_get_work_area_for_xinerama (window, + monitor->number, + &work_area); + + if (y >= monitor->rect.y && + y < (monitor->rect.y + monitor->rect.height)) + { + /* check if cursor is near an edge of the work area */ + if (x >= monitor->rect.x && x < (work_area.x + shake_threshold)) + window->tile_mode = META_TILE_LEFT; + else if (x >= work_area.x + work_area.width - shake_threshold && + x < (monitor->rect.x + monitor->rect.width)) + window->tile_mode = META_TILE_RIGHT; + else + window->tile_mode = META_TILE_NONE; + } + } + + /* 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 (window) && (MAX (ABS (dx), ABS (dy)) >= shake_threshold))) { double prop; /* Shake loose */ - window->shaken_loose = TRUE; + window->shaken_loose = META_WINDOW_MAXIMIZED (window); + window->tile_mode = META_TILE_NONE; /* move the unmaximized window to the cursor */ prop = @@ -6995,13 +7096,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 (window)) new_x = old.x; if (window->maximized_vertically) new_y = old.y; @@ -7417,7 +7525,9 @@ 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_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); } @@ -7575,6 +7685,29 @@ 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) +{ + const MetaXineramaScreenInfo *monitor; + + g_return_if_fail (window->tile_mode != META_TILE_NONE); + + /* The definition of "current" of meta_window_get_work_area_current_xinerama() + * and meta_screen_get_current_xinerama() 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, 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/include/core.h b/src/include/core.h index 66db2f81..14c1c151 100644 --- a/src/include/core.h +++ b/src/include/core.h @@ -116,6 +116,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 2b7cfe41..4856d580 100644 --- a/src/include/prefs.h +++ b/src/include/prefs.h @@ -63,6 +63,7 @@ typedef enum META_PREF_COMPOSITING_FAST_ALT_TAB, META_PREF_RESIZE_WITH_RIGHT_BUTTON, META_PREF_CENTER_NEW_WINDOWS, + META_PREF_SIDE_BY_SIDE_TILING, META_PREF_FORCE_FULLSCREEN } MetaPreference; @@ -95,6 +96,7 @@ MetaWrapStyle meta_prefs_get_wrap_style (void); gboolean meta_prefs_get_reduced_resources (void); gboolean meta_prefs_get_mate_accessibility (void); gboolean meta_prefs_get_mate_animations (void); +gboolean meta_prefs_get_side_by_side_tiling (void); const char* meta_prefs_get_command (int i); diff --git a/src/include/tile-preview.h b/src/include/tile-preview.h new file mode 100644 index 00000000..b0ca3b01 --- /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 */ \ No newline at end of file diff --git a/src/include/ui.h b/src/include/ui.h index a8866498..23525193 100644 --- a/src/include/ui.h +++ b/src/include/ui.h @@ -205,5 +205,6 @@ MetaUIDirection meta_ui_get_direction (void); GdkPixbuf *meta_ui_get_pixbuf_from_pixmap (Pixmap pmap); #include "tabpopup.h" +#include "tile-preview.h" #endif diff --git a/src/org.mate.marco.gschema.xml b/src/org.mate.marco.gschema.xml index 464deb34..23b10dee 100644 --- a/src/org.mate.marco.gschema.xml +++ b/src/org.mate.marco.gschema.xml @@ -166,6 +166,11 @@ Determine if new windows are created on the center of the screen By default, marco open new windows on the top left of the screen. If this option is enabled, new windows are open on the center of the screen, instead. + + false + Whether to enable side-by-side tiling + If enabled, dropping windows on screen edges maximizes them vertically and resizes them horizontally to cover half of the available area. Drag-dropping to the top maximizes the window. + diff --git a/src/ui/tile-preview.c b/src/ui/tile-preview.c new file mode 100644 index 00000000..e782e6a5 --- /dev/null +++ b/src/ui/tile-preview.c @@ -0,0 +1,251 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Mutter 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 + +#include +#include + +#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_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer user_data) +{ + MetaTilePreview *preview = user_data; + GdkWindow *window; + cairo_t *cr; + + window = gtk_widget_get_window (widget); + cr = gdk_cairo_create (window); + + cairo_set_line_width (cr, 1.0); + + if (preview->has_alpha) + { + + /* Fill the preview area with a transparent color */ + cairo_set_source_rgba (cr, + (double)preview->preview_color->red / 0xFFFF, + (double)preview->preview_color->green / 0xFFFF, + (double)preview->preview_color->blue / 0xFFFF, + (double)preview->preview_alpha / 0xFF); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + + /* Use the opaque color for the border */ + gdk_cairo_set_source_color (cr, preview->preview_color); + } + else + { + GtkStyle *style = gtk_widget_get_style (preview->preview_window); + + gdk_cairo_set_source_color (cr, &style->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); + + cairo_destroy (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; + GdkColormap *rgba_colormap; + GdkScreen *screen; + + screen = gdk_display_get_screen (gdk_display_get_default (), screen_number); + rgba_colormap = gdk_screen_get_rgba_colormap (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 = rgba_colormap && composited; + + if (preview->has_alpha) + { + gtk_widget_set_colormap (preview->preview_window, rgba_colormap); + + 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, "expose-event", + G_CALLBACK (meta_tile_preview_expose), 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_display, + GDK_WINDOW_XWINDOW (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) + { + GdkRectangle outer_rect, inner_rect; + GdkRegion *outer_region, *inner_region; + GdkColor black; + + black = gtk_widget_get_style (preview->preview_window)->black; + gdk_window_set_background (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 = gdk_region_rectangle (&outer_rect); + inner_region = gdk_region_rectangle (&inner_rect); + + gdk_region_subtract (outer_region, inner_region); + gdk_region_destroy (inner_region); + + gdk_window_shape_combine_region (window, outer_region, 0, 0); + gdk_region_destroy (outer_region); + } +} + +void +meta_tile_preview_hide (MetaTilePreview *preview) +{ + gtk_widget_hide (preview->preview_window); +} \ No newline at end of file -- cgit v1.2.1