Пример #1
0
class AutoCompleteLineEdit(QLineEdit):
    # http://blog.elentok.com/2011/08/autocomplete-textbox-for-multiple.html
    def __init__(self, items, parent=None):
        super(AutoCompleteLineEdit, self).__init__(parent)

        self._separators = [",", " "]

        self._completer = QCompleter(items, self)
        self._completer.setWidget(self)
        self._completer.activated[str].connect(self.__insertCompletion)
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)

        self.__keysToIgnore = [
            Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab
        ]

    def __insertCompletion(self, completion):
        extra = len(completion) - len(self._completer.completionPrefix())
        extra_text = completion[-extra:]
        extra_text += ', '
        self.setText(self.text() + extra_text)

    def textUnderCursor(self):
        text = self.text()
        text_under_cursor = ''
        i = self.cursorPosition() - 1
        while i >= 0 and text[i] not in self._separators:
            text_under_cursor = text[i] + text_under_cursor
            i -= 1
        return text_under_cursor

    def keyPressEvent(self, event):
        if self._completer.popup().isVisible():
            if event.key() in self.__keysToIgnore:
                event.ignore()
                return

        super(AutoCompleteLineEdit, self).keyPressEvent(event)

        completion_prefix = self.textUnderCursor()
        if completion_prefix != self._completer.completionPrefix():
            self.__updateCompleterPopupItems(completion_prefix)
        if len(event.text()) > 0 and len(completion_prefix) > 0:
            self._completer.complete()
        if len(completion_prefix) == 0:
            self._completer.popup().hide()

    def __updateCompleterPopupItems(self, completionPrefix):
        self._completer.setCompletionPrefix(completionPrefix)
        self._completer.popup().setCurrentIndex(
            self._completer.completionModel().index(0, 0))
Пример #2
0
class AutoCompleteLineEdit(QLineEdit):
    #http://blog.elentok.com/2011/08/autocomplete-textbox-for-multiple.html
    def __init__(self, items, parent=None):
        super(AutoCompleteLineEdit, self).__init__(parent)

        self.__separators = [",", " "]

        self.__completer = QCompleter(items, self)
        self.__completer.setWidget(self)
        self.__completer.activated[str].connect(self.__insertCompletion)
        self.__completer.setCaseSensitivity(Qt.CaseInsensitive)

        self.__keysToIgnore = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab]

    def __insertCompletion(self, completion):
        extra = len(completion) - len(self.__completer.completionPrefix())
        extra_text = completion[-extra:]
        extra_text += ', '
        self.setText(self.text() + extra_text)


    def textUnderCursor(self):
        text = self.text()
        text_under_cursor = ''
        i = self.cursorPosition() - 1
        while i >=0 and text[i] not in self.__separators:
            text_under_cursor = text[i] + text_under_cursor
            i -= 1
        return text_under_cursor


    def keyPressEvent(self, event):
        if self.__completer.popup().isVisible():
            if event.key() in self.__keysToIgnore:
                event.ignore()
                return

        super(AutoCompleteLineEdit, self).keyPressEvent(event)

        completion_prefix = self.textUnderCursor()
        if completion_prefix != self.__completer.completionPrefix():
            self.__updateCompleterPopupItems(completion_prefix)
        if len(event.text()) > 0 and len(completion_prefix) > 0:
            self.__completer.complete()
        if len(completion_prefix) == 0:
            self.__completer.popup().hide()


    def __updateCompleterPopupItems(self, completionPrefix):
        self.__completer.setCompletionPrefix(completionPrefix)
        self.__completer.popup().setCurrentIndex(self.__completer.completionModel().index(0,0))
Пример #3
0
class OWDatabasesUpdate(OWWidget):

    name = "Databases update"
    description = "Update local system biology databases."
    icon = "../widgets/icons/Databases.svg"
    priority = 10

    inputs = []
    outputs = []

    want_main_area = False

    def __init__(self, parent=None, signalManager=None,
                 name="Databases update", domains=None):
        OWWidget.__init__(self, parent, signalManager, name,
                          wantMainArea=False)

        self.searchString = ""
        self.accessCode = ""
        self.domains = domains or DOMAINS
        self.serverFiles = serverfiles.ServerFiles()

        self.__in_progress_update = False

        fbox = gui.widgetBox(self.controlArea, "Filter")

        # The completer model token strings
        self.completertokens = []
        # A 'dynamic' completer item model will be updated with
        # 'prefix {token}' where prefix is current 'active' filter list
        # (the QCompleter only completes on one item in the model)
        self.completermodel = QStringListModel(self)
        self.completer = QCompleter(
            self.completermodel, self,
            caseSensitivity=Qt.CaseInsensitive
        )

        self.lineEditFilter = QLineEdit(textChanged=self.SearchUpdate)

        self.lineEditFilter.setCompleter(self.completer)

        fbox.layout().addWidget(self.lineEditFilter)

        box = gui.widgetBox(self.controlArea, "Files")

        self.filesView = QTreeWidget(self)
        self.filesView.setHeaderLabels(
            ["", "Data Source", "Update", "Last Updated", "Size"])

        self.filesView.setRootIsDecorated(False)
        self.filesView.setUniformRowHeights(True)
        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
        self.filesView.setSortingEnabled(True)
        self.filesView.sortItems(1, Qt.AscendingOrder)
        self.filesView.setItemDelegateForColumn(
            0, UpdateOptionsItemDelegate(self.filesView))

        self.filesView.model().layoutChanged.connect(self.SearchUpdate)

        box.layout().addWidget(self.filesView)

        box = gui.widgetBox(self.controlArea, orientation="horizontal")
        self.updateButton = gui.button(
            box, self, "Update all",
            callback=self.UpdateAll,
            tooltip="Update all updatable files",
         )

        self.downloadButton = gui.button(
            box, self, "Download all",
            callback=self.DownloadFiltered,
            tooltip="Download all filtered files shown"
        )

        self.cancelButton = gui.button(
            box, self, "Cancel", callback=self.Cancel,
            tooltip="Cancel scheduled downloads/updates."
        )
        gui.rubber(box)

        gui.lineEdit(box, self, "accessCode", "Access Code",
                     orientation="horizontal",
                     callback=self.RetrieveFilesList)

        self.retryButton = gui.button(
            box, self, "Retry", callback=self.RetrieveFilesList
        )
        self.retryButton.hide()

        box = gui.widgetBox(self.controlArea, orientation="horizontal")
        gui.rubber(box)

        self.infoLabel = QLabel()
        self.infoLabel.setAlignment(Qt.AlignCenter)

        self.controlArea.layout().addWidget(self.infoLabel)
        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.updateItems = []

        self.resize(800, 600)

        self.progress = ProgressState(self, maximum=3)
        self.progress.valueChanged.connect(self._updateProgress)
        self.progress.rangeChanged.connect(self._updateProgress)
        self.executor = ThreadExecutor(
            threadPool=QThreadPool(maxThreadCount=2)
        )

        task = Task(self, function=self.RetrieveFilesList)
        task.exceptionReady.connect(self.HandleError)
        task.start()

        self._tasks = []
        self._haveProgress = False

    def RetrieveFilesList(self):
        self.progress.setRange(0, 3)
        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode)

        task = Task(function=partial(retrieveFilesList, self.serverFiles,
                                     self.domains,
                                     methodinvoke(self.progress, "advance")))

        task.resultReady.connect(self.SetFilesList)
        task.exceptionReady.connect(self.HandleError)

        self.executor.submit(task)

        self.setEnabled(False)

    def SetFilesList(self, serverInfo):
        """
        Set the files to show.
        """
        self.setEnabled(True)

        domains = serverInfo.keys()
        if not domains:
            if self.domains:
                domains = self.domains
            else:
                domains = serverfiles.listdomains()

        localInfo = dict([(dom, serverfiles.allinfo(dom)) for dom in domains])

        all_tags = set()

        self.filesView.clear()
        self.updateItems = []

        for item in join_info_dict(localInfo, serverInfo):
            tree_item = UpdateTreeWidgetItem(item)
            options_widget = UpdateOptionsWidget(item.state)
            options_widget.item = item

            options_widget.installClicked.connect(
                partial(self.SubmitDownloadTask, item.domain, item.filename)
            )
            options_widget.removeClicked.connect(
                partial(self.SubmitRemoveTask, item.domain, item.filename)
            )

            self.updateItems.append((item, tree_item, options_widget))
            all_tags.update(item.tags)

        self.filesView.addTopLevelItems(
            [tree_item for _, tree_item, _ in self.updateItems]
        )

        for item, tree_item, options_widget in self.updateItems:
            self.filesView.setItemWidget(tree_item, 0, options_widget)

            # Add an update button if the file is updateable
            if item.state == OUTDATED:
                button = QToolButton(
                    None, text="Update",
                    maximumWidth=120,
                    maximumHeight=30
                )

                if sys.platform == "darwin":
                    button.setAttribute(Qt.WA_MacSmallSize)

                button.clicked.connect(
                    partial(self.SubmitDownloadTask, item.domain,
                            item.filename)
                )

                self.filesView.setItemWidget(tree_item, 2, button)

        self.progress.advance()

        self.filesView.setColumnWidth(0, self.filesView.sizeHintForColumn(0))

        for column in range(1, 4):
            contents_hint = self.filesView.sizeHintForColumn(column)
            header_hint = self.filesView.header().sectionSizeHint(column)
            width = max(min(contents_hint, 400), header_hint)
            self.filesView.setColumnWidth(column, width)

        hints = [hint for hint in sorted(all_tags) if not hint.startswith("#")]
        self.completertokens = hints
        self.completermodel.setStringList(hints)

        self.SearchUpdate()
        self.UpdateInfoLabel()
        self.toggleButtons()
        self.cancelButton.setEnabled(False)

        self.progress.setRange(0, 0)

    def buttonCheck(self, selected_items, state, button):
        for item in selected_items:
            if item.state != state:
                button.setEnabled(False)
            else:
                button.setEnabled(True)
                break

    def toggleButtons(self):
        selected_items = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()]
        self.buttonCheck(selected_items, OUTDATED, self.updateButton)
        self.buttonCheck(selected_items, AVAILABLE, self.downloadButton)

    def HandleError(self, exception):
        if isinstance(exception, IOError):
            self.error(0,
                       "Could not connect to server! Press the Retry "
                       "button to try again.")
            self.SetFilesList({})
        else:
            sys.excepthook(type(exception), exception.args, None)
            self.progress.setRange(0, 0)
            self.setEnabled(True)

    def UpdateInfoLabel(self):
        local = [item for item, tree_item, _ in self.updateItems
                 if item.state != AVAILABLE and not tree_item.isHidden()]
        size = sum(float(item.size) for item in local)

        onServer = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()]
        sizeOnServer = sum(float(item.size) for item in onServer)

        text = ("%i items, %s (on server: %i items, %s)" %
                (len(local),
                 sizeof_fmt(size),
                 len(onServer),
                 sizeof_fmt(sizeOnServer)))

        self.infoLabel.setText(text)

    def UpdateAll(self):
        for item, tree_item, _ in self.updateItems:
            if item.state == OUTDATED and not tree_item.isHidden():
                self.SubmitDownloadTask(item.domain, item.filename)

    def DownloadFiltered(self):
        # TODO: submit items in the order shown.
        for item, tree_item, _ in self.updateItems:
            if not tree_item.isHidden() and item.state in \
                    [AVAILABLE, OUTDATED]:
                self.SubmitDownloadTask(item.domain, item.filename)

    def _updateCompleterPrefix(self, prefix, seperator=" "):
        prefix = str(self.completer.completionPrefix())

        tokens = self.completertokens
        model = self.completer.model()

        if not prefix.endswith(seperator) and seperator in prefix:
            prefix, _ = prefix.rsplit(seperator, 1)
            items = [prefix + seperator + item for item in tokens]
        else:
            items = tokens
        old = set(str(item) for item in model.stringList())

        if old != set(items):
            model.setStringList(items)

    def SearchUpdate(self, searchString=None):
        self._updateCompleterPrefix(searchString)
        strings = str(self.lineEditFilter.text()).split()
        for item, tree_item, _ in self.updateItems:
            hide = not all(UpdateItem_match(item, string)
                           for string in strings)
            tree_item.setHidden(hide)
        self.UpdateInfoLabel()
        self.toggleButtons()

    def SubmitDownloadTask(self, domain, filename):
        """
        Submit the (domain, filename) to be downloaded/updated.
        """
        self.cancelButton.setEnabled(True)

        index = self.updateItemIndex(domain, filename)
        _, tree_item, opt_widget = self.updateItems[index]

        if self.accessCode:
            sf = serverfiles.ServerFiles(access_code=self.accessCode)
        else:
            sf = serverfiles.ServerFiles()

        task = DownloadTask(domain, filename, sf)

        self.executor.submit(task)

        self.progress.adjustRange(0, 100)

        pb = ItemProgressBar(self.filesView)
        pb.setRange(0, 100)
        pb.setTextVisible(False)

        task.advanced.connect(pb.advance)
        task.advanced.connect(self.progress.advance)
        task.finished.connect(pb.hide)
        task.finished.connect(self.onDownloadFinished, Qt.QueuedConnection)
        task.exception.connect(self.onDownloadError, Qt.QueuedConnection)

        self.filesView.setItemWidget(tree_item, 2, pb)

        # Clear the text so it does not show behind the progress bar.
        tree_item.setData(2, Qt.DisplayRole, "")
        pb.show()

        # Disable the options widget
        opt_widget.setEnabled(False)
        self._tasks.append(task)

    def EndDownloadTask(self, task):
        future = task.future()
        index = self.updateItemIndex(task.domain, task.filename)
        item, tree_item, opt_widget = self.updateItems[index]

        self.filesView.removeItemWidget(tree_item, 2)
        opt_widget.setEnabled(True)

        if future.cancelled():
            # Restore the previous state
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

        elif future.exception():
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

            # Show the exception string in the size column.
            tree_item.setData(
                2, Qt.DisplayRole,
                "Error occurred while downloading:" + str(future.exception())
            )

        else:
            # get the new updated info dict and replace the the old item
            info = serverfiles.info(item.domain, item.filename)
            new_item = update_item_from_info(item.domain, item.filename,
                                             info, info)

            self.updateItems[index] = (new_item, tree_item, opt_widget)

            tree_item.setUpdateItem(new_item)
            opt_widget.setState(new_item.state)

            self.UpdateInfoLabel()

    def SubmitRemoveTask(self, domain, filename):
        serverfiles.remove(domain, filename)
        index = self.updateItemIndex(domain, filename)
        item, tree_item, opt_widget = self.updateItems[index]

        if item.info_server:
            new_item = item._replace(state=AVAILABLE, local=None,
                                      info_local=None)
        else:
            new_item = item._replace(local=None, info_local=None)
            # Disable the options widget. No more actions can be performed
            # for the item.
            opt_widget.setEnabled(False)

        tree_item.setUpdateItem(new_item)
        opt_widget.setState(new_item.state)
        self.updateItems[index] = (new_item, tree_item, opt_widget)

        self.UpdateInfoLabel()

    def Cancel(self):
        """
        Cancel all pending update/download tasks (that have not yet started).
        """
        for task in self._tasks:
            task.future().cancel()

    def onDeleteWidget(self):
        self.Cancel()
        self.executor.shutdown(wait=False)
        OWWidget.onDeleteWidget(self)

    def onDownloadFinished(self):
        # on download completed/canceled/error
        assert QThread.currentThread() is self.thread()
        for task in list(self._tasks):
            future = task.future()
            if future.done():
                self.EndDownloadTask(task)
                self._tasks.remove(task)

        if not self._tasks:
            # Clear/reset the overall progress
            self.progress.setRange(0, 0)

            self.cancelButton.setEnabled(False)

    def onDownloadError(self, exc_info):
        sys.excepthook(*exc_info)

    def updateItemIndex(self, domain, filename):
        for i, (item, _, _) in enumerate(self.updateItems):
            if item.domain == domain and item.filename == filename:
                return i
        raise ValueError("%r, %r not in update list" % (domain, filename))

    def _updateProgress(self, *args):
        rmin, rmax = self.progress.range()
        if rmin != rmax:
            if not self._haveProgress:
                self._haveProgress = True
                self.progressBarInit()

            self.progressBarSet(self.progress.ratioCompleted() * 100)
#             self.progressBarSet(self.progress.ratioCompleted() * 100,
#                                 processEventsFlags=None)
        if rmin == rmax:
            self._haveProgress = False
            self.progressBarFinished()

    def progressBarSet(self, value):
        if not self.__in_progress_update:
            self.__in_progress_update = True
            try:
                OWWidget.progressBarSet(self, value)
            finally:
                self.__in_progress_update = False
Пример #4
0
class OWDatabasesUpdate(OWWidget):

    name = "Databases Update"
    description = "Update local systems biology databases."
    icon = "../widgets/icons/Databases.svg"
    priority = 10

    inputs = []
    outputs = []

    want_main_area = False

    def __init__(self,
                 parent=None,
                 signalManager=None,
                 name="Databases update",
                 domains=None):
        OWWidget.__init__(self,
                          parent,
                          signalManager,
                          name,
                          wantMainArea=False)

        self.searchString = ""
        self.accessCode = ""
        self.domains = domains or DOMAINS
        self.serverFiles = serverfiles.ServerFiles()

        fbox = gui.widgetBox(self.controlArea, "Filter")

        # The completer model token strings
        self.completertokens = []
        # A 'dynamic' completer item model will be updated with
        # 'prefix {token}' where prefix is current 'active' filter list
        # (the QCompleter only completes on one item in the model)
        self.completermodel = QStringListModel(self)
        self.completer = QCompleter(self.completermodel,
                                    self,
                                    caseSensitivity=Qt.CaseInsensitive)

        self.lineEditFilter = QLineEdit(textChanged=self.SearchUpdate)

        self.lineEditFilter.setCompleter(self.completer)

        fbox.layout().addWidget(self.lineEditFilter)

        box = gui.widgetBox(self.controlArea, "Files")

        self.filesView = QTreeWidget(self)
        self.filesView.setHeaderLabels(
            ["", "Data Source", "Update", "Last Updated", "Size"])

        self.filesView.setRootIsDecorated(False)
        self.filesView.setUniformRowHeights(True)
        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
        self.filesView.setSortingEnabled(True)
        self.filesView.sortItems(1, Qt.AscendingOrder)
        self.filesView.setItemDelegateForColumn(
            0, UpdateOptionsItemDelegate(self.filesView))

        self.filesView.model().layoutChanged.connect(self.SearchUpdate)

        box.layout().addWidget(self.filesView)

        box = gui.widgetBox(self.controlArea, orientation="horizontal")
        self.updateButton = gui.button(
            box,
            self,
            "Update all",
            callback=self.UpdateAll,
            tooltip="Update all updatable files",
        )

        self.downloadButton = gui.button(
            box,
            self,
            "Download all",
            callback=self.DownloadFiltered,
            tooltip="Download all filtered files shown")

        self.cancelButton = gui.button(
            box,
            self,
            "Cancel",
            callback=self.Cancel,
            tooltip="Cancel scheduled downloads/updates.")

        self.retryButton = gui.button(box,
                                      self,
                                      "Reconnect",
                                      callback=self.RetrieveFilesList)
        self.retryButton.hide()

        gui.rubber(box)

        gui.lineEdit(box,
                     self,
                     "accessCode",
                     "Access Code",
                     orientation="horizontal",
                     callback=self.RetrieveFilesList)

        self.warning(0)

        box = gui.widgetBox(self.controlArea, orientation="horizontal")
        gui.rubber(box)

        self.infoLabel = QLabel()
        self.infoLabel.setAlignment(Qt.AlignCenter)

        self.controlArea.layout().addWidget(self.infoLabel)
        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.updateItems = []

        self.resize(800, 600)

        self.progress = ProgressState(self, maximum=3)
        self.progress.valueChanged.connect(self._updateProgress)
        self.progress.rangeChanged.connect(self._updateProgress)
        self.executor = ThreadExecutor(threadPool=QThreadPool(
            maxThreadCount=2))

        task = Task(self, function=self.RetrieveFilesList)
        task.exceptionReady.connect(self.HandleError)
        task.start()

        self._tasks = []
        self._haveProgress = False

    def RetrieveFilesList(self):
        self.retryButton.hide()
        self.warning(0)
        self.progress.setRange(0, 3)
        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode)

        task = Task(
            function=partial(retrieveFilesList, self.serverFiles, self.domains,
                             methodinvoke(self.progress, "advance")))

        task.resultReady.connect(self.SetFilesList)
        task.exceptionReady.connect(self.HandleError)

        self.executor.submit(task)

        self.setEnabled(False)

    def SetFilesList(self, serverInfo):
        """
        Set the files to show.
        """
        self.setEnabled(True)

        domains = serverInfo.keys()
        if not domains:
            if self.domains:
                domains = self.domains
            else:
                domains = serverfiles.listdomains()

        localInfo = dict([(dom, serverfiles.allinfo(dom)) for dom in domains])

        all_tags = set()

        self.filesView.clear()
        self.updateItems = []

        for item in join_info_dict(localInfo, serverInfo):
            tree_item = UpdateTreeWidgetItem(item)
            options_widget = UpdateOptionsWidget(item.state)
            options_widget.item = item

            options_widget.installClicked.connect(
                partial(self.SubmitDownloadTask, item.domain, item.filename))
            options_widget.removeClicked.connect(
                partial(self.SubmitRemoveTask, item.domain, item.filename))

            self.updateItems.append((item, tree_item, options_widget))
            all_tags.update(item.tags)

        self.filesView.addTopLevelItems(
            [tree_item for _, tree_item, _ in self.updateItems])

        for item, tree_item, options_widget in self.updateItems:
            self.filesView.setItemWidget(tree_item, 0, options_widget)

            # Add an update button if the file is updateable
            if item.state == OUTDATED:
                button = QToolButton(None,
                                     text="Update",
                                     maximumWidth=120,
                                     minimumHeight=20,
                                     maximumHeight=20)

                if sys.platform == "darwin":
                    button.setAttribute(Qt.WA_MacSmallSize)

                button.clicked.connect(
                    partial(self.SubmitDownloadTask, item.domain,
                            item.filename))

                self.filesView.setItemWidget(tree_item, 2, button)

        self.progress.advance()

        self.filesView.setColumnWidth(0, self.filesView.sizeHintForColumn(0))

        for column in range(1, 4):
            contents_hint = self.filesView.sizeHintForColumn(column)
            header_hint = self.filesView.header().sectionSizeHint(column)
            width = max(min(contents_hint, 400), header_hint)
            self.filesView.setColumnWidth(column, width)

        hints = [hint for hint in sorted(all_tags) if not hint.startswith("#")]
        self.completertokens = hints
        self.completermodel.setStringList(hints)

        self.SearchUpdate()
        self.UpdateInfoLabel()
        self.toggleButtons()
        self.cancelButton.setEnabled(False)

        self.progress.setRange(0, 0)

    def buttonCheck(self, selected_items, state, button):
        for item in selected_items:
            if item.state != state:
                button.setEnabled(False)
            else:
                button.setEnabled(True)
                break

    def toggleButtons(self):
        selected_items = [
            item for item, tree_item, _ in self.updateItems
            if not tree_item.isHidden()
        ]
        self.buttonCheck(selected_items, OUTDATED, self.updateButton)
        self.buttonCheck(selected_items, AVAILABLE, self.downloadButton)

    def HandleError(self, exception):
        if isinstance(exception, ConnectionError):
            self.warning(
                0, "Could not connect to server! Check your connection "
                "and try to reconnect.")
            self.SetFilesList({})
            self.retryButton.show()
        else:
            sys.excepthook(type(exception), exception.args, None)
            self.progress.setRange(0, 0)
            self.setEnabled(True)

    def UpdateInfoLabel(self):
        local = [
            item for item, tree_item, _ in self.updateItems
            if item.state != AVAILABLE and not tree_item.isHidden()
        ]
        size = sum(float(item.size) for item in local)

        onServer = [
            item for item, tree_item, _ in self.updateItems
            if not tree_item.isHidden()
        ]
        sizeOnServer = sum(float(item.size) for item in onServer)

        text = ("%i items, %s (on server: %i items, %s)" %
                (len(local), sizeof_fmt(size), len(onServer),
                 sizeof_fmt(sizeOnServer)))

        self.infoLabel.setText(text)

    def UpdateAll(self):
        self.warning(0)
        for item, tree_item, _ in self.updateItems:
            if item.state == OUTDATED and not tree_item.isHidden():
                self.SubmitDownloadTask(item.domain, item.filename)

    def DownloadFiltered(self):
        # TODO: submit items in the order shown.
        for item, tree_item, _ in self.updateItems:
            if not tree_item.isHidden() and item.state in \
                    [AVAILABLE, OUTDATED]:
                self.SubmitDownloadTask(item.domain, item.filename)

    def _updateCompleterPrefix(self, prefix, seperator=" "):
        prefix = str(self.completer.completionPrefix())

        tokens = self.completertokens
        model = self.completer.model()

        if not prefix.endswith(seperator) and seperator in prefix:
            prefix, _ = prefix.rsplit(seperator, 1)
            items = [prefix + seperator + item for item in tokens]
        else:
            items = tokens
        old = set(str(item) for item in model.stringList())

        if old != set(items):
            model.setStringList(items)

    def SearchUpdate(self, searchString=None):
        self._updateCompleterPrefix(searchString)
        strings = str(self.lineEditFilter.text()).split()
        for item, tree_item, _ in self.updateItems:
            hide = not all(
                UpdateItem_match(item, string) for string in strings)
            tree_item.setHidden(hide)
        self.UpdateInfoLabel()
        self.toggleButtons()

    def SubmitDownloadTask(self, domain, filename):
        """
        Submit the (domain, filename) to be downloaded/updated.
        """
        self.cancelButton.setEnabled(True)

        index = self.updateItemIndex(domain, filename)
        _, tree_item, opt_widget = self.updateItems[index]

        if self.accessCode:
            sf = serverfiles.ServerFiles(access_code=self.accessCode)
        else:
            sf = serverfiles.ServerFiles()

        task = DownloadTask(domain, filename, sf)

        self.progress.adjustRange(0, 100)

        pb = ItemProgressBar(self.filesView)
        pb.setRange(0, 100)
        pb.setTextVisible(False)

        task.advanced.connect(pb.advance)
        task.advanced.connect(self.progress.advance)
        task.finished.connect(pb.hide)
        task.finished.connect(self.onDownloadFinished, Qt.QueuedConnection)
        task.exception.connect(self.onDownloadError, Qt.QueuedConnection)

        self.filesView.setItemWidget(tree_item, 2, pb)

        # Clear the text so it does not show behind the progress bar.
        tree_item.setData(2, Qt.DisplayRole, "")
        pb.show()

        # Disable the options widget
        opt_widget.setEnabled(False)
        self._tasks.append(task)

        self.executor.submit(task)

    def EndDownloadTask(self, task):
        future = task.future()
        index = self.updateItemIndex(task.domain, task.filename)
        item, tree_item, opt_widget = self.updateItems[index]

        self.filesView.removeItemWidget(tree_item, 2)
        opt_widget.setEnabled(True)

        if future.cancelled():
            # Restore the previous state
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

        elif future.exception():
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

            # Show the exception string in the size column.
            self.warning(
                0, "Error while downloading. Check your connection "
                "and retry.")

            # recreate button for download
            button = QToolButton(None,
                                 text="Retry",
                                 maximumWidth=120,
                                 minimumHeight=20,
                                 maximumHeight=20)

            if sys.platform == "darwin":
                button.setAttribute(Qt.WA_MacSmallSize)

            button.clicked.connect(
                partial(self.SubmitDownloadTask, item.domain, item.filename))

            self.filesView.setItemWidget(tree_item, 2, button)

        else:
            # get the new updated info dict and replace the the old item
            self.warning(0)
            info = serverfiles.info(item.domain, item.filename)
            new_item = update_item_from_info(item.domain, item.filename, info,
                                             info)

            self.updateItems[index] = (new_item, tree_item, opt_widget)

            tree_item.setUpdateItem(new_item)
            opt_widget.setState(new_item.state)

            self.UpdateInfoLabel()

    def SubmitRemoveTask(self, domain, filename):
        serverfiles.remove(domain, filename)
        index = self.updateItemIndex(domain, filename)
        item, tree_item, opt_widget = self.updateItems[index]

        if item.info_server:
            new_item = item._replace(state=AVAILABLE,
                                     local=None,
                                     info_local=None)
        else:
            new_item = item._replace(local=None, info_local=None)
            # Disable the options widget. No more actions can be performed
            # for the item.
            opt_widget.setEnabled(False)

        tree_item.setUpdateItem(new_item)
        opt_widget.setState(new_item.state)
        self.updateItems[index] = (new_item, tree_item, opt_widget)

        self.UpdateInfoLabel()

    def Cancel(self):
        """
        Cancel all pending update/download tasks (that have not yet started).
        """
        for task in self._tasks:
            task.future().cancel()

    def onDeleteWidget(self):
        self.Cancel()
        self.executor.shutdown(wait=False)
        OWWidget.onDeleteWidget(self)

    def onDownloadFinished(self):
        # on download completed/canceled/error
        assert QThread.currentThread() is self.thread()
        for task in list(self._tasks):
            future = task.future()
            if future.done():
                self.EndDownloadTask(task)
                self._tasks.remove(task)

        if not self._tasks:
            # Clear/reset the overall progress
            self.progress.setRange(0, 0)
            self.cancelButton.setEnabled(False)

    def onDownloadError(self, exc_info):
        sys.excepthook(*exc_info)
        self.warning(
            0, "Error while downloading. Check your connection and "
            "retry.")

    def updateItemIndex(self, domain, filename):
        for i, (item, _, _) in enumerate(self.updateItems):
            if item.domain == domain and item.filename == filename:
                return i
        raise ValueError("%r, %r not in update list" % (domain, filename))

    def _updateProgress(self, *args):
        rmin, rmax = self.progress.range()
        if rmin != rmax:
            if not self._haveProgress:
                self._haveProgress = True
                self.progressBarInit()

            self.progressBarSet(self.progress.ratioCompleted() * 100,
                                processEvents=None)
        if rmin == rmax:
            self._haveProgress = False
            self.progressBarFinished()