"""Module containing the FxPy (Fox) implementation for the GUI tutorial COMMENTS: ========= + Events are tied to the ID of the widget via FXMAPFUNC(_XXX wrapper functions + Very complete documentation for C++ (with Python differences noted) [http://home.hiwaay.net/~johnson2/fox/fox-api/] + Widget constructors are not as explicit in the FxPy as they are in the C++ doc; use named parameters to make sure you get what you want + Easiest widget layout of all the GUIs in the tutorial, with a 'plan-ahead' approach to widget hierarchy management + Map all the messages to target functions at the beginning (not necessary, but seems to be the 'fox' way :) IDIOMS: ======= + Layout management is done via the type of container AND 'bit' flags for expand/fill behavior (http://www.fox-toolkit.org/layout.html) + Event handling is done via FOX's 'Target/Message System,' where each Widget sends its 'message' to a certain object called the 'target' (an FXObject) + Event handlers have return values ... return either 0 or 1 to allow 'intelligent' message routine throughout the application (1 if message was processed normally). Don't forget to return FXApp.run()!! + Messages can also be re-targeted within an event handler by calling the "handle()" method on the delegate object that may or may not be mapped to handle the message. """ import os from FXPy.fox import * from oogui import OOGui import oogui _PRESTATUS = 'Directory: ' _NUM_TEXT_COLS = 20 _NUM_VIS_ITEMS = 7 _ANCHOR_ALL = LAYOUT_LEFT|LAYOUT_FILL_X|LAYOUT_FILL_Y def _getLabeledList(w, label, id, target): """Returns a listbox with selection message mapped to targetFunction, labeled at the top left with value of label. """ box = FXGroupBox(w, label, _ANCHOR_ALL | FRAME_NONE) listbox = FXList(box, nvis=_NUM_VIS_ITEMS , tgt=target, sel=id, opts=FRAME_SUNKEN|FRAME_THICK|_ANCHOR_ALL) return listbox class FxPyGui(FXApp, OOGui): """Specialization of OOGui that uses the FxPy widget set""" # Widget IDs used to map messages to targets (event handling) # When adding new widgets, DON'T FORGET to change end range value! (_GOTO_BUTTON_ID, _GOTO_ENTRY_ID, _DIR_LIST_ID, _FILE_LIST_ID, _FILENAME_ID) = range(FXMainWindow.ID_LAST, FXMainWindow.ID_LAST+5) def __init__(self, argv, geometry=None, resfile=None, startdir=None): """Create the FxPy QApplication and OOGui base objects""" self._statusBar = None FXApp.__init__(self) self.init(argv) # set up the selectors, messages, and targets the GUI will use self._MSG_FUNC_MAP = { FxPyGui._GOTO_BUTTON_ID : (SEL_COMMAND, self._setDirectory), FxPyGui._GOTO_ENTRY_ID : (SEL_COMMAND, self._setDirectory), FxPyGui._DIR_LIST_ID : (SEL_DOUBLECLICKED, self._directorySelected), FxPyGui._FILE_LIST_ID : (SEL_DOUBLECLICKED, self._fileSelected), FxPyGui._FILENAME_ID : (SEL_COMMAND, self._setFile) } topLevel = FXMainWindow(self, self.__class__.__name__) # init the base class to build the gui OOGui.__init__(self, topLevel, geometry, resfile, startdir) # ------------------------------------------------------------------- # polymorphic implementations # ------------------------------------------------------------------- def _buildGui(self, title): """Builds the gui for the FXPy implementation""" # map the messages to their targets for id in self._MSG_FUNC_MAP.keys(): msg, target = self._MSG_FUNC_MAP[id] FXMAPFUNC(self._topLevel, msg, id, target) # use the layout frames to manage child hierarchy vframe = FXVerticalFrame(self._topLevel, FRAME_NONE|_ANCHOR_ALL) # build the cwd panel self._buildCWD(vframe) # build the split pane containing lists and display self._buildPanes(vframe) # use status bar as current directory label self._statusBar = FXStatusbar(vframe, LAYOUT_SIDE_BOTTOM| LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER) self._setEntries() self._topLevel.setTitle(title) def _loadResources(self, resfile=None): """Load the resources (options) for all the 'named' widgets""" if resfile: # FIXME print 'FIXME: Load resource file!!!' return 1 def _startGui(self, geometry=None): """Starts the main event loop for the application""" # use geometry if specified width = oogui.DISPLAY_WIDTH height = oogui.APP_HEIGHT if geometry: # FIXME print 'set geometry: ', geometry self.create() self._topLevel.setWidth(width) self._topLevel.setHeight(height) self._topLevel.show(PLACEMENT_SCREEN) return self.run() # main event loop def _getCurrentDirectory(self): """Return the full pathname of current working directory""" return self._statusBar.getStatusline().getNormalText()[len(_PRESTATUS):] def _getSelectedFile(self): """Returns the name of the selected file""" file = self._fileList.getItemText(self._fileList.getCurrentItem()) if self._selectedFile: file = self._selectedFile return file def _getSelectedDirectory(self): """Returns the name of the selected directory""" return self._dirList.getItemText(self._dirList.getCurrentItem()) def _getTopLevel(self): """Special access method used by base to ensure top level is created""" return self._topLevel def _displayBinary(self, filename): """Displays binary data files""" self._display.binary(filename) return 1 # message handled properly def _displayHTML(self, filename): """Displays HTML files""" self._display.html(filename) return 1 # message handled properly def _displayImage(self, filename): """Displays image files""" self._display.image(filename) return 1 # message handled properly def _displayText(self, filename): """Displays text (other) files""" self._display.text(filename) return 1 # message handled properly # ------------------------------------------------------------------- # private methods # ------------------------------------------------------------------- def _buildCWD(self, w): """Private method to construct the change working directory window""" hframe = FXHorizontalFrame(w, LAYOUT_SIDE_TOP|FRAME_RAISED|LAYOUT_FILL_X) gotoButton = FXButton(hframe, "GoTo", tgt=self._topLevel, sel=FxPyGui._GOTO_BUTTON_ID) self._gotoEntry = FXTextField(hframe, _NUM_TEXT_COLS, sel=FxPyGui._GOTO_ENTRY_ID, tgt=self._topLevel, opts=FRAME_SUNKEN| FRAME_THICK|LAYOUT_FILL_X) def _buildPanes(self, w): """Use a SplitterWindow with panes for lists and display views""" splitter = FXSplitter(w, _ANCHOR_ALL|SPLITTER_VERTICAL|SPLITTER_TRACKING) self._buildLists(splitter) self._display = DisplayPanel(splitter) def _buildLists(self, w): """Builds the panel of directories and files list boxes""" # panel to contain the lists panel and filename entry at bottom vframe = FXVerticalFrame(w, FRAME_NONE|_ANCHOR_ALL) listsPanel = FXHorizontalFrame(vframe, FRAME_NONE|_ANCHOR_ALL) # create the directories list self._dirList = _getLabeledList(listsPanel, oogui.DIRS_LABEL, FxPyGui._DIR_LIST_ID, self._topLevel) # create the files list self._fileList = _getLabeledList(listsPanel, oogui.FILES_LABEL, FxPyGui._FILE_LIST_ID, self._topLevel) # create the filename entry fbox = FXGroupBox(vframe, oogui.FILENAME_LABEL, LAYOUT_SIDE_TOP| FRAME_NONE|_ANCHOR_ALL) self._filename = FXTextField(fbox, _NUM_TEXT_COLS, sel=FxPyGui._FILENAME_ID, tgt=self._topLevel, opts=FRAME_SUNKEN| FRAME_THICK|LAYOUT_FILL_X) def _setCWD(self, cwd): if not cwd or not os.path.isdir(cwd): return self._setCurrentDirectory(cwd) self._setEntries() def _setDirectory(self, *unused): "Callback for setting the name of the current directory" self._setCWD(self._gotoEntry.getText()) return 1 def _setFilename(self, filename=''): """Sets the filename entry field when a file is selected; called by base class's _fileSelected() method """ self._filename.setText(filename) def _setEntries(self): """Sets the names of files and directories in the lists for the 'current selected directory'. """ cwd = self._getCurrentDirectory() if not cwd: cwd = os.getcwd() self._setCurrentDirectory(cwd) self._gotoEntry.setText(cwd) # clear the items from the list boxes self._dirList.clearItems() self._fileList.clearItems() dirs, files = self._getEntries(cwd) # iterate through the list of directories and files and # populate the list boxes with their respective entries [self._dirList.appendItem(d) for d in dirs] [self._fileList.appendItem(f) for f in files] # clear the selection, but only if there is at least one item self._dirList.killSelection() self._fileList.killSelection() def _setFile(self, *unused): """Records the currently selected file as a hook for the call to the base class's 'fileSelected()' method which determines the 'type' of the file, and dispatches the appropriate viewer. """ self._selectedFile = self._filename.getText() self._fileSelected() self._selectedFile = None return 1 def _setCurrentDirectory(self, pathname): """Use the status bar to indicate name of current working directory""" self._statusBar.getStatusline().setNormalText(_PRESTATUS + pathname) # ----------------------------------------------------------------- # Specialized panel containing viewers for various file types # ----------------------------------------------------------------- class DisplayPanel(FXGroupBox): """The Display viewer at bottom of the GUI""" def __init__(self, w): FXGroupBox.__init__(self, w, 'Ready', opts=_ANCHOR_ALL|FRAME_SUNKEN) # image viewer holds onto server side resources, so keep around # to garbage collect correctly self._imageviewer = None def binary(self, unused): """Display binary viewer as unavailable""" self._show(None) def html(self, filename): """Use textual display for HTML for now""" self.text(filename) def image(self, filename): """Displays the image file specified""" self._show(filename) imageType = oogui._getFileType(filename).upper() # create name of function based on imageType, and retrieve the # loader function defined in FXPY.fox dict globals(), e.g., # FXBMPImage loader = globals().get('FX' + imageType + 'Image') if not loader: print 'Error loading image', 'Unsupported type: ', ext return img = loader(self.getApp(), None, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) if not img: print 'Error loading image file', filename return stream = FXFileStream() if stream.open(filename, FXStreamLoad): self.getApp().beginWaitCursor() img.loadPixels(stream) stream.close() img.create() self._imageviewer = FXImageView(self, opts=LAYOUT_FILL_X|LAYOUT_FILL_Y) self._imageviewer.setImage(img) self.getApp().endWaitCursor() def text(self, filename): self._show(filename) fileText, isBinary = oogui._getText(filename) if isBinary: self.binary(filename) return text = FXText(self, opts=_ANCHOR_ALL) print 'text: ', text text.setText(fileText) text.create() def _show(self, filename): """Creates the labeled container that manages the display widget""" # ensures old image is garbage collected, if we have one if self._imageviewer: old = self._imageviewer.getImage() self._imageviewer = None # unmanage all children of this window for c in range(self.numChildren()): print 'detaching', c, self.childAtIndex(c) #self.childAtIndex(c).detach() # causes crash in KERNEL32.DLL self.childAtIndex(c).destroy() # does not destroy/unmanage child #self.getApp().flush() # causes crash in KERNEL32.DLL if not filename: filename = oogui.DISPLAY_UNAVAIL labelText = oogui.DISPLAY_LABEL + filename # display filename as a label self.setText(labelText) # should be 0, but still thinks it has the unmanaged children :( print 'numChildren: ', self.numChildren()