コード例 #1
1
ファイル: table_widget.py プロジェクト: intermezzo-fr/pireal
class TableWidget(QWidget):

    def __init__(self):
        super(TableWidget, self).__init__()

        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)

        self.relations = {}

        ## Splitter
        #self._splitter = QSplitter()

        ## List of names of tables/relations
        #self._list_tables = QListWidget()
        #self._splitter.addWidget(self._list_tables)
        ## Stack
        self.stacked = QStackedWidget()
        vbox.addWidget(self.stacked)

        #vbox.addWidget(self._splitter)

        #self.connect(self._list_tables, SIGNAL("currentRowChanged(int)"),
                     #self.__change_table)

    #def __change_table(self, index):
        #self.stacked.setCurrentIndex(index)

    def count(self):
        return self.stacked.count()

    def add_table(self, rows, columns, name, data):
        table = QTableWidget()
        table.setRowCount(rows)
        table.setColumnCount(columns)

        for k, v in list(data.items()):
            item = QTableWidgetItem()
            item.setText(v)
            if k[0] == 0:
                table.setHorizontalHeaderItem(k[1], item)
            else:
                table.setItem(k[0] - 1, k[1], item)
        #ntuples = " [ " + str(rows) + " ]"
        #item_list = QListWidgetItem(name + ntuples)
        #item_list.setTextAlignment(Qt.AlignHCenter)
        #self._list_tables.addItem(item_list)
        self.stacked.addWidget(table)
        self.stacked.setCurrentIndex(self.stacked.count() - 1)

    def add_new_table(self, rel, name):
        import itertools

        table = QTableWidget()
        table.setRowCount(0)
        table.setColumnCount(0)

        data = itertools.chain([rel.fields], rel.content)

        for row_data in data:
            row = table.rowCount()
            table.setColumnCount(len(row_data))
            for col, text in enumerate(row_data):
                item = QTableWidgetItem()
                item.setText(text)
                if row == 0:
                    table.setHorizontalHeaderItem(col, item)
                else:
                    table.setItem(row - 1, col, item)
            table.insertRow(row)
        table.removeRow(table.rowCount() - 1)
        self.stacked.addWidget(table)
        self.stacked.setCurrentIndex(self.stacked.count() - 1)
        lateral = Pireal.get_service("lateral")
        lateral.add_item_list([name])

    def remove_table(self, index):
        table = self.stacked.widget(index)
        self.stacked.removeWidget(table)

    def add_table_from_file(self):
        pass
コード例 #2
0
class ComboTabWidget(QWidget):
    def __init__(self, parent):
        super(ComboTabWidget, self).__init__(parent)
        layout = QVBoxLayout(self)
        layout.setSpacing(0)
        
        self.switchCombo = QComboBox(self)
        layout.addWidget(self.switchCombo, 0, Qt.AlignCenter)
        
        groupBox = QGroupBox(self)
        groupBoxLayout = QVBoxLayout(groupBox)
        groupBoxLayout.setSpacing(0)
        
        self.pageArea = QStackedWidget(groupBox)
        groupBoxLayout.addWidget(self.pageArea)
        
        layout.addWidget(groupBox, 1)
        
        self.switchCombo.currentIndexChanged.connect(self.pageArea.setCurrentIndex)
        
    def setTabPosition(self, tabPos):
        pass
        
    def addTab(self, w, tabText):
        self.pageArea.addWidget(w)
        self.switchCombo.addItem(tabText)
    
    def insertTab(self, pos, w, tabText):
        self.pageArea.insertWidget(pos, w)
        self.switchCombo.insertItem(pos, tabText)
        
    def removeTab(self, index=-1):
        if index < 0:
            index = self.currentIndex()
        
        w = self.pageArea.widget(index)
        
        self.pageArea.removeWidget(w)
        self.switchCombo.removeItem(index)
        
    def updateTab(self, w, tabText, index=-1):
        if index < 0:
            index = self.switchCombo.currentIndex()
        
        self.removeTab(index)
        self.insertTab(index, w, tabText)
        self.setCurrentIndex(index)
        
    def setCurrentIndex(self, index):
        self.switchCombo.setCurrentIndex(index)
        
    def widget(self, index):
        return self.pageArea.widget(index)
        
    def currentIndex(self):
        return self.switchCombo.currentIndex()
    
    def count(self):
        return self.switchCombo.count()
コード例 #3
0
class DataSelectionGui(QWidget):
    """
    Manages all GUI elements in the data selection applet.
    This class itself is the central widget and also owns/manages the applet drawer widgets.
    """

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################

    def centralWidget( self ):
        return self

    def appletDrawer( self ):
        return self._drawer

    def menus( self ):
        return []

    def viewerControlWidget(self):
        return self._viewerControlWidgetStack

    def setImageIndex(self, imageIndex):
        if imageIndex is not None:
            self.laneSummaryTableView.selectRow(imageIndex)
            for detailWidget in self._detailViewerWidgets:
                detailWidget.datasetDetailTableView.selectRow(imageIndex)

    def stopAndCleanUp(self):
        for editor in self.volumeEditors.values():
            self.viewerStack.removeWidget( editor )
            self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
            editor.stopAndCleanUp()
        self.volumeEditors.clear()

    def imageLaneAdded(self, laneIndex):
        if len(self.laneSummaryTableView.selectedIndexes()) == 0:
            self.laneSummaryTableView.selectRow(laneIndex)
        
        # We don't have any real work to do because this gui initiated the lane addition in the first place
        if self.guiMode != GuiMode.Batch:
            if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1):
                import warnings
                warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.Dataset), laneIndex))

    def imageLaneRemoved(self, laneIndex, finalLength):
        # We assume that there's nothing to do here because THIS GUI initiated the lane removal
        if self.guiMode != GuiMode.Batch:
            assert len(self.topLevelOperator.DatasetGroup) == finalLength

    ###########################################
    ###########################################

    def __init__(self, dataSelectionOperator, serializer, guiControlSignal, guiMode=GuiMode.Normal, title="Input Selection"):
        with Tracer(traceLogger):
            super(DataSelectionGui, self).__init__()

            self.title = title

            self._viewerControls = QWidget()
            self.topLevelOperator = dataSelectionOperator
            self.guiMode = guiMode
            self.serializer = serializer
            self.guiControlSignal = guiControlSignal
            self.threadRouter = ThreadRouter(self)

            self._initCentralUic()
            self._initAppletDrawerUic()
            
            self._viewerControlWidgetStack = QStackedWidget(self)

            def handleImageRemoved(multislot, index, finalLength):
                # Remove the viewer for this dataset
                imageSlot = self.topLevelOperator.Image[index]
                if imageSlot in self.volumeEditors.keys():
                    editor = self.volumeEditors[imageSlot]
                    self.viewerStack.removeWidget( editor )
                    self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
                    editor.stopAndCleanUp()

            self.topLevelOperator.Image.notifyRemove( bind( handleImageRemoved ) )

    def _initCentralUic(self):
        """
        Load the GUI from the ui file into this class and connect it with event handlers.
        """
        # Load the ui file into this class (find it in our own directory)
        localDir = os.path.split(__file__)[0]+'/'
        uic.loadUi(localDir+"/dataSelection.ui", self)

        self._initTableViews()
        self._initViewerStack()
        self.splitter.setSizes( [150, 850] )

    def _initAppletDrawerUic(self):
        """
        Load the ui file for the applet drawer, which we own.
        """
        localDir = os.path.split(__file__)[0]+'/'
        self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui")

    def _initTableViews(self):
        self.fileInfoTabWidget.setTabText( 0, "Summary" )
        self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) )
        self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset )
        self.laneSummaryTableView.addFilesRequested.connect( self.handleAddFiles )
        self.laneSummaryTableView.addStackRequested.connect( self.handleAddStack )
        self.laneSummaryTableView.addByPatternRequested.connect( self.handleAddByPattern )
        self.removeLaneButton.clicked.connect( self.handleRemoveLaneButtonClicked )
        self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked )

        self._retained = [] # Retain menus so they don't get deleted
        self._detailViewerWidgets = []
        for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value):
            detailViewer = DataDetailViewerWidget( self, self.topLevelOperator, roleIndex )
            self._detailViewerWidgets.append( detailViewer )

            # Button
            menu = QMenu(parent=self)
            menu.setObjectName("addFileButton_role_{}".format( roleIndex ))
            menu.addAction( "Add File(s)..." ).triggered.connect( partial(self.handleAddFiles, roleIndex) )
            menu.addAction( "Add Volume from Stack..." ).triggered.connect( partial(self.handleAddStack, roleIndex) )
            menu.addAction( "Add Many by Pattern..." ).triggered.connect( partial(self.handleAddByPattern, roleIndex) )
            detailViewer.appendButton.setMenu( menu )
            self._retained.append(menu)
            
            # Context menu            
            detailViewer.datasetDetailTableView.replaceWithFileRequested.connect( partial(self.handleReplaceFile, roleIndex) )
            detailViewer.datasetDetailTableView.replaceWithStackRequested.connect( partial(self.replaceWithStack, roleIndex) )
            detailViewer.datasetDetailTableView.editRequested.connect( partial(self.editDatasetInfo, roleIndex) )
            detailViewer.datasetDetailTableView.resetRequested.connect( partial(self.handleClearDatasets, roleIndex) )

            # Drag-and-drop
            detailViewer.datasetDetailTableView.addFilesRequested.connect( partial( self.addFileNames, roleIndex=roleIndex ) )

            # Selection handling
            def showFirstSelectedDataset( _roleIndex, lanes ):
                if lanes:
                    self.showDataset( lanes[0], _roleIndex )
            detailViewer.datasetDetailTableView.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) )

            self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role)
                
        self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs )
        self.fileInfoTabWidget.setCurrentIndex(0)

    def handleSwitchTabs(self, tabIndex ):
        if tabIndex < len(self._detailViewerWidgets):
            roleIndex = tabIndex # If summary tab is moved to the front, change this line.
            detailViewer = self._detailViewerWidgets[roleIndex]
            selectedLanes = detailViewer.datasetDetailTableView.selectedLanes
            if selectedLanes:
                self.showDataset( selectedLanes[0], roleIndex )

    def _initViewerStack(self):
        self.volumeEditors = {}
        self.viewerStack.addWidget( QWidget() )

    def handleRemoveLaneButtonClicked(self):
        """
        The user clicked the "Remove" button.
        Remove the currently selected row(s) from both the GUI and the top-level operator.
        """
        # Figure out which lanes to remove
        selectedIndexes = self.laneSummaryTableView.selectedIndexes()
        rows = set()
        for modelIndex in selectedIndexes:
            rows.add( modelIndex.row() )
        rows.discard( self.laneSummaryTableView.model().rowCount() )

        # Remove in reverse order so row numbers remain consistent
        for row in reversed(sorted(rows)):
            # Remove from the GUI
            self.laneSummaryTableView.model().removeRow(row)
            # Remove from the operator
            finalSize = len(self.topLevelOperator.DatasetGroup) - 1
            self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize)
    
            # The gui and the operator should be in sync
            assert self.laneSummaryTableView.model().rowCount() == len(self.topLevelOperator.DatasetGroup)+1

    def showDataset(self, laneIndex, roleIndex=None):
        if laneIndex == -1:
            self.viewerStack.setCurrentIndex(0)
            return
        
        assert threading.current_thread().name == "MainThread"
        imageSlot = self.topLevelOperator.Image[laneIndex]

        # Create if necessary
        if imageSlot not in self.volumeEditors.keys():
            
            class DatasetViewer(LayerViewerGui):
                def moveToTop(self, roleIndex):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return
                    datasetRoles = opLaneView.DatasetRoles.value
                    roleName = datasetRoles[roleIndex]
                    try:
                        layerIndex = [l.name for l in self.layerstack].index(roleName)
                    except ValueError:
                        return
                    else:
                        self.layerstack.selectRow(layerIndex)
                        self.layerstack.moveSelectedToTop()

                def setupLayers(self):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return []
                    layers = []
                    datasetRoles = opLaneView.DatasetRoles.value
                    for roleIndex, slot in enumerate(opLaneView.ImageGroup):
                        if slot.ready():
                            roleName = datasetRoles[roleIndex]
                            layer = self.createStandardLayerFromSlot(slot)
                            layer.name = roleName
                            layers.append(layer)
                    return layers

            opLaneView = self.topLevelOperator.getLane(laneIndex)
            layerViewer = DatasetViewer(opLaneView, crosshair=False)
            
            # Maximize the x-y view by default.
            layerViewer.volumeEditorWidget.quadview.ensureMaximized(2)

            self.volumeEditors[imageSlot] = layerViewer
            self.viewerStack.addWidget( layerViewer )
            self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() )

        # Show the right one
        viewer = self.volumeEditors[imageSlot]
        displayedRole = self.fileInfoTabWidget.currentIndex()
        viewer.moveToTop(displayedRole)
        self.viewerStack.setCurrentWidget( viewer )
        self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() )


    def handleAddFiles(self, roleIndex):
        self.addFiles(roleIndex)

    def handleReplaceFile(self, roleIndex, startingLane):
        self.addFiles(roleIndex, startingLane)

    def addFiles(self, roleIndex, startingLane=None):
        """
        The user clicked the "Add File" button.
        Ask him to choose a file (or several) and add them to both
          the GUI table and the top-level operator inputs.
        """
        # Find the directory of the most recently opened image file
        mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' )
        if mostRecentImageFile is not None:
            defaultDirectory = os.path.split(mostRecentImageFile)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        # Launch the "Open File" dialog
        fileNames = self.getImageFileNamesToOpen(defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent image', fileNames[0])
            try:
                self.addFileNames(fileNames, roleIndex, startingLane)
            except RuntimeError as e:
                QMessageBox.critical(self, "Error loading file", str(e))

    def handleAddByPattern(self, roleIndex):
        # Find the most recent directory

        # TODO: remove code duplication
        mostRecentDirectory = PreferencesManager().get( 'DataSelection', 'recent mass directory' )
        if mostRecentDirectory is not None:
            defaultDirectory = os.path.split(mostRecentDirectory)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        fileNames = self.getMass(defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent mass directory', os.path.split(fileNames[0])[0])
            try:
                self.addFileNames(fileNames, roleIndex)
            except RuntimeError as e:
                QMessageBox.critical(self, "Error loading file", str(e))


    def getImageFileNamesToOpen(self, defaultDirectory):
        """
        Launch an "Open File" dialog to ask the user for one or more image files.
        """
        extensions = OpDataSelection.SupportedExtensions
        filt = "Image files (" + ' '.join('*.' + x for x in extensions) + ')'
        options = QFileDialog.Options()
        if ilastik_config.getboolean("ilastik", "debug"):
            options |=  QFileDialog.DontUseNativeDialog
        fileNames = QFileDialog.getOpenFileNames( self, "Select Images", 
                                 defaultDirectory, filt, options=options )
        # Convert from QtString to python str
        fileNames = [str(s) for s in fileNames]
        return fileNames

    def _findFirstEmptyLane(self, roleIndex):
        opTop = self.topLevelOperator
        
        # Determine the number of files this role already has
        # Search for the last valid value.
        firstNewLane = 0
        for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)):
            if slot[roleIndex].ready():
                firstNewLane = laneIndex+1
                break
        return firstNewLane

    def addFileNames(self, fileNames, roleIndex, startingLane=None):
        """
        Add the given filenames to both the GUI table and the top-level operator inputs.
        If startingLane is None, the filenames will be *appended* to the role's list of files.
        """
        infos = []

        if startingLane is None:        
            startingLane = self._findFirstEmptyLane(roleIndex)
            endingLane = startingLane+len(fileNames)-1
        else:
            assert startingLane < len(self.topLevelOperator.DatasetGroup)
            endingLane = startingLane+len(fileNames)-1

        # Assign values to the new inputs we just allocated.
        # The GUI will be updated by callbacks that are listening to slot changes
        for i, filePath in enumerate(fileNames):
            datasetInfo = DatasetInfo()
            cwd = self.topLevelOperator.WorkingDirectory.value
            
            if not areOnSameDrive(filePath,cwd):
                QMessageBox.critical(self, "Drive Error","Data must be on same drive as working directory.")
                return
                
            absPath, relPath = getPathVariants(filePath, cwd)
            
            # Relative by default, unless the file is in a totally different tree from the working directory.
            if len(os.path.commonprefix([cwd, absPath])) > 1:
                datasetInfo.filePath = relPath
            else:
                datasetInfo.filePath = absPath
                
            datasetInfo.nickname = PathComponents(absPath).filenameBase

            h5Exts = ['.ilp', '.h5', '.hdf5']
            if os.path.splitext(datasetInfo.filePath)[1] in h5Exts:
                datasetNames = self.getPossibleInternalPaths( absPath )
                if len(datasetNames) > 0:
                    datasetInfo.filePath += str(datasetNames[0])
                else:
                    raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath)

            # Allow labels by default if this gui isn't being used for batch data.
            datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal )
            infos.append(datasetInfo)

        # if no exception was thrown, set up the operator now
        opTop = self.topLevelOperator
        originalSize = len(opTop.DatasetGroup)
            
        if len( opTop.DatasetGroup ) < endingLane+1:
            opTop.DatasetGroup.resize( endingLane+1 )
        for laneIndex, info in zip(range(startingLane, endingLane+1), infos):
            try:
                self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info )
            except DatasetConstraintError as ex:
                # Give the user a chance to fix the problem
                if not self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex):
                    opTop.DatasetGroup.resize( originalSize )
                    break
            except:
                QMessageBox.critical( self, "Dataset Load Error", "Wasn't able to load your dataset into the workflow.  See console for details." )
                opTop.DatasetGroup.resize( originalSize )
                raise
        self.updateInternalPathVisiblity()

    @threadRouted
    def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex):
        msg = "Can't use default properties for dataset:\n\n" + \
              filename + "\n\n" + \
              "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \
              ex.message + "\n\n" + \
              "Please enter valid dataset properties to continue."
        QMessageBox.warning( self, "Dataset Needs Correction", msg )
        
        return self.repairDatasetInfo( info, roleIndex, laneIndex )

    def repairDatasetInfo(self, info, roleIndex, laneIndex):
        defaultInfos = {}
        defaultInfos[laneIndex] = info
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos)
        return ( editorDlg.exec_() == QDialog.Accepted )

    def getPossibleInternalPaths(self, absPath):
        datasetNames = []
        # Open the file as a read-only so we can get a list of the internal paths
        with h5py.File(absPath, 'r') as f:
            # Define a closure to collect all of the dataset names in the file.
            def accumulateDatasetPaths(name, val):
                if type(val) == h5py._hl.dataset.Dataset and 3 <= len(val.shape) <= 5:
                    datasetNames.append( '/' + name )
            # Visit every group/dataset in the file
            f.visititems(accumulateDatasetPaths)
        return datasetNames

    def handleAddStack(self, roleIndex):
        self.replaceWithStack(roleIndex, laneIndex=None)

    def replaceWithStack(self, roleIndex, laneIndex):
        """
        The user clicked the "Import Stack Files" button.
        """
        stackDlg = StackFileSelectionWidget(self)
        stackDlg.exec_()
        if stackDlg.result() != QDialog.Accepted :
            return
        files = stackDlg.selectedFiles
        if len(files) == 0:
            return

        info = DatasetInfo()
        info.filePath = "//".join( files )
        prefix = os.path.commonprefix(files)
        info.nickname = PathComponents(prefix).filenameBase + "..."

        # Allow labels by default if this gui isn't being used for batch data.
        info.allowLabels = ( self.guiMode == GuiMode.Normal )
        info.fromstack = True

        originalNumLanes = len(self.topLevelOperator.DatasetGroup)

        if laneIndex is None:
            laneIndex = self._findFirstEmptyLane(roleIndex)
        if len(self.topLevelOperator.DatasetGroup) < laneIndex+1:
            self.topLevelOperator.DatasetGroup.resize(laneIndex+1)

        def importStack():
            self.guiControlSignal.emit( ControlCommand.DisableAll )
            # Serializer will update the operator for us, which will propagate to the GUI.
            try:
                self.serializer.importStackAsLocalDataset( info )
                try:
                    self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info)
                except DatasetConstraintError as ex:
                    # Give the user a chance to repair the problem.
                    filename = files[0] + "\n...\n" + files[-1]
                    if not self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex ):
                        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)
            finally:
                self.guiControlSignal.emit( ControlCommand.Pop )

        req = Request( importStack )
        req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) )
        req.submit()

    @threadRouted
    def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info):
        import traceback
        traceback.print_tb(exc_info[2])
        msg = "Failed to load stack due to the following error:\n{}".format( exc )
        msg += "Attempted stack files were:"
        for f in files:
            msg += f + "\n"
        QMessageBox.critical(self, "Failed to load image stack", msg)
        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)

    def getMass(self, defaultDirectory):
        # TODO: launch dialog and get files

        # Convert from QtString to python str
        loader = MassFileLoader(defaultDirectory=defaultDirectory)
        loader.exec_()
        if loader.result() == QDialog.Accepted:
            fileNames = [str(s) for s in loader.filenames]
        else:
            fileNames = []
        return fileNames

    def handleClearDatasets(self, roleIndex, selectedRows):
        for row in selectedRows:
            self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect()

        # Remove all operators that no longer have any connected slots        
        last_valid = -1
        laneIndexes = range( len(self.topLevelOperator.DatasetGroup) )
        for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)):
            any_ready = False
            for slot in multislot:
                any_ready |= slot.ready()
            if not any_ready:
                self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 )

    def editDatasetInfo(self, roleIndex, laneIndexes):
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes)
        editorDlg.exec_()

    def updateInternalPathVisiblity(self):
        for widget in self._detailViewerWidgets:
            view = widget.datasetDetailTableView
            model = view.model()
            view.setColumnHidden(DatasetDetailedInfoColumn.InternalID,
                                 not model.hasInternalPaths())
