console: Use a hiding automenu for fullscreen mode

Code is a python implementation of autodrawer. I originally found it in the
vinagre sources:
http://git.gnome.org/browse/vinagre/tree/vinagre/view

For now the menu just provides a button to leave fullscreen
This commit is contained in:
Cole Robinson 2011-05-18 17:22:07 -04:00
parent 9567ab9181
commit 77479267f3
2 changed files with 634 additions and 0 deletions

View File

@ -0,0 +1,582 @@
#
# Copyright (C) 2011 Red Hat, Inc.
# Copyright (C) 2011 Cole Robinson <crobinso@redhat.com>
#
# Python implementation of autodrawer, originally found in vinagre sources:
# http://git.gnome.org/browse/vinagre/tree/vinagre/view
# Copyright (c) 2005 VMware Inc.
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import gobject
import gtk
parentclass = gtk.VBox
class OverBox(parentclass):
"""
Implementation of an overlapping box
"""
def __init__(self):
parentclass.__init__(self)
self.underWin = None
self.underWidget = None
self.overWin = None
self.overWidget = None
self.overWidth = -1
self.overHeight = -1
self.min = 0
self._fraction = 0
self.verticalOffset = 0
self.set_has_window(True)
####################
# Internal helpers #
####################
def is_realized(self):
return bool(self.flags() & gtk.REALIZED)
def set_realized(self):
flags = self.flags() | gtk.REALIZED
self.set_flags(flags)
def _get_actual_min(self):
"""
Retrieve the actual 'min' value, i.e. a value that is guaranteed
not to exceed the height of the 'over' child.
"""
ret = min(self.min, self.overHeight)
return ret
def _get_under_window_geometry(self):
geo = gtk.gdk.Rectangle()
actual_min = self._get_actual_min()
geo.x = 0
geo.y = actual_min
geo.width = self.allocation.width
geo.height = (self.allocation.height - actual_min)
return geo
def _get_over_window_geometry(self):
geo = gtk.gdk.Rectangle()
boxwidth = self.allocation.width
expand = True
fill = True
padding = 0
actual_min = self._get_actual_min()
if self.overWidget:
expand = self.child_get(self.overWidget, "expand")[0]
fill = self.child_get(self.overWidget, "fill")[0]
padding = self.child_get(self.overWidget, "padding")[0]
if not expand:
width = min(self.overWidth, boxwidth - padding)
x = padding
elif not fill:
width = min(self.overWidth, boxwidth)
x = ((boxwidth - width) / 2)
else:
width = boxwidth
x = 0
y = (((self.overHeight - actual_min) * (self.fraction - 1)) +
self.verticalOffset)
height = self.overHeight
geo.x = x
geo.y = y
geo.width = width
geo.height = height
return geo
def _set_background(self):
style = self.get_style()
style.set_background(self.window, gtk.STATE_NORMAL)
style.set_background(self.underWin, gtk.STATE_NORMAL)
style.set_background(self.overWin, gtk.STATE_NORMAL)
def _size_request(self):
underw, underh = self.underWidget.size_request()
overw, overh = self.overWidget.size_request()
self.overWidth = overw
self.overHeight = overh
expand = self.child_get(self.overWidget, "expand")
fill = self.child_get(self.overWidget, "fill")
padding = self.child_get(self.overWidget, "padding")
if expand or fill:
wpad = 0
else:
wpad = padding
width = max(underw, overw + wpad)
height = max(underh + self._get_actual_min(), overh)
return width, height
########################
# Custom functionality #
########################
def do_set_over(self, widget):
self.set_over(widget)
def set_over(self, widget):
if self.overWidget:
self.remove(self.overWidget)
widget.set_parent_window(self.overWin)
self.add(widget)
self.overWidget = widget
def set_under(self, widget):
if self.underWidget:
self.remove(self.underWidget)
widget.set_parent_window(self.underWin)
self.add(widget)
self.underWidget = widget
self.underWidget.show_all()
def set_min(self, newmin):
self.min = newmin
self.queue_resize()
def set_fraction(self, newfraction):
self._fraction = newfraction
if self.is_realized():
overgeo = self._get_over_window_geometry()
self.overWin.move(overgeo.x, overgeo.y)
def get_fraction(self):
return self._fraction
fraction = property(get_fraction, set_fraction)
def set_vertical_offset(self, newoff):
self.verticalOffset = newoff
if self.is_realized():
overgeo = self._get_over_window_geometry()
self.overWin.move(overgeo.x, overgeo.y)
####################
# Standard methods #
####################
def do_map(self):
self.get_window().show()
parentclass.do_map(self)
def do_unmap(self):
self.get_window().hide()
parentclass.do_unmap(self)
def do_realize(self):
event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK
colormap = self.get_colormap()
visual = self.get_visual()
self.set_realized()
def make_window(parent, rect):
return gtk.gdk.Window(parent,
rect.width,
rect.height,
gtk.gdk.WINDOW_CHILD,
event_mask,
gtk.gdk.INPUT_OUTPUT,
x=rect.x,
y=rect.y,
colormap=colormap,
visual=visual)
window = make_window(self.get_parent_window(), self.allocation)
self.window = window
self.window.set_user_data(self)
self.style.attach(window)
self.underWin = make_window(window,
self._get_under_window_geometry())
self.underWin.set_user_data(self)
if self.underWidget:
self.underWidget.set_parent_window(self.underWin)
self.underWin.show()
self.overWin = make_window(window,
self._get_over_window_geometry())
self.overWin.set_user_data(self)
if self.overWidget:
self.overWidget.set_parent_window(self.overWin)
self.overWin.show()
self._set_background()
def do_unrealize(self):
parentclass.do_unrealize(self)
self.overWin.destroy()
self.overWin = None
self.underWin.destroy()
self.underWin = None
def do_size_request(self, req):
height, width = self._size_request()
req.height = height
req.width = width
def do_size_allocate(self, newalloc):
self.allocation = newalloc
over = self._get_over_window_geometry()
under = self._get_under_window_geometry()
if self.is_realized():
self.window.move_resize(newalloc.x, newalloc.y,
newalloc.width, newalloc.height)
self.underWin.move_resize(under.x, under.y,
under.width, under.height)
self.overWin.move_resize(over.x, over.y,
over.width, over.height)
under.x = 0
under.y = 0
self.underWidget.size_allocate(under)
over.x = 0
over.y = 0
self.overWidget.size_allocate(over)
def do_style_set(self, style):
if self.is_realized():
self.set_background()
parentclass.do_style_set(self, style)
class Drawer(OverBox):
"""
Implementation of a drawer, basically a floating toolbar
"""
def __init__(self):
OverBox.__init__(self)
self.period = 10
self.step = 0.2
self.goal = 0
self.timer_pending = False
self.timer_id = None
# XXX: C version has a finalize impl
####################
# Internal helpers #
####################
def _on_timer(self):
fraction = self.fraction
if self.goal == fraction:
self.timer_pending = False
return False
if self.goal > fraction:
self.fraction = min(fraction + self.step, self.goal)
else:
self.fraction = max(fraction - self.step, self.goal)
return True
##############
# Public API #
##############
def set_speed(self, period, step):
self.period = period
if self.timer_pending:
gobject.source_remove(self.timer_id)
self.timer_id = gobject.timeout_add(self.period, self._on_timer)
self.step = step
def set_goal(self, goal):
self.goal = goal
if not self.timer_pending:
self.timer_id = gobject.timeout_add(self.period, self._on_timer)
self.timer_pending = True
def get_close_time(self):
return (self.period * (int(1 / self.step) + 1))
class AutoDrawer(Drawer):
"""
Implemenation of an autodrawer, a drawer that hides and reappears on
mouse over, slowly sliding into view
"""
def __init__(self):
Drawer.__init__(self)
self.active = True
self.pinned = False
self.forceClosing = False
self.inputUngrabbed = True
self.opened = False
self.fill = True
self.offset = -1
self.closeConnection = 0
self.delayConnection = 0
self.delayValue = 250
self.overlapPixels = 0
self.noOverlapPixels = 1
self.over = None
self.eventBox = gtk.EventBox()
self.eventBox.show()
OverBox.set_over(self, self.eventBox)
self.eventBox.connect("enter-notify-event", self._on_over_enter_leave)
self.eventBox.connect("leave-notify-event", self._on_over_enter_leave)
self.eventBox.connect("grab-notify", self._on_grab_notify)
self.connect("hierarchy-changed", self._on_hierarchy_changed)
self._update(True)
self._refresh_packing()
# XXX: Has a finalize method
####################
# Internal Helpers #
####################
def set_over(self, newover):
oldChild = self.eventBox.get_child()
if oldChild:
self.eventBox.remove(oldChild)
if newover:
self.eventBox.add(newover)
self.over = newover
def _update(self, do_immediate):
toplevel = self.get_toplevel()
if not toplevel or not toplevel.is_toplevel():
# The autoDrawer cannot function properly without a toplevel.
return
self.opened = False
# Is the drawer pinned open?
if self.pinned:
do_immediate = True
self.opened = True
# Is the mouse cursor inside the event box? */
x, y = self.eventBox.get_pointer()
alloc = self.eventBox.get_allocation()
if x > -1 and y > -1 and x < alloc.width and y < alloc.height:
self.opened = True
# If there is a focused widget, is it inside the event box? */
focus = toplevel.get_focus()
if focus and focus.is_ancestor(self.eventBox):
do_immediate = True
self.opened = True
# If input is grabbed, is it on behalf of a widget inside the
# event box?
if not self.inputUngrabbed:
grabbed = None
if toplevel.get_group():
# XXX: Not in pygtk?
#grabbed = toplevel.get_group().get_current_grab()
pass
if not grabbed:
grabbed = gtk.grab_get_current()
if grabbed and isinstance(grabbed, gtk.Menu):
while True:
menuAttach = grabbed.get_attach_widget()
if not menuAttach:
break
grabbed = menuAttach
if not isinstance(grabbed, gtk.MenuItem):
break
menuItemParent = grabbed.get_parent()
if not isinstance(menuItemParent, gtk.Menu):
break
grabbed = menuItemParent
if grabbed and grabbed.is_ancestor(self.eventBox):
do_immediate = True
self.opened = True
if self.delayConnection:
gobject.source_remove(self.delayConnection)
if self.forceClosing:
self._enforce(True)
elif do_immediate:
self._enforce(False)
else:
self.delayConnection = gobject.timeout_add(self.delayValue,
self._on_enforce_delay)
def _refresh_packing(self):
expand = bool(self.fill or (self.offset < 0))
if expand or self.fill:
padding = 0
else:
padding = self.offset
self.set_child_packing(self.eventBox, expand, self.fill, padding,
gtk.PACK_START)
def _enforce(self, do_animate):
if not self.active:
self.set_min(0)
self.set_fraction(0)
return
self.set_min(self.noOverlapPixels)
if self.opened and not self.forceClosing:
fraction = 1
else:
alloc = self.over.get_allocation()
fraction = (float(self.overlapPixels) / alloc.height)
if not do_animate:
self.set_fraction(fraction)
self.set_goal(fraction)
#############
# Listeners #
#############
def _set_focus(self, ignore1, ignore2):
self._update(False)
def _on_over_enter_leave(self, ignore1, ignore2):
self._update(False)
def _on_grab_notify(self, eventbox, is_ungrabbed):
ignore = eventbox
self.inputUngrabbed = is_ungrabbed
self._update(False)
def _on_hierarchy_changed(self, oldTopLevel, ignore):
newTopLevel = self.get_toplevel()
if oldTopLevel and oldTopLevel.is_toplevel():
oldTopLevel.disconnect_by_func(self._set_focus)
if newTopLevel and newTopLevel.is_toplevel():
newTopLevel.connect_after("set_focus", self._set_focus)
self._update(True)
def _on_enforce_delay(self):
self.delayConnection = 0
self._enforce(True)
return False
def _on_close_delay(self):
self.closeConnection = 0
self.forceClosing = False
return False
##############
# Public API #
##############
def set_slide_delay(self, delay):
self.delayValue = delay
def set_overlap_pixels(self, overlap_pixels):
self.overlapPixels = overlap_pixels
self._update(True)
def set_nooverlap_pixels(self, nooverlap_pixels):
self.noOverlapPixels = nooverlap_pixels
self._update(True)
def set_active(self, active):
self.active = active
self._update(True)
def set_pinned(self, pinned):
self.pinned = pinned
self._update(False)
def set_fill(self, fill):
self.fill = fill
self._refresh_packing()
def set_offset(self, offset):
self.offset = offset
self._refresh_packing()
def drawer_close(self):
toplevel = self.get_toplevel()
if not toplevel or not toplevel.is_toplevel():
# The autoDrawer cannot function properly without a toplevel.
return
focus = toplevel.get_focus()
if focus and focus.is_ancestor(self.eventBox):
toplevel.set_focus(None)
self.forceClosing = True
self.closeConnection = gobject.timeout_add(
self.get_close_time() + self.delayValue,
self._on_close_delay)
self._update(True)
gobject.type_register(OverBox)
gobject.type_register(Drawer)
gobject.type_register(AutoDrawer)

