#-----------------------------------------------------------------------------
# Name:        Controllers.py
# Purpose:     Controller classes for the MVC pattern
#
# Author:      Riaan Booysen
#
# Created:     2001/13/08
# RCS-ID:      $Id: Controllers.py,v 1.16 2007/07/02 15:01:11 riaan Exp $
# Copyright:   (c) 2001 - 2007 Riaan Booysen
# Licence:     GPL
#-----------------------------------------------------------------------------
print 'importing Models.Controllers'

import os, codecs

import wx

import Preferences, Utils
from Preferences import keyDefs, IS
from Utils import _

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.startswith(codecs.BOM_UTF8):
                        line = line[len(codecs.BOM_UTF8):]
                    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 = []