コード例 #4
0
ファイル: container.py プロジェクト: papablopo07/pireal
class Container(QSplitter):

    def __init__(self, orientation=Qt.Vertical):
        super(Container, self).__init__(orientation)
        self.__last_open_folder = None
        self.__filename = ""
        self.__created = False
        self.__modified = False
        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)
        # Stacked
        self.stacked = QStackedWidget()
        vbox.addWidget(self.stacked)

        # Table
        self.table_widget = table_widget.TableWidget()

        Pireal.load_service("container", self)

    def create_data_base(self, filename=''):
        """ This function opens or creates a database

        :param filename: Database filename
        """

        if self.__created:
            QMessageBox.critical(self, self.tr("Error"),
                                 self.tr("Solo puede tener una base de datos "
                                         "abierta a la vez."))
            return
        if not filename:
            db_name, ok = QInputDialog.getText(self, self.tr("Nueva DB"),
                                               self.tr("Nombre:"))
            if not ok:
                return
        else:
            # From file
            try:
                db_name, data = file_manager.open_database(filename)
            except Exception as reason:
                QMessageBox.critical(self, self.tr("Error!"),
                                     reason.__str__())
                return

            self.table_widget.add_data_base(data)

        # Remove Start Page widget
        if isinstance(self.stacked.widget(0), start_page.StartPage):
            self.stacked.removeWidget(self.stacked.widget(0))
        self.stacked.addWidget(self.table_widget)
        # Title
        pireal = Pireal.get_service("pireal")
        pireal.change_title(db_name)
        # Enable QAction's
        pireal.enable_disable_db_actions()
        self.__created = True

    def create_new_relation(self):
        dialog = new_relation_dialog.NewRelationDialog(self)
        dialog.show()

    def remove_relation(self):
        lateral = Pireal.get_service("lateral")
        rname = lateral.get_relation_name()
        if not rname:
            QMessageBox.critical(self, self.tr("Error"),
                                 self.tr("No se ha seleccionado ninguna "
                                         "relación."))
            return
        r = QMessageBox.question(self, self.tr("Confirmación"),
                                 self.tr("Seguro que quieres eliminar la "
                                         "relación <b>{}</b>").format(rname),
                                             QMessageBox.Yes | QMessageBox.No)
        if r == QMessageBox.No:
            return
        index = lateral.current_index()
        # Remove table
        self.table_widget.remove_table(index)
        # Remove item from list widget
        lateral.remove_item(index)

    def new_query(self, filename=''):
        query_widget = Pireal.get_service("query_widget")
        self.addWidget(query_widget)
        if not query_widget.isVisible():
            query_widget.show()
        pireal = Pireal.get_service("pireal")
        pireal.enable_disable_query_actions()
        query_widget.new_query(filename)

        self.connect(query_widget,
                     SIGNAL("currentEditorSaved(QPlainTextEdit)"),
                     self.save_query)

    @property
    def modified(self):
        return self.__modified

    def show_start_page(self):
        sp = start_page.StartPage()
        self.stacked.addWidget(sp)

    def close_db(self):
        """ Close data base """

        widget = self.stacked.currentWidget()
        if isinstance(widget, table_widget.TableWidget):
            # Clear list of relations
            lateral = Pireal.get_service("lateral")
            lateral.clear_items()
            lateral.hide()
            # Close table widget
            self.stacked.removeWidget(widget)
            # Add start page
            self.show_start_page()

            self.__created = False

    def save_query(self, weditor=None):
        if weditor is None:
            query_widget = Pireal.get_service("query_widget")
            # Editor instance
            weditor = query_widget.get_active_editor()
        if weditor.rfile.is_new:
            return self.save_query_as(weditor)
        content = weditor.toPlainText()
        weditor.rfile.write(content)
        weditor.document().setModified(False)

        self.emit(SIGNAL("currentFileSaved(QString)"),
                  self.tr("Archivo guardado: {}").format(weditor.filename))

    def open_file(self):

        if self.__last_open_folder is None:
            directory = os.path.expanduser("~")
        else:
            directory = self.__last_open_folder
        filename = QFileDialog.getOpenFileName(self, self.tr("Abrir Archivo"),
                                               directory, settings.DBFILE,
                                               QFileDialog.DontUseNativeDialog)
        if not filename:
            return
        # Save folder
        self.__last_open_folder = file_manager.get_path(filename)

        ext = file_manager.get_extension(filename)
        if ext == '.pqf':
            # Query file
            self.new_query(filename)
        elif ext == '.rdb':
            self.load_rdb_database(file_manager.read_rdb_file(filename))
        else:
            self.create_data_base(filename)

    def load_rdb_database(self, content):
        csv_content = ""
        for line in content.splitlines():
            if line.startswith('@'):
                csv_content += '@'
                portion = line.split('(')
                name = portion[0][1:]
                csv_content += name + ':'
                for i in portion[1].split(','):
                    if not i.startswith(' '):
                        field = i.split('/')[0].strip()
                        csv_content += field + ','
            else:
                if not line:
                    continue
                csv_content += line
            csv_content += '\n'

        self.table_widget.add_table_from_rdb_content(csv_content)

    def save_query_as(self, editor=None):
        if editor is None:
            query_widget = Pireal.get_service("query_widget")
            editor = query_widget.get_active_editor()
        directory = os.path.expanduser("~")
        filename = QFileDialog.getSaveFileName(self,
                                               self.tr("Guardar Archivo"),
                                               directory)
        if not filename:
            return
        content = editor.toPlainText()
        editor.rfile.write(content, filename)
        editor.document().setModified(False)

    def load_relation(self, filenames=[]):
        """ Load relation from file """

        if not filenames:
            native_dialog = QFileDialog.DontUseNativeDialog
            if self.__last_open_folder is None:
                directory = os.path.expanduser("~")
            else:
                directory = self.__last_open_folder
            ffilter = settings.RFILES.split(';;')[-1]
            filenames = QFileDialog.getOpenFileNames(self,
                                                     self.tr("Abrir Archivo"),
                                                     directory, ffilter,
                                                     native_dialog)
            if not filenames:
                return
            # Save folder
            self.__last_open_folder = file_manager.get_path(filenames[0])
            self.__modified = True

        # Load tables
        self.table_widget.load_relation(filenames)

    def execute_queries(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.execute_queries()

    def undo_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.undo()

    def redo_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.redo()

    def cut_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.cut()

    def copy_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.copy()

    def paste_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.paste()

    def check_opened_query_files(self):
        query_widget = Pireal.get_service("query_widget")
        return query_widget.opened_files()
コード例 #5
0
class E4SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown, if the
    current tab is clicked again.
    """
    Version = 1
    
    North = 0
    East  = 1
    South = 2
    West  = 3
    
    def __init__(self, orientation = None, delay = 200, parent = None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East, South, West)
        @param delay value for the expand/shrink delay in milliseconds (integer)
        @param parent parent widget (QWidget)
        """
        QWidget.__init__(self, parent)
        
        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.trUtf8("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setMargin(0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setMargin(0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)
        
        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.connect(self.__delayTimer, SIGNAL("timeout()"), self.__delayedAction)
        
        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()
        
        self.splitter = None
        self.splitterSizes = []
        
        self.__hasFocus = False # flag storing if this widget or any child has the focus
        self.__autoHide = False
        
        self.__tabBar.installEventFilter(self)
        
        self.__orientation = E4SideBar.North
        if orientation is None:
            orientation = E4SideBar.North
        self.setOrientation(orientation)
        
        self.connect(self.__tabBar, SIGNAL("currentChanged(int)"), 
                     self.__stackedWidget, SLOT("setCurrentIndex(int)"))
        self.connect(e4App(), SIGNAL("focusChanged(QWidget*, QWidget*)"), 
                     self.__appFocusChanged)
        self.connect(self.__autoHideButton, SIGNAL("toggled(bool)"), 
                     self.__autoHideToggled)
    
    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.connect(self.splitter, SIGNAL("splitterMoved(int, int)"), 
                     self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)
    
    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
    
    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()
    
    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds (integer)
        """
        self.__delayTimer.setInterval(delay)
    
    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()
    
    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None
    
    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()
   
    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
        
        self.__stackedWidget.hide()
        
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())
        
        self.__actionMethod = None
    
    def expand(self):
        """
        Private method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()
    
    def __expandIt(self):
        """
        Public method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)
        
        self.__actionMethod = None
    
    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized
    
    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide
    
    def eventFilter(self, obj, evt):
        """
        Protected method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break
                
                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel and not self.__stackedWidget.isHidden():
                if evt.delta() > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True
        
        return QWidget.eventFilter(self, obj, evt)
    
    def addTab(self, widget, iconOrLabel, label = None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string or QString)
        @param label the labeltext of the tab (string or QString) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def insertTab(self, index, widget, iconOrLabel, label = None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string or QString)
        @param label the labeltext of the tab (string or QString) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)
    
    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()
    
    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()
    
    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
    
    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()
    
    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()
    
    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)
    
    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)
    
    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)
    
    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation
    
    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.
        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E4SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E4SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E4SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E4SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient
    
    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)
    
    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)
    
    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (QString)
        """
        return self.__tabBar.tabText(index)
    
    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (QString)
        """
        self.__tabBar.setTabText(index, text)
    
    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (QString)
        """
        return self.__tabBar.tabToolTip(index)
    
    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tooltip text text to set (QString)
        """
        self.__tabBar.setTabToolTip(index, tip)
    
    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (QString)
        """
        return self.__tabBar.tabWhatsThis(index)
    
    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param WhatsThis text text to set (QString)
        """
        self.__tabBar.setTabWhatsThis(index, text)
    
    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)
    
    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E4SideBar.North, E4SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()
        
        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        
        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)
        
        return data
    
    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False
        
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()
        
        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.readUInt16()  # version
        minimized = stream.readBool()
        
        if minimized:
            self.shrink()
        
        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())
        
        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)
        
        if not minimized:
            self.expand()
        
        return True
    
    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################
    
    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff.png"))
    
    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()
    
    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()
    
    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()
    
    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted properly.
        It disconnects from the focusChanged signal in order to avoid trouble later
        on.
        """
        self.disconnect(e4App(), SIGNAL("focusChanged(QWidget*, QWidget*)"), 
                        self.__appFocusChanged)
コード例 #6
0
ファイル: container.py プロジェクト: intermezzo-fr/pireal
class Container(QSplitter):

    def __init__(self, orientation=Qt.Vertical):
        super(Container, self).__init__(orientation)
        self._data_bases = []
        self.__filename = ""
        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)
        self.__created = False
        # Stacked
        self.stacked = QStackedWidget()
        vbox.addWidget(self.stacked)

        # Table
        self.table_widget = table_widget.TableWidget()

        Pireal.load_service("container", self)

    def create_data_base(self, filename=''):
        """ This function opens or creates a database

        :param filename: Database filename
        """

        if self.__created:
            QMessageBox.critical(self, self.tr("Error"),
                                 self.tr("Solo puede tener una base de datos "
                                         "abierta a la vez."))
            return
        from_file = False
        if not filename:
            db_name, ok = QInputDialog.getText(self, self.tr("Nueva DB"),
                                               self.tr("Nombre:"))
            if not ok:
                return
        else:
            from_file = True
            # Read database from file
            try:
                data = file_manager.open_database(filename)
            except Exception as reason:
                QMessageBox.critical(self, self.tr("Error!"),
                                     reason.__str__())
                return
            # This is intended to give support multiple database
            for name, files in data.items():
                db_name = name
        # Remove the start page
        self.stacked.removeWidget(self.stacked.widget(0))
        self.stacked.addWidget(self.table_widget)
        pireal = Pireal.get_service("pireal")
        # Title
        pireal.change_title(db_name)
        if from_file:
            self.load_relation(files)
        # Enable QAction's
        pireal.enable_disable_db_actions()
        self.__created = True

    def create_new_relation(self):
        dialog = new_relation_dialog.NewRelationDialog(self)
        dialog.show()

    def remove_relation(self):
        lateral = Pireal.get_service("lateral")
        lateral.remove_table()

    def new_query(self, filename=''):
        query_widget = Pireal.get_service("query_widget")
        self.addWidget(query_widget)
        if not query_widget.isVisible():
            query_widget.show()
        pireal = Pireal.get_service("pireal")
        pireal.enable_disable_query_actions()
        query_widget.new_query(filename)

        self.connect(query_widget,
                     SIGNAL("currentEditorSaved(QPlainTextEdit)"),
                     self.save_query)

    def show_start_page(self):
        sp = start_page.StartPage()
        self.stacked.addWidget(sp)

    def close_db(self):
        widget = self.stacked.currentWidget()
        if isinstance(widget, table_widget.TableWidget):
            self.close()

    def save_query(self, weditor=None):
        if weditor is None:
            query_widget = Pireal.get_service("query_widget")
            # Editor instance
            weditor = query_widget.get_active_editor()
        if weditor.rfile.is_new:
            return self.save_query_as(weditor)
        content = weditor.toPlainText()
        weditor.rfile.write(content)
        weditor.document().setModified(False)

        self.emit(SIGNAL("currentFileSaved(QString)"),
                  self.tr("Archivo guardado: {}").format(weditor.filename))

    def open_file(self):

        directory = os.path.expanduser("~")
        filename = QFileDialog.getOpenFileName(self, self.tr("Abrir Archivo"),
                                               directory, settings.DBFILE,
                                               QFileDialog.DontUseNativeDialog)
        if not filename:
            return
        ext = file_manager.get_extension(filename)
        if ext == '.pqf':
            # Query file
            self.new_query(filename)
        else:
            self.create_data_base(filename)

    def save_query_as(self, editor=None):
        if editor is None:
            query_widget = Pireal.get_service("query_widget")
            editor = query_widget.get_active_editor()
        directory = os.path.expanduser("~")
        filename = QFileDialog.getSaveFileName(self,
                                               self.tr("Guardar Archivo"),
                                               directory)
        if not filename:
            return
        content = editor.toPlainText()
        editor.rfile.write(content, filename)
        editor.document().setModified(False)

    def load_relation(self, filenames=[]):
        """ Load relation from file """

        import csv
        from PyQt4.QtGui import QTableWidgetItem, QTableWidget
        from src.core import relation

        if not filenames:
            native_dialog = QFileDialog.DontUseNativeDialog
            directory = os.path.expanduser("~")
            ffilter = settings.RFILES.split(';;')[-1]
            filenames = QFileDialog.getOpenFileNames(self,
                                                     self.tr("Abrir Archivo"),
                                                     directory, ffilter,
                                                     native_dialog)
            if not filenames:
                return
        lateral = Pireal.get_service("lateral")
        for filename in filenames:
            rel = relation.Relation(filename)
            relation_name = os.path.splitext(os.path.basename(filename))[0]
            self.table_widget.relations[relation_name] = rel
            table = QTableWidget()
            with open(filename, newline='') as f:
                table.setRowCount(0)
                table.setColumnCount(0)
                csv_reader = csv.reader(f)
                for row_data in csv_reader:
                    row = table.rowCount()
                    table.setColumnCount(len(row_data))
                    for column, data in enumerate(row_data):
                        item = QTableWidgetItem()
                        item.setText(data)
                        if row == 0:
                            table.setHorizontalHeaderItem(column, item)
                        else:
                            table.setItem(row - 1, column, item)
                    table.insertRow(row)
                table.removeRow(table.rowCount() - 1)
            self.table_widget.stacked.addWidget(table)
        #FIXME: names
        names = [os.path.splitext(os.path.basename(i))[0]
                 for i in filenames]
        lateral.add_item_list(names)
        lateral.show()

    def execute_queries(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.execute_queries()

    def undo_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.undo()

    def redo_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.redo()

    def cut_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.cut()

    def copy_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.copy()

    def paste_action(self):
        query_widget = Pireal.get_service("query_widget")
        query_widget.paste()

    def check_opened_query_files(self):
        query_widget = Pireal.get_service("query_widget")
        return query_widget.opened_files()
コード例 #7
0
class EditorContainer(QWidget):

    # Señales
    closedFile = pyqtSignal(int)
    savedFile = pyqtSignal('QString')
    cursorPosition = pyqtSignal(int, int, int)
    updateSymbols = pyqtSignal('PyQt_PyObject')
    fileChanged = pyqtSignal('QString')
    openedFile = pyqtSignal('QString')

    def __init__(self, edis=None):
        QWidget.__init__(self, edis)
        self.setAcceptDrops(True)
        self.box = QVBoxLayout(self)
        self.box.setContentsMargins(0, 0, 0, 0)
        self.box.setSpacing(0)

        # Stacked
        self.stack = QStackedWidget()
        self.box.addWidget(self.stack)

        # Replace widget
        #FIXME: mover esto
        self._replace_widget = replace_widget.ReplaceWidget()
        self._replace_widget.hide()
        self.box.addWidget(self._replace_widget)

        # Editor widget
        self.editor_widget = editor_widget.EditorWidget()

        # Conexiones
        self.connect(self.editor_widget, SIGNAL("saveCurrentFile()"),
                     self.save_file)
        self.connect(self.editor_widget, SIGNAL("fileClosed(int)"),
                     self._file_closed)
        self.connect(self.editor_widget, SIGNAL("recentFile(QStringList)"),
                     self.update_recents_files)
        self.connect(self.editor_widget, SIGNAL("allFilesClosed()"),
                     self.add_start_page)
        self.connect(self.editor_widget, SIGNAL("currentWidgetChanged(int)"),
                     self.change_widget)

        Edis.load_component("principal", self)

    def update_recents_files(self, recents_files):
        """ Actualiza el submenú de archivos recientes """

        menu = Edis.get_component("menu_recent_file")
        self.connect(menu, SIGNAL("triggered(QAction*)"),
                     self._open_recent_file)
        menu.clear()
        for _file in recents_files:
            menu.addAction(_file)

    def _open_recent_file(self, accion):
        """ Abre el archivo desde el menú """

        self.open_file(accion.text())

    def get_recents_files(self):
        """ Devuelve una lista con los archivos recientes en el menú """

        menu = Edis.get_component('menu_recent_file')
        actions = menu.actions()
        recents_files = []
        for filename in actions:
            recents_files.append(filename.text())
        return recents_files

    def _file_closed(self, index):
        self.closedFile.emit(index)

    def _file_modified(self, value):
        self.editor_widget.editor_modified(value)

    def _file_saved(self, filename):
        self.editor_widget.editor_modified(False)
        self.emit(SIGNAL("updateSymbols(QString)"), filename)

    def change_widget(self, index):
        weditor = self.get_active_editor()
        self.editor_widget.combo.combo_file.setCurrentIndex(index)
        if weditor is not None and not weditor.obj_file.is_new:
            self.emit(SIGNAL("updateSymbols(QString)"), weditor.filename)
            self.emit(SIGNAL("fileChanged(QString)"), weditor.filename)
            weditor.setFocus()

    def create_editor(self, obj_file=None, filename=""):
        if obj_file is None:
            obj_file = object_file.EdisFile(filename)
        self.stack.addWidget(self.editor_widget)
        # Quito la página de inicio, si está
        _start_page = self.stack.widget(0)
        if isinstance(_start_page, start_page.StartPage):
            self.remove_widget(_start_page)
            # Detengo el tiimer
            _start_page.timer.stop()
        weditor = editor.Editor(obj_file)
        self.editor_widget.add_widget(weditor)
        self.editor_widget.add_item_combo(obj_file.filename)
        lateral = Edis.get_component("tab_container")
        if not lateral.isVisible():
            lateral.show()

        # Conexiones
        self.connect(obj_file, SIGNAL("fileChanged(PyQt_PyObject)"),
                     self._file_changed)
        self.connect(weditor, SIGNAL("cursorPositionChanged(int, int)"),
                     self.update_cursor)
        self.connect(weditor, SIGNAL("modificationChanged(bool)"),
                     self._file_modified)
        self.connect(weditor, SIGNAL("fileSaved(QString)"),
                     self._file_saved)
        self.connect(weditor, SIGNAL("linesChanged(int)"),
                     self.editor_widget.combo.move_to_symbol)
        self.connect(weditor, SIGNAL("dropEvent(PyQt_PyObject)"),
                     self._drop_editor)
        self.emit(SIGNAL("fileChanged(QString)"), obj_file.filename)

        weditor.setFocus()

        return weditor

    def _file_changed(self, obj_file):
        filename = obj_file.filename
        flags = QMessageBox.Yes
        flags |= QMessageBox.No
        result = QMessageBox.information(self, self.tr("Monitoreo de Archivo"),
                                         self.tr("El archivo <b>{0}</b> fué "
                                         "modificado fuera de Edis. "
                                         "<br><br> Quieres recar"
                                         "garlo?".format(filename)), flags)
        if result == QMessageBox.No:
            return
        self.reload_file(obj_file)

    def reload_file(self, obj_file=None):
        weditor = self.get_active_editor()
        if weditor is None:
            return
        if obj_file is None:
            obj_file = weditor.obj_file
        content = obj_file.read()
        if weditor.is_modified:
            result = QMessageBox.information(self, self.tr(
                "Archivo no guardado!"),
                self.tr("Seguro que quieres recargar el archivo <b>{0}</b>?"
                        "<br><br>"
                        "Se perderán los cambios no guardados.").format(
                            obj_file.filename),
                QMessageBox.Cancel | QMessageBox.Yes)
            if result == QMessageBox.Cancel:
                return
        weditor.setText(content)
        weditor.markerDeleteAll()
        weditor.setModified(False)

    def open_file(self, filename="", cursor_position=None):
        filter_files = "Archivos C(*.cpp *.c);;ASM(*.s);;HEADERS(*.h);;(*.*)"
        if not filename:
            working_directory = os.path.expanduser("~")
            weditor = self.get_active_editor()
            if weditor and weditor.filename:
                working_directory = self._last_folder(weditor.filename)
            filenames = QFileDialog.getOpenFileNames(self,
                                                     self.tr("Abrir Archivo"),
                                                     working_directory,
                                                     filter_files)
        else:
            filenames = [filename]
        try:
            for _file in filenames:
                if not self._is_open(_file):
                    #self.editor_widget.not_open = False
                    # Creo el objeto Edis File
                    obj_file = object_file.EdisFile(_file)
                    content = obj_file.read()
                    weditor = self.create_editor(obj_file, _file)
                    weditor.setText(content)
                    # Cuando se setea el contenido en el editor
                    # se emite la señal textChanged() por lo tanto se agrega
                    # el marker, entonces se procede a borrarlo
                    weditor.markerDelete(0, 3)
                    # FIXME: Cursor position not found
                    #if cursor_position is not None:
                        #line, row = cursor_position
                        #weditor.setCursorPosition(line, row)
                    weditor.setModified(False)
                    obj_file.run_system_watcher()
                else:
                    # Se cambia el índice del stacked
                    # para mostrar el archivo que ya fué abierto
                    for index in range(self.editor_widget.count()):
                        editor = self.editor_widget.widget(index)
                        if editor.filename == _file:
                            self.change_widget(index)

                self.emit(SIGNAL("fileChanged(QString)"), _file)
                self.emit(SIGNAL("openedFile(QString)"), _file)
                self.emit(SIGNAL("updateSymbols(QString)"), _file)
        except EdisIOError as error:
            ERROR('Error al intentar abrir archivo: %s', error)
            QMessageBox.critical(self, self.tr('No se pudo abrir el archivo'),
                                 str(error))
        #self.editor_widget.not_open = True

    def _last_folder(self, path):
        """ Devuelve la última carpeta a la que se accedió """

        return QFileInfo(path).absolutePath()

    def _is_open(self, archivo):
        """
        Retorna True si un archivo ya esta abierto,
        False en caso contrario

        """

        for index in range(self.editor_widget.count()):
            widget = self.editor_widget.widget(index)
            if widget.filename == archivo:
                return True
        return False

    def add_widget(self, widget):
        """ Agrega @widget al stacked """

        self.editor_widget.add_widget(widget)

    def add_start_page(self):
        """ Agrega la página de inicio al stack """

        if settings.get_setting('general/show-start-page'):
            _start_page = start_page.StartPage()
            self.stack.insertWidget(0, _start_page)
            self.stack.setCurrentIndex(0)
        else:
            self.editor_widget.combo.setVisible(False)

    def remove_widget(self, widget):
        """ Elimina el @widget del stacked """

        self.stack.removeWidget(widget)

    def current_widget(self):
        """ Widget actual """

        return self.editor_widget.current_widget()

    def current_index(self):
        return self.editor_widget.current_index()

    def get_active_editor(self):
        """ Devuelve el Editor si el widget actual es una instancia de él,
        de lo contrario devuelve None. """

        widget = self.current_widget()
        if isinstance(widget, editor.Editor):
            return widget
        return None

    def close_file(self):
        self.editor_widget.close_file()

    def close_file_from_project(self, filename):
        #FIXME: revisar
        for index in range(self.editor_widget.count()):
            widget = self.editor_widget.widget(index)
            if widget.filename == filename:
                editor, i = widget, index
        self.editor_widget.close_file_project(editor, i)

    def close_all(self):
        self.editor_widget.close_all()

    def show_selector(self):
        if self.get_active_editor() is not None:
            selector = file_selector.FileSelector(self)
            selector.show()

    def save_file(self, weditor=None):
        if weditor is None:
            weditor = self.get_active_editor()
            if weditor is None:
                return
        if weditor.obj_file.is_new:
            return self.save_file_as(weditor)
        source_code = weditor.text()
        weditor.obj_file.write(source_code)
        weditor.saved()
        # System watcher
        weditor.obj_file.run_system_watcher()
        self.savedFile.emit(weditor.filename)
        return weditor.filename

    def save_file_as(self, weditor=None):
        if weditor is None:
            weditor = self.get_active_editor()
            if weditor is None:
                return
        working_directory = os.path.expanduser("~")
        filename = QFileDialog.getSaveFileName(self, self.tr("Guardar Archivo"),
                                               working_directory)
        if not filename:
            return False
        content = weditor.text()
        weditor.obj_file.write(content, filename)
        weditor.saved()
        self.fileChanged.emit(weditor.filename)
        self.savedFile.emit(weditor.filename)
        return filename

    def save_selected(self, filename):
        for index in range(self.editor_widget.count()):
            if self.editor_widget.widget(index).filename == filename:
                self.save_file(self.editor_widget.widget(index))

    def files_not_saved(self):
        return self.editor_widget.files_not_saved()

    def check_files_not_saved(self):
        return self.editor_widget.check_files_not_saved()

    def find(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            dialog = find_popup.PopupBusqueda(self.get_active_editor())
            dialog.show()

    def find_and_replace(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            if self._replace_widget.isVisible():
                self._replace_widget.hide()
                weditor.setFocus()
            else:
                self._replace_widget.show()

    def action_undo(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.undo()

    def action_redo(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.redo()

    def action_cut(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.cut()

    def action_copy(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.copy()

    def action_paste(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.paste()

    def show_tabs_and_spaces(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            tabs_spaces = settings.get_setting('editor/show-tabs-spaces')
            settings.set_setting('editor/show-tabs-spaces', not tabs_spaces)
            weditor.update_options()

    def show_indentation_guides(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            guides = settings.get_setting('editor/show-guides')
            settings.set_setting('editor/show-guides', not guides)
            weditor.update_options()

    def delete_editor_markers(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.markerDeleteAll()

    def action_zoom_in(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.zoom_in()

    def action_zoom_out(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.zoom_out()

    def action_normal_size(self):
        """ Carga el tamaño por default de la fuente """

        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.zoomTo(0)

    def action_select_all(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.selectAll()

    def opened_files(self):
        return self.editor_widget.opened_files()

    def opened_files_for_selector(self):
        self.index_file_selector = 0
        files = []
        for index in range(self.editor_widget.count()):
            weditor = self.editor_widget.widget(index)
            path = weditor.filename
            if not path:
                path = weditor.display + ' (%s)' % self.index_file_selector
                self.index_file_selector += 1
            files.append(path)
        return files

    def get_open_projects(self):
        tree_projects = Edis.get_lateral("tree_projects")
        return tree_projects.get_open_projects()

    def file_properties(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            dialog = file_properties.FileProperty(weditor, self)
            dialog.show()

    def update_cursor(self, line, row):
        weditor = self.get_active_editor()
        lines = weditor.lines()
        self.editor_widget.combo.update_cursor_position(
            line + 1, row + 1, lines)
        self.cursorPosition.emit(line + 1, row + 1, lines)

    def build_source_code(self):
        output = Edis.get_component("output")
        project = Edis.get_lateral("tree_projects")
        weditor = self.get_active_editor()
        if weditor is not None:
            filename = self.save_file()
            if project.sources:
                output.build((filename, project.sources))
            else:
                if filename:
                    output.build((weditor.filename, []))

    def run_binary(self):
        """ Ejecuta el programa objeto """

        output = Edis.get_component("output")
        output.run()

    def build_and_run(self):
        output = Edis.get_component("output")
        weditor = self.get_active_editor()
        if weditor is not None:
            self.save_file()
            output.build_and_run(weditor.filename)

    def clean_construction(self):
        output = Edis.get_component("output")
        output.clean()

    def stop_program(self):
        output = Edis.get_component("output")
        output.stop()

    def action_comment(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.comment()

    def action_uncomment(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.uncomment()

    def action_indent(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.indent_more()

    def action_unindent(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.indent_less()

    def action_to_lowercase(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.to_lowercase()

    def action_to_uppercase(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.to_uppercase()

    def action_to_title(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.to_title()

    def action_duplicate_line(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.duplicate_line()

    def action_delete_line(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.delete_line()

    def action_move_down(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.move_down()

    def action_move_up(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.move_up()

    def reemplazar_tabs_por_espacios(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.reemplazar_tabs_por_espacios()

    def go_to_line(self, line):
        weditor = self.get_active_editor()
        if weditor is not None:
            weditor.setCursorPosition(line, 0)

    def show_go_to_line(self):
        weditor = self.get_active_editor()
        if weditor is not None:
            dialog = goto_line_widget.GoToLineDialog(weditor)
            dialog.show()

    def add_symbols_combo(self, symbols):
        self.editor_widget.add_symbols(symbols)

    def dragEnterEvent(self, event):
        data = event.mimeData()
        if data.hasText():
            # Se acepta el evento de arrastrado
            event.accept()

    def dropEvent(self, event):
        self._drop_event(event)

    def _drop_editor(self, event):
        self._drop_event(event)

    def _drop_event(self, event):
        data = event.mimeData()
        filename = data.urls()[0].toLocalFile()
        self.open_file(filename)

    def show_settings(self):
        preferences_widget = Edis.get_component("preferences")
        current_widget = self.stack.currentWidget()
        if isinstance(current_widget, preferences_widget.__class__):
            return
        self.connect(preferences_widget,
                     SIGNAL("configurationsClose(PyQt_PyObject)"),
                     lambda widget: self.remove_widget(widget))
        index = self.stack.addWidget(preferences_widget)
        self.stack.setCurrentIndex(index)

    def open_project(self, filename='', edis_project=True):
        if edis_project:
            if not filename:
                filename = QFileDialog.getOpenFileName(self,
                                                       self.tr("Cargar "
                                                       "Proyecto"),
                                                       paths.PROJECT_DIR,
                                                       "Archivo Edis(*.epf)")
                if not filename:
                    return
                project_file = json.load(open(filename))
                project_path = project_file.get('path', '')
            else:
                project_path = os.path.dirname(filename)
        else:
            result = QFileDialog.getExistingDirectory(self,
                                                      self.tr("Selecciona "
                                                      "la carpeta"))
            if not result:
                return
            project_path = result
        project_structure = {}
        filter_files = ['.c', '.h']
        for parent, dirs, files in os.walk(project_path):
            files = [fi for fi in files
                     if os.path.splitext(fi)[-1] in filter_files]
            project_structure[parent] = (files, dirs)
        self.emit(SIGNAL("projectOpened(PyQt_PyObject)"),
                  (project_structure, project_path, edis_project, filename))

    def open_directory(self):
        self.open_project(edis_project=False)

    def create_new_project(self):
        project_creator = new_project.NewProjectDialog(self)
        self.connect(project_creator, SIGNAL("projectReady(PyQt_PyObject)"),
                     self._update_data)
        project_creator.show()

    def _update_data(self, data):
        self.emit(SIGNAL("projectReady(PyQt_PyObject)"), data)

    def opened_projects(self):
        pass

    def code_pasting(self):
        if self.get_active_editor() is not None:
            code = self.get_active_editor().text()
            code_pasting = code_pasting_dialog.CodePastingDialog(self, code)
            code_pasting.exec_()

    def show_snake(self):
        from src.ui.widgets.pyborita import pyborita_widget
        w = pyborita_widget.PyboritaWidget(self)
        toolbar = Edis.get_component("toolbar")
        lateral = Edis.get_component("tab_container")
        status = Edis.get_component("status_bar")
        output = Edis.get_component("output")
        widgets = [toolbar, status, lateral, output]
        for widget in widgets:
            widget.hide()
        self.stack.insertWidget(0, w)
        self.stack.setCurrentIndex(0)
コード例 #8
0
class DataSelectionGui(QWidget):
    """
    Manages all GUI elements in the data selection applet.
    This class itself is the central widget and also owns/manages the applet drawer widgets.
    """

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################

    def centralWidget(self):
        return self

    def appletDrawer(self):
        return self._drawer

    def menus(self):
        return []

    def viewerControlWidget(self):
        return self._viewerControlWidgetStack

    def setImageIndex(self, imageIndex):
        if imageIndex is not None:
            self.laneSummaryTableView.selectRow(imageIndex)
            for detailWidget in self._detailViewerWidgets:
                detailWidget.selectRow(imageIndex)

    def stopAndCleanUp(self):
        self._cleaning_up = True
        for editor in self.volumeEditors.values():
            self.viewerStack.removeWidget(editor)
            self._viewerControlWidgetStack.removeWidget(
                editor.viewerControlWidget())
            editor.stopAndCleanUp()
        self.volumeEditors.clear()

    def imageLaneAdded(self, laneIndex):
        if len(self.laneSummaryTableView.selectedIndexes()) == 0:
            self.laneSummaryTableView.selectRow(laneIndex)

        # We don't have any real work to do because this gui initiated the lane addition in the first place
        if self.guiMode != GuiMode.Batch:
            if (len(self.topLevelOperator.DatasetGroup) != laneIndex + 1):
                import warnings
                warnings.warn(
                    "DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]"
                    % (len(self.topLevelOperator.DatasetGroup), laneIndex))

    def imageLaneRemoved(self, laneIndex, finalLength):
        # There's nothing to do here because the GUI already
        #  handles operator resizes via slot callbacks.
        pass

    def allowLaneSelectionChange(self):
        return False

    ###########################################
    ###########################################

    class UserCancelledError(Exception):
        # This exception type is raised when the user cancels the
        #  addition of dataset files in the middle of the process somewhere.
        # It isn't an error -- it's used for control flow.
        pass

    def __init__(self,
                 parentApplet,
                 dataSelectionOperator,
                 serializer,
                 instructionText,
                 guiMode=GuiMode.Normal,
                 max_lanes=None,
                 show_axis_details=False):
        """
        Constructor.
        
        :param dataSelectionOperator: The top-level operator.  Must be of type :py:class:`OpMultiLaneDataSelectionGroup`.
        :param serializer: The applet's serializer.  Must be of type :py:class:`DataSelectionSerializer`
        :param instructionText: A string to display in the applet drawer.
        :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``.  Currently, there is no difference between normal and batch mode.
        :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow.  If ``None``, there is no maximum.
        """
        super(DataSelectionGui, self).__init__()
        self._cleaning_up = False
        self.parentApplet = parentApplet
        self._max_lanes = max_lanes
        self._default_h5_volumes = {}
        self.show_axis_details = show_axis_details

        self._viewerControls = QWidget()
        self.topLevelOperator = dataSelectionOperator
        self.guiMode = guiMode
        self.serializer = serializer
        self.threadRouter = ThreadRouter(self)

        self._initCentralUic()
        self._initAppletDrawerUic(instructionText)

        self._viewerControlWidgetStack = QStackedWidget(self)

        def handleImageRemove(multislot, index, finalLength):
            # Remove the viewer for this dataset
            datasetSlot = self.topLevelOperator.DatasetGroup[index]
            if datasetSlot in self.volumeEditors.keys():
                editor = self.volumeEditors[datasetSlot]
                self.viewerStack.removeWidget(editor)
                self._viewerControlWidgetStack.removeWidget(
                    editor.viewerControlWidget())
                editor.stopAndCleanUp()

        self.topLevelOperator.DatasetGroup.notifyRemove(
            bind(handleImageRemove))

        opWorkflow = self.topLevelOperator.parent
        assert hasattr(opWorkflow.shell, 'onSaveProjectActionTriggered'), \
            "This class uses the IlastikShell.onSaveProjectActionTriggered function.  Did you rename it?"

    def _initCentralUic(self):
        """
        Load the GUI from the ui file into this class and connect it with event handlers.
        """
        # Load the ui file into this class (find it in our own directory)
        localDir = os.path.split(__file__)[0] + '/'
        uic.loadUi(localDir + "/dataSelection.ui", self)

        self._initTableViews()
        self._initViewerStack()
        self.splitter.setSizes([150, 850])

    def _initAppletDrawerUic(self, instructionText):
        """
        Load the ui file for the applet drawer, which we own.
        """
        localDir = os.path.split(__file__)[0] + '/'
        self._drawer = uic.loadUi(localDir + "/dataSelectionDrawer.ui")
        self._drawer.instructionLabel.setText(instructionText)

    def _initTableViews(self):
        self.fileInfoTabWidget.setTabText(0, "Summary")
        self.laneSummaryTableView.setModel(
            DataLaneSummaryTableModel(self, self.topLevelOperator))
        self.laneSummaryTableView.dataLaneSelected.connect(self.showDataset)
        self.laneSummaryTableView.addFilesRequested.connect(self.addFiles)
        self.laneSummaryTableView.addStackRequested.connect(self.addStack)
        self.laneSummaryTableView.removeLanesRequested.connect(
            self.handleRemoveLaneButtonClicked)

        # These two helper functions enable/disable an 'add files' button for a given role
        #  based on the the max lane index for that role and the overall permitted max_lanes
        def _update_button_status(viewer, role_index):
            if self._max_lanes:
                viewer.setEnabled(
                    self._findFirstEmptyLane(role_index) < self._max_lanes)

        def _handle_lane_added(button, role_index, lane_slot, lane_index):
            def _handle_role_slot_added(role_slot, added_slot_index, *args):
                if added_slot_index == role_index:
                    role_slot.notifyReady(
                        bind(_update_button_status, button, role_index))
                    role_slot.notifyUnready(
                        bind(_update_button_status, button, role_index))

            lane_slot[lane_index].notifyInserted(_handle_role_slot_added)

        self._retained = []  # Retain menus so they don't get deleted
        self._detailViewerWidgets = []
        for roleIndex, role in enumerate(
                self.topLevelOperator.DatasetRoles.value):
            detailViewer = DatasetDetailedInfoTableView(self)
            detailViewer.setModel(
                DatasetDetailedInfoTableModel(self, self.topLevelOperator,
                                              roleIndex))
            self._detailViewerWidgets.append(detailViewer)

            # Button
            detailViewer.addFilesRequested.connect(
                partial(self.addFiles, roleIndex))
            detailViewer.addStackRequested.connect(
                partial(self.addStack, roleIndex))
            detailViewer.addRemoteVolumeRequested.connect(
                partial(self.addDvidVolume, roleIndex))

            # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab
            self.topLevelOperator.DatasetGroup.notifyInserted(
                bind(_handle_lane_added, detailViewer, roleIndex))
            self.topLevelOperator.DatasetGroup.notifyRemoved(
                bind(_update_button_status, detailViewer, roleIndex))

            # While we're at it, do the same for the buttons in the summary table, too
            self.topLevelOperator.DatasetGroup.notifyInserted(
                bind(_handle_lane_added,
                     self.laneSummaryTableView.addFilesButtons[roleIndex],
                     roleIndex))
            self.topLevelOperator.DatasetGroup.notifyRemoved(
                bind(_update_button_status,
                     self.laneSummaryTableView.addFilesButtons[roleIndex],
                     roleIndex))

            # Context menu
            detailViewer.replaceWithFileRequested.connect(
                partial(self.handleReplaceFile, roleIndex))
            detailViewer.replaceWithStackRequested.connect(
                partial(self.addStack, roleIndex))
            detailViewer.editRequested.connect(
                partial(self.editDatasetInfo, roleIndex))
            detailViewer.resetRequested.connect(
                partial(self.handleClearDatasets, roleIndex))

            # Drag-and-drop
            detailViewer.addFilesRequestedDrop.connect(
                partial(self.addFileNames, roleIndex=roleIndex))

            # Selection handling
            def showFirstSelectedDataset(_roleIndex, lanes):
                if lanes:
                    self.showDataset(lanes[0], _roleIndex)

            detailViewer.dataLaneSelected.connect(
                partial(showFirstSelectedDataset, roleIndex))

            self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role)

        self.fileInfoTabWidget.currentChanged.connect(self.handleSwitchTabs)
        self.fileInfoTabWidget.setCurrentIndex(0)

    def handleSwitchTabs(self, tabIndex):
        if tabIndex < len(self._detailViewerWidgets):
            roleIndex = tabIndex  # If summary tab is moved to the front, change this line.
            detailViewer = self._detailViewerWidgets[roleIndex]
            selectedLanes = detailViewer.selectedLanes
            if selectedLanes:
                self.showDataset(selectedLanes[0], roleIndex)

    def _initViewerStack(self):
        self.volumeEditors = {}
        self.viewerStack.addWidget(QWidget())

    def handleRemoveLaneButtonClicked(self):
        """
        The user clicked the "Remove" button.
        Remove the currently selected row(s) from both the GUI and the top-level operator.
        """
        # Figure out which lanes to remove
        selectedIndexes = self.laneSummaryTableView.selectedIndexes()
        rows = set()
        for modelIndex in selectedIndexes:
            rows.add(modelIndex.row())

        # Don't remove the last row, which is just buttons.
        rows.discard(self.laneSummaryTableView.model().rowCount() - 1)

        # Remove in reverse order so row numbers remain consistent
        for row in reversed(sorted(rows)):
            # Remove lanes from the operator.
            # The table model will notice the changes and update the rows accordingly.
            finalSize = len(self.topLevelOperator.DatasetGroup) - 1
            self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize)

    @threadRouted
    def showDataset(self, laneIndex, roleIndex=None):
        if self._cleaning_up:
            return
        if laneIndex == -1:
            self.viewerStack.setCurrentIndex(0)
            return

        assert threading.current_thread().name == "MainThread"

        if laneIndex >= len(self.topLevelOperator.DatasetGroup):
            return
        datasetSlot = self.topLevelOperator.DatasetGroup[laneIndex]

        # Create if necessary
        if datasetSlot not in self.volumeEditors.keys():

            class DatasetViewer(LayerViewerGui):
                def moveToTop(self, roleIndex):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return
                    datasetRoles = opLaneView.DatasetRoles.value
                    if roleIndex >= len(datasetRoles):
                        return
                    roleName = datasetRoles[roleIndex]
                    try:
                        layerIndex = [l.name
                                      for l in self.layerstack].index(roleName)
                    except ValueError:
                        return
                    else:
                        self.layerstack.selectRow(layerIndex)
                        self.layerstack.moveSelectedToTop()

                def setupLayers(self):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return []
                    layers = []
                    datasetRoles = opLaneView.DatasetRoles.value
                    for roleIndex, slot in enumerate(opLaneView.ImageGroup):
                        if slot.ready():
                            roleName = datasetRoles[roleIndex]
                            layer = self.createStandardLayerFromSlot(slot)
                            layer.name = roleName
                            layers.append(layer)
                    return layers

            opLaneView = self.topLevelOperator.getLane(laneIndex)
            layerViewer = DatasetViewer(self.parentApplet,
                                        opLaneView,
                                        crosshair=False)

            # Maximize the x-y view by default.
            layerViewer.volumeEditorWidget.quadview.ensureMaximized(2)

            self.volumeEditors[datasetSlot] = layerViewer
            self.viewerStack.addWidget(layerViewer)
            self._viewerControlWidgetStack.addWidget(
                layerViewer.viewerControlWidget())

        # Show the right one
        viewer = self.volumeEditors[datasetSlot]
        displayedRole = self.fileInfoTabWidget.currentIndex()
        viewer.moveToTop(displayedRole)
        self.viewerStack.setCurrentWidget(viewer)
        self._viewerControlWidgetStack.setCurrentWidget(
            viewer.viewerControlWidget())

    def handleReplaceFile(self, roleIndex, startingLane):
        self.addFiles(roleIndex, startingLane)

    def addFiles(self, roleIndex, startingLane=None):
        """
        The user clicked the "Add File" button.
        Ask him to choose a file (or several) and add them to both
          the GUI table and the top-level operator inputs.
        """
        # Find the directory of the most recently opened image file
        mostRecentImageFile = PreferencesManager().get('DataSelection',
                                                       'recent image')
        if mostRecentImageFile is not None:
            defaultDirectory = os.path.split(mostRecentImageFile)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        # Launch the "Open File" dialog
        fileNames = self.getImageFileNamesToOpen(self, defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent image',
                                     fileNames[0])
            try:
                self.addFileNames(fileNames, roleIndex, startingLane)
            except Exception as ex:
                log_exception(logger)
                QMessageBox.critical(self, "Error loading file", str(ex))

    @classmethod
    def getImageFileNamesToOpen(cls, parent_window, defaultDirectory):
        """
        Launch an "Open File" dialog to ask the user for one or more image files.
        """
        extensions = OpDataSelection.SupportedExtensions
        filter_strs = ["*." + x for x in extensions]
        filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs]
        filt_all_str = "Image files (" + ' '.join(filter_strs) + ')'

        fileNames = []

        if ilastik_config.getboolean("ilastik", "debug"):
            # use Qt dialog in debug mode (more portable?)
            file_dialog = QFileDialog(parent_window, "Select Images")
            file_dialog.setOption(QFileDialog.DontUseNativeDialog, True)
            # do not display file types associated with a filter
            # the line for "Image files" is too long otherwise
            file_dialog.setFilters([filt_all_str] + filters)
            file_dialog.setNameFilterDetailsVisible(False)
            # select multiple files
            file_dialog.setFileMode(QFileDialog.ExistingFiles)
            file_dialog.setDirectory(defaultDirectory)

            if file_dialog.exec_():
                fileNames = file_dialog.selectedFiles()
        else:
            # otherwise, use native dialog of the present platform
            fileNames = QFileDialog.getOpenFileNames(parent_window,
                                                     "Select Images",
                                                     defaultDirectory,
                                                     filt_all_str)
        # Convert from QtString to python str
        fileNames = map(encode_from_qstring, fileNames)
        return fileNames

    def _findFirstEmptyLane(self, roleIndex):
        opTop = self.topLevelOperator

        # Determine the number of files this role already has
        # Search for the last valid value.
        firstNewLane = 0
        for laneIndex, slot in reversed(
                zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)):
            if slot[roleIndex].ready():
                firstNewLane = laneIndex + 1
                break
        return firstNewLane

    def addFileNames(self, fileNames, roleIndex, startingLane=None, rois=None):
        """
        Add the given filenames to both the GUI table and the top-level operator inputs.
        If startingLane is None, the filenames will be *appended* to the role's list of files.
        
        If rois is provided, it must be a list of (start,stop) tuples (one for each fileName)
        """
        # What lanes will we touch?
        startingLane, endingLane = self._determineLaneRange(
            fileNames, roleIndex, startingLane)
        if startingLane is None:
            # Something went wrong.
            return

        # If we're only adding new lanes, NOT modifying existing lanes...
        adding_only = startingLane == len(self.topLevelOperator)

        # Create a list of DatasetInfos
        try:
            infos = self._createDatasetInfos(roleIndex, fileNames, rois)
        except DataSelectionGui.UserCancelledError:
            return

        # If no exception was thrown so far, set up the operator now
        loaded_all = self._configureOpWithInfos(roleIndex, startingLane,
                                                endingLane, infos)

        if loaded_all:
            # Now check the resulting slots.
            # If they should be copied to the project file, say so.
            self._reconfigureDatasetLocations(roleIndex, startingLane,
                                              endingLane)

            self._checkDataFormatWarnings(roleIndex, startingLane, endingLane)

            # If we succeeded in adding all images, show the first one.
            self.showDataset(startingLane, roleIndex)

        # Notify the workflow that we just added some new lanes.
        if adding_only:
            workflow = self.parentApplet.topLevelOperator.parent
            workflow.handleNewLanesAdded()

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

        self.updateInternalPathVisiblity()

    def _determineLaneRange(self, fileNames, roleIndex, startingLane=None):
        """
        Determine which lanes should be configured if the user wants to add the 
            given fileNames to the specified role, starting at startingLane.
        If startingLane is None, assume the user wants to APPEND the 
            files to the role's slots.
        """
        if startingLane is None or startingLane == -1:
            startingLane = len(self.topLevelOperator.DatasetGroup)
            endingLane = startingLane + len(fileNames) - 1
        else:
            assert startingLane < len(self.topLevelOperator.DatasetGroup)
            max_files = len(self.topLevelOperator.DatasetGroup) - \
                    startingLane
            if len(fileNames) > max_files:
                msg = "You selected {num_selected} files for {num_slots} "\
                      "slots. To add new files use the 'Add new...' option "\
                      "in the context menu or the button in the last row."\
                              .format(num_selected=len(fileNames),
                                      num_slots=max_files)
                QMessageBox.critical(self, "Too many files", msg)
                return (None, None)
            endingLane = min(startingLane + len(fileNames) - 1,
                             len(self.topLevelOperator.DatasetGroup))

        if self._max_lanes and endingLane >= self._max_lanes:
            msg = "You may not add more than {} file(s) to this workflow.  Please try again.".format(
                self._max_lanes)
            QMessageBox.critical(self, "Too many files", msg)
            return (None, None)

        return (startingLane, endingLane)

    def _createDatasetInfos(self, roleIndex, filePaths, rois):
        """
        Create a list of DatasetInfos for the given filePaths and rois
        rois may be None, in which case it is ignored.
        """
        if rois is None:
            rois = [None] * len(filePaths)
        assert len(rois) == len(filePaths)

        infos = []
        for filePath, roi in zip(filePaths, rois):
            info = self._createDatasetInfo(roleIndex, filePath, roi)
            infos.append(info)
        return infos

    def _createDatasetInfo(self, roleIndex, filePath, roi):
        """
        Create a DatasetInfo object for the given filePath and roi.
        roi may be None, in which case it is ignored.
        """
        cwd = self.topLevelOperator.WorkingDirectory.value
        datasetInfo = DatasetInfo(filePath, cwd=cwd)
        datasetInfo.subvolume_roi = roi  # (might be None)

        absPath, relPath = getPathVariants(filePath, cwd)

        # If the file is in a totally different tree from the cwd,
        # then leave the path as absolute.  Otherwise, override with the relative path.
        if relPath is not None and len(os.path.commonprefix([cwd, absPath
                                                             ])) > 1:
            datasetInfo.filePath = relPath

        h5Exts = ['.ilp', '.h5', '.hdf5']
        if os.path.splitext(datasetInfo.filePath)[1] in h5Exts:
            datasetNames = self.getPossibleInternalPaths(absPath)
            if len(datasetNames) == 0:
                raise RuntimeError("HDF5 file %s has no image datasets" %
                                   datasetInfo.filePath)
            elif len(datasetNames) == 1:
                datasetInfo.filePath += str(datasetNames[0])
            else:
                # If exactly one of the file's datasets matches a user's previous choice, use it.
                if roleIndex not in self._default_h5_volumes:
                    self._default_h5_volumes[roleIndex] = set()
                previous_selections = self._default_h5_volumes[roleIndex]
                possible_auto_selections = previous_selections.intersection(
                    datasetNames)
                if len(possible_auto_selections) == 1:
                    datasetInfo.filePath += str(
                        list(possible_auto_selections)[0])
                else:
                    # Ask the user which dataset to choose
                    dlg = H5VolumeSelectionDlg(datasetNames, self)
                    if dlg.exec_() == QDialog.Accepted:
                        selected_index = dlg.combo.currentIndex()
                        selected_dataset = str(datasetNames[selected_index])
                        datasetInfo.filePath += selected_dataset
                        self._default_h5_volumes[roleIndex].add(
                            selected_dataset)
                    else:
                        raise DataSelectionGui.UserCancelledError()

        # Allow labels by default if this gui isn't being used for batch data.
        datasetInfo.allowLabels = (self.guiMode == GuiMode.Normal)
        return datasetInfo

    def _configureOpWithInfos(self, roleIndex, startingLane, endingLane,
                              infos):
        """
        Attempt to configure the specified role and lanes of the 
        top-level operator with the given DatasetInfos.
        
        Returns True if all lanes were configured successfully, or False if something went wrong.
        """
        opTop = self.topLevelOperator
        originalSize = len(opTop.DatasetGroup)

        # Resize the slot if necessary
        if len(opTop.DatasetGroup) < endingLane + 1:
            opTop.DatasetGroup.resize(endingLane + 1)

        # Configure each subslot
        for laneIndex, info in zip(range(startingLane, endingLane + 1), infos):
            try:
                self.topLevelOperator.DatasetGroup[laneIndex][
                    roleIndex].setValue(info)
            except DatasetConstraintError as ex:
                return_val = [False]
                # Give the user a chance to fix the problem
                self.handleDatasetConstraintError(info, info.filePath, ex,
                                                  roleIndex, laneIndex,
                                                  return_val)
                if not return_val[0]:
                    # Not successfully repaired.  Roll back the changes and give up.
                    opTop.DatasetGroup.resize(originalSize)
                    return False
            except OpDataSelection.InvalidDimensionalityError as ex:
                opTop.DatasetGroup.resize(originalSize)
                QMessageBox.critical(self,
                                     "Dataset has different dimensionality",
                                     ex.message)
                return False
            except Exception as ex:
                msg = "Wasn't able to load your dataset into the workflow.  See error log for details."
                log_exception(logger, msg)
                QMessageBox.critical(self, "Dataset Load Error", msg)
                opTop.DatasetGroup.resize(originalSize)
                return False

        return True

    def _reconfigureDatasetLocations(self, roleIndex, startingLane,
                                     endingLane):
        """
        Check the metadata for the given slots.  
        If the data is stored a format that is poorly optimized for 3D access, 
        then configure it to be copied to the project file.
        Finally, save the project if we changed something. 
        """
        save_needed = False
        opTop = self.topLevelOperator
        for lane_index in range(startingLane, endingLane + 1):
            output_slot = opTop.ImageGroup[lane_index][roleIndex]
            if output_slot.meta.prefer_2d and 'z' in output_slot.meta.axistags:
                shape = numpy.array(output_slot.meta.shape)
                total_volume = numpy.prod(shape)

                # Only copy to the project file if the total volume is reasonably small
                if total_volume < 0.5e9:
                    info_slot = opTop.DatasetGroup[lane_index][roleIndex]
                    info = info_slot.value
                    info.location = DatasetInfo.Location.ProjectInternal
                    info_slot.setValue(info, check_changed=False)
                    save_needed = True

        if save_needed:
            logger.info(
                "Some of your data cannot be accessed efficiently in 3D in its current format."
                "  It will now be copied to the project file.")
            opWorkflow = self.topLevelOperator.parent
            opWorkflow.shell.onSaveProjectActionTriggered()

    def _checkDataFormatWarnings(self, roleIndex, startingLane, endingLane):
        warn_needed = False
        opTop = self.topLevelOperator
        for lane_index in range(startingLane, endingLane + 1):
            output_slot = opTop.ImageGroup[lane_index][roleIndex]
            if output_slot.meta.inefficient_format:
                warn_needed = True

        if warn_needed:
            QMessageBox.warning(
                self, "Inefficient Data Format",
                "Your data cannot be accessed efficiently in its current format.  "
                "Check the console output for details.\n"
                "(For HDF5 files, be sure to enable chunking on your dataset.)"
            )

    @threadRouted
    def handleDatasetConstraintError(self,
                                     info,
                                     filename,
                                     ex,
                                     roleIndex,
                                     laneIndex,
                                     return_val=[False]):
        msg = "Can't use default properties for dataset:\n\n" + \
              filename + "\n\n" + \
              "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \
              ex.message + "\n\n" + \
              "Please enter valid dataset properties to continue."
        QMessageBox.warning(self, "Dataset Needs Correction", msg)

        # The success of this is 'returned' via our special out-param
        # (We can't return a value from this func because it is @threadRouted.
        successfully_repaired = self.repairDatasetInfo(info, roleIndex,
                                                       laneIndex)
        return_val[0] = successfully_repaired

    def repairDatasetInfo(self, info, roleIndex, laneIndex):
        """Open the dataset properties editor and return True if the new properties are acceptable."""
        defaultInfos = {}
        defaultInfos[laneIndex] = info
        editorDlg = DatasetInfoEditorWidget(
            self,
            self.topLevelOperator,
            roleIndex, [laneIndex],
            defaultInfos,
            show_axis_details=self.show_axis_details)
        dlg_state = editorDlg.exec_()
        return (dlg_state == QDialog.Accepted)

    @classmethod
    def getPossibleInternalPaths(cls, absPath, min_ndim=3, max_ndim=5):
        datasetNames = []
        # Open the file as a read-only so we can get a list of the internal paths
        with h5py.File(absPath, 'r') as f:
            # Define a closure to collect all of the dataset names in the file.
            def accumulateDatasetPaths(name, val):
                if type(val) == h5py._hl.dataset.Dataset and min_ndim <= len(
                        val.shape) <= max_ndim:
                    datasetNames.append('/' + name)

            # Visit every group/dataset in the file
            f.visititems(accumulateDatasetPaths)
        return datasetNames

    def addStack(self, roleIndex, laneIndex):
        """
        The user clicked the "Import Stack Files" button.
        """
        stackDlg = StackFileSelectionWidget(self)
        stackDlg.exec_()
        if stackDlg.result() != QDialog.Accepted:
            return
        files = stackDlg.selectedFiles
        sequence_axis = stackDlg.sequence_axis
        if len(files) == 0:
            return

        cwd = self.topLevelOperator.WorkingDirectory.value
        info = DatasetInfo(os.path.pathsep.join(files), cwd=cwd)

        originalNumLanes = len(self.topLevelOperator.DatasetGroup)

        if laneIndex is None or laneIndex == -1:
            laneIndex = len(self.topLevelOperator.DatasetGroup)
        if len(self.topLevelOperator.DatasetGroup) < laneIndex + 1:
            self.topLevelOperator.DatasetGroup.resize(laneIndex + 1)

        def importStack():
            self.parentApplet.busy = True
            self.parentApplet.appletStateUpdateRequested.emit()

            # Serializer will update the operator for us, which will propagate to the GUI.
            try:
                self.serializer.importStackAsLocalDataset(info, sequence_axis)
                try:
                    self.topLevelOperator.DatasetGroup[laneIndex][
                        roleIndex].setValue(info)
                except DatasetConstraintError as ex:
                    # Give the user a chance to repair the problem.
                    filename = files[0] + "\n...\n" + files[-1]
                    return_val = [False]
                    self.handleDatasetConstraintError(info, filename, ex,
                                                      roleIndex, laneIndex,
                                                      return_val)
                    if not return_val[0]:
                        # Not successfully repaired.  Roll back the changes and give up.
                        self.topLevelOperator.DatasetGroup.resize(
                            originalNumLanes)
            finally:
                self.parentApplet.busy = False
                self.parentApplet.appletStateUpdateRequested.emit()

        req = Request(importStack)
        req.notify_finished(
            lambda result: self.showDataset(laneIndex, roleIndex))
        req.notify_failed(
            partial(self.handleFailedStackLoad, files, originalNumLanes))
        req.submit()

    @threadRouted
    def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info):
        msg = "Failed to load stack due to the following error:\n{}".format(
            exc)
        msg += "\nAttempted stack files were:\n"
        msg += "\n".join(files)
        log_exception(logger, msg, exc_info)
        QMessageBox.critical(self, "Failed to load image stack", msg)
        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)

    def handleClearDatasets(self, roleIndex, selectedRows):
        for row in selectedRows:
            self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect()

        # Remove all operators that no longer have any connected slots
        laneIndexes = range(len(self.topLevelOperator.DatasetGroup))
        for laneIndex, multislot in reversed(
                zip(laneIndexes, self.topLevelOperator.DatasetGroup)):
            any_ready = False
            for slot in multislot:
                any_ready |= slot.ready()
            if not any_ready:
                self.topLevelOperator.DatasetGroup.removeSlot(
                    laneIndex,
                    len(self.topLevelOperator.DatasetGroup) - 1)

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

    def editDatasetInfo(self, roleIndex, laneIndexes):
        editorDlg = DatasetInfoEditorWidget(
            self,
            self.topLevelOperator,
            roleIndex,
            laneIndexes,
            show_axis_details=self.show_axis_details)
        editorDlg.exec_()
        self.parentApplet.appletStateUpdateRequested.emit()

    def updateInternalPathVisiblity(self):
        for view in self._detailViewerWidgets:
            model = view.model()
            view.setColumnHidden(DatasetDetailedInfoColumn.InternalID,
                                 not model.hasInternalPaths())

    def addDvidVolume(self, roleIndex, laneIndex):
        # TODO: Provide list of recently used dvid hosts, loaded from user preferences
        recent_hosts_pref = PreferencesManager.Setting("DataSelection",
                                                       "Recent DVID Hosts")
        recent_hosts = recent_hosts_pref.get()
        if not recent_hosts:
            recent_hosts = ["localhost:8000"]
        recent_hosts = filter(lambda h: h, recent_hosts)

        from dvidDataSelectionBrowser import DvidDataSelectionBrowser
        browser = DvidDataSelectionBrowser(recent_hosts, parent=self)
        if browser.exec_() == DvidDataSelectionBrowser.Rejected:
            return

        if None in browser.get_selection():
            QMessageBox.critical("Couldn't use your selection.")
            return

        rois = None
        hostname, dset_uuid, volume_name, uuid = browser.get_selection()
        dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format(
            **locals())
        subvolume_roi = browser.get_subvolume_roi()

        # Relocate host to top of 'recent' list, and limit list to 10 items.
        try:
            i = recent_hosts.index(hostname)
            del recent_hosts[i]
        except ValueError:
            pass
        finally:
            recent_hosts.insert(0, hostname)
            recent_hosts = recent_hosts[:10]

        # Save pref
        recent_hosts_pref.set(recent_hosts)

        if subvolume_roi is None:
            self.addFileNames([dvid_url], roleIndex, laneIndex)
        else:
            # In ilastik, we display the dvid volume axes in C-order, despite the dvid convention of F-order
            # Transpose the subvolume roi to match
            # (see implementation of OpDvidVolume)
            start, stop = subvolume_roi
            start = tuple(reversed(start))
            stop = tuple(reversed(stop))
            self.addFileNames([dvid_url], roleIndex, laneIndex,
                              [(start, stop)])
コード例 #9
0
ファイル: table_widget.py プロジェクト: papablopo07/pireal
class TableWidget(QWidget):

    def __init__(self):
        super(TableWidget, self).__init__()

        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)

        self.relations = {}

        # Stack
        self.stacked = QStackedWidget()
        vbox.addWidget(self.stacked)

    def count(self):
        return self.stacked.count()

    def add_data_base(self, data):
        lateral = Pireal.get_service("lateral")
        rel = None
        for part in data.split('@'):
            for e, line in enumerate(part.splitlines()):
                if e == 0:
                    name = line.split(':')[0]
                    rel = relation.Relation()
                    rel.fields = line.split(':')[-1].split(',')
                else:
                    rel.insert(line.split(','))
            if rel is not None:
                table = Table()
                table.setRowCount(1)
                table.setColumnCount(0)
                self.relations[name] = rel

                for _tuple in rel.content:
                    row = table.rowCount()
                    table.setColumnCount(len(rel.fields))
                    for column, text in enumerate(_tuple):
                        item = Item()
                        item.setText(text)
                        table.setItem(row - 1, column, item)
                    table.insertRow(row)
                table.setHorizontalHeaderLabels(rel.fields)
                self.stacked.addWidget(table)
                table.removeRow(table.rowCount() - 1)
                lateral.add_item_list([name])

    def load_relation(self, filenames):
        lateral = Pireal.get_service("lateral")
        for filename in filenames:
            rel = relation.Relation(filename)
            rel_name = file_manager.get_basename(filename)
            self.relations[rel_name] = rel
            table = Table()
            table.setRowCount(1)
            table.setColumnCount(0)

            for _tuple in rel.content:
                row = table.rowCount()
                table.setColumnCount(len(rel.fields))
                for column, text in enumerate(_tuple):
                    item = Item()
                    item.setText(text)
                    table.setItem(row - 1, column, item)
                table.insertRow(row)
            table.removeRow(table.rowCount() - 1)
            table.setHorizontalHeaderLabels(rel.fields)
            self.stacked.addWidget(table)
            lateral.add_item_list([rel_name])

    def add_table(self, rows, columns, name, data, fields):
        table = Table()
        table.setRowCount(rows)
        table.setColumnCount(columns)
        table.setHorizontalHeaderLabels(fields)

        for k, v in list(data.items()):
            item = QTableWidgetItem()
            item.setText(v)
            table.setItem(k[0] - 1, k[1], item)

        self.stacked.addWidget(table)
        self.stacked.setCurrentIndex(self.stacked.count() - 1)
        lateral = Pireal.get_service("lateral")
        lateral.add_item_list([name])

    def add_new_table(self, rel, name):
        import itertools

        table = Table()
        table.setRowCount(0)
        table.setColumnCount(0)

        data = itertools.chain([rel.fields], rel.content)

        for row_data in data:
            row = table.rowCount()
            table.setColumnCount(len(row_data))
            for col, text in enumerate(row_data):
                item = QTableWidgetItem()
                item.setText(text)
                if row == 0:
                    table.setHorizontalHeaderItem(col, item)
                else:
                    table.setItem(row - 1, col, item)
            table.insertRow(row)
        table.removeRow(table.rowCount() - 1)
        self.stacked.addWidget(table)
        self.stacked.setCurrentIndex(self.stacked.count() - 1)
        lateral = Pireal.get_service("lateral")
        lateral.add_item_list([name])

    def remove_table(self, index):
        table = self.stacked.widget(index)
        self.stacked.removeWidget(table)

    def add_table_from_rdb_content(self, content):
        lateral = Pireal.get_service("lateral")
        lateral.show()
        for line in content.splitlines():
            if line.startswith('@'):
                table = Table()
                name = line.split(':')[0][1:]
                lateral.add_item_list([name])
                fields = line.split(':')[-1].split(',')[:-1]
                table.setColumnCount(len(fields))
                table.setHorizontalHeaderLabels(fields)
                self.stacked.addWidget(table)
            else:
                row = table.rowCount()
                for e, i in enumerate(line.split(',')):
                    item = QTableWidgetItem()
                    item.setText(i)
                    table.setItem(row - 1, e, item)
                table.insertRow(row)
コード例 #10
0
class LunchMenuWidget(QWidget):
    textViewIndex = 0
    textViewAdditivesMap = {}
    
    def __init__(self, parent):
        super(LunchMenuWidget, self).__init__(parent)
        
        box = QVBoxLayout(self)
        box.addWidget(QLabel(u"Initializing...", self))
    
    def initializeLayout(self):
        layout = self.layout()
        
        child = layout.takeAt(0)
        while child != None:
            child.widget().deleteLater()
            child = layout.takeAt(0)
        
        self.messages = LunchMenu.messages()
        self.toggleMessages = LunchMenu.toggleMessages()
        
        self.additives = LunchMenu.additives()
        self.toggleAdditives = LunchMenu.toggleAdditives()
        
        buttonBar = self.createButtonBar(self)
        
        layout.addLayout(buttonBar)
        
        self.menuNotebook = QStackedWidget(self)
        self.createNotebook()
        layout.addWidget(self.menuNotebook)
        
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
    
    def create_arrow_button(self, parent, arrow_type):
        button = QToolButton(parent)
        button.setArrowType(arrow_type)
        return button
    
    def goLeft(self):
        curIndex = self.combobox.currentIndex()
        if curIndex > 0:
            self.combobox.setCurrentIndex(curIndex - 1)
    
    def goRight(self):
        curIndex = self.combobox.currentIndex()
        if curIndex < 4:
            self.combobox.setCurrentIndex(curIndex + 1)
    
    def goToday(self):
        now = LunchMenu.today()
        
        minDelta = sys.maxint
        minDeltaI = 0
        i = 0
        for aLunchMenu in LunchMenu.allLunchMenus():
            if aLunchMenu == None or isinstance(aLunchMenu, Exception):
                # parse error, use current day of week
                if now.weekday() < 5:
                    minDeltaI = now.weekday()
                else:
                    minDeltaI = 4
                break
            td = now - aLunchMenu.lunchDate
            delta = abs((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)
            if delta < minDelta:
                minDelta = delta
                minDeltaI = i
            i = i + 1
                
        self.combobox.setCurrentIndex(minDeltaI)
            
    def goTodayClicked(self):
        self.goToday()
        
    def isToggled(self):
        index = self.menuNotebook.currentIndex()
        return (index >= 5)
        
    def changed_combo(self,index):
        if self.isToggled():
            self.menuNotebook.setCurrentIndex(index + 5)
        else:
            self.menuNotebook.setCurrentIndex(index)
        self.leftButton.setEnabled(index != 0)
        self.rightButton.setEnabled(index != 4)
   
    def toggleLanguage(self):
        index = self.menuNotebook.currentIndex()
        isToggle = (index >= 5)
        if isToggle:
            self.switchLanguageButton.setText(self.messages["toggleLanguage"])
            index = index - 5
        else:
            self.switchLanguageButton.setText(self.messages["toggleLanguage2"])
            index = index + 5
        self.menuNotebook.setCurrentIndex(index)
   
    def createButtonBar(self, parent):
        self.combobox = QComboBox(parent)
        self.combobox.addItem(self.messages['monday'])
        self.combobox.addItem(self.messages['tuesday'])
        self.combobox.addItem(self.messages['wednesday'])
        self.combobox.addItem(self.messages['thursday'])
        self.combobox.addItem(self.messages['friday'])
        self.combobox.currentIndexChanged.connect(self.changed_combo)
        comboBoxHeight = self.combobox.sizeHint().height()
        
        self.leftButton = self.create_arrow_button(parent, Qt.LeftArrow)
        self.leftButton.clicked.connect(self.goLeft)
        self.leftButton.setMinimumSize(comboBoxHeight, comboBoxHeight)
        
        self.rightButton = self.create_arrow_button(parent, Qt.RightArrow)
        self.rightButton.clicked.connect(self.goRight)
        self.rightButton.setMinimumSize(comboBoxHeight, comboBoxHeight)
        
        
        navButtons = QHBoxLayout()
        navButtons.addWidget(self.leftButton, 0, Qt.AlignRight)
        navButtons.addWidget(self.combobox, 0, Qt.AlignCenter)
        navButtons.addWidget(self.rightButton, 0, Qt.AlignLeft)
        
        buttonBar = QHBoxLayout()
        todayButton = QPushButton(self.messages['today'], parent)
        todayButton.clicked.connect(self.goTodayClicked)
        todayButton.setMinimumHeight(comboBoxHeight)
        buttonBar.addWidget(todayButton)
        
        buttonBar.addWidget(QWidget(parent), 1)
        buttonBar.addLayout(navButtons, 1)
        buttonBar.addWidget(QWidget(parent), 1)
        
        self.switchLanguageButton = QPushButton(self.messages["toggleLanguage"], parent)
        self.switchLanguageButton.clicked.connect(self.toggleLanguage)
        self.switchLanguageButton.setMinimumHeight(comboBoxHeight)
        buttonBar.addWidget(self.switchLanguageButton, 0, Qt.AlignRight)
                
        return buttonBar
    
    def addMenuLine(self, parent, text, box, header = False):
        aLabel = QLabel(text, parent)
        if header:
            aLabel.setAlignment(Qt.AlignCenter)
            oldFont = aLabel.font()
            aLabel.setFont(QFont(oldFont.family(), 13, QFont.Bold))
        box.addWidget(aLabel, 0, Qt.AlignBottom)
        
    def addLocaleErrorPage(self, parent, box, toggle):
        aLabel = QLabel(self.messages['parseLocaleError'], parent)
        aLabel.setWordWrap(True)
        box.addWidget(aLabel)
        
        aButton = QPushButton(self.messages['installLocaleButton'], parent)
        if toggle:
            aButton.clicked.connect(self.installLanguageSupportToggle)
        else:
            aButton.clicked.connect(self.installLanguageSupport)
        box.addWidget(aButton)
        
    def addExceptionPage(self, parent, box, error, _toggle):
        aLabel = QLabel(self.messages['otherException'] + u" " + unicode(error), parent)
        aLabel.setWordWrap(True)
        box.addWidget(aLabel)
        
    def installLanguageSupportForLocale(self, locale):
        locale = locale.partition("_")[0]
        if subprocess.call(['gksu', "apt-get -q -y install language-pack-%s" % locale])!=0:
            QMessageBox().critical(self.menuNotebook, "Installation Error", self.messages['installLocaleError'], buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok)
        else:
            QMessageBox().information(self.menuNotebook, "Success", self.messages['installLocaleSuccess'], buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok)
    
    def installLanguageSupport(self):
        self.installLanguageSupportForLocale(self.defaultLocaleString)
    def installLanguageSupportToggle(self):
        self.installLanguageSupportForLocale(self.messages['toggleLocale'])

    def formatTitleAndDescription(self, title, description, keyInfo):
        if title and description:
            result = "%s, %s" % (title, description)
        elif title:
            result = title
        else:
            result = description
        
        if keyInfo:
            return "%s: %s" % (keyInfo.title(), result)
        return result

    def addMenuContent(self, parent, desc, menuContents, box, messages, additivesDict):
        self.addMenuLine(parent, desc, box)
        if desc in menuContents:
            contentList = menuContents[desc]
        else:
            contentList = [(messages[u'noContents'], None, [], None)]
            log_debug("lunch menu does not contain key '%s'" % desc)
        
        textview = GrowingTextEdit(parent, messages, additivesDict)
        textview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        textview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        textview.setLineWrapMode(QTextEdit.WidgetWidth)
        textview.setReadOnly(True)
        textview.document().setIndentWidth(10)
        
        if len(contentList) == 1:
            title, description, additives, keyInfo = contentList[0]
            textview.append(self.formatTitleAndDescription(title, description, keyInfo), additives)
        elif len(contentList) > 1:
            cursor = textview.textCursor()
            listFormat = QTextListFormat()
            listFormat.setStyle(QTextListFormat.ListDisc)
            listFormat.setIndent(1)
            cursor.createList(listFormat)
            for title, description, additives, keyInfo in contentList:
                textview.append(self.formatTitleAndDescription(title, description, keyInfo), additives)
        
        box.addWidget(textview, 0)
    
    def createNotebook(self):
        self.combobox.setCurrentIndex(0)
        for _ in range(self.menuNotebook.count()):
            self.menuNotebook.removeWidget(self.menuNotebook.widget(0))
        curMessages = self.messages
        curAdditives = self.additives
        for index in range(10):
            if index == 5:
                try:
                    if getPlatform() != PLATFORM_WINDOWS:
                        locale.setlocale(locale.LC_TIME, (self.messages["toggleLocale"],"UTF-8"))
                except:
                    log_exception("error setting locale")
                curMessages = self.toggleMessages
                curAdditives = self.toggleAdditives
            pageWidget = QWidget(self.menuNotebook)
            page = QVBoxLayout(pageWidget)
            thisLunchMenu = LunchMenu.allLunchMenus()[index]
            if thisLunchMenu != None and type(thisLunchMenu) == LunchMenu:
                title = curMessages['lunchMenuFor'] + u" " + thisLunchMenu.lunchDate.strftime(curMessages['dateFormatDisplayed']).decode("utf-8")
                self.addMenuLine(pageWidget, title, page, True)
                if thisLunchMenu.isValid():
                    self.addMenuContent(pageWidget, curMessages['soupDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives)
                    self.addMenuContent(pageWidget, curMessages['mainDishesDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives)
                    self.addMenuContent(pageWidget, curMessages['supplementsDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives)
                    self.addMenuContent(pageWidget, curMessages['dessertsDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives)
                else:
                    self.addMenuLine(pageWidget, curMessages['noLunchToday'], page)
            elif type(thisLunchMenu) == locale.Error:
                self.addLocaleErrorPage(pageWidget, page, index >= 5)
                pass
            elif isinstance(thisLunchMenu, Exception):
                self.addExceptionPage(pageWidget, page, thisLunchMenu, index >= 5)
            
            self.menuNotebook.addWidget(pageWidget)
        try:
            if getPlatform() != PLATFORM_WINDOWS:
                locale.setlocale(locale.LC_TIME, (LunchMenu.defaultLocaleString,"UTF-8"))
        except:
            log_exception("error setting locale")
        
        self.goToday()
コード例 #11
0
ファイル: preview.py プロジェクト: kzwkt/dff
class Preview(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.stack = QStackedWidget(self)
        self.toolBar = QToolBar(self)
        self.connect(self.toolBar, SIGNAL('actionTriggered(QAction*)'),
                     self.clicked)
        self.layout.addWidget(self.toolBar)
        self.layout.addWidget(self.stack)
        self.setLayout(self.layout)
        self.__mainWindow = parent
        self.name = "Preview"
        self.loader = loader()
        self.lmodules = self.loader.modules
        void = QWidget()
        self.previousWidget = void
        self.stack.addWidget(void)
        self.setWindowIcon(QIcon(QPixmap(":viewer.png")))
        self.retranslateUi(self)
        self.previousNode = None
        self.currentNode = None
        self.mustUpdate = True

    def updateCheckState(self, state):
        self.mustUpdate = state
        if state:
            self.setDisabled(False)
            self.update(self.currentNode)
        else:
            self.setDisabled(True)

    def clicked(self, action):
        if self.isVisible(
        ) and self.mustUpdate and self.currentNode and self.currentNode.size():
            self.display(self.currentNode, str(action.text()))

    def update(self, node):
        if self.isVisible() and self.mustUpdate and node and node.size():
            if self.previousNode == node.this:
                return
            else:
                self.toolBar.clear()
                self.toolBar.addAction("hexadecimal")
                self.previousNode = node.this
                self.currentNode = node
                previewModules = []
                compat = node.compatibleModules()

                if len(compat):
                    for module in compat:
                        if "Viewers" in self.lmodules[module].tags:
                            previewModules.append(module)
                            self.toolBar.addAction(module)

                if not len(previewModules):
                    self.display(node, "hexadecimal")
                else:
                    self.display(node, str(previewModules[0]))
        elif node and node.size():
            self.currentNode = node

    def display(self, node, previewModule):
        try:
            args = {}
            args["file"] = node
            args["preview"] = True
            conf = self.loader.get_conf(previewModule)
            genargs = conf.generate(args)
            if self.previousWidget:
                self.stack.removeWidget(self.previousWidget)
                self.previousWidget.close()
                del self.previousWidget
            self.previousWidget = self.lmodules[previewModule].create()
            self.previousWidget.start(genargs)
            self.previousWidget.g_display()
            if str(self.previousWidget).find("player.PLAYER") == -1:
                self.previousWidget.setAttribute(Qt.WA_DeleteOnClose)
            self.stack.addWidget(self.previousWidget)
        except:
            pass

    def retranslateUi(self, widget):
        widget.setWindowTitle(
            QApplication.translate("Preview", "Preview", None,
                                   QApplication.UnicodeUTF8))
コード例 #12
0
class DataSelectionGui(QWidget):
    """
    Manages all GUI elements in the data selection applet.
    This class itself is the central widget and also owns/manages the applet drawer widgets.
    """

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################

    def centralWidget(self):
        return self

    def appletDrawer(self):
        return self._drawer

    def menus(self):
        return []

    def viewerControlWidget(self):
        return self._viewerControlWidgetStack

    def setImageIndex(self, imageIndex):
        if imageIndex is not None:
            self.laneSummaryTableView.selectRow(imageIndex)
            for detailWidget in self._detailViewerWidgets:
                detailWidget.datasetDetailTableView.selectRow(imageIndex)

    def stopAndCleanUp(self):
        for editor in self.volumeEditors.values():
            self.viewerStack.removeWidget(editor)
            self._viewerControlWidgetStack.removeWidget(
                editor.viewerControlWidget())
            editor.stopAndCleanUp()
        self.volumeEditors.clear()

    def imageLaneAdded(self, laneIndex):
        if len(self.laneSummaryTableView.selectedIndexes()) == 0:
            self.laneSummaryTableView.selectRow(laneIndex)

        # We don't have any real work to do because this gui initiated the lane addition in the first place
        if self.guiMode != GuiMode.Batch:
            if (len(self.topLevelOperator.DatasetGroup) != laneIndex + 1):
                import warnings
                warnings.warn(
                    "DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]"
                    % (len(self.topLevelOperator.Dataset), laneIndex))

    def imageLaneRemoved(self, laneIndex, finalLength):
        # We assume that there's nothing to do here because THIS GUI initiated the lane removal
        if self.guiMode != GuiMode.Batch:
            assert len(self.topLevelOperator.DatasetGroup) == finalLength

    ###########################################
    ###########################################

    def __init__(self,
                 dataSelectionOperator,
                 serializer,
                 guiControlSignal,
                 instructionText,
                 guiMode=GuiMode.Normal,
                 max_lanes=None):
        """
        Constructor.
        
        :param dataSelectionOperator: The top-level operator.  Must be of type :py:class:`OpMultiLaneDataSelectionGroup`.
        :param serializer: The applet's serializer.  Must be of type :py:class:`DataSelectionSerializer`
        :param instructionText: A string to display in the applet drawer.
        :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``.  Currently, there is no difference between normal and batch mode.
        :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow.  If ``None``, there is no maximum.
        """
        super(DataSelectionGui, self).__init__()

        self._max_lanes = max_lanes

        self._viewerControls = QWidget()
        self.topLevelOperator = dataSelectionOperator
        self.guiMode = guiMode
        self.serializer = serializer
        self.guiControlSignal = guiControlSignal
        self.threadRouter = ThreadRouter(self)

        self._initCentralUic()
        self._initAppletDrawerUic(instructionText)

        self._viewerControlWidgetStack = QStackedWidget(self)

        def handleImageRemoved(multislot, index, finalLength):
            # Remove the viewer for this dataset
            imageSlot = self.topLevelOperator.Image[index]
            if imageSlot in self.volumeEditors.keys():
                editor = self.volumeEditors[imageSlot]
                self.viewerStack.removeWidget(editor)
                self._viewerControlWidgetStack.removeWidget(
                    editor.viewerControlWidget())
                editor.stopAndCleanUp()

        self.topLevelOperator.Image.notifyRemove(bind(handleImageRemoved))

    def _initCentralUic(self):
        """
        Load the GUI from the ui file into this class and connect it with event handlers.
        """
        # Load the ui file into this class (find it in our own directory)
        localDir = os.path.split(__file__)[0] + '/'
        uic.loadUi(localDir + "/dataSelection.ui", self)

        self._initTableViews()
        self._initViewerStack()
        self.splitter.setSizes([150, 850])

    def _initAppletDrawerUic(self, instructionText):
        """
        Load the ui file for the applet drawer, which we own.
        """
        localDir = os.path.split(__file__)[0] + '/'
        self._drawer = uic.loadUi(localDir + "/dataSelectionDrawer.ui")
        self._drawer.instructionLabel.setText(instructionText)

    def _initTableViews(self):
        self.fileInfoTabWidget.setTabText(0, "Summary")
        self.laneSummaryTableView.setModel(
            DataLaneSummaryTableModel(self, self.topLevelOperator))
        self.laneSummaryTableView.dataLaneSelected.connect(self.showDataset)
        self.laneSummaryTableView.addFilesRequested.connect(
            self.handleAddFiles)
        self.laneSummaryTableView.addStackRequested.connect(
            self.handleAddStack)
        self.laneSummaryTableView.addByPatternRequested.connect(
            self.handleAddByPattern)
        self.removeLaneButton.clicked.connect(
            self.handleRemoveLaneButtonClicked)
        self.laneSummaryTableView.removeLanesRequested.connect(
            self.handleRemoveLaneButtonClicked)

        # These two helper functions enable/disable an 'add files' button for a given role
        #  based on the the max lane index for that role and the overall permitted max_lanes
        def _update_button_status(button, role_index):
            if self._max_lanes:
                button.setEnabled(
                    self._findFirstEmptyLane(role_index) < self._max_lanes)

        def _handle_lane_added(button, role_index, slot, lane_index):
            slot[lane_index][role_index].notifyReady(
                bind(_update_button_status, button, role_index))
            slot[lane_index][role_index].notifyUnready(
                bind(_update_button_status, button, role_index))

        self._retained = []  # Retain menus so they don't get deleted
        self._detailViewerWidgets = []
        for roleIndex, role in enumerate(
                self.topLevelOperator.DatasetRoles.value):
            detailViewer = DataDetailViewerWidget(self, self.topLevelOperator,
                                                  roleIndex)
            self._detailViewerWidgets.append(detailViewer)

            # Button
            menu = QMenu(parent=self)
            menu.setObjectName("addFileButton_role_{}".format(roleIndex))
            menu.addAction("Add File(s)...").triggered.connect(
                partial(self.handleAddFiles, roleIndex))
            menu.addAction("Add Volume from Stack...").triggered.connect(
                partial(self.handleAddStack, roleIndex))
            menu.addAction("Add Many by Pattern...").triggered.connect(
                partial(self.handleAddByPattern, roleIndex))
            detailViewer.appendButton.setMenu(menu)
            self._retained.append(menu)

            # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab
            self.topLevelOperator.DatasetGroup.notifyInserted(
                bind(_handle_lane_added, detailViewer.appendButton, roleIndex))
            self.topLevelOperator.DatasetGroup.notifyRemoved(
                bind(_update_button_status, detailViewer.appendButton,
                     roleIndex))

            # While we're at it, do the same for the buttons in the summary table, too
            self.topLevelOperator.DatasetGroup.notifyInserted(
                bind(_handle_lane_added,
                     self.laneSummaryTableView.addFilesButtons[roleIndex],
                     roleIndex))
            self.topLevelOperator.DatasetGroup.notifyRemoved(
                bind(_update_button_status,
                     self.laneSummaryTableView.addFilesButtons[roleIndex],
                     roleIndex))

            # Context menu
            detailViewer.datasetDetailTableView.replaceWithFileRequested.connect(
                partial(self.handleReplaceFile, roleIndex))
            detailViewer.datasetDetailTableView.replaceWithStackRequested.connect(
                partial(self.replaceWithStack, roleIndex))
            detailViewer.datasetDetailTableView.editRequested.connect(
                partial(self.editDatasetInfo, roleIndex))
            detailViewer.datasetDetailTableView.resetRequested.connect(
                partial(self.handleClearDatasets, roleIndex))

            # Drag-and-drop
            detailViewer.datasetDetailTableView.addFilesRequested.connect(
                partial(self.addFileNames, roleIndex=roleIndex))

            # Selection handling
            def showFirstSelectedDataset(_roleIndex, lanes):
                if lanes:
                    self.showDataset(lanes[0], _roleIndex)

            detailViewer.datasetDetailTableView.dataLaneSelected.connect(
                partial(showFirstSelectedDataset, roleIndex))

            self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role)

        self.fileInfoTabWidget.currentChanged.connect(self.handleSwitchTabs)
        self.fileInfoTabWidget.setCurrentIndex(0)

    def handleSwitchTabs(self, tabIndex):
        if tabIndex < len(self._detailViewerWidgets):
            roleIndex = tabIndex  # If summary tab is moved to the front, change this line.
            detailViewer = self._detailViewerWidgets[roleIndex]
            selectedLanes = detailViewer.datasetDetailTableView.selectedLanes
            if selectedLanes:
                self.showDataset(selectedLanes[0], roleIndex)

    def _initViewerStack(self):
        self.volumeEditors = {}
        self.viewerStack.addWidget(QWidget())

    def handleRemoveLaneButtonClicked(self):
        """
        The user clicked the "Remove" button.
        Remove the currently selected row(s) from both the GUI and the top-level operator.
        """
        # Figure out which lanes to remove
        selectedIndexes = self.laneSummaryTableView.selectedIndexes()
        rows = set()
        for modelIndex in selectedIndexes:
            rows.add(modelIndex.row())

        # Don't remove the last row, which is just buttons.
        rows.discard(self.laneSummaryTableView.model().rowCount() - 1)

        # Remove in reverse order so row numbers remain consistent
        for row in reversed(sorted(rows)):
            # Remove from the GUI
            self.laneSummaryTableView.model().removeRow(row)
            # Remove from the operator
            finalSize = len(self.topLevelOperator.DatasetGroup) - 1
            self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize)

            # The gui and the operator should be in sync (model has one extra row for the button row)
            assert self.laneSummaryTableView.model().rowCount() == len(
                self.topLevelOperator.DatasetGroup) + 1

    def showDataset(self, laneIndex, roleIndex=None):
        if laneIndex == -1:
            self.viewerStack.setCurrentIndex(0)
            return

        assert threading.current_thread().name == "MainThread"
        imageSlot = self.topLevelOperator.Image[laneIndex]

        # Create if necessary
        if imageSlot not in self.volumeEditors.keys():

            class DatasetViewer(LayerViewerGui):
                def moveToTop(self, roleIndex):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return
                    datasetRoles = opLaneView.DatasetRoles.value
                    if roleIndex >= len(datasetRoles):
                        return
                    roleName = datasetRoles[roleIndex]
                    try:
                        layerIndex = [l.name
                                      for l in self.layerstack].index(roleName)
                    except ValueError:
                        return
                    else:
                        self.layerstack.selectRow(layerIndex)
                        self.layerstack.moveSelectedToTop()

                def setupLayers(self):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return []
                    layers = []
                    datasetRoles = opLaneView.DatasetRoles.value
                    for roleIndex, slot in enumerate(opLaneView.ImageGroup):
                        if slot.ready():
                            roleName = datasetRoles[roleIndex]
                            layer = self.createStandardLayerFromSlot(slot)
                            layer.name = roleName
                            layers.append(layer)
                    return layers

            opLaneView = self.topLevelOperator.getLane(laneIndex)
            layerViewer = DatasetViewer(opLaneView, crosshair=False)

            # Maximize the x-y view by default.
            layerViewer.volumeEditorWidget.quadview.ensureMaximized(2)

            self.volumeEditors[imageSlot] = layerViewer
            self.viewerStack.addWidget(layerViewer)
            self._viewerControlWidgetStack.addWidget(
                layerViewer.viewerControlWidget())

        # Show the right one
        viewer = self.volumeEditors[imageSlot]
        displayedRole = self.fileInfoTabWidget.currentIndex()
        viewer.moveToTop(displayedRole)
        self.viewerStack.setCurrentWidget(viewer)
        self._viewerControlWidgetStack.setCurrentWidget(
            viewer.viewerControlWidget())

    def handleAddFiles(self, roleIndex):
        self.addFiles(roleIndex)

    def handleReplaceFile(self, roleIndex, startingLane):
        self.addFiles(roleIndex, startingLane)

    def addFiles(self, roleIndex, startingLane=None):
        """
        The user clicked the "Add File" button.
        Ask him to choose a file (or several) and add them to both
          the GUI table and the top-level operator inputs.
        """
        # Find the directory of the most recently opened image file
        mostRecentImageFile = PreferencesManager().get('DataSelection',
                                                       'recent image')
        if mostRecentImageFile is not None:
            defaultDirectory = os.path.split(mostRecentImageFile)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        # Launch the "Open File" dialog
        fileNames = self.getImageFileNamesToOpen(defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent image',
                                     fileNames[0])
            try:
                self.addFileNames(fileNames, roleIndex, startingLane)
            except RuntimeError as e:
                QMessageBox.critical(self, "Error loading file", str(e))

    def handleAddByPattern(self, roleIndex):
        # Find the most recent directory

        # TODO: remove code duplication
        mostRecentDirectory = PreferencesManager().get(
            'DataSelection', 'recent mass directory')
        if mostRecentDirectory is not None:
            defaultDirectory = os.path.split(mostRecentDirectory)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        fileNames = self.getMass(defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent mass directory',
                                     os.path.split(fileNames[0])[0])
            try:
                self.addFileNames(fileNames, roleIndex)
            except RuntimeError as e:
                QMessageBox.critical(self, "Error loading file", str(e))

    def getImageFileNamesToOpen(self, defaultDirectory):
        """
        Launch an "Open File" dialog to ask the user for one or more image files.
        """
        extensions = OpDataSelection.SupportedExtensions
        filt = "Image files (" + ' '.join('*.' + x for x in extensions) + ')'
        options = QFileDialog.Options()
        if ilastik_config.getboolean("ilastik", "debug"):
            options |= QFileDialog.DontUseNativeDialog
        fileNames = QFileDialog.getOpenFileNames(self,
                                                 "Select Images",
                                                 defaultDirectory,
                                                 filt,
                                                 options=options)
        # Convert from QtString to python str
        fileNames = [str(s) for s in fileNames]
        return fileNames

    def _findFirstEmptyLane(self, roleIndex):
        opTop = self.topLevelOperator

        # Determine the number of files this role already has
        # Search for the last valid value.
        firstNewLane = 0
        for laneIndex, slot in reversed(
                zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)):
            if slot[roleIndex].ready():
                firstNewLane = laneIndex + 1
                break
        return firstNewLane

    def addFileNames(self, fileNames, roleIndex, startingLane=None):
        """
        Add the given filenames to both the GUI table and the top-level operator inputs.
        If startingLane is None, the filenames will be *appended* to the role's list of files.
        """
        infos = []

        if startingLane is None:
            startingLane = self._findFirstEmptyLane(roleIndex)
            endingLane = startingLane + len(fileNames) - 1
        else:
            assert startingLane < len(self.topLevelOperator.DatasetGroup)
            endingLane = startingLane + len(fileNames) - 1

        if self._max_lanes and endingLane >= self._max_lanes:
            msg = "You may not add more than {} file(s) to this workflow.  Please try again.".format(
                self._max_lanes)
            QMessageBox.critical(self, "Too many files", msg)
            return

        # Assign values to the new inputs we just allocated.
        # The GUI will be updated by callbacks that are listening to slot changes
        for i, filePath in enumerate(fileNames):
            datasetInfo = DatasetInfo()
            cwd = self.topLevelOperator.WorkingDirectory.value

            if not areOnSameDrive(filePath, cwd):
                QMessageBox.critical(
                    self, "Drive Error",
                    "Data must be on same drive as working directory.")
                return

            absPath, relPath = getPathVariants(filePath, cwd)

            # Relative by default, unless the file is in a totally different tree from the working directory.
            if len(os.path.commonprefix([cwd, absPath])) > 1:
                datasetInfo.filePath = relPath
            else:
                datasetInfo.filePath = absPath

            datasetInfo.nickname = PathComponents(absPath).filenameBase

            h5Exts = ['.ilp', '.h5', '.hdf5']
            if os.path.splitext(datasetInfo.filePath)[1] in h5Exts:
                datasetNames = self.getPossibleInternalPaths(absPath)
                if len(datasetNames) > 0:
                    datasetInfo.filePath += str(datasetNames[0])
                else:
                    raise RuntimeError("HDF5 file %s has no image datasets" %
                                       datasetInfo.filePath)

            # Allow labels by default if this gui isn't being used for batch data.
            datasetInfo.allowLabels = (self.guiMode == GuiMode.Normal)
            infos.append(datasetInfo)

        # if no exception was thrown, set up the operator now
        opTop = self.topLevelOperator
        originalSize = len(opTop.DatasetGroup)

        if len(opTop.DatasetGroup) < endingLane + 1:
            opTop.DatasetGroup.resize(endingLane + 1)
        for laneIndex, info in zip(range(startingLane, endingLane + 1), infos):
            try:
                self.topLevelOperator.DatasetGroup[laneIndex][
                    roleIndex].setValue(info)
            except DatasetConstraintError as ex:
                return_val = [False]
                # Give the user a chance to fix the problem
                self.handleDatasetConstraintError(info, info.filePath, ex,
                                                  roleIndex, laneIndex,
                                                  return_val)
                if not return_val[0]:
                    # Not successfully repaired.  Roll back the changes and give up.
                    opTop.DatasetGroup.resize(originalSize)
                    break
            except OpDataSelection.InvalidDimensionalityError as ex:
                opTop.DatasetGroup.resize(originalSize)
                QMessageBox.critical(self,
                                     "Dataset has different dimensionality",
                                     ex.message)
                break
            except:
                QMessageBox.critical(
                    self, "Dataset Load Error",
                    "Wasn't able to load your dataset into the workflow.  See console for details."
                )
                opTop.DatasetGroup.resize(originalSize)
                raise
        self.updateInternalPathVisiblity()

    @threadRouted
    def handleDatasetConstraintError(self,
                                     info,
                                     filename,
                                     ex,
                                     roleIndex,
                                     laneIndex,
                                     return_val=[False]):
        msg = "Can't use default properties for dataset:\n\n" + \
              filename + "\n\n" + \
              "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \
              ex.message + "\n\n" + \
              "Please enter valid dataset properties to continue."
        QMessageBox.warning(self, "Dataset Needs Correction", msg)

        # The success of this is 'returned' via our special out-param
        # (We can't return a value from this func because it is @threadRouted.
        successfully_repaired = self.repairDatasetInfo(info, roleIndex,
                                                       laneIndex)
        return_val[0] = successfully_repaired

    def repairDatasetInfo(self, info, roleIndex, laneIndex):
        """Open the dataset properties editor and return True if the new properties are acceptable."""
        defaultInfos = {}
        defaultInfos[laneIndex] = info
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator,
                                            roleIndex, [laneIndex],
                                            defaultInfos)
        dlg_state = editorDlg.exec_()
        return (dlg_state == QDialog.Accepted)

    def getPossibleInternalPaths(self, absPath):
        datasetNames = []
        # Open the file as a read-only so we can get a list of the internal paths
        with h5py.File(absPath, 'r') as f:
            # Define a closure to collect all of the dataset names in the file.
            def accumulateDatasetPaths(name, val):
                if type(val) == h5py._hl.dataset.Dataset and 3 <= len(
                        val.shape) <= 5:
                    datasetNames.append('/' + name)

            # Visit every group/dataset in the file
            f.visititems(accumulateDatasetPaths)
        return datasetNames

    def handleAddStack(self, roleIndex):
        self.replaceWithStack(roleIndex, laneIndex=None)

    def replaceWithStack(self, roleIndex, laneIndex):
        """
        The user clicked the "Import Stack Files" button.
        """
        stackDlg = StackFileSelectionWidget(self)
        stackDlg.exec_()
        if stackDlg.result() != QDialog.Accepted:
            return
        files = stackDlg.selectedFiles
        if len(files) == 0:
            return

        info = DatasetInfo()
        info.filePath = "//".join(files)
        prefix = os.path.commonprefix(files)
        info.nickname = PathComponents(prefix).filenameBase
        # Add an underscore for each wildcard digit
        num_wildcards = len(files[-1]) - len(prefix) - len(
            os.path.splitext(files[-1])[1])
        info.nickname += "_" * num_wildcards

        # Allow labels by default if this gui isn't being used for batch data.
        info.allowLabels = (self.guiMode == GuiMode.Normal)
        info.fromstack = True

        originalNumLanes = len(self.topLevelOperator.DatasetGroup)

        if laneIndex is None:
            laneIndex = self._findFirstEmptyLane(roleIndex)
        if len(self.topLevelOperator.DatasetGroup) < laneIndex + 1:
            self.topLevelOperator.DatasetGroup.resize(laneIndex + 1)

        def importStack():
            self.guiControlSignal.emit(ControlCommand.DisableAll)
            # Serializer will update the operator for us, which will propagate to the GUI.
            try:
                self.serializer.importStackAsLocalDataset(info)
                try:
                    self.topLevelOperator.DatasetGroup[laneIndex][
                        roleIndex].setValue(info)
                except DatasetConstraintError as ex:
                    # Give the user a chance to repair the problem.
                    filename = files[0] + "\n...\n" + files[-1]
                    return_val = [False]
                    self.handleDatasetConstraintError(info, filename, ex,
                                                      roleIndex, laneIndex,
                                                      return_val)
                    if not return_val[0]:
                        # Not successfully repaired.  Roll back the changes and give up.
                        self.topLevelOperator.DatasetGroup.resize(
                            originalNumLanes)
            finally:
                self.guiControlSignal.emit(ControlCommand.Pop)

        req = Request(importStack)
        req.notify_failed(
            partial(self.handleFailedStackLoad, files, originalNumLanes))
        req.submit()

    @threadRouted
    def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info):
        import traceback
        traceback.print_tb(exc_info[2])
        msg = "Failed to load stack due to the following error:\n{}".format(
            exc)
        msg += "Attempted stack files were:"
        for f in files:
            msg += f + "\n"
        QMessageBox.critical(self, "Failed to load image stack", msg)
        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)

    def getMass(self, defaultDirectory):
        # TODO: launch dialog and get files

        # Convert from QtString to python str
        loader = MassFileLoader(defaultDirectory=defaultDirectory)
        loader.exec_()
        if loader.result() == QDialog.Accepted:
            fileNames = [str(s) for s in loader.filenames]
        else:
            fileNames = []
        return fileNames

    def handleClearDatasets(self, roleIndex, selectedRows):
        for row in selectedRows:
            self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect()

        # Remove all operators that no longer have any connected slots
        last_valid = -1
        laneIndexes = range(len(self.topLevelOperator.DatasetGroup))
        for laneIndex, multislot in reversed(
                zip(laneIndexes, self.topLevelOperator.DatasetGroup)):
            any_ready = False
            for slot in multislot:
                any_ready |= slot.ready()
            if not any_ready:
                self.topLevelOperator.DatasetGroup.removeSlot(
                    laneIndex,
                    len(self.topLevelOperator.DatasetGroup) - 1)

    def editDatasetInfo(self, roleIndex, laneIndexes):
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator,
                                            roleIndex, laneIndexes)
        editorDlg.exec_()

    def updateInternalPathVisiblity(self):
        for widget in self._detailViewerWidgets:
            view = widget.datasetDetailTableView
            model = view.model()
            view.setColumnHidden(DatasetDetailedInfoColumn.InternalID,
                                 not model.hasInternalPaths())
コード例 #13
0
class SideBar(QWidget):
    """ Sidebar with a widget area which is hidden or shown.
        On by clicking any tab, off by clicking the current tab.
    """

    North = 0
    East = 1
    South = 2
    West = 3

    def __init__(self, orientation=2, parent=None):
        QWidget.__init__(self, parent)

        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setFocusPolicy(Qt.NoFocus)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setElideMode(1)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setMargin(0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setMargin(0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)

        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()

        self.splitter = None

        self.__tabBar.installEventFilter(self)

        self.__orientation = orientation
        self.setOrientation(orientation)

        self.__tabBar.currentChanged.connect(
            self.__stackedWidget.setCurrentIndex)
        return

    def setSplitter(self, splitter):
        """ Set the splitter managing the sidebar """
        self.splitter = splitter
        return

    def __getIndex(self):
        " Provides the widget index in splitters "
        if self.__orientation == SideBar.West:
            return 0
        if self.__orientation == SideBar.East:
            return 2
        if self.__orientation == SideBar.South:
            return 1
        return 0

    def __getWidget(self):
        " Provides a reference to the widget "
        return self.splitter.widget(self.__getIndex())

    def shrink(self):
        """ Shrink the sidebar """
        if self.__minimized:
            return

        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [SideBar.North, SideBar.South]:
            self.__minSize = self.minimumHeight()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumWidth()
            self.__maxSize = self.maximumWidth()

        self.__stackedWidget.hide()

        sizes = self.splitter.sizes()
        selfIndex = self.__getIndex()

        if self.__orientation in [SideBar.North, SideBar.South]:
            newHeight = self.__tabBar.minimumSizeHint().height()
            self.setFixedHeight(newHeight)

            diff = sizes[selfIndex] - newHeight
            sizes[selfIndex] = newHeight
        else:
            newWidth = self.__tabBar.minimumSizeHint().width()
            self.setFixedWidth(newWidth)

            diff = sizes[selfIndex] - newWidth
            sizes[selfIndex] = newWidth

        if selfIndex == 0:
            sizes[1] += diff
        else:
            sizes[selfIndex - 1] += diff

        self.splitter.setSizes(sizes)
        return

    def expand(self):
        """ Expand the sidebar """
        if not self.__minimized:
            return

        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)

        sizes = self.splitter.sizes()
        selfIndex = self.__getIndex()

        if self.__orientation in [SideBar.North, SideBar.South]:
            self.setMinimumHeight(self.__minSize)
            self.setMaximumHeight(self.__maxSize)

            diff = self.__bigSize.height() - sizes[selfIndex]
            sizes[selfIndex] = self.__bigSize.height()
        else:
            self.setMinimumWidth(self.__minSize)
            self.setMaximumWidth(self.__maxSize)

            diff = self.__bigSize.width() - sizes[selfIndex]
            sizes[selfIndex] = self.__bigSize.width()

        if selfIndex == 0:
            sizes[1] -= diff
        else:
            sizes[selfIndex - 1] -= diff

        self.splitter.setSizes(sizes)
        return

    def isMinimized(self):
        """ Provides the minimized state """
        return self.__minimized

    def eventFilter(self, obj, evt):
        """ Handle click events for the tabbar """

        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()

                index = self.__tabBar.count() - 1
                while index >= 0:
                    if self.__tabBar.tabRect(index).contains(pos):
                        break
                    index -= 1

                if index == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True

                elif self.isMinimized():
                    if self.isTabEnabled(index):
                        self.expand()

        return QWidget.eventFilter(self, obj, evt)

    def addTab(self, widget, iconOrLabel, label=None):
        """ Add a tab to the sidebar """

        if label:
            self.__tabBar.addTab(iconOrLabel, label)
        else:
            self.__tabBar.addTab(iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        return

    def insertTab(self, index, widget, iconOrLabel, label=None):
        """ Insert a tab into the sidebar """

        if label:
            self.__tabBar.insertTab(index, iconOrLabel, label)
        else:
            self.__tabBar.insertTab(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        return

    def removeTab(self, index):
        """ Remove a tab """

        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        return

    def clear(self):
        """ Remove all tabs """

        while self.count() > 0:
            self.removeTab(0)
        return

    def prevTab(self):
        """ Show the previous tab """

        index = self.currentIndex() - 1
        if index < 0:
            index = self.count() - 1

        self.setCurrentIndex(index)
        self.currentWidget().setFocus()
        return

    def nextTab(self):
        """ Show the next tab """

        index = self.currentIndex() + 1
        if index >= self.count():
            index = 0

        self.setCurrentIndex(index)
        self.currentWidget().setFocus()
        return

    def count(self):
        """ Provides the number of tabs """

        return self.__tabBar.count()

    def currentIndex(self):
        """ Provides the index of the current tab """

        return self.__stackedWidget.currentIndex()

    def setCurrentIndex(self, index):
        """ Switch to the certain tab """

        if index >= self.currentIndex():
            return

        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
        return

    def currentWidget(self):
        """ Provide a reference to the current widget """

        return self.__stackedWidget.currentWidget()

    def setCurrentWidget(self, widget):
        """ Set the current widget """

        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()
        return

    def indexOf(self, widget):
        """ Provides the index of the given widget """

        return self.__stackedWidget.indexOf(widget)

    def isTabEnabled(self, index):
        """ Check if the tab is enabled """

        return self.__tabBar.isTabEnabled(index)

    def setTabEnabled(self, index, enabled):
        """ Set the enabled state of the tab """

        self.__tabBar.setTabEnabled(index, enabled)
        return

    def orientation(self):
        """ Provides the orientation of the sidebar """

        return self.__orientation

    def setOrientation(self, orient):
        """ Set the orientation of the sidebar """

        if orient == SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        else:
            # default
            orient = SideBar.West
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient
        return

    def tabIcon(self, index):
        """ Provide the icon of the tab """

        return self.__tabBar.tabIcon(index)

    def setTabIcon(self, index, icon):
        """ Set the icon of the tab """

        self.__tabBar.setTabIcon(index, icon)
        return

    def tabText(self, index):
        """ Provide the text of the tab """

        return self.__tabBar.tabText(index)

    def setTabText(self, index, text):
        """ Set the text of the tab """

        self.__tabBar.setTabText(index, text)
        return

    def tabToolTip(self, index):
        """ Provide the tooltip text of the tab """

        return self.__tabBar.tabToolTip(index)

    def setTabToolTip(self, index, tip):
        """ Set the tooltip text of the tab """

        self.__tabBar.setTabToolTip(index, tip)
        return

    def tabWhatsThis(self, index):
        """ Provide the WhatsThis text of the tab """

        return self.__tabBar.tabWhatsThis(index)

    def setTabWhatsThis(self, index, text):
        """ Set the WhatsThis text for the tab """

        self.__tabBar.setTabWhatsThis(index, text)
        return

    def widget(self, index):
        """ Provides the reference to the widget (QWidget) """

        return self.__stackedWidget.widget(index)
コード例 #14
0
ファイル: sidebar.py プロジェクト: eaglexmw/codimension
class SideBar( QWidget ):
    """ Sidebar with a widget area which is hidden or shown.
        On by clicking any tab, off by clicking the current tab.
    """

    North = 0
    East  = 1
    South = 2
    West  = 3

    def __init__( self, orientation = 2, parent = None ):
        QWidget.__init__( self, parent )

        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase( True )
        self.__tabBar.setShape( QTabBar.RoundedNorth )
        self.__tabBar.setFocusPolicy( Qt.NoFocus )
        self.__tabBar.setUsesScrollButtons( True )
        self.__tabBar.setElideMode( 1 )
        self.__stackedWidget = QStackedWidget( self )
        self.__stackedWidget.setContentsMargins( 0, 0, 0, 0 )
        self.barLayout = QBoxLayout( QBoxLayout.LeftToRight )
        self.barLayout.setMargin( 0 )
        self.layout = QBoxLayout( QBoxLayout.TopToBottom )
        self.layout.setMargin( 0 )
        self.layout.setSpacing( 0 )
        self.barLayout.addWidget( self.__tabBar )
        self.layout.addLayout( self.barLayout )
        self.layout.addWidget( self.__stackedWidget )
        self.setLayout( self.layout )

        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()

        self.splitter = None

        self.__tabBar.installEventFilter( self )

        self.__orientation = orientation
        self.setOrientation( orientation )

        self.__tabBar.currentChanged.connect(
                                        self.__stackedWidget.setCurrentIndex )
        return

    def setSplitter( self, splitter ):
        """ Set the splitter managing the sidebar """
        self.splitter = splitter
        return

    def __getIndex( self ):
        " Provides the widget index in splitters "
        if self.__orientation == SideBar.West:
            return 0
        if self.__orientation == SideBar.East:
            return 2
        if self.__orientation == SideBar.South:
            return 1
        return 0

    def __getWidget( self ):
        " Provides a reference to the widget "
        return self.splitter.widget( self.__getIndex() )

    def shrink( self ):
        """ Shrink the sidebar """
        if self.__minimized:
            return

        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [ SideBar.North, SideBar.South ]:
            self.__minSize = self.minimumHeight()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumWidth()
            self.__maxSize = self.maximumWidth()

        self.__stackedWidget.hide()

        sizes = self.splitter.sizes()
        selfIndex = self.__getIndex()

        if self.__orientation in [ SideBar.North, SideBar.South ]:
            newHeight = self.__tabBar.minimumSizeHint().height()
            self.setFixedHeight( newHeight )

            diff = sizes[ selfIndex ] - newHeight
            sizes[ selfIndex ] = newHeight
        else:
            newWidth = self.__tabBar.minimumSizeHint().width()
            self.setFixedWidth( newWidth )

            diff = sizes[ selfIndex ] - newWidth
            sizes[ selfIndex ] = newWidth

        if selfIndex == 0:
            sizes[ 1 ] += diff
        else:
            sizes[ selfIndex - 1 ] += diff

        self.splitter.setSizes( sizes )
        return

    def expand( self ):
        """ Expand the sidebar """
        if not self.__minimized:
            return

        self.__minimized = False
        self.__stackedWidget.show()
        self.resize( self.__bigSize )

        sizes = self.splitter.sizes()
        selfIndex = self.__getIndex()

        if self.__orientation in [ SideBar.North, SideBar.South ]:
            self.setMinimumHeight( self.__minSize )
            self.setMaximumHeight( self.__maxSize )

            diff = self.__bigSize.height() - sizes[ selfIndex ]
            sizes[ selfIndex ] = self.__bigSize.height()
        else:
            self.setMinimumWidth( self.__minSize )
            self.setMaximumWidth( self.__maxSize )

            diff = self.__bigSize.width() - sizes[ selfIndex ]
            sizes[ selfIndex ] = self.__bigSize.width()

        if selfIndex == 0:
            sizes[ 1 ] -= diff
        else:
            sizes[ selfIndex - 1 ] -= diff

        self.splitter.setSizes( sizes )
        return

    def isMinimized( self ):
        """ Provides the minimized state """
        return self.__minimized

    def eventFilter( self, obj, evt ):
        """ Handle click events for the tabbar """

        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()

                index = self.__tabBar.count() - 1
                while index >= 0:
                    if self.__tabBar.tabRect( index ).contains( pos ):
                        break
                    index -= 1

                if index == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True

                elif self.isMinimized():
                    if self.isTabEnabled( index ):
                        self.expand()

        return QWidget.eventFilter( self, obj, evt )

    def addTab( self, widget, iconOrLabel, label = None ):
        """ Add a tab to the sidebar """

        if label:
            self.__tabBar.addTab( iconOrLabel, label )
        else:
            self.__tabBar.addTab( iconOrLabel )
        self.__stackedWidget.addWidget( widget )
        return

    def insertTab( self, index, widget, iconOrLabel, label = None ):
        """ Insert a tab into the sidebar """

        if label:
            self.__tabBar.insertTab( index, iconOrLabel, label )
        else:
            self.__tabBar.insertTab( index, iconOrLabel )
        self.__stackedWidget.insertWidget( index, widget )
        return

    def removeTab( self, index ):
        """ Remove a tab """

        self.__stackedWidget.removeWidget( self.__stackedWidget.widget( index ) )
        self.__tabBar.removeTab( index )
        return

    def clear( self ):
        """ Remove all tabs """

        while self.count() > 0:
            self.removeTab( 0 )
        return

    def prevTab( self ):
        """ Show the previous tab """

        index = self.currentIndex() - 1
        if index < 0:
            index = self.count() - 1

        self.setCurrentIndex( index )
        self.currentWidget().setFocus()
        return

    def nextTab( self ):
        """ Show the next tab """

        index = self.currentIndex() + 1
        if index >= self.count():
            index = 0

        self.setCurrentIndex( index )
        self.currentWidget().setFocus()
        return

    def count( self ):
        """ Provides the number of tabs """

        return self.__tabBar.count()

    def currentIndex( self ):
        """ Provides the index of the current tab """

        return self.__stackedWidget.currentIndex()

    def setCurrentIndex( self, index ):
        """ Switch to the certain tab """

        if index >= self.currentIndex():
            return

        self.__tabBar.setCurrentIndex( index )
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
        return

    def currentWidget( self ):
        """ Provide a reference to the current widget """

        return self.__stackedWidget.currentWidget()

    def setCurrentWidget( self, widget ):
        """ Set the current widget """

        self.__stackedWidget.setCurrentWidget( widget )
        self.__tabBar.setCurrentIndex( self.__stackedWidget.currentIndex() )
        if self.isMinimized():
            self.expand()
        return

    def indexOf( self, widget ):
        """ Provides the index of the given widget """

        return self.__stackedWidget.indexOf( widget )

    def isTabEnabled( self, index ):
        """ Check if the tab is enabled """

        return self.__tabBar.isTabEnabled( index )

    def setTabEnabled( self, index, enabled ):
        """ Set the enabled state of the tab """

        self.__tabBar.setTabEnabled( index, enabled )
        return

    def orientation( self ):
        """ Provides the orientation of the sidebar """

        return self.__orientation

    def setOrientation( self, orient ):
        """ Set the orientation of the sidebar """

        if orient == SideBar.North:
            self.__tabBar.setShape( QTabBar.RoundedNorth )
            self.barLayout.setDirection( QBoxLayout.LeftToRight )
            self.layout.setDirection( QBoxLayout.TopToBottom )
            self.layout.setAlignment( self.barLayout, Qt.AlignLeft )
        elif orient == SideBar.East:
            self.__tabBar.setShape( QTabBar.RoundedEast )
            self.barLayout.setDirection( QBoxLayout.TopToBottom )
            self.layout.setDirection( QBoxLayout.RightToLeft )
            self.layout.setAlignment( self.barLayout, Qt.AlignTop )
        elif orient == SideBar.South:
            self.__tabBar.setShape( QTabBar.RoundedSouth )
            self.barLayout.setDirection( QBoxLayout.LeftToRight )
            self.layout.setDirection( QBoxLayout.BottomToTop )
            self.layout.setAlignment( self.barLayout, Qt.AlignLeft )
        else:
            # default
            orient = SideBar.West
            self.__tabBar.setShape( QTabBar.RoundedWest )
            self.barLayout.setDirection( QBoxLayout.TopToBottom )
            self.layout.setDirection( QBoxLayout.LeftToRight )
            self.layout.setAlignment( self.barLayout, Qt.AlignTop )
        self.__orientation = orient
        return

    def tabIcon( self, index ):
        """ Provide the icon of the tab """

        return self.__tabBar.tabIcon( index )

    def setTabIcon( self, index, icon ):
        """ Set the icon of the tab """

        self.__tabBar.setTabIcon( index, icon )
        return

    def tabText( self, index ):
        """ Provide the text of the tab """

        return self.__tabBar.tabText( index )

    def setTabText( self, index, text ):
        """ Set the text of the tab """

        self.__tabBar.setTabText( index, text )
        return

    def tabToolTip( self, index ):
        """ Provide the tooltip text of the tab """

        return self.__tabBar.tabToolTip( index )

    def setTabToolTip( self, index, tip ):
        """ Set the tooltip text of the tab """

        self.__tabBar.setTabToolTip( index, tip )
        return

    def tabWhatsThis( self, index ):
        """ Provide the WhatsThis text of the tab """

        return self.__tabBar.tabWhatsThis( index )

    def setTabWhatsThis( self, index, text ):
        """ Set the WhatsThis text for the tab """

        self.__tabBar.setTabWhatsThis( index, text )
        return

    def widget( self, index ):
        """ Provides the reference to the widget (QWidget) """

        return self.__stackedWidget.widget( index )
コード例 #15
0
class LunchMenuWidget(QWidget):
    textViewIndex = 0
    textViewAdditivesMap = {}

    def __init__(self, parent):
        super(LunchMenuWidget, self).__init__(parent)

        box = QVBoxLayout(self)
        box.addWidget(QLabel(u"Initializing...", self))

    def initializeLayout(self):
        layout = self.layout()

        child = layout.takeAt(0)
        while child != None:
            child.widget().deleteLater()
            child = layout.takeAt(0)

        self.messages = LunchMenu.messages()
        self.toggleMessages = LunchMenu.toggleMessages()

        self.additives = LunchMenu.additives()
        self.toggleAdditives = LunchMenu.toggleAdditives()

        buttonBar = self.createButtonBar(self)

        layout.addLayout(buttonBar)

        self.menuNotebook = QStackedWidget(self)
        self.createNotebook()
        layout.addWidget(self.menuNotebook)

        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)

    def create_arrow_button(self, parent, arrow_type):
        button = QToolButton(parent)
        button.setArrowType(arrow_type)
        return button

    def goLeft(self):
        curIndex = self.combobox.currentIndex()
        if curIndex > 0:
            self.combobox.setCurrentIndex(curIndex - 1)

    def goRight(self):
        curIndex = self.combobox.currentIndex()
        if curIndex < 4:
            self.combobox.setCurrentIndex(curIndex + 1)

    def goToday(self):
        now = LunchMenu.today()

        minDelta = sys.maxint
        minDeltaI = 0
        i = 0
        for aLunchMenu in LunchMenu.allLunchMenus():
            if aLunchMenu == None or isinstance(aLunchMenu, Exception):
                # parse error, use current day of week
                if now.weekday() < 5:
                    minDeltaI = now.weekday()
                else:
                    minDeltaI = 4
                break
            td = now - aLunchMenu.lunchDate
            delta = abs((td.microseconds +
                         (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)
            if delta < minDelta:
                minDelta = delta
                minDeltaI = i
            i = i + 1

        self.combobox.setCurrentIndex(minDeltaI)

    def goTodayClicked(self):
        self.goToday()

    def isToggled(self):
        index = self.menuNotebook.currentIndex()
        return (index >= 5)

    def changed_combo(self, index):
        if self.isToggled():
            self.menuNotebook.setCurrentIndex(index + 5)
        else:
            self.menuNotebook.setCurrentIndex(index)
        self.leftButton.setEnabled(index != 0)
        self.rightButton.setEnabled(index != 4)

    def toggleLanguage(self):
        index = self.menuNotebook.currentIndex()
        isToggle = (index >= 5)
        if isToggle:
            self.switchLanguageButton.setText(self.messages["toggleLanguage"])
            index = index - 5
        else:
            self.switchLanguageButton.setText(self.messages["toggleLanguage2"])
            index = index + 5
        self.menuNotebook.setCurrentIndex(index)

    def createButtonBar(self, parent):
        self.combobox = QComboBox(parent)
        self.combobox.addItem(self.messages['monday'])
        self.combobox.addItem(self.messages['tuesday'])
        self.combobox.addItem(self.messages['wednesday'])
        self.combobox.addItem(self.messages['thursday'])
        self.combobox.addItem(self.messages['friday'])
        self.combobox.currentIndexChanged.connect(self.changed_combo)
        comboBoxHeight = self.combobox.sizeHint().height()

        self.leftButton = self.create_arrow_button(parent, Qt.LeftArrow)
        self.leftButton.clicked.connect(self.goLeft)
        self.leftButton.setMinimumSize(comboBoxHeight, comboBoxHeight)

        self.rightButton = self.create_arrow_button(parent, Qt.RightArrow)
        self.rightButton.clicked.connect(self.goRight)
        self.rightButton.setMinimumSize(comboBoxHeight, comboBoxHeight)

        navButtons = QHBoxLayout()
        navButtons.addWidget(self.leftButton, 0, Qt.AlignRight)
        navButtons.addWidget(self.combobox, 0, Qt.AlignCenter)
        navButtons.addWidget(self.rightButton, 0, Qt.AlignLeft)

        buttonBar = QHBoxLayout()
        todayButton = QPushButton(self.messages['today'], parent)
        todayButton.clicked.connect(self.goTodayClicked)
        todayButton.setMinimumHeight(comboBoxHeight)
        buttonBar.addWidget(todayButton)

        buttonBar.addWidget(QWidget(parent), 1)
        buttonBar.addLayout(navButtons, 1)
        buttonBar.addWidget(QWidget(parent), 1)

        self.switchLanguageButton = QPushButton(
            self.messages["toggleLanguage"], parent)
        self.switchLanguageButton.clicked.connect(self.toggleLanguage)
        self.switchLanguageButton.setMinimumHeight(comboBoxHeight)
        buttonBar.addWidget(self.switchLanguageButton, 0, Qt.AlignRight)

        return buttonBar

    def addMenuLine(self, parent, text, box, header=False):
        aLabel = QLabel(text, parent)
        if header:
            aLabel.setAlignment(Qt.AlignCenter)
            oldFont = aLabel.font()
            aLabel.setFont(QFont(oldFont.family(), 13, QFont.Bold))
        box.addWidget(aLabel, 0, Qt.AlignBottom)

    def addLocaleErrorPage(self, parent, box, toggle):
        aLabel = QLabel(self.messages['parseLocaleError'], parent)
        aLabel.setWordWrap(True)
        box.addWidget(aLabel)

        aButton = QPushButton(self.messages['installLocaleButton'], parent)
        if toggle:
            aButton.clicked.connect(self.installLanguageSupportToggle)
        else:
            aButton.clicked.connect(self.installLanguageSupport)
        box.addWidget(aButton)

    def addExceptionPage(self, parent, box, error, _toggle):
        aLabel = QLabel(
            self.messages['otherException'] + u" " + unicode(error), parent)
        aLabel.setWordWrap(True)
        box.addWidget(aLabel)

    def installLanguageSupportForLocale(self, locale):
        locale = locale.partition("_")[0]
        if subprocess.call(
            ['gksu', "apt-get -q -y install language-pack-%s" % locale]) != 0:
            QMessageBox().critical(self.menuNotebook,
                                   "Installation Error",
                                   self.messages['installLocaleError'],
                                   buttons=QMessageBox.Ok,
                                   defaultButton=QMessageBox.Ok)
        else:
            QMessageBox().information(self.menuNotebook,
                                      "Success",
                                      self.messages['installLocaleSuccess'],
                                      buttons=QMessageBox.Ok,
                                      defaultButton=QMessageBox.Ok)

    def installLanguageSupport(self):
        self.installLanguageSupportForLocale(self.defaultLocaleString)

    def installLanguageSupportToggle(self):
        self.installLanguageSupportForLocale(self.messages['toggleLocale'])

    def formatTitleAndDescription(self, title, description, keyInfo):
        if title and description:
            result = "%s, %s" % (title, description)
        elif title:
            result = title
        else:
            result = description

        if keyInfo:
            return "%s: %s" % (keyInfo.title(), result)
        return result

    def addMenuContent(self, parent, desc, menuContents, box, messages,
                       additivesDict):
        self.addMenuLine(parent, desc, box)
        if desc in menuContents:
            contentList = menuContents[desc]
        else:
            contentList = [(messages[u'noContents'], None, [], None)]
            log_debug("lunch menu does not contain key '%s'" % desc)

        textview = GrowingTextEdit(parent, messages, additivesDict)
        textview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        textview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        textview.setLineWrapMode(QTextEdit.WidgetWidth)
        textview.setReadOnly(True)
        textview.document().setIndentWidth(10)

        if len(contentList) == 1:
            title, description, additives, keyInfo = contentList[0]
            textview.append(
                self.formatTitleAndDescription(title, description, keyInfo),
                additives)
        elif len(contentList) > 1:
            cursor = textview.textCursor()
            listFormat = QTextListFormat()
            listFormat.setStyle(QTextListFormat.ListDisc)
            listFormat.setIndent(1)
            cursor.createList(listFormat)
            for title, description, additives, keyInfo in contentList:
                textview.append(
                    self.formatTitleAndDescription(title, description,
                                                   keyInfo), additives)

        box.addWidget(textview, 0)

    def createNotebook(self):
        self.combobox.setCurrentIndex(0)
        for _ in range(self.menuNotebook.count()):
            self.menuNotebook.removeWidget(self.menuNotebook.widget(0))
        curMessages = self.messages
        curAdditives = self.additives
        for index in range(10):
            if index == 5:
                try:
                    if getPlatform() != PLATFORM_WINDOWS:
                        locale.setlocale(
                            locale.LC_TIME,
                            (self.messages["toggleLocale"], "UTF-8"))
                except:
                    log_exception("error setting locale")
                curMessages = self.toggleMessages
                curAdditives = self.toggleAdditives
            pageWidget = QWidget(self.menuNotebook)
            page = QVBoxLayout(pageWidget)
            thisLunchMenu = LunchMenu.allLunchMenus()[index]
            if thisLunchMenu != None and type(thisLunchMenu) == LunchMenu:
                title = curMessages[
                    'lunchMenuFor'] + u" " + thisLunchMenu.lunchDate.strftime(
                        curMessages['dateFormatDisplayed']).decode("utf-8")
                self.addMenuLine(pageWidget, title, page, True)
                if thisLunchMenu.isValid():
                    self.addMenuContent(pageWidget,
                                        curMessages['soupDisplayed'],
                                        thisLunchMenu.contents, page,
                                        curMessages, curAdditives)
                    self.addMenuContent(pageWidget,
                                        curMessages['mainDishesDisplayed'],
                                        thisLunchMenu.contents, page,
                                        curMessages, curAdditives)
                    self.addMenuContent(pageWidget,
                                        curMessages['supplementsDisplayed'],
                                        thisLunchMenu.contents, page,
                                        curMessages, curAdditives)
                    self.addMenuContent(pageWidget,
                                        curMessages['dessertsDisplayed'],
                                        thisLunchMenu.contents, page,
                                        curMessages, curAdditives)
                else:
                    self.addMenuLine(pageWidget, curMessages['noLunchToday'],
                                     page)
            elif type(thisLunchMenu) == locale.Error:
                self.addLocaleErrorPage(pageWidget, page, index >= 5)
                pass
            elif isinstance(thisLunchMenu, Exception):
                self.addExceptionPage(pageWidget, page, thisLunchMenu,
                                      index >= 5)

            self.menuNotebook.addWidget(pageWidget)
        try:
            if getPlatform() != PLATFORM_WINDOWS:
                locale.setlocale(locale.LC_TIME,
                                 (LunchMenu.defaultLocaleString, "UTF-8"))
        except:
            log_exception("error setting locale")

        self.goToday()
コード例 #16
0
class EditorWidget(QWidget):

    # Señales
    allFilesClosed = pyqtSignal()

    def __init__(self):
        super(EditorWidget, self).__init__()
        self._recents_files = []

        box = QVBoxLayout(self)
        box.setContentsMargins(0, 0, 0, 0)
        box.setSpacing(0)

        # Combo container
        self.combo = ComboContainer(self)
        box.addWidget(self.combo)

        # Stacked
        self.stack = QStackedWidget()
        box.addWidget(self.stack)

        self.connect(self.combo.combo_file,
                     SIGNAL("currentIndexChanged(int)"), self.change_item)

    def add_widget(self, widget):
        index = self.stack.addWidget(widget)
        if not self.combo.isVisible():
            self.combo.setVisible(True)
        self.stack.setCurrentIndex(index)

    def add_item_combo(self, text):
        self.combo.combo_file.addItem(text)
        self.combo.combo_file.setCurrentIndex(
            self.combo.combo_file.count() - 1)

    def remove_item_combo(self, index):
        self.combo.combo_file.removeItem(index)

    def change_item(self, index):
        self.stack.setCurrentIndex(index)
        self.emit(SIGNAL("currentWidgetChanged(int)"), index)

    def current_widget(self):
        return self.stack.currentWidget()

    def current_index(self):
        return self.stack.currentIndex()

    def widget(self, index):
        return self.stack.widget(index)

    def count(self):
        return self.stack.count()

    def close_file(self):
        self.remove_widget(self.current_widget(), self.current_index())

    def close_file_project(self, widget, index):
        #FIXME: unir con close file
        self.remove_widget(widget, index)

    def close_all(self):
        for index in range(self.count()):
            self.remove_widget(self.current_widget(), 0)

    def editor_modified(self, value):
        weditor = self.current_widget()
        index = self.current_index()
        self.combo.set_modified(weditor, index, value)

    def _add_to_recent(self, filename):
        if filename == 'Untitled':
            return
        if filename not in self._recents_files:
            self._recents_files.append(filename)
            self.emit(SIGNAL("recentFile(QStringList)"), self._recents_files)

    def check_files_not_saved(self):
        value = False
        for index in range(self.count()):
            weditor = self.widget(index)
            value = value or weditor.is_modified
        return value

    def files_not_saved(self):
        files = []
        for index in range(self.count()):
            weditor = self.widget(index)
            if weditor.is_modified:
                files.append(weditor.filename)
        return files

    def opened_files(self):
        files = []
        for index in range(self.count()):
            weditor = self.widget(index)
            path = weditor.filename
            if path == 'Untitled':
                continue
            files.append(path)
        return files

    def remove_widget(self, widget, index):
        if not isinstance(widget, editor.Editor):
            return
        if index != -1:
            self.stack.setCurrentIndex(index)

            flags = QMessageBox.Yes
            flags |= QMessageBox.No
            flags |= QMessageBox.Cancel

            result = QMessageBox.No
            if widget.is_modified:
                result = QMessageBox.question(self, self.tr(
                    "Archivo no guardado!"),
                    self.tr("El archivo <b>{0}</b> "
                            "tiene cambios sin guardar. "
                            "Quieres guardarlos?").format(widget.filename),
                    QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel)
                if result == QMessageBox.Cancel:
                    return
                elif result == QMessageBox.Yes:
                    self.emit(SIGNAL("saveCurrentFile()"))
            self._add_to_recent(widget.filename)
            self.stack.removeWidget(widget)
            self.emit(SIGNAL("fileClosed(int)"), index)
            self.remove_item_combo(index)
            widget.obj_file.stop_system_watcher()
            if self.current_widget() is not None:
                self.current_widget().setFocus()
            else:
                self.allFilesClosed.emit()

    def add_symbols(self, symbols):
        self.combo.add_symbols_combo(symbols)
コード例 #17
0
ファイル: dataSelectionGui.py プロジェクト: sc65/ilastik
class DataSelectionGui(QWidget):
    """
    Manages all GUI elements in the data selection applet.
    This class itself is the central widget and also owns/manages the applet drawer widgets.
    """

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################

    def centralWidget( self ):
        return self

    def appletDrawer( self ):
        return self._drawer

    def menus( self ):
        return []

    def viewerControlWidget(self):
        return self._viewerControlWidgetStack

    def setImageIndex(self, imageIndex):
        if imageIndex is not None:
            self.laneSummaryTableView.selectRow(imageIndex)
            for detailWidget in self._detailViewerWidgets:
                detailWidget.selectRow(imageIndex)

    def stopAndCleanUp(self):
        self._cleaning_up = True
        for editor in self.volumeEditors.values():
            self.viewerStack.removeWidget( editor )
            self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
            editor.stopAndCleanUp()
        self.volumeEditors.clear()

    def imageLaneAdded(self, laneIndex):
        if len(self.laneSummaryTableView.selectedIndexes()) == 0:
            self.laneSummaryTableView.selectRow(laneIndex)
        
        # We don't have any real work to do because this gui initiated the lane addition in the first place
        if self.guiMode != GuiMode.Batch:
            if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1):
                import warnings
                warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.DatasetGroup), laneIndex))

    def imageLaneRemoved(self, laneIndex, finalLength):
        # There's nothing to do here because the GUI already 
        #  handles operator resizes via slot callbacks.
        pass

    def allowLaneSelectionChange(self):
        return False

    ###########################################
    ###########################################

    class UserCancelledError(Exception):
        # This exception type is raised when the user cancels the 
        #  addition of dataset files in the middle of the process somewhere.
        # It isn't an error -- it's used for control flow.
        pass

    def __init__(self, parentApplet, dataSelectionOperator, serializer, instructionText, guiMode=GuiMode.Normal, max_lanes=None, show_axis_details=False):
        """
        Constructor.
        
        :param dataSelectionOperator: The top-level operator.  Must be of type :py:class:`OpMultiLaneDataSelectionGroup`.
        :param serializer: The applet's serializer.  Must be of type :py:class:`DataSelectionSerializer`
        :param instructionText: A string to display in the applet drawer.
        :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``.  Currently, there is no difference between normal and batch mode.
        :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow.  If ``None``, there is no maximum.
        """
        super(DataSelectionGui, self).__init__()
        self._cleaning_up = False
        self.parentApplet = parentApplet
        self._max_lanes = max_lanes
        self._default_h5_volumes = {}
        self.show_axis_details = show_axis_details

        self._viewerControls = QWidget()
        self.topLevelOperator = dataSelectionOperator
        self.guiMode = guiMode
        self.serializer = serializer
        self.threadRouter = ThreadRouter(self)

        self._initCentralUic()
        self._initAppletDrawerUic(instructionText)
        
        self._viewerControlWidgetStack = QStackedWidget(self)

        def handleImageRemove(multislot, index, finalLength):
            # Remove the viewer for this dataset
            datasetSlot = self.topLevelOperator.DatasetGroup[index]
            if datasetSlot in self.volumeEditors.keys():
                editor = self.volumeEditors[datasetSlot]
                self.viewerStack.removeWidget( editor )
                self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
                editor.stopAndCleanUp()

        self.topLevelOperator.DatasetGroup.notifyRemove( bind( handleImageRemove ) )
        
        opWorkflow = self.topLevelOperator.parent
        assert hasattr(opWorkflow.shell, 'onSaveProjectActionTriggered'), \
            "This class uses the IlastikShell.onSaveProjectActionTriggered function.  Did you rename it?"


    def _initCentralUic(self):
        """
        Load the GUI from the ui file into this class and connect it with event handlers.
        """
        # Load the ui file into this class (find it in our own directory)
        localDir = os.path.split(__file__)[0]+'/'
        uic.loadUi(localDir+"/dataSelection.ui", self)

        self._initTableViews()
        self._initViewerStack()
        self.splitter.setSizes( [150, 850] )

    def _initAppletDrawerUic(self, instructionText):
        """
        Load the ui file for the applet drawer, which we own.
        """
        localDir = os.path.split(__file__)[0]+'/'
        self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui")
        self._drawer.instructionLabel.setText( instructionText )

    def _initTableViews(self):
        self.fileInfoTabWidget.setTabText( 0, "Summary" )
        self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) )
        self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset )
        self.laneSummaryTableView.addFilesRequested.connect( self.addFiles )
        self.laneSummaryTableView.addStackRequested.connect( self.addStack )
        self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked )

        # These two helper functions enable/disable an 'add files' button for a given role  
        #  based on the the max lane index for that role and the overall permitted max_lanes
        def _update_button_status(viewer, role_index):
            if self._max_lanes:
                viewer.setEnabled( self._findFirstEmptyLane(role_index) < self._max_lanes )

        def _handle_lane_added( button, role_index, lane_slot, lane_index ):
            def _handle_role_slot_added( role_slot, added_slot_index, *args ):
                if added_slot_index == role_index:
                    role_slot.notifyReady( bind(_update_button_status, button, role_index) )
                    role_slot.notifyUnready( bind(_update_button_status, button, role_index) )
            lane_slot[lane_index].notifyInserted( _handle_role_slot_added )

        self._retained = [] # Retain menus so they don't get deleted
        self._detailViewerWidgets = []
        for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value):
            detailViewer = DatasetDetailedInfoTableView(self)
            detailViewer.setModel(DatasetDetailedInfoTableModel(self,
                self.topLevelOperator, roleIndex))
            self._detailViewerWidgets.append( detailViewer )

            # Button
            detailViewer.addFilesRequested.connect(
                    partial(self.addFiles, roleIndex))
            detailViewer.addStackRequested.connect(
                    partial(self.addStack, roleIndex))
            detailViewer.addRemoteVolumeRequested.connect(
                    partial(self.addDvidVolume, roleIndex))

            # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab
            self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, detailViewer, roleIndex ) )
            self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, detailViewer, roleIndex ) )
            
            # While we're at it, do the same for the buttons in the summary table, too
            self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) )
            self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) )
            
            # Context menu
            detailViewer.replaceWithFileRequested.connect(
                    partial(self.handleReplaceFile, roleIndex) )
            detailViewer.replaceWithStackRequested.connect(
                    partial(self.addStack, roleIndex) )
            detailViewer.editRequested.connect(
                    partial(self.editDatasetInfo, roleIndex) )
            detailViewer.resetRequested.connect(
                    partial(self.handleClearDatasets, roleIndex) )

            # Drag-and-drop
            detailViewer.addFilesRequestedDrop.connect( partial( self.addFileNames, roleIndex=roleIndex ) )

            # Selection handling
            def showFirstSelectedDataset( _roleIndex, lanes ):
                if lanes:
                    self.showDataset( lanes[0], _roleIndex )
            detailViewer.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) )

            self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role)
                
        self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs )
        self.fileInfoTabWidget.setCurrentIndex(0)

    def handleSwitchTabs(self, tabIndex ):
        if tabIndex < len(self._detailViewerWidgets):
            roleIndex = tabIndex # If summary tab is moved to the front, change this line.
            detailViewer = self._detailViewerWidgets[roleIndex]
            selectedLanes = detailViewer.selectedLanes
            if selectedLanes:
                self.showDataset( selectedLanes[0], roleIndex )

    def _initViewerStack(self):
        self.volumeEditors = {}
        self.viewerStack.addWidget( QWidget() )

    def handleRemoveLaneButtonClicked(self):
        """
        The user clicked the "Remove" button.
        Remove the currently selected row(s) from both the GUI and the top-level operator.
        """
        # Figure out which lanes to remove
        selectedIndexes = self.laneSummaryTableView.selectedIndexes()
        rows = set()
        for modelIndex in selectedIndexes:
            rows.add( modelIndex.row() )

        # Don't remove the last row, which is just buttons.
        rows.discard( self.laneSummaryTableView.model().rowCount()-1 )

        # Remove in reverse order so row numbers remain consistent
        for row in reversed(sorted(rows)):
            # Remove lanes from the operator.
            # The table model will notice the changes and update the rows accordingly.
            finalSize = len(self.topLevelOperator.DatasetGroup) - 1
            self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize)
    
    @threadRouted
    def showDataset(self, laneIndex, roleIndex=None):
        if self._cleaning_up:
            return
        if laneIndex == -1:
            self.viewerStack.setCurrentIndex(0)
            return
        
        assert threading.current_thread().name == "MainThread"
        
        if laneIndex >= len(self.topLevelOperator.DatasetGroup):
            return
        datasetSlot = self.topLevelOperator.DatasetGroup[laneIndex]

        # Create if necessary
        if datasetSlot not in self.volumeEditors.keys():
            class DatasetViewer(LayerViewerGui):
                def moveToTop(self, roleIndex):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return
                    datasetRoles = opLaneView.DatasetRoles.value
                    if roleIndex >= len(datasetRoles):
                        return
                    roleName = datasetRoles[roleIndex]
                    try:
                        layerIndex = [l.name for l in self.layerstack].index(roleName)
                    except ValueError:
                        return
                    else:
                        self.layerstack.selectRow(layerIndex)
                        self.layerstack.moveSelectedToTop()

                def setupLayers(self):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return []
                    layers = []
                    datasetRoles = opLaneView.DatasetRoles.value
                    for roleIndex, slot in enumerate(opLaneView.ImageGroup):
                        if slot.ready():
                            roleName = datasetRoles[roleIndex]
                            layer = self.createStandardLayerFromSlot(slot)
                            layer.name = roleName
                            layers.append(layer)
                    return layers

            opLaneView = self.topLevelOperator.getLane(laneIndex)
            layerViewer = DatasetViewer(self.parentApplet, opLaneView, crosshair=False)
            
            # Maximize the x-y view by default.
            layerViewer.volumeEditorWidget.quadview.ensureMaximized(2)

            self.volumeEditors[datasetSlot] = layerViewer
            self.viewerStack.addWidget( layerViewer )
            self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() )

        # Show the right one
        viewer = self.volumeEditors[datasetSlot]
        displayedRole = self.fileInfoTabWidget.currentIndex()
        viewer.moveToTop(displayedRole)
        self.viewerStack.setCurrentWidget( viewer )
        self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() )


    def handleReplaceFile(self, roleIndex, startingLane):
        self.addFiles(roleIndex, startingLane)

    def addFiles(self, roleIndex, startingLane=None):
        """
        The user clicked the "Add File" button.
        Ask him to choose a file (or several) and add them to both
          the GUI table and the top-level operator inputs.
        """
        # Find the directory of the most recently opened image file
        mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' )
        if mostRecentImageFile is not None:
            defaultDirectory = os.path.split(mostRecentImageFile)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        # Launch the "Open File" dialog
        fileNames = self.getImageFileNamesToOpen(self, defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent image', fileNames[0])
            try:
                self.addFileNames(fileNames, roleIndex, startingLane)
            except Exception as ex:
                log_exception( logger )
                QMessageBox.critical(self, "Error loading file", str(ex))

    @classmethod
    def getImageFileNamesToOpen(cls, parent_window, defaultDirectory):
        """
        Launch an "Open File" dialog to ask the user for one or more image files.
        """
        extensions = OpDataSelection.SupportedExtensions
        filter_strs = ["*." + x for x in extensions]
        filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs]
        filt_all_str = "Image files (" + ' '.join(filter_strs) + ')'

        fileNames = []
        
        if ilastik_config.getboolean("ilastik", "debug"):
            # use Qt dialog in debug mode (more portable?)
            file_dialog = QFileDialog(parent_window, "Select Images")
            file_dialog.setOption(QFileDialog.DontUseNativeDialog, True)
            # do not display file types associated with a filter
            # the line for "Image files" is too long otherwise
            file_dialog.setFilters([filt_all_str] + filters)
            file_dialog.setNameFilterDetailsVisible(False)
            # select multiple files
            file_dialog.setFileMode(QFileDialog.ExistingFiles)
            file_dialog.setDirectory( defaultDirectory )

            if file_dialog.exec_():
                fileNames = file_dialog.selectedFiles()
        else:
            # otherwise, use native dialog of the present platform
            fileNames = QFileDialog.getOpenFileNames(parent_window, "Select Images", defaultDirectory, filt_all_str)
        # Convert from QtString to python str
        fileNames = map(encode_from_qstring, fileNames)
        return fileNames

    def _findFirstEmptyLane(self, roleIndex):
        opTop = self.topLevelOperator
        
        # Determine the number of files this role already has
        # Search for the last valid value.
        firstNewLane = 0
        for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)):
            if slot[roleIndex].ready():
                firstNewLane = laneIndex+1
                break
        return firstNewLane

    def addFileNames(self, fileNames, roleIndex, startingLane=None, rois=None):
        """
        Add the given filenames to both the GUI table and the top-level operator inputs.
        If startingLane is None, the filenames will be *appended* to the role's list of files.
        
        If rois is provided, it must be a list of (start,stop) tuples (one for each fileName)
        """
        # What lanes will we touch?
        startingLane, endingLane = self._determineLaneRange(fileNames, roleIndex, startingLane)
        if startingLane is None:
            # Something went wrong.
            return

        # If we're only adding new lanes, NOT modifying existing lanes...
        adding_only = startingLane == len(self.topLevelOperator)

        # Create a list of DatasetInfos
        try:
            infos = self._createDatasetInfos(roleIndex, fileNames, rois)
        except DataSelectionGui.UserCancelledError:
            return
        
        # If no exception was thrown so far, set up the operator now
        loaded_all = self._configureOpWithInfos(roleIndex, startingLane, endingLane, infos)
        
        if loaded_all:
            # Now check the resulting slots.
            # If they should be copied to the project file, say so.
            self._reconfigureDatasetLocations(roleIndex, startingLane, endingLane)
    
            self._checkDataFormatWarnings(roleIndex, startingLane, endingLane)
    
            # If we succeeded in adding all images, show the first one.
            self.showDataset(startingLane, roleIndex)

        # Notify the workflow that we just added some new lanes.
        if adding_only:
            workflow = self.parentApplet.topLevelOperator.parent
            workflow.handleNewLanesAdded()

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

        self.updateInternalPathVisiblity()

    def _determineLaneRange(self, fileNames, roleIndex, startingLane=None):
        """
        Determine which lanes should be configured if the user wants to add the 
            given fileNames to the specified role, starting at startingLane.
        If startingLane is None, assume the user wants to APPEND the 
            files to the role's slots.
        """
        if startingLane is None or startingLane == -1:
            startingLane = len(self.topLevelOperator.DatasetGroup)
            endingLane = startingLane+len(fileNames)-1
        else:
            assert startingLane < len(self.topLevelOperator.DatasetGroup)
            max_files = len(self.topLevelOperator.DatasetGroup) - \
                    startingLane
            if len(fileNames) > max_files:
                msg = "You selected {num_selected} files for {num_slots} "\
                      "slots. To add new files use the 'Add new...' option "\
                      "in the context menu or the button in the last row."\
                              .format(num_selected=len(fileNames),
                                      num_slots=max_files)
                QMessageBox.critical( self, "Too many files", msg )
                return (None, None)
            endingLane = min(startingLane+len(fileNames)-1,
                    len(self.topLevelOperator.DatasetGroup))
            
        if self._max_lanes and endingLane >= self._max_lanes:
            msg = "You may not add more than {} file(s) to this workflow.  Please try again.".format( self._max_lanes )
            QMessageBox.critical( self, "Too many files", msg )
            return (None, None)

        return (startingLane, endingLane)

    def _createDatasetInfos(self, roleIndex, filePaths, rois):
        """
        Create a list of DatasetInfos for the given filePaths and rois
        rois may be None, in which case it is ignored.
        """
        if rois is None:
            rois = [None]*len(filePaths)
        assert len(rois) == len(filePaths)

        infos = []
        for filePath, roi in zip(filePaths, rois):
            info = self._createDatasetInfo(roleIndex, filePath, roi)
            infos.append(info)
        return infos

    def _createDatasetInfo(self, roleIndex, filePath, roi):
        """
        Create a DatasetInfo object for the given filePath and roi.
        roi may be None, in which case it is ignored.
        """
        cwd = self.topLevelOperator.WorkingDirectory.value
        datasetInfo = DatasetInfo(filePath, cwd=cwd)
        datasetInfo.subvolume_roi = roi # (might be None)
                
        absPath, relPath = getPathVariants(filePath, cwd)
        
        # If the file is in a totally different tree from the cwd,
        # then leave the path as absolute.  Otherwise, override with the relative path.
        if relPath is not None and len(os.path.commonprefix([cwd, absPath])) > 1:
            datasetInfo.filePath = relPath
            
        h5Exts = ['.ilp', '.h5', '.hdf5']
        if os.path.splitext(datasetInfo.filePath)[1] in h5Exts:
            datasetNames = self.getPossibleInternalPaths( absPath )
            if len(datasetNames) == 0:
                raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath)
            elif len(datasetNames) == 1:
                datasetInfo.filePath += str(datasetNames[0])
            else:
                # If exactly one of the file's datasets matches a user's previous choice, use it.
                if roleIndex not in self._default_h5_volumes:
                    self._default_h5_volumes[roleIndex] = set()
                previous_selections = self._default_h5_volumes[roleIndex]
                possible_auto_selections = previous_selections.intersection(datasetNames)
                if len(possible_auto_selections) == 1:
                    datasetInfo.filePath += str(list(possible_auto_selections)[0])
                else:
                    # Ask the user which dataset to choose
                    dlg = H5VolumeSelectionDlg(datasetNames, self)
                    if dlg.exec_() == QDialog.Accepted:
                        selected_index = dlg.combo.currentIndex()
                        selected_dataset = str(datasetNames[selected_index])
                        datasetInfo.filePath += selected_dataset
                        self._default_h5_volumes[roleIndex].add( selected_dataset )
                    else:
                        raise DataSelectionGui.UserCancelledError()

        # Allow labels by default if this gui isn't being used for batch data.
        datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal )
        return datasetInfo

    def _configureOpWithInfos(self, roleIndex, startingLane, endingLane, infos):
        """
        Attempt to configure the specified role and lanes of the 
        top-level operator with the given DatasetInfos.
        
        Returns True if all lanes were configured successfully, or False if something went wrong.
        """
        opTop = self.topLevelOperator
        originalSize = len(opTop.DatasetGroup)

        # Resize the slot if necessary            
        if len( opTop.DatasetGroup ) < endingLane+1:
            opTop.DatasetGroup.resize( endingLane+1 )
        
        # Configure each subslot
        for laneIndex, info in zip(range(startingLane, endingLane+1), infos):
            try:
                self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info )
            except DatasetConstraintError as ex:
                return_val = [False]
                # Give the user a chance to fix the problem
                self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex, return_val)
                if not return_val[0]:
                    # Not successfully repaired.  Roll back the changes and give up.
                    opTop.DatasetGroup.resize( originalSize )
                    return False
            except OpDataSelection.InvalidDimensionalityError as ex:
                    opTop.DatasetGroup.resize( originalSize )
                    QMessageBox.critical( self, "Dataset has different dimensionality", ex.message )
                    return False
            except Exception as ex:
                msg = "Wasn't able to load your dataset into the workflow.  See error log for details."
                log_exception( logger, msg )
                QMessageBox.critical( self, "Dataset Load Error", msg )
                opTop.DatasetGroup.resize( originalSize )
                return False
        
        return True

    def _reconfigureDatasetLocations(self, roleIndex, startingLane, endingLane):
        """
        Check the metadata for the given slots.  
        If the data is stored a format that is poorly optimized for 3D access, 
        then configure it to be copied to the project file.
        Finally, save the project if we changed something. 
        """
        save_needed = False
        opTop = self.topLevelOperator
        for lane_index in range(startingLane, endingLane+1):
            output_slot = opTop.ImageGroup[lane_index][roleIndex]
            if output_slot.meta.prefer_2d and 'z' in output_slot.meta.axistags:
                shape = numpy.array(output_slot.meta.shape)
                total_volume = numpy.prod(shape)
                
                # Only copy to the project file if the total volume is reasonably small
                if total_volume < 0.5e9:
                    info_slot = opTop.DatasetGroup[lane_index][roleIndex]
                    info = info_slot.value
                    info.location = DatasetInfo.Location.ProjectInternal
                    info_slot.setValue( info, check_changed=False )
                    save_needed = True

        if save_needed:
            logger.info("Some of your data cannot be accessed efficiently in 3D in its current format."
                        "  It will now be copied to the project file.")
            opWorkflow = self.topLevelOperator.parent
            opWorkflow.shell.onSaveProjectActionTriggered()

    def _checkDataFormatWarnings(self, roleIndex, startingLane, endingLane):
        warn_needed = False
        opTop = self.topLevelOperator
        for lane_index in range(startingLane, endingLane+1):
            output_slot = opTop.ImageGroup[lane_index][roleIndex]
            if output_slot.meta.inefficient_format:
                warn_needed = True

        if warn_needed:        
            QMessageBox.warning( self, "Inefficient Data Format", 
                              "Your data cannot be accessed efficiently in its current format.  "
                              "Check the console output for details.\n"
                              "(For HDF5 files, be sure to enable chunking on your dataset.)" )

    @threadRouted
    def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex, return_val=[False]):
        msg = "Can't use default properties for dataset:\n\n" + \
              filename + "\n\n" + \
              "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \
              ex.message + "\n\n" + \
              "Please enter valid dataset properties to continue."
        QMessageBox.warning( self, "Dataset Needs Correction", msg )
        
        # The success of this is 'returned' via our special out-param
        # (We can't return a value from this func because it is @threadRouted.
        successfully_repaired = self.repairDatasetInfo( info, roleIndex, laneIndex )
        return_val[0] = successfully_repaired

    def repairDatasetInfo(self, info, roleIndex, laneIndex):
        """Open the dataset properties editor and return True if the new properties are acceptable."""
        defaultInfos = {}
        defaultInfos[laneIndex] = info
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos, show_axis_details=self.show_axis_details)
        dlg_state = editorDlg.exec_()
        return ( dlg_state == QDialog.Accepted )

    @classmethod
    def getPossibleInternalPaths(cls, absPath, min_ndim=3, max_ndim=5):
        datasetNames = []
        # Open the file as a read-only so we can get a list of the internal paths
        with h5py.File(absPath, 'r') as f:
            # Define a closure to collect all of the dataset names in the file.
            def accumulateDatasetPaths(name, val):
                if type(val) == h5py._hl.dataset.Dataset and min_ndim <= len(val.shape) <= max_ndim:
                    datasetNames.append( '/' + name )
            # Visit every group/dataset in the file
            f.visititems(accumulateDatasetPaths)
        return datasetNames

    def addStack(self, roleIndex, laneIndex):
        """
        The user clicked the "Import Stack Files" button.
        """
        stackDlg = StackFileSelectionWidget(self)
        stackDlg.exec_()
        if stackDlg.result() != QDialog.Accepted :
            return
        files = stackDlg.selectedFiles
        sequence_axis = stackDlg.sequence_axis
        if len(files) == 0:
            return

        cwd = self.topLevelOperator.WorkingDirectory.value
        info = DatasetInfo(os.path.pathsep.join(files), cwd=cwd)

        originalNumLanes = len(self.topLevelOperator.DatasetGroup)

        if laneIndex is None or laneIndex == -1:
            laneIndex = len(self.topLevelOperator.DatasetGroup)
        if len(self.topLevelOperator.DatasetGroup) < laneIndex+1:
            self.topLevelOperator.DatasetGroup.resize(laneIndex+1)

        def importStack():
            self.parentApplet.busy = True
            self.parentApplet.appletStateUpdateRequested.emit()

            # Serializer will update the operator for us, which will propagate to the GUI.
            try:
                self.serializer.importStackAsLocalDataset( info, sequence_axis )
                try:
                    self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info)
                except DatasetConstraintError as ex:
                    # Give the user a chance to repair the problem.
                    filename = files[0] + "\n...\n" + files[-1]
                    return_val = [False]
                    self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex, return_val )
                    if not return_val[0]:
                        # Not successfully repaired.  Roll back the changes and give up.
                        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)
            finally:
                self.parentApplet.busy = False
                self.parentApplet.appletStateUpdateRequested.emit()

        req = Request( importStack )
        req.notify_finished( lambda result: self.showDataset(laneIndex, roleIndex) )
        req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) )
        req.submit()

    @threadRouted
    def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info):
        msg = "Failed to load stack due to the following error:\n{}".format( exc )
        msg += "\nAttempted stack files were:\n"
        msg += "\n".join(files)
        log_exception( logger, msg, exc_info )
        QMessageBox.critical(self, "Failed to load image stack", msg)
        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)

    def handleClearDatasets(self, roleIndex, selectedRows):
        for row in selectedRows:
            self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect()

        # Remove all operators that no longer have any connected slots        
        laneIndexes = range( len(self.topLevelOperator.DatasetGroup) )
        for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)):
            any_ready = False
            for slot in multislot:
                any_ready |= slot.ready()
            if not any_ready:
                self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 )

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

    def editDatasetInfo(self, roleIndex, laneIndexes):
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes, show_axis_details=self.show_axis_details)
        editorDlg.exec_()
        self.parentApplet.appletStateUpdateRequested.emit()

    def updateInternalPathVisiblity(self):
        for view in self._detailViewerWidgets:
            model = view.model()
            view.setColumnHidden(DatasetDetailedInfoColumn.InternalID,
                                 not model.hasInternalPaths())
    
    def addDvidVolume(self, roleIndex, laneIndex):
        # TODO: Provide list of recently used dvid hosts, loaded from user preferences
        recent_hosts_pref = PreferencesManager.Setting("DataSelection", "Recent DVID Hosts")
        recent_hosts = recent_hosts_pref.get()
        if not recent_hosts:
            recent_hosts = ["localhost:8000"]
        recent_hosts = filter(lambda h: h, recent_hosts)
            
        from dvidDataSelectionBrowser import DvidDataSelectionBrowser
        browser = DvidDataSelectionBrowser(recent_hosts, parent=self)
        if browser.exec_() == DvidDataSelectionBrowser.Rejected:
            return

        if None in browser.get_selection():
            QMessageBox.critical("Couldn't use your selection.")
            return

        rois = None
        hostname, dset_uuid, volume_name, uuid = browser.get_selection()
        dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals() )
        subvolume_roi = browser.get_subvolume_roi()

        # Relocate host to top of 'recent' list, and limit list to 10 items.
        try:
            i = recent_hosts.index(recent_hosts)
            del recent_hosts[i]
        except ValueError:
            pass
        finally:
            recent_hosts.insert(0, hostname)        
            recent_hosts = recent_hosts[:10]

        # Save pref
        recent_hosts_pref.set(recent_hosts)

        if subvolume_roi is None:
            self.addFileNames([dvid_url], roleIndex, laneIndex)
        else:
            # In ilastik, we display the dvid volume axes in C-order, despite the dvid convention of F-order
            # Transpose the subvolume roi to match
            # (see implementation of OpDvidVolume)
            start, stop = subvolume_roi
            start = tuple(reversed(start))
            stop = tuple(reversed(stop))
            self.addFileNames([dvid_url], roleIndex, laneIndex, [(start, stop)])
コード例 #18
0
class FltsSearchDockWidget(QDockWidget):
    """
    Dock widget for showing search widgets with each widget corresponding to
    a search configuration.
    """
    def __init__(self, *args, **kwargs):
        super(FltsSearchDockWidget, self).__init__(*args, **kwargs)
        self.setAllowedAreas(Qt.BottomDockWidgetArea)
        self._search_reg = SearchConfigurationRegistry.instance()
        self._stack_widget = QStackedWidget(self)
        self.setWidget(self._stack_widget)

        # Index data source to their location in the stack widget
        self._search_widget_idx = dict()

    def show_search_widget(self, data_source):
        """
        Shows the search widget associated with the given data source. If
        not found then it will create a new one by using the factory
        method in the search configuration.
        :param data_source: Data source name.
        :type data_source: str
        :return: Returns True if the operation was successful else False. It
        will be false if the data source is not found in the registry.
        :rtype: bool
        """
        config = self._search_reg.search_config(data_source)
        if not config:
            return False

        # Set current search widget
        self._set_current_widget(config)

        return True

    def _set_current_widget(self, config):
        # Updates dock widget based on the specified config.
        data_source = config.data_source

        # Create widget if it does not exist in the stack
        if not data_source in self._search_widget_idx:
            search_widget = config.create_widget()
            idx = self._stack_widget.addWidget(search_widget)
            self._search_widget_idx[data_source] = idx
        else:
            idx = self._search_widget_idx[data_source]

        self._stack_widget.setCurrentIndex(idx)

        # Set title
        self.setWindowTitle(u'Search {0}'.format(config.display_name))

    def current_widget(self):
        """
        :return: Returns the current search widget or None if there are no
        widgets in the stack.
        :rtype: QWidget
        """
        return self._stack_widget.currentWidget()

    def clear(self):
        """
        Removes all the search widgets and resets the indices.
        """
        while self._stack_widget.count() > 0:
            sw = self._stack_widget.widget(0)
            self._stack_widget.removeWidget(sw)
            del sw

        self._search_widget_idx = dict()
コード例 #19
0
class DataSelectionGui(QWidget):
    """
    Manages all GUI elements in the data selection applet.
    This class itself is the central widget and also owns/manages the applet drawer widgets.
    """

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################

    def centralWidget( self ):
        return self

    def appletDrawer( self ):
        return self._drawer

    def menus( self ):
        return []

    def viewerControlWidget(self):
        return self._viewerControlWidgetStack

    def setImageIndex(self, imageIndex):
        if imageIndex is not None:
            self.laneSummaryTableView.selectRow(imageIndex)
            for detailWidget in self._detailViewerWidgets:
                detailWidget.selectRow(imageIndex)

    def stopAndCleanUp(self):
        for editor in self.volumeEditors.values():
            self.viewerStack.removeWidget( editor )
            self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
            editor.stopAndCleanUp()
        self.volumeEditors.clear()

    def imageLaneAdded(self, laneIndex):
        if len(self.laneSummaryTableView.selectedIndexes()) == 0:
            self.laneSummaryTableView.selectRow(laneIndex)
        
        # We don't have any real work to do because this gui initiated the lane addition in the first place
        if self.guiMode != GuiMode.Batch:
            if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1):
                import warnings
                warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.DatasetGroup), laneIndex))

    def imageLaneRemoved(self, laneIndex, finalLength):
        # We assume that there's nothing to do here because THIS GUI initiated the lane removal
        if self.guiMode != GuiMode.Batch:
            assert len(self.topLevelOperator.DatasetGroup) == finalLength

    ###########################################
    ###########################################

    def __init__(self, parentApplet, dataSelectionOperator, serializer, instructionText, guiMode=GuiMode.Normal, max_lanes=None):
        """
        Constructor.
        
        :param dataSelectionOperator: The top-level operator.  Must be of type :py:class:`OpMultiLaneDataSelectionGroup`.
        :param serializer: The applet's serializer.  Must be of type :py:class:`DataSelectionSerializer`
        :param instructionText: A string to display in the applet drawer.
        :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``.  Currently, there is no difference between normal and batch mode.
        :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow.  If ``None``, there is no maximum.
        """
        super(DataSelectionGui, self).__init__()

        self.parentApplet = parentApplet
        self._max_lanes = max_lanes

        self._viewerControls = QWidget()
        self.topLevelOperator = dataSelectionOperator
        self.guiMode = guiMode
        self.serializer = serializer
        self.threadRouter = ThreadRouter(self)

        self._initCentralUic()
        self._initAppletDrawerUic(instructionText)
        
        self._viewerControlWidgetStack = QStackedWidget(self)

        def handleImageRemoved(multislot, index, finalLength):
            # Remove the viewer for this dataset
            imageSlot = self.topLevelOperator.Image[index]
            if imageSlot in self.volumeEditors.keys():
                editor = self.volumeEditors[imageSlot]
                self.viewerStack.removeWidget( editor )
                self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() )
                editor.stopAndCleanUp()

        self.topLevelOperator.Image.notifyRemove( bind( handleImageRemoved ) )

    def _initCentralUic(self):
        """
        Load the GUI from the ui file into this class and connect it with event handlers.
        """
        # Load the ui file into this class (find it in our own directory)
        localDir = os.path.split(__file__)[0]+'/'
        uic.loadUi(localDir+"/dataSelection.ui", self)

        self._initTableViews()
        self._initViewerStack()
        self.splitter.setSizes( [150, 850] )

    def _initAppletDrawerUic(self, instructionText):
        """
        Load the ui file for the applet drawer, which we own.
        """
        localDir = os.path.split(__file__)[0]+'/'
        self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui")
        self._drawer.instructionLabel.setText( instructionText )

    def _initTableViews(self):
        self.fileInfoTabWidget.setTabText( 0, "Summary" )
        self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) )
        self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset )
        self.laneSummaryTableView.addFilesRequested.connect( self.addFiles )
        self.laneSummaryTableView.addStackRequested.connect( self.addStack )
        self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked )

        # These two helper functions enable/disable an 'add files' button for a given role  
        #  based on the the max lane index for that role and the overall permitted max_lanes
        def _update_button_status(viewer, role_index):
            if self._max_lanes:
                viewer.setEnabled( self._findFirstEmptyLane(role_index) < self._max_lanes )

        def _handle_lane_added( button, role_index, slot, lane_index ):
            slot[lane_index][role_index].notifyReady( bind(_update_button_status, button, role_index) )
            slot[lane_index][role_index].notifyUnready( bind(_update_button_status, button, role_index) )

        self._retained = [] # Retain menus so they don't get deleted
        self._detailViewerWidgets = []
        for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value):
            detailViewer = DatasetDetailedInfoTableView(self)
            detailViewer.setModel(DatasetDetailedInfoTableModel(self,
                self.topLevelOperator, roleIndex))
            self._detailViewerWidgets.append( detailViewer )

            # Button
            detailViewer.addFilesRequested.connect(
                    partial(self.addFiles, roleIndex))
            detailViewer.addStackRequested.connect(
                    partial(self.addStack, roleIndex))
            detailViewer.addRemoteVolumeRequested.connect(
                    partial(self.addDvidVolume, roleIndex))

            # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab
            self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, detailViewer, roleIndex ) )
            self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, detailViewer, roleIndex ) )
            
            # While we're at it, do the same for the buttons in the summary table, too
            self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) )
            self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) )
            
            # Context menu
            detailViewer.replaceWithFileRequested.connect(
                    partial(self.handleReplaceFile, roleIndex) )
            detailViewer.replaceWithStackRequested.connect(
                    partial(self.addStack, roleIndex) )
            detailViewer.editRequested.connect(
                    partial(self.editDatasetInfo, roleIndex) )
            detailViewer.resetRequested.connect(
                    partial(self.handleClearDatasets, roleIndex) )

            # Drag-and-drop
            detailViewer.addFilesRequestedDrop.connect( partial( self.addFileNames, roleIndex=roleIndex ) )

            # Selection handling
            def showFirstSelectedDataset( _roleIndex, lanes ):
                if lanes:
                    self.showDataset( lanes[0], _roleIndex )
            detailViewer.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) )

            self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role)
                
        self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs )
        self.fileInfoTabWidget.setCurrentIndex(0)

    def handleSwitchTabs(self, tabIndex ):
        if tabIndex < len(self._detailViewerWidgets):
            roleIndex = tabIndex # If summary tab is moved to the front, change this line.
            detailViewer = self._detailViewerWidgets[roleIndex]
            selectedLanes = detailViewer.selectedLanes
            if selectedLanes:
                self.showDataset( selectedLanes[0], roleIndex )

    def _initViewerStack(self):
        self.volumeEditors = {}
        self.viewerStack.addWidget( QWidget() )

    def handleRemoveLaneButtonClicked(self):
        """
        The user clicked the "Remove" button.
        Remove the currently selected row(s) from both the GUI and the top-level operator.
        """
        # Figure out which lanes to remove
        selectedIndexes = self.laneSummaryTableView.selectedIndexes()
        rows = set()
        for modelIndex in selectedIndexes:
            rows.add( modelIndex.row() )

        # Don't remove the last row, which is just buttons.
        rows.discard( self.laneSummaryTableView.model().rowCount()-1 )

        # Remove in reverse order so row numbers remain consistent
        for row in reversed(sorted(rows)):
            # Remove from the GUI
            self.laneSummaryTableView.model().removeRow(row)
            # Remove from the operator
            finalSize = len(self.topLevelOperator.DatasetGroup) - 1
            self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize)
    
            # The gui and the operator should be in sync (model has one extra row for the button row)
            assert self.laneSummaryTableView.model().rowCount() == len(self.topLevelOperator.DatasetGroup)+1

    @threadRouted
    def showDataset(self, laneIndex, roleIndex=None):
        if laneIndex == -1:
            self.viewerStack.setCurrentIndex(0)
            return
        
        assert threading.current_thread().name == "MainThread"
        imageSlot = self.topLevelOperator.Image[laneIndex]

        # Create if necessary
        if imageSlot not in self.volumeEditors.keys():
            
            class DatasetViewer(LayerViewerGui):
                def moveToTop(self, roleIndex):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return
                    datasetRoles = opLaneView.DatasetRoles.value
                    if roleIndex >= len(datasetRoles):
                        return
                    roleName = datasetRoles[roleIndex]
                    try:
                        layerIndex = [l.name for l in self.layerstack].index(roleName)
                    except ValueError:
                        return
                    else:
                        self.layerstack.selectRow(layerIndex)
                        self.layerstack.moveSelectedToTop()

                def setupLayers(self):
                    opLaneView = self.topLevelOperatorView
                    if not opLaneView.DatasetRoles.ready():
                        return []
                    layers = []
                    datasetRoles = opLaneView.DatasetRoles.value
                    for roleIndex, slot in enumerate(opLaneView.ImageGroup):
                        if slot.ready():
                            roleName = datasetRoles[roleIndex]
                            layer = self.createStandardLayerFromSlot(slot)
                            layer.name = roleName
                            layers.append(layer)
                    return layers

            opLaneView = self.topLevelOperator.getLane(laneIndex)
            layerViewer = DatasetViewer(self.parentApplet, opLaneView, crosshair=False)
            
            # Maximize the x-y view by default.
            layerViewer.volumeEditorWidget.quadview.ensureMaximized(2)

            self.volumeEditors[imageSlot] = layerViewer
            self.viewerStack.addWidget( layerViewer )
            self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() )

        # Show the right one
        viewer = self.volumeEditors[imageSlot]
        displayedRole = self.fileInfoTabWidget.currentIndex()
        viewer.moveToTop(displayedRole)
        self.viewerStack.setCurrentWidget( viewer )
        self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() )


    def handleReplaceFile(self, roleIndex, startingLane):
        self.addFiles(roleIndex, startingLane)

    def addFiles(self, roleIndex, startingLane=None):
        """
        The user clicked the "Add File" button.
        Ask him to choose a file (or several) and add them to both
          the GUI table and the top-level operator inputs.
        """
        # Find the directory of the most recently opened image file
        mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' )
        if mostRecentImageFile is not None:
            defaultDirectory = os.path.split(mostRecentImageFile)[0]
        else:
            defaultDirectory = os.path.expanduser('~')

        # Launch the "Open File" dialog
        fileNames = self.getImageFileNamesToOpen(defaultDirectory)

        # If the user didn't cancel
        if len(fileNames) > 0:
            PreferencesManager().set('DataSelection', 'recent image', fileNames[0])
            try:
                self.addFileNames(fileNames, roleIndex, startingLane)
            except RuntimeError as e:
                QMessageBox.critical(self, "Error loading file", str(e))

    def getImageFileNamesToOpen(self, defaultDirectory):
        """
        Launch an "Open File" dialog to ask the user for one or more image files.
        """
        file_dialog = QFileDialog(self, "Select Images")

        extensions = OpDataSelection.SupportedExtensions
        filter_strs = ["*." + x for x in extensions]
        filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs]
        filt_all_str = "Image files (" + ' '.join(filter_strs) + ')'
        file_dialog.setFilters([filt_all_str] + filters)

        # do not display file types associated with a filter
        # the line for "Image files" is too long otherwise
        file_dialog.setNameFilterDetailsVisible(False)
        # select multiple files
        file_dialog.setFileMode(QFileDialog.ExistingFiles)
        if ilastik_config.getboolean("ilastik", "debug"):
            file_dialog.setOption(QFileDialog.DontUseNativeDialog, True)

        if file_dialog.exec_():
            fileNames = file_dialog.selectedFiles()
            # Convert from QtString to python str
            fileNames = map(encode_from_qstring, fileNames)
            return fileNames

        return []

    def _findFirstEmptyLane(self, roleIndex):
        opTop = self.topLevelOperator
        
        # Determine the number of files this role already has
        # Search for the last valid value.
        firstNewLane = 0
        for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)):
            if slot[roleIndex].ready():
                firstNewLane = laneIndex+1
                break
        return firstNewLane

    def addFileNames(self, fileNames, roleIndex, startingLane=None):
        """
        Add the given filenames to both the GUI table and the top-level operator inputs.
        If startingLane is None, the filenames will be *appended* to the role's list of files.
        """
        infos = []

        if startingLane is None or startingLane == -1:
            startingLane = len(self.topLevelOperator.DatasetGroup)
            endingLane = startingLane+len(fileNames)-1
        else:
            assert startingLane < len(self.topLevelOperator.DatasetGroup)
            max_files = len(self.topLevelOperator.DatasetGroup) - \
                    startingLane
            if len(fileNames) > max_files:
                msg = "You selected {num_selected} files for {num_slots} "\
                      "slots. To add new files use the 'Add new...' option "\
                      "in the context menu or the button in the last row."\
                              .format(num_selected=len(fileNames),
                                      num_slots=max_files)
                QMessageBox.critical( self, "Too many files", msg )
                return
            endingLane = min(startingLane+len(fileNames)-1,
                    len(self.topLevelOperator.DatasetGroup))
            
        if self._max_lanes and endingLane >= self._max_lanes:
            msg = "You may not add more than {} file(s) to this workflow.  Please try again.".format( self._max_lanes )
            QMessageBox.critical( self, "Too many files", msg )
            return

        # Assign values to the new inputs we just allocated.
        # The GUI will be updated by callbacks that are listening to slot changes
        for i, filePath in enumerate(fileNames):
            datasetInfo = DatasetInfo()
            cwd = self.topLevelOperator.WorkingDirectory.value
            
            absPath, relPath = getPathVariants(filePath, cwd)
            
            # Relative by default, unless the file is in a totally different tree from the working directory.
            if relPath is not None and len(os.path.commonprefix([cwd, absPath])) > 1:
                datasetInfo.filePath = relPath
            else:
                datasetInfo.filePath = absPath
                
            datasetInfo.nickname = PathComponents(absPath).filenameBase

            h5Exts = ['.ilp', '.h5', '.hdf5']
            if os.path.splitext(datasetInfo.filePath)[1] in h5Exts:
                datasetNames = self.getPossibleInternalPaths( absPath )
                if len(datasetNames) > 0:
                    datasetInfo.filePath += str(datasetNames[0])
                else:
                    raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath)

            # Allow labels by default if this gui isn't being used for batch data.
            datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal )
            infos.append(datasetInfo)

        # if no exception was thrown, set up the operator now
        opTop = self.topLevelOperator
        originalSize = len(opTop.DatasetGroup)
            
        if len( opTop.DatasetGroup ) < endingLane+1:
            opTop.DatasetGroup.resize( endingLane+1 )
        for laneIndex, info in zip(range(startingLane, endingLane+1), infos):
            try:
                self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info )
            except DatasetConstraintError as ex:
                return_val = [False]
                # Give the user a chance to fix the problem
                self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex, return_val)
                if not return_val[0]:
                    # Not successfully repaired.  Roll back the changes and give up.
                    opTop.DatasetGroup.resize( originalSize )
                    break
            except OpDataSelection.InvalidDimensionalityError as ex:
                    opTop.DatasetGroup.resize( originalSize )
                    QMessageBox.critical( self, "Dataset has different dimensionality", ex.message )
                    break
            except:
                QMessageBox.critical( self, "Dataset Load Error", "Wasn't able to load your dataset into the workflow.  See console for details." )
                opTop.DatasetGroup.resize( originalSize )
                raise

        # If we succeeded in adding all images, show the first one.
        if laneIndex == endingLane:
            self.showDataset(startingLane, roleIndex)

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

        self.updateInternalPathVisiblity()

    @threadRouted
    def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex, return_val=[False]):
        msg = "Can't use default properties for dataset:\n\n" + \
              filename + "\n\n" + \
              "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \
              ex.message + "\n\n" + \
              "Please enter valid dataset properties to continue."
        QMessageBox.warning( self, "Dataset Needs Correction", msg )
        
        # The success of this is 'returned' via our special out-param
        # (We can't return a value from this func because it is @threadRouted.
        successfully_repaired = self.repairDatasetInfo( info, roleIndex, laneIndex )
        return_val[0] = successfully_repaired

    def repairDatasetInfo(self, info, roleIndex, laneIndex):
        """Open the dataset properties editor and return True if the new properties are acceptable."""
        defaultInfos = {}
        defaultInfos[laneIndex] = info
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos)
        dlg_state = editorDlg.exec_()
        return ( dlg_state == QDialog.Accepted )

    def getPossibleInternalPaths(self, absPath):
        datasetNames = []
        # Open the file as a read-only so we can get a list of the internal paths
        with h5py.File(absPath, 'r') as f:
            # Define a closure to collect all of the dataset names in the file.
            def accumulateDatasetPaths(name, val):
                if type(val) == h5py._hl.dataset.Dataset and 3 <= len(val.shape) <= 5:
                    datasetNames.append( '/' + name )
            # Visit every group/dataset in the file
            f.visititems(accumulateDatasetPaths)
        return datasetNames

    def addStack(self, roleIndex, laneIndex):
        """
        The user clicked the "Import Stack Files" button.
        """
        stackDlg = StackFileSelectionWidget(self)
        stackDlg.exec_()
        if stackDlg.result() != QDialog.Accepted :
            return
        files = stackDlg.selectedFiles
        if len(files) == 0:
            return

        info = DatasetInfo()
        info.filePath = "//".join( files )
        prefix = os.path.commonprefix(files)
        info.nickname = PathComponents(prefix).filenameBase
        # Add an underscore for each wildcard digit
        num_wildcards = len(files[-1]) - len(prefix) - len( os.path.splitext(files[-1])[1] )
        info.nickname += "_"*num_wildcards

        # Allow labels by default if this gui isn't being used for batch data.
        info.allowLabels = ( self.guiMode == GuiMode.Normal )
        info.fromstack = True

        originalNumLanes = len(self.topLevelOperator.DatasetGroup)

        if laneIndex is None or laneIndex == -1:
            laneIndex = len(self.topLevelOperator.DatasetGroup)
        if len(self.topLevelOperator.DatasetGroup) < laneIndex+1:
            self.topLevelOperator.DatasetGroup.resize(laneIndex+1)

        def importStack():
            self.parentApplet.busy = True
            self.parentApplet.appletStateUpdateRequested.emit()

            # Serializer will update the operator for us, which will propagate to the GUI.
            try:
                self.serializer.importStackAsLocalDataset( info )
                try:
                    self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info)
                except DatasetConstraintError as ex:
                    # Give the user a chance to repair the problem.
                    filename = files[0] + "\n...\n" + files[-1]
                    return_val = [False]
                    self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex, return_val )
                    if not return_val[0]:
                        # Not successfully repaired.  Roll back the changes and give up.
                        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)
            finally:
                self.parentApplet.busy = False
                self.parentApplet.appletStateUpdateRequested.emit()

        req = Request( importStack )
        req.notify_finished( lambda result: self.showDataset(laneIndex, roleIndex) )
        req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) )
        req.submit()

    @threadRouted
    def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info):
        import traceback
        traceback.print_tb(exc_info[2])
        msg = "Failed to load stack due to the following error:\n{}".format( exc )
        msg += "Attempted stack files were:"
        for f in files:
            msg += f + "\n"
        QMessageBox.critical(self, "Failed to load image stack", msg)
        self.topLevelOperator.DatasetGroup.resize(originalNumLanes)

    def handleClearDatasets(self, roleIndex, selectedRows):
        for row in selectedRows:
            self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect()

        # Remove all operators that no longer have any connected slots        
        last_valid = -1
        laneIndexes = range( len(self.topLevelOperator.DatasetGroup) )
        for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)):
            any_ready = False
            for slot in multislot:
                any_ready |= slot.ready()
            if not any_ready:
                self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 )

        # Notify the workflow that something that could affect applet readyness has occurred.
        self.parentApplet.appletStateUpdateRequested.emit()

    def editDatasetInfo(self, roleIndex, laneIndexes):
        editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes)
        editorDlg.exec_()

    def updateInternalPathVisiblity(self):
        for view in self._detailViewerWidgets:
            model = view.model()
            view.setColumnHidden(DatasetDetailedInfoColumn.InternalID,
                                 not model.hasInternalPaths())
    
    def addDvidVolume(self, roleIndex, laneIndex):
        # TODO: Provide list of recently used dvid hosts, loaded from user preferences
        from dvidclient.gui.contents_browser import ContentsBrowser
        browser = ContentsBrowser(["localhost:8000"], parent=self)
        if browser.exec_() == ContentsBrowser.Rejected:
            return

        hostname, dset_index, volume_name, uuid = browser.get_selection()
        dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals() )        
        self.addFileNames([dvid_url], roleIndex, laneIndex)
コード例 #20
0
ファイル: fragment_frame.py プロジェクト: maximerobin/Ufwi
class FragmentFrame(QFrame):

    # Signals:
    SIG_REMOVE_FRAGMENT = 'removeFragment()'
    # Fragment edition switch
    FRAGMENT_EDITION = False

    view = None
    fetcher = None

    def __init__(self, fragment, args, client, animation_manager=None, parent=None, has_menu_pos=True):
        """
            @fragment [Fragment] user_settings.Fragment object to describe fragment
            @client [RpcdClient] client
            @parent [QWidget] parent widget
        """

        #assert isinstance(client, RpcdClient)
        assert frag_types.has_key(fragment.type)

        QFrame.__init__(self, parent)

        self.animation_manager = animation_manager
        self.has_menu_pos = has_menu_pos
        self.fragment = fragment
        self.fetcher = frag_types[fragment.type].fetcher(fragment, args, client)
        self.connect(self.fetcher, SIGNAL(self.fetcher.ERROR_SIGNAL), self.errorHandler)
        self.window = parent
        self.cumulative_mode = False
        self.interval = Interval('daily')

        self.setFragmentColor()

        self.setFrameShadow(QFrame.Sunken)
        self.setFrameShape(QFrame.StyledPanel)
        self.setContextMenuPolicy(Qt.ActionsContextMenu)

        self.toolspace = None
        self.vbox = QVBoxLayout()
#        self.vbox.setContentsMargins(9,0,9,9)
        self.vbox.setContentsMargins(9,0,9,0)
        self.setLayout(self.vbox)

        self.stacked = QStackedWidget(self)
        updating_label = QLabel("<img src=\":/icons/refresh.png\" /><br />%s" % self.tr("Updating..."))
        updating_label.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter)
        self.stacked.addWidget(updating_label)

        # Create all actions for the rightclick context menu
        self.action_list = []
        self.switch_actions = {}

        self.viewlayout = QHBoxLayout()
        self.viewlayout.setContentsMargins(0,0,0,0)
        self.viewlayout.setSpacing(2)
        self.viewlayout.addStretch()
        widget = QWidget()
        widget.setLayout(self.viewlayout)
        self.vbox.addWidget(widget)

        self.line = QFrame(self)
        self.line.setFrameShape(QFrame.HLine)
        self.line.setFrameShadow(QFrame.Sunken)
        self.line.setObjectName("line")

        # Menu to choose position of fragment
        if self.has_menu_pos:
            self.pos_menu = QMenu(tr('Position'), self)
#        self.pos_action = QAction(tr('Position'), self.pos_menu)

        def make_lambda(l):
            """ usefull to create the lambda function with a copied parameter. or it'll bug """
            return lambda: QTimer.singleShot(0, lambda: self.setView(l))

        self.buttons = []

        button = QToolButton()
        button.visible = True
        button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)
        button.setMinimumSize(0,16)
        button.setIcon(QIcon(":/icons/refresh.png"))
        button.setFixedHeight(16)
        button.setToolTip(tr("Refresh"))
        self.connect(button, SIGNAL("clicked()"), self.updateData)
        self.viewlayout.addWidget(button)
        self.buttons.append(button)

        # All of the views available for this kind of fragment.
        if len(frag_types[fragment.type].views) > 1:
            for label in frag_types[fragment.type].views:

                try:
                    item_name = views_list_label[label]
                except KeyError:
                    continue

                # item_name returns a unicode string, but PyQT (Qt 4.2.1) won't convert it to a char*
                # unless we convert it to a non-unicode string ....
                action = QAction(QIcon(':/icons/%s' % label),
                                 tr("Switch to %s") % self.tr(unicode(item_name)), self)
                self.connect(action, SIGNAL("triggered()"), make_lambda(label))
                self.action_list += [action]
                self.switch_actions[label] = action

                button = QToolButton()
                button.visible = True
                button.setBackgroundRole(QPalette.Button)
                button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum)
                button.setMinimumSize(0,16)
                button.setFixedHeight(16)
                button.setIcon(QIcon(":/icons/%s" % label))
                button.setToolTip(tr("Switch to %s") % self.tr(unicode(item_name)))
                self.connect(button, SIGNAL("clicked()"), make_lambda(label))
                self.viewlayout.addWidget(button)
                self.buttons.append(button)

            # Separator
            action = QAction(self)
            action.setSeparator(True)
            self.action_list += [action]

        # Refresh
        action = QAction(QIcon(':/icons/refresh.png'), tr('Refresh'), self)
        self.connect(action, SIGNAL('triggered()'), self.updateData)
        self.action_list += [action]
        if self.FRAGMENT_EDITION:
            # Edit
            action = QAction(QIcon(':/icons/edit.png'), tr('Edit this fragment...'), self)
            self.connect(action, SIGNAL('triggered()'), self.editFragment)
            self.action_list += [action]
            # Delete
            action = QAction(QIcon(':/icons/moins.png'), tr('Delete this fragment'), self)
            self.connect(action, SIGNAL('triggered()'), self.removeFragment)
            self.action_list += [action]

        self.setView(fragment.view, update=False)

        self.setAcceptDrops(True)
        self.pos = -1

    def mouseMoveEvent(self, event):
        if event.buttons() != Qt.LeftButton:
            return

        mimeData = QMimeData()
        if self.pos == -1:
            return
        mimeData.setData("splitter/fragment", QByteArray.number(self.pos))
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - self.rect().topLeft())

        dropAction = drag.start(Qt.MoveAction)

        if dropAction == Qt.MoveAction:
            self.close()

    def dragEnterEvent(self, event):
        event.accept()

    def dropEvent(self, event):

        data = event.mimeData().data("splitter/fragment").toInt()
        if not data[1]:
            return

        frame_pos = data[0]

        if frame_pos == self.pos:
            return

        event.setDropAction(Qt.MoveAction)
        event.accept()

        self.window.changedPositionFragment(self.pos, frame_pos)

