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

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

"""
Module implementing the listspace viewmanager class.
"""

import os

from qt import *

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

class WidgetStack(QWidgetStack):
    """
    Class implementing a custimized WidgetStack.
    """
    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        QWidgetStack.__init__(self, parent)
        
        self.editors = []
        
    def addWidget(self, editor, id = -1):
        """
        Overwritten method to add a new widget.
        
        @param editor the editor object to be added (QScintilla.Editor.Editor)
        @param id id of the widget (integer)
        """
        QWidgetStack.addWidget(self, editor, id)
        if not editor in self.editors:
            self.editors.append(editor)
        
    def removeWidget(self, object):
        """
        Overwritten method to remove a widget.
        
        @param object object to be removed (QObject)
        """
        QWidgetStack.removeWidget(self, object)
        if isinstance(object, QScintilla.Editor.Editor):
            self.editors.remove(object)
            
    def raiseWidget(self, widgetOrId):
        """
        Overwritten method to raise a widget.
        
        @param widgetOrId widget to be raised or its id (QWidget or integer)
        """
        if isinstance(widgetOrId, QScintilla.Editor.Editor):
            self.editors.remove(widgetOrId)
            self.editors.insert(0, widgetOrId)
        QWidgetStack.raiseWidget(self, widgetOrId)
        
    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 firstEditor(self):
        """
        Public method to retrieve the first editor in the list of managed editors.
        
        @return first editor in list (QScintilla.Editor.Editor)
        """
        return len(self.editors) and self.editors[0] or None
        
class Listspace(QSplitter, ViewManager):
    """
    Class implementing the listspace viewmanager class.
    
    @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.stacks = []
        
        QSplitter.__init__(self,parent)
        ViewManager.__init__(self, ui, dbs)
        self.viewlist = QListBox(self)
        self.setResizeMode(self.viewlist, QSplitter.KeepSize)
        self.stackArea = QSplitter(self)
        self.stackArea.setOrientation(QSplitter.Vertical)
        stack = WidgetStack(self.stackArea)
        self.stacks.append(stack)
        self.currentStack = stack
        stack.installEventFilter(self)
        self.connect(self.viewlist, SIGNAL("highlighted(int)"),
                     self.__showViewByIndex)
        self.connect(self.viewlist, SIGNAL("selected(QListBoxItem*)"),
                     self.__showSelectedView)
        self.connect(self.viewlist, SIGNAL("clicked(QListBoxItem*)"),
                     self.__showSelectedView)

        self.initMenu()
        self.viewlist.installEventFilter(self)
        self.contextMenuEditor = None
        
    def initMenu(self):
        """
        Private method to initialize the viewlist 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 viewlist context menu.
        """
        if self.contextMenuEditor:
            self.menu.setItemEnabled(self.saveMenuId, self.contextMenuEditor.isModified())
        self.menu.setItemEnabled(self.projectMenuId, qApp.mainWidget().getProject().isOpen())
    
    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 i in range(self.viewlist.count() - 1, -1, -1):
            self.viewlist.removeItem(i)
        for win in self.editors:
            for stack in self.stacks:
                if stack.hasEditor(win):
                    stack.removeWidget(win)
                    break
            self.disconnect(win, PYSIGNAL('captionChanged'),
                self.handleCaptionChange)
            win.closeIt()
            
    def removeView(self, win):
        """
        Private method to remove a view (i.e. window)
        
        @param win editor window to be removed
        """
        ind = self.editors.index(win)
        self.disconnect(win, PYSIGNAL('captionChanged'),
            self.handleCaptionChange)
        self.viewlist.removeItem(ind)
        for stack in self.stacks:
            if stack.hasEditor(win):
                break
        stack.removeWidget(win)
        win.closeIt()
        if ind > 0:
            ind -= 1
        else:
            if len(self.editors) > 1:
                ind = 1
            else:
                return
        stack.raiseWidget(stack.firstEditor())
        self.showView(self.editors[ind])
        
        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)
        """
        if fn is None:
            if not unicode(noName):
                self.untitledCount += 1
                noName = self.trUtf8("Untitled %1").arg(self.untitledCount)
            self.viewlist.insertItem(noName, len(self.editors) - 1)
            win.setNoName(noName)
        else:
            txt = os.path.basename(fn)
            if not QFileInfo(fn).isWritable():
                txt = '%s (ro)' % txt
            self.viewlist.insertItem(txt, len(self.editors) - 1)
        self.currentStack.addWidget(win, len(self.editors) - 1)
        self.currentStack.raiseWidget(win)
        self.connect(win, PYSIGNAL('captionChanged'),
            self.handleCaptionChange)

        self.viewlist.setSelected(len(self.editors) - 1, 1)
        win.setFocus()
        if fn:
            self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
            self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
        else:
            self.emit(PYSIGNAL('changeCaption'), ("",))
    
    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:
            self.setEditorName(editor, fn)

    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)
        """
        pass
        
    def nextTab(self):
        """
        Public slot to activate the viewlist giving it the input focus.
        """
        self.viewlist.setFocus()
        
    def showView(self, win, fn=None, changeFocus=True):
        """
        Private method to show a view (i.e. window)
        
        @param win editor window to be shown
        @param fn filename of this editor
        @param changeFocus flag indicating, that the editor should
            receive the focus (boolean)
        """
        for stack in self.stacks:
            if stack.hasEditor(win):
                stack.raiseWidget(win)
                self.currentStack = stack
                break
        self.viewlist.setSelected(self.editors.index(win), 1)
        if changeFocus or not self.viewlist.hasFocus():
            win.setFocus()
        fn = win.getFileName()
        if fn:
            self.emit(PYSIGNAL('changeCaption'), (unicode(fn),))
            self.emit(PYSIGNAL('editorChanged'), (unicode(fn),))
        else:
            self.emit(PYSIGNAL('changeCaption'), ("",))
        
    def __showViewByIndex(self, index, changeFocus=False):
        """
        Internal slot called to show a view (i.e. window) selected in the list.
        
        @param index index of the selected entry (int)
        @param changeFocus flag indicating, that the editor should
            receive the focus (boolean)
        """
        self.showView(self.editors[index], changeFocus=changeFocus)
        self.checkActions(self.editors[index])

    def __showSelectedView(self, itm):
        """
        Internal slot called to show a view selected in the list by a mouse click.
        
        @param itm item clicked on (QListBoxItem)
        """
        if itm:
            index = self.viewlist.index(itm)
            self.__showViewByIndex(index, True)
        
    def activeWindow(self):
        """
        Private method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        return self.currentStack.visibleWidget()
        
    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)
        """
        oldidx = self.viewlist.currentItem()
        idx = self.editors.index(editor)
        txt = os.path.basename(unicode(newName))
        if not QFileInfo(newName).isWritable():
            txt = '%s (ro)' % txt
        self.viewlist.changeItem(txt, idx)
        self.viewlist.setCurrentItem(oldidx)
        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
        """
        oldidx = self.viewlist.currentItem()
        idx = self.editors.index(editor)
        if m:
            self.viewlist.changeItem(UI.PixmapCache.getPixmap("fileModified.png"), 
                self.viewlist.text(idx), idx)
        elif editor.hasSyntaxErrors():
            self.viewlist.changeItem(UI.PixmapCache.getPixmap("syntaxError.png"), 
                self.viewlist.text(idx), idx)
        else:
            self.viewlist.changeItem(self.viewlist.text(idx), idx)
        self.viewlist.setCurrentItem(oldidx)
        self.checkActions(editor)
        
    def handleSyntaxErrorToggled(self, editor):
        """
        Private slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        oldidx = self.viewlist.currentItem()
        idx = self.editors.index(editor)
        if editor.hasSyntaxErrors():
            self.viewlist.changeItem(UI.PixmapCache.getPixmap("syntaxError.png"), 
                self.viewlist.text(idx), idx)
        else:
            self.viewlist.changeItem(self.viewlist.text(idx), idx)
                
        ViewManager.handleSyntaxErrorToggled(self, editor)
        
    def addSplit(self):
        """
        Public method used to split the current view.
        """
        stack = WidgetStack(self.stackArea)
        stack.show()
        self.stacks.append(stack)
        self.currentStack = stack
        stack.installEventFilter(self)
        self.stackArea.setSizes([int(100/len(self.stacks))] * len(self.stacks))
        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.stacks) > 1:
            stack = self.currentStack
            res = 1
            savedEditors = stack.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                i = self.stacks.index(stack)
                if i == len(self.stacks)-1:
                    i -= 1
                self.stacks.remove(stack)
                stack.close(1)
                self.currentStack = self.stacks[i]
                if len(self.stacks) == 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.stackArea.setOrientation(orientation)
        
    def nextSplit(self):
        """
        Public slot used to move to the next split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) + 1
        if ind == len(self.stacks):
            ind = 0
        
        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        id = self.currentStack.id(self.currentStack.visibleWidget())
        self.viewlist.setSelected(id, 1)
        
    def prevSplit(self):
        """
        Public slot used to move to the previous split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) - 1
        if ind == -1:
            ind = len(self.stacks) - 1
        
        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        id = self.currentStack.id(self.currentStack.visibleWidget())
        self.viewlist.setSelected(id, 1)
        
    def handleContextClose(self):
        """
        Private method to close the selected tab.
        """
        if self.contextMenuEditor:
            self.handleCloseEditor(self.contextMenuEditor)
        
    def handleContextCloseAll(self):
        """
        Private method to close all tabs.
        """
        savedEditors = self.editors[:]
        for editor in savedEditors:
            self.handleCloseEditor(editor)
        
    def handleContextSave(self):
        """
        Private method to save the selected tab.
        """
        if self.contextMenuEditor:
            self.saveEditorEd(self.contextMenuEditor)
        
    def handleContextSaveAs(self):
        """
        Private method to save the selected tab to a new file.
        """
        if self.contextMenuEditor:
            self.saveAsEditorEd(self.contextMenuEditor)
        
    def handleContextSaveAll(self):
        """
        Private method to save all tabs.
        """
        self.saveEditorsList(self.editors)
        
    def handleContextSaveToProject(self):
        """
        Private method to save the selected tab to the current project.
        """
        if self.contextMenuEditor:
            self.saveEditorToProjectEd(self.contextMenuEditor)
        
    def handleContextPrintFile(self):
        """
        Private method to print the selected tab.
        """
        if self.contextMenuEditor:
            self.printEditor(self.contextMenuEditor)
        
    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 flag indicating, if we handled the event
        """
        if event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.RightButton:
                if watched is self.viewlist:
                    itm = self.viewlist.itemAt(event.pos())
                    if itm is not None:
                        index = self.viewlist.index(itm)
                        self.contextMenuEditor = self.editors[index]
                        if self.contextMenuEditor:
                            self.menu.exec_loop(self.viewlist.mapToGlobal(event.pos()))
                        return 1
            else:
                if watched is not self.viewlist:
                    if isinstance(watched, QWidgetStack):
                        switched = watched is not self.currentStack
                        self.currentStack = watched
                    elif isinstance(watched, QScintilla.Editor.Editor):
                        for stack in self.stacks:
                            if stack.hasEditor(watched):
                                switched = stack is not self.currentStack
                                self.currentStack = stack
                                break
                    id = self.currentStack.id(self.currentStack.visibleWidget())
                    self.viewlist.setSelected(id, 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
