class ConfigFrame(Frame): # pylint: disable=too-many-public-methods """ConfigFrame is used as the primary app context""" BOUND_ACTIONS = 12 PROMPTS = { 'dirty_values': 'You have unsaved changes. ', 'probably_modified': 'File has been modified. ' } config = None dirty_values = [] groups = None menu_bar = None notebook = None def __init__(self, parent, title=""): Frame.__init__(self, parent=parent, id=ID_ANY, size=(800, 640), title=title) self.construct_config() self.construct_gui() self.bind_events() def construct_config(self, config_path=None): """Constucts the Rofi config object and parses its groups""" self.config = Rofi() self.config.build(config_path) self.groups = {} for _, entry in self.config.config.items(): if entry.group in self.groups: self.groups[entry.group].append(entry) else: self.groups[entry.group] = [entry] def construct_tabs(self): """Constructs all available tabs""" for key, config_list in self.groups.items(): page = ConfigPage(self.notebook, config_list) self.notebook.AddPage(page, key) self.clean_edit_state() def construct_notebook(self): """Constructs the main Notebook panel""" panel = Panel(self) self.notebook = Notebook(panel, style=NB_LEFT) self.construct_tabs() sizer = BoxSizer(HORIZONTAL) sizer.Add(self.notebook, 1, EXPAND) panel.SetSizer(sizer) def construct_gui(self): """Constructs ConfigFrame's GUI""" self.menu_bar = ConfigFrameMenuBar() self.SetMenuBar(self.menu_bar) self.status_bar = ConfigFrameStatusBar(self) self.SetStatusBar(self.status_bar) self.construct_notebook() self.toggle_restoration() def bind_events(self): """Binds events on ConfigFrame""" self.Bind(EVT_MENU, self.open, self.menu_bar.open_menu_item) self.Bind(EVT_MENU, self.force_refresh_config, self.menu_bar.refresh_menu_item) self.Bind(EVT_MENU, self.restore, self.menu_bar.restore_menu_item) self.Bind(EVT_MENU, self.save_as, self.menu_bar.save_as_menu_item) self.Bind(EVT_MENU, self.save, self.menu_bar.save_menu_item) self.Bind(EVT_MENU, self.menu_bar.exit, self.menu_bar.exit_menu_item) self.Bind(EVT_MENU, self.modi_launcher, self.menu_bar.launch_menu_item) self.Bind(EVT_MENU, self.menu_bar.toggle_display, self.menu_bar.help_values_menu_item) self.Bind(EVT_MENU, self.menu_bar.toggle_display, self.menu_bar.man_values_menu_item) self.Bind(EVT_CHECKBOX, self.dirty_edit_state) self.Bind(EVT_SPINCTRL, self.dirty_edit_state) self.Bind(EVT_TEXT, self.dirty_edit_state) def modi_launcher(self, event=None): # pylint: disable=unused-argument """Launches a modi selection dialog""" ModiLauncher(self.config.available_modi) def update_config_entry(self, key_name, entry): """Updates the value for a single entry""" widget = FindWindowByName(key_name) if hasattr(widget, 'GetValue'): value = widget.GetValue() elif hasattr(widget, 'GetLabel'): value = widget.GetLabel() else: value = entry.current self.config.config[key_name].current = value def update_config(self): """Updates the entire config object""" for key_name, entry in self.config.config.items(): self.update_config_entry(key_name, entry) def save(self, event=None): # pylint: disable=unused-argument """Saves the config file""" self.update_config() self.config.save(backup=self.menu_bar.backup_on_menu_item.IsChecked()) send('status_update', message='Saved!') self.clean_edit_state() self.toggle_refresh() self.toggle_restoration() def toggle_restoration(self, event=None): # pylint: disable=unused-argument """Enables/disables the restore menu item""" self.menu_bar.restore_menu_item.Enable(self.config.can_restore()) def refresh_config(self, event=None, config_path=None): # pylint: disable=unused-argument """Refreshes the config object and controls""" current_page = self.notebook.GetSelection() self.construct_config(config_path) while self.notebook.GetPageCount() > 0: self.notebook.DeletePage(0) self.construct_tabs() if current_page >= 0 and current_page < self.notebook.GetPageCount(): self.notebook.SetSelection(current_page) self.toggle_refresh() self.toggle_restoration() def restore(self, event=None): # pylint: disable=unused-argument """Restores a previously backed up config""" if self.config.can_restore(): self.config.backup(restore=True) self.refresh_config() def clean_edit_state(self): """Resets the dirty value list""" self.dirty_values = [] def dirty_edit_state(self, event=None): """Updates the dirty value list""" if event is None: return control_value = event.EventObject.GetValue() control_name = event.EventObject.GetName() config_value = self.config.config[control_name].current is_dirty = control_value != config_value if is_dirty: if not control_name in self.dirty_values: self.dirty_values.append(control_name) else: self.dirty_values = [ key for key in self.dirty_values if control_name != key ] self.toggle_refresh() def toggle_refresh(self): """Toggle refresh availability""" self.menu_bar.refresh_menu_item.Enable( len(self.dirty_values) > 0 or self.config.probably_modified()) @staticmethod def ignore_dirty_state(prompt=None): """Checks if dirty state can be abandoned""" with MessageDialog(None, "%sContinue?" % prompt, 'Confirm overwrite', YES_NO | ICON_QUESTION) as dialog: if ID_YES == dialog.ShowModal(): return True return False def force_refresh_config(self, event=None): # pylint: disable=unused-argument """Forces a config refresh""" if self.dirty_values: if self.ignore_dirty_state(self.PROMPTS['dirty_values']): self.refresh_config() elif self.config.probably_modified(): if self.ignore_dirty_state(self.PROMPTS['probably_modified']): self.refresh_config() def file_dialog(self, style=None): """Opens a dialog to find a file""" with FileDialog( None, 'Choose a file', dirname(self.config.active_file), wildcard='Rasi files (*.rasi)|*.rasi|All Files (*.*)|*.*', style=style) as dialog: if ID_OK == dialog.ShowModal(): return dialog.GetPath() return None def pick_save_file(self): """Launches a dialog to pick the save location""" return self.file_dialog(FD_SAVE | FD_OVERWRITE_PROMPT) def save_as(self, event=None): # pylint: disable=unused-argument """Saves the config as an arbitrary file""" new_location = self.pick_save_file() if new_location: self.config.active_file = new_location self.save() def pick_open_file(self): """Launches a dialog to pick the open location""" return self.file_dialog(FD_OPEN | FD_FILE_MUST_EXIST) def open(self, event=None): # pylint: disable=unused-argument """Opens the chosen config for editing""" new_location = self.pick_open_file() if new_location: self.refresh_config(config_path=new_location)
class MainUI: """ The main portion of the User Interface. Used by the main application frame (AppFrame) to host all UML frames, the notebook and the project tree. Handles the the project files, projects, documents and their relationship to the various UI Tree elements and the notebook tabs in the UI All actions called from AppFrame are executed on the current frame """ MAX_NOTEBOOK_PAGE_NAME_LENGTH: int = 12 def __init__(self, parent, mediator): """ Args: parent: An AppFrame mediator: Our one and only mediator """ self.logger: Logger = getLogger(__name__) from org.pyut.ui.AppFrame import AppFrame # Prevent recursion import problem from org.pyut.general.Mediator import Mediator self.__parent: AppFrame = parent self._mediator: Mediator = mediator self._projects: List[PyutProject] = [] self._currentProject: PyutProject = cast(PyutProject, None) self._currentFrame: UmlDiagramsFrame = cast(UmlDiagramsFrame, None) if not self._mediator.isInScriptMode(): self.__splitter: SplitterWindow = cast(SplitterWindow, None) self.__projectTree: TreeCtrl = cast(TreeCtrl, None) self.__projectTreeRoot: TreeItemId = cast(TreeItemId, None) self.__notebook: Notebook = cast(Notebook, None) self.__projectPopupMenu: Menu = cast(Menu, None) self.__documentPopupMenu: Menu = cast(Menu, None) self._initializeUIElements() def registerUmlFrame(self, frame): """ Register the current UML Frame Args: frame: """ self._currentFrame = frame self._currentProject = self.getProjectFromFrame(frame) def showFrame(self, frame): self._frame = frame frame.Show() def getProjects(self): """ Returns: Return all projects """ return self._projects def isProjectLoaded(self, filename) -> bool: """ Args: filename: Returns: `True` if the project is already loaded """ for project in self._projects: if project.getFilename == filename: return True return False def isDefaultFilename(self, filename: str) -> bool: """ Args: filename: Returns: `True` if the filename is the default filename """ return filename == PyutConstants.DefaultFilename def openFile(self, filename, project=None) -> bool: """ Open a file Args: filename: project: Returns: `True` if operation succeeded """ # Exit if the file is already loaded if not self.isDefaultFilename(filename) and self.isProjectLoaded(filename): PyutUtils.displayError(_("The selected file is already loaded !")) return False # Create a new project ? if project is None: project = PyutProject(PyutConstants.DefaultFilename, self.__notebook, self.__projectTree, self.__projectTreeRoot) # print ">>>FileHandling-openFile-3" # Load the project and add it try: if not project.loadFromFilename(filename): PyutUtils.displayError(_("The specified file can't be loaded !")) return False self._projects.append(project) # self._ctrl.registerCurrentProject(project) self._currentProject = project except (ValueError, Exception) as e: PyutUtils.displayError(_(f"An error occurred while loading the project ! {e}")) return False try: if not self._mediator.isInScriptMode(): for document in project.getDocuments(): diagramTitle: str = document.getTitle() shortName: str = self.shortenNotebookPageFileName(diagramTitle) self.__notebook.AddPage(document.getFrame(), shortName) self.__notebookCurrentPage = self.__notebook.GetPageCount()-1 self.__notebook.SetSelection(self.__notebookCurrentPage) if len(project.getDocuments()) > 0: self._currentFrame = project.getDocuments()[0].getFrame() except (ValueError, Exception) as e: PyutUtils.displayError(_(f"An error occurred while adding the project to the notebook {e}")) return False return True def insertFile(self, filename): """ Insert a file in the current project Args: filename: filename of the project to insert """ # Get current project project = self._currentProject # Save number of initial documents nbInitialDocuments = len(project.getDocuments()) # Load data... if not project.insertProject(filename): PyutUtils.displayError(_("The specified file can't be loaded !")) return False # ... if not self._mediator.isInScriptMode(): try: for document in project.getDocuments()[nbInitialDocuments:]: self.__notebook.AddPage(document.getFrame(), document.getFullyQualifiedName()) self.__notebookCurrentPage = self.__notebook.GetPageCount()-1 self.__notebook.SetSelection(self.__notebookCurrentPage) except (ValueError, Exception) as e: PyutUtils.displayError(_(f"An error occurred while adding the project to the notebook {e}")) return False # Select first frame as current frame if len(project.getDocuments()) > nbInitialDocuments: self._frame = project.getDocuments()[nbInitialDocuments].getFrame() def saveFile(self) -> bool: """ save to the current filename Returns: `True` if the save succeeds else `False` """ currentProject = self._currentProject if currentProject is None: PyutUtils.displayError(_("No diagram to save !"), _("Error")) return False if currentProject.getFilename() is None or currentProject.getFilename() == PyutConstants.DefaultFilename: return self.saveFileAs() else: return currentProject.saveXmlPyut() def saveFileAs(self): """ Ask for a filename and save the diagram data Returns: `True` if the save succeeds else `False` """ if self._mediator.isInScriptMode(): PyutUtils.displayError(_("Save File As is not accessible in script mode !")) return # Test if no diagram exists if self._mediator.getDiagram() is None: PyutUtils.displayError(_("No diagram to save !"), _("Error")) return # Ask for filename filenameOK = False # TODO revisit this to figure out how to get rid of Pycharm warning 'dlg referenced before assignment' # Bad thing is dlg can be either a FileDialog or a MessageDialog dlg: DialogType = cast(DialogType, None) while not filenameOK: dlg = FileDialog(self.__parent, defaultDir=self.__parent.getCurrentDir(), wildcard=_("Pyut file (*.put)|*.put"), style=FD_SAVE | FD_OVERWRITE_PROMPT) # Return False if canceled if dlg.ShowModal() != ID_OK: dlg.Destroy() return False # Find if a specified filename is already opened filename = dlg.GetPath() if len([project for project in self._projects if project.getFilename() == filename]) > 0: dlg = MessageDialog(self.__parent, _("Error ! The filename '%s" + "' correspond to a project which is currently opened !" + " Please choose another filename !") % str(filename), _("Save change, filename error"), OK | ICON_ERROR) dlg.ShowModal() dlg.Destroy() return filenameOK = True project = self._currentProject project.setFilename(dlg.GetPath()) project.saveXmlPyut() # Modify notebook text for i in range(self.__notebook.GetPageCount()): frame = self.__notebook.GetPage(i) document = [document for document in project.getDocuments() if document.getFrame() is frame] if len(document) > 0: document = document[0] if frame in project.getFrames(): diagramTitle: str = document.getTitle() shortName: str = self.shortenNotebookPageFileName(diagramTitle) self.__notebook.SetPageText(i, shortName) else: self.logger.info("Not updating notebook in FileHandling") self.__parent.updateCurrentDir(dlg.GetPath()) project.setModified(False) dlg.Destroy() return True def newProject(self): """ Begin a new project """ project = PyutProject(PyutConstants.DefaultFilename, self.__notebook, self.__projectTree, self.__projectTreeRoot) self._projects.append(project) self._currentProject = project self._currentFrame = None def newDocument(self, docType: DiagramType): """ Begin a new document Args: docType: Type of document """ project = self._currentProject if project is None: self.newProject() project = self.getCurrentProject() frame = project.newDocument(docType).getFrame() self._currentFrame = frame self._currentProject = project if not self._mediator.isInScriptMode(): shortName: str = self.shortenNotebookPageFileName(project.getFilename()) self.__notebook.AddPage(frame, shortName) wxYield() self.__notebookCurrentPage = self.__notebook.GetPageCount() - 1 self.logger.info(f'Current notebook page: {self.__notebookCurrentPage}') self.__notebook.SetSelection(self.__notebookCurrentPage) def getCurrentFrame(self): """ Returns: Get the current frame """ return self._currentFrame def getCurrentProject(self) -> PyutProject: """ Get the current working project Returns: the current project or None if not found """ return self._currentProject def getProjectFromFrame(self, frame: UmlDiagramsFrame) -> PyutProject: """ Return the project that owns a given frame Args: frame: the frame to get This project Returns: PyutProject or None if not found """ for project in self._projects: if frame in project.getFrames(): return project return cast(PyutProject, None) def getCurrentDocument(self) -> PyutDocument: """ Get the current document. Returns: the current document or None if not found """ project = self.getCurrentProject() if project is None: return cast(PyutDocument, None) for document in project.getDocuments(): if document.getFrame() is self._currentFrame: return document return cast(PyutDocument, None) def onClose(self) -> bool: """ Close all files Returns: True if everything is ok """ # Display warning if we are in scripting mode if self._mediator.isInScriptMode(): print("WARNING : in script mode, the non-saved projects are closed without warning") # Close projects and ask for unsaved but modified projects if not self._mediator.isInScriptMode(): for project in self._projects: if project.getModified() is True: frames = project.getFrames() if len(frames) > 0: frame = frames[0] frame.SetFocus() wxYield() # if self._ctrl is not None: # self._ctrl.registerUMLFrame(frame) self.showFrame(frame) dlg = MessageDialog(self.__parent, _("Your diagram has not been saved! Would you like to save it ?"), _("Save changes ?"), YES_NO | ICON_QUESTION) if dlg.ShowModal() == ID_YES: # save if self.saveFile() is False: return False dlg.Destroy() # dereference all self.__parent = None self._mediator = None self.__splitter = None self.__projectTree = None self.__notebook.DeleteAllPages() self.__notebook = None self.__splitter = None self._projects = None self._currentProject = None self._currentFrame = None def setModified(self, theNewValue: bool = True): """ Set the Modified flag of the currently opened diagram Args: theNewValue: """ if self._currentProject is not None: self._currentProject.setModified(theNewValue) self._mediator.updateTitle() def closeCurrentProject(self): """ Close the current project Returns: True if everything is ok """ if self._currentProject is None and self._currentFrame is not None: self._currentProject = self.getProjectFromFrame(self._currentFrame) if self._currentProject is None: PyutUtils.displayError(_("No frame to close !"), _("Error...")) return False # Display warning if we are in scripting mode if self._mediator.isInScriptMode(): self.logger.warning("WARNING : in script mode, the non-saved projects are closed without warning") # Close the file if self._currentProject.getModified() is True and not self._mediator.isInScriptMode(): frame = self._currentProject.getFrames()[0] frame.SetFocus() self.showFrame(frame) dlg = MessageDialog(self.__parent, _("Your project has not been saved. " "Would you like to save it ?"), _("Save changes ?"), YES_NO | ICON_QUESTION) if dlg.ShowModal() == ID_YES: if self.saveFile() is False: return False # Remove the frame in the notebook if not self._mediator.isInScriptMode(): # Python 3 update pages = list(range(self.__notebook.GetPageCount())) pages.reverse() for i in pages: pageFrame = self.__notebook.GetPage(i) if pageFrame in self._currentProject.getFrames(): self.__notebook.DeletePage(i) self._currentProject.removeFromTree() self._projects.remove(self._currentProject) self._currentProject = None self._currentFrame = None return True def removeAllReferencesToUmlFrame(self, umlFrame): """ Remove all my references to a given uml frame Args: umlFrame: """ # Current frame ? if self._currentFrame is umlFrame: self._currentFrame = None # Exit if we are in scripting mode if self._mediator.isInScriptMode(): return for i in range(self.__notebook.GetPageCount()): pageFrame = self.__notebook.GetPage(i) if pageFrame is umlFrame: self.__notebook.DeletePage(i) break def getProjectFromOglObjects(self, oglObjects) -> PyutProject: """ Get a project that owns oglObjects Args: oglObjects: Objects to find their parents Returns: PyutProject if found, None else """ for project in self._projects: for frame in project.getFrames(): diagram = frame.getDiagram() shapes = diagram.GetShapes() for obj in oglObjects: if obj in shapes: self.logger.info(f'obj: {obj} is part of project: {project}') return project self.logger.warning(f'The oglObjects: {oglObjects} appear to not belong to any project') return cast(PyutProject, None) def _initializeUIElements(self): """ Instantiate all the UI elements """ self.__splitter = SplitterWindow(self.__parent, ID_ANY) self.__projectTree = TreeCtrl(self.__splitter, ID_ANY, style=TR_HIDE_ROOT + TR_HAS_BUTTONS) self.__projectTreeRoot = self.__projectTree.AddRoot(_("Root")) # self.__projectTree.SetPyData(self.__projectTreeRoot, None) # Expand root, since wx.TR_HIDE_ROOT is not supported under windows # Not supported for hidden tree since wx.Python 2.3.3.1 ? # self.__projectTree.Expand(self.__projectTreeRoot) # diagram container self.__notebook = Notebook(self.__splitter, ID_ANY, style=CLIP_CHILDREN) # Set splitter self.__splitter.SetMinimumPaneSize(20) self.__splitter.SplitVertically(self.__projectTree, self.__notebook, 160) self.__notebookCurrentPage = -1 # Callbacks self.__parent.Bind(EVT_NOTEBOOK_PAGE_CHANGED, self.__onNotebookPageChanged) self.__parent.Bind(EVT_TREE_SEL_CHANGED, self.__onProjectTreeSelChanged) self.__projectTree.Bind(EVT_TREE_ITEM_RIGHT_CLICK, self.__onProjectTreeRightClick) # noinspection PyUnusedLocal def __onNotebookPageChanged(self, event): """ Callback for notebook page changed Args: event: """ self.__notebookCurrentPage = self.__notebook.GetSelection() if self._mediator is not None: # hasii maybe I got this right from the old pre PEP-8 code # self._ctrl.registerUMLFrame(self._getCurrentFrame()) self._currentFrame = self._getCurrentFrameFromNotebook() self.__parent.notifyTitleChanged() # self.__projectTree.SelectItem(getID(self.getCurrentFrame())) # TODO : how can I do getID ??? # Register the current project self._currentProject = self.getProjectFromFrame(self._currentFrame) def __onProjectTreeSelChanged(self, event: TreeEvent): """ Callback for notebook page changed Args: event: """ itm: TreeItemId = event.GetItem() pyutData: TreeDataType = self.__projectTree.GetItemData(itm) self.logger.debug(f'Clicked on: `{pyutData}`') # Use our own base type if isinstance(pyutData, UmlDiagramsFrame): frame: UmlDiagramsFrame = pyutData self._currentFrame = frame self._currentProject = self.getProjectFromFrame(frame) # Select the frame in the notebook for i in range(self.__notebook.GetPageCount()): pageFrame = self.__notebook.GetPage(i) if pageFrame is frame: self.__notebook.SetSelection(i) return elif isinstance(pyutData, PyutProject): self._currentProject = pyutData def _getCurrentFrameFromNotebook(self): """ Get the current frame in the notebook Returns: """ # Return None if we are in scripting mode if self._mediator.isInScriptMode(): return None noPage = self.__notebookCurrentPage if noPage == -1: return None frame = self.__notebook.GetPage(noPage) return frame def __onProjectTreeRightClick(self, treeEvent: TreeEvent): itemId: TreeItemId = treeEvent.GetItem() data = self.__projectTree.GetItemData(item=itemId) self.logger.info(f'Item Data: `{data}`') if isinstance(data, PyutProject): self.__popupProjectMenu() elif isinstance(data, UmlDiagramsFrame): self.__popupProjectDocumentMenu() def __popupProjectMenu(self): self._mediator.resetStatusText() if self.__projectPopupMenu is None: self.logger.info(f'Create the project popup menu') [closeProjectMenuID] = PyutUtils.assignID(1) popupMenu: Menu = Menu('Actions') popupMenu.AppendSeparator() popupMenu.Append(closeProjectMenuID, 'Close Project', 'Remove project from tree', ITEM_NORMAL) popupMenu.Bind(EVT_MENU, self.__onCloseProject, id=closeProjectMenuID) self.__projectPopupMenu = popupMenu self.logger.info(f'currentProject: `{self._currentProject}`') self.__parent.PopupMenu(self.__projectPopupMenu) def __popupProjectDocumentMenu(self): if self.__documentPopupMenu is None: self.logger.info(f'Create the document popup menu') [editDocumentNameMenuID, removeDocumentMenuID] = PyutUtils.assignID(2) popupMenu: Menu = Menu('Actions') popupMenu.AppendSeparator() popupMenu.Append(editDocumentNameMenuID, 'Edit Document Name', 'Change document name', ITEM_NORMAL) popupMenu.Append(removeDocumentMenuID, 'Remove Document', 'Delete it', ITEM_NORMAL) popupMenu.Bind(EVT_MENU, self.__onEditDocumentName, id=editDocumentNameMenuID) popupMenu.Bind(EVT_MENU, self.__onRemoveDocument, id=removeDocumentMenuID) self.__documentPopupMenu = popupMenu self.logger.info(f'Current Document: `{self.getCurrentDocument()}`') self.__parent.PopupMenu(self.__documentPopupMenu) # noinspection PyUnusedLocal def __onCloseProject(self, event: CommandEvent): self.closeCurrentProject() # noinspection PyUnusedLocal def __onEditDocumentName(self, event: CommandEvent): self.logger.info(f'self.__notebookCurrentPage: {self.__notebookCurrentPage} nb Selection: {self.__notebook.GetSelection()}') if self.__notebookCurrentPage == -1: self.__notebookCurrentPage = self.__notebook.GetSelection() # must be default empty project currentDocument: PyutDocument = self.getCurrentDocument() dlgEditDocument: DlgEditDocument = DlgEditDocument(parent=self.getCurrentFrame(), dialogIdentifier=ID_ANY, document=currentDocument) dlgEditDocument.Destroy() self.__notebook.SetPageText(page=self.__notebookCurrentPage, text=currentDocument.title) currentDocument.updateTreeText() # noinspection PyUnusedLocal def __onRemoveDocument(self, event: CommandEvent): """ Invoked from the popup menu in the tree Args: event: """ project: PyutProject = self.getCurrentProject() currentDocument: PyutDocument = self.getCurrentDocument() project.removeDocument(currentDocument) def shortenNotebookPageFileName(self, filename: str) -> str: """ Return a shorter filename to display; For file names longer than `MAX_NOTEBOOK_PAGE_NAME_LENGTH` this method takes the first four characters and the last eight as the shortened file name Args: filename: The file name to display Returns: A better file name """ justFileName: str = osPath.split(filename)[1] if len(justFileName) > MainUI.MAX_NOTEBOOK_PAGE_NAME_LENGTH: firstFour: str = justFileName[:4] lastEight: str = justFileName[-8:] return f'{firstFour}{lastEight}' else: return justFileName
class SettingsFrame(Frame): def __init__(self, parent, title, server): Frame.__init__(self, parent, title=title, size=(500, 400)) global SETTINGS_FRAME SETTINGS_FRAME = self self.server = server self.setup_xmlrpc_server() self.completed = False self.notebook = Notebook(self, style=NB_MULTILINE) file_menu = Menu() self.next_page = file_menu.Append(ID_ANY, '&Next page\tRAWCTRL+TAB', 'Next page') self.prev_page = file_menu.Append(ID_ANY, '&Prev page\tRAWCTRL+SHIFT+TAB', 'Prev page') self.Bind(EVT_MENU, self.OnTab, self.next_page) self.Bind(EVT_MENU, self.OnTab, self.prev_page) exit_item = file_menu.Append(ID_EXIT, '&Exit...', 'Exit Settings Window') self.Bind(EVT_MENU, self.prepare_for_exit, exit_item) menu_bar = MenuBar() menu_bar.Append(file_menu, '&File') self.SetMenuBar(menu_bar) self.fields = [] for top in sorted(settings.SETTINGS.keys()): # pylint: disable=no-member self.make_page(top) self.CenterOnScreen() self.Show() self.Bind(EVT_ON_KILL, self.OnKill) self.Bind(EVT_ON_COMPLETE, self.OnComplete) self.Bind(EVT_CLOSE, self.xmlrpc_kill) self.expiration = threading.Timer(300, self.xmlrpc_kill) self.expiration.start() def OnTab(self, event): the_id = event.GetId() curr = self.notebook.GetSelection() next = curr + 1 if the_id == self.next_page.GetId() else curr - 1 page_count = self.notebook.GetPageCount() next = 0 if next == page_count else page_count - 1 if next < 0 else next self.notebook.ChangeSelection(next) def OnKill(self, event): self.expiration.cancel() self.server.shutdown() self.Destroy() def OnComplete(self, event): self.completed = True self.Hide() def prepare_for_exit(self, e): self.Hide() self.completed = True threading.Timer(10, self.xmlrpc_kill).start() def tree_to_dictionary(self, t=None): d = {} children = self.fields if t is None else t.children for field in children: value = None if isinstance(field.widget, TextCtrl): value = field.widget.GetValue() if field.text_type == STRING_LIST_SETTING: d[field.original] = [ x for x in value.replace(", ", ",").split(",") if x ] # don't count empty strings elif field.text_type == NUMBER_LIST_SETTING: temp_list = (float(x) for x in value.replace(", ", ",").split(",") if x) # don't count empty strings d[field.original] = [ int(x) if x.is_integer() else x for x in temp_list ] elif field.text_type == NUMBER_SETTING: value = float(value) if value.is_integer(): value = int(value) d[field.original] = float(value) else: d[field.original] = value.replace("\\", "/") elif isinstance(field.widget, (Panel, ScrolledPanel)): d[field.original] = self.tree_to_dictionary(field) elif isinstance(field.widget, CheckBox): d[field.original] = field.widget.GetValue() return d def setup_xmlrpc_server(self): self.server.register_function(self.xmlrpc_get_message, "get_message") self.server.register_function(self.xmlrpc_complete, "complete") self.server.register_function(self.xmlrpc_kill, "kill") server_thread = threading.Thread(target=self.server.serve_forever) server_thread.daemon = True server_thread.start() def xmlrpc_kill(self, e=None): wx.PostEvent(SETTINGS_FRAME, OnKillEvent()) def xmlrpc_get_message(self): if self.completed: threading.Timer(1, self.xmlrpc_kill).start() return self.tree_to_dictionary() else: return None def xmlrpc_complete(self): wx.PostEvent(SETTINGS_FRAME, OnCompleteEvent()) def make_page(self, title): page = ScrolledPanel(parent=self.notebook, id=-1) vbox = BoxSizer(VERTICAL) field = Field(page, title) self.get_fields(page, vbox, field) self.fields.append(field) page.SetupScrolling() page.SetSizer(vbox) self.notebook.AddPage(page, title) def get_fields(self, page, vbox, field): for label in sorted(settings.SETTINGS[field.original].keys()): hbox = BoxSizer(HORIZONTAL) value = settings.SETTINGS[field.original][label] lbl = StaticText(page, label=label) hbox.Add(lbl, flag=RIGHT, border=8) subfield = Field(None, label) item = self.field_from_value(page, value, subfield) field.add_child(subfield) if item is not None: hbox.Add(item, proportion=1) vbox.Add(hbox, flag=EXPAND | LEFT | RIGHT | TOP, border=5) vbox.Add((-1, 5)) def field_from_value(self, window, value, field): item = None if isinstance(value, six.string_types): item = TextCtrl(window, value=value) field.text_type = STRING_SETTING elif isinstance(value, list): if isinstance(value[0], six.string_types): item = TextCtrl(window, value=", ".join(value)) field.text_type = STRING_LIST_SETTING elif isinstance(value[0], numbers.Real): item = TextCtrl(window, value=", ".join((str(x) for x in value))) field.text_type = NUMBER_LIST_SETTING elif isinstance(value, bool): item = CheckBox(window, -1, '', (120, 75)) item.SetValue(value) elif isinstance(value, numbers.Real): item = TextCtrl(window, value=str(value)) field.text_type = NUMBER_SETTING elif isinstance(value, dict): subpage = Panel(window) vbox = BoxSizer(VERTICAL) for lbl in sorted(value.keys()): hbox = BoxSizer(HORIZONTAL) value2 = value[lbl] label = StaticText(subpage, label=lbl) hbox.Add(label, flag=RIGHT, border=8) subfield = Field(None, lbl) item = self.field_from_value(subpage, value2, subfield) field.add_child(subfield) if item is not None: hbox.Add(item, proportion=1) vbox.Add(hbox, flag=EXPAND | LEFT | RIGHT | TOP, border=5) vbox.Add((-1, 5)) subpage.SetSizer(vbox) subpage.Show() item = subpage else: # This is left for bug reporting purposes. printer.out(("{} from the field {} was not assigned to " + "{} because type {} wasn't properly handled.").format( value, field, window, type(value))) field.widget = item return item