diff mbox

[6/9] gtk: add support for screen scaling and full screen (v3)

Message ID 1361231825-12037-7-git-send-email-aliguori@us.ibm.com
State New
Headers show

Commit Message

Anthony Liguori Feb. 18, 2013, 11:57 p.m. UTC
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 <aliguori@us.ibm.com>
---
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
---
 ui/gtk.c | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 240 insertions(+), 17 deletions(-)

Comments

Kevin Wolf Feb. 19, 2013, 2:06 p.m. UTC | #1
On Mon, Feb 18, 2013 at 05:57:02PM -0600, Anthony Liguori wrote:
> 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 <aliguori@us.ibm.com>

Zoom to fit doesn't only change whether the contents of the window is
scaled or not, but it also resizes the window to 100% when it's toggled.
I don't think this is the right behaviour.

Kevin
Anthony Liguori Feb. 19, 2013, 4:10 p.m. UTC | #2
Kevin Wolf <kwolf@redhat.com> writes:

> On Mon, Feb 18, 2013 at 05:57:02PM -0600, Anthony Liguori wrote:
>> 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 <aliguori@us.ibm.com>
>
> Zoom to fit doesn't only change whether the contents of the window is
> scaled or not, but it also resizes the window to 100% when it's toggled.
> I don't think this is the right behaviour.

This behavior is controlled by two lines in gd_menu_zoom_fit:

    s->scale_x = 1.0;
    s->scale_y = 1.0;

If you remove them, it keeps the window size from zooming but works
you're expecting.

I'm not sure what the Right Way is from a Gnome point of view but
evince's "Best Fit" doesn't alter the window size which I think is a
close analogy.  So I'm inclined to agree with your assessment.  I'll
remove these lines from the next revision.

Regards,

Anthony Liguori

>
> Kevin
Kevin Wolf Feb. 19, 2013, 4:29 p.m. UTC | #3
On Tue, Feb 19, 2013 at 10:10:18AM -0600, Anthony Liguori wrote:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > On Mon, Feb 18, 2013 at 05:57:02PM -0600, Anthony Liguori wrote:
> >> 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 <aliguori@us.ibm.com>
> >
> > Zoom to fit doesn't only change whether the contents of the window is
> > scaled or not, but it also resizes the window to 100% when it's toggled.
> > I don't think this is the right behaviour.
> 
> This behavior is controlled by two lines in gd_menu_zoom_fit:
> 
>     s->scale_x = 1.0;
>     s->scale_y = 1.0;
> 
> If you remove them, it keeps the window size from zooming but works
> you're expecting.

I thought the same at the first sight, but when I tried that it didn't
work and just caused strange behaviour. That's why I commented on the
patch in general.

> I'm not sure what the Right Way is from a Gnome point of view but
> evince's "Best Fit" doesn't alter the window size which I think is a
> close analogy.  So I'm inclined to agree with your assessment.  I'll
> remove these lines from the next revision.

Thanks.

Kevin
Anthony Liguori Feb. 19, 2013, 5:13 p.m. UTC | #4
Kevin Wolf <kwolf@redhat.com> writes:

> On Mon, Feb 18, 2013 at 05:57:02PM -0600, Anthony Liguori wrote:
>> 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 <aliguori@us.ibm.com>
>
> Zoom to fit doesn't only change whether the contents of the window is
> scaled or not, but it also resizes the window to 100% when it's toggled.
> I don't think this is the right behaviour.

git://github.com/aliguori/qemu.git gtk.11

Has all of the changes suggested in this thread.  I'll send out a new
set of patches this afternoon.

Regards,

Anthony Liguori

>
> Kevin
diff mbox

Patch

diff --git a/ui/gtk.c b/ui/gtk.c
index 0c1ec4d..2eaa3a8 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;
+
+    gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
 
-    x = motion->x / s->scale_x;
-    y = motion->y / s->scale_y;
+    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,93 @@  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;
+    }
+
+    s->scale_x = 1.0;
+    s->scale_y = 1.0;
+
+    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 +729,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 +920,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 +965,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),
+                                 "<QEMU>/View/Full Screen");
+    gtk_accel_map_add_entry("<QEMU>/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),
+                                 "<QEMU>/View/Zoom In");
+    gtk_accel_map_add_entry("<QEMU>/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),
+                                 "<QEMU>/View/Zoom Out");
+    gtk_accel_map_add_entry("<QEMU>/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),
+                                 "<QEMU>/View/Zoom Fixed");
+    gtk_accel_map_add_entry("<QEMU>/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 +1061,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 +1090,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);