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

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

"""
Module implementing a tabbed viewmanager class.
"""

import os

from qt import *

from ViewManager import ViewManager
import QScintilla.Editor
import UI.PixmapCache

from UI.E3Action import E3Action
from UI.E3TabWidget import E3TabWidget

from eric3config import getConfig

class TabWidget(E3TabWidget):
    """
    Class implementing a custimized TabWidget.
    """
    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        E3TabWidget.__init__(self, parent)
        
        self.editors = []
        
        self.pixmapActive = UI.PixmapCache.getPixmap("led_green.png")
        self.pixmapInactive = UI.PixmapCache.getPixmap("led_red.png")
        self.indicator = QLabel(self)
        self.indicator.setPixmap(self.pixmapInactive)
        self.indicator.setFixedSize(self.pixmapInactive.size())
        self.indicator.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        try:
            self.setCornerWidget(self.indicator, Qt.TopRight)
        except AttributeError:
            pass
        
        self.initMenu()
        self._tabBar = self.tabBar()
        self._tabBar.installEventFilter(self)
        self.contextMenuEditor = None
        
        ericPic = QPixmap(os.path.join(getConfig('ericPixDir'), 'eric_small.png'))
        self.emptyLabel = QLabel(self)
        self.emptyLabel.setPixmap(ericPic)
        self.emptyLabel.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
        E3TabWidget.addTab(self, self.emptyLabel, "")
        
    def initMenu(self):
        """
        Private method to initialize the tab context menu.
        """
        self.menu = QPopupMenu(self)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("close.png")),
            self.trUtf8('Close'), self.handleContextClose)
        self.menu.insertItem(self.trUtf8('Close All'), self.handleContextCloseAll)
        self.menu.insertSeparator()
        self.saveMenuId = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSave.png")),
            self.trUtf8('Save'), self.handleContextSave)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSaveAs.png")),
            self.trUtf8('Save As...'), self.handleContextSaveAs)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSaveAll.png")),
            self.trUtf8('Save All'), self.handleContextSaveAll)
        self.projectMenuId = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSaveProject.png")),
                self.trUtf8('Save to Project'), self.handleContextSaveToProject)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("print.png")),
            self.trUtf8('Print'), self.handleContextPrintFile)
            
        self.connect(self.menu, SIGNAL("aboutToShow()"), self.handleShowMenu)
        
    def handleShowMenu(self):
        """
        Private slot to handle the aboutToShow signal of the tab context menu.
        """
        if self.contextMenuEditor:
            self.menu.setItemEnabled(self.saveMenuId, self.contextMenuEditor.isModified())
        self.menu.setItemEnabled(self.projectMenuId, qApp.mainWidget().getProject().isOpen())
    
    def showIndicator(self, on):
        """
        Public slot to set the indicator on or off.
        
        @param on flag indicating the dtate of the indicator (boolean)
        """
        if on:
            self.indicator.setPixmap(self.pixmapActive)
        else:
            self.indicator.setPixmap(self.pixmapInactive)
        self.indicator.update()
        
    def addTab(self, editor, title):
        """
        Overwritten method to add a new tab.
        
        @param editor the editor object to be added (QScintilla.Editor.Editor)
        @param title title for the new tab (string, QString or QTab)
        """
        E3TabWidget.addTab(self, editor, title)
        
        if not editor in self.editors:
            self.editors.append(editor)
            self.connect(editor, PYSIGNAL('captionChanged'),
                self.handleCaptionChange)
        
        E3TabWidget.removePage(self, self.emptyLabel)
        
    def handleCaptionChange(self, cap, editor):
        """
        Private method to handle Caption change signals from the editor. 
        
        Updates the listview text to reflect the new caption information.
        
        @param cap Caption for the editor
        @param editor Editor to update the caption for
        """
        fn = editor.getFileName()
        if fn:
            txt = os.path.basename(fn)
            if editor.isReadOnly():
                txt = '%s (ro)' % txt
            self.changeTab(editor, txt)
        
    def removePage(self, object):
        """
        Overwritten method to remove a page.
        
        @param object object to be removed (QObject)
        """
        E3TabWidget.removePage(self, object)
        
        if isinstance(object, QScintilla.Editor.Editor):
            self.disconnect(object, PYSIGNAL('captionChanged'),
                self.handleCaptionChange)
            self.editors.remove(object)
        
        if not self.editors:
            E3TabWidget.addTab(self, self.emptyLabel, "")
            self.emptyLabel.show()
        
    def currentPage(self):
        """
        Overridden method to return a reference to the current page.
        
        @return reference to the current page (QWidget)
        """
        if not self.editors:
            return None
        else:
            return E3TabWidget.currentPage(self)
        
    def hasEditor(self, editor):
        """
        Public method to check for an editor.
        
        @param editor editor object to check for
        @return flag indicating, whether the editor to be checked belongs
            to the list of editors managed by this tab widget.
        """
        return editor in self.editors
        
    def hasEditors(self):
        """
        Public method to test, if any editor is managed.
        
        @return flag indicating editors are managed
        """
        return len(self.editors) and 1 or 0
        
    def handleContextClose(self):
        """
        Private method to close the selected tab.
        """
        if self.contextMenuEditor:
            self.parent().handleCloseEditor(self.contextMenuEditor)
        
    def handleContextCloseAll(self):
        """
        Private method to close all tabs.
        """
        savedEditors = self.editors[:]
        for editor in savedEditors:
            self.parent().handleCloseEditor(editor)
        
    def handleContextSave(self):
        """
        Private method to save the selected tab.
        """
        if self.contextMenuEditor:
            self.parent().saveEditorEd(self.contextMenuEditor)
        
    def handleContextSaveAs(self):
        """
        Private method to save the selected tab to a new file.
        """
        if self.contextMenuEditor:
            self.parent().saveAsEditorEd(self.contextMenuEditor)
        
    def handleContextSaveAll(self):
        """
        Private method to save all tabs.
        """
        self.parent().saveEditorsList(self.editors)
        
    def handleContextSaveToProject(self):
        """
        Private method to save the selected tab to the current project.
        """
        if self.contextMenuEditor:
            self.parent().saveEditorToProjectEd(self.contextMenuEditor)
        
    def handleContextPrintFile(self):
        """
        Private method to print the selected tab.
        """
        if self.contextMenuEditor:
            self.parent().printEditor(self.contextMenuEditor)
        
    def eventFilter(self, object, event):
        """
        Private method called to filter an event of our tabbar.
        
        @param object object, that generated the event (QObject)
        @param event the event, that was generated by object (QEvent)
        @return flag indicating if event was filtered out
        """
        if event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.RightButton:
                if self.editors:
                    tab = self._tabBar.selectTab(event.pos())
                    i = 0
                    self.contextMenuEditor = None
                    while i < self._tabBar.count():
                        if self._tabBar.tabAt(i) == tab:
                            self.contextMenuEditor = self.page(i)
                            break
                        i += 1
                    if self.contextMenuEditor:
                        self.menu.exec_loop(self._tabBar.mapToGlobal(event.pos()))
                        return 1
            
        return 0
        
class Tabview(QSplitter, ViewManager):
    """
    Class implementing a tabbed viewmanager class embedded in a splitter.
    
    @signal changeCaption(string) emitted if a change of the caption is neccessary
    @signal editorChanged(string) emitted when the current editor has changed
    """
    def __init__(self,parent, ui, dbs):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        @param ui reference to the main user interface
        @param dbs reference to the debug server object
        """
        self.tabWidgets = []
        
        QSplitter.__init__(self,parent)
        ViewManager.__init__(self, ui, dbs)
        tw = TabWidget(self)
        self.tabWidgets.append(tw)
        self.currentTabWidget = tw
        self.currentTabWidget.showIndicator(1)
        self.connect(tw, SIGNAL('currentChanged(QWidget*)'),
            self.handleCurrentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        self.setOrientation(QSplitter.Vertical)
        self.__inRemoveView = 0
        
        self.maxFileNameChars = 40
        
    def initViewActions(self):
        """
        Protected method defining the user interface actions for the view commands.
        """
        ViewManager.initViewActions(self)
        
    def canCascade(self):
        """
        Public method to signal if cascading of managed windows is available.
        
        @return flag indicating cascading of windows is available
        """
        return 0
        
    def canTile(self):
        """
        Public method to signal if tiling of managed windows is available.
        
        @return flag indicating tiling of windows is available
        """
        return 0
        
    def canSplit(self):
        """
        public method to signal if splitting of the view is available.
        
        @return flag indicating splitting of the view is available.
        """
        return 1
        
    def tile(self):
        """
        Public method to tile the managed windows.
        """
        pass
        
    def cascade(self):
        """
        Public method to cascade the managed windows.
        """
        pass
        
    def removeAllViews(self):
        """
        Private method to remove all views (i.e. windows)
        """
        for win in self.editors:
            self.removeView(win)
            
    def removeView(self, win):
        """
        Private method to remove a view (i.e. window)
        
        @param win editor window to be removed
        """
        self.__inRemoveView = 1
        for tw in self.tabWidgets:
            if tw.hasEditor(win):
                tw.removePage(win)
                break
        win.closeIt()
        self.__inRemoveView = 0
        
        # if this was the last editor in this view, switch to the next, that
        # still has open editors
        for i in range(self.tabWidgets.index(tw), -1, -1) + \
                 range(self.tabWidgets.index(tw) + 1, len(self.tabWidgets)):
            if self.tabWidgets[i].hasEditors():
                self.currentTabWidget.showIndicator(0)
                self.currentTabWidget = self.tabWidgets[i]
                self.currentTabWidget.showIndicator(1)
                self.activeWindow().setFocus()
                break
            
        aw = self.activeWindow()
        fn = aw and aw.getFileName() or None
        if fn:
            self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
            self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
        else:
            self.emit(PYSIGNAL('changeCaption'), ("",))
    
    def addView(self, win, fn=None, noName = ""):
        """
        Private method to add a view (i.e. window)
        
        @param win editor window to be added
        @param fn filename of this editor
        @param noName name to be used for an unnamed editor (string or QString)
        """
        win.show()
        if fn is None:
            if not unicode(noName):
                self.untitledCount += 1
                noName = self.trUtf8("Untitled %1").arg(self.untitledCount)
            self.currentTabWidget.addTab(win, noName)
            win.setNoName(noName)
        else:
            txt = os.path.basename(fn)
            if not QFileInfo(fn).isWritable():
                txt = '%s (ro)' % txt
            self.currentTabWidget.addTab(win, txt)
            self.currentTabWidget.setTabToolTip(win, os.path.dirname(fn))
        self.currentTabWidget.showPage(win)
        win.setFocus()
        if fn:
            self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
            self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
        else:
            self.emit(PYSIGNAL('changeCaption'), ("",))
    
    def showView(self, win, fn=None):
        """
        Private method to show a view (i.e. window)
        
        @param win editor window to be shown
        @param fn filename of this editor
        """
        win.show()
        for tw in self.tabWidgets:
            if tw.hasEditor(win):
                tw.showPage(win)
                self.currentTabWidget.showIndicator(0)
                self.currentTabWidget = tw
                self.currentTabWidget.showIndicator(1)
                break
        win.setFocus()
    
    def activeWindow(self):
        """
        Private method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        return self.currentTabWidget.currentPage()
        
    def handleShowWindowMenu(self, windowMenu):
        """
        Private method to set up the viewmanager part of the Window menu.
        
        @param windowMenu reference to the window menu
        """
        pass
        
    def initWindowActions(self):
        """
        Define the user interface actions for window handling.
        """
        pass
        
    def setEditorName(self, editor, newName):
        """
        Change the displayed name of the editor.
        
        @param editor editor window to be changed
        @param newName new name to be shown (string or QString)
        """
        tabName = os.path.basename(unicode(newName))
        if len(tabName) > self.maxFileNameChars:
            tabName = "...%s" % tabName[-self.maxFileNameChars:]
        self.currentTabWidget.changeTab(editor, tabName)
        self.currentTabWidget.setTabToolTip(editor, newName)
        self.emit(PYSIGNAL('changeCaption'), (unicode(newName),))

    def handleModificationStatusChanged(self, m, editor):
        """
        Private slot to handle the modificationStatusChanged signal.
        
        @param m flag indicating the modification status (boolean)
        @param editor editor window changed
        """
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                break
        if m:
            tw.setTabIconSet(editor, 
                QIconSet(UI.PixmapCache.getPixmap("fileModified.png")))
        elif editor.hasSyntaxErrors():
            tw.setTabIconSet(editor, 
                QIconSet(UI.PixmapCache.getPixmap("syntaxError.png")))
        else:
            tw.setTabIconSet(editor, 
                QIconSet(UI.PixmapCache.getPixmap("empty.png")))
        self.checkActions(editor)
        
    def handleSyntaxErrorToggled(self, editor):
        """
        Private slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                break
        if editor.hasSyntaxErrors():
            tw.setTabIconSet(editor, 
                QIconSet(UI.PixmapCache.getPixmap("syntaxError.png")))
        else:
            tw.setTabIconSet(editor, 
                QIconSet(UI.PixmapCache.getPixmap("empty.png")))
                
        ViewManager.handleSyntaxErrorToggled(self, editor)
        
    def addSplit(self):
        """
        Public method used to split the current view.
        """
        tw = TabWidget(self)
        tw.show()
        self.tabWidgets.append(tw)
        self.currentTabWidget.showIndicator(0)
        self.currentTabWidget = self.tabWidgets[-1]
        self.currentTabWidget.showIndicator(1)
        self.connect(tw, SIGNAL('currentChanged(QWidget*)'),
            self.handleCurrentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        self.setSizes([int(100/len(self.tabWidgets))] * len(self.tabWidgets))
        self.splitRemoveAct.setEnabled(1)
        self.nextSplitAct.setEnabled(1)
        self.prevSplitAct.setEnabled(1)
        
    def removeSplit(self):
        """
        Public method used to remove the current split view.
        
        @return flag indicating successfull removal
        """
        if len(self.tabWidgets) > 1:
            tw = self.currentTabWidget
            res = 1
            savedEditors = tw.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                try:
                    i = self.tabWidgets.index(tw)
                except ValueError:
                    return 1
                if i == len(self.tabWidgets)-1:
                    i -= 1
                self.tabWidgets.remove(tw)
                tw.close(1)
                self.currentTabWidget = self.tabWidgets[i]
                self.currentTabWidget.showIndicator(1)
                if len(self.tabWidgets) == 1:
                    self.splitRemoveAct.setEnabled(0)
                    self.nextSplitAct.setEnabled(0)
                    self.prevSplitAct.setEnabled(0)
                return 1
                
        return 0
        
    def setSplitOrientation(self, orientation):
        """
        Public method used to set the orientation of the split view.
        
        @param orientation orientation of the split
                (QSplitter.Horizontal or QSplitter.Vertical)
        """
        self.setOrientation(orientation)
        
    def nextSplit(self):
        """
        Public slot used to move to the next split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.tabWidgets.index(self.currentTabWidget) + 1
        if ind == len(self.tabWidgets):
            ind = 0
        
        self.currentTabWidget.showIndicator(0)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(1)
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        
    def prevSplit(self):
        """
        Public slot used to move to the previous split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.tabWidgets.index(self.currentTabWidget) - 1
        if ind == -1:
            ind = len(self.tabWidgets) - 1
        
        self.currentTabWidget.showIndicator(0)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(1)
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        
    def handleCurrentChanged(self, editor):
        """
        Private slot to handle the currentChanged signal.
        
        @param editor selected editor window
        """
        if not self.editors:
            return
        
        self.checkActions(editor)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
            if not self.__inRemoveView:
                self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
        else:
            self.emit(PYSIGNAL('changeCaption'), ("",))
        
    def eventFilter(self, watched, event):
        """
        Method called to filter the event queue.
        
        @param watched the QObject being watched
        @param event the event that occurred
        @return always 0
        """
        if event.type() == QEvent.MouseButtonPress and \
           not event.button() == Qt.RightButton:
            self.currentTabWidget.showIndicator(0)
            if isinstance(watched, E3TabWidget):
                switched = watched is not self.currentTabWidget
                self.currentTabWidget = watched
            elif isinstance(watched, QTabBar):
                switched = watched.parent() is not self.currentTabWidget
                self.currentTabWidget = watched.parent()
                if switched:
                    tab = self.currentTabWidget._tabBar.selectTab(event.pos())
                    i = 0
                    while i < self.currentTabWidget._tabBar.count():
                        if self.currentTabWidget._tabBar.tabAt(i) == tab:
                            switched = self.currentTabWidget.page(i) is self.activeWindow()
                            break
                        i += 1
            elif isinstance(watched, QScintilla.Editor.Editor):
                for tw in self.tabWidgets:
                    if tw.hasEditor(watched):
                        switched = tw is not self.currentTabWidget
                        self.currentTabWidget = tw
                        break
            self.currentTabWidget.showIndicator(1)
            
            aw = self.activeWindow()
            if aw is not None:
                self.checkActions(aw)
                aw.setFocus()
                fn = aw.getFileName()
                if fn:
                    self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
                    if switched:
                        self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
                else:
                    self.emit(PYSIGNAL('changeCaption'), ("",))
            
        return 0