#    def __del__(self):
#        self.destructor()

    def destructor(self):
        if self.view:
            self.freeView()
        if self.fetcher:
            self.disconnect(self.fetcher, SIGNAL(self.fetcher.ERROR_SIGNAL), self.errorHandler)
            self.fetcher.destructor()
            self.fetcher = None

    def getView(self):
        return self.view

    def getFragment(self):
        return self.fragment

    def setCumulative(self, cumulative):
        self.cumulative_mode = cumulative
        if self.view:
            self.view.setCumulative(cumulative)

    def setInterval(self, interval):
        self.interval = interval
        if self.view:
            self.view.setInterval(interval)

    def setFragmentColor(self):

        # XXX: We have to put classes which we colorize, because some objects bug
        # when we try to put a background on them. For example, do NOT colorize
        # QScrollBar, it'll not work anyway:
        # http://doc.trolltech.com/4.4/stylesheet-examples.html#customizing-qscrollbar
        # "The QScrollBar can be styled using its subcontrols like handle,
        # add-line, sub-line, and so on. Note that if one property or
        # sub-control is customized, all the other properties or sub-controls
        # must be customized as well."
        self.setStyleSheet("""
         QFrame, QPushButton, QTableCornerButton, QAbstractSpinBox, QLineEdit {
             background-color: #%06X;
         }

         """ % self.fragment.background_color)

    def editFragment(self):
        if AddFragDialog(self.window, self.fragment, self).run():
            self.setFragmentColor()
            self.setView(self.fragment.view, update=True)

    def removeFragment(self):
        reply = QMessageBox.question(self, self.tr("Delete a fragment"),
                                           unicode(self.tr('Are you sure to delete the fragment "%s" from the view?')) % self.fragment.title,
                                           QMessageBox.Yes|QMessageBox.No)

        if reply == QMessageBox.Yes:
            QTimer.singleShot(0, self._removeFragment_emit)

    def _removeFragment_emit(self):
        self.emit(SIGNAL(self.SIG_REMOVE_FRAGMENT))

