#----------------------------------------------------------------------------- # Name: Controllers.py # Purpose: Controller classes for the MVC pattern # # Author: Riaan Booysen # # Created: 2001/13/08 # RCS-ID: $Id: Controllers.py,v 1.13 2005/05/18 12:05:11 riaan Exp $ # Copyright: (c) 2001 - 2005 Riaan Booysen # Licence: GPL #----------------------------------------------------------------------------- print 'importing Models.Controllers' import os import wx import Preferences, Utils from Preferences import keyDefs, IS import EditorHelper, PaletteStore, EditorModels from Views import EditorViews, SourceViews, DiffView from Explorers import ExplorerNodes addTool = Utils.AddToolButtonBmpIS class BaseEditorController: """ Between user and model operations Provides interface to add new and open existing models Manages toolbar and menu actions Custom classes should define Model operations as events """ docked = True Model = None DefaultViews = [] AdditionalViews = [] plugins = () def __init__(self, editor): self.editor = editor self.evts = [] self.plugins = [Plugin(self) for Plugin in self.plugins] def getModel(self): return self.editor.getActiveModulePage().model def createModel(self, source, filename, name, editor, saved, modelParent=None): pass def createNewModel(self, modelParent=None): pass def afterAddModulePage(self, model): pass def newFileTransport(self, name, filename): from Explorers.FileExplorer import FileSysNode return FileSysNode(name, filename, None, -1, None, None, properties = {}) def addEvt(self, wId, meth): self.editor.Bind(wx.EVT_MENU, meth, id=wId) self.evts.append(wId) def disconnectEvts(self): for wId in self.evts: self.editor.Disconnect(wId) self.evts = [] def addMenu(self, menu, wId, label, accls, code=(), bmp=''): Utils.appendMenuItem(menu, wId, label, code, bmp) if code: accls.append( (code[0], code[1], wId) ) #------------------------------------------------------------------------------- def actions(self, model): """ Override to define Controller/Model actions Should return a list of tuples in this form: [('Name', self.OnEvent, 'BmpPath', 'KeyDef'), ...] """ return [] def addActions(self, toolbar, menu, model): actions = self.actions(model) for plugin in self.plugins: actions.extend(plugin.actions(model)) accls = [] for name, event, bmp, key in actions: if name != '-': wId = wx.NewId() self.addEvt(wId, event) if key: code = keyDefs[key] else: code = () self.addMenu(menu, wId, name, accls, code, bmp) else: menu.AppendSeparator() if bmp: if bmp != '-': addTool(self.editor, toolbar, bmp, name, event) elif name == '-' and bmp == '-': toolbar.AddSeparator() return accls class EditorController(BaseEditorController): closeBmp = 'Images/Editor/Close.png' def actions(self, model): return BaseEditorController.actions(self, model) + \ [('Close', self.OnClose, self.closeBmp, 'Close')] def OnClose(self, event): self.editor.closeModulePage(self.editor.getActiveModulePage()) class PersistentController(EditorController): saveBmp = 'Images/Editor/Save.png' saveAsBmp = 'Images/Editor/SaveAs.png' def actions(self, model): return EditorController.actions(self, model) + \ [('Reload', self.OnReload, '-', ''), ('Save', self.OnSave, self.saveBmp, 'Save'), ('Save as...', self.OnSaveAs, self.saveAsBmp, 'SaveAs'), ('-', None, '', ''), ('Toggle read-only', self.OnToggleReadOnly, '-', ''), ('NDiff files...', self.OnNDiffFile, '-', '')] def createModel(self, source, filename, main, saved, modelParent=None): return self.Model(source, filename, self.editor, saved) def createNewModel(self, modelParent=None): name = self.editor.getValidName(self.Model) model = self.createModel('', name, '', False) model.transport = self.newFileTransport('', name) model.new() return model, name def checkUnsaved(self, model, checkModified=False): if not model.savedAs or checkModified and (model.modified or \ len(model.viewsModified)): wx.LogError('Cannot perform this action on an unsaved%s module'%( checkModified and '/modified' or '') ) return True else: return False def OnSave(self, event): try: self.editor.activeModSaveOrSaveAs() except ExplorerNodes.TransportModifiedSaveError, err: errStr = err.args[0] if errStr == 'Reload': self.OnReload(event) elif errStr == 'Cancel': pass else: wx.LogError(str(err)) except ExplorerNodes.TransportSaveError, err: wx.LogError(str(err)) def OnSaveAs(self, event): try: self.editor.activeModSaveOrSaveAs(forceSaveAs=True) except ExplorerNodes.TransportModifiedSaveError, err: errStr = err.args[0] if errStr == 'Reload': self.OnReload(event) elif errStr == 'Cancel': pass else: wx.LogError(str(err)) except ExplorerNodes.TransportSaveError, err: wx.LogError(str(err)) def OnReload(self, event): model = self.getModel() if model: if not model.savedAs: wx.MessageBox('Cannot reload, this file has not been saved yet.', 'Reload', wx.OK | wx.ICON_ERROR) return if model.hasUnsavedChanges() and \ wx.MessageBox('There are unsaved changes.\n'\ 'Are you sure you want to reload?', 'Confirm reload', wx.YES_NO | wx.ICON_WARNING) != wx.YES: return try: model.load() self.editor.updateModuleState(model) except ExplorerNodes.TransportLoadError, error: wx.LogError(str(error)) def OnToggleReadOnly(self, event): model = self.getModel() if model and model.transport and model.transport.stdAttrs.has_key('read-only'): model.transport.updateStdAttrs() ro = model.transport.stdAttrs['read-only'] model.transport.setStdAttr('read-only', not ro) if model.views.has_key('Source'): model.views['Source'].updateFromAttrs() self.editor.updateModuleState(model) else: wx.LogError('Read-only not supported on this transport') def OnNDiffFile(self, event=None, filename=''): model = self.getModel() model.refreshFromViews() if model: if self.checkUnsaved(model): return if not filename: filename = self.editor.openFileDlg(curfile=os.path.basename(model.filename)) if filename: tbName = 'Diff with : '+filename if not model.views.has_key(tbName): resultView = self.editor.addNewView(tbName, DiffView.PythonSourceDiffView) else: resultView = model.views[tbName] resultView.tabName = tbName resultView.diffWith = filename resultView.refresh() resultView.focus() class SourceController(PersistentController): AdditionalViews = [EditorViews.CVSConflictsView] def OnDiffFile(self, event, diffWithFilename=''): model = self.getModel() if model: if self.checkUnsaved(model): return if not diffWithFilename: diffWithFilename = self.editor.openFileDlg() filename = model.assertLocalFile(filename) def OnPatchFile(self, event, patchFilename=''): model = self.getModel() if model: if self.checkUnsaved(model): return if not patchFilename: patchFilename = self.editor.openFileDlg() filename = model.assertLocalFile(filename) class TextController(PersistentController): Model = EditorModels.TextModel DefaultViews = [SourceViews.TextView] AdditionalViews = [] class UndockedController(BaseEditorController): docked = False def createModel(self, source, filename, main, saved, modelParent=None): return self.Model(source, filename, self.editor, saved) def display(self, model): """ Override to display undocked interface """ class BitmapFileController(UndockedController): Model = EditorModels.BitmapFileModel DefaultViews = [] AdditionalViews = [] def display(self, model): from ZopeLib import ImageViewer ImageViewer.create(self.editor).showImage(model.filename, model.transport) # XXX move to a new module PythonComControllers class MakePyController(BaseEditorController): docked = False Model = None DefaultViews = [] AdditionalViews = [] def createNewModel(self, modelParent=None): return None, None def display(self, model): import makepydialog dlg = makepydialog.create(self.editor) try: if dlg.ShowModal() == wx.ID_OK and dlg.generatedFilename: self.editor.openOrGotoModule(dlg.generatedFilename) finally: dlg.Destroy() #------------------------------------------------------------------------------- def identifyFilename(filename): dummy, name = os.path.split(filename) base, ext = os.path.splitext(filename) lext = ext.lower() if fullnameTypes.has_key(name): return fullnameTypes[name] if not ext and base.upper() == base: return EditorModels.TextModel, '', lext if EditorHelper.extMap.has_key(lext): return EditorHelper.extMap[lext], '', lext if lext in EditorHelper.internalFilesReg: return EditorModels.InternalFileModel, '', lext return None, '', lext def identifyFile(filename, source=None, localfs=True): """ Return appropriate model for given source file. Assumes header will be part of the first continious comment block """ Model, main, lext = identifyFilename(filename) if Model is not None: return Model, main if lext == defaultExt: BaseModel = DefaultModel else: BaseModel = EditorModels.UnknownFileModel if source is None and not localfs: if lext in EditorHelper.inspectableFilesReg.keys(): return EditorHelper.inspectableFilesReg[lext], '' else: return BaseModel, '' elif lext in EditorHelper.inspectableFilesReg.keys(): BaseModel = EditorHelper.inspectableFilesReg[lext] if source is not None: return identifySource[lext](source.split('\n')) elif not Preferences.exInspectInspectableFiles: return BaseModel, '' if os.path.exists(filename): f = open(filename) try: while 1: line = f.readline() if not line: break line = line.strip() if line and headerStartChar.has_key(lext): if line[0] != headerStartChar[lext]: return BaseModel, '' headerInfo = identifyHeader[lext](line) if headerInfo[0] != BaseModel: return headerInfo return BaseModel, '' finally: f.close() return BaseModel, '' #-Registration of this modules classes--------------------------------------- modelControllerReg = {EditorModels.TextModel: TextController, EditorModels.BitmapFileModel: BitmapFileController} # Default filetype DefaultController = TextController DefaultModel = EditorModels.TextModel defaultExt = EditorModels.TextModel.ext # Model identifiers for application type files appModelIdReg = [] # Dictionaries of functions keyed on file extension headerStartChar = {} identifyHeader = {} identifySource = {} # dictionary of filetypes recognised by the whole name fullnameTypes = {} # Classed from which the Designer can load resources resourceClasses = []