From patchwork Wed Feb 20 13:43:23 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 222109 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 8825B2C007A for ; Thu, 21 Feb 2013 02:56:39 +1100 (EST) Received: from localhost ([::1]:32809 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89yt-0001nJ-30 for incoming@patchwork.ozlabs.org; Wed, 20 Feb 2013 08:45:07 -0500 Received: from eggs.gnu.org ([208.118.235.92]:45219) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89xo-0000NG-8k for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:44:03 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1U89xf-0005Tx-1e for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:44:00 -0500 Received: from e28smtp09.in.ibm.com ([122.248.162.9]:35566) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89xe-0005TP-AA for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:43:50 -0500 Received: from /spool/local by e28smtp09.in.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Wed, 20 Feb 2013 19:11:56 +0530 Received: from d28dlp02.in.ibm.com (9.184.220.127) by e28smtp09.in.ibm.com (192.168.1.139) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Wed, 20 Feb 2013 19:11:53 +0530 Received: from d28relay05.in.ibm.com (d28relay05.in.ibm.com [9.184.220.62]) by d28dlp02.in.ibm.com (Postfix) with ESMTP id 456423940053 for ; Wed, 20 Feb 2013 19:13:44 +0530 (IST) Received: from d28av04.in.ibm.com (d28av04.in.ibm.com [9.184.220.66]) by d28relay05.in.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r1KDhfIW25493522 for ; Wed, 20 Feb 2013 19:13:41 +0530 Received: from d28av04.in.ibm.com (loopback [127.0.0.1]) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r1KDhioP014353 for ; Thu, 21 Feb 2013 00:43:44 +1100 Received: from titi.austin.rr.com (sig-9-76-138-226.mts.ibm.com [9.76.138.226]) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r1KDhSxx013442; Thu, 21 Feb 2013 00:43:42 +1100 From: Anthony Liguori To: qemu-devel@nongnu.org Date: Wed, 20 Feb 2013 07:43:23 -0600 Message-Id: <1361367806-4599-7-git-send-email-aliguori@us.ibm.com> X-Mailer: git-send-email 1.8.0 In-Reply-To: <1361367806-4599-1-git-send-email-aliguori@us.ibm.com> References: <1361367806-4599-1-git-send-email-aliguori@us.ibm.com> X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13022013-2674-0000-0000-000007E563E8 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 122.248.162.9 Cc: Kevin Wolf , Anthony Liguori Subject: [Qemu-devel] [PATCH 6/9] gtk: add support for screen scaling and full screen (v5) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Basic menu items to enter full screen mode and zoom in/out. Unlike SDL, we don't allow arbitrary scaling based on window resizing. The current behavior with SDL causes a lot of problems for me. Sometimes I accidentally resize the window a tiny bit while trying to move it (Ubuntu's 1-pixel window decorations don't help here). After that, scaling is now active and if the screen changes size again, badness ensues since the aspect ratio is skewed. Allowing zooming by 25% in and out should cover most use cases. We can add a more flexible scaling later but for now, I think this is a more friendly behavior. Signed-off-by: Anthony Liguori --- v1 -> v2 - fix scaling (Paolo) - use ctrl-alt-+ instead of ctrl-alt-= for zoom v2 -> v3 - add a zoom to 100% menu (Stefan) - allow window to be resizable v4 -> v5 - keep window size when enabling zoom to fit (Kevin) --- ui/gtk.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 237 insertions(+), 17 deletions(-) diff --git a/ui/gtk.c b/ui/gtk.c index 0c1ec4d..01a1777 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -80,6 +80,11 @@ typedef struct GtkDisplayState GtkWidget *view_menu_item; GtkWidget *view_menu; + GtkWidget *full_screen_item; + GtkWidget *zoom_in_item; + GtkWidget *zoom_out_item; + GtkWidget *zoom_fixed_item; + GtkWidget *zoom_fit_item; GtkWidget *grab_item; GtkWidget *grab_on_hover_item; GtkWidget *vga_item; @@ -101,9 +106,11 @@ typedef struct GtkDisplayState double scale_x; double scale_y; + gboolean full_screen; GdkCursor *null_cursor; Notifier mouse_mode_notifier; + gboolean free_scale; } GtkDisplayState; static GtkDisplayState *global_state; @@ -135,7 +142,7 @@ static void gd_update_cursor(GtkDisplayState *s, gboolean override) on_vga = gd_on_vga(s); if ((override || on_vga) && - (kbd_mouse_is_absolute() || gd_is_grab_active(s))) { + (s->full_screen || kbd_mouse_is_absolute() || gd_is_grab_active(s))) { gdk_window_set_cursor(window, s->null_cursor); } else { gdk_window_set_cursor(window, NULL); @@ -173,6 +180,9 @@ static void gd_update(DisplayState *ds, int x, int y, int w, int h) { GtkDisplayState *s = ds->opaque; int x1, x2, y1, y2; + int mx, my; + int fbw, fbh; + int ww, wh; DPRINTF("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h); @@ -182,7 +192,20 @@ static void gd_update(DisplayState *ds, int x, int y, int w, int h) x2 = ceil(x * s->scale_x + w * s->scale_x); y2 = ceil(y * s->scale_y + h * s->scale_y); - gtk_widget_queue_draw_area(s->drawing_area, x1, y1, (x2 - x1), (y2 - y1)); + fbw = ds_get_width(s->ds) * s->scale_x; + fbh = ds_get_height(s->ds) * s->scale_y; + + gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh); + + mx = my = 0; + if (ww > fbw) { + mx = (ww - fbw) / 2; + } + if (wh > fbh) { + my = (wh - fbh) / 2; + } + + gtk_widget_queue_draw_area(s->drawing_area, mx + x1, my + y1, (x2 - x1), (y2 - y1)); } static void gd_refresh(DisplayState *ds) @@ -227,9 +250,29 @@ static void gd_resize(DisplayState *ds) ds_get_height(ds), ds_get_linesize(ds)); - gtk_widget_set_size_request(s->drawing_area, - ds_get_width(ds) * s->scale_x, - ds_get_height(ds) * s->scale_y); + if (!s->full_screen) { + GtkRequisition req; + double sx, sy; + + if (s->free_scale) { + sx = s->scale_x; + sy = s->scale_y; + + s->scale_y = 1.0; + s->scale_x = 1.0; + } else { + sx = 1.0; + sy = 1.0; + } + + gtk_widget_set_size_request(s->drawing_area, + ds_get_width(ds) * s->scale_x, + ds_get_height(ds) * s->scale_y); + gtk_widget_size_request(s->vbox, &req); + + gtk_window_resize(GTK_WINDOW(s->window), + req.width * sx, req.height * sy); + } } /** QEMU Events **/ @@ -266,29 +309,55 @@ static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event, static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) { GtkDisplayState *s = opaque; + int mx, my; int ww, wh; int fbw, fbh; + if (!gtk_widget_get_realized(widget)) { + return FALSE; + } + fbw = ds_get_width(s->ds); fbh = ds_get_height(s->ds); gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh); - cairo_rectangle(cr, 0, 0, ww, wh); - - if (ww != fbw || wh != fbh) { + if (s->full_screen) { s->scale_x = (double)ww / fbw; s->scale_y = (double)wh / fbh; - cairo_scale(cr, s->scale_x, s->scale_y); - } else { - s->scale_x = 1.0; - s->scale_y = 1.0; + } else if (s->free_scale) { + double sx, sy; + + sx = (double)ww / fbw; + sy = (double)wh / fbh; + + s->scale_x = s->scale_y = MIN(sx, sy); } fbw *= s->scale_x; fbh *= s->scale_y; - cairo_set_source_surface(cr, s->surface, 0, 0); + mx = my = 0; + if (ww > fbw) { + mx = (ww - fbw) / 2; + } + if (wh > fbh) { + my = (wh - fbh) / 2; + } + + cairo_rectangle(cr, 0, 0, ww, wh); + + /* Optionally cut out the inner area where the pixmap + will be drawn. This avoids 'flashing' since we're + not double-buffering. Note we're using the undocumented + behaviour of drawing the rectangle from right to left + to cut out the whole */ + cairo_rectangle(cr, mx + fbw, my, + -1 * fbw, fbh); + cairo_fill(cr); + + cairo_scale(cr, s->scale_x, s->scale_y); + cairo_set_source_surface(cr, s->surface, mx / s->scale_x, my / s->scale_y); cairo_paint(cr); return TRUE; @@ -321,9 +390,31 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, GtkDisplayState *s = opaque; int dx, dy; int x, y; + int mx, my; + int fbh, fbw; + int ww, wh; + + fbw = ds_get_width(s->ds) * s->scale_x; + fbh = ds_get_height(s->ds) * s->scale_y; - x = motion->x / s->scale_x; - y = motion->y / s->scale_y; + gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh); + + mx = my = 0; + if (ww > fbw) { + mx = (ww - fbw) / 2; + } + if (wh > fbh) { + my = (wh - fbh) / 2; + } + + x = (motion->x - mx) / s->scale_x; + y = (motion->y - my) / s->scale_y; + + if (x < 0 || y < 0 || + x >= ds_get_width(s->ds) || + y >= ds_get_height(s->ds)) { + return TRUE; + } if (kbd_mouse_is_absolute()) { dx = x * 0x7FFF / (ds_get_width(s->ds) - 1); @@ -492,6 +583,90 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque) } } +static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->full_screen_item))) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); + gtk_widget_set_size_request(s->menu_bar, 0, 0); + gtk_widget_set_size_request(s->drawing_area, -1, -1); + gtk_window_fullscreen(GTK_WINDOW(s->window)); + if (gd_on_vga(s)) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), TRUE); + } + s->full_screen = TRUE; + } else { + gtk_window_unfullscreen(GTK_WINDOW(s->window)); + gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s); + gtk_widget_set_size_request(s->menu_bar, -1, -1); + gtk_widget_set_size_request(s->drawing_area, + ds_get_width(s->ds), ds_get_height(s->ds)); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE); + s->full_screen = FALSE; + s->scale_x = 1.0; + s->scale_y = 1.0; + } + + gd_update_cursor(s, FALSE); +} + +static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item), + FALSE); + + s->scale_x += .25; + s->scale_y += .25; + + gd_resize(s->ds); +} + +static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item), + FALSE); + + s->scale_x -= .25; + s->scale_y -= .25; + + s->scale_x = MAX(s->scale_x, .25); + s->scale_y = MAX(s->scale_y, .25); + + gd_resize(s->ds); +} + +static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + + s->scale_x = 1.0; + s->scale_y = 1.0; + + gd_resize(s->ds); +} + +static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + int ww, wh; + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) { + s->free_scale = TRUE; + } else { + s->free_scale = FALSE; + } + + gd_resize(s->ds); + + gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh); + gtk_widget_queue_draw_area(s->drawing_area, 0, 0, ww, wh); +} + static void gd_grab_keyboard(GtkDisplayState *s) { gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)), @@ -551,6 +726,9 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, if (!on_vga) { gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE); + } else if (s->full_screen) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + TRUE); } if (arg2 == 0) { @@ -739,6 +917,16 @@ static void gd_connect_signals(GtkDisplayState *s) g_signal_connect(s->quit_item, "activate", G_CALLBACK(gd_menu_quit), s); + g_signal_connect(s->full_screen_item, "activate", + G_CALLBACK(gd_menu_full_screen), s); + g_signal_connect(s->zoom_in_item, "activate", + G_CALLBACK(gd_menu_zoom_in), s); + g_signal_connect(s->zoom_out_item, "activate", + G_CALLBACK(gd_menu_zoom_out), s); + g_signal_connect(s->zoom_fixed_item, "activate", + G_CALLBACK(gd_menu_zoom_fixed), s); + g_signal_connect(s->zoom_fit_item, "activate", + G_CALLBACK(gd_menu_zoom_fit), s); g_signal_connect(s->vga_item, "activate", G_CALLBACK(gd_menu_switch_vc), s); g_signal_connect(s->grab_item, "activate", @@ -774,6 +962,39 @@ static void gd_create_menus(GtkDisplayState *s) gtk_menu_set_accel_group(GTK_MENU(s->view_menu), accel_group); s->view_menu_item = gtk_menu_item_new_with_mnemonic("_View"); + s->full_screen_item = gtk_check_menu_item_new_with_mnemonic("_Full Screen"); + gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item), + "/View/Full Screen"); + gtk_accel_map_add_entry("/View/Full Screen", GDK_KEY_f, GDK_CONTROL_MASK | GDK_MOD1_MASK); + gtk_menu_append(GTK_MENU(s->view_menu), s->full_screen_item); + + separator = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(s->view_menu), separator); + + s->zoom_in_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_IN, NULL); + gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item), + "/View/Zoom In"); + gtk_accel_map_add_entry("/View/Zoom In", GDK_KEY_plus, GDK_CONTROL_MASK | GDK_MOD1_MASK); + gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_in_item); + + s->zoom_out_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_OUT, NULL); + gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item), + "/View/Zoom Out"); + gtk_accel_map_add_entry("/View/Zoom Out", GDK_KEY_minus, GDK_CONTROL_MASK | GDK_MOD1_MASK); + gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_out_item); + + s->zoom_fixed_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_100, NULL); + gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item), + "/View/Zoom Fixed"); + gtk_accel_map_add_entry("/View/Zoom Fixed", GDK_KEY_0, GDK_CONTROL_MASK | GDK_MOD1_MASK); + gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fixed_item); + + s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic("Zoom To _Fit"); + gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fit_item); + + separator = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(s->view_menu), separator); + s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic("Grab On _Hover"); gtk_menu_append(GTK_MENU(s->view_menu), s->grab_on_hover_item); @@ -837,6 +1058,7 @@ void gtk_display_init(DisplayState *ds) s->scale_x = 1.0; s->scale_y = 1.0; + s->free_scale = FALSE; s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR); @@ -865,8 +1087,6 @@ void gtk_display_init(DisplayState *ds) gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE); - gtk_window_set_resizable(GTK_WINDOW(s->window), FALSE); - gd_update_caption(s); gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);