manager: Move button toolbar to top of the window.

A toolbar at the top is much more in line with existing UI convention, and is
more intuitive. Delete is dropped from the upfront buttons (since this likely
isn't a common operation), and buttons for shutdown and pause are added.
This commit is contained in:
Cole Robinson 2009-07-26 16:15:05 -04:00
parent 8725f6ed59
commit 387045636a
4 changed files with 227 additions and 146 deletions

View File

@ -34,6 +34,7 @@ import cairo
from virtManager.error import vmmErrorDialog from virtManager.error import vmmErrorDialog
from virtManager.addhardware import vmmAddHardware from virtManager.addhardware import vmmAddHardware
from virtManager.choosecd import vmmChooseCD from virtManager.choosecd import vmmChooseCD
from virtManager.manager import build_shutdown_button_menu
from virtManager.serialcon import vmmSerialConsole from virtManager.serialcon import vmmSerialConsole
from virtManager.graphwidgets import Sparkline from virtManager.graphwidgets import Sparkline
from virtManager import util as util from virtManager import util as util
@ -134,35 +135,11 @@ class vmmDetails(gobject.GObject):
else: else:
self.window.get_widget("add-hardware-button").set_sensitive(True) self.window.get_widget("add-hardware-button").set_sensitive(True)
icon_name = self.config.get_shutdown_icon_name() build_shutdown_button_menu(self.config,
self.window.get_widget("control-shutdown").set_icon_name(icon_name) self.window.get_widget("control-shutdown"),
menu = gtk.Menu() self.control_vm_shutdown,
self.window.get_widget("control-shutdown").set_menu(menu) self.control_vm_reboot,
self.control_vm_destroy)
rebootimg = gtk.image_new_from_icon_name(icon_name,
gtk.ICON_SIZE_MENU)
shutdownimg = gtk.image_new_from_icon_name(icon_name,
gtk.ICON_SIZE_MENU)
destroyimg = gtk.image_new_from_icon_name(icon_name,
gtk.ICON_SIZE_MENU)
reboot = gtk.ImageMenuItem(_("_Reboot"))
reboot.set_image(rebootimg)
reboot.show()
reboot.connect("activate", self.control_vm_reboot)
menu.add(reboot)
shutdown = gtk.ImageMenuItem(_("_Shut Down"))
shutdown.set_image(shutdownimg)
shutdown.show()
shutdown.connect("activate", self.control_vm_shutdown)
menu.add(shutdown)
destroy = gtk.ImageMenuItem(_("_Force Off"))
destroy.set_image(destroyimg)
destroy.show()
destroy.connect("activate", self.control_vm_destroy)
menu.add(destroy)
smenu = gtk.Menu() smenu = gtk.Menu()
smenu.connect("show", self.populate_serial_menu) smenu.connect("show", self.populate_serial_menu)

View File

@ -671,6 +671,27 @@ class vmmDomain(gobject.GObject):
def status(self): def status(self):
return self.lastStatus return self.lastStatus
def is_stoppable(self):
return self.status() in [libvirt.VIR_DOMAIN_RUNNING,
libvirt.VIR_DOMAIN_PAUSED]
def is_destroyable(self):
return (self.is_stoppable() or
self.status() in [libvirt.VIR_DOMAIN_CRASHED])
def is_runable(self):
return self.status() in [libvirt.VIR_DOMAIN_SHUTOFF,
libvirt.VIR_DOMAIN_CRASHED]
def is_pauseable(self):
return self.status() in [libvirt.VIR_DOMAIN_RUNNING]
def is_unpauseable(self):
return self.status() in [libvirt.VIR_DOMAIN_PAUSED]
def is_paused(self):
return self.status() in [libvirt.VIR_DOMAIN_PAUSED]
def run_status(self): def run_status(self):
if self.lastStatus == libvirt.VIR_DOMAIN_RUNNING: if self.lastStatus == libvirt.VIR_DOMAIN_RUNNING:
return _("Running") return _("Running")

View File

@ -67,6 +67,51 @@ COL_MEM = 5
COL_DISK = 6 COL_DISK = 6
COL_NETWORK = 7 COL_NETWORK = 7
rcstring = """
style "toolbar-style" {
#GtkToolbar::button_relief = GTK_RELIEF_NONE
#GtkToolbar::shadow_type = GTK_SHADOW_NONE
GtkToolbar::internal_padding = 2
}
style "treeview-style" {
GtkTreeView::indent_expanders = 0
}
class "GtkToolbar" style "toolbar-style"
class "GtkTreeView" style "treeview-style"
"""
gtk.rc_parse_string(rcstring)
def build_shutdown_button_menu(config, widget, shutdown_cb, reboot_cb,
destroy_cb):
icon_name = config.get_shutdown_icon_name()
widget.set_icon_name(icon_name)
menu = gtk.Menu()
widget.set_menu(menu)
rebootimg = gtk.image_new_from_icon_name(icon_name, gtk.ICON_SIZE_MENU)
shutdownimg = gtk.image_new_from_icon_name(icon_name, gtk.ICON_SIZE_MENU)
destroyimg = gtk.image_new_from_icon_name(icon_name, gtk.ICON_SIZE_MENU)
reboot = gtk.ImageMenuItem(_("_Reboot"))
reboot.set_image(rebootimg)
reboot.show()
reboot.connect("activate", reboot_cb)
menu.add(reboot)
shutdown = gtk.ImageMenuItem(_("_Shut Down"))
shutdown.set_image(shutdownimg)
shutdown.show()
shutdown.connect("activate", shutdown_cb)
menu.add(shutdown)
destroy = gtk.ImageMenuItem(_("_Force Off"))
destroy.set_image(destroyimg)
destroy.show()
destroy.connect("activate", destroy_cb)
menu.add(destroy)
class vmmManager(gobject.GObject): class vmmManager(gobject.GObject):
__gsignals__ = { __gsignals__ = {
"action-show-connect":(gobject.SIGNAL_RUN_FIRST, "action-show-connect":(gobject.SIGNAL_RUN_FIRST,
@ -124,6 +169,7 @@ class vmmManager(gobject.GObject):
self.delete_dialog = None self.delete_dialog = None
self.startup_error = None self.startup_error = None
self.ignore_pause = False
self.prepare_vmlist() self.prepare_vmlist()
@ -161,6 +207,19 @@ class vmmManager(gobject.GObject):
self.vmmenu_icons["resume"].set_from_stock(gtk.STOCK_MEDIA_PAUSE, self.vmmenu_icons["resume"].set_from_stock(gtk.STOCK_MEDIA_PAUSE,
gtk.ICON_SIZE_MENU) gtk.ICON_SIZE_MENU)
def set_toolbar_image(widget, iconfile):
filename = self.config.get_icon_dir() + "/%s" % iconfile
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 38, 38)
image = gtk.image_new_from_pixbuf(pixbuf)
self.window.get_widget(widget).set_icon_widget(image)
set_toolbar_image("vm-new", "vm_new_large.png")
build_shutdown_button_menu(self.config,
self.window.get_widget("vm-shutdown"),
self.poweroff_vm,
self.reboot_vm,
self.destroy_vm)
icon_name = self.config.get_shutdown_icon_name() icon_name = self.config.get_shutdown_icon_name()
rebootimg = gtk.image_new_from_icon_name(icon_name, rebootimg = gtk.image_new_from_icon_name(icon_name,
gtk.ICON_SIZE_MENU) gtk.ICON_SIZE_MENU)
@ -257,7 +316,7 @@ class vmmManager(gobject.GObject):
self.connmenu_items["create"] = gtk.ImageMenuItem(gtk.STOCK_NEW) self.connmenu_items["create"] = gtk.ImageMenuItem(gtk.STOCK_NEW)
self.connmenu_items["create"].show() self.connmenu_items["create"].show()
self.connmenu_items["create"].connect("activate", self.show_vm_create) self.connmenu_items["create"].connect("activate", self.new_vm)
self.connmenu.add(self.connmenu_items["create"]) self.connmenu.add(self.connmenu_items["create"])
self.connmenu_items["connect"] = gtk.ImageMenuItem(gtk.STOCK_CONNECT) self.connmenu_items["connect"] = gtk.ImageMenuItem(gtk.STOCK_CONNECT)
@ -297,8 +356,10 @@ class vmmManager(gobject.GObject):
"on_menu_restore_saved_activate": self.restore_saved, "on_menu_restore_saved_activate": self.restore_saved,
"on_vmm_close_clicked": self.close, "on_vmm_close_clicked": self.close,
"on_vm_open_clicked": self.open_vm_console, "on_vm_open_clicked": self.open_vm_console,
"on_vm_delete_clicked": self.delete_vm, "on_vm_run_clicked": self.start_vm,
"on_vm_new_clicked": self.new_vm, "on_vm_new_clicked": self.new_vm,
"on_vm_shutdown_clicked": self.poweroff_vm,
"on_vm_pause_clicked": self.pause_vm_button,
"on_menu_edit_details_activate": self.open_vm_console, "on_menu_edit_details_activate": self.open_vm_console,
"on_menu_edit_delete_activate": self.delete_vm, "on_menu_edit_delete_activate": self.delete_vm,
"on_menu_host_details_activate": self.show_host, "on_menu_host_details_activate": self.show_host,
@ -517,6 +578,8 @@ class vmmManager(gobject.GObject):
if missing: if missing:
self._append_vm(model, vm, vm.get_connection()) self._append_vm(model, vm, vm.get_connection())
# Update run/shutdown/pause button states
self.vm_selected()
def vm_resources_sampled(self, vm): def vm_resources_sampled(self, vm):
vmlist = self.window.get_widget("vm-list") vmlist = self.window.get_widget("vm-list")
@ -543,18 +606,10 @@ class vmmManager(gobject.GObject):
row[ROW_NET_TX] = vm.network_tx_rate() row[ROW_NET_TX] = vm.network_tx_rate()
model.row_changed(row.path, row.iter) model.row_changed(row.path, row.iter)
if vm == self.current_vm():
if vm.is_active():
self.window.get_widget("vm-delete").set_sensitive(False)
self.window.get_widget("menu_edit_delete").set_sensitive(False)
else:
self.window.get_widget("vm-delete").set_sensitive(True)
self.window.get_widget("menu_edit_delete").set_sensitive(True)
def conn_state_changed(self, conn): def conn_state_changed(self, conn):
self.conn_refresh_resources(conn) self.conn_refresh_resources(conn)
self.vm_selected(self.window.get_widget("vm-list").get_selection()) self.vm_selected()
def conn_refresh_resources(self, conn): def conn_refresh_resources(self, conn):
vmlist = self.window.get_widget("vm-list") vmlist = self.window.get_widget("vm-list")
@ -626,9 +681,6 @@ class vmmManager(gobject.GObject):
else: else:
self.emit("action-show-console", conn.get_uri(), self.current_vmuuid()) self.emit("action-show-console", conn.get_uri(), self.current_vmuuid())
def show_vm_create(self,ignore):
self.emit("action-show-create", self.current_connection_uri())
def close_connection(self, ignore): def close_connection(self, ignore):
conn = self.current_connection() conn = self.current_connection()
if conn.get_state() != vmmConnection.STATE_DISCONNECTED: if conn.get_state() != vmmConnection.STATE_DISCONNECTED:
@ -650,45 +702,34 @@ class vmmManager(gobject.GObject):
self.emit("action-clone-domain", self.current_connection_uri(), self.emit("action-clone-domain", self.current_connection_uri(),
self.current_vmuuid()) self.current_vmuuid())
def vm_selected(self, selection): def vm_selected(self, ignore=None):
conn = self.current_connection() conn = self.current_connection()
vm = self.current_vm() vm = self.current_vm()
if selection == None or selection.count_selected_rows() == 0:
# Nothing is selected show_open = bool(vm)
self.window.get_widget("vm-open").set_sensitive(False) show_details = bool(vm)
self.window.get_widget("vm-delete").set_sensitive(False) host_details = bool(vm or conn)
self.window.get_widget("menu_edit_details").set_sensitive(False) delete = bool((vm and vm.is_runable()) or conn)
self.window.get_widget("menu_edit_delete").set_sensitive(False) show_run = bool(vm and vm.is_runable())
self.window.get_widget("menu_host_details").set_sensitive(False) is_paused = bool(vm and vm.is_paused())
self.window.get_widget("menu_file_restore_saved").set_sensitive(False) if is_paused:
elif vm is not None: show_pause = bool(vm and vm.is_unpauseable())
# A VM is selected
# this is strange to call this here, but it simplifies the code
# updating the treeview
self.vm_resources_sampled(vm)
self.window.get_widget("vm-open").set_sensitive(True)
if vm.status() == libvirt.VIR_DOMAIN_SHUTOFF:
self.window.get_widget("vm-delete").set_sensitive(True)
else:
self.window.get_widget("vm-delete").set_sensitive(False)
self.window.get_widget("menu_edit_details").set_sensitive(True)
self.window.get_widget("menu_edit_delete").set_sensitive(True)
self.window.get_widget("menu_host_details").set_sensitive(True)
self.window.get_widget("menu_file_restore_saved").set_sensitive(False)
else: else:
# A connection is selected show_pause = bool(vm and vm.is_pauseable())
self.window.get_widget("vm-open").set_sensitive(False) show_shutdown = bool(vm and vm.is_stoppable())
if conn.get_state() == vmmConnection.STATE_DISCONNECTED: restore = bool(conn and conn.get_state() == vmmConnection.STATE_ACTIVE)
self.window.get_widget("vm-delete").set_sensitive(True)
else: self.window.get_widget("vm-open").set_sensitive(show_open)
self.window.get_widget("vm-delete").set_sensitive(False) self.window.get_widget("vm-run").set_sensitive(show_run)
if conn.get_state() == vmmConnection.STATE_ACTIVE: self.window.get_widget("vm-shutdown").set_sensitive(show_shutdown)
self.window.get_widget("menu_file_restore_saved").set_sensitive(True) self.set_pause_state(is_paused)
else: self.window.get_widget("vm-pause").set_sensitive(show_pause)
self.window.get_widget("menu_file_restore_saved").set_sensitive(False)
self.window.get_widget("menu_edit_details").set_sensitive(False) self.window.get_widget("menu_edit_details").set_sensitive(show_details)
self.window.get_widget("menu_edit_delete").set_sensitive(False) self.window.get_widget("menu_host_details").set_sensitive(host_details)
self.window.get_widget("menu_host_details").set_sensitive(True) self.window.get_widget("menu_edit_delete").set_sensitive(delete)
self.window.get_widget("menu_file_restore_saved").set_sensitive(restore)
def popup_vm_menu(self, widget, event): def popup_vm_menu(self, widget, event):
tup = widget.get_path_at_pos(int(event.x), int(event.y)) tup = widget.get_path_at_pos(int(event.x), int(event.y))
@ -1022,6 +1063,29 @@ class vmmManager(gobject.GObject):
data = model.get_value(_iter, ROW_HANDLE).network_traffic_vector_limit(40) data = model.get_value(_iter, ROW_HANDLE).network_traffic_vector_limit(40)
cell.set_property('data_array', data) cell.set_property('data_array', data)
def set_pause_state(self, state):
src = self.window.get_widget("vm-pause")
try:
self.ignore_pause = True
src.set_active(state)
finally:
self.ignore_pause = False
def pause_vm_button(self, src):
if self.ignore_pause:
return
do_pause = src.get_active()
if do_pause:
self.pause_vm(None)
else:
self.resume_vm(None)
# Set button state back to original value: just let the status
# update function fix things for us
self.set_pause_state(not do_pause)
def start_vm(self, ignore): def start_vm(self, ignore):
vm = self.current_vm() vm = self.current_vm()
if vm is not None: if vm is not None:

View File

@ -262,94 +262,113 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkVBox" id="vbox2"> <widget class="GtkVBox" id="vbox2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="border_width">6</property> <child>
<property name="spacing">6</property> <widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<widget class="GtkToolbar" id="vm-toolbar1">
<property name="visible">True</property>
<property name="show_arrow">False</property>
<property name="icon_size">GTK_ICON_SIZE_DND</property>
<child>
<widget class="GtkToolButton" id="vm-new">
<property name="visible">True</property>
<property name="label" translatable="yes">_New</property>
<property name="use_underline">True</property>
<property name="icon_name">vm_new_large</property>
<signal name="clicked" handler="on_vm_new_clicked"/>
</widget>
<packing>
<property name="homogeneous">True</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkToolbar" id="vm-toolbar2">
<property name="visible">True</property>
<property name="show_arrow">False</property>
<child>
<widget class="GtkToolButton" id="vm-open">
<property name="visible">True</property>
<property name="label" translatable="yes">_Open</property>
<property name="use_underline">True</property>
<property name="icon_name">icon_console</property>
<signal name="clicked" handler="on_vm_open_clicked"/>
</widget>
<packing>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkToolButton" id="vm-run">
<property name="visible">True</property>
<property name="label" translatable="yes">_Run</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-media-play</property>
<signal name="clicked" handler="on_vm_run_clicked"/>
</widget>
<packing>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkToggleToolButton" id="vm-pause">
<property name="visible">True</property>
<property name="label" translatable="yes">_Pause</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-media-pause</property>
<signal name="clicked" handler="on_vm_pause_clicked"/>
</widget>
<packing>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkMenuToolButton" id="vm-shutdown">
<property name="visible">True</property>
<property name="label" translatable="yes">_Shutdown</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_vm_shutdown_clicked"/>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow1"> <widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property> <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">automatic</property> <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">in</property> <property name="shadow_type">GTK_SHADOW_IN</property>
<child> <child>
<widget class="GtkTreeView" id="vm-list"> <widget class="GtkTreeView" id="vm-list">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="rules_hint">True</property> <property name="rules_hint">True</property>
<signal name="button_press_event" handler="on_vm_list_button_press_event"/> <signal name="button_press_event" handler="on_vm_list_button_press_event"/>
<signal name="row_expanded" handler="on_vm_list_row_expanded"/>
<signal name="row_collapsed" handler="on_vm_list_row_collapsed"/> <signal name="row_collapsed" handler="on_vm_list_row_collapsed"/>
<signal name="row_expanded" handler="on_vm_list_row_expanded"/>
<signal name="row_activated" handler="on_vm_list_row_activated"/> <signal name="row_activated" handler="on_vm_list_row_activated"/>
</widget> </widget>
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="spacing">3</property>
<property name="layout_style">end</property>
<child>
<widget class="GtkButton" id="vm-delete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_vm_delete_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="vm-new">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_vm_new_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="vm-open">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_vm_open_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>