def refresh(): """Empties the data of all loaded addons from the database and reloads everything. Sends out a 'addon-database-reloaded' notification.""" init() loadAll() events.send(events.Notify('addon-database-reloaded'))
def onWindowClose(self, ev): """Handle the window close event that is sent when the user tries to close the main window. Broadcasts a 'quit' events.Notify object so that all the listeners will know that the application is closing down. @param ev A wxCloseEvent object. """ # Send a 'quit' command so that everyone has a chance to save # their current status. events.send(events.Notify('quit')) # Save window size to UserHome/conf/window.conf. winSize = self.GetSizeTuple() fileName = os.path.join(paths.getUserPath(paths.CONF), 'window.conf') try: f = file(fileName, 'w') f.write('# This file is generated automatically.\n') f.write('appearance main (\n') f.write(' width = %i\n height = %i\n' % winSize) f.write(' x = %i\n y = %i\n' % self.GetPositionTuple()) if self.splitPos != None: f.write(' split-position = %i\n' % self.splitPos) f.write(' profile-split-position = %i\n' % self.profSplitPos) f.write(')\n') except: # Window size not saved. pass # Destroy the main frame. self.Destroy() return True
def prepareWindows(): """Layout windows and make everything ready for action.""" events.send(events.Notify('preparing-windows')) app.mainFrame.updateMenus() app.mainFrame.updateLayout() app.mainFrame.Show() app.mainFrame.Freeze()
def startMainLoop(): """Start the wxWidgets main loop.""" # Before we give wxPython all control, update the layout of UI # areas. app.showMainWindow() # Display any issues that came up during init. logger.show() # Notify everyone that the main loop is about to kick in. events.send(events.Notify('init-done')) app.MainLoop()
def onRightClick(self, ev): # Let wxWidgets handle the event, too. widget = self.getWxWidget() if not widget.IsEnabled(): return # Going up or down? if ev.ButtonDown(): # Mouse right down doesn't do anything. return # Request an update to the popup menu. if self.menuId: events.send(events.Notify(self.menuId + '-update-request')) # Display the popup menu. if self.menuItems: menu = wx.Menu() self.commandMap = {} idCounter = 10000 # Create the menu items. for item in self.menuItems: if type(item) == tuple: itemId = item[0] itemCommand = item[1] else: itemId = item itemCommand = item if itemId == '-': # This is just a separator. menu.AppendSeparator() continue # Generate a new ID for the item. wxId = idCounter idCounter += 1 self.commandMap[wxId] = itemCommand menu.Append( wxId, widgets.uniConv(language.translate('menu-' + itemId))) wx.EVT_MENU(widget, wxId, self.onPopupCommand) # Display the menu. The callback gets called during this. widget.PopupMenu(menu, ev.GetPosition()) menu.Destroy()
def install(sourceName): """Installs the specified addon into the user's addon database. @param fileName Full path of the addon to install. @throw AddonFormatError If the addon is not one of the recognized formats, this exception is raised. @return Identifier of the installed addon. @throws Exception The installation failed. """ isManifest = paths.hasExtension('manifest', sourceName) if isManifest: # Manifests are copied to the manifest directory. destPath = paths.getUserPath(paths.MANIFESTS) else: destPath = paths.getUserPath(paths.ADDONS) destName = os.path.join(destPath, os.path.basename(sourceName)) try: # Directories must be copied as a tree. if os.path.isdir(sourceName): shutil.copytree(sourceName, destName) else: shutil.copy2(sourceName, destName) # Load the new addon. if not isManifest: identifier = load(destName) else: identifier = loadManifest(destName) # Now that the addon has been installed, save the updated cache. writeMetaCache() # Send a notification of the new addon. notify = events.Notify('addon-installed') notify.addon = identifier events.send(notify) except Exception, x: # Unsuccessful. raise x
def uninstall(identifier, doNotify=False): """Uninstall an addon. Uninstalling an addon causes a full reload of the entire addon database. @param identifier Identifier of the addon to uninstall. @param doNotify True, if a notification should be sent. """ addon = get(identifier) path = addon.getSource() destPath = os.path.join(paths.getUserPath(paths.UNINSTALLED), os.path.basename(path)) if os.path.exists(destPath): # Simply remove any existing addons with the same name. # TODO: Is this wise? Rename old. if os.path.isdir(destPath): shutil.rmtree(destPath) else: os.remove(destPath) shutil.move(path, destPath) # Is there a manifest to uninstall? manifest = addon.getId() + '.manifest' manifestFile = os.path.join(paths.getUserPath(paths.MANIFESTS), manifest) if os.path.exists(manifestFile): # Move it to the uninstalled folder. destPath = os.path.join(paths.getUserPath(paths.UNINSTALLED), manifest) if os.path.exists(destPath): os.remove(destPath) shutil.move(manifestFile, destPath) # Mark as uninstalled. addon.uninstall() # Detach from all profiles. for p in sb.profdb.getProfiles(): p.dontUseAddon(identifier) # Send a notification. if doNotify: notify = events.Notify('addon-uninstalled') notify.addon = identifier events.send(notify)
def onLeftUp(self, ev): """Handle the wxWidgets event when the user clicks on the list widget. @param ev A wxWidgets event object. """ w = self.getWxWidget() item, flags = w.HitTest(ev.GetPosition()) if item < 0: ev.Skip() return if flags & wx.LIST_HITTEST_ONITEMICON: e = events.Notify(self.widgetId + '-icon-click') e.item = self.items[w.GetItemData(item)] events.send(e) ev.Skip()
def runWizard(): """Run the wizard dialog.""" # Make sure the help panel isn't updated during the wizard. #events.send(events.Command('freeze')) suggested = { 'doom1': 'DOOM.WAD', 'doom1-share': 'DOOM1.WAD', 'doom1-ultimate': 'DOOM.WAD', 'doom2': 'DOOM2.WAD', 'doom2-tnt': 'TNT.WAD', 'doom2-plut': 'PLUTONIA.WAD', 'heretic-ext': 'HERETIC.WAD', 'heretic-share': 'HERETIC.WAD', 'heretic': 'HERETIC.WAD', 'hexen': 'HEXEN.WAD', 'hexen-demo': 'HEXEN.WAD', 'hexen-dk': 'HEXEN.WAD', 'hacx': 'HACX.WAD', 'chex': 'CHEX.WAD' } events.mute() # Make the Defaults profile active. pr.setActive(pr.getDefaults()) wiz = WizardDialog(language.translate('setup-wizard'), paths.findBitmap('wizard')) # Language selection page. langPage = WizardPage(wiz, 'wizard-language') area = langPage.getArea() area.createText('wizard-language-explanation', maxLineLength=65).resizeToBestSize() sar, languageCheckBox = area.createSetting(st.getSetting('language')) languageCheckBox.getFromProfile(pr.getActive()) # Game selection. gamePage = ProfileWizardPage(wiz, 'wizard-games', None) gamePage.follows(langPage) area = gamePage.getArea() area.createText('wizard-select-games', maxLineLength=65).resizeToBestSize() area.setBorderDirs(ui.BORDER_NOT_BOTTOM) games = area.createList('', style=sb.widget.list.List.STYLE_CHECKBOX) area.setBorderDirs(ui.BORDER_NOT_TOP) games.setMinSize(50, 180) gamePage.setGameList(games) def allGames(): # Check the entire list. for item in games.getItems(): games.checkItem(item, True) def clearGames(): # Uncheck the entire list. for item in games.getItems(): games.checkItem(item, False) controls = area.createArea(alignment=ui.ALIGN_HORIZONTAL, border=2) controls.setWeight(0) button = controls.createButton('wizard-games-all', wb.Button.STYLE_MINI) button.addReaction(allGames) button.resizeToBestSize() button = controls.createButton('wizard-games-clear', wb.Button.STYLE_MINI) button.addReaction(clearGames) button.resizeToBestSize() # The pages will be linked together. previousPage = gamePage deathKingsWad = None # We'll do this dynamically. checkedProfiles = ['doom1', 'doom2', 'heretic', 'hexen'] # Only display the system profiles in the wizard (not any user # profiles). profiles = pr.getProfiles(lambda p: p.isSystemProfile()) for p in profiles: if p is not pr.getDefaults(): games.addItem(p.getId(), p.getId() in checkedProfiles) # Create a page for the profile. page = ProfileWizardPage(wiz, p.getId(), games) # Link the pages together. page.follows(previousPage) previousPage = page area = page.getArea() area.createText('wizard-locate-iwad', maxLineLength=65).resizeToBestSize() # The suggestion. if suggested.has_key(p.getId()): sugArea = area.createArea(alignment=ui.ALIGN_HORIZONTAL, border=2) sugArea.setExpanding(False) sugArea.setWeight(1) sugArea.createText('wizard-suggested-iwad', ':', align=wt.Text.RIGHT) sugArea.setWeight(2) sug = sugArea.createText('') sug.setText(suggested[p.getId()]) sar, page.iwadText = area.createSetting(st.getSetting('iwad')) if p.getId() == 'hexen-dk': area.setBorder(12, ui.BORDER_ALL) area.createLine() area.setBorder(6, ui.BORDER_ALL) area.createText('deathkings-selection-title').setHeadingStyle() # Death Kings is an extension to Hexen. It uses the # same Hexen IWAD, but also an addon IWAD. area.createText('wizard-locate-iwad-deathkings', maxLineLength=65).resizeToBestSize() entry = area.createArea(alignment=ui.ALIGN_HORIZONTAL, border=4) entry.setExpanding(False) entry.setWeight(1) entry.setBorderDirs(ui.BORDER_NOT_LEFT) deathKingsWad = entry.createTextField('') entry.setWeight(0) entry.setBorderDirs(ui.BORDER_TOP_BOTTOM) browseButton = entry.createButton('browse-button', wb.Button.STYLE_MINI) def browseDeathKings(): # Open a file browser. selection = sb.util.dialog.chooseFile('deathkings-selection-title', '', True, [('file-type-wad', 'wad')]) if len(selection) > 0: deathKingsWad.setText(selection) browseButton.addReaction(browseDeathKings) # Addon paths. pathsPage = ProfileWizardPage(wiz, 'wizard-addon-paths', games) pathsPage.follows(previousPage) area = pathsPage.getArea() area.createText('wizard-addon-paths-explanation', maxLineLength=65).resizeToBestSize() area.setBorderDirs(ui.BORDER_NOT_BOTTOM) pathList = area.createList('addon-paths-list') pathList.setMinSize(100, 120) # Insert the current custom paths into the list. for p in paths.getAddonPaths(): pathList.addItem(p) def addAddonPath(): selection = sb.util.dialog.chooseFolder('addon-paths-add-prompt', '') if selection: pathList.addItem(selection) pathList.selectItem(selection) def removeAddonPath(): selection = pathList.getSelectedItem() if selection: pathList.removeItem(selection) area.setWeight(0) area.setBorderDirs(ui.BORDER_NOT_TOP) commands = area.createArea(alignment=ui.ALIGN_HORIZONTAL, border=2) commands.setWeight(0) # Button for adding new paths. button = commands.createButton('new-addon-path', wb.Button.STYLE_MINI) button.addReaction(addAddonPath) # Button for removing a path. button = commands.createButton('delete-addon-path', wb.Button.STYLE_MINI) button.addReaction(removeAddonPath) # Launch options. quitPage = WizardPage(wiz, 'wizard-launching') quitPage.follows(pathsPage) area = quitPage.getArea() area.createText('wizard-launching-explanation').resizeToBestSize() sar, quitCheckBox = area.createSetting(st.getSetting('quit-on-launch')) quitCheckBox.getFromProfile(pr.getActive()) # List of unusable profiles, due to a missing IWAD. unusableProfiles = [] # When the page changes in the wizard, change the active profile. def changeActiveProfile(page): prof = pr.get(page.getId()) if prof: pr.setActive(prof) else: pr.setActive(pr.getDefaults()) # Update page with values from the current profile. page.update() wiz.setPageReaction(changeActiveProfile) if wiz.run(langPage) == 'ok': events.unmute() # Show the profiles that now have an IWAD. for prof in profiles: if prof.getValue('iwad', False) != None: pr.show(prof.getId()) if prof.getId() in unusableProfiles: unusableProfiles.remove(prof.getId()) else: pr.hide(prof.getId()) if prof.getId() not in unusableProfiles and \ prof.getId() in games.getSelectedItems(): unusableProfiles.append(prof.getId()) # Install the Death Kings WAD? if deathKingsWad: try: ident = ao.install(deathKingsWad.getText()) # Attach the WAD as an addon. kingsProfile = pr.get('hexen-dk') kingsProfile.useAddon(ident) except: # TODO: Handle error. pass # Update custom addon paths. currentAddonPaths = paths.getAddonPaths() wasModified = False for path in pathList.getItems(): if path not in currentAddonPaths: paths.addAddonPath(path) wasModified = True for path in currentAddonPaths: if path not in pathList.getItems(): paths.removeAddonPath(path) wasModified = True if wasModified: # Load addons from new paths. ao.refresh() events.send(events.Notify('addon-paths-changed')) # The wizard will only be shown once automatically. pr.getDefaults().setValue(HAS_BEEN_RUN, 'yes', False) else: # Wizard was canceled. events.unmute() pr.refresh() # This'll destroy all the pages of the wizard as well. wiz.destroy() # Enable help panel updates again. #events.send(events.Command('unfreeze')) # Tell the user about unusable profiles. if len(unusableProfiles) > 0: dialog, area = sb.util.dialog.createButtonDialog( 'wizard-unlaunchable-profiles', ['ok'], 'ok', resizable=False) # Compose a list of the unlaunchable profiles. profList = '' unusableProfiles.sort(lambda a, b: cmp(language.translate(a), language.translate(b))) for p in unusableProfiles: profList += "\n" + language.translate(p) msg = area.createText() msg.setText(language.translate('wizard-unlaunchable-profiles-listed') + "\n" + profList) msg.resizeToBestSize() dialog.run()
def onColumnClick(self, ev): events.send( events.Notify(self.columns[ev.GetColumn()] + '-column-click')) ev.Skip()
def chooseAddons(dialogId, title, actionButton): """Opens an addon selection dialog. @param title Title of the dialog. @param actionButton The button that will perform the affirmative action of the dialog. """ dialog, area = sb.util.dialog.createButtonDialog(dialogId, ['cancel', actionButton], actionButton) dialog.addEndCommand('addon-dialog-add-to-custom-folders') # The action button is disabled until a valid selection has been # made. dialog.disableWidget(actionButton) area.setWeight(0) folderArea = area.createArea(alignment=ui.ALIGN_HORIZONTAL) folderArea.setExpanding(False) folderArea.setBorder(2, ui.BORDER_NOT_LEFT) folderArea.setWeight(0) folderArea.createText('addon-dialog-folder').resizeToBestSize() folderArea.setBorderDirs(ui.BORDER_ALL) folderArea.setWeight(1) pathField = folderArea.createTextField('') pathField.setText(os.getcwd()) pathField.select() pathField.focus() folderArea.setWeight(0) folderArea.setBorderDirs(ui.BORDER_NOT_RIGHT) browseButton = folderArea.createButton('addon-dialog-folder-browse', style=wg.Button.STYLE_MINI) #folderArea.addSpacer() #folderArea.setBorderDirs(ui.BORDER_NOT_RIGHT) folderCmdArea = area.createArea(alignment=ui.ALIGN_HORIZONTAL) folderCmdArea.setExpanding(False) folderCmdArea.setBorder(6, ui.BORDER_NOT_TOP) folderCmdArea.setWeight(1) folderCmdArea.addSpacer() folderCmdArea.setWeight(0) uninstButton = folderCmdArea.createButton( 'addon-dialog-folder-uninstalled') # Add to Custom Folders button. folderCmdArea.setBorder(6, ui.BORDER_LEFT | ui.BORDER_BOTTOM) addToMyButton = folderCmdArea.createButton( 'addon-dialog-add-to-custom-folders') def goToUninstalled(): pathField.setText(paths.getUserPath(paths.UNINSTALLED)) pathField.select() addToMyButton.disable() uninstButton.addReaction(goToUninstalled) area.createText('addon-dialog-found') area.setWeight(1) foundList = area.createList('', style=sb.widget.list.List.STYLE_COLUMNS) foundList.setMinSize(500, 300) area.setWeight(0) area.createText('addon-dialog-addons-copied', maxLineLength=70).resizeToBestSize() def selectAction(): dialog.enableWidget(actionButton) foundList.addReaction(selectAction) for col, width in [('name', None), ('type', 180)]: foundList.addColumn('addon-dialog-' + col, width) def updateList(): # Update the found addons list. foundList.clear() dialog.disableWidget(actionButton) extensions = ao.getAddonExtensions() + ['manifest'] # This should be done in addons.py. fileNames = os.listdir(pathField.getText()) for name in fileNames: type = '' for ext in extensions: if paths.hasExtension(ext, name): type = ext break if not type: # Unknown files are skipped. continue # Manifests don't appear in the list if the corresponding # addon is in the same directory. if paths.hasExtension('manifest', name): foundSame = False # Identifier of the addon the manifest belongs to. manifestId = paths.getBase(name) # See if the addon is in the list. for other in fileNames: if other == name: continue if manifestId == ao.formIdentifier(other)[0]: foundSame = True break if foundSame: # Don't add it. continue foundList.addItemWithColumns( name, name, language.translate('addon-dialog-type-' + type)) # Update reactions. def pathChanged(): if os.path.exists(pathField.getText()): updateList() if pathField.getText() != paths.getUserPath(paths.UNINSTALLED): addToMyButton.enable() else: addToMyButton.disable() def browseAction(): # Show a directory browser. selection = sb.util.dialog.chooseFolder('addon-dialog-browse-prompt', pathField.getText()) if len(selection): pathField.setText(selection) pathField.select() # The initial contents of the list. updateList() pathField.addReaction(pathChanged) browseButton.addReaction(browseAction) dialog.addEndCommand(actionButton) result = dialog.run() if result == actionButton: addonFiles = map(lambda name: os.path.join(pathField.getText(), name), foundList.getSelectedItems()) # Include any associated manifests. for name in addonFiles: manifest = ao.formIdentifier(name)[0] + '.manifest' manifestFile = os.path.join(pathField.getText(), manifest) if manifest not in addonFiles and os.path.exists(manifestFile): addonFiles.append(manifestFile) #print 'including ' + manifestFile + ' due to ' + name return addonFiles elif result == 'addon-dialog-add-to-custom-folders': paths.addAddonPath(pathField.getText()) events.send(events.Notify('addon-paths-changed')) ao.refresh() return [] # The dialog was canceled. return []
def expire(self): """Send a notification if one has been defined. This method can be overridden by subclasses. """ if self.notifyId: events.send(events.Notify(self.notifyId))