View File

@ -37,6 +37,8 @@ import signal
import socket
import logging
import virtManager.util as util
from virtManager.autodrawer import AutoDrawer
from virtManager.baseclass import vmmGObjectUI, vmmGObject
from virtManager.error import vmmErrorDialog
@ -545,6 +547,11 @@ class vmmConsolePages(vmmGObjectUI):
self.viewer_connecting = False
self.scale_type = self.vm.get_console_scaling()
# Fullscreen toolbar
self.fs_toolbar = None
self.fs_drawer = None
self.init_fs_toolbar()
finish_img = gtk.image_new_from_stock(gtk.STOCK_YES,
gtk.ICON_SIZE_BUTTON)
self.window.get_widget("console-auth-login").set_image(finish_img)
@ -579,10 +586,44 @@ class vmmConsolePages(vmmGObjectUI):
self.viewer.cleanup()
self.viewer = None
self.fs_toolbar.destroy()
self.fs_toolbar = None
self.fs_drawer.destroy()
self.fs_drawer = None
##########################
# Initialization helpers #
##########################
def init_fs_toolbar(self):
scroll = self.window.get_widget("console-vnc-scroll")
pages = self.window.get_widget("console-pages")
pages.remove(scroll)
self.fs_toolbar = gtk.Toolbar()
self.fs_toolbar.set_show_arrow(False)
self.fs_toolbar.set_no_show_all(True)
self.fs_toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
# Exit fullscreen button
button = gtk.ToolButton(gtk.STOCK_LEAVE_FULLSCREEN)
util.tooltip_wrapper(button, _("Leave fullscreen"))
button.show()
self.fs_toolbar.add(button)
button.connect("clicked", self.leave_fullscreen)
self.fs_drawer = AutoDrawer()
self.fs_drawer.set_active(False)
self.fs_drawer.set_over(self.fs_toolbar)
self.fs_drawer.set_under(scroll)
self.fs_drawer.set_offset(-1)
self.fs_drawer.set_fill(False)
self.fs_drawer.set_overlap_pixels(1)
self.fs_drawer.set_nooverlap_pixels(0)
self.fs_drawer.show_all()
pages.add(self.fs_drawer)
def change_title(self, ignore1=None):
title = self.vm.get_name() + " " + _("Virtual Machine")
@ -703,17 +744,28 @@ class vmmConsolePages(vmmGObjectUI):
def toggle_fullscreen(self, src):
do_fullscreen = src.get_active()
self._change_fullscreen(do_fullscreen)
def leave_fullscreen(self, ignore):
self._change_fullscreen(False)
def _change_fullscreen(self, do_fullscreen):
self.window.get_widget("control-fullscreen").set_active(do_fullscreen)
if do_fullscreen:
self.topwin.fullscreen()
self.fs_toolbar.show()
self.fs_drawer.set_active(True)
self.window.get_widget("toolbar-box").hide()
self.window.get_widget("details-menubar").hide()
else:
self.fs_toolbar.hide()
self.fs_drawer.set_active(False)
self.topwin.unfullscreen()
if self.window.get_widget("details-menu-view-toolbar").get_active():
self.window.get_widget("toolbar-box").show()
self.window.get_widget("details-menubar").show()
self.update_scaling()