Example #1
0
    def addContextMenu(self):
        """ Adds a context menu to the tab bar """

        from pyzo.core.menu import EditorTabContextMenu
        self._menu = EditorTabContextMenu(self, "EditorTabMenu")
        self._tabs.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self._tabs.customContextMenuRequested.connect(
            self.contextMenuTriggered)
Example #2
0
 def addContextMenu(self):
     """ Adds a context menu to the tab bar """
     
     from pyzo.core.menu import EditorTabContextMenu
     self._menu = EditorTabContextMenu(self, "EditorTabMenu")
     self._tabs.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
     self._tabs.customContextMenuRequested.connect(self.contextMenuTriggered)    
Example #3
0
class EditorTabs(QtGui.QWidget):
    """ The EditorTabs instance manages the open files and corresponding
    editors. It does the saving loading etc.
    """

    # Signal to indicate that a breakpoint has changed, emits dict
    breakPointsChanged = QtCore.Signal(object)

    # Signal to notify that a different file was selected
    currentChanged = QtCore.Signal()

    # Signal to notify that the parser has parsed the text (emit by parser)
    parserDone = QtCore.Signal()

    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)

        # keep a booking of opened directories
        self._lastpath = ''

        # keep track of all breakpoints
        self._breakPoints = {}

        # create tab widget
        self._tabs = FileTabWidget(self)
        self._tabs.tabCloseRequested.connect(self.closeFile)
        self._tabs.currentChanged.connect(self.onCurrentChanged)

        # Double clicking a tab saves the file, clicking on the bar opens a new file
        self._tabs.tabBar().tabDoubleClicked.connect(self.saveFile)
        self._tabs.tabBar().barDoubleClicked.connect(self.newFile)

        # Create find/replace widget
        self._findReplace = FindReplaceWidget(self)

        # create box layout control and add widgets
        self._boxLayout = QtGui.QVBoxLayout(self)
        self._boxLayout.addWidget(self._tabs, 1)
        self._boxLayout.addWidget(self._findReplace, 0)
        # spacing of widgets
        self._boxLayout.setSpacing(0)
        # apply
        self.setLayout(self._boxLayout)

        #self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips,True)

        # accept drops
        self.setAcceptDrops(True)

        # restore state (call later so that the menu module can bind to the
        # currentChanged signal first, in order to set tab/indentation
        # checkmarks appropriately)
        # todo: Resetting the scrolling would work better if set after
        # the widgets are properly sized.
        pyzo.callLater(self.restoreEditorState)

    def addContextMenu(self):
        """ Adds a context menu to the tab bar """

        from pyzo.core.menu import EditorTabContextMenu
        self._menu = EditorTabContextMenu(self, "EditorTabMenu")
        self._tabs.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self._tabs.customContextMenuRequested.connect(
            self.contextMenuTriggered)

    def contextMenuTriggered(self, p):
        """ Called when context menu is clicked """

        # Get index of current tab
        index = self._tabs.tabBar().tabAt(p)
        self._menu.setIndex(index)

        # Show menu if item is available
        if index >= 0:
            p = self._tabs.tabBar().tabRect(index).bottomLeft()
            self._menu.popup(self._tabs.tabBar().mapToGlobal(p))

    def onCurrentChanged(self):
        self.currentChanged.emit()

    def getCurrentEditor(self):
        """ Get the currently active editor. """
        item = self._tabs.currentItem()
        if item:
            return item.editor
        else:
            return None

    def getMainEditor(self):
        """ Get the editor that represents the main file, or None if
        there is no main file. """
        item = self._tabs.mainItem()
        if item:
            return item.editor
        else:
            return None

    def __iter__(self):
        tmp = [item.editor for item in self._tabs.items()]
        return tmp.__iter__()

    def updateBreakPoints(self, editor=None):
        # Get list of editors to update keypoints for
        if editor is None:
            editors = self
            self._breakPoints = {}  # Full reset
        else:
            editors = [editor]

        # Update our keypoints dict
        for editor in editors:
            fname = editor._filename or editor._name
            if not fname:
                continue
            linenumbers = editor.breakPoints()
            if linenumbers:
                self._breakPoints[fname] = linenumbers
            else:
                self._breakPoints.pop(fname, None)

        # Emit signal so shells can update the kernel
        self.breakPointsChanged.emit(self._breakPoints)

    def setDebugLineIndicators(self, *filename_linenr):
        """ Set the debug line indicator. There is one indicator
        global to pyzo, corresponding to the last shell for which we
        received the indicator.
        """
        if len(filename_linenr) and filename_linenr[0] is None:
            filename_linenr = []

        # Normalize case
        filename_linenr = [(os.path.normcase(i[0]), int(i[1]))
                           for i in filename_linenr]

        for item in self._tabs.items():
            # Prepare
            editor = item._editor
            fname = editor._filename or editor._name
            fname = os.path.normcase(fname)
            # Reset
            editor.setDebugLineIndicator(None)
            # Set
            for filename, linenr in filename_linenr:
                if fname == filename:
                    active = (filename, linenr) == filename_linenr[-1]
                    editor.setDebugLineIndicator(linenr, active)

    ## Loading ad saving files

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    def dropEvent(self, event):
        """ Drop files in the list. """
        for qurl in event.mimeData().urls():
            path = str(qurl.toLocalFile())
            if os.path.isfile(path):
                self.loadFile(path)
            elif os.path.isdir(path):
                self.loadDir(path)
            else:
                pass

    def newFile(self):
        """ Create a new (unsaved) file. """

        # create editor
        editor = createEditor(self, None)
        editor.document().setModified(False)  # Start out as OK
        # add to list
        item = FileItem(editor)
        self._tabs.addItem(item)
        self._tabs.setCurrentItem(item)
        # set focus to new file
        editor.setFocus()

        return item

    def openFile(self):
        """ Create a dialog for the user to select a file. """

        # determine start dir
        # todo: better selection of dir, using project manager
        editor = self.getCurrentEditor()
        if editor and editor._filename:
            startdir = os.path.split(editor._filename)[0]
        else:
            startdir = self._lastpath
        if (not startdir) or (not os.path.isdir(startdir)):
            startdir = ''

        # show dialog
        msg = translate("editorTabs", "Select one or more files to open")
        filter = "Python (*.py *.pyw);;"
        filter += "Pyrex (*.pyi *.pyx *.pxd);;"
        filter += "C (*.c *.h *.cpp *.c++);;"
        #filter += "Py+Cy+C (*.py *.pyw *.pyi *.pyx *.pxd *.c *.h *.cpp);;"
        filter += "All (*)"
        if True:
            filenames = QtGui.QFileDialog.getOpenFileNames(
                self, msg, startdir, filter)
            if isinstance(filenames, tuple):  # PySide
                filenames = filenames[0]
        else:
            # Example how to preselect files, can be used when the users
            # opens a file in a project to select all files currently not
            # loaded.
            d = QtGui.QFileDialog(self, msg, startdir, filter)
            d.setFileMode(d.ExistingFiles)
            d.selectFile('"codeparser.py" "editorStack.py"')
            d.exec_()
            if d.result():
                filenames = d.selectedFiles()
            else:
                filenames = []

        # were some selected?
        if not filenames:
            return

        # load
        for filename in filenames:
            self.loadFile(filename)

    def openDir(self):
        """ Create a dialog for the user to select a directory. """

        # determine start dir
        editor = self.getCurrentEditor()
        if editor and editor._filename:
            startdir = os.path.split(editor._filename)[0]
        else:
            startdir = self._lastpath
        if not os.path.isdir(startdir):
            startdir = ''

        # show dialog
        msg = "Select a directory to open"
        dirname = QtGui.QFileDialog.getExistingDirectory(self, msg, startdir)

        # was a dir selected?
        if not dirname:
            return

        # load
        self.loadDir(dirname)

    def loadFile(self, filename, updateTabs=True):
        """ Load the specified file. 
        On success returns the item of the file, also if it was
        already open."""

        # Note that by giving the name of a tempfile, we can select that
        # temp file.

        # normalize path
        if filename[0] != '<':
            filename = normalizePath(filename)
        if not filename:
            return None

        # if the file is already open...
        for item in self._tabs.items():
            if item.id == filename:
                # id gets _filename or _name for temp files
                break
        else:
            item = None
        if item:
            self._tabs.setCurrentItem(item)
            print("File already open: '{}'".format(filename))
            return item

        # create editor
        try:
            editor = createEditor(self, filename)
        except Exception as err:
            # Notify in logger
            print("Error loading file: ", err)
            # Make sure the user knows
            m = QtGui.QMessageBox(self)
            m.setWindowTitle("Error loading file")
            m.setText(str(err))
            m.setIcon(m.Warning)
            m.exec_()
            return None

        # create list item
        item = FileItem(editor)
        self._tabs.addItem(item, updateTabs)
        if updateTabs:
            self._tabs.setCurrentItem(item)

        # store the path
        self._lastpath = os.path.dirname(item.filename)

        return item

    def loadDir(self, path):
        """ Create a project with the dir's name and add all files
        contained in the directory to it.
        extensions is a komma separated list of extenstions of files
        to accept...        
        """

        # if the path does not exist, stop
        path = os.path.abspath(path)
        if not os.path.isdir(path):
            print("ERROR loading dir: the specified directory does not exist!")
            return

        # get extensions
        extensions = pyzo.config.advanced.fileExtensionsToLoadFromDir
        extensions = extensions.replace(',', ' ').replace(';', ' ')
        extensions = [
            "." + a.lstrip(".").strip() for a in extensions.split(" ")
        ]

        # init item
        item = None

        # open all qualified files...
        self._tabs.setUpdatesEnabled(False)
        try:
            filelist = os.listdir(path)
            for filename in filelist:
                filename = os.path.join(path, filename)
                ext = os.path.splitext(filename)[1]
                if str(ext) in extensions:
                    item = self.loadFile(filename, False)
        finally:
            self._tabs.setUpdatesEnabled(True)
            self._tabs.updateItems()

        # return lastopened item
        return item

    def saveFileAs(self, editor=None):
        """ Create a dialog for the user to select a file. 
        returns: True if succesfull, False if fails
        """

        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
        if editor is None:
            return False

        # get startdir
        if editor._filename:
            startdir = os.path.dirname(editor._filename)
        else:
            startdir = self._lastpath
            # Try the file browser or project manager to suggest a path
            fileBrowser = pyzo.toolManager.getTool('pyzofilebrowser')
            projectManager = pyzo.toolManager.getTool('pyzoprojectmanager')
            if fileBrowser:
                startdir = fileBrowser.getDefaultSavePath()
            if projectManager and not startdir:
                startdir = projectManager.getDefaultSavePath()

        if not os.path.isdir(startdir):
            startdir = ''

        # show dialog
        msg = translate("editorTabs", "Select the file to save to")
        filter = "Python (*.py *.pyw);;"
        filter += "Pyrex (*.pyi *.pyx *.pxd);;"
        filter += "C (*.c *.h *.cpp);;"
        #filter += "Py+Cy+C (*.py *.pyw *.pyi *.pyx *.pxd *.c *.h *.cpp);;"
        filter += "All (*.*)"
        filename = QtGui.QFileDialog.getSaveFileName(self, msg, startdir,
                                                     filter)
        if isinstance(filename, tuple):  # PySide
            filename = filename[0]

        # give python extension if it has no extension
        head, tail = os.path.split(filename)
        if tail and '.' not in tail:
            filename += '.py'

        # proceed or cancel
        if filename:
            return self.saveFile(editor, filename)
        else:
            return False  # Cancel was pressed

    def saveFile(self, editor=None, filename=None):
        """ Save the file. 
        returns: True if succesfull, False if fails
        """

        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
        elif isinstance(editor, int):
            index = editor
            editor = None
            if index >= 0:
                item = self._tabs.items()[index]
                editor = item.editor
        if editor is None:
            return False

        # get filename
        if filename is None:
            filename = editor._filename
        if not filename:
            return self.saveFileAs(editor)

        # let the editor do the low level stuff...
        try:
            editor.save(filename)
        except Exception as err:
            # Notify in logger
            print("Error saving file:", err)
            # Make sure the user knows
            m = QtGui.QMessageBox(self)
            m.setWindowTitle("Error saving file")
            m.setText(str(err))
            m.setIcon(m.Warning)
            m.exec_()
            # Return now
            return False

        # get actual normalized filename
        filename = editor._filename

        # notify
        # TODO: message concerining line endings
        print("saved file: {} ({})".format(filename,
                                           editor.lineEndingsHumanReadable))
        self._tabs.updateItems()

        # todo: this is where we once detected whether the file being saved was a style file.

        # Notify done
        return True

    def saveAllFiles(self):
        """ Save all files"""
        for editor in self:
            self.saveFile(editor)

    ## Closing files / closing down

    def askToSaveFileIfDirty(self, editor):
        """ askToSaveFileIfDirty(editor)
        
        If the given file is not saved, pop up a dialog
        where the user can save the file
        . 
        Returns 1 if file need not be saved.
        Returns 2 if file was saved.
        Returns 3 if user discarded changes.
        Returns 0 if cancelled.
        
        """

        # should we ask to save the file?
        if editor.document().isModified():

            # Ask user what to do
            result = simpleDialog(editor, "Closing", "Save modified file?",
                                  ['Discard', 'Cancel', 'Save'], 'Save')
            result = result.lower()

            # Get result and act
            if result == 'save':
                return 2 if self.saveFile(editor) else 0
            elif result == 'discard':
                return 3
            else:  # cancel
                return 0

        return 1

    def closeFile(self, editor=None):
        """ Close the selected (or current) editor. 
        Returns same result as askToSaveFileIfDirty() """

        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
            item = self._tabs.currentItem()
        elif isinstance(editor, int):
            index = editor
            editor, item = None, None
            if index >= 0:
                item = self._tabs.items()[index]
                editor = item.editor
        else:
            item = None
            for i in self._tabs.items():
                if i.editor is editor:
                    item = i
        if editor is None or item is None:
            return

        # Ask if dirty
        result = self.askToSaveFileIfDirty(editor)

        # Ask if closing pinned file
        if result and item.pinned:
            result = simpleDialog(
                editor, "Closing pinned",
                "Are you sure you want to close this pinned file?",
                ['Close', 'Cancel'], 'Cancel')
            result = result == 'Close'

        # ok, close...
        if result:
            if editor._name.startswith("<tmp"):
                # Temp file, try to find its index
                for i in range(len(self._tabs.items())):
                    if self._tabs.getItemAt(i).editor is editor:
                        self._tabs.removeTab(i)
                        break
            else:
                self._tabs.removeTab(editor)

        # Clear any breakpoints that it may have had
        self.updateBreakPoints()

        return result

    def closeAllFiles(self):
        """Close all files"""
        for editor in self:
            self.closeFile(editor)

    def saveEditorState(self):
        """ Save the editor's state configuration.
        """
        fr = self._findReplace
        pyzo.config.state.find_matchCase = fr._caseCheck.isChecked()
        pyzo.config.state.find_regExp = fr._regExp.isChecked()
        pyzo.config.state.find_wholeWord = fr._wholeWord.isChecked()
        pyzo.config.state.find_show = fr.isVisible()
        #
        pyzo.config.state.editorState2 = self._getCurrentOpenFilesAsSsdfList()

    def restoreEditorState(self):
        """ Restore the editor's state configuration.
        """

        # Restore opened editors
        if pyzo.config.state.editorState2:
            self._setCurrentOpenFilesAsSsdfList(pyzo.config.state.editorState2)
        else:
            self.newFile()

        # The find/replace state is set in the corresponding class during init

    def _getCurrentOpenFilesAsSsdfList(self):
        """ Get the state as it currently is as an ssdf list.
        The state entails all open files and their structure in the
        projects. The being collapsed of projects and their main files.
        The position of the cursor in the editors.
        """

        # Init
        state = []

        # Get items
        for item in self._tabs.items():

            # Get editor
            ed = item.editor
            if not ed._filename:
                continue

            # Init info
            info = []
            # Add filename, line number, and scroll distance
            info.append(ed._filename)
            info.append(int(ed.textCursor().position()))
            info.append(int(ed.verticalScrollBar().value()))
            # Add whether pinned or main file
            if item.pinned:
                info.append('pinned')
            if item.id == self._tabs._mainFile:
                info.append('main')

            # Add to state
            state.append(tuple(info))

        # Get history
        history = [item for item in self._tabs._itemHistory]
        history.reverse()  # Last one is current
        for item in history:
            if isinstance(item, FileItem):
                ed = item._editor
                if ed._filename:
                    state.append((ed._filename, 'hist'))

        # Done
        return state

    def _setCurrentOpenFilesAsSsdfList(self, state):
        """ Set the state of the editor in terms of opened files.
        The input should be a list object as returned by 
        ._getCurrentOpenFilesAsSsdfList().
        """

        # Init dict
        fileItems = {}

        # Process items
        for item in state:
            fname = item[0]
            if item[1] == 'hist':
                # select item (to make the history right)
                if fname in fileItems:
                    self._tabs.setCurrentItem(fileItems[fname])
            elif fname:
                # a file item, create editor-item and store
                itm = self.loadFile(fname)
                fileItems[fname] = itm
                # set position
                if itm:
                    try:
                        ed = itm.editor
                        cursor = ed.textCursor()
                        cursor.setPosition(int(item[1]))
                        ed.setTextCursor(cursor)
                        # set scrolling
                        ed.verticalScrollBar().setValue(int(item[2]))
                        #ed.centerCursor() #TODO: this does not work properly yet
                        # set main and/or pinned?
                        if 'main' in item:
                            self._tabs._mainFile = itm.id
                        if 'pinned' in item:
                            itm._pinned = True
                    except Exception as err:
                        print('Could not set position for %s' % fname, err)

    def closeAll(self):
        """ Close all files (well technically, we don't really close them,
        so that they are all stil there when the user presses cancel).
        Returns False if the user pressed cancel when asked for
        saving an unsaved file. 
        """

        # try closing all editors.
        for editor in self:
            result = self.askToSaveFileIfDirty(editor)
            if not result:
                return False

        # we're good to go closing
        return True
Example #4
0
class EditorTabs(QtGui.QWidget):
    """ The EditorTabs instance manages the open files and corresponding
    editors. It does the saving loading etc.
    """ 
    
    # Signal to indicate that a breakpoint has changed, emits dict
    breakPointsChanged = QtCore.Signal(object)
    
    # Signal to notify that a different file was selected
    currentChanged = QtCore.Signal()
    
    # Signal to notify that the parser has parsed the text (emit by parser)
    parserDone = QtCore.Signal()
    
    
    def __init__(self, parent):
        QtGui.QWidget.__init__(self,parent)
        
        # keep a booking of opened directories
        self._lastpath = ''
        
        # keep track of all breakpoints
        self._breakPoints = {}
        
        # create tab widget
        self._tabs = FileTabWidget(self)       
        self._tabs.tabCloseRequested.connect(self.closeFile)
        self._tabs.currentChanged.connect(self.onCurrentChanged)
        
        # Double clicking a tab saves the file, clicking on the bar opens a new file
        self._tabs.tabBar().tabDoubleClicked.connect(self.saveFile)
        self._tabs.tabBar().barDoubleClicked.connect(self.newFile)
        
        # Create find/replace widget
        self._findReplace = FindReplaceWidget(self)
        
        # create box layout control and add widgets
        self._boxLayout = QtGui.QVBoxLayout(self)
        self._boxLayout.addWidget(self._tabs, 1)
        self._boxLayout.addWidget(self._findReplace, 0)
        # spacing of widgets
        self._boxLayout.setSpacing(0)
        # apply
        self.setLayout(self._boxLayout)
        
        #self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips,True)
        
        # accept drops
        self.setAcceptDrops(True)
        
        # restore state (call later so that the menu module can bind to the
        # currentChanged signal first, in order to set tab/indentation
        # checkmarks appropriately)
        # todo: Resetting the scrolling would work better if set after
        # the widgets are properly sized.
        pyzo.callLater(self.restoreEditorState)
    
    
    def addContextMenu(self):
        """ Adds a context menu to the tab bar """
        
        from pyzo.core.menu import EditorTabContextMenu
        self._menu = EditorTabContextMenu(self, "EditorTabMenu")
        self._tabs.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self._tabs.customContextMenuRequested.connect(self.contextMenuTriggered)    
    
    
    def contextMenuTriggered(self, p):
        """ Called when context menu is clicked """
        
        # Get index of current tab
        index = self._tabs.tabBar().tabAt(p)
        self._menu.setIndex(index)
        
        # Show menu if item is available
        if index >= 0:
            p = self._tabs.tabBar().tabRect(index).bottomLeft()
            self._menu.popup(self._tabs.tabBar().mapToGlobal(p))
    
    
    def onCurrentChanged(self):
        self.currentChanged.emit()
    
    
    def getCurrentEditor(self):
        """ Get the currently active editor. """
        item = self._tabs.currentItem()
        if item:
            return item.editor
        else:
            return None
    
    
    def getMainEditor(self):
        """ Get the editor that represents the main file, or None if
        there is no main file. """
        item = self._tabs.mainItem()
        if item:
            return item.editor
        else:
            return None
    
    
    def __iter__(self):
        tmp = [item.editor for item in self._tabs.items()]
        return tmp.__iter__()
    
    
    def updateBreakPoints(self, editor=None):
        # Get list of editors to update keypoints for
        if editor is None:
            editors = self
            self._breakPoints = {}  # Full reset
        else:
            editors = [editor]
        
        # Update our keypoints dict
        for editor in editors:
            fname = editor._filename or editor._name
            if not fname:
                continue
            linenumbers = editor.breakPoints()
            if linenumbers:
                self._breakPoints[fname] = linenumbers
            else:
                self._breakPoints.pop(fname, None)
        
        # Emit signal so shells can update the kernel
        self.breakPointsChanged.emit(self._breakPoints)
    
    
    def setDebugLineIndicators(self, *filename_linenr):
        """ Set the debug line indicator. There is one indicator
        global to pyzo, corresponding to the last shell for which we
        received the indicator.
        """
        if len(filename_linenr) and filename_linenr[0] is None:
            filename_linenr = []
        
        # Normalize case
        filename_linenr = [(os.path.normcase(i[0]), int(i[1])) for i in filename_linenr]
        
        for item in self._tabs.items():
            # Prepare
            editor = item._editor
            fname = editor._filename or editor._name
            fname = os.path.normcase(fname)
            # Reset
            editor.setDebugLineIndicator(None)
            # Set
            for filename, linenr in filename_linenr:
                if fname == filename:
                    active = (filename, linenr) == filename_linenr[-1]
                    editor.setDebugLineIndicator(linenr, active)
            

    ## Loading ad saving files
    
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
    
    def dropEvent(self, event):
        """ Drop files in the list. """
        for qurl in event.mimeData().urls():
            path = str( qurl.toLocalFile() )
            if os.path.isfile(path):
                self.loadFile(path)
            elif os.path.isdir(path):
                self.loadDir(path)
            else:
                pass
    
    
    def newFile(self):
        """ Create a new (unsaved) file. """
        
        # create editor
        editor = createEditor(self, None)
        editor.document().setModified(False)  # Start out as OK
        # add to list
        item = FileItem(editor)
        self._tabs.addItem(item)
        self._tabs.setCurrentItem(item)
        # set focus to new file
        editor.setFocus()

        return item
    
    
    def openFile(self):
        """ Create a dialog for the user to select a file. """
        
        # determine start dir
        # todo: better selection of dir, using project manager
        editor = self.getCurrentEditor()
        if editor and editor._filename:
            startdir = os.path.split(editor._filename)[0]
        else:
            startdir = self._lastpath            
        if (not startdir) or (not os.path.isdir(startdir)):
            startdir = ''
        
        # show dialog
        msg = translate("editorTabs", "Select one or more files to open")
        filter =  "Python (*.py *.pyw);;"
        filter += "Pyrex (*.pyi *.pyx *.pxd);;"
        filter += "C (*.c *.h *.cpp *.c++);;"
        #filter += "Py+Cy+C (*.py *.pyw *.pyi *.pyx *.pxd *.c *.h *.cpp);;"
        filter += "All (*)"
        if True:
            filenames = QtGui.QFileDialog.getOpenFileNames(self,
                msg, startdir, filter)
            if isinstance(filenames, tuple): # PySide
                filenames = filenames[0]
        else:
            # Example how to preselect files, can be used when the users
            # opens a file in a project to select all files currently not
            # loaded.
            d = QtGui.QFileDialog(self, msg, startdir, filter)
            d.setFileMode(d.ExistingFiles)
            d.selectFile('"codeparser.py" "editorStack.py"')
            d.exec_()
            if d.result():
                filenames = d.selectedFiles()
            else:
                filenames = []
        
        # were some selected?
        if not filenames:
            return
        
        # load
        for filename in filenames:
            self.loadFile(filename)
    
    
    def openDir(self):
        """ Create a dialog for the user to select a directory. """
        
        # determine start dir
        editor = self.getCurrentEditor()
        if editor and editor._filename:
            startdir = os.path.split(editor._filename)[0]
        else:
            startdir = self._lastpath            
        if not os.path.isdir(startdir):
            startdir = ''
        
        # show dialog
        msg = "Select a directory to open"
        dirname = QtGui.QFileDialog.getExistingDirectory(self, msg, startdir)
        
        # was a dir selected?
        if not dirname:
            return
        
        # load
        self.loadDir(dirname)
    
    
    def loadFile(self, filename, updateTabs=True):
        """ Load the specified file. 
        On success returns the item of the file, also if it was
        already open."""
        
        # Note that by giving the name of a tempfile, we can select that
        # temp file.
        
        # normalize path
        if filename[0] != '<':
            filename = normalizePath(filename)
        if not filename:
            return None
        
        # if the file is already open...
        for item in self._tabs.items():
            if item.id == filename:
                # id gets _filename or _name for temp files
                break
        else:
            item = None
        if item:
            self._tabs.setCurrentItem(item)
            print("File already open: '{}'".format(filename))
            return item
        
        # create editor
        try:
            editor = createEditor(self, filename)
        except Exception as err:
            # Notify in logger
            print("Error loading file: ", err)
            # Make sure the user knows
            m = QtGui.QMessageBox(self)
            m.setWindowTitle("Error loading file")
            m.setText(str(err))
            m.setIcon(m.Warning)
            m.exec_()
            return None
        
        # create list item
        item = FileItem(editor)
        self._tabs.addItem(item, updateTabs)        
        if updateTabs:
            self._tabs.setCurrentItem(item)
        
        # store the path
        self._lastpath = os.path.dirname(item.filename)
        
        return item
    
    
    def loadDir(self, path):
        """ Create a project with the dir's name and add all files
        contained in the directory to it.
        extensions is a komma separated list of extenstions of files
        to accept...        
        """
        
        # if the path does not exist, stop     
        path = os.path.abspath(path)   
        if not os.path.isdir(path):
            print("ERROR loading dir: the specified directory does not exist!")
            return
        
        # get extensions
        extensions = pyzo.config.advanced.fileExtensionsToLoadFromDir
        extensions = extensions.replace(',',' ').replace(';',' ')
        extensions = ["."+a.lstrip(".").strip() for a in extensions.split(" ")]
        
        # init item
        item = None
        
        # open all qualified files...
        self._tabs.setUpdatesEnabled(False)
        try:
            filelist = os.listdir(path)
            for filename in filelist:
                filename = os.path.join(path, filename)
                ext = os.path.splitext(filename)[1]            
                if str(ext) in extensions:
                    item = self.loadFile(filename, False)
        finally:
            self._tabs.setUpdatesEnabled(True)
            self._tabs.updateItems()
        
        # return lastopened item
        return item
    
    
    def saveFileAs(self, editor=None):
        """ Create a dialog for the user to select a file. 
        returns: True if succesfull, False if fails
        """
        
        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
        if editor is None:
            return False
        
        # get startdir
        if editor._filename:
            startdir = os.path.dirname(editor._filename)
        else:
            startdir = self._lastpath 
            # Try the file browser or project manager to suggest a path
            fileBrowser = pyzo.toolManager.getTool('pyzofilebrowser')
            projectManager = pyzo.toolManager.getTool('pyzoprojectmanager')
            if fileBrowser:
                startdir = fileBrowser.getDefaultSavePath()
            if projectManager and not startdir:
                startdir = projectManager.getDefaultSavePath()
        
        if not os.path.isdir(startdir):
            startdir = ''
        
        # show dialog
        msg = translate("editorTabs", "Select the file to save to")
        filter =  "Python (*.py *.pyw);;"
        filter += "Pyrex (*.pyi *.pyx *.pxd);;"
        filter += "C (*.c *.h *.cpp);;"
        #filter += "Py+Cy+C (*.py *.pyw *.pyi *.pyx *.pxd *.c *.h *.cpp);;"
        filter += "All (*.*)"
        filename = QtGui.QFileDialog.getSaveFileName(self,
            msg, startdir, filter)
        if isinstance(filename, tuple): # PySide
            filename = filename[0]
        
        # give python extension if it has no extension
        head, tail = os.path.split(filename)
        if tail and '.' not in tail:
            filename += '.py'
        
        # proceed or cancel
        if filename:
            return self.saveFile(editor, filename)
        else:
            return False # Cancel was pressed
    
    
    def saveFile(self, editor=None, filename=None):
        """ Save the file. 
        returns: True if succesfull, False if fails
        """
        
        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
        elif isinstance(editor, int):
            index = editor
            editor = None
            if index>=0:
                item = self._tabs.items()[index]
                editor = item.editor
        if editor is None:
            return False
        
        # get filename
        if filename is None:
            filename = editor._filename
        if not filename:
            return self.saveFileAs(editor)

        
        # let the editor do the low level stuff...
        try:
            editor.save(filename)
        except Exception as err:
            # Notify in logger
            print("Error saving file:",err)
            # Make sure the user knows
            m = QtGui.QMessageBox(self)
            m.setWindowTitle("Error saving file")
            m.setText(str(err))
            m.setIcon(m.Warning)
            m.exec_()
            # Return now            
            return False
        
        # get actual normalized filename
        filename = editor._filename
        
        # notify
        # TODO: message concerining line endings
        print("saved file: {} ({})".format(filename, editor.lineEndingsHumanReadable))
        self._tabs.updateItems()
        
        # todo: this is where we once detected whether the file being saved was a style file.
        
        # Notify done
        return True
        
    def saveAllFiles(self):
        """ Save all files"""
        for editor in self:
            self.saveFile(editor)
    
    
    ## Closing files / closing down
    
    def askToSaveFileIfDirty(self, editor):
        """ askToSaveFileIfDirty(editor)
        
        If the given file is not saved, pop up a dialog
        where the user can save the file
        . 
        Returns 1 if file need not be saved.
        Returns 2 if file was saved.
        Returns 3 if user discarded changes.
        Returns 0 if cancelled.
        
        """ 
        
        # should we ask to save the file?
        if editor.document().isModified():
            
            # Ask user what to do
            result = simpleDialog(editor, "Closing", "Save modified file?", 
                                    ['Discard', 'Cancel', 'Save'], 'Save')
            result = result.lower()
            
            # Get result and act            
            if result == 'save':
                return 2 if self.saveFile(editor) else 0
            elif result == 'discard':
                return 3
            else: # cancel
                return 0
        
        return 1
    
    
    def closeFile(self, editor=None):
        """ Close the selected (or current) editor. 
        Returns same result as askToSaveFileIfDirty() """
        
        # get editor
        if editor is None:
            editor = self.getCurrentEditor()
            item = self._tabs.currentItem()
        elif isinstance(editor, int):
            index = editor
            editor, item = None, None
            if index>=0:
                item = self._tabs.items()[index]
                editor = item.editor
        else:
            item = None
            for i in self._tabs.items():
                if i.editor is editor:
                    item = i
        if editor is None or item is None:
            return
        
        # Ask if dirty
        result = self.askToSaveFileIfDirty(editor)
        
        # Ask if closing pinned file
        if result and item.pinned:
            result = simpleDialog(editor, "Closing pinned", 
                "Are you sure you want to close this pinned file?",
                ['Close', 'Cancel'], 'Cancel')
            result = result == 'Close'
        
        # ok, close...
        if result:
            if editor._name.startswith("<tmp"):
                # Temp file, try to find its index
                for i in range(len(self._tabs.items())):
                    if self._tabs.getItemAt(i).editor is editor:
                        self._tabs.removeTab(i)
                        break
            else:
                self._tabs.removeTab(editor)
        
        # Clear any breakpoints that it may have had
        self.updateBreakPoints()
        
        return result
    
    def closeAllFiles(self):
        """Close all files"""
        for editor in self:
            self.closeFile(editor)
    
    
    def saveEditorState(self):
        """ Save the editor's state configuration.
        """
        fr = self._findReplace
        pyzo.config.state.find_matchCase = fr._caseCheck.isChecked()
        pyzo.config.state.find_regExp = fr._regExp.isChecked()
        pyzo.config.state.find_wholeWord = fr._wholeWord.isChecked()
        pyzo.config.state.find_show = fr.isVisible()
        #
        pyzo.config.state.editorState2 = self._getCurrentOpenFilesAsSsdfList()
    
    
    def restoreEditorState(self):
        """ Restore the editor's state configuration.
        """
        
        # Restore opened editors
        if pyzo.config.state.editorState2:
            self._setCurrentOpenFilesAsSsdfList(pyzo.config.state.editorState2)
        else:
            self.newFile()
        
        # The find/replace state is set in the corresponding class during init
    
    
    def _getCurrentOpenFilesAsSsdfList(self):
        """ Get the state as it currently is as an ssdf list.
        The state entails all open files and their structure in the
        projects. The being collapsed of projects and their main files.
        The position of the cursor in the editors.
        """
        
        # Init
        state = []
        
        # Get items
        for item in self._tabs.items():
            
            # Get editor
            ed = item.editor
            if not ed._filename:
                continue
            
            # Init info
            info = []
            # Add filename, line number, and scroll distance
            info.append(ed._filename)
            info.append(int(ed.textCursor().position()))
            info.append(int(ed.verticalScrollBar().value()))
            # Add whether pinned or main file
            if item.pinned:
                info.append('pinned')
            if item.id == self._tabs._mainFile:
                info.append('main')
            
            # Add to state
            state.append( tuple(info) )
        
        # Get history
        history = [item for item in self._tabs._itemHistory]
        history.reverse() # Last one is current
        for item in history:
            if isinstance(item, FileItem):
                ed = item._editor
                if ed._filename:
                    state.append( (ed._filename, 'hist') )
        
        # Done
        return state
    
    
    def _setCurrentOpenFilesAsSsdfList(self, state):
        """ Set the state of the editor in terms of opened files.
        The input should be a list object as returned by 
        ._getCurrentOpenFilesAsSsdfList().
        """
        
        # Init dict
        fileItems = {}
        
        # Process items
        for item in state:
            fname = item[0]
            if item[1] == 'hist':
                # select item (to make the history right)
                if fname in fileItems:
                    self._tabs.setCurrentItem( fileItems[fname] )
            elif fname:
                # a file item, create editor-item and store
                itm = self.loadFile(fname)
                fileItems[fname] = itm
                # set position
                if itm:
                    try:
                        ed = itm.editor
                        cursor = ed.textCursor()
                        cursor.setPosition(int(item[1]))
                        ed.setTextCursor(cursor)
                        # set scrolling
                        ed.verticalScrollBar().setValue(int(item[2]))
                        #ed.centerCursor() #TODO: this does not work properly yet
                        # set main and/or pinned?
                        if 'main' in item:
                            self._tabs._mainFile = itm.id
                        if 'pinned' in item:
                            itm._pinned = True
                    except Exception as err:
                        print('Could not set position for %s' % fname, err)
        
    
    def closeAll(self):
        """ Close all files (well technically, we don't really close them,
        so that they are all stil there when the user presses cancel).
        Returns False if the user pressed cancel when asked for
        saving an unsaved file. 
        """
        
        # try closing all editors.
        for editor in self:
            result = self.askToSaveFileIfDirty(editor)
            if not result:
                return False
        
        # we're good to go closing
        return True