###############################################################################
##
## Copyright (C) 2014-2016, New York University.
## Copyright (C) 2011-2014, NYU-Poly.
## Copyright (C) 2006-2011, University of Utah.
## All rights reserved.
## Contact: contact@vistrails.org
##
## This file is part of VisTrails.
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##
##  - Redistributions of source code must retain the above copyright notice,
##    this list of conditions and the following disclaimer.
##  - Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
##  - Neither the name of the New York University nor the names of its
##    contributors may be used to endorse or promote products derived from
##    this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
## OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
## ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
###############################################################################

"""This file implements the Spreadsheet Tab Controller, to manages tabs:
StandardWidgetTabController
"""

from __future__ import division

from ast import literal_eval
import copy
import gc
import os.path
from PyQt4 import QtCore, QtGui

from vistrails.core.db.locator import FileLocator, _DBLocator as DBLocator
from vistrails.core.interpreter.default import get_default_interpreter
from vistrails.core.utils import DummyView
from vistrails.core.utils.uxml import XMLWrapper, named_elements
from vistrails.db.services.io import SaveBundle
from vistrails.gui.theme import CurrentTheme
from vistrails.gui.utils import show_warning

from .spreadsheet_registry import spreadsheetRegistry
from .spreadsheet_tab import StandardWidgetTabBar, StandardWidgetSheetTab, \
    StandardTabDockWidget


class StandardWidgetTabController(QtGui.QTabWidget):
    """
    StandardWidgetTabController inherits from QTabWidget to contain a
    list of StandardWidgetSheetTab. This is the major component that
    will handle most of the spreadsheet actions

    """
    def __init__(self, parent=None):
        """ StandardWidgetTabController(parent: QWidget)
                                        -> StandardWidgetTabController
        Initialize signals/slots and widgets for the tab bar

        """

        QtGui.QTabWidget.__init__(self, parent)
        self.operatingWidget = self
        self.setTabBar(StandardWidgetTabBar(self))
        self.setTabShape(QtGui.QTabWidget.Triangular)
        self.setTabPosition(QtGui.QTabWidget.South)
        self.tabWidgets = []
        self.floatingTabWidgets = []
        self.addTabWidget(StandardWidgetSheetTab(self), 'Sheet 1')
        self.connect(self.tabBar(),
                     QtCore.SIGNAL('tabMoveRequest(int,int)'),
                     self.moveTab)
        self.connect(self.tabBar(),
                     QtCore.SIGNAL('tabSplitRequest(int,QPoint)'),
                     self.splitTab)
        self.connect(self.tabBar(),
                     QtCore.SIGNAL('tabTextChanged(int,QString)'),
                     self.changeTabText)
        self.addAction(self.showNextTabAction())
        self.addAction(self.showPrevTabAction())
        self.executedPipelines = [[],{},{}]
        self.monitoredPipelines = {}
        self.spreadsheetFileName = None
        self.loadingMode = False
        self.editingMode = False
        self.closeButton = QtGui.QToolButton(self)
        self.closeButton.setIcon(CurrentTheme.VIEW_MANAGER_CLOSE_ICON)
        self.closeButton.setAutoRaise(True)
        self.setCornerWidget(self.closeButton)
        self.connect(self.closeButton, QtCore.SIGNAL('clicked()'),
                     self.deleteSheetAction().trigger)

    def isLoadingMode(self):
        """ isLoadingMode() -> boolean
        Checking if the controller is in loading mode

        """
        return self.loadingMode

    def getMonitoredLocations(self, spec):
        """ getMonitoredLocations(spec: tuple) -> location
        Return the monitored location associated with spec

        """
        key = ((spec[0]['locator'], spec[0]['version']), spec[1], spec[2])
        return self.monitoredPipelines.get(key, [])

    def appendMonitoredLocations(self, spec, value):
        """ getMonitoredLocations(spec: tuple, value: location) -> None
        Return the monitored location associated with spec

        """
        key = ((spec[0]['locator'], spec[0]['version']), spec[1], spec[2])
        if key in self.monitoredPipelines:
            self.monitoredPipelines[key].append(value)
        else:
            self.monitoredPipelines[key] = [value]

    def newSheetAction(self):
        """ newSheetAction() -> QAction
        Return the 'New Sheet' action

        """
        if not hasattr(self, 'newSheetActionVar'):
            icon = QtGui.QIcon(':/images/newsheet.png')
            self.newSheetActionVar = QtGui.QAction(icon, '&New sheet', self)
            self.newSheetActionVar.setToolTip('Create a new sheet')
            self.newSheetActionVar.setStatusTip('Create and show a new sheet')
            self.newSheetActionVar.setShortcut(QtGui.QKeySequence('Ctrl+N'))
            self.connect(self.newSheetActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.newSheetActionTriggered)
        return self.newSheetActionVar

    def deleteSheetAction(self):
        """ deleteSheetAction() -> QAction
        Return the 'Delete Sheet' action:

        """
        if not hasattr(self, 'deleteSheetActionVar'):
            icon = QtGui.QIcon(':/images/deletesheet.png')
            self.deleteSheetActionVar = QtGui.QAction(icon, '&Delete sheet',
                                                      self)
            self.deleteSheetActionVar.setToolTip('Delete the current sheet')
            self.deleteSheetActionVar.setStatusTip('Delete the current sheet '
                                                   'if there are more than one')
            key = QtGui.QKeySequence('Ctrl+Backspace')
            self.deleteSheetActionVar.setShortcut(key)
            self.connect(self.deleteSheetActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.deleteSheetActionTriggered)
        return self.deleteSheetActionVar

    def showNextTabAction(self):
        """ showNextTabAction() -> QAction
        Return the 'Next Sheet' action

        """
        if not hasattr(self, 'showNextTabActionVar'):
            icon = QtGui.QIcon(':/images/forward.png')
            self.showNextTabActionVar = QtGui.QAction(icon, '&Next sheet', self)
            self.showNextTabActionVar.setToolTip('Show the next sheet')
            self.showNextTabActionVar.setStatusTip('Show the next sheet if it '
                                                   'is available')
            self.showNextTabActionShortcut = QtGui.QShortcut(self)
            self.showNextTabActionVar.setShortcut('Ctrl+PgDown')
            self.connect(self.showNextTabActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.showNextTab)
        return self.showNextTabActionVar

    def showPrevTabAction(self):
        """ showPrevTabAction() -> QAction
        Return the 'Prev Sheet' action

        """
        if not hasattr(self, 'showPrevTabActionVar'):
            icon = QtGui.QIcon(':/images/back.png')
            self.showPrevTabActionVar = QtGui.QAction(icon, '&Prev sheet', self)
            self.showPrevTabActionVar.setToolTip('Show the previous sheet')
            self.showPrevTabActionVar.setStatusTip('Show the previous sheet if '
                                                   'it is available')
            self.showPrevTabActionVar.setShortcut('Ctrl+PgUp')
            self.connect(self.showPrevTabActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.showPrevTab)
        return self.showPrevTabActionVar

    def saveAction(self):
        """ saveAction() -> QAction
        Return the 'Save' action

        """
        if not hasattr(self, 'saveActionVar'):
            self.saveActionVar = QtGui.QAction(QtGui.QIcon(':/images/save.png'),
                                               '&Save', self)
            self.saveActionVar.setStatusTip('Save the current spreadsheet')
            self.saveActionVar.setShortcut('Ctrl+S')
            self.connect(self.saveActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.saveSpreadsheet)
        return self.saveActionVar

    def saveAsAction(self):
        """ saveAsAction() -> QAction
        Return the 'Save As...' action

        """
        if not hasattr(self, 'saveAsActionVar'):
            icon = QtGui.QIcon(':/images/saveas.png')
            self.saveAsActionVar = QtGui.QAction(icon, 'Save &As...', self)
            self.saveAsActionVar.setStatusTip('Save the current spreadsheet '
                                              'at a new location')
            self.connect(self.saveAsActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.saveSpreadsheetAs)
        return self.saveAsActionVar

    def openAction(self):
        """ openAction() -> QAction
        Return the 'Open...' action

        """
        if not hasattr(self, 'openActionVar'):
            self.openActionVar = QtGui.QAction(QtGui.QIcon(':/images/open.png'),
                                               '&Open...', self)
            self.openActionVar.setStatusTip('Open a saved spreadsheet')
            self.openActionVar.setShortcut('Ctrl+O')
            self.connect(self.openActionVar,
                         QtCore.SIGNAL('triggered()'),
                         self.openSpreadsheetAs)
        return self.openActionVar

    def exportSheetToImageAction(self):
        """ exportSheetToImageAction() -> QAction
        Export the current sheet to an image

        """
        if not hasattr(self, 'exportSheetToImageVar'):
            self.exportSheetToImageVar = QtGui.QAction('Export Sheet', self)
            self.exportSheetToImageVar.setStatusTip(
                'Export all cells in the spreadsheet to a montaged image')

            exportMenu = QtGui.QMenu(self)
            singleAction = exportMenu.addAction('As a Single Image')
            multiAction = exportMenu.addAction('Separately')
            self.exportSheetToImageVar.setMenu(exportMenu)

            self.connect(self.exportSheetToImageVar,
                         QtCore.SIGNAL('triggered(bool)'),
                         self.exportSheetToSingleImageActionTriggered)

            self.connect(singleAction,
                         QtCore.SIGNAL('triggered()'),
                         self.exportSheetToSingleImageActionTriggered)
            self.connect(multiAction,
                         QtCore.SIGNAL('triggered()'),
                         self.exportSheetToSeparateImagesActionTriggered)
        return self.exportSheetToImageVar

    def exportSheetToSingleImageActionTriggered(self, action=None):
        """ exportSheetToSingleImageActionTriggered() -> None
        Exports the sheet as a big image
        """
        filename = QtGui.QFileDialog.getSaveFileName(
            self, "Select a File to Export the Sheet",
            ".", "Images (*.png *.xpm *.jpg)")
        if filename:
            self.currentWidget().exportSheetToImage(filename)

    def exportSheetToSeparateImagesActionTriggered(self, action=None):
        """ exportSheetToSeparateImagesActionTriggered() -> None
        Exports the cells as separate images
        """
        dirname = QtGui.QFileDialog.getExistingDirectory(
            self, 'Select a Directory to Export Images', ".",
            QtGui.QFileDialog.ShowDirsOnly)
        if dirname:
            self.currentWidget().exportSheetToImages(dirname)

    def newSheetActionTriggered(self, checked=False):
        """ newSheetActionTriggered(checked: boolean) -> None
        Actual code to create a new sheet

        """
        self.setCurrentIndex(self.addTabWidget(StandardWidgetSheetTab(self),
                                               'Sheet %d' % (self.count()+1)))
        self.currentWidget().sheet.stretchCells()

    def tabInserted(self, index):
        """tabInserted(index: int) -> None
        event handler to get when sheets are inserted """
        self.deleteSheetAction().setEnabled(True)
        self.saveAction().setEnabled(True)
        self.saveAsAction().setEnabled(True)

    def tabRemoved(self, index):
        """tabInserted(index: int) -> None
        event handler to get when sheets are removed """
        if self.count() == 0:
            self.deleteSheetAction().setEnabled(False)
            self.saveAction().setEnabled(False)
            self.saveAsAction().setEnabled(False)

    def removeSheetReference(self, sheet):
        """ removeSheetReference(sheet: StandardWidgetSheetTab) -> None
        Remove references of a sheet from the spreadsheet
        """
        for (code, locations) in self.monitoredPipelines.iteritems():
            for lid in reversed(xrange(len(locations))):
                if sheet==locations[lid][0]:
                    del locations[lid]

    def deleteSheetActionTriggered(self, checked=False):
        """ deleteSheetActionTriggered(checked: boolean) -> None
        Actual code to delete the current sheet

        """
        if self.count()>0:
            widget = self.currentWidget()
            self.tabWidgets.remove(widget)
            self.removeTab(self.currentIndex())
            self.removeSheetReference(widget)
            widget.deleteAllCells()
            widget.deleteLater()
            QtCore.QCoreApplication.processEvents()
            gc.collect()

    def clearTabs(self):
        """ clearTabs() -> None
        Clear and reset the controller

        """
        self.executedPipelines = [[], {}, {}]
        while self.count()>0:
            self.deleteSheetActionTriggered()
        for i in reversed(range(len(self.tabWidgets))):
            t = self.tabWidgets[i]
            del self.tabWidgets[i]
            self.removeSheetReference(t)
            t.deleteAllCells()
            t.deleteLater()

    def insertTab(self, idx, tabWidget, tabText):
        """ insertTab(idx: int, tabWidget: QWidget, tabText: str)
                      -> QTabWidget
        Redirect insertTab command to operatingWidget, this can either be a
        QTabWidget or a QStackedWidget

        """
        if self.operatingWidget!=self:
            ret = self.operatingWidget.insertWidget(idx, tabWidget)
            self.operatingWidget.setCurrentIndex(ret)
            return ret
        else:
            return QtGui.QTabWidget.insertTab(self, idx, tabWidget, tabText)

    def findSheet(self, sheetReference):
        """ findSheet(sheetReference: subclass(SheetReference)) -> Sheet widget
        Find/Create a sheet that meets a certen sheet reference

        """
        if not sheetReference:
            return None
        sheetReference.clearCandidate()
        for idx in xrange(len(self.tabWidgets)):
            tabWidget = self.tabWidgets[idx]
            tabLabel = tabWidget.windowTitle()
            sheetReference.checkCandidate(tabWidget, tabLabel, idx,
                                          self.operatingWidget.currentIndex())
        return sheetReference.setupCandidate(self)

    def changeTabText(self, tabIdx, newTabText):
        """ changeTabText(tabIdx: int, newTabText: str) -> None
        Update window title on the operating widget when the tab text
        has changed

        """
        self.operatingWidget.widget(tabIdx).setWindowTitle(newTabText)

    def moveTab(self, tabIdx, destination):
        """ moveTab(tabIdx: int, destination: int) -> None
        Move a tab at tabIdx to a different position at destination

        """
        if (tabIdx<0 or tabIdx>self.count() or
            destination<0 or destination>self.count()):
            return
        tabText = self.tabText(tabIdx)
        tabWidget = self.widget(tabIdx)
        self.removeTab(tabIdx)
        self.insertTab(destination, tabWidget, tabText)
        if tabIdx==self.currentIndex():
            self.setCurrentIndex(destination)

    def splitTab(self, tabIdx, pos=None):
        """ splitTab(tabIdx: int, pos: QPoint) -> None
        Split a tab to be  a stand alone window and move to position pos

        """
        if tabIdx<0 or tabIdx>self.count() or self.count()==0:
            return
        tabWidget = self.widget(tabIdx)
        self.removeTab(tabIdx)

        frame = StandardTabDockWidget(tabWidget.windowTitle(), tabWidget,
                                      self.tabBar(), self)
        if pos:
            frame.move(pos)
        frame.show()
        self.floatingTabWidgets.append(frame)

    def mergeTab(self, frame, tabIdx):
        """ mergeTab(frame: StandardTabDockWidget, tabIdx: int) -> None
        Merge a tab dock widget back to the controller at position tabIdx

        """
        if tabIdx<0 or tabIdx>self.count():
            return
        if tabIdx==self.count(): tabIdx = -1
        tabWidget = frame.widget()
        frame.setWidget(None)
        while frame in self.floatingTabWidgets:
            self.floatingTabWidgets.remove(frame)
        frame.deleteLater()
        tabWidget.setParent(None)
        newIdx = self.insertTab(tabIdx, tabWidget, tabWidget.windowTitle())
        self.setCurrentIndex(newIdx)

    def addTabWidget(self, tabWidget, sheetLabel):
        """ addTabWidget(tabWidget: QWidget, sheetLabel: str) -> int
        Add a new tab widget to the controller

        """
        return self.insertTabWidget(-1, tabWidget, sheetLabel)

    def insertTabWidget(self, index, tabWidget, sheetLabel):
        """ insertTabWidget(index: int, tabWidget: QWidget, sheetLabel: str)
                            -> int
        Insert a tab widget to the controller at some location

        """
        if sheetLabel==None:
            sheetLabel = 'Sheet %d' % (len(self.tabWidgets)+1)
        if not tabWidget in self.tabWidgets:
            self.tabWidgets.append(tabWidget)
            tabWidget.setWindowTitle(sheetLabel)
        return self.insertTab(index, tabWidget, sheetLabel)

    def tabWidgetUnderMouse(self):
        """ tabWidgetUnderMouse() -> QWidget
        Return the tab widget that is under mouse, hide helpers for the rest

        """
        result = None
        for t in self.tabWidgets:
            if t.underMouse():
                result = t
            else:
                t.showHelpers(False, QtCore.QPoint(-1,-1))
        return result

    def setupFullScreenWidget(self, fs, stackedWidget):
        """ setupFullScreenWidget(fs: boolean, stackedWidget: QStackedWidget)
                                  -> None
        Prepare(fs=True)/Clean(fs=False) up full screen mode

        """
        if fs:
            idx = self.currentIndex()
            for i in xrange(self.count()):
                widget = self.widget(0)
                self.removeTab(0)
                self.tabTrueParent = widget.parent()
                widget.setParent(stackedWidget)
                stackedWidget.addWidget(widget)
            stackedWidget.setCurrentIndex(idx)
            self.operatingWidget = stackedWidget
        else:
            idx = stackedWidget.currentIndex()
            for i in xrange(stackedWidget.count()):
                widget = stackedWidget.widget(0)
                stackedWidget.removeWidget(widget)
                widget.setParent(self.tabTrueParent)
                self.addTab(widget, widget.windowTitle())
            self.setCurrentIndex(idx)
            self.operatingWidget = self

    def showNextTab(self):
        """ showNextTab() -> None
        Bring the next tab up

        """
        if self.operatingWidget.currentIndex()<self.operatingWidget.count()-1:
            index = self.operatingWidget.currentIndex()+1
            self.operatingWidget.setCurrentIndex(index)

    def showPrevTab(self):
        """ showPrevTab() -> None
        Bring the previous tab up

        """
        if self.operatingWidget.currentIndex()>0:
            index = self.operatingWidget.currentIndex()-1
            self.operatingWidget.setCurrentIndex(index)

    def tabPopupMenu(self):
        """ tabPopupMenu() -> QMenu
        Return a menu containing a list of all tabs

        """
        menu = QtGui.QMenu(self)
        en = self.operatingWidget.currentIndex()<self.operatingWidget.count()-1
        self.showNextTabAction().setEnabled(en)
        menu.addAction(self.showNextTabAction())
        en = self.operatingWidget.currentIndex()>0
        self.showPrevTabAction().setEnabled(en)
        menu.addAction(self.showPrevTabAction())
        menu.addSeparator()
        for idx in xrange(self.operatingWidget.count()):
            t = self.operatingWidget.widget(idx)
            action = menu.addAction(t.windowTitle())
            action.setData(idx)
            if t==self.operatingWidget.currentWidget():
                action.setIcon(QtGui.QIcon(':/images/ok.png'))
        menu.addAction(self.parent().parent().fullScreenAction())
        return menu

    def showPopupMenu(self):
        """ showPopupMenu() -> None
        Activate the tab list and show the popup menu

        """
        menu = self.tabPopupMenu()
        action = menu.exec_(QtGui.QCursor.pos())
        self.showNextTabAction().setEnabled(True)
        self.showPrevTabAction().setEnabled(True)
        if not action: return
        if not action in self.actions():
            self.operatingWidget.setCurrentIndex(action.data()[0])
        menu.deleteLater()

    def changeSpreadsheetFileName(self, fileName):
        """ changeSpreadsheetFileName(fileName: str) -> None
        Change the current spreadsheet filename and reflect it on the
        window title

        """
        self.spreadsheetFileName = fileName
        if self.spreadsheetFileName:
            displayName = self.spreadsheetFileName
        else:
            displayName = 'Untitled'
        self.emit(QtCore.SIGNAL('needChangeTitle'),
                  '%s - VisTrails Spreadsheet' % displayName)

    def pipelineId(self, pipelineInfo):
        return (pipelineInfo['controller'], pipelineInfo['version'])

    def addPipeline(self, pipelineInfo):
        """ addPipeline(pipelineInfo: dict) -> None
        Add vistrail pipeline executions to history

        """
        vistrail = self.pipelineId(pipelineInfo)
        self.executedPipelines[0].append(vistrail)
        if not vistrail in self.executedPipelines[1]:
            self.executedPipelines[1][vistrail] = 0
        else:
            self.executedPipelines[1][vistrail] += 1
        self.executedPipelines[2][vistrail] = 0

    def getCurrentPipelineId(self, pipelineInfo):
        """ getCurrentPipelineId(pipelineInfo: dict) -> Int
        Get the current pipeline id

        """
        vistrail = self.pipelineId(pipelineInfo)
        return self.executedPipelines[1][vistrail]

    def increasePipelineCellId(self, pipelineInfo):
        """ increasePipelineCellId(pipelineInfo: dict) -> int
        Increase the current cell pipeline id

        """
        vistrail = self.pipelineId(pipelineInfo)
        cid = self.executedPipelines[2][vistrail]
        self.executedPipelines[2][vistrail] += 1
        return cid

    def getCurrentPipelineCellId(self, pipelineInfo):
        """ getCurrentPipelineCellId(pipelineInfo: dict) -> int
        Get current pipeline cell id

        """
        vistrail = self.pipelineId(pipelineInfo)
        return self.executedPipelines[2][vistrail]

    def addPipelineCell(self, pipelineInfo):
        """ addPipelineCell(pipelineInfo: dict) -> None
        Add vistrail pipeline executions to history

        """
        vistrail = self.pipelineId(pipelineInfo)
        self.executedPipelines[0].append(vistrail)
        if not vistrail in self.executedPipelines[1]:
            self.executedPipelines[1][vistrail] = 0
        else:
            self.executedPipelines[1][vistrail] += 1


    def saveSpreadsheet(self, fileName=None):
        """ saveSpreadsheet(fileName: str) -> None
        Save the current spreadsheet to a file if fileName is not
        None. Else, pop up a dialog to ask for a file name.

        """
        def serialize_locator(locator):
            wrapper = XMLWrapper()
            dom = wrapper.create_document('spreadsheet_locator')
            root = dom.documentElement
            root.setAttribute("version", "1.0")
            locator.serialize(dom,root)
            return dom.toxml()

        def need_save():
            from vistrails.gui.vistrails_window import _app
            need_save_vt = False
            for t in self.tabWidgets:
                dim = t.getDimension()
                for r in xrange(dim[0]):
                    for c in xrange(dim[1]):
                        info = t.getCellPipelineInfo(r,c)
                        if info:
                            controller = info[0]['controller']
                            view = _app.ensureController(controller)
                            if view:
                                controller = view.get_controller()
                                if controller.changed:
                                    need_save_vt = True
            return need_save_vt

        if need_save():
            show_warning('Save Spreadsheet', 'Please save your vistrails and try again.')
            return

        if fileName==None:
            fileName = self.spreadsheetFileName
        if fileName:
            indexFile = open(fileName, 'w')
            indexFile.write(str(len(self.tabWidgets))+'\n')
            for t in self.tabWidgets:
                dim = t.getDimension()
                sheet = spreadsheetRegistry.getSheetByType(type(t))
                indexFile.write('%s\n'%str((str(t.windowTitle()),
                                            sheet,
                                            dim[0], dim[1])))
                for r in xrange(dim[0]):
                    for c in xrange(dim[1]):
                        info = t.getCellPipelineInfo(r,c)
                        if info:
                            newinfo0 = copy.copy(info[0])
                            newinfo0['pipeline'] = None
                            newinfo0['actions'] = []
                            newinfo0['locator'] = \
                                          serialize_locator(newinfo0['controller'].locator)
                            newinfo0['controller'] = None
                            indexFile.write('%s\n'
                                            %str((r, c,
                                                  newinfo0,
                                                  info[1], info[2])))
                indexFile.write('---\n')
            indexFile.write(str(len(self.executedPipelines[0]))+'\n')
            for vistrail in self.executedPipelines[0]:
                indexFile.write('%s\n'%str((serialize_locator(vistrail[0].locator),
                                            vistrail[1])))
            self.changeSpreadsheetFileName(fileName)
            indexFile.close()
        else:
            self.saveSpreadsheetAs()

    def saveSpreadsheetAs(self):
        """ saveSpreadsheetAs() -> None
        Asking a file name before saving the spreadsheet

        """
        fileName = QtGui.QFileDialog.getSaveFileName(self,
                                                     'Choose a spreadsheet '
                                                     'name',
                                                     '',
                                                     'VisTrails Spreadsheet '
                                                     '(*.vss)')
        if fileName:
            (root, ext) = os.path.splitext(fileName)
            if ext=='':
                fileName += '.vss'
            self.saveSpreadsheet(fileName)

    def openSpreadsheet(self, fileName):
        """ openSpreadsheet(fileName: str) -> None
        Open a saved spreadsheet assuming that all VTK files must exist and have
        all the version using the saved spreadsheet

        """
        def parse_locator(text):
            locator = None
            wrapper = XMLWrapper()
            dom = wrapper.create_document_from_string(text)
            root = dom.documentElement
            version = None
            version = root.getAttribute('version')
            if version == '1.0':
                for element in named_elements(root, 'locator'):
                    if str(element.getAttribute('type')) == 'file':
                        locator = FileLocator.parse(element)
                    elif str(element.getAttribute('type')) == 'db':
                        locator = DBLocator.parse(element)
            return locator
        locators = {}
        indexFile = open(fileName, 'r')
        contents = indexFile.read()
        self.clearTabs()
        lidx = 0
        lines = contents.split('\n')
        tabCount = int(lines[lidx])
        lidx += 1
        for tabIdx in xrange(tabCount):
            # FIXME: eval should pretty much never be used
            tabInfo = literal_eval(lines[lidx])
            lidx += 1
            sheet = spreadsheetRegistry.getSheet(tabInfo[1])(self)
            sheet.setDimension(tabInfo[2], tabInfo[3])
            self.addTabWidget(sheet, tabInfo[0])
            while lines[lidx]!='---':
                (r, c, vistrail, pid, cid) = literal_eval(lines[lidx])
                locator = vistrail['locator']
                if locators.has_key(locator):
                    vistrail['locator'] = locators[locator]
                else:
                    locators[locator] = parse_locator(vistrail['locator'])
                    vistrail['locator'] = locators[locator]
                self.appendMonitoredLocations((vistrail, pid, cid),
                                              (sheet, r, c))
                lidx += 1
            lidx += 1
        pipelineCount = int(lines[lidx])
        lidx += 1
        self.loadingMode = True
        progress = QtGui.QProgressDialog("Loading spreadsheet...",
                                         "&Cancel", 0, pipelineCount,
                                         self,
                                         QtCore.Qt.WindowStaysOnTopHint
                                         )
        progress.show()
        for pipelineIdx in xrange(pipelineCount):
            # FIXME: eval should pretty much never be used
            (serializedLocator, version) = literal_eval(lines[lidx])
            try:
                locator = locators[serializedLocator]
            except KeyError:
                locator = parse_locator(serializedLocator)
            if locator:
                bundle = locator.load()
                if isinstance(bundle, SaveBundle):
                    pipeline = bundle.vistrail.getPipeline(version)
                else:
                    pipeline = bundle.getPipeline(version)
                execution = get_default_interpreter()
                progress.setValue(pipelineIdx)
                QtCore.QCoreApplication.processEvents()
                if progress.wasCanceled():
                    break
                kwargs = {'locator': locator,
                          'current_version': version,
                          'view': DummyView(),
                          }
                execution.execute(pipeline, **kwargs)
            else:
                raise RuntimeError("Couldn't load spreadsheet")
            lidx += 1
        progress.setValue(pipelineCount)
        QtCore.QCoreApplication.processEvents()
        self.changeSpreadsheetFileName(fileName)
        self.loadingMode = False
        indexFile.close()

    def openSpreadsheetAs(self):
        """ openSpreadsheetAs() -> None
        Open a saved spreadsheet and set its filename in the dialog box

        """
        fileName = QtGui.QFileDialog.getOpenFileName(self,
                                                     'Choose a spreadsheet',
                                                     '',
                                                     'VisTrails Spreadsheet '
                                                     '(*.vss)',
                                                     )
        if fileName:
            self.openSpreadsheet(fileName)

    def cleanup(self):
        """ cleanup() -> None
        Clear reference of non-collectable objects/temp files for gc

        """
        self.clearTabs()

    def setEditingMode(self, editing=True):
        """ setEditingMode(editing: bool) -> None
        Turn on/off the editing mode of the whole spreadsheet

        """
        self.editingMode = editing
        for w in self.tabWidgets:
            w.setEditingMode(editing)