#    def resizeEvent(self, event):
#        QFrame.resizeEvent(self, event)
#        self.view.resize(event.size())

    def freeView(self):
        if self.view:
            self.view.destructor()
            self.disconnect(self.view, SIGNAL('open_page'), self._open_page)
            self.disconnect(self.view, SIGNAL('add_filter'), self._add_filter)
            self.disconnect(self.view, SIGNAL('updating'), self._show_animation)
            self.disconnect(self.view, SIGNAL('updated'), self._show_view)
            self.disconnect(self.view, SIGNAL('EAS_Message'), self.EAS_SendMessage)
            self.stacked.removeWidget(self.view)
            self.vbox.removeWidget(self.stacked)
            self.vbox.removeWidget(self.line)
            self.view.setParent(None)
            self.view.hide()
            self.view.deleteLater()
        if self.toolspace:
            self.viewlayout.removeWidget(self.toolspace)
            self.toolspace.setParent(None)
            self.toolspace.hide()
            self.toolspace.deleteLater()

        if self.view and hasattr(self.view, "uuid"):
            if self.animation_manager:
                self.animation_manager.remove(self.view.uuid)
            self.view = None
        self.toolspace = None

    def setView(self, label, update=True):

        # If there isn't any view for this fragment, use the first available view
        # of this kind of fragment.
        if not label:
            assert frag_types.has_key(self.fragment.type)
            assert len(frag_types[self.fragment.type].views) > 0

            label = frag_types[self.fragment.type].views[0]


        for button in self.buttons:
            if label in button.toolTip():
                button.visible = False
            else:
                if not button.isEnabled():
                    button.visible = True
