# DRI configuration GUI: complex UI components # Copyright (C) 2003-2006 Felix Kuehling # 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 # Contact: http://fxk.de.vu/ import dri import pygtk pygtk.require ("2.0") import gtk import gobject import driconf_commonui commonui = driconf_commonui # short cut from driconf_commonui import _, lang, findInShared, escapeMarkup, WrappingCheckButton, SectionPage, UnknownSectionPage class DriverPanel (gtk.Frame): """ Panel for driver settings for a specific application. """ def __init__ (self, driver, app): """ Constructor. """ gtk.Frame.__init__ (self) frameLabel = gtk.Label() frameLabel.set_markup ("" + escapeMarkup( _("Application")+": "+app.name) + "") frameLabel.show() self.set_label_widget (frameLabel) self.driver = driver self.app = app tooltips = gtk.Tooltips() table = gtk.Table(2, 2) self.execCheck = WrappingCheckButton (_("Apply only to this executable")) self.execCheck.set_sensitive (app.device.config.writable) tooltips.set_tip (self.execCheck, _( "Leave this disabled to configure all applications.\n" "Beware that some applications or games are just a shell script " "that starts a real executable with a different name.")) self.execCheck.show() table.attach (self.execCheck, 0, 1, 0, 1, 0, 0, 5, 5) self.execEntry = gtk.Entry() if app.executable != None: self.execCheck.set_active (True) self.execEntry.set_text (app.executable) self.execEntry.set_sensitive (app.device.config.writable and app.executable != None) self.execEntry.show() self.execCheck.connect ("toggled", self.execToggled) self.execEntry.connect ("changed", self.execChanged) table.attach (self.execEntry, 1, 2, 0, 1, gtk.EXPAND|gtk.FILL, 0, 5, 5) notebook = gtk.Notebook() notebook.popup_enable() notebook.set_scrollable (True) self.sectPages = [] self.sectLabels = [] unknownPage = UnknownSectionPage (driver, app) if not driver or len(unknownPage.opts) > 0: unknownPage.show() unknownLabel = gtk.Label (_("Unknown")) unknownLabel.show() notebook.append_page (unknownPage, unknownLabel) self.sectPages.append (unknownPage) self.sectLabels.append (unknownLabel) if driver: for sect in driver.optSections: sectPage = SectionPage (sect, app, False) sectPage.show() desc = sect.getDesc([lang]) if desc: sectLabel = gtk.Label (desc) sectLabel.set_line_wrap (True) else: sectLabel = gtk.Label (_("(no description)")) sectLabel.show() notebook.append_page (sectPage, sectLabel) self.sectPages.append (sectPage) self.sectLabels.append (sectLabel) if len(self.sectLabels) > 0: style = self.sectLabels[0].get_style() self.default_normal_fg = style.fg[gtk.STATE_NORMAL].copy() self.default_active_fg = style.fg[gtk.STATE_ACTIVE].copy() self.validate() notebook.show() table.attach (notebook, 0, 2, 1, 2, gtk.FILL, gtk.EXPAND|gtk.FILL, 5, 5) table.show() self.add (table) def execChanged (self, widget): self.app.modified(self.app) def execToggled (self, widget): self.execEntry.set_sensitive (self.execCheck.get_active()) self.app.modified(self.app) def validate (self): """ Validate the configuration. Labels of invalid section pages are highlighted. Returns whether there were invalid option values. """ index = 0 allValid = True for sectPage in self.sectPages: valid = sectPage.validate() if not valid: # strange, active and normal appear to be swapped :-/ self.sectLabels[index].modify_fg ( gtk.STATE_NORMAL, gtk.gdk.Color (65535, 0, 0)) self.sectLabels[index].modify_fg ( gtk.STATE_ACTIVE, gtk.gdk.Color (65535, 0, 0)) else: self.sectLabels[index].modify_fg ( gtk.STATE_NORMAL, self.default_normal_fg) self.sectLabels[index].modify_fg ( gtk.STATE_ACTIVE, self.default_active_fg) allValid = allValid and valid index = index+1 return allValid def commit (self): """ Commit changes to the configuration. """ executable = self.execEntry.get_text() if not self.execCheck.get_active(): if self.app.executable != None: self.app.executable = None self.app.modified(self.app) elif executable != self.app.executable: self.app.executable = executable self.app.modified(self.app) for sectPage in self.sectPages: sectPage.commit() def renameApp (self): """ Change the application name. """ self.set_label ("Application: " + self.app.name) class NameDialog (gtk.Dialog): """ Dialog for setting the name of an application. """ def __init__ (self, title, callback, name, data): gtk.Dialog.__init__ (self, title, commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self.callback = callback self.data = data self.connect ("response", self.responseSignal) table = gtk.Table(2, 2) commentLabel = gtk.Label (_( "Enter the name of the application below. This just serves as " "a description for you. Don't forget to set the executable " "afterwards.")) commentLabel.set_line_wrap (True) commentLabel.show() table.attach (commentLabel, 0, 2, 0, 1, gtk.EXPAND|gtk.FILL, gtk.EXPAND, 10, 5) label = gtk.Label (_("Application Name")) label.show() table.attach (label, 0, 1, 1, 2, 0, gtk.EXPAND, 10, 5) self.entry = gtk.Entry() self.entry.set_text (name) self.entry.select_region (0, len(name)) self.entry.connect ("activate", self.activateSignal) self.entry.show() table.attach (self.entry, 1, 2, 1, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND, 10, 5) table.show() self.vbox.pack_start (table, True, True, 5) self.show() self.entry.grab_focus() def activateSignal (self, widget): self.response (gtk.RESPONSE_OK) def responseSignal (self, dialog, response): if response == gtk.RESPONSE_OK: self.callback (self.entry.get_text(), self.data) self.destroy() class DeviceDialog (gtk.Dialog): """ Dialog for choosing driver and screen of a device. """ def __init__ (self, title, callback, data): gtk.Dialog.__init__ (self, title, commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self.callback = callback self.data = data self.connect ("response", self.responseSignal) table = gtk.Table (2, 3) commentLabel = gtk.Label (_( "Describe the device that you would like to configure.")) commentLabel.set_line_wrap (True) commentLabel.show() table.attach (commentLabel, 0, 2, 0, 1, gtk.EXPAND|gtk.FILL, gtk.EXPAND, 10, 5) screenLabel = gtk.Label (_("Screen Number")) screenLabel.show() table.attach (screenLabel, 0, 1, 1, 2, 0, gtk.EXPAND, 10, 5) self.screenCombo = gtk.Combo() self.screenCombo.set_popdown_strings ( [""]+map(str,range(len(commonui.dpy.screens)))) self.screenCombo.entry.connect ("activate", self.screenSignal) self.screenCombo.list.connect ("select_child", self.screenSignal) self.screenCombo.show() table.attach (self.screenCombo, 1, 2, 1, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND, 10, 5) driverLabel = gtk.Label (_("Driver Name")) driverLabel.show() table.attach (driverLabel, 0, 1, 2, 3, 0, gtk.EXPAND, 10, 5) self.driverCombo = gtk.Combo() self.driverCombo.set_popdown_strings ( [""]+[str(driver.name) for driver in dri.DisplayInfo.drivers.values()]) self.driverCombo.show() table.attach (self.driverCombo, 1, 2, 2, 3, gtk.EXPAND|gtk.FILL, gtk.EXPAND, 10, 5) if data and data.__class__ == dri.DeviceConfig: if data.screen: self.screenCombo.entry.set_text (data.screen) if data.driver: self.driverCombo.entry.set_text (data.driver) table.show() self.vbox.pack_start (table, True, True, 5) self.show() def screenSignal (self, widget, data=None): screenName = self.screenCombo.entry.get_text() try: screenNum = int(screenName) except ValueError: pass else: screen = commonui.dpy.getScreen(screenNum) if screen != None: driver = screen.driver self.driverCombo.entry.set_text (str(driver.name)) def responseSignal (self, dialog, response): if response == gtk.RESPONSE_OK: self.callback (self.screenCombo.entry.get_text(), self.driverCombo.entry.get_text(), self.data) self.destroy() class ConfigTreeModel (gtk.GenericTreeModel): # constructur def __init__ (self, configList): gtk.GenericTreeModel.__init__ (self) self.configList = [] for config in configList: self.addNode (config) def iconFromShared (self, name, size, missing): path = findInShared (name) if path: icon = gtk.gdk.pixbuf_new_from_file (path) return icon.scale_simple (size, size, gtk.gdk.INTERP_BILINEAR) else: return missing def renderIcons (self, widget): self.configIcon = widget.render_icon ("gtk-properties", gtk.ICON_SIZE_MENU, None) self.appIcon = widget.render_icon ("gtk-execute", gtk.ICON_SIZE_MENU, None) self.unspecIcon = widget.render_icon ("gtk-dialog-question", gtk.ICON_SIZE_MENU, None) missing = widget.render_icon ("gtk-missing-image", gtk.ICON_SIZE_MENU, None) size = max (missing.get_width(), missing.get_height()) self.screenIcon = self.iconFromShared ("screen.png", size, missing) self.driverIcon = self.iconFromShared ("card.png", size, missing) self.screenDriverIcon = self.iconFromShared ("screencard.png", size, missing) # implementation of the GenericTreeModel interface def on_get_flags (self): return gtk.TREE_MODEL_ITERS_PERSIST def on_get_n_columns (self): return 2 def on_get_column_type (self, col): if col == 0: return gobject.TYPE_OBJECT else: return gobject.TYPE_STRING def on_get_path (self, node): if node.__class__ == dri.DRIConfig: return (self.configList.index(node),) elif node.__class__ == dri.DeviceConfig: config = node.config return (self.configList.index(config), config.devices.index(node)) elif node.__class__ == dri.AppConfig: device = node.device config = device.config return (self.configList.index(config), config.devices.index(device), device.apps.index(node)) else: assert 0 def on_get_iter (self, path): if len(path) > 3: return None configIndex = path[0] # config if configIndex < len(self.configList): config = self.configList[configIndex] else: return None if len(path) == 1: return config deviceIndex = path[1] # device if deviceIndex < len(config.devices): device = config.devices[deviceIndex] else: return None if len(path) == 2: return device appIndex = path[2] # application if appIndex < len(device.apps): app = device.apps[appIndex] else: return None return app def on_get_value (self, node, col): if node.__class__ == dri.DRIConfig: if col == 0: return self.configIcon else: return "" + escapeMarkup(str(node.fileName)) + "" elif node.__class__ == dri.DeviceConfig: if node.screen and node.driver: name = _("%s on screen %s") % (node.driver.capitalize(), node.screen) icon = self.screenDriverIcon elif node.screen: name = _("Screen %s") % node.screen icon = self.screenIcon elif node.driver: name = "%s" % node.driver.capitalize() icon = self.driverIcon else: name = _("Unspecified device") icon = self.unspecIcon if col == 0: return icon else: return escapeMarkup(str(name)) elif node.__class__ == dri.AppConfig: if col == 0: return self.appIcon elif not node.isValid: return '' + \ escapeMarkup(str(node.name)) + '' else: return escapeMarkup(str(node.name)) def on_iter_next (self, node): if node.__class__ == dri.DRIConfig: list = self.configList elif node.__class__ == dri.DeviceConfig: list = node.config.devices elif node.__class__ == dri.AppConfig: list = node.device.apps else: return None index = list.index(node) if index+1 < len(list): return list[index+1] else: return None def on_iter_children (self, node): return self.on_iter_nth_child (node, 0) def on_iter_has_child (self, node): return (self.on_iter_n_children (node) > 0) def on_iter_n_children (self, node): if not node: return len(self.configList) elif node.__class__ == dri.DRIConfig: return len(node.devices) elif node.__class__ == dri.DeviceConfig: return len(node.apps) else: return 0 def on_iter_nth_child (self, node, index): if not node: list = self.configList elif node.__class__ == dri.DRIConfig: list = node.devices elif node.__class__ == dri.DeviceConfig: list = node.apps else: return None if len(list) > index: return list[index] else: return None def on_iter_parent (self, node): if node.__class__ == dri.DeviceConfig: return node.config elif node.__class__ == dri.AppConfig: return node.device else: return None # helpers for converting between nodes, paths and TreeIterators def getPathFromNode (self, node): return self.on_get_path (node) def getNodeFromPath (self, path): return self.on_get_iter (path) def getIterFromNode (self, node): return self.get_iter (self.getPathFromNode(node)) # config list def getConfigList (self): return self.configList # callback for registring modifications def nodeModified (self, node, b=True): if node.__class__ == dri.DRIConfig: config = node elif node.__class__ == dri.DeviceConfig: config = node.config elif node.__class__ == dri.AppConfig: config = node.device.config if config.isModified != b: config.isModified = b curNode = commonui.mainWindow.configTree.getSelection (allowNone=True) if not curNode: commonui.mainWindow.deactivateButtons() elif curNode.__class__ == dri.DRIConfig: commonui.mainWindow.activateConfigButtons(curNode) elif curNode.__class__ == dri.DeviceConfig: commonui.mainWindow.activateDeviceButtons(curNode) elif curNode.__class__ == dri.AppConfig: commonui.mainWindow.activateAppButtons(curNode) # higher level model manipulations def addNode (self, node, sibling = None): """ Add a new node and inform the TreeView. """ self.initNode (node) if node.__class__ == dri.DRIConfig: list = self.configList elif node.__class__ == dri.DeviceConfig: list = node.config.devices elif node.__class__ == dri.AppConfig: list = node.device.apps if sibling != None: index = list.index (sibling) list.insert (index, node) else: list.append (node) self.registerNode (node) def initNode (self, node): node.modified = self.nodeModified if node.__class__ == dri.DRIConfig: if not hasattr(node,"isModified"): node.isModified = False for device in node.devices: self.initNode (device) elif node.__class__ == dri.DeviceConfig: for app in node.apps: self.initNode (app) elif node.__class__ == dri.AppConfig: self.validateAppNode (node) def registerNode (self, node): path = self.on_get_path (node) iter = self.get_iter (path) self.row_inserted (path, iter) if node.__class__ == dri.DRIConfig: for device in node.devices: self.registerNode (device) elif node.__class__ == dri.DeviceConfig: for app in node.apps: self.registerNode (app) def removeNode (self, node): if node.__class__ == dri.DRIConfig: list = self.configList while len(node.devices) > 0: self.removeNode (node.devices[0]) elif node.__class__ == dri.DeviceConfig: list = node.config.devices while len(node.apps) > 0: self.removeNode (node.apps[0]) elif node.__class__ == dri.AppConfig: list = node.device.apps path = self.on_get_path (node) list.remove (node) self.row_deleted (path) # find the first writable application def findFirstWritableApp (self): foundApp = None for config in self.configList: if not config.writable: continue for device in config.devices: if len(device.apps) > 0: foundApp = device.apps[0] break return foundApp # validate an application node def validateAppNode (self, app): try: driver = app.device.getDriver(commonui.dpy) except dri.XMLError: driver = None if driver and not driver.validate (app.options): app.isValid = False else: app.isValid = True class ConfigTreeView (gtk.TreeView): def __init__ (self, configList): self.model = ConfigTreeModel (configList) gtk.TreeView.__init__ (self, self.model) self.model.renderIcons (self) self.connect ("style-set", lambda widget, prevStyle: self.model.renderIcons (widget)) self.set_size_request (200, -1) self.set_headers_visible (False) self.expand_all() self.get_selection().set_mode (gtk.SELECTION_BROWSE) self.get_selection().connect ("changed", self.selectionChangedSignal) column = gtk.TreeViewColumn() column.set_spacing (2) renderPixbuf = gtk.CellRendererPixbuf() column.pack_start (renderPixbuf, expand=False) column.add_attribute (renderPixbuf, "pixbuf", 0) renderText = gtk.CellRendererText() column.pack_start (renderText, expand=True) column.add_attribute (renderText, "markup", 1) self.append_column (column) def getConfigList (self): return self.model.getConfigList() # selection handling def getSelection (self, allowNone=False): model, iter = self.get_selection().get_selected() assert iter or allowNone if iter: path = self.model.get_path (iter) return self.model.getNodeFromPath (path) else: return None def selectFirstWritableApp (self): app = self.model.findFirstWritableApp() if app: path = self.model.getPathFromNode (app) self.get_selection().select_path (path) self.scroll_to_cell (path=path, use_align=False) def selectionChangedSignal (self, data): node = self.getSelection (allowNone=True) if not node: driver = None app = None elif node.__class__ == dri.AppConfig: app = node try: driver = app.device.getDriver (commonui.dpy) except dri.XMLError, problem: driver = None dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Parsing the driver's configuration information: %s") % problem) dialog.connect ("response", lambda d,r: d.destroy()) dialog.show() else: driver = None app = None commonui.mainWindow.commitDriverPanel() commonui.mainWindow.switchDriverPanel (driver, app) if not node: commonui.mainWindow.deactivateButtons() elif node.__class__ == dri.DRIConfig: commonui.mainWindow.activateConfigButtons(node) elif node.__class__ == dri.DeviceConfig: commonui.mainWindow.activateDeviceButtons(node) elif node.__class__ == dri.AppConfig: commonui.mainWindow.activateAppButtons(node) # highlight invalid nodes def validateAppNode (self, app): self.model.validateAppNode (app) # Emit a row_changed event so that the view gets updated properly. path = self.model.getPathFromNode (app) iter = self.model.get_iter (path) self.model.row_changed (path, iter) # UI actions on the config tree def moveUp (self, widget): self.moveItem (-1) def moveDown (self, widget): self.moveItem (1) def removeItem (self, widget): node = self.getSelection() if node.__class__ == dri.AppConfig: parent = node.device dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Really delete application \"%s\"?") % node.name) elif node.__class__ == dri.DeviceConfig: parent = node.config dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Really delete device and all applications in it?")) else: # The remove button should be unsensitive. assert False response = dialog.run() dialog.destroy () if response != gtk.RESPONSE_YES: return if node.__class__ == dri.AppConfig: commonui.mainWindow.removeApp (node) elif node.__class__ == dri.DeviceConfig: for app in node.apps: commonui.mainWindow.removeApp (app) self.model.removeNode (node) parent.modified(parent) path = self.model.getPathFromNode (parent) self.get_selection().select_path (path) self.scroll_to_cell (path=path, use_align=False) def properties (self, widget): node = self.getSelection() if node.__class__ == dri.AppConfig: dialog = NameDialog (_("Rename Application"), self.renameCallback, node.name, node) else: dialog = DeviceDialog (_("Device Properties"), self.propertiesCallback, node) def newItem (self, widget): node = self.getSelection() if node.__class__ == dri.AppConfig: node = node.device if node.__class__ == dri.DeviceConfig: dialog = NameDialog (_("New Application"), self.newAppCallback, "", node) elif node.__class__ == dri.DRIConfig: dialog = DeviceDialog (_("New Device"), self.newDeviceCallback, node) def saveConfig (self, widget): node = self.getSelection() if node.__class__ == dri.AppConfig: config = node.device.config elif node.__class__ == dri.DeviceConfig: config = node.config elif node.__class__ == dri.DRIConfig: config = node valid = True for device in config.devices: try: driver = device.getDriver (commonui.dpy) except dri.XMLError: driver = None if driver == None: continue for app in device.apps: valid = valid and driver.validate (app.options) if not valid: dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("The configuration contains invalid entries. Save anyway?")) response = dialog.run() dialog.destroy() if response != gtk.RESPONSE_YES: return try: file = open (config.fileName, "w") except IOError: dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't open \"%s\" for writing.") % config.fileName) dialog.run() dialog.destroy() return commonui.mainWindow.commitDriverPanel() file.write (str(config)) file.close() config.modified(config, False) def reloadConfig (self, widget): node = self.getSelection() if node.__class__ == dri.AppConfig: config = node.device.config elif node.__class__ == dri.DeviceConfig: config = node.config else: config = node dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Really reload \"%s\" from disk?") % config.fileName) response = dialog.run() dialog.destroy() if response != gtk.RESPONSE_YES: return try: cfile = open (config.fileName, "r") except IOError: dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Couldn't open \"%s\" for reading. " "The file was not reloaded.") % config.fileName) dialog.run() dialog.destroy() return # Try to parse the configuration file. try: newConfig = dri.DRIConfig (cfile) except dri.XMLError, problem: dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Configuration file \"%s\" contains errors:\n" "%s\n" "The file was not reloaded.") % (config.fileName, str(problem))) dialog.run() dialog.destroy() cfile.close() return cfile.close() # Check if the file is writable in the end. newConfig.writable = commonui.fileIsWritable (config.fileName) # find the position of config configList = self.getConfigList() index = configList.index (config) if index == len(configList)-1: sibling = None else: sibling = configList[index+1] if node.__class__ == dri.AppConfig: commonui.mainWindow.removeApp (node) elif node.__class__ == dri.DeviceConfig: for app in node.apps: commonui.mainWindow.removeApp (app) self.model.removeNode (config) self.model.addNode (newConfig, sibling) path = self.model.getPathFromNode (newConfig) self.expand_row (path, True) self.get_selection().select_path (path) self.scroll_to_cell (path=path, use_align=False) # helper function for moving tree nodes around def moveItem (self, inc): node = self.getSelection() if node.__class__ == dri.AppConfig: parent = node.device siblings = parent.apps elif node.__class__ == dri.DeviceConfig: parent = node.config siblings = parent.devices else: assert False index = siblings.index (node) newIndex = index+inc if newIndex < 0 or newIndex >= len(siblings): return siblings.remove (node) siblings.insert (newIndex, node) newOrder = range(len(siblings)) newOrder[index] = newIndex newOrder[newIndex] = index path = self.model.getPathFromNode (parent) iter = self.model.get_iter (path) self.model.rows_reordered (path, iter, newOrder) parent.modified(parent) path = self.model.getPathFromNode (node) self.scroll_to_cell (path=path, use_align=False) # callbacks from dialogs def renameCallback (self, name, app): app.name = name path = self.model.getPathFromNode (app) iter = self.model.get_iter (path) self.model.row_changed (path, iter) commonui.mainWindow.renameApp (app) app.modified(app) def propertiesCallback (self, screen, driver, device): device.screen = screen device.driver = driver path = self.model.getPathFromNode (device) iter = self.model.get_iter (path) self.model.row_changed (path, iter) device.modified(device) def newAppCallback (self, name, device): app = dri.AppConfig (device, name) self.model.addNode (app) if len(device.apps) == 1: self.expand_row (self.model.getPathFromNode(device), True) device.modified(device) path = self.model.getPathFromNode (app) self.get_selection().select_path (path) self.scroll_to_cell (path=path, use_align=False) def newDeviceCallback (self, screen, driver, config): device = dri.DeviceConfig (config, screen, driver) self.model.addNode (device) config.modified(config) path = self.model.getPathFromNode (device) self.get_selection().select_path (path) self.scroll_to_cell (path=path, use_align=False) class MainWindow (gtk.Window): """ The main window consiting of ConfigTree, DriverPanel and toolbar. """ def __init__ (self, configList): gtk.Window.__init__ (self) self.set_title ("DRIconf") self.connect ("destroy", lambda dummy: gtk.main_quit()) self.connect ("delete_event", self.exitHandler) self.vbox = gtk.VBox() self.paned = gtk.HPaned() self.configTree = ConfigTreeView (configList) self.configTree.show() scrolledWindow = gtk.ScrolledWindow () scrolledWindow.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolledWindow.add (self.configTree) scrolledWindow.show() self.paned.add1(scrolledWindow) self.paned.show() self.toolbar = gtk.Toolbar () iconSize = self.toolbar.get_icon_size() self.saveButton = self.toolbar.insert_stock ( "gtk-save", _("Save selected configuration file"), "priv", self.configTree.saveConfig, None, -1) self.reloadButton = self.toolbar.insert_stock ( "gtk-revert-to-saved", _("Reload selected configuration file"), "priv", self.configTree.reloadConfig, None, -1) self.toolbar.append_space() self.newButton = self.toolbar.insert_stock ( "gtk-new", _("Create a new device or application"), "priv", self.configTree.newItem, None, -1) self.removeButton = self.toolbar.insert_stock ( "gtk-delete", _("Remove selected device or application"), "priv", self.configTree.removeItem, None, -1) self.upButton = self.toolbar.insert_stock ( "gtk-go-up", _("Move selected item up"), "priv", self.configTree.moveUp, None, -1) self.downButton = self.toolbar.insert_stock ( "gtk-go-down", _("Move selected item down"), "priv", self.configTree.moveDown, None, -1) self.propertiesButton = self.toolbar.insert_stock ( "gtk-properties", _("Properties of selected device or application"), "priv", self.configTree.properties, None, -1) self.toolbar.append_space() # The gtk-about stock item is available with gtk >= 2.6. # It's definitely not available with gtk 2.2. Not sure about 2.4. if gtk.gtk_version[0] == 2 and gtk.gtk_version[1] < 6: aboutStock = "gtk-dialog-info" else: aboutStock = "gtk-about" self.aboutButton = self.toolbar.insert_stock ( aboutStock, _("About DRIconf"), "priv", self.aboutHandler, None, -1) self.toolbar.append_space() self.exitButton = self.toolbar.insert_stock ( "gtk-quit", _("Exit DRIconf"), "priv", self.exitHandler, None, -1) if len(configList) != 0: self.activateConfigButtons (configList[0]) self.toolbar.show() self.vbox.pack_start (self.toolbar, False, True, 0) self.vbox.pack_start (self.paned, True, True, 0) self.vbox.show() self.add (self.vbox) self.curDriverPanel = None self.logo = gtk.EventBox () logoPath = findInShared("drilogo.jpg") if logoPath: image = gtk.Image() image.set_from_file (logoPath) self.logo.add (image) self.logo.modify_bg (gtk.STATE_NORMAL, gtk.gdk.Color (65535, 65535, 65535)) self.logo.show_all() self.paned.add2 (self.logo) def initSelection (self): self.configTree.selectFirstWritableApp() def commitDriverPanel (self): if self.curDriverPanel != None: self.curDriverPanel.commit() self.configTree.validateAppNode (self.curDriverPanel.app) def validateDriverPanel (self): if self.curDriverPanel != None: self.curDriverPanel.validate() self.curDriverPanel.commit() self.configTree.validateAppNode (self.curDriverPanel.app) def switchDriverPanel (self, driver=None, app=None): if self.curDriverPanel != None: if self.curDriverPanel.driver == driver and \ self.curDriverPanel.app == app: return self.paned.remove (self.curDriverPanel) elif app != None: self.paned.remove (self.logo) if app != None: self.curDriverPanel = DriverPanel (driver, app) self.curDriverPanel.show () self.paned.add2 (self.curDriverPanel) elif self.curDriverPanel != None: self.curDriverPanel = None self.logo.show() self.paned.add2 (self.logo) def removeApp (self, app): if self.curDriverPanel != None and self.curDriverPanel.app == app: self.paned.remove (self.curDriverPanel) self.curDriverPanel = None self.logo.show() self.paned.add2 (self.logo) def renameApp (self, app): if self.curDriverPanel != None and self.curDriverPanel.app == app: self.curDriverPanel.renameApp() def deactivateButtons (self): self.saveButton .set_sensitive (False) self.reloadButton .set_sensitive (False) self.newButton .set_sensitive (False) self.removeButton .set_sensitive (False) self.upButton .set_sensitive (False) self.downButton .set_sensitive (False) self.propertiesButton.set_sensitive (False) def activateConfigButtons (self, config): writable = config.writable modified = config.isModified self.saveButton .set_sensitive (writable and modified) self.reloadButton .set_sensitive (True) self.newButton .set_sensitive (writable) self.removeButton .set_sensitive (False) self.upButton .set_sensitive (False) self.downButton .set_sensitive (False) self.propertiesButton.set_sensitive (False) def activateDeviceButtons (self, device): writable = device.config.writable modified = device.config.isModified self.saveButton .set_sensitive (writable and modified) self.reloadButton .set_sensitive (True) self.newButton .set_sensitive (writable) self.removeButton .set_sensitive (writable) self.upButton .set_sensitive (writable) self.downButton .set_sensitive (writable) self.propertiesButton.set_sensitive (writable) def activateAppButtons (self, app): # Button sensitivity is identical for apps and devices. self.activateDeviceButtons (app.device) def aboutHandler (self, widget): dialog = commonui.AboutDialog() dialog.connect("response", lambda dialog, response: dialog.destroy()) dialog.show() def exitHandler (self, widget, event=None): modified = False for config in self.configTree.getConfigList(): if config.isModified: modified = True break if modified: dialog = gtk.MessageDialog ( commonui.mainWindow, gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("There are unsaved modifications. Exit anyway?")) dialog.connect ("response", self.doExit) dialog.show() return True elif event == None: # called from toolbar button: main_quit! gtk.main_quit() else: # called from delete_event: indicate it's ok to destroy return False def doExit (self, dialog, response): dialog.destroy() if response == gtk.RESPONSE_YES: gtk.main_quit() def start (configList): # initSelection must be called before and after mainWindow.show(). # Before makes sure that the initial window size is correct. # After is needed since the selection seems to get lost in # mainWindow.show(). mainWindow = MainWindow(configList) commonui.mainWindow = mainWindow mainWindow.set_default_size (750, 375) mainWindow.initSelection() mainWindow.show () mainWindow.initSelection()