# -*- coding: utf-8 -*-

# Copyright (c) 2005 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a template viewer and associated classes.
"""

import os
import sys
import re

from qt import *

from TemplatePropertiesDialog import TemplatePropertiesDialog
from TemplateMultipleVariablesDialog import TemplateMultipleVariablesDialog
from TemplateSingleVariableDialog import TemplateSingleVariableDialog
import PixmapCache

from KdeQt import KQMessageBox, KQFileDialog

import Preferences

class TemplateGroup(QListViewItem):
    """
    Class implementing a template group.
    """
    def __init__(self, parent, name, language="All"):
        """
        Constructor
        
        @param parent parent widget of the template group (QWidget)
        @param name name of the group (string or QString)
        @param language programming language for the group (string or QString)
        """
        self.name = unicode(name)
        self.language = unicode(language)
        self.entries = {}
        
        QListViewItem.__init__(self, parent, name)
        
        self.setExpandable(0)
    
    def setName(self, name):
        """
        Public method to update the name of the group.
        
        @param name name of the group (string or QString)
        """
        self.name = unicode(name)
        self.setText(0, name)

    def getName(self):
        """
        Public method to get the name of the group.
        
        @return name of the group (string)
        """
        return self.name
        
    def setLanguage(self, language):
        """
        Public method to update the name of the group.
        
        @param language programming language for the group (string or QString)
        """
        self.language = unicode(language)

    def getLanguage(self):
        """
        Public method to get the name of the group.
        
        @return language of the group (string)
        """
        return self.language
        
    def addEntry(self, name, template, quiet=0):
        """
        Public method to add a template entry to this group.
        
        @param name name of the entry (string or QString)
        @param template template text of the entry (string or QString)
        @param quiet flag indicating quiet operation (boolean)
        """
        name = unicode(name)
        if self.entries.has_key(name):
            if not quiet:
                QMessageBox.critical(None,
                    self.trUtf8("Add Template"),
                    self.trUtf8("""<p>The group <b>%1</b> already contains a"""
                                """ template named <b>%2</b>.</p>""")\
                        .arg(self.name).arg(name),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
            return
        
        self.entries[name] = TemplateEntry(self, name, template)
        self.setExpandable(1)
        
        if Preferences.getTemplates("AutoOpenGroups") and not self.isOpen():
            self.setOpen(1)
    
    def removeEntry(self, name):
        """
        Public method to remove a template entry from this group.
        
        @param name name of the entry to be removed (string or QString)
        """
        name = unicode(name)
        if not self.entries.has_key(name):
            return
        
        self.takeItem(self.entries[name])
        del self.entries[name]
        
        if len(self.entries) == 0:
            self.setExpandable(0)
            if Preferences.getTemplates("AutoOpenGroups") and self.isOpen():
                self.setOpen(0)
    
    def removeAllEntries(self):
        """
        Public method to remove all template entries of this group.
        """
        for name in self.entries.keys()[:]:
            self.removeEntry(name)

    def getEntry(self, name):
        """
        Public method to get an entry.
        
        @param name name of the entry to retrieve (string or QString)
        @return reference to the entry (TemplateEntry)
        """
        return self.entries[unicode(name)]

    def getAllEntries(self):
        """
        Public method to retrieve all entries.
        
        @return list of all entries (list of TemplateEntry)
        """
        return self.entries.values()

class TemplateEntry(QListViewItem):
    """
    Class immplementing a template entry.
    """
    def __init__(self, parent, name, templateText):
        """
        Constructor
        
        @param parent parent widget of the template group (QWidget)
        @param name name of the group (string or QString)
        @param templateText text of the template entry (string or QString)
        """
        self.name = unicode(name)
        self.template = unicode(templateText)
        self.__extractVariables()
        
        QListViewItem.__init__(self, parent, name)

    def setName(self, name):
        """
        Public method to update the name of the group.
        
        @param name name of the group (string or QString)
        """
        self.name = unicode(name)
        self.setText(0, name)

    def getName(self):
        """
        Public method to get the name of the group.
        
        @return name of the group (string)
        """
        return self.name

    def getGroupName(self):
        """
        Public method to get the name of the group this entry belongs to.
        
        @return name of the group containing this entry (string)
        """
        return self.parent().getName()
        
    def setTemplateText(self, templateText):
        """
        Public method to update the template text.
        
        @param templateText text of the template entry (string or QString)
        """
        self.template = unicode(templateText)
        self.__extractVariables()

    def getTemplateText(self):
        """
        Public method to get the template text.
        
        @return the template text (string)
        """
        return self.template

    def getExpandedText(self, varDict):
        """
        Public method to get the template text with all variables expanded.
        
        @param varDict dictionary containing the texts of each variable
            with the variable name as key.
        @return the expanded template text (string)
        """
        txt = self.template
        for var, val in varDict.items():
            if var in self.formatedVariables:
                txt = self.expandFormattedVariable(var, val, txt)
            else:
                txt = txt.replace(var, val)
        sepchar = str(Preferences.getTemplates("SeparatorChar"))
        txt = txt.replace("%s%s" % (sepchar, sepchar), sepchar)
        return txt

    def expandFormattedVariable(self, var, val, txt):
        """
        Private method to expand a template variable with special formatting.
        
        @param var template variable name (string)
        @param val value of the template variable (string)
        @param txt template text (string)
        """
        t = ""
        for line in txt.splitlines():
            ind = line.find(var)
            if ind >= 0:
                format = var[1:-1].split(':', 1)[1]
                if format == 'ml':
                    prefix = line[:ind]
                    for v in val.splitlines():
                        t = "%s%s%s%s" % (t, os.linesep, prefix, v)
                else:
                    t = "%s%s%s" % (t, os.linesep, line)
            else:
                t = "%s%s%s" % (t, os.linesep, line)
        return "".join(t.splitlines(1)[1:])

    def getVariables(self):
        """
        Public method to get the list of variables.
        
        @return list of variables (list of strings)
        """
        return self.variables

    def __extractVariables(self):
        """
        Private method to retrieve the list of variables.
        """
        sepchar = str(Preferences.getTemplates("SeparatorChar"))
        variablesPattern = \
            re.compile(r"""\%s[a-zA-Z][a-zA-Z0-9_]*(?::(?:ml))?\%s""" % (sepchar, sepchar))
        variables = variablesPattern.findall(self.template)
        self.variables = []
        self.formatedVariables = []
        for var in variables:
            if not var in self.variables:
                self.variables.append(var)
            if var.find(':') >= 0 and not var in self.formatedVariables:
                self.formatedVariables.append(var)

class TemplateViewer(QListView):
    """
    Class implementing the template viewer.
    """
    def __init__(self, parent, viewmanager):
        """
        Constructor
        
        @param parent the parent (QWidget)
        @param viewmanager reference to the viewmanager object
        """
        QListView.__init__(self,parent)
        
        self.vm = viewmanager
        self.groups = {}
        
        self.addColumn("Template", 150)
        self.header().hide()
        self.setShowToolTips(1)
        self.setRootIsDecorated(1)
        
        self.menu = QPopupMenu(self)
        act = QAction(self.trUtf8("Apply"), QKeySequence(), self)
        self.connect(act, SIGNAL('activated()'), self.contentsMouseDoubleClickEvent)
        act.addTo(self.menu)
        self.menu.insertSeparator()
        act = QAction(self.trUtf8("Add entry..."), QKeySequence(), self)
        self.connect(act, SIGNAL('activated()'), self.handleAddEntry)
        act.addTo(self.menu)
        addGroupAct = QAction(self.trUtf8("Add group..."), QKeySequence(), self)
        self.connect(addGroupAct, SIGNAL('activated()'), self.handleAddGroup)
        addGroupAct.addTo(self.menu)
        act = QAction(self.trUtf8("Edit..."), QKeySequence(), self)
        self.connect(act, SIGNAL('activated()'), self.handleEdit)
        act.addTo(self.menu)
        act = QAction(self.trUtf8("Remove"), QKeySequence(), self)
        self.connect(act, SIGNAL('activated()'), self.handleRemove)
        act.addTo(self.menu)
        self.menu.insertSeparator()
        saveAct = QAction(self.trUtf8("Save"), QKeySequence(), self)
        self.connect(saveAct, SIGNAL('activated()'), self.handleSave)
        saveAct.addTo(self.menu)
        importAct = QAction(self.trUtf8("Import..."), QKeySequence(), self)
        self.connect(importAct, SIGNAL('activated()'), self.handleImport)
        importAct.addTo(self.menu)
        exportAct = QAction(self.trUtf8("Export..."), QKeySequence(), self)
        self.connect(exportAct, SIGNAL('activated()'), self.handleExport)
        exportAct.addTo(self.menu)
        self.menu.insertSeparator()
        helpAct = QAction(self.trUtf8("Help about Templates..."), QKeySequence(), self)
        self.connect(helpAct, SIGNAL('activated()'), self.handleHelp)
        helpAct.addTo(self.menu)
        
        self.backMenu = QPopupMenu(self)
        addGroupAct.addTo(self.backMenu)
        self.backMenu.insertSeparator()
        saveAct.addTo(self.backMenu)
        importAct.addTo(self.backMenu)
        exportAct.addTo(self.backMenu)
        self.backMenu.insertSeparator()
        helpAct.addTo(self.backMenu)
        
        self.connect(self,SIGNAL('contextMenuRequested(QListViewItem *, const QPoint &, int)'),
                     self.handleContextMenu)
        
        QToolTip.remove(self)
        self._tooltip = TemplateToolTip(self)
        
        self.setIcon(PixmapCache.getPixmap("eric.png"))
        
    def contentsMouseDoubleClickEvent(self, mouseEvent=None):
        """
        Protected method of QListView. 
        
        Reimplemented to disable expanding/collapsing
        of items when double-clicking. Instead the selected
        template text is expanded and inserted at the cursor
        position of the active document.
        
        @param mouseEvent the mouse event (QMouseEvent)
        """
        itm = self.currentItem()
        if isinstance(itm, TemplateEntry):
            self.applyTemplate(itm)
        
    def handleContextMenu(self, itm, coord, col):
        """
        Private slot to show the context menu of the listview.
        
        @param itm the selected listview item (QListViewItem)
        @param coord the position of the mouse pointer (QPoint)
        @param col the column of the mouse pointer (int)
        """
        if itm is None:
            self.backMenu.popup(coord)
        else:
            self.menu.popup(coord)
    
    def handleAddEntry(self):
        """
        Private slot to handle the Add Entry context menu action.
        """
        itm = self.currentItem()
        if isinstance(itm, TemplateGroup):
            groupName = itm.getName()
        else:
            groupName = itm.getGroupName()
        
        dlg = TemplatePropertiesDialog(self)
        dlg.setSelectedGroup(groupName)
        if dlg.exec_loop() == QDialog.Accepted:
            name, groupName, template = dlg.getData()
            self.addEntry(groupName, name, template)
        
    def handleAddGroup(self):
        """
        Private slot to handle the Add Group context menu action.
        """
        dlg = TemplatePropertiesDialog(self, 1)
        if dlg.exec_loop() == QDialog.Accepted:
            name, language = dlg.getData()
            self.addGroup(name, language)
        
    def handleEdit(self):
        """
        Private slot to handle the Edit context menu action.
        """
        itm = self.currentItem()
        if isinstance(itm, TemplateEntry):
            editGroup = 0
        else:
            editGroup = 1
        dlg = TemplatePropertiesDialog(self, editGroup, itm)
        if dlg.exec_loop() == QDialog.Accepted:
            if editGroup:
                name, language = dlg.getData()
                self.changeGroup(itm.getName(), name, language)
            else:
                name, groupName, template = dlg.getData()
                self.changeEntry(itm, name, groupName, template)
        
    def handleRemove(self):
        """
        Private slot to handle the Remove context menu action.
        """
        itm = self.currentItem()
        res = QMessageBox.question(None,
            self.trUtf8("Remove Template"),
            self.trUtf8("""<p>Do you really want to remove <b>%1</b>?</p>""")\
                .arg(itm.getName()),
            self.trUtf8("&Yes"),
            self.trUtf8("&No"),
            QString.null,
            1, -1)
        if res > 0:
            return

        if isinstance(itm, TemplateGroup):
            self.removeGroup(itm)
        else:
            self.removeEntry(itm)

    def handleSave(self):
        """
        Private slot to handle the Save context menu action.
        """
        qApp.mainWidget().writeTemplates()

    def handleImport(self):
        """
        Private slot to handle the Import context menu action.
        """
        fn = KQFileDialog.getOpenFileName(\
            QString.null,
            self.trUtf8("Templates Files (*.e3c);; All Files (*)"),
            self, None,
            self.trUtf8("Import Templates"),
            None, 1)
        
        if not fn.isEmpty():
            qApp.mainWidget().readTemplates(fn)

    def handleExport(self):
        """
        Private slot to handle the Export context menu action.
        """
        selectedFilter = QString('')
        fn = KQFileDialog.getSaveFileName(\
            QString.null,
            self.trUtf8("Templates Files (*.e3c);; All Files (*)"),
            self, None,
            self.trUtf8("Export Templates"),
            selectedFilter, 1)
        
        if not fn.isEmpty():
            ext = QFileInfo(fn).extension()
            if ext.isEmpty():
                ex = selectedFilter.section('(*',1,1).section(')',0,0)
                if not ex.isEmpty():
                    fn.append(ex)
            qApp.mainWidget().writeTemplates(fn)

    def handleHelp(self):
        """
        Private method to show some help.
        """
        QMessageBox.information(self,
            self.trUtf8("Template Help"),
            self.trUtf8("""<p><b>Template groups</b> are a means of grouping individual"""
                        """ templates. Groups have an attribute that specifies,"""
                        """ which programming language they apply for."""
                        """ In order to add template entries, at least one group"""
                        """ has to be defined.</p>"""
                        """<p><b>Template entries</b> are the actual templates."""
                        """ They are grouped by the template groups. Help about"""
                        """ how to define them is available in the template edit"""
                        """ dialog. There is an example template available in the"""
                        """ Examples subdirectory of the eric3 distribution.</p>"""),
            self.trUtf8("&OK"),
            QString.null,
            QString.null,
            0, -1)

    def applyTemplate(self, itm):
        editor = self.vm.activeWindow()
        if editor is None:
            return
        
        ok = 0
        vars = itm.getVariables()
        if vars:
            if Preferences.getTemplates("SingleDialog"):
                dlg = TemplateMultipleVariablesDialog(vars, self)
                if dlg.exec_loop() == QDialog.Accepted:
                    varValues = dlg.getVariables()
                    ok = 1
            else:
                varValues = {}
                for var in vars:
                    dlg = TemplateSingleVariableDialog(var, self)
                    if dlg.exec_loop() == QDialog.Accepted:
                        varValues[var] = dlg.getVariable()
                    else:
                        return
                    del dlg
                ok = 1
        else:
            varValues = {}        
            ok = 1
        
        if ok:
            txt = itm.getExpandedText(varValues)
            line, index = editor.getCursorPosition()
            # It should be done on this way to allow undo
            editor.beginUndoAction()
            editor.insertAt(txt, line, index)
            editor.endUndoAction()

    def addEntry(self, groupName, name, template, quiet=0):
        """
        Public method to add a template entry.
        
        @param groupName name of the group to add to (string or QString)
        @param name name of the entry to add (string or QString)
        @param template template text of the entry (string or QString)
        @param quiet flag indicating quiet operation (boolean)
        """
        self.groups[unicode(groupName)].addEntry(unicode(name), unicode(template),
                                                 quiet=quiet)

    def addGroup(self, name, language="All"):
        """
        Public method to add a group.
        
        @param name name of the group to be added (string or QString)
        @param language programming language for the group (string or QString)
        """
        name = unicode(name)
        if not self.groups.has_key(name):
            self.groups[name] = TemplateGroup(self, name, language)

    def changeGroup(self, oldname, newname, language="All"):
        """
        Public method to rename a group.
        
        @param oldname old name of the group (string or QString)
        @param newname new name of the group (string or QString)
        @param language programming language for the group (string or QString)
        """
        if oldname != newname:
            if self.groups.has_key(newname):
                KQMessageBox.warning(self,
                    self.trUtf8("Edit Template Group"),
                    self.trUtf8("""<p>A template group with the name"""
                                """ <b>%1</b> already exists.</p>""")\
                        .arg(newname),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
                return
            
            self.groups[newname] = self.groups[oldname]
            del self.groups[oldname]
            self.groups[newname].setName(newname)
        
        self.groups[newname].setLanguage(language)

    def getAllGroups(self):
        """
        Public method to get all groups.
        
        @return list of all groups (list of TemplateGroup)
        """
        return self.groups.values()
    
    def getGroupNames(self):
        """
        Public method to get all group names.
        
        @return list of all group names (list of strings)
        """
        groups = self.groups.keys()[:]
        groups.sort()
        return groups

    def removeGroup(self, itm):
        """
        Public method to remove a group.
        
        @param itm template group to be removed (TemplateGroup)
        """
        name = itm.getName()
        itm.removeAllEntries()
        self.takeItem(itm)
        del self.groups[name]

    def removeEntry(self, itm):
        """
        Public method to remove a template entry.
        
        @param itm template entry to be removed (TemplateEntry)
        """
        groupName = itm.getGroupName()
        self.groups[groupName].removeEntry(itm.getName())

    def changeEntry(self, itm, name, groupName, template):
        """
        Public method to change a template entry.
        
        @param itm template entry to be changed (TemplateEntry)
        @param name new name for the entry (string or QString)
        @param groupName name of the group the entry should belong to
            (string or QString)
        @param template template text of the entry (string or QString)
        """
        if itm.getGroupName() != groupName:
            # move entry to another group
            self.groups[itm.getGroupName()].removeEntry(itm.getName())
            self.groups[groupName].addEntry(name, template)
            return
        
        if itm.getName() != name:
            # entry was renamed
            self.groups[groupName].removeEntry(itm.getName())
            self.groups[groupName].addEntry(name, template)
            return
        
        self.groups[groupName].getEntry(name).setTemplateText(template)

class TemplateToolTip(QToolTip):
    """
    Class implementing a specialized tooltip.
    """
    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget of the tooltip
        """
        QToolTip.__init__(self, parent.viewport())
        
        self.listView = parent
    
    def maybeTip(self, p):
        """
        Overridden protected slot to show the tooltip.
        
        @param p position of the mouse cursor (QPoint)
        """
        itm = self.listView.itemAt(p)
        if itm is None or \
           not isinstance(itm, TemplateEntry) or \
           not Preferences.getTemplates("ShowTooltip"):
            return
        
        itmRect = self.listView.itemRect(itm)
        if not itmRect.isValid():
            return
        
        col = self.listView.header().sectionAt(p.x())
        if col == -1:
            return
        
        headerRect = self.listView.header().sectionRect(col)
        if not headerRect.isValid():
            return
        
        cellRect = QRect(headerRect.left(), itmRect.top(),
                         headerRect.width(), itmRect.height())
        
        self.tip(cellRect, itm.getTemplateText())