#        assert views_list.has_key(label)

        self.freeView()

        # Create the view object.
        self.view = views_list[label](self.fetcher, self)
        if label == "histo" or label == 'pie':
            self.view.is_graphics_view = True
            if label == 'histo':
                self.view.chart_type = BARCHART
            else:
                self.view.chart_type = PIECHART
        else:
            self.view.is_graphics_view = False

        if label != "error":
            self.connect(self, SIGNAL("closed"), self.setClosed)
            if self.animation_manager:
                if self.view.is_graphics_view:
                    self.connect(self.view, SIGNAL("animation_done(QString)"), self, SIGNAL("animation_done(QString)"))
                self.animation_manager.addView(self.view)

        self.connect(self.view, SIGNAL("showButtons"), self.showButtonsSlot)
        self.connect(self.view, SIGNAL("autoRefresh"), self.updateData)
                
        self.view.setCumulative(self.cumulative_mode)
        self.view.setInterval(self.interval)
        self.stacked.insertWidget(0, self.view)
        self._show_view()
        self.connect(self.view, SIGNAL('open_page'), self._open_page)
        self.connect(self.view, SIGNAL('add_filter'), self._add_filter)
        self.connect(self.view, SIGNAL('updating'), self._show_animation)
        self.connect(self.view, SIGNAL('updated'), self._show_view)
        self.connect(self.view, SIGNAL('EAS_Message'), self.EAS_SendMessage)

        # Set some features if there are available on each or each type of widget.
        if hasattr(self.view, 'setFrameShape'):
            self.view.setFrameShape(QFrame.NoFrame)
        self.view.setContextMenuPolicy(Qt.ActionsContextMenu)

        # All views can ask me to display a toolspace (a widget with all kind of
        # things in).
        self.view.title.setText(self.view.getTitle())
        self.fragment.view = label

        self.toolspace = self.view.getToolspace()
        self.viewlayout.insertWidget(0, self.toolspace)
        self.vbox.addWidget(self.line)
        self.vbox.addWidget(self.stacked)

        # Set the new menu.
        for action in self.actions():
            self.removeAction(action)

        for view_label, action in self.switch_actions.items():
            action.setEnabled(view_label != self.fragment.view)

        for action in self.action_list:
            self.addAction(action)
            self.view.addAction(action)

        # Add view's eventual menu.
        view_actions = self.view.getActions()
        if view_actions:
            separator = QAction(self)
            separator.setSeparator(True)
            view_actions = [separator] + view_actions

            for action in view_actions:
                self.view.addAction(action)
                self.addAction(action)

        if self.has_menu_pos:
            self.view.addAction(self.pos_menu.menuAction())
            self.addAction(self.pos_menu.menuAction())

        if update:
            self.updateData()

    def setClosed(self):
        if self.view:
            self.view.setClosed()
        self.destructor()

    def errorHandler(self, e):
        """ This method is called when fetcher raises an error. """

        # Store error in fragment, and the ErrorFragmentView will able to
        # load it

        error = exceptionAsUnicode(e)
        self.fragment.error = error

        # We keep last view in the fragment, to prevent setView() method to
        # put 'error' in the fragment.view string attribute.
        last_view = self.fragment.view
        self.setView('error', update=False) # load the error fragment
        self.fragment.view = last_view

    def showButtonsSlot(self):
        for button in self.buttons:
            if hasattr(button, 'visible'):
                if button.visible:
                    button.setEnabled(True)
                else:
                    button.setEnabled(False)

    def updateData(self):

        if hasattr(self.fragment, 'error'):
            self.setView(self.fragment.view, update=False)
            del self.fragment.error

        for button in self.buttons:
            button.setEnabled(False)

        self.view.requestData()

    def _open_page(self, *args, **kwargs):
        self.emit(SIGNAL('open_page'), *args, **kwargs)

    def _add_filter(self, *args, **kwargs):
        self.emit(SIGNAL('add_filter'), *args, **kwargs)

    def EAS_SendMessage(self, *args, **kwargs):
        self.emit(SIGNAL('EAS_Message'), *args, **kwargs)

    def _show_animation(self):
        self.stacked.setCurrentIndex(1)

    def _show_view(self):
        self.stacked.setCurrentIndex(0)