Plugins/ViewManagerPlugins/Tabview/Tabview.py

Wed, 01 Jan 2014 14:40:41 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 01 Jan 2014 14:40:41 +0100
branch
5_3_x
changeset 3163
9f50365a0870
parent 2825
323d417d256b
permissions
-rw-r--r--

Updated copyright for 2014.

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

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

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

import os

from PyQt4.QtCore import QPoint, QFileInfo, pyqtSignal, QEvent, QByteArray, QMimeData, Qt
from PyQt4.QtGui import QWidget, QColor, QHBoxLayout, QDrag, QPixmap, QSplitter, \
    QTabBar, QApplication, QToolButton, QMenu, QLabel

from E5Gui.E5Application import e5App

from ViewManager.ViewManager import ViewManager

import QScintilla.Editor
from QScintilla.Editor import Editor

import UI.PixmapCache

from E5Gui.E5TabWidget import E5TabWidget, E5WheelTabBar
from E5Gui.E5Led import E5Led

import Preferences
from Globals import isMacPlatform

from eric5config import getConfig


class TabBar(E5WheelTabBar):
    """
    Class implementing a customized tab bar supporting drag & drop.
    
    @signal tabMoveRequested(int, int) emitted to signal a tab move request giving
        the old and new index position
    @signal tabRelocateRequested(str, int, int) emitted to signal a tab relocation
        request giving the string encoded id of the old tab widget, the index in
        the old tab widget and the new index position
    @signal tabCopyRequested(str, int, int) emitted to signal a clone request
        giving the string encoded id of the source tab widget, the index in the
        source tab widget and the new index position
    @signal tabCopyRequested(int, int) emitted to signal a clone request giving
        the old and new index position
    """
    tabMoveRequested = pyqtSignal(int, int)
    tabRelocateRequested = pyqtSignal(str, int, int)
    tabCopyRequested = pyqtSignal((str, int, int), (int, int))
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super().__init__(parent)
        self.setAcceptDrops(True)
        
        self.__dragStartPos = QPoint()
    
    def mousePressEvent(self, event):
        """
        Protected method to handle mouse press events.
        
        @param event reference to the mouse press event (QMouseEvent)
        """
        if event.button() == Qt.LeftButton:
            self.__dragStartPos = QPoint(event.pos())
        super().mousePressEvent(event)
    
    def mouseMoveEvent(self, event):
        """
        Protected method to handle mouse move events.
        
        @param event reference to the mouse move event (QMouseEvent)
        """
        if event.buttons() == Qt.MouseButtons(Qt.LeftButton) and \
           (event.pos() - self.__dragStartPos).manhattanLength() > \
                QApplication.startDragDistance():
            drag = QDrag(self)
            mimeData = QMimeData()
            index = self.tabAt(event.pos())
            mimeData.setText(self.tabText(index))
            mimeData.setData("action", "tab-reordering")
            mimeData.setData("tabbar-id", str(id(self)))
            mimeData.setData("source-index",
                             QByteArray.number(self.tabAt(self.__dragStartPos)))
            mimeData.setData("tabwidget-id", str(id(self.parentWidget())))
            drag.setMimeData(mimeData)
            if event.modifiers() == Qt.KeyboardModifiers(Qt.ShiftModifier):
                drag.exec_(Qt.DropActions(Qt.CopyAction))
            elif event.modifiers() == Qt.KeyboardModifiers(Qt.NoModifier):
                drag.exec_(Qt.DropActions(Qt.MoveAction))
        super().mouseMoveEvent(event)
    
    def dragEnterEvent(self, event):
        """
        Protected method to handle drag enter events.
        
        @param event reference to the drag enter event (QDragEnterEvent)
        """
        mimeData = event.mimeData()
        formats = mimeData.formats()
        if "action" in formats and \
           mimeData.data("action") == "tab-reordering" and \
           "tabbar-id" in formats and \
           "source-index" in formats and \
           "tabwidget-id" in formats:
            event.acceptProposedAction()
        super().dragEnterEvent(event)
    
    def dropEvent(self, event):
        """
        Protected method to handle drop events.
        
        @param event reference to the drop event (QDropEvent)
        """
        mimeData = event.mimeData()
        oldID = int(mimeData.data("tabbar-id"))
        fromIndex = mimeData.data("source-index").toInt()[0]
        toIndex = self.tabAt(event.pos())
        if oldID != id(self):
            parentID = int(mimeData.data("tabwidget-id"))
            if event.proposedAction() == Qt.MoveAction:
                self.tabRelocateRequested.emit(str(parentID), fromIndex, toIndex)
                event.acceptProposedAction()
            elif event.proposedAction() == Qt.CopyAction:
                self.tabCopyRequested[str, int, int].emit(
                    str(parentID), fromIndex, toIndex)
                event.acceptProposedAction()
        else:
            if fromIndex != toIndex:
                if event.proposedAction() == Qt.MoveAction:
                    self.tabMoveRequested.emit(fromIndex, toIndex)
                    event.acceptProposedAction()
                elif event.proposedAction() == Qt.CopyAction:
                    self.tabCopyRequested[int, int].emit(fromIndex, toIndex)
                    event.acceptProposedAction()
        super().dropEvent(event)


class TabWidget(E5TabWidget):
    """
    Class implementing a custimized tab widget.
    """
    def __init__(self, vm):
        """
        Constructor
        
        @param vm view manager widget (Tabview)
        """
        super().__init__()
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        
        self.__tabBar = TabBar(self)
        self.setTabBar(self.__tabBar)
        
        self.setUsesScrollButtons(True)
        self.setElideMode(Qt.ElideNone)
        if isMacPlatform():
            self.setDocumentMode(True)
        
        self.__tabBar.tabMoveRequested.connect(self.moveTab)
        self.__tabBar.tabRelocateRequested.connect(self.__relocateTab)
        self.__tabBar.tabCopyRequested[str, int, int].connect(self.__copyTabOther)
        self.__tabBar.tabCopyRequested[int, int].connect(self.__copyTab)
        
        self.vm = vm
        self.editors = []
        
        self.indicator = E5Led(self)
        self.setCornerWidget(self.indicator, Qt.TopLeftCorner)
        
        self.rightCornerWidget = QWidget(self)
        self.rightCornerWidgetLayout = QHBoxLayout(self.rightCornerWidget)
        self.rightCornerWidgetLayout.setContentsMargins(0, 0, 0, 0)
        self.rightCornerWidgetLayout.setSpacing(0)
        
        self.__navigationMenu = QMenu(self)
        self.__navigationMenu.aboutToShow.connect(self.__showNavigationMenu)
        self.__navigationMenu.triggered.connect(self.__navigationMenuTriggered)
        
        self.navigationButton = QToolButton(self)
        self.navigationButton.setIcon(UI.PixmapCache.getIcon("1downarrow.png"))
        self.navigationButton.setToolTip(self.trUtf8("Show a navigation menu"))
        self.navigationButton.setPopupMode(QToolButton.InstantPopup)
        self.navigationButton.setMenu(self.__navigationMenu)
        self.navigationButton.setEnabled(False)
        self.rightCornerWidgetLayout.addWidget(self.navigationButton)
        
        if Preferences.getUI("SingleCloseButton") or \
           not hasattr(self, 'setTabsClosable'):
            self.closeButton = QToolButton(self)
            self.closeButton.setIcon(UI.PixmapCache.getIcon("close.png"))
            self.closeButton.setToolTip(self.trUtf8("Close the current editor"))
            self.closeButton.setEnabled(False)
            self.closeButton.clicked[bool].connect(self.__closeButtonClicked)
            self.rightCornerWidgetLayout.addWidget(self.closeButton)
        else:
            self.tabCloseRequested.connect(self.__closeRequested)
            self.closeButton = None
        
        self.setCornerWidget(self.rightCornerWidget, Qt.TopRightCorner)
        
        self.__initMenu()
        self.contextMenuEditor = None
        self.contextMenuIndex = -1
        
        self.setTabContextMenuPolicy(Qt.CustomContextMenu)
        self.customTabContextMenuRequested.connect(self.__showContextMenu)
        
        ericPic = QPixmap(os.path.join(getConfig('ericPixDir'), 'eric_small.png'))
        self.emptyLabel = QLabel()
        self.emptyLabel.setPixmap(ericPic)
        self.emptyLabel.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
        super().addTab(self.emptyLabel, UI.PixmapCache.getIcon("empty.png"), "")
        
    def __initMenu(self):
        """
        Private method to initialize the tab context menu.
        """
        self.__menu = QMenu(self)
        self.leftMenuAct = \
            self.__menu.addAction(UI.PixmapCache.getIcon("1leftarrow.png"),
            self.trUtf8('Move Left'), self.__contextMenuMoveLeft)
        self.rightMenuAct = \
            self.__menu.addAction(UI.PixmapCache.getIcon("1rightarrow.png"),
            self.trUtf8('Move Right'), self.__contextMenuMoveRight)
        self.firstMenuAct = \
            self.__menu.addAction(UI.PixmapCache.getIcon("2leftarrow.png"),
            self.trUtf8('Move First'), self.__contextMenuMoveFirst)
        self.lastMenuAct = \
            self.__menu.addAction(UI.PixmapCache.getIcon("2rightarrow.png"),
            self.trUtf8('Move Last'), self.__contextMenuMoveLast)
        self.__menu.addSeparator()
        self.__menu.addAction(UI.PixmapCache.getIcon("tabClose.png"),
            self.trUtf8('Close'), self.__contextMenuClose)
        self.closeOthersMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("tabCloseOther.png"), self.trUtf8("Close Others"),
            self.__contextMenuCloseOthers)
        self.__menu.addAction(self.trUtf8('Close All'), self.__contextMenuCloseAll)
        self.__menu.addSeparator()
        self.saveMenuAct = \
            self.__menu.addAction(UI.PixmapCache.getIcon("fileSave.png"),
            self.trUtf8('Save'), self.__contextMenuSave)
        self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"),
            self.trUtf8('Save As...'), self.__contextMenuSaveAs)
        self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAll.png"),
            self.trUtf8('Save All'), self.__contextMenuSaveAll)
        self.__menu.addSeparator()
        self.openRejectionsMenuAct = \
            self.__menu.addAction(self.trUtf8("Open 'rejection' file"),
            self.__contextMenuOpenRejections)
        self.__menu.addSeparator()
        self.__menu.addAction(UI.PixmapCache.getIcon("print.png"),
            self.trUtf8('Print'), self.__contextMenuPrintFile)
        self.__menu.addSeparator()
        self.copyPathAct = self.__menu.addAction(self.trUtf8("Copy Path to Clipboard"),
            self.__contextMenuCopyPathToClipboard)
        
    def __showContextMenu(self, coord, index):
        """
        Private slot to show the tab context menu.
        
        @param coord the position of the mouse pointer (QPoint)
        @param index index of the tab the menu is requested for (integer)
        """
        if self.editors:
            self.contextMenuEditor = self.widget(index).getEditor()
            if self.contextMenuEditor:
                self.saveMenuAct.setEnabled(self.contextMenuEditor.isModified())
                fileName = self.contextMenuEditor.getFileName()
                self.copyPathAct.setEnabled(bool(fileName))
                if fileName:
                    rej = "{0}.rej".format(fileName)
                    self.openRejectionsMenuAct.setEnabled(os.path.exists(rej))
                else:
                    self.openRejectionsMenuAct.setEnabled(False)
            
            self.contextMenuIndex = index
            self.leftMenuAct.setEnabled(index > 0)
            self.rightMenuAct.setEnabled(index < self.count() - 1)
            self.firstMenuAct.setEnabled(index > 0)
            self.lastMenuAct.setEnabled(index < self.count() - 1)
            
            self.closeOthersMenuAct.setEnabled(self.count() > 1)
            
            coord = self.mapToGlobal(coord)
            self.__menu.popup(coord)
        
    def __showNavigationMenu(self):
        """
        Private slot to show the navigation button menu.
        """
        self.__navigationMenu.clear()
        for index in range(self.count()):
            act = self.__navigationMenu.addAction(self.tabIcon(index),
                                                  self.tabText(index))
            act.setData(index)
        
    def __navigationMenuTriggered(self, act):
        """
        Private slot called to handle the navigation button menu selection.
        
        @param act reference to the selected action (QAction)
        """
        index = act.data()
        if index is not None:
            self.setCurrentIndex(index)
        
    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.setColor(QColor("green"))
        else:
            self.indicator.setColor(QColor("red"))
        
    def addTab(self, assembly, title):
        """
        Overwritten method to add a new tab.
        
        @param assembly editor assembly object to be added
            (QScintilla.EditorAssembly.EditorAssembly)
        @param title title for the new tab (string)
        """
        editor = assembly.getEditor()
        super().addTab(assembly, UI.PixmapCache.getIcon("empty.png"), title)
        if self.closeButton:
            self.closeButton.setEnabled(True)
        else:
            self.setTabsClosable(True)
        self.navigationButton.setEnabled(True)
        
        if not editor in self.editors:
            self.editors.append(editor)
            editor.captionChanged.connect(self.__captionChange)
        
        emptyIndex = self.indexOf(self.emptyLabel)
        if emptyIndex > -1:
            self.removeTab(emptyIndex)
        
    def insertWidget(self, index, assembly, title):
        """
        Overwritten method to insert a new tab.
        
        @param index index position for the new tab (integer)
        @param assembly editor assembly object to be added
            (QScintilla.EditorAssembly.EditorAssembly)
        @param title title for the new tab (string)
        @return index of the inserted tab (integer)
        """
        editor = assembly.getEditor()
        newIndex = super().insertTab(index, assembly,
                                     UI.PixmapCache.getIcon("empty.png"),
                                     title)
        if self.closeButton:
            self.closeButton.setEnabled(True)
        else:
            self.setTabsClosable(True)
        self.navigationButton.setEnabled(True)
        
        if not editor in self.editors:
            self.editors.append(editor)
            editor.captionChanged.connect(self.__captionChange)
        
        emptyIndex = self.indexOf(self.emptyLabel)
        if emptyIndex > -1:
            self.removeTab(emptyIndex)
        
        return newIndex
        
    def __captionChange(self, cap, editor):
        """
        Private method to handle Caption change signals from the editor.
        
        Updates the tab text and tooltip 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:
            if Preferences.getUI("TabViewManagerFilenameOnly"):
                txt = os.path.basename(fn)
            else:
                txt = e5App().getObject("Project").getRelativePath(fn)
            
            maxFileNameChars = Preferences.getUI("TabViewManagerFilenameLength")
            if len(txt) > maxFileNameChars:
                txt = "...{0}".format(txt[-maxFileNameChars:])
            if editor.isReadOnly():
                txt = self.trUtf8("{0} (ro)").format(txt)
            
            assembly = editor.parent()
            index = self.indexOf(assembly)
            if index > -1:
                self.setTabText(index, txt)
                self.setTabToolTip(index, fn)
        
    def removeWidget(self, object):
        """
        Public method to remove a widget.
        
        @param object object to be removed (QWidget)
        """
        if isinstance(object, QScintilla.Editor.Editor):
            object.captionChanged.disconnect(self.__captionChange)
            self.editors.remove(object)
            index = self.indexOf(object.parent())
        else:
            index = self.indexOf(object)
        if index > -1:
            self.removeTab(index)
        
        if not self.editors:
            super().addTab(self.emptyLabel, UI.PixmapCache.getIcon("empty.png"), "")
            self.emptyLabel.show()
            if self.closeButton:
                self.closeButton.setEnabled(False)
            else:
                self.setTabsClosable(False)
            self.navigationButton.setEnabled(False)
        
    def __relocateTab(self, sourceId, sourceIndex, targetIndex):
        """
        Private method to relocate an editor from another TabWidget.
        
        @param sourceId id of the TabWidget to get the editor from (string)
        @param sourceIndex index of the tab in the old tab widget (integer)
        @param targetIndex index position to place it to (integer)
        """
        tw = self.vm.getTabWidgetById(int(sourceId))
        if tw is not None:
            # step 1: get data of the tab of the source
            toolTip = tw.tabToolTip(sourceIndex)
            text = tw.tabText(sourceIndex)
            icon = tw.tabIcon(sourceIndex)
            whatsThis = tw.tabWhatsThis(sourceIndex)
            assembly = tw.widget(sourceIndex)
            
            # step 2: relocate the tab
            tw.removeWidget(assembly.getEditor())
            self.insertWidget(targetIndex, assembly, text)
            
            # step 3: set the tab data again
            self.setTabIcon(targetIndex, icon)
            self.setTabToolTip(targetIndex, toolTip)
            self.setTabWhatsThis(targetIndex, whatsThis)
            
            # step 4: set current widget
            self.setCurrentIndex(targetIndex)
        
    def __copyTabOther(self, sourceId, sourceIndex, targetIndex):
        """
        Private method to copy an editor from another TabWidget.
        
        @param sourceId id of the TabWidget to get the editor from (string)
        @param sourceIndex index of the tab in the old tab widget (integer)
        @param targetIndex index position to place it to (integer)
        """
        tw = self.vm.getTabWidgetById(int(sourceId))
        if tw is not None:
            editor = tw.widget(sourceIndex).getEditor()
            newEditor, assembly = self.vm.cloneEditor(editor, editor.getFileType(),
                                            editor.getFileName())
            self.vm.insertView(assembly, self, targetIndex,
                               editor.getFileName(), editor.getNoName())
        
    def __copyTab(self, sourceIndex, targetIndex):
        """
        Private method to copy an editor.
        
        @param sourceIndex index of the tab (integer)
        @param targetIndex index position to place it to (integer)
        """
        editor = self.widget(sourceIndex).getEditor()
        newEditor, assembly = self.vm.cloneEditor(editor, editor.getFileType(),
                                        editor.getFileName())
        self.vm.insertView(assembly, self, targetIndex,
                           editor.getFileName(), editor.getNoName())
        
    def currentWidget(self):
        """
        Overridden method to return a reference to the current page.
        
        @return reference to the current page (Editor)
        """
        if not self.editors:
            return None
        else:
            return super().currentWidget()
        
    def setCurrentWidget(self, assembly):
        """
        Public method to set the current tab by the given editor assembly.
        
        @param assembly editor assembly to determine current tab from
            (EditorAssembly.EditorAssembly)
        """
        super().setCurrentWidget(assembly)
        
    def indexOf(self, object):
        """
        Public method to get the tab index of the given editor.
        
        @param object object to get the index for (QLabel or Editor)
        @return tab index of the editor (integer)
        """
        if isinstance(object, QScintilla.Editor.Editor):
            object = object.parent()
        return super().indexOf(object)
        
    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) > 0
        
    def __contextMenuClose(self):
        """
        Private method to close the selected tab.
        """
        if self.contextMenuEditor:
            self.vm.closeEditorWindow(self.contextMenuEditor)
        
    def __contextMenuCloseOthers(self):
        """
        Private method to close the other tabs.
        """
        index = self.contextMenuIndex
        for i in list(range(self.count() - 1, index, -1)) + \
                 list(range(index - 1, -1, -1)):
            editor = self.widget(i).getEditor()
            self.vm.closeEditorWindow(editor)
        
    def __contextMenuCloseAll(self):
        """
        Private method to close all tabs.
        """
        savedEditors = self.editors[:]
        for editor in savedEditors:
            self.vm.closeEditorWindow(editor)
        
    def __contextMenuSave(self):
        """
        Private method to save the selected tab.
        """
        if self.contextMenuEditor:
            self.vm.saveEditorEd(self.contextMenuEditor)
        
    def __contextMenuSaveAs(self):
        """
        Private method to save the selected tab to a new file.
        """
        if self.contextMenuEditor:
            self.vm.saveAsEditorEd(self.contextMenuEditor)
        
    def __contextMenuSaveAll(self):
        """
        Private method to save all tabs.
        """
        self.vm.saveEditorsList(self.editors)
        
    def __contextMenuOpenRejections(self):
        """
        Private slot to open a rejections file associated with the selected tab.
        """
        if self.contextMenuEditor:
            fileName = self.contextMenuEditor.getFileName()
            if fileName:
                rej = "{0}.rej".format(fileName)
                if os.path.exists(rej):
                    self.vm.openSourceFile(rej)
        
    def __contextMenuPrintFile(self):
        """
        Private method to print the selected tab.
        """
        if self.contextMenuEditor:
            self.vm.printEditor(self.contextMenuEditor)
    
    def __contextMenuCopyPathToClipboard(self):
        """
        Private method to copy the file name of the selected tab to the clipboard.
        """
        if self.contextMenuEditor:
            fn = self.contextMenuEditor.getFileName()
            if fn:
                cb = QApplication.clipboard()
                cb.setText(fn)
        
    def __contextMenuMoveLeft(self):
        """
        Private method to move a tab one position to the left.
        """
        self.moveTab(self.contextMenuIndex, self.contextMenuIndex - 1)
        
    def __contextMenuMoveRight(self):
        """
        Private method to move a tab one position to the right.
        """
        self.moveTab(self.contextMenuIndex, self.contextMenuIndex + 1)
        
    def __contextMenuMoveFirst(self):
        """
        Private method to move a tab to the first position.
        """
        self.moveTab(self.contextMenuIndex, 0)
        
    def __contextMenuMoveLast(self):
        """
        Private method to move a tab to the last position.
        """
        self.moveTab(self.contextMenuIndex, self.count() - 1)
        
    def __closeButtonClicked(self):
        """
        Private method to handle the press of the close button.
        """
        self.vm.closeEditorWindow(self.currentWidget().getEditor())
        
    def __closeRequested(self, index):
        """
        Private method to handle the press of the individual tab close button.
        
        @param index index of the tab (integer)
        """
        if index >= 0:
            self.vm.closeEditorWindow(self.widget(index).getEditor())
        
    def mouseDoubleClickEvent(self, event):
        """
        Protected method handling double click events.
        
        @param event reference to the event object (QMouseEvent)
        """
        self.vm.newEditor()


class Tabview(QSplitter, ViewManager):
    """
    Class implementing a tabbed viewmanager class embedded in a splitter.
    
    @signal changeCaption(str) emitted if a change of the caption is necessary
    @signal editorChanged(str) emitted when the current editor has changed
    @signal lastEditorClosed() emitted after the last editor window was closed
    @signal editorOpened(str) emitted after an editor window was opened
    @signal editorOpenedEd(Editor) emitted after an editor window was opened
    @signal editorClosed(str) emitted just before an editor window gets closed
    @signal editorClosedEd(Editor) emitted just before an editor window gets closed
    @signal editorSaved(str) emitted after an editor window was saved
    @signal checkActions(Editor) emitted when some actions should be checked
            for their status
    @signal cursorChanged(Editor) emitted after the cursor position of the active
            window has changed
    @signal breakpointToggled(Editor) emitted when a breakpoint is toggled.
    @signal bookmarkToggled(Editor) emitted when a bookmark is toggled.
    @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled.
    """
    changeCaption = pyqtSignal(str)
    editorChanged = pyqtSignal(str)
    
    lastEditorClosed = pyqtSignal()
    editorOpened = pyqtSignal(str)
    editorOpenedEd = pyqtSignal(Editor)
    editorClosed = pyqtSignal(str)
    editorClosedEd = pyqtSignal(Editor)
    editorSaved = pyqtSignal(str)
    checkActions = pyqtSignal(Editor)
    cursorChanged = pyqtSignal(Editor)
    breakpointToggled = pyqtSignal(Editor)
    bookmarkToggled = pyqtSignal(Editor)
    syntaxerrorToggled = pyqtSignal(Editor)
    
    def __init__(self, parent):
        """
        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)
        tw = TabWidget(self)
        self.addWidget(tw)
        self.tabWidgets.append(tw)
        self.currentTabWidget = tw
        self.currentTabWidget.showIndicator(True)
        tw.currentChanged.connect(self.__currentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        self.setOrientation(Qt.Vertical)
        self.__inRemoveView = False
        
        self.maxFileNameChars = Preferences.getUI("TabViewManagerFilenameLength")
        self.filenameOnly = Preferences.getUI("TabViewManagerFilenameOnly")
        
    def canCascade(self):
        """
        Public method to signal if cascading of managed windows is available.
        
        @return flag indicating cascading of windows is available
        """
        return False
        
    def canTile(self):
        """
        Public method to signal if tiling of managed windows is available.
        
        @return flag indicating tiling of windows is available
        """
        return False
        
    def canSplit(self):
        """
        public method to signal if splitting of the view is available.
        
        @return flag indicating splitting of the view is available.
        """
        return True
        
    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):
        """
        Protected method to remove all views (i.e. windows)
        """
        for win in self.editors:
            self._removeView(win)
        
    def _removeView(self, win):
        """
        Protected method to remove a view (i.e. window)
        
        @param win editor window to be removed
        """
        self.__inRemoveView = True
        for tw in self.tabWidgets:
            if tw.hasEditor(win):
                tw.removeWidget(win)
                break
        win.closeIt()
        self.__inRemoveView = False
        
        # if this was the last editor in this view, switch to the next, that
        # still has open editors
        for i in list(range(self.tabWidgets.index(tw), -1, -1)) + \
                 list(range(self.tabWidgets.index(tw) + 1, len(self.tabWidgets))):
            if self.tabWidgets[i].hasEditors():
                self.currentTabWidget.showIndicator(False)
                self.currentTabWidget = self.tabWidgets[i]
                self.currentTabWidget.showIndicator(True)
                self.activeWindow().setFocus()
                break
        
        aw = self.activeWindow()
        fn = aw and aw.getFileName() or None
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
        else:
            self.changeCaption.emit("")
        
    def _addView(self, win, fn=None, noName=""):
        """
        Protected method to add a view (i.e. window)
        
        @param win editor assembly to be added
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.trUtf8("Untitled {0}").format(self.untitledCount)
            self.currentTabWidget.addTab(win, noName)
            editor.setNoName(noName)
        else:
            if self.filenameOnly:
                txt = os.path.basename(fn)
            else:
                txt = e5App().getObject("Project").getRelativePath(fn)
            if len(txt) > self.maxFileNameChars:
                txt = "...{0}".format(txt[-self.maxFileNameChars:])
            if not QFileInfo(fn).isWritable():
                txt = self.trUtf8("{0} (ro)").format(txt)
            self.currentTabWidget.addTab(win, txt)
            index = self.currentTabWidget.indexOf(win)
            self.currentTabWidget.setTabToolTip(index, fn)
        self.currentTabWidget.setCurrentWidget(win)
        win.show()
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
        else:
            self.changeCaption.emit("")
        
    def insertView(self, win, tabWidget, index, fn=None, noName=""):
        """
        Protected method to add a view (i.e. window)
        
        @param win editor assembly to be inserted
        @param tabWidget reference to the tab widget to insert the editor into (TabWidget)
        @param index index position to insert at (integer)
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.trUtf8("Untitled {0}").format(self.untitledCount)
            tabWidget.insertWidget(index, win, noName)
            editor.setNoName(noName)
        else:
            if self.filenameOnly:
                txt = os.path.basename(fn)
            else:
                txt = e5App().getObject("Project").getRelativePath(fn)
            if len(txt) > self.maxFileNameChars:
                txt = "...{0}".format(txt[-self.maxFileNameChars:])
            if not QFileInfo(fn).isWritable():
                txt = self.trUtf8("{0} (ro)").format(txt)
            nindex = tabWidget.insertWidget(index, win, txt)
            tabWidget.setTabToolTip(nindex, fn)
        tabWidget.setCurrentWidget(win)
        win.show()
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
        else:
            self.changeCaption.emit("")
        
        self._modificationStatusChanged(editor.isModified(), editor)
        self._checkActions(editor)
        
    def _showView(self, win, fn=None):
        """
        Protected method to show a view (i.e. window)
        
        @param win editor assembly to be shown
        @param fn filename of this editor (string)
        """
        win.show()
        editor = win.getEditor()
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                tw.setCurrentWidget(win)
                self.currentTabWidget.showIndicator(False)
                self.currentTabWidget = tw
                self.currentTabWidget.showIndicator(True)
                break
        editor.setFocus()
        
    def activeWindow(self):
        """
        Public method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        cw = self.currentTabWidget.currentWidget()
        if cw:
            return cw.getEditor()
        else:
            return None
        
    def showWindowMenu(self, windowMenu):
        """
        Public method to set up the viewmanager part of the Window menu.
        
        @param windowMenu reference to the window menu
        """
        pass
        
    def _initWindowActions(self):
        """
        Protected method to define the user interface actions for window handling.
        """
        pass
        
    def setEditorName(self, editor, newName):
        """
        Public method to change the displayed name of the editor.
        
        @param editor editor window to be changed
        @param newName new name to be shown (string)
        """
        if newName:
            if self.filenameOnly:
                tabName = os.path.basename(newName)
            else:
                tabName = e5App().getObject("Project").getRelativePath(newName)
            if len(tabName) > self.maxFileNameChars:
                tabName = "...{0}".format(tabName[-self.maxFileNameChars:])
            index = self.currentTabWidget.indexOf(editor)
            self.currentTabWidget.setTabText(index, tabName)
            self.currentTabWidget.setTabToolTip(index, newName)
            self.changeCaption.emit(newName)

    def _modificationStatusChanged(self, m, editor):
        """
        Protected 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
        index = tw.indexOf(editor)
        if m:
            tw.setTabIcon(index, UI.PixmapCache.getIcon("fileModified.png"))
        elif editor.hasSyntaxErrors():
            tw.setTabIcon(index, UI.PixmapCache.getIcon("syntaxError.png"))
        elif editor.hasFlakesWarnings():
            tw.setTabIcon(index, UI.PixmapCache.getIcon("warning.png"))
        else:
            tw.setTabIcon(index, UI.PixmapCache.getIcon("empty.png"))
        self._checkActions(editor)
        
    def _syntaxErrorToggled(self, editor):
        """
        Protected slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                break
        index = tw.indexOf(editor)
        if editor.hasSyntaxErrors():
            tw.setTabIcon(index, UI.PixmapCache.getIcon("syntaxError.png"))
        elif editor.hasFlakesWarnings():
            tw.setTabIcon(index, UI.PixmapCache.getIcon("warning.png"))
        elif editor.isModified():
            tw.setTabIcon(index, UI.PixmapCache.getIcon("fileModified.png"))
        else:
            tw.setTabIcon(index, UI.PixmapCache.getIcon("empty.png"))
        
        ViewManager._syntaxErrorToggled(self, editor)
        
    def addSplit(self):
        """
        Public method used to split the current view.
        """
        tw = TabWidget(self)
        tw.show()
        self.addWidget(tw)
        self.tabWidgets.append(tw)
        self.currentTabWidget.showIndicator(False)
        self.currentTabWidget = self.tabWidgets[-1]
        self.currentTabWidget.showIndicator(True)
        tw.currentChanged.connect(self.__currentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        if self.orientation() == Qt.Horizontal:
            size = self.width()
        else:
            size = self.height()
        self.setSizes([int(size / len(self.tabWidgets))] * len(self.tabWidgets))
        self.splitRemoveAct.setEnabled(True)
        self.nextSplitAct.setEnabled(True)
        self.prevSplitAct.setEnabled(True)
        
    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 = True
            savedEditors = tw.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                try:
                    i = self.tabWidgets.index(tw)
                except ValueError:
                    return True
                if i == len(self.tabWidgets) - 1:
                    i -= 1
                self.tabWidgets.remove(tw)
                tw.close()
                self.currentTabWidget = self.tabWidgets[i]
                self.currentTabWidget.showIndicator(True)
                if len(self.tabWidgets) == 1:
                    self.splitRemoveAct.setEnabled(False)
                    self.nextSplitAct.setEnabled(False)
                    self.prevSplitAct.setEnabled(False)
                return True
        
        return False
        
    def setSplitOrientation(self, orientation):
        """
        Public method used to set the orientation of the split view.
        
        @param orientation orientation of the split
                (Qt.Horizontal or Qt.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(False)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(True)
        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(False)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(True)
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        
    def __currentChanged(self, index):
        """
        Private slot to handle the currentChanged signal.
        
        @param index index of the current tab (integer)
        """
        if index == -1 or not self.editors:
            return
        
        editor = self.activeWindow()
        if editor is None:
            return
        
        self._checkActions(editor)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            if not self.__inRemoveView:
                self.editorChanged.emit(fn)
        else:
            self.changeCaption.emit("")
        
    def eventFilter(self, watched, event):
        """
        Public method called to filter the event queue.
        
        @param watched the QObject being watched (QObject)
        @param event the event that occurred (QEvent)
        @return always False
        """
        if event.type() == QEvent.MouseButtonPress and \
           not event.button() == Qt.RightButton:
            self.currentTabWidget.showIndicator(False)
            if isinstance(watched, E5TabWidget):
                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:
                    index = self.currentTabWidget.selectTab(event.pos())
                    switched = self.currentTabWidget.widget(index) is self.activeWindow()
            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(True)
            
            aw = self.activeWindow()
            if aw is not None:
                self._checkActions(aw)
                aw.setFocus()
                fn = aw.getFileName()
                if fn:
                    self.changeCaption.emit(fn)
                    if switched:
                        self.editorChanged.emit(fn)
                else:
                    self.changeCaption.emit("")
        
        return False
        
    def preferencesChanged(self):
        """
        Public slot to handle the preferencesChanged signal.
        """
        ViewManager.preferencesChanged(self)
        
        self.maxFileNameChars = Preferences.getUI("TabViewManagerFilenameLength")
        self.filenameOnly = Preferences.getUI("TabViewManagerFilenameOnly")
        
        for tabWidget in self.tabWidgets:
            for index in range(tabWidget.count()):
                editor = tabWidget.widget(index)
                if isinstance(editor, QScintilla.Editor.Editor):
                    fn = editor.getFileName()
                    if fn:
                        if self.filenameOnly:
                            txt = os.path.basename(fn)
                        else:
                            txt = e5App().getObject("Project").getRelativePath(fn)
                        if len(txt) > self.maxFileNameChars:
                            txt = "...{0}".format(txt[-self.maxFileNameChars:])
                        if not QFileInfo(fn).isWritable():
                            txt = self.trUtf8("{0} (ro)").format(txt)
                        tabWidget.setTabText(index, txt)
        
    def getTabWidgetById(self, id_):
        """
        Public method to get a reference to a tab widget knowing its ID.
        
        @param id_ id of the tab widget (long)
        @return reference to the tab widget (TabWidget)
        """
        for tw in self.tabWidgets:
            if id(tw) == id_:
                return tw
        return None

eric ide

mercurial