def create_issue(self):
     """
     Opens a QDialog to create new Issue.
     """
     # Load the QDialog to create new issue.
     issue_dialog = QDialog()
     issue_dialog.setWindowFlags(Qt.WindowTitleHint)
     issue_dialog.setWindowFlags(Qt.WindowSystemMenuHint)
     issue_dialog.setWindowFlags(Qt.WindowCloseButtonHint)
     self.create_issue_dialog_ui.setupUi(issue_dialog)
     # Add the list of current projects from redmine.
     projects_list = self.redmine_manager.get_projects()
     self.create_issue_dialog_ui.projectComboBox.addItems(projects_list)
     # Add the trackers of the selected project.
     self.create_issue_dialog_ui.projectComboBox.currentIndexChanged.connect(
         self.add_project_trackers)
     # Add priorities for the new issue.
     priorities_list = [
         priority_name for priority_name in constants.PRIORITIES_DICT
     ]
     self.create_issue_dialog_ui.priorityComboBox.addItems(priorities_list)
     # Detect clicked signal for the create button.
     self.create_issue_dialog_ui.newIssueButton.clicked.connect(
         self.create_new_issue_clicked)
     issue_dialog.exec_()
class NotificationsDialog(object):
    def __init__(self, parent, parent_window, notifications, dp=None):
        self._dialog = QDialog(parent_window)
        self._dp = dp

        self._notifications = notifications
        self._parent = parent
        self._parent_window = parent_window

        self._dialog.setWindowIcon(QIcon(':/images/icon.png'))
        self._ui = Ui_Dialog()
        self._ui.setupUi(self._dialog)

        self._init_ui()

    def _init_ui(self):
        self._dialog.setWindowFlags(Qt.Dialog)
        self._dialog.setAttribute(Qt.WA_TranslucentBackground)
        self._dialog.setAttribute(Qt.WA_MacFrameworkScaled)

        self._notifications_list = NotificationsList(self._dp)
        self._ui.notifications_area.setWidget(self._notifications_list)
        self._ui.notifications_area.verticalScrollBar().valueChanged.connect(
            self._on_list_scroll_changed)

        self._old_main_resize_event = self._ui.centralwidget.resizeEvent
        self._ui.centralwidget.resizeEvent = self._main_resize_event

        self._loader_movie = QMovie(":/images/loader.gif")
        self._ui.loader_label.setMovie(self._loader_movie)

    def show(self, on_finished):
        def finished():
            self.show_cursor_normal()
            self._dialog.finished.disconnect(finished)
            on_finished()

        logger.debug("Opening notifications dialog")

        screen_width = QApplication.desktop().width()
        parent_x = self._dialog.parent().x()
        parent_width = self._dialog.parent().width()
        width = self._dialog.width()
        offset = 16
        if parent_x + parent_width / 2 > screen_width / 2:
            x = parent_x - width - offset
            if x < 0:
                x = 0
        else:
            x = parent_x + parent_width + offset
            diff = x + width - screen_width
            if diff > 0:
                x -= diff
        self._dialog.move(x, self._dialog.parent().y())

        # Execute dialog
        self._dialog.finished.connect(finished)
        if not self._parent.load_notifications(show_loading=True):
            self.show_notifications()
        self._dialog.raise_()
        self._dialog.show()

    def raise_dialog(self):
        self._dialog.raise_()

    def close(self):
        self._dialog.reject()

    def show_cursor_loading(self, show_movie=False):
        if show_movie:
            self._ui.notifications_pages.setCurrentIndex(2)
            self._loader_movie.start()
        else:
            self._dialog.setCursor(Qt.WaitCursor)
            self._parent_window.setCursor(Qt.WaitCursor)

    def show_cursor_normal(self):
        self._dialog.setCursor(Qt.ArrowCursor)
        self._parent_window.setCursor(Qt.ArrowCursor)
        if self._loader_movie.state() == QMovie.Running:
            self._loader_movie.stop()

    def show_notifications(self):
        if not self._notifications:
            self._ui.notifications_pages.setCurrentIndex(1)
        else:
            self._ui.notifications_pages.setCurrentIndex(0)
            self._notifications_list.show_notifications(self._notifications)

        self.show_cursor_normal()

    def _on_list_scroll_changed(self, *args, **kwargs):
        # value = self._ui.notifications_area.verticalScrollBar().value()
        # logger.debug("Scroll value %s", value)
        if self._parent.all_loaded or self._parent.is_querying:
            return

        if self._notifications_list.loading_needed(self._parent.limit):
            logger.debug("Loading notifications")
            self._parent.load_notifications()

    def _main_resize_event(self, e):
        self._old_main_resize_event(e)
        self._notifications_list.setFixedWidth(
            self._ui.notifications_pages.width() - 8)

        self._on_list_scroll_changed()
Example #3
0
class SupportDialog(QObject):
    SHORT_FEEDBACK_INTERVAL = 5 * 60 * 1000
    DAYS_TO_FEEDBACK = 7
    DROPDOWN_BACKGROUND_COLOR = "#f78d1e"
    DROPDOWN_COLOR = "white"

    _sending_error = Signal()

    SUBJECT = {
        1: "TECHNICAL",
        2: "OTHER",
        3: "FEEDBACK",
    }

    def __init__(self, parent, parent_window, config, dp=1, selected_index=0):
        QObject.__init__(self, parent)

        self._parent = parent
        self._parent_window = parent_window
        self._config = config
        self._dp = dp
        self._selected_index = selected_index
        self._dialog = QDialog(parent_window)
        self._dialog.setWindowFlags(Qt.Dialog)
        self._dialog.setAttribute(Qt.WA_MacFrameworkScaled)

        self._is_opened = False
        self._pipe = None
        self._feedback_mode = False

        self._ui = Ui_Dialog()
        self._ui.setupUi(self._dialog)
        self._init_ui()
        self._old_close_event = self._dialog.closeEvent

        self._feedback_timer = QTimer(self)
        self._feedback_timer.setSingleShot(True)
        self._feedback_timer.timeout.connect(self._show_feedback_form)

        self._parent.service_started.connect(self._check_feedback_needed)
        self._parent.exit_request.connect(self._on_exit_request)

    def _init_ui(self):
        self._ui.pushButton.setEnabled(False)
        self._ui.comboBox.addItem(tr("---Select Subject---"))
        self._ui.comboBox.addItem(tr("Technical Question"))
        self._ui.comboBox.addItem(tr("Other Question"))
        self._ui.comboBox.addItem(tr("Feedback"))
        self._ui.comboBox.setCurrentIndex(self._selected_index)

        palette = self._ui.comboBox.palette()
        palette.setColor(QPalette.HighlightedText, QColor(self.DROPDOWN_COLOR))
        palette.setColor(QPalette.Highlight,
                         QColor(self.DROPDOWN_BACKGROUND_COLOR))
        self._ui.comboBox.setPalette(palette)
        palette = self._ui.comboBox.view().palette()
        palette.setColor(QPalette.HighlightedText, QColor(self.DROPDOWN_COLOR))
        palette.setColor(QPalette.Highlight,
                         QColor(self.DROPDOWN_BACKGROUND_COLOR))
        self._ui.comboBox.view().setPalette(palette)

        self._set_tooltip()

        self._ui.comboBox.currentIndexChanged.connect(self._on_index_changed)
        self._ui.plainTextEdit.textChanged.connect(self._set_tooltip)
        self._ui.pushButton.clicked.connect(self._on_send_clicked)
        self._ui.text_label.linkActivated.connect(self._on_link_activated)
        self._sending_error.connect(self._clear_pipe_state)

        self._set_fonts()

    def _set_fonts(self):
        ui = self._ui
        controls = [
            ui.plainTextEdit, ui.pushButton, ui.comboBox, ui.text_label,
            ui.checkBox
        ]

        for control in controls:
            font = control.font()
            font_size = control.font().pointSize() * self._dp
            if font_size > 0:
                control_font = QFont(font.family(), font_size)
                control_font.setBold(font.bold())
                control.setFont(control_font)

    def set_selected_index(self, selected_index):
        self._selected_index = selected_index
        if self._is_opened:
            self._ui.comboBox.setCurrentIndex(self._selected_index)

    def show(self):
        if self._parent.dialogs_opened():
            return

        self._is_opened = True
        logger.debug("Support dialog opening...")
        self._pipe = None
        self.set_selected_index(self._selected_index)
        self._ui.checkBox.setChecked(False)
        self._ui.comboBox.setEnabled(not self._feedback_mode)
        self._dialog.exec_()

        logger.debug("Support dialog closed")
        if self._pipe:
            try:
                self._pipe.stop()
                self._clear_pipe_state()
            except Exception as e:
                logger.error("Unexpected error stopping pipe: (%s)", e)
        self._is_opened = False
        self._selected_index = 0
        self._ui.plainTextEdit.document().clear()
        self._ui.checkBox.setChecked(False)

    def dialog_opened(self):
        return self._is_opened

    def close(self):
        self._dialog.close()

    def _set_tooltip(self):
        if not self._selected_index:
            tooltip = tr("Please select subject")
            self._ui.pushButton.setEnabled(False)
        elif not self._ui.plainTextEdit.document().toPlainText():
            tooltip = tr("Message can't be empty")
            self._ui.pushButton.setEnabled(False)
        else:
            tooltip = tr("Click to send message")
            self._ui.pushButton.setEnabled(True)
        self._ui.pushButton.setToolTip(tooltip)

    def _on_index_changed(self, selected_index):
        self._selected_index = selected_index
        self._set_tooltip()

    def _on_send_clicked(self):
        self._dialog.setEnabled(False)
        self._pipe = ProgressPipe(self,
                                  self._ui.pushButton,
                                  timeout=1000,
                                  final_text=tr("Sent"),
                                  final_timeout=500)
        self._pipe.pipe_finished.connect(self._on_pipe_finished)
        if self._ui.checkBox.isChecked():
            self._pipe.add_task(tr("Compressing"), self._archive_logs())
            self._pipe.add_task(tr("Uploading"), self._upload_file())
        self._pipe.add_task(tr("Sending"), self._send_message())
        self._pipe.start()

    def _on_pipe_finished(self):
        self._clear_feedback_flag()
        self.close()

    def _clear_pipe_state(self):
        self._dialog.setEnabled(True)
        try:
            self._pipe.pipe_finished.disconnect(self._on_pipe_finished)
        except Exception as e:
            logger.warning("Can't disconnect signal: %s", e)
        self._ui.pushButton.setText(tr("SEND"))

    def _send_message(self):
        def send(log_file_name=""):
            logger.debug("Support compressed log_file_name %s", log_file_name)
            if self._selected_index not in self.SUBJECT:
                logger.warning("Attempt to send message to support "
                               "with invalid subject")
                return

            subject = self.SUBJECT[self._selected_index]
            res = self._parent.web_api.send_support_message(
                subject,
                self._ui.plainTextEdit.document().toPlainText(), log_file_name)
            was_error = False
            msg = tr("Can't send message to support")
            if res and "result" in res:
                if res["result"] != "success":
                    was_error = True
                    msg = str(res.get("info", msg))
            else:
                was_error = True
            if was_error:
                self._parent.show_tray_notification(msg)
                self._sending_error.emit()
                raise SendingError(msg)

        return send

    def _archive_logs(self):
        def archive():
            # uses function attributes to track progress
            # archive.size, archive.progress, archive.stop
            logs_dir = get_bases_dir(self._config.sync_directory)
            log_files = glob("{}{}*.log".format(logs_dir, os.sep))
            log_sizes = list(map(os.path.getsize, log_files))
            # mark overall size
            archive.size = sum(log_sizes)

            old_archives = glob("{}{}2*_logs.zip".format(logs_dir, os.sep))
            try:
                list(map(remove_file, old_archives))
            except Exception as e:
                logger.warning("Can't delete old archives. Reason: (%s)", e)

            if get_free_space(logs_dir) < archive.size // 5:
                # archive.size // 5 is approx future archive size
                msg = tr("Insufficient disk space to archive logs. "
                         "Please clean disk")
                self._parent.show_tray_notification(msg)
                self._sending_error.emit()
                raise SendingError(msg)

            archive_name = time.strftime('%Y%m%d_%H%M%S_logs.zip')
            archive_path = "{}{}{}".format(logs_dir, os.sep, archive_name)
            archive_dir = op.dirname(archive_path)
            f = zipfile.ZipFile(archive_path,
                                "w",
                                compression=zipfile.ZIP_DEFLATED,
                                compresslevel=9)
            try:
                with cwd(archive_dir):
                    for i, log_file in enumerate(log_files):
                        if not op.isfile(log_file):
                            continue

                        f.write(op.basename(log_file))
                        # mark progress
                        archive.progress += log_sizes[i]
                        if archive.stop:
                            return

            except Exception as e:
                msg = tr("Can't archive logs.")
                logger.warning(msg + " Reason: (%s)", e)
                self._parent.show_tray_notification(msg)
                self._sending_error.emit()
                raise SendingError(msg)
            finally:
                f.close()
                if archive.stop:
                    remove_file(archive_path)

            return archive_path

        return archive

    def _upload_file(self):
        def upload(path):
            # uses function attributes to track progress
            # upload.size, upload.progress, upload.stop
            upload.size = op.getsize(path)

            res = self._parent.web_api.upload_file(path, "application/zip",
                                                   callback)
            was_error = False
            msg = tr("Can't upload archive file")
            if res and "result" in res:
                if res["result"] == "success":
                    filename = res.get("file_name", "")
                else:
                    was_error = True
                    msg = str(res.get("info", msg))
            else:
                was_error = True
            if was_error and not upload.stop:
                self._parent.show_tray_notification(msg)
                self._sending_error.emit()
                raise SendingError(msg)

            remove_file(path)
            return filename

        def callback(monitor):
            upload.progress = monitor.bytes_read
            if upload.stop:
                raise SendingError("Stopped")

        return upload

    def _on_link_activated(self):
        open_link(self._parent.get_help_uri())()
        self.close()

    def _check_feedback_needed(self):
        if self._feedback_timer.isActive():
            return

        start_date = get_init_done()
        now = datetime.datetime.now()
        logger.debug("Start date is %s", start_date)
        if start_date is None:
            return

        interval = (start_date - now) \
                   + datetime.timedelta(days=self.DAYS_TO_FEEDBACK)

        if interval.total_seconds() <= 0:
            logger.debug("Feedback form date time is now")
            self._show_feedback_form()
        else:
            self._feedback_timer.setInterval(interval.seconds * 1000)
            self._feedback_timer.start()
            logger.debug("Feedback form date time is %s", now + interval)

    def _show_feedback_form(self):
        if self._is_opened:
            self._feedback_timer.setInterval(self.SHORT_FEEDBACK_INTERVAL)
            self._feedback_timer.start()
            return

        self._feedback_mode = True
        self._selected_index = 3
        self._dialog.closeEvent = self._close_event
        window_title = self._dialog.windowTitle()
        label_text = self._ui.text_label.text()
        feedback_text = tr("Please leave your feedback for Pvtbox")
        self._ui.text_label.setText(
            "<html><head/><body><p>{}</p></body></html>".format(feedback_text))
        self._dialog.setWindowTitle(tr("Feedback"))
        self.show()
        self._dialog.setWindowTitle(window_title)
        self._ui.text_label.setText(label_text)

    def _close_event(self, event):
        if event.spontaneous():
            self._clear_feedback_flag()
        self._old_close_event(event)

    def _clear_feedback_flag(self):
        if self._feedback_mode:
            logger.debug("Feedback flag cleared")
            clear_init_done()
            self._feedback_mode = False
            self._dialog.closeEvent = self._old_close_event

    def _on_exit_request(self):
        if self._is_opened:
            self.close()
Example #4
0
class View(QMainWindow):
    def __init__(self, model, controller):
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        #################################################################
        # define GUI layout and connect input widgets to external slots #
        #################################################################

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # figure0 for signal
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter-
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # figure1 for marker
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # figure2 for statistics
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        # navigation bar
        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # peak editing
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # peak saving batch
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # peak auto-correction batch
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # selecting stats for saving
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # channel selection
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        # initialize with default value
        self._model.set_signalchan(self.sigchanmenu.currentText())

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        # initialize with default value
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # processing mode (batch or single file)
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # modality selection
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # segment selection; this widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap);
        # it provides utilities to select a segment from the signal
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        # disable closing such that widget can only be closed by confirming
        # selection or custom button
        self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures)
        # Limit number of decimals to four.
        regex = QRegExp("[0-9]*\.?[0-9]{4}")
        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_signal)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        # reset the segment to None
        self.segmentermap.setMapping(self.abortedit, 2)

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Set up dialog to gather user input for custom files.

        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # set up menubar
        menubar = self.menuBar()

        # signal menu
        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.get_wpathsignal)
        signalmenu.addAction(saveSignal)

        # peak menu
        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.get_wpathpeaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.get_rpathpeaks)
        peakmenu.addAction(loadPeaks)

        # stats menu
        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.get_wpathstats)
        statsmenu.addAction(saveStats)

        # set up status bar to display error messages and current file path
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        # set up the central widget containing the plot and navigationtoolbar
        self.centwidget = QWidget()
        self.setCentralWidget(self.centwidget)

        # connect canvas0 to keyboard and mouse input for peak editing;
        # only widgets (e.g. canvas) that currently have focus capture
        # keyboard input: "You must enable keyboard focus for a widget if
        # it processes keyboard events."
        self.canvas0.setFocusPolicy(Qt.ClickFocus)
        self.canvas0.setFocus()
        self.canvas0.mpl_connect("key_press_event",
                                 self._controller.edit_peaks)
        self.canvas0.mpl_connect("button_press_event", self.get_xcursor)

        # arrange the three figure canvases in splitter object
        self.splitter = QSplitter(Qt.Vertical)
        # setting opaque resizing to false is important, since resizing gets
        # very slow otherwise once axes are populated
        self.splitter.setOpaqueResize(False)
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        # define GUI layout
        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        ##############################################
        # connect output widgets to external signals #
        ##############################################
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    ###########
    # methods #
    ###########

    def plot_signal(self, value):
        self.ax00.clear()
        self.ax00.relim()
        # reset navitools history
        self.navitools.update()
        self.line00 = self.ax00.plot(self._model.sec, value, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()
#        print("plot_signal listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_peaks(self, value):
        # self.scat is listed in ax.collections
        if self.ax00.collections:
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[value],
                                      self._model.signal[value],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()
#        print("plot_peaks listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_segment(self, value):
        # If an invalid signal has been selected reset the segmenter interface.
        if value is None:
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(value[0],
                                             value[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_marker(self, value):
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(value[0], value[1])
        self.canvas1.draw()
#        print("plot_marker listening")

    def plot_period(self, value):
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, value, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, value)
        self.ax20.set_ylim(bottom=min(value), top=max(value))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_period listening")

    def plot_rate(self, value):
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, value, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, value)
        self.ax21.set_ylim(bottom=min(value), top=max(value))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_rate listening")

    def plot_tidalamp(self, value):
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, value, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, value)
        self.ax22.set_ylim(bottom=min(value), top=max(value))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_tidalamp listening")

    def display_path(self, value):
        self.currentFile.setText(value)

    def display_status(self, status):
        # display status until new status is set
        self.statusBar.showMessage(status)

    def display_progress(self, value):
        # if value is 0, the progressbar indicates a busy state
        self.progressBar.setRange(0, value)

    def toggle_segmenter(self, value):
        if not self._model.loaded:
            return
        # Open segmenter when called from signalmenu or clear segmenter
        # upon selection of invalid segment.
        if value == 1:
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segment has been confirmed.
        elif value == 0:
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segmentation has been aborted (reset
        # segment).
        elif value == 2:
            self._model.set_segment([0,
                                     0])  # This will reset the model to None
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()

    def enable_segmentedit(self):
        # disable peak editing to avoid interference
        self.editcheckbox.setChecked(False)
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog"""

        # Check if one of the mandatory fields is missing.
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text()

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.get_fpaths()

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.get_fpaths()  # move on to file selection

    def get_xcursor(self, event):
        # event.button 1 corresponds to left mouse button
        if event.button != 1:
            return
        # limit number of decimal places to two
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(event.xdata))
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(event.xdata))
        # disable segment cursor again after value has been set
        self.segmentcursor = False

    def select_stats(self, event):
        """
        select or deselect statistics to be saved; toggle boolean with xor
        operator ^=, toggle color with dictionary
        """
        self._model.savestats[event] ^= True
        line = None
        if event == "period":
            if self.line20:
                line = self.line20[0]
        elif event == "rate":
            if self.line21:
                line = self.line21[0]
        elif event == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, event):
        if event in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif event == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif event == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif event == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
def snip(bounding_box=None,
         file_name_pattern=DEFAULT_OUTPUT_FILE_NAME_PATTERN,
         override=False,
         show_time=1000,
         assign_description=0):
    """ Snip screen image and save it to file.

    :param bounding_box: [tuple] The image rectangle in screen, formatted in (left, upper, width, height).
    :param file_name_pattern: [string] The file name pattern (absolute path or relative path to this python script file). For example: "ScreenSnippingImages/ScreenSnippingImage_%Y-%m-%d_%H-%M-%S.png".
    :param override: [bool] Whether to override the output file if it exists before.
    :param show_time: [int] Milliseconds time to show the screen image (if 0, the image won't be shown).
    :param assign_description: [int] Whether to assign description to the screen image (1 for manually input, 2 for OCR, others for no description).
    """

    logging.info("Started snipping screen.")

    screen_image = get_screen_image(bounding_box=bounding_box)
    file_name = save_image(image=screen_image,
                           file_name_pattern=file_name_pattern,
                           override=override)

    if file_name:
        logging.info(
            "Screen image has been saved in file: {}".format(file_name))

        if show_time > 0:
            image_dialog = QDialog()
            image_dialog.setWindowTitle(APP_DESCRIPTION + " " + APP_VERSION)
            image_dialog.setWindowFlags(Qt.WindowStaysOnTopHint)
            image_dialog.setFixedSize(640, 360)
            image_dialog.setContentsMargins(0, 0, 0, 0)
            image_label = QLabel()
            image_dialog_layout = QGridLayout(image_dialog)
            image_dialog_layout.setContentsMargins(0, 0, 0, 0)
            image_dialog_layout.addWidget(image_label)
            image_label.setPixmap(screen_image)
            image_label.setScaledContents(True)
            QTimer().singleShot(10, image_dialog.activateWindow)
            QTimer().singleShot(show_time, image_dialog.close)
            image_dialog.exec()

            if assign_description == 1:
                description_input_dialog = QInputDialog()
                description_input_dialog.setWindowTitle(APP_DESCRIPTION + " " +
                                                        APP_VERSION)
                description_input_dialog.setWindowFlags(
                    Qt.WindowStaysOnTopHint)
                description_input_dialog.setFixedSize(400, 200)
                description_input_dialog.setInputMode(
                    description_input_dialog.TextInput)
                description_input_dialog.setLabelText(
                    "Please input description:")
                QTimer().singleShot(10,
                                    description_input_dialog.activateWindow)
                description_input_dialog.exec()
                description = description_input_dialog.textValue()
                if description:
                    description_file_name = file_name + ".txt"
                    with open(description_file_name, "w") as file:
                        file.write(description)
                    logging.info(
                        "Assigned a description for screen image file: {}".
                        format(file_name))
                    logging.debug("Description: {}".format(description))
            elif assign_description == 2:
                import pytesseract
                text_from_image = pytesseract.image_to_string(
                    Image.open(file_name))
                description_file_name = file_name + "-OCR.txt"
                with open(description_file_name, "w") as file:
                    file.write(text_from_image)
                os.startfile(description_file_name)
            else:
                pass
    else:
        logging.error("Error occurred.")
Example #6
0
class DatePicker(QWidget):

    selectionChanged = Signal()

    def __init__(self, parent=None):
        super(DatePicker, self).__init__(parent)
        self.button = QPushButton(self)
        icon = QIcon("logo.svg")
        self.button.setIcon(icon)
        self.setFixedSize(32, 32)
        self.button.setFixedSize(32, 32)
        self.button.setIconSize(QSize(22, 22))

        self.__margin__ = 5

        self.dialog = QDialog()
        self.dialog.setWindowFlags(Qt.Window | Qt.FramelessWindowHint
                                   | Qt.Popup)
        self.dialog.setFixedSize(480, 240)
        self.dialog.setLayout(QHBoxLayout())
        self.calender = QCalendarWidget(self)
        self.dialog.layout().addWidget(self.calender)
        self.dialog.layout().setContentsMargins(0, 0, 0, 0)
        self.dialog.layout().setSpacing(0)

        self.button.clicked.connect(self, SLOT("showCalender()"))

        self.calender.selectionChanged.connect(self.__emitSelectionChanged__)

    @Slot()
    def showCalender(self):
        print('in show')

        p = self.mapToGlobal(QPoint(0, self.height() + self.__margin__))

        self.dialog.setGeometry(p.x(), p.y(), 0, 0)
        self.dialog.show()

    def setIcon(self, icon):
        if type(icon) is QIcon:
            self.button.setIcon(icon)
        elif type(icon) is str:
            self.button.setIcon(QIcon(icon))
        else:
            raise Exception(
                'Wrong argument type, icon should be either PySide2.QtGui.QIcon or str "string"'
            )

    def icon(self):
        return self.button.icon()

    def setIconSize(self, iconsize):
        if type(iconsize) is QSize:
            self.button.setIconSize(iconsize)
        elif type(iconsize) is int:
            self.button.setIcon(QSize(iconsize, iconsize))
        elif type(type) is iter:
            import collections
            if isinstance(iconsize, collections.Iterable):
                if len(iconsize) == 1:
                    self.setIconSize(iconsize[0])
                elif len(iconsize) == 2:
                    self.setIconSize(QSize(iconsize[0], iconsize[1]))
                else:
                    raise Exception()
        else:
            raise Exception(
                "Wrong argument type, iconSize should be either PySide2.QtCore.QSize or int value or width and height "
                "or iterable contains one QSize, one int or two int values for width and height respectively"
            )

    def iconSize(self):
        return self.button.iconSize()

    def setFirstDayOfWeek(self, dayOfWeek):
        if type(dayOfWeek) is Qt.DayOfWeek:
            self.calender.setFirstDayOfWeek(dayOfWeek)
        elif type(dayOfWeek) is int:
            if dayOfWeek < 1 or dayOfWeek > 7:
                raise Exception(
                    "Wrong argument, dayOfWeek should be from 1 to 7 (Monday --> Sunday)"
                )
            self.calender.setFirstDayOfWeek(Qt.DayOfWeek(dayOfWeek))
        else:
            raise Exception(
                "Wrong type, dayOfWeek should be either PySide2.QtCore.Qt.DayOf or int (1 --> 7) (Monday --> Sunday)"
            )

    def firstDayOfWeek(self):
        self.calender.firstDayOfWeek()

    def selectedDate(self):

        self.calender.selectedDate()

    def setSelectedDate(self, args, kwargs):
        self.calender.setSelectedDate(args, kwargs)

    def minimumDate(self):
        self.calender.minimumDate()

    def setMinimumDate(self):
        self.calender.setMinimumDate()

    def selectedDate(self):

        return self.calender.selectedDate()

    def __emitSelectionChanged__(self):
        self.selectionChanged.emit()
Example #7
0
class CollaborationSettingsDialog(object):
    ADD_BUTTON_ACTIVE_COLOR = "#f78d1e"
    ADD_BUTTON_PASSIVE_COLOR = "#9a9a9a"
    ERROR_COLOR = '#FF9999'
    LINE_EDIT_NORMAL_COLOR = "#EFEFF1"

    def __init__(self, parent, parent_window, colleagues, folder, dp):
        self._dialog = QDialog(parent_window)
        self._dp = dp
        self._colleagues = colleagues
        self._parent = parent
        self._parent_window = parent_window
        self._folder = folder

        self._is_owner = False
        self._dialog.setWindowIcon(QIcon(':/images/icon.png'))
        self._ui = Ui_Dialog()
        self._ui.setupUi(self._dialog)

        self._init_ui()

    def _init_ui(self):
        self._dialog.setWindowFlags(Qt.Dialog)
        self._dialog.setAttribute(Qt.WA_TranslucentBackground)
        self._dialog.setAttribute(Qt.WA_MacFrameworkScaled)
        self._dialog.setWindowTitle(self._dialog.windowTitle() + self._folder)

        self._ui.colleagues_list.setAlternatingRowColors(True)
        self._colleagues_list = ColleaguesList(self._parent,
                                               self._ui.colleagues_list,
                                               self._dp, self._show_menu)

        self._loader_movie = QMovie(":/images/loader.gif")
        self._ui.loader_label.setMovie(self._loader_movie)
        self._set_fonts()

        self._ui.add_frame.setVisible(False)
        self._set_add_button_background(self.ADD_BUTTON_PASSIVE_COLOR)
        self._ui.add_button.clicked.connect(self._on_add_button_clicked)
        self._ui.add_button.setVisible(False)
        self._ui.close_button.clicked.connect(self._on_close_button_clicked)
        self._ui.refresh_button.clicked.connect(self._on_refresh)

        self._line_edit_style = "background-color: {};"
        self._ui.error_label.setStyleSheet("color: {};".format(
            self.ERROR_COLOR))

    def _set_fonts(self):
        ui = self._ui
        controls = [
            ui.colleagues_label, ui.mail_edit, ui.edit_radio, ui.view_radio,
            ui.add_button
        ]

        for control in controls:
            font = control.font()
            font_size = control.font().pointSize() * self._dp
            if font_size > 0:
                control_font = QFont(font.family(), font_size)
                control_font.setBold(font.bold())
                control.setFont(control_font)

    def show(self):
        logger.debug("Opening collaboration settings dialog")

        screen_width = QApplication.desktop().width()
        parent_x = self._dialog.parent().x()
        parent_width = self._dialog.parent().width()
        width = self._dialog.width()
        offset = 16
        if parent_x + parent_width / 2 > screen_width / 2:
            x = parent_x - width - offset
            if x < 0:
                x = 0
        else:
            x = parent_x + parent_width + offset
            diff = x + width - screen_width
            if diff > 0:
                x -= diff
        self._dialog.move(x, self._dialog.parent().y())

        # Execute dialog
        self._dialog.raise_()
        self.show_cursor_loading(True)
        self._dialog.exec_()

    def close(self):
        self._dialog.reject()

    def show_cursor_loading(self, show_movie=False):
        if show_movie:
            self._ui.stackedWidget.setCurrentIndex(1)
            self._loader_movie.start()
        else:
            self._dialog.setCursor(Qt.WaitCursor)
            self._parent_window.setCursor(Qt.WaitCursor)

    def show_cursor_normal(self):
        self._dialog.setCursor(Qt.ArrowCursor)
        self._parent_window.setCursor(Qt.ArrowCursor)
        if self._loader_movie.state() == QMovie.Running:
            self._loader_movie.stop()

    def show_colleagues(self):
        if not self._colleagues:
            self._ui.stackedWidget.setCurrentIndex(2)
        else:
            self._ui.stackedWidget.setCurrentIndex(0)
            self._colleagues_list.show_colleagues(self._colleagues)
        self.show_cursor_normal()

    def set_owner(self, is_owner):
        self._is_owner = is_owner
        self._ui.add_button.setVisible(self._is_owner)

    def _on_add_button_clicked(self):
        if self._ui.add_frame.isVisible():
            if not self._validate_email():
                return

            to_edit = self._ui.edit_radio.isChecked()
            self._ui.add_frame.setVisible(False)
            self._set_add_button_background(self.ADD_BUTTON_PASSIVE_COLOR)
            self._parent.add_colleague(self._ui.mail_edit.text(), to_edit)
        else:
            self._ui.add_frame.setVisible(True)
            self._set_add_button_background(self.ADD_BUTTON_ACTIVE_COLOR)
            self._ui.mail_edit.setText("")

    def _set_add_button_background(self, color):
        self._ui.add_button.setStyleSheet(
            'background-color: {}; color: #fff; '
            'border-radius: 4px; font: bold "Gargi"'.format(color))

    def _on_close_button_clicked(self):
        self._ui.add_frame.setVisible(False)
        self._set_add_button_background(self.ADD_BUTTON_PASSIVE_COLOR)
        self._clear_error()
        self._ui.mail_edit.setText("")

    def _validate_email(self):
        email_control = self._ui.mail_edit
        email_control.setStyleSheet(
            self._line_edit_style.format(self.LINE_EDIT_NORMAL_COLOR))
        regex = '^.+@.{2,}$'

        email_control.setText(email_control.text().strip())
        if not re.match(regex, email_control.text()):
            self._ui.error_label.setText(tr("Please enter a valid e-mail"))
            email_control.setStyleSheet(
                self._line_edit_style.format(self.ERROR_COLOR))
            email_control.setFocus()
            return False

        self._clear_error()
        return True

    def _clear_error(self):
        self._ui.error_label.setText("")
        self._ui.mail_edit.setStyleSheet(
            self._line_edit_style.format(self.LINE_EDIT_NORMAL_COLOR))

    def _on_refresh(self):
        self.show_cursor_loading()
        self._parent.query_collaboration_info()

    def _show_menu(self, colleague, pos):
        if not self._is_owner and not colleague.is_you or colleague.is_deleting:
            return

        menu = QMenu(self._ui.colleagues_list)
        menu.setStyleSheet("background-color: #EFEFF4; ")
        if colleague.is_you:
            if colleague.is_owner:
                action = menu.addAction(tr("Quit collaboration"))
                action.triggered.connect(self._on_quit_collaboration)
            else:
                action = menu.addAction(tr("Leave collaboration"))
                action.triggered.connect(self._on_leave_collaboration)
        else:
            rights_group = QActionGroup(menu)
            rights_group.setExclusive(True)

            menu.addSection(tr("Access rights"))
            action = menu.addAction(tr("Can view"))
            action.setCheckable(True)
            rights_action = rights_group.addAction(action)
            rights_action.setData(False)
            rights_action.setChecked(not colleague.can_edit)
            action = menu.addAction(tr("Can edit"))
            action.setCheckable(True)
            rights_action = rights_group.addAction(action)
            rights_action.setChecked(colleague.can_edit)
            rights_action.setData(True)
            rights_group.triggered.connect(
                lambda a: self._on_grant_edit(colleague, a))
            menu.addSeparator()

            action = menu.addAction(tr("Remove user"))
            action.triggered.connect(lambda: self._on_remove_user(colleague))

        pos_to_show = QPoint(pos.x(), pos.y() + 10)
        menu.exec_(pos_to_show)

    def _on_quit_collaboration(self):
        alert_str = "Collaboration will be cancelled, " \
                    "collaboration folder will be deleted " \
                    "from all colleagues' Pvtbox secured sync folders " \
                    "on all nodes."
        if self._user_confirmed_action(alert_str):
            self._parent.cancel_collaboration()

    def _on_leave_collaboration(self):
        alert_str = "Collaboration folder will be deleted " \
                    "from Pvtbox secured sync folders " \
                    "on all your nodes."
        if self._user_confirmed_action(alert_str):
            self._parent.leave_collaboration()

    def _on_remove_user(self, colleague):
        alert_str = "Colleague {} will be removed from collaboration. " \
                    "Collaboration folder will be deleted from colleague's " \
                    "Pvtbox secured sync folders on all nodes." \
            .format(colleague.email)
        if self._user_confirmed_action(alert_str):
            self._parent.remove(colleague.id)

    def _on_grant_edit(self, colleague, action):
        to_edit = action.data()
        self._parent.grant_edit(colleague.id, to_edit)

    def _user_confirmed_action(self, alert_str):
        msg = tr("<b>Are</b> you <b>sure</b>?<br><br>{}".format(alert_str))
        user_answer = msgbox(msg,
                             title=' ',
                             buttons=[
                                 (tr('Cancel'), 'Cancel'),
                                 (tr('Yes'), 'Yes'),
                             ],
                             parent=self._dialog,
                             default_index=0,
                             enable_close_button=True)

        return user_answer == 'Yes'
Example #8
0
class View(QMainWindow):
    """View component of the MVC application.

    Presents the state of the application as well as the available means of
    interaction. Receives updates about the state from the Model and informs
    Controller about user interactions.

    """
    def __init__(self, model, controller):
        """Define GUI elements and their layout.

        Parameters
        ----------
        model : QObject
            Model component of the MVC application.
        controller : QObject
            Controller component of the MVC application.
        """
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # Figure for biosignal.
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter.
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # Figure for marker.
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # Figure for statistics.
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # Peak editing.
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # Peak saving during batch processing.
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # Peak auto-correction during batch processing.
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # Selection of stats for saving.
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # Channel selection.
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        self._model.set_signalchan(
            self.sigchanmenu.currentText())  # initialize with default value

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # Processing mode.
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # Modality selection.
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # Segment selection. This widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap).
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        self.segmenter.setFeatures(
            QDockWidget.NoDockWidgetFeatures
        )  # disable closing such that widget can only be closed by confirming selection or custom button
        regex = QRegExp(
            "[0-9]*\.?[0-9]{4}")  # Limit number of decimals to four

        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_dataset)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.abortedit,
                                     2)  # resets the segment to None

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Custom file dialog.
        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # Layout.
        menubar = self.menuBar()

        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.save_channels)
        signalmenu.addAction(saveSignal)

        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.save_peaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.load_peaks)
        peakmenu.addAction(loadPeaks)

        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.save_stats)
        statsmenu.addAction(saveStats)

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        self.centwidget = QWidget()  # contains figures and navigationtoolbar
        self.setCentralWidget(self.centwidget)

        self.canvas0.setFocusPolicy(
            Qt.ClickFocus
        )  # only widgets (e.g. canvas) that currently have focus capture keyboard input
        self.canvas0.setFocus()
        self.canvas0.mpl_connect(
            "key_press_event", self._controller.edit_peaks
        )  # connect canvas to keyboard input for peak editing
        self.canvas0.mpl_connect(
            "button_press_event",
            self.get_xcursor)  # connect canvas to mouse input for peak editing

        self.splitter = QSplitter(
            Qt.Vertical
        )  # arrange the three figure canvases in splitter object
        self.splitter.setOpaqueResize(
            False)  # resizing gets very slow otherwise once axes are populated
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        # Subscribe to updates from the Model.
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    def plot_signal(self, signal):
        """Plot the biosignal.

        Receives updates in signal from Model.

        Parameters
        ----------
        signal : ndarray of float
            Vector representing the biosignal.

        See Also
        --------
        model.Model.signal
        """
        self.ax00.clear()
        self.ax00.relim()
        self.navitools.update()  # reset navitools history
        self.line00 = self.ax00.plot(self._model.sec, signal, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()

    def plot_peaks(self, peaks):
        """Plot the extrema.

        Receives updates in peaks from Model.

        Parameters
        ----------
        peaks : ndarray of int
            Vector representing the extrema.

        See Also
        --------
        model.Model.peaks
        """
        if self.ax00.collections:  # self.scat is listed in ax.collections
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[peaks],
                                      self._model.signal[peaks],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()

    def plot_segment(self, segment):
        """Show preview of segment.

        Receives updates in segment from Model.

        Parameters
        ----------
        segment : list of float
            The start and end of the segment in seconds.

        See Also
        --------
        model.Model.segment
        """
        if segment is None:  # if an invalid segment has been selected reset the segmenter interface
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(segment[0],
                                             segment[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)

    def plot_marker(self, marker):
        """Plot the marker channel.

        Receives updates in marker from Model.

        Parameters
        ----------
        marker : list of ndarray
            Seconds element is vector representing the marker channel and first
            element is a vector representing the seconds associated with each
            sample in the marker channel.

        See Also
        --------
        model.Model.marker
        """
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(marker[0], marker[1])
        self.canvas1.draw()

    def plot_period(self, period):
        """Plot instantaneous period.

        Receives updates in period from Model.

        Parameters
        ----------
        period : ndarray of float
            Vector representing the instantaneous period.

        See Also
        --------
        model.Model.periodintp
        """
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, period, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, period)
        self.ax20.set_ylim(bottom=min(period), top=max(period))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_rate(self, rate):
        """Plot instantaneous rate.

        Receives updates in rate from Model.

        Parameters
        ----------
        rate : ndarray of float
            Vector representing the instantaneous rate.

        See Also
        --------
        model.Model.rateintp
        """
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, rate, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, rate)
        self.ax21.set_ylim(bottom=min(rate), top=max(rate))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_tidalamp(self, tidalamp):
        """Plot instantaneous tidal amplitude.

        Receives updates in tidal amplitude from Model.

        Parameters
        ----------
        tidalamp : ndarray of float
            Vector representing the instantaneous tidal amplitude.

        See Also
        --------
        model.Model.tidalampintp
        """
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp)
        self.ax22.set_ylim(bottom=min(tidalamp), top=max(tidalamp))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def display_path(self, path):
        """Display the path to the current dataset.

        Receives update in path from Model.

        Parameters
        ----------
        path : str
            The path to the file containing the current dataset.

        See Also
        --------
        model.Model.rpathsignal
        """
        self.currentFile.setText(path)

    def display_status(self, status):
        """Display a status message.

        Receives updates in status message from Model.

        Parameters
        ----------
        status : str
            A status message.

        See Also
        --------
        model.Model.status
        """
        self.statusBar.showMessage(status)

    def display_progress(self, progress):
        """Display task progress.

        Receives updates in progress from Model.

        Parameters
        ----------
        progress : int
            Integer indicating the current task progress.

        See Also
        --------
        model.Model.progress, controller.Worker, controller.threaded
        """
        self.progressBar.setRange(
            0, progress)  # indicates busy state if progress is 0

    def toggle_segmenter(self, visibility_state):
        """Toggle visibility of segmenter widget.

        Parameters
        ----------
        visibility_state : int
            Update in state of the segmenter widget's visibility.
        """
        if not self._model.loaded:
            return
        if visibility_state == 1:  # open segmenter when called from signalmenu or clear segmenter upon selection of invalid segment
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
        elif visibility_state == 0:  # close segmenter after segment has been confirmed
            self.segmenter.setVisible(False)
        elif visibility_state == 2:  # close segmenter after segmentation has been aborted (reset segment)
            self._model.set_segment([0, 0])
            self.segmenter.setVisible(False)
        if self.ax00.patches:
            self.ax00.patches[0].remove()
            self.canvas0.draw()

    def enable_segmentedit(self):
        """Associate cursor position with a specific segmenter text field.

        Regulate if cursor position is associated with editing the start or
        end of a segment.
        """
        self.editcheckbox.setChecked(
            False)  # disable peak editing to avoid interference
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def get_xcursor(self, mouse_event):
        """Retrieve input to segmenter text fields from cursor position.

        Retrieve the start or end of a segment in seconds from the current
        cursor position.

        Parameters
        ----------
        mouse_event : MouseEvent
            Event containing information about the current cursor position
            in data coordinates.

        See Also
        --------
        matplotlib.backend_bases.MouseEvent
        """
        if mouse_event.button != 1:  # 1 = left mouse button
            return
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(
                mouse_event.xdata))  # limit number of decimal places to two
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(mouse_event.xdata))
        self.segmentcursor = False  # disable segment cursor again after value has been set

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog."""
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text(
        )  # check if one of the mandatory fields is missing

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.load_chanels

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.load_channels()  # move on to file selection

    def select_stats(self, statistic):
        """Select statistics to be saved.

        Parameters
        ----------
        statistic : str
            The selected statistic.
        """
        self._model.savestats[
            statistic] ^= True  # toggle boolean with xor operator
        line = None
        if statistic == "period":
            if self.line20:
                line = self.line20[0]
        elif statistic == "rate":
            if self.line21:
                line = self.line21[0]
        elif statistic == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, state):
        """Toggle availability of configuration options.

        Based on current state.

        Parameters
        ----------
        state : str
            The aspect of the current state to which the availability of
            configuration options needs to be adapted.
        """
        if state in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif state == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif state == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif state == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        """Reset plot elements associated with the current dataset."""
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
class TransfersDialog(object):
    FILE_LIST_ITEM_SIZE = 88
    CURRENT_TASK_STATES = {
        DOWNLOAD_STARTING, DOWNLOAD_LOADING, DOWNLOAD_FINISHING,
        DOWNLOAD_FAILED
    }
    ERROR_STATES = {DOWNLOAD_NO_DISK_ERROR}
    STATE_NOTIFICATIONS = {
        DOWNLOAD_NOT_READY: tr("Waiting for nodes..."),
        DOWNLOAD_READY: tr("Waiting for other downloads..."),
        DOWNLOAD_STARTING: tr("Starting download..."),
        DOWNLOAD_LOADING: tr("Downloading..."),
        DOWNLOAD_FINISHING: tr("Finishing download..."),
        DOWNLOAD_FAILED: tr("Download failed"),
        DOWNLOAD_NO_DISK_ERROR: tr("Insufficient disk space"),
    }

    WORKING = 0
    PAUSED = 1
    RESUMING = 2
    PAUSED_NOTIFICATIONS = {
        PAUSED: tr("Paused..."),
        RESUMING: tr("Resuming..."),
    }

    def __init__(self,
                 parent,
                 revert_downloads,
                 pause_resume_clicked,
                 add_to_sync_folder,
                 handle_link,
                 transfers_ready,
                 paused,
                 dp=None,
                 speed_chart_capacity=0,
                 download_speeds=(),
                 upload_speeds=(),
                 signalserver_address=''):
        self._dialog = QDialog(parent)
        self._dp = dp
        self._revert_downloads = revert_downloads
        self._pause_resume_clicked = pause_resume_clicked
        self._add_to_sync_folder = add_to_sync_folder
        self._handle_link = handle_link
        self._transfers_ready = transfers_ready
        self._parent = parent
        self._signalserver_address = signalserver_address

        self._dialog.setWindowIcon(QIcon(':/images/icon.png'))
        self._ui = Ui_Dialog()
        self._ui.setupUi(self._dialog)

        self._reverted_downloads = set()
        self._downloads_items = defaultdict(list)
        self._uploads_items = defaultdict(list)
        self._http_downloads = set()

        self._paused_state = self.WORKING if not paused else self.PAUSED

        self._total_files = 0
        self._total_size = 0

        self._init_ui()
        self._init_charts(download_speeds, upload_speeds, speed_chart_capacity)

    def _init_ui(self):
        self._icon_urls = {
            'add_file': [
                ':/images/transfers/add_file.svg',
                ':/images/transfers/add_file_hovered.svg'
            ],
            'link_insert': [
                ':/images/transfers/link_insert.svg',
                ':/images/transfers/link_insert_hovered.svg'
            ],
            'revert':
            [':/images/revert.svg', ':/images/transfers/revert_clicked.svg'],
            'pause': [':/images/pause.svg', ':/images/pause_hovered.svg'],
            'play': [':/images/play.svg', ':/images/play_hovered.svg'],
        }

        ui = self._ui
        self._dialog.setWindowFlags(Qt.Dialog)
        self._dialog.setAttribute(Qt.WA_TranslucentBackground)
        self._dialog.setAttribute(Qt.WA_MacFrameworkScaled)

        self._set_file_list_options(ui.downloads_list)
        self._set_file_list_options(ui.uploads_list)
        ui.downloads_list.verticalScrollBar().valueChanged.connect(
            self.on_downloads_scroll_changed)
        ui.uploads_list.verticalScrollBar().valueChanged.connect(
            self.on_uploads_scroll_changed)

        self._old_main_resize_event = ui.centralwidget.resizeEvent
        ui.centralwidget.resizeEvent = self._main_resize_event

        self._set_fonts()

        ui.add_button.enterEvent = lambda _: \
            self._enter_leave(ui.add_button, 'add_file')
        ui.add_button.leaveEvent = lambda _: \
            self._enter_leave(ui.add_button, 'add_file', False)
        ui.insert_link_button.enterEvent = lambda _: \
            self._enter_leave(ui.insert_link_button, 'link_insert')
        ui.insert_link_button.leaveEvent = lambda _: \
            self._enter_leave(ui.insert_link_button, 'link_insert', False)
        ui.revert_all_button.enterEvent = lambda _: \
            self._enter_leave(ui.revert_all_button, 'revert')
        ui.revert_all_button.leaveEvent = lambda _: \
            self._enter_leave(ui.revert_all_button, 'revert', False)
        ui.pause_all_button.enterEvent = lambda _: \
            self._enter_leave(ui.pause_all_button,
                              'play' if self._paused_state == self.PAUSED
                              else 'pause')
        ui.pause_all_button.leaveEvent = lambda _: \
            self._enter_leave(ui.pause_all_button,
                              'play' if self._paused_state == self.PAUSED
                              else 'pause', False)

        if self._paused_state == self.PAUSED:
            ui.pause_all_button.setText(tr("Resume all"))
            ui.pause_all_button.setIcon(QIcon(":/images/play.svg"))
        else:
            ui.pause_all_button.setText(tr("Pause all   "))
            ui.pause_all_button.setIcon(QIcon(":/images/pause.svg"))

    def _init_charts(self, download_speeds, upload_speeds,
                     speed_chart_capacity):
        self._last_downloads_speeds = deque(download_speeds,
                                            maxlen=speed_chart_capacity)
        self._last_uploads_speeds = deque(upload_speeds,
                                          maxlen=speed_chart_capacity)
        max_download_speed = max(self._last_downloads_speeds) \
            if self._last_downloads_speeds else 0
        max_upload_speed = max(self._last_uploads_speeds) \
            if self._last_uploads_speeds else 0
        max_speed = max(max_download_speed, max_upload_speed)
        self._download_speed_chart = SpeedChart(
            self._ui.downloads_speed_widget,
            speed_chart_capacity,
            QColor("green"),
            speeds=download_speeds,
            dp=self._dp,
            max_speed=max_speed)
        self._upload_speed_chart = SpeedChart(self._ui.uploads_speed_widget,
                                              speed_chart_capacity,
                                              QColor("orange"),
                                              speeds=upload_speeds,
                                              is_upload=True,
                                              dp=self._dp,
                                              max_speed=max_speed)

    def on_size_speed_changed(self, download_speed, download_size,
                              upload_speed, upload_size):
        self._ui.download_speed_value.setText(
            tr("{}/s").format(format_with_units(download_speed)))
        self._ui.download_size_value.setText(format_with_units(download_size))

        self._ui.upload_speed_value.setText(
            tr("{}/s").format(format_with_units(upload_speed)))
        self._ui.upload_size_value.setText(format_with_units(upload_size))

    def on_downloads_info_changed(self, downloads_info, supress_paused=False):
        logger.verbose("Updating downloads_info")
        self._update_downloads_list(downloads_info, supress_paused)
        self._transfers_ready()

    def on_downloads_state_changed(self, changed_info):
        if self._paused_state == self.PAUSED:
            self._transfers_ready()
            return

        elif self._paused_state == self.RESUMING:
            self._paused_state = self.WORKING

        logger.verbose("Changing downloads state with %s", changed_info)
        for obj_id in changed_info:
            items = self._downloads_items.get(obj_id, [])
            for item in items:
                self._change_item_widget(self._ui.downloads_list, item,
                                         changed_info[obj_id]["state"],
                                         changed_info[obj_id]["downloaded"])

        self._transfers_ready()

    def on_uploads_info_changed(self, uploads_info):
        logger.verbose("Updating uploads_info")
        self._update_uploads_list(uploads_info)
        self._transfers_ready()

    def on_uploads_state_changed(self, changed_info):
        logger.verbose("Changing uploads state with %s", changed_info)
        for obj_id in changed_info:
            items = self._uploads_items.get(obj_id, [])
            for item in items:
                self._change_item_widget(self._ui.uploads_list, item,
                                         changed_info[obj_id]["state"],
                                         changed_info[obj_id]["uploaded"])

        self._transfers_ready()

    def refresh_time_deltas(self):
        self._refresh_file_list_time_deltas(self._ui.downloads_list,
                                            self._downloads_items)
        self._refresh_file_list_time_deltas(self._ui.uploads_list,
                                            self._uploads_items)

    def show(self, on_finished):
        def finished():
            self._dialog.finished.disconnect(finished)
            self._ui.pause_all_button.clicked.disconnect(pause_all)
            self._ui.revert_all_button.clicked.disconnect(revert_all)
            self._ui.add_button.clicked.disconnect(add)
            self._ui.insert_link_button.clicked.disconnect(insert_link)
            on_finished()

        def pause_all():
            self._toggle_paused_state()

        def revert_all():
            if self._downloads_items and \
                    not self._has_user_confirmed_revert():
                return

            self._revert_all()

        def add():
            self._on_add_to_sync_folder()

        def insert_link():
            self._on_insert_link()

        logger.debug("Opening transfers dialog")

        screen_width = QApplication.desktop().width()
        parent_x = self._dialog.parent().x()
        parent_width = self._dialog.parent().width()
        width = self._dialog.width()
        offset = 16
        if parent_x + parent_width / 2 > screen_width / 2:
            x = parent_x - width - offset
            if x < 0:
                x = 0
        else:
            x = parent_x + parent_width + offset
            diff = x + width - screen_width
            if diff > 0:
                x -= diff
        self._dialog.move(x, self._dialog.parent().y())

        self._dialog.setAcceptDrops(True)
        self._dialog.dragEnterEvent = self._drag_enter_event
        self._dialog.dropEvent = self._drop_event

        # Execute dialog
        self._dialog.finished.connect(finished)
        self._ui.pause_all_button.clicked.connect(pause_all)
        self._ui.revert_all_button.clicked.connect(revert_all)
        self._ui.add_button.clicked.connect(add)
        self._ui.insert_link_button.clicked.connect(insert_link)
        self._dialog.raise_()
        self._dialog.show()

    def raise_dialog(self):
        self._dialog.raise_()

    def close(self):
        self._dialog.reject()

    def revert_failed(self, failed_uuids):
        self._reverted_downloads.difference_update(set(failed_uuids))

    def set_nodes_num(self, nodes_num):
        self._dialog.setWindowTitle(
            tr("Transfers - {} peer(s) connected").format(nodes_num))

    def show_all_disconnected_alert(self):
        self._dialog.setWindowTitle(
            tr("Transfers - Connect more devices to sync"))

    def _get_downloads_obj_ids_sorted(self, downloads_info):
        def sort_key(obj_id):
            info = downloads_info[obj_id]
            return -info['priority'] * 10000 - \
                   (info['downloaded'] - info['size']) // (64 * 1024)

        current_tasks = []
        ready_tasks = []
        not_ready_tasks = []
        for obj_id, info in downloads_info.items():
            state = info["state"]
            if state in self.CURRENT_TASK_STATES:
                current_tasks.append(obj_id)
            elif state == DOWNLOAD_READY:
                ready_tasks.append(obj_id)
            else:
                not_ready_tasks.append(obj_id)
        ready_tasks.sort(key=sort_key)
        not_ready_tasks.sort(key=sort_key)
        obj_ids_sorted = current_tasks + ready_tasks + not_ready_tasks
        return obj_ids_sorted

    def _update_downloads_list(self, downloads_info, supress_paused=False):
        if self._paused_state == self.PAUSED and not supress_paused:
            return

        elif self._paused_state == self.RESUMING:
            self._paused_state = self.WORKING

        obj_ids_sorted = self._get_downloads_obj_ids_sorted(downloads_info)
        self._downloads_items.clear()
        self._http_downloads.clear()
        self._ui.downloads_list.setUpdatesEnabled(False)
        self._total_size = 0
        self._total_files = 0
        index = 0
        for obj_id in obj_ids_sorted:
            if obj_id in self._reverted_downloads:
                continue

            info = downloads_info[obj_id]
            for file_info in info["files_info"]:
                self._add_file_to_file_list(
                    index,
                    self._ui.downloads_list,
                    self._downloads_items,
                    obj_id,
                    rel_path=file_info["target_file_path"],
                    created_time=file_info["mtime"],
                    was_updated=not file_info.get("is_created", True),
                    is_deleted=file_info.get("is_deleted"),
                    transfered=info["downloaded"],
                    size=info["size"],
                    state=info["state"],
                    is_file=info["is_file"])
                self._total_size += info["size"]
                self._total_files += 1
                index += 1

        for i in range(index, self._ui.downloads_list.count()):
            item = self._ui.downloads_list.takeItem(index)
            self._ui.downloads_list.removeItemWidget(item)

        self._reverted_downloads.intersection_update(set(obj_ids_sorted))

        self._update_totals()
        self._set_revert_all_enabled()
        self._set_current_downloads_page()
        self._ui.downloads_list.setUpdatesEnabled(True)

    def _update_totals(self):
        self._ui.total_files_label.setText(
            tr("{} file(s)").format(self._total_files))
        self._ui.total_size_label.setText(format_with_units(self._total_size))

    def _update_uploads_list(self, uploads_info):
        self._uploads_items.clear()
        self._ui.uploads_list.setUpdatesEnabled(False)
        total_files = 0
        index = 0
        for obj_id in uploads_info:
            info = uploads_info[obj_id]
            for file_info in info["files_info"]:
                self._add_file_to_file_list(
                    index,
                    self._ui.uploads_list,
                    self._uploads_items,
                    obj_id,
                    rel_path=file_info["target_file_path"],
                    created_time=file_info["mtime"],
                    was_updated=not file_info.get("is_created", True),
                    is_deleted=file_info.get("is_deleted"),
                    transfered=info["uploaded"],
                    size=info["size"],
                    state=info["state"],
                    is_file=info["is_file"])
                total_files += 1
                index += 1

        for i in range(index, self._ui.uploads_list.count()):
            item = self._ui.uploads_list.takeItem(index)
            self._ui.uploads_list.removeItemWidget(item)

        self._set_current_uploads_page()
        self._ui.uploads_list.setUpdatesEnabled(True)

    def _set_fonts(self):
        ui = self._ui
        controls = [ui.no_downloads_label, ui.no_uploads_label]
        controls.extend([c for c in ui.downloads_frame.findChildren(QLabel)])
        controls.extend([c for c in ui.downloads_bottom.findChildren(QLabel)])
        controls.extend(
            [c for c in ui.downloads_bottom.findChildren(QPushButton)])
        controls.extend([c for c in ui.uploads_frame.findChildren(QLabel)])
        controls.extend(
            [c for c in ui.uploads_bottom.findChildren(QPushButton)])

        for control in controls:
            font = control.font()
            font_size = control.font().pointSize() * self._dp
            if font_size > 0:
                control.setFont(QFont(font.family(), font_size))

    def _enter_leave(self, button, icon_str, entered=True):
        icon_url = self._icon_urls[icon_str][int(entered)]
        button.setIcon(QIcon(icon_url))

    def _set_file_list_options(self, file_list):
        file_list.setFocusPolicy(Qt.NoFocus)
        file_list.setFont(QFont('Nano', 10 * self._dp))
        # file_list.setGridSize(QSize(
        #     self.FILE_LIST_ITEM_SIZE, self.FILE_LIST_ITEM_SIZE - 14))
        file_list.setResizeMode(QListView.Adjust)
        file_list.setAutoScroll(False)
        file_list.setUniformItemSizes(True)

    def _add_file_to_file_list(self,
                               index,
                               file_list,
                               items_dict,
                               obj_id,
                               rel_path,
                               created_time,
                               was_updated,
                               is_deleted,
                               transfered,
                               size=0,
                               state=None,
                               is_file=True):
        item = file_list.item(index)
        if item:
            item.setData(Qt.UserRole, [
                rel_path, created_time, size, was_updated, is_deleted,
                transfered, state, is_file, obj_id
            ])
            self._update_file_list_item_widget(file_list, item)
            items_dict[obj_id].append(item)
            return

        item = QListWidgetItem()
        item.setFlags(item.flags() & ~Qt.ItemIsSelectable)
        item.setSizeHint(QSize(file_list.width(), self.FILE_LIST_ITEM_SIZE))
        item.setData(Qt.UserRole, [
            rel_path, created_time, size, was_updated, is_deleted, transfered,
            state, is_file, obj_id
        ])

        file_list.addItem(item)
        rect = file_list.viewport().contentsRect()
        top = file_list.indexAt(rect.topLeft())
        if top.isValid():
            bottom = file_list.indexAt(rect.bottomLeft())
            if not bottom.isValid():
                bottom = file_list.model().index(file_list.count() - 1)
            if top.row() <= file_list.row(item) <= bottom.row() + 1:
                widget = self._create_file_list_item_widget(
                    file_list, [
                        rel_path, created_time, size, was_updated, is_deleted,
                        transfered, state, is_file, obj_id
                    ])
                file_list.setItemWidget(item, widget)
        if item not in items_dict[obj_id]:
            items_dict[obj_id].append(item)

    def on_downloads_scroll_changed(self, *args, **kwargs):
        self._on_list_scroll_changed(self._ui.downloads_list)

    def on_uploads_scroll_changed(self, *args, **kwargs):
        self._on_list_scroll_changed(self._ui.uploads_list)

    def _on_list_scroll_changed(self, file_list):
        rect = file_list.viewport().contentsRect()
        top = file_list.indexAt(rect.topLeft())
        if top.isValid():
            bottom = file_list.indexAt(rect.bottomLeft())
            if not bottom.isValid():
                bottom = file_list.model().index(file_list.count() - 1)
            for index in range(top.row(), bottom.row() + 1):
                item = file_list.item(index)
                widget = file_list.itemWidget(item)
                if widget:
                    continue
                widget = self._create_file_list_item_widget(
                    file_list, item.data(Qt.UserRole))
                file_list.setItemWidget(item, widget)

    def _create_file_list_item_widget(self, file_list, data):
        rel_path, created_time, \
        size, was_updated, is_deleted, \
        transfered, state, is_file, obj_id = data
        is_upload = state is None  # uploads list
        is_shared = not is_upload and created_time == 0
        is_http_download = not is_upload and created_time < 0
        if is_http_download:
            self._http_downloads.add(obj_id)

        widget = QWidget(parent=file_list)
        widget.setFixedHeight(self.FILE_LIST_ITEM_SIZE)

        main_layout = QVBoxLayout(widget)
        main_layout.setSpacing(2)

        file_name_label = QLabel(widget)
        file_name_label.setObjectName("file_name_label")
        file_name_label.setFixedWidth(max(file_list.width() - 80, 320))
        file_name_label.setFixedHeight(20)
        file_name_label.setFont(QFont('Noto Sans', 10 * self._dp))
        file_name_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        file_name_label.setText(elided(rel_path, file_name_label))
        main_layout.addWidget(file_name_label)

        time_size_revert_layout = QHBoxLayout()
        time_size_revert_layout.setSpacing(0)
        main_layout.addLayout(time_size_revert_layout)

        time_size_layout = QVBoxLayout()
        time_size_layout.setSpacing(0)
        time_size_revert_layout.addLayout(time_size_layout)
        time_size_revert_layout.addStretch()

        time_delta_label = QLabel(widget)
        time_delta_label.setObjectName("time_delta_label")
        if is_shared:
            time_delta_label.setText(tr("Shared file"))
        elif is_http_download:
            time_delta_label.setText(tr("Uploaded from web"))
        else:
            try:
                time_delta_label.setText(
                    get_added_time_string(created_time, was_updated,
                                          is_deleted))
            except RuntimeError:
                pass
        time_delta_label.setFont(QFont('Noto Sans', 8 * self._dp))
        time_delta_label.setMinimumHeight(14)
        time_delta_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        time_delta_label.setStyleSheet('color: #A792A9;')
        time_size_layout.addWidget(time_delta_label)

        is_created = not was_updated and not is_deleted and not is_shared
        if not is_upload:
            revert_button = QPushButton(widget)
            revert_button.is_entered = False
            revert_button.setObjectName("revert_button")
            revert_button.setFlat(True)
            revert_button.setChecked(True)
            revert_button.setFont(QFont("Noto Sans", 8 * self._dp,
                                        italic=True))
            revert_button.setMouseTracking(True)
            revert_button.setCursor(Qt.PointingHandCursor)
            self._set_revert_button_options(revert_button, obj_id, is_created,
                                            is_shared, is_http_download,
                                            is_file, rel_path, size)

            time_size_revert_layout.addWidget(revert_button,
                                              alignment=Qt.AlignVCenter)
            spacerItem = QSpacerItem(6, 10, QSizePolicy.Maximum,
                                     QSizePolicy.Minimum)
            time_size_layout.addItem(spacerItem)

        size_layout = QHBoxLayout()
        size_layout.setSpacing(0)
        time_size_layout.addLayout(size_layout)
        self._set_size_layout(size_layout, widget, transfered, size, is_upload)

        if is_upload:
            spacerItem = QSpacerItem(6, 6, QSizePolicy.Maximum,
                                     QSizePolicy.Minimum)
            main_layout.addItem(spacerItem)
        else:
            progress_layout = QHBoxLayout()
            progress_layout.setSpacing(6)
            main_layout.addLayout(progress_layout)
            self._set_progress_layout(progress_layout, widget, transfered,
                                      size, state)

        if is_upload:
            return widget

        def enter(_):
            revert_button.is_entered = True
            is_created = revert_button.property("properties")[0]
            color = '#f9af61' if not is_created else 'red'
            revert_button.setStyleSheet(
                'QPushButton {{margin: 0;border: 0; text-align:right center;'
                'color: {0};}} '
                'QPushButton:!enabled {{color: #aaaaaa;}} '
                'QToolTip {{background-color: #222222; color: white;}}'.format(
                    color))
            revert_button.setIcon(
                QIcon(':images/transfers/{}_active.svg'.format(
                    revert_button.text().strip().lower())))

        def leave(_):
            revert_button.is_entered = False
            revert_button.setStyleSheet(
                'QPushButton {margin: 0;border: 0; text-align:right center;'
                'color: #333333;} '
                'QPushButton:!enabled {color: #aaaaaa;}')
            revert_button.setIcon(
                QIcon(':images/transfers/{}_inactive.svg'.format(
                    revert_button.text().strip().lower())))

        revert_button.enterEvent = enter
        revert_button.leaveEvent = leave

        def revert_button_clicked():
            is_created, is_shared, is_file, rel_path, obj_id, size = \
                revert_button.property("properties")
            color = '#f78d1e' if not is_created else '#e50000'
            revert_button.setStyleSheet(
                'margin: 0; border: 0; text-align:right center;'
                'color: {};'.format(color))
            revert_button.setIcon(
                QIcon(':images/transfers/{}_clicked.svg'.format(
                    revert_button.text().strip().lower())))
            if not self._has_user_confirmed_revert(rel_path, is_shared,
                                                   is_created):
                return

            self._reverted_downloads.add(obj_id)
            reverted_files = reverted_patches = reverted_shares = []
            if is_shared:
                reverted_shares = [obj_id]
            elif is_file:
                reverted_files = [obj_id]
            else:
                reverted_patches = [obj_id]
            self._revert_downloads(reverted_files, reverted_patches,
                                   reverted_shares)
            items = self._downloads_items.get(obj_id, [])
            for item in items:
                self._ui.downloads_list.takeItem(
                    self._ui.downloads_list.row(item))
            self._total_files = max(self._total_files - len(items), 0)
            self._total_size = max(self._total_size - size, 0)
            self._update_totals()
            self._set_revert_all_enabled()
            self._set_current_downloads_page()

        revert_button.clicked.connect(revert_button_clicked)

        return widget

    def _set_size_layout(self, size_layout, widget, transfered, size,
                         is_upload):
        direction_label = QLabel(widget)
        direction_label.setMinimumHeight(14)
        direction_text = '\u2191\u0020' if is_upload else '\u2193\u0020'
        direction_label.setText(direction_text)
        direction_label.setFont(QFont('Noto Sans', 8 * self._dp))
        direction_label.setAlignment(Qt.AlignRight | Qt.AlignTrailing
                                     | Qt.AlignVCenter)
        direction_label.setStyleSheet('color: #A792A9;')
        size_layout.addWidget(direction_label)

        transfered_label = QLabel(widget)
        transfered_label.setObjectName("transfered_label")
        transfered_label.setMinimumHeight(14)
        transfered_label.setText(format_with_units(transfered))
        transfered_label.setFont(QFont('Noto Sans', 8 * self._dp))
        transfered_label.setAlignment(Qt.AlignLeading | Qt.AlignLeft
                                      | Qt.AlignVCenter)
        transfered_label.setStyleSheet('color: #A792A9;')
        size_layout.addWidget(transfered_label)

        if not is_upload:
            slash_label = QLabel(widget)
            slash_label.setMinimumHeight(14)
            slash_label.setText('/')
            slash_label.setFont(QFont('Noto Sans', 8 * self._dp))
            slash_label.setAlignment(Qt.AlignRight | Qt.AlignTrailing
                                     | Qt.AlignVCenter)
            slash_label.setStyleSheet('color: #A792A9;')
            size_layout.addWidget(slash_label)

            size_label = QLabel(widget)
            size_label.setObjectName("size_label")
            size_label.setMinimumHeight(14)
            size_label.setText(format_with_units(size))
            size_label.setFont(QFont('Noto Sans', 8 * self._dp))
            size_label.setAlignment(Qt.AlignLeading | Qt.AlignLeft
                                    | Qt.AlignVCenter)
            size_label.setStyleSheet('color: #A792A9;')
            size_layout.addWidget(size_label)

        size_layout.addStretch()

    def _set_progress_layout(self, progress_layout, widget, transfered, size,
                             state):
        is_current = state in self.CURRENT_TASK_STATES
        is_error = state in self.ERROR_STATES

        progress_background = QStackedWidget(widget)
        progress_background.setObjectName("progress_background")
        progress_bar = QProgressBar(progress_background)
        progress_bar.setObjectName("progress_bar")
        progress_bar.setMinimum(0)
        progress_bar.setMaximum(size if is_current and state != DOWNLOAD_FAILED
                                and self._paused_state == self.WORKING else 0)
        if is_current:
            progress_bar.setValue(transfered)
        progress_bar.setTextVisible(False)

        progress_label = QLabel(widget)
        progress_label.setObjectName("progress_label")

        self._set_progress_bar_style(progress_bar, progress_background,
                                     progress_label, state, is_current,
                                     is_error)

        progress_background.addWidget(progress_bar)
        progress_layout.addWidget(progress_background,
                                  alignment=Qt.AlignVCenter)

        progress_label.setFont(QFont('Noto Sans', 7 * self._dp))
        progress_layout.addWidget(progress_label)
        spacerItem = QSpacerItem(6, 10, QSizePolicy.Maximum,
                                 QSizePolicy.Minimum)
        progress_layout.addItem(spacerItem)

    def _set_revert_button_options(self, revert_button, obj_id, is_created,
                                   is_shared, is_http_download, is_file,
                                   rel_path, size):
        revert_text = tr("Delete") if is_created \
            else tr('Revert') if not is_shared and not is_http_download \
            else tr("Cancel")
        revert_button.setText(revert_text + '  ')
        revert_button.setIcon(
            QIcon(':images/transfers/{}_{}.svg'.format(
                revert_button.text().strip().lower(),
                'active' if revert_button.is_entered else 'inactive')))
        tooltip_text = tr("Action disabled while sync paused") \
            if not is_http_download and self._paused_state == self.PAUSED \
            else tr("Delete file and cancel download") if is_created \
            else tr("Revert changes and cancel download") \
            if not is_shared and not is_http_download \
            else tr("Cancel shared file download") if is_shared \
            else tr("You can cancel upload from web panel")
        revert_button.setToolTip(tooltip_text)
        revert_button.setStyleSheet(
            'QPushButton {{margin: 0;border: 0; text-align:right center;'
            'color: {0};}} '
            'QPushButton:!enabled {{color: #aaaaaa;}}'.format(
                '#333333' if not revert_button.is_entered else
                '#f9af61' if not is_created else 'red'))
        revert_button.setEnabled(not is_http_download
                                 and self._paused_state != self.PAUSED)

        revert_button.setProperty(
            "properties",
            [is_created, is_shared, is_file, rel_path, obj_id, size])

    def _update_file_list_item_widget(self, file_list, item):
        rel_path, \
        created_time, \
        size, \
        was_updated, \
        is_deleted, \
        transfered, \
        state, \
        is_file, \
        obj_id = item.data(Qt.UserRole)

        is_upload = state is None  # uploads list
        is_shared = not is_upload and created_time == 0
        is_created = not was_updated and not is_deleted and not is_shared
        is_http_download = not is_upload and created_time < 0
        if is_http_download:
            self._http_downloads.add(obj_id)

        is_current = state in self.CURRENT_TASK_STATES
        is_error = state in self.ERROR_STATES

        widget = file_list.itemWidget(item)
        if not widget:
            return

        file_name_label = widget.findChildren(QLabel, "file_name_label")[0]
        file_name_label.setText(elided(rel_path, file_name_label))

        time_delta_label = widget.findChildren(QLabel, "time_delta_label")[0]
        if is_shared:
            time_delta_label.setText(tr("Shared file"))
        elif is_http_download:
            time_delta_label.setText(tr("Uploaded from web"))
        else:
            try:
                time_delta_label.setText(
                    get_added_time_string(created_time, was_updated,
                                          is_deleted))
            except RuntimeError:
                pass

        transfered_label = widget.findChildren(QLabel, "transfered_label")[0]
        transfered_label.setText(format_with_units(transfered))

        if is_upload:
            return

        size_label = widget.findChildren(QLabel, "size_label")[0]
        size_label.setText(format_with_units(size))

        revert_button = widget.findChildren(QPushButton, "revert_button")[0]
        self._set_revert_button_options(revert_button, obj_id, is_created,
                                        is_shared, is_http_download, is_file,
                                        rel_path, size)

        progress_bar = widget.findChildren(QProgressBar, "progress_bar")[0]
        progress_background = widget.findChildren(QStackedWidget,
                                                  "progress_background")[0]
        progress_bar.setValue(transfered)
        progress_bar.setMaximum(size if is_current and state != DOWNLOAD_FAILED
                                and self._paused_state == self.WORKING else 0)
        progress_label = widget.findChildren(QLabel, "progress_label")[0]
        self._set_progress_bar_style(progress_bar, progress_background,
                                     progress_label, state, is_current,
                                     is_error)

    def _change_item_widget(self,
                            file_list,
                            item,
                            state=None,
                            transfered=None):
        rel_path, \
        created_time, \
        size, \
        was_updated, \
        is_deleted, \
        old_transfered, \
        old_state, \
        is_file, \
        obj_id = item.data(Qt.UserRole)

        if transfered is None:
            state = old_state
            transfered = old_transfered
            is_upload = False
        else:
            is_upload = state is None
            item.setData(Qt.UserRole, [
                rel_path, created_time, size, was_updated, is_deleted,
                transfered, state, is_file, obj_id
            ])

        widget = file_list.itemWidget(item)
        if not widget:
            return

        is_shared = not is_upload and created_time == 0
        is_created = not was_updated and not is_deleted and not is_shared
        is_http_download = not is_upload and created_time < 0

        children = widget.findChildren(QLabel, "transfered_label")
        if not children or len(children) > 1:
            logger.warning("Can't find transfered_label for %s", rel_path)
        else:
            transfered_label = children[0]
            transfered_label.setText(format_with_units(transfered))

        if is_upload:
            return

        is_current = state in self.CURRENT_TASK_STATES
        is_error = state in self.ERROR_STATES

        children = widget.findChildren(QProgressBar, "progress_bar")
        back_children = widget.findChildren(QStackedWidget,
                                            "progress_background")
        if not children or len(children) > 1 or \
                not back_children or len(back_children) > 1:
            logger.warning("Can't find progress_bar for %s", rel_path)
            return

        progress_background = back_children[0]
        progress_bar = children[0]
        progress_bar.setValue(transfered)
        progress_bar.setMaximum(size if is_current and state != DOWNLOAD_FAILED
                                and self._paused_state == self.WORKING else 0)

        children = widget.findChildren(QLabel, "progress_label")
        if not children or len(children) > 1:
            logger.warning("Can't find progress_label for %s", rel_path)
            return

        progress_label = children[0]
        self._set_progress_bar_style(progress_bar, progress_background,
                                     progress_label, state, is_current,
                                     is_error)

        revert_button = widget.findChildren(QPushButton, "revert_button")[0]
        self._set_revert_button_options(revert_button, obj_id, is_created,
                                        is_shared, is_http_download, is_file,
                                        rel_path, size)

    def _set_progress_bar_style(self, progress_bar, progress_background,
                                progress_label, state, is_current, is_error):
        progress_active = is_current and self._paused_state == self.WORKING
        if progress_active:
            progress_background.setStyleSheet("background-color: #cceed6")
            progress_bar.setStyleSheet("QProgressBar::chunk {"
                                       "background-color: #01AB33;"
                                       "}")
            progress_background.setFixedHeight(2)
            progress_bar.setFixedHeight(2)
        elif is_error:
            progress_background.setStyleSheet("background-color: #red")
            progress_bar.setStyleSheet("QProgressBar::chunk {"
                                       "background-color: #ffcccb;"
                                       "}")
            progress_background.setFixedHeight(1)
            progress_bar.setFixedHeight(1)
        else:
            progress_background.setStyleSheet("background-color: #d6d6d6")
            progress_bar.setStyleSheet("QProgressBar::chunk {"
                                       "background-color: #777777;"
                                       "}")
            progress_background.setFixedHeight(1)
            progress_bar.setFixedHeight(1)

        progress_text = self.STATE_NOTIFICATIONS[state] \
            if self._paused_state == self.WORKING or is_error \
            else self.PAUSED_NOTIFICATIONS[self._paused_state]
        progress_label.setText(progress_text)
        progress_label.setStyleSheet(
            "color: #01AB33" if progress_active else
            "color: #A792A9;" if not is_error else "color: red;")

    def _refresh_file_list_time_deltas(self, file_list, items):
        for obj_id in items:
            for item in items.get(obj_id, []):
                self._refresh_item_time_delta(file_list, item)

    def _refresh_item_time_delta(self, file_list, item):
        rel_path, \
        created_time, \
        size, \
        was_updated, \
        is_deleted, \
        transfered, \
        state, \
        is_file, \
        obj_id = item.data(Qt.UserRole)

        is_upload = state is None  # uploads list
        is_shared = not is_upload and created_time == 0
        is_http_download = not is_upload and created_time < 0
        if is_shared or is_http_download:
            return

        widget = file_list.itemWidget(item)
        if not widget:
            return
        children = widget.findChildren(QLabel, "time_delta_label")
        if not children or len(children) > 1:
            logger.warning("Can't find time_delta_label for %s", rel_path)
        else:
            time_delta_label = children[0]
            try:
                time_delta_label.setText(
                    get_added_time_string(created_time, was_updated,
                                          is_deleted))
            except RuntimeError:
                pass

    def _revert_all(self):
        logger.verbose("Revert downloads")
        reverted_files = reverted_patches = reverted_shares = []
        for obj_id in list(self._downloads_items.keys()):
            if obj_id in self._reverted_downloads:
                continue

            items = self._downloads_items.get(obj_id, [])
            if not items:
                logger.warning("No items for obj_id %s", obj_id)
                continue
            first_item = items[0]

            rel_path, \
            created_time, \
            size, \
            was_updated, \
            is_deleted, \
            transfered, \
            state, \
            is_file, \
            old_obj_id = first_item.data(Qt.UserRole)
            is_shared = created_time == 0
            is_http_download = created_time < 0
            if is_http_download:
                continue

            if is_shared:
                reverted_shares.append(obj_id)
            elif is_file:
                reverted_files.append(obj_id)
            else:
                reverted_patches.append(obj_id)
            self._reverted_downloads.add(obj_id)
            self._total_files = max(self._total_files - len(items), 0)
            self._total_size = max(self._total_size - size, 0)
            for item in self._downloads_items[obj_id]:
                self._ui.downloads_list.takeItem(
                    self._ui.downloads_list.row(item))
            self._downloads_items.pop(obj_id, None)

        logger.verbose("Reverting downloads %s, %s, %s", reverted_files,
                       reverted_patches, reverted_shares)
        self._revert_downloads(reverted_files, reverted_patches,
                               reverted_shares)

        self._set_revert_all_enabled()
        self._update_totals()
        self._set_current_downloads_page()

    def _toggle_paused_state(self):
        self._pause_resume_clicked()

    def set_paused_state(self, paused=True):
        under_mouse = self._ui.pause_all_button.underMouse()
        if paused:
            self._paused_state = self.PAUSED
            self._ui.pause_all_button.setText(tr("Resume all"))
            self._enter_leave(self._ui.pause_all_button, 'play', under_mouse)
        else:
            self._paused_state = self.RESUMING
            self._ui.pause_all_button.setText(tr("Pause all   "))
            self._enter_leave(self._ui.pause_all_button, 'pause', under_mouse)

        self._set_revert_all_enabled()

        logger.verbose("Downloads %s",
                       self.PAUSED_NOTIFICATIONS[self._paused_state])
        for obj_id in self._downloads_items:
            items = self._downloads_items.get(obj_id, [])
            for item in items:
                self._change_item_widget(self._ui.downloads_list, item)

    def _has_user_confirmed_revert(self,
                                   file_path=None,
                                   is_share=False,
                                   is_created=False):
        if file_path:  # 1 file
            if is_share:
                msg_text = tr("Do you want to cancel shared file {} download?") \
                    .format(file_path)
            elif is_created:
                msg_text = tr("Do you want to delete file {} "
                              "from all your devices?").format(file_path)
            else:
                msg_text = tr("Do you want to revert last changes for file {} "
                              "on all your devices?").format(file_path)
        else:  # many files
            msg_text = tr(
                "Do you want to delete new files from all your devices,\n"
                "revert all last changes on all devices,\n"
                "and cancel shared files downloads?")

        userAnswer = msgbox(msg_text,
                            buttons=[
                                (tr('Yes'), 'Yes'),
                                (tr('No'), 'No'),
                            ],
                            parent=self._dialog,
                            default_index=1)
        return userAnswer == 'Yes'

    def _main_resize_event(self, e):
        self._old_main_resize_event(e)
        if e.oldSize().height() != self._ui.centralwidget.height():
            self.on_downloads_scroll_changed()
            self.on_uploads_scroll_changed()

        width = (self._ui.centralwidget.width() - 6) // 2
        self._ui.downloads_frame.setFixedWidth(width)
        self._ui.uploads_frame.setFixedWidth(width)
        if e.oldSize().width() == self._ui.centralwidget.width():
            return

        speed_charts_height = self._ui.downloads_speed_widget.width() * 0.3
        self._ui.downloads_speed_widget.setFixedHeight(speed_charts_height)
        self._download_speed_chart.resize()
        self._ui.uploads_speed_widget.setFixedHeight(speed_charts_height)
        self._upload_speed_chart.resize()

        self._file_list_resizeEvent(self._ui.downloads_list,
                                    self._downloads_items)
        self._file_list_resizeEvent(self._ui.uploads_list, self._uploads_items)

    def _file_list_resizeEvent(self, file_list, file_list_items):
        for items in file_list_items.values():
            for item in items:
                self._resize_item(item, file_list)

    def _resize_item(self, item, file_list):
        rel_path, \
        created_time, \
        size, \
        was_updated, \
        is_deleted, \
        transfered, \
        state, \
        is_file, \
        obj_id = item.data(Qt.UserRole)

        widget = file_list.itemWidget(item)
        if not widget:
            return
        widget.setMaximumWidth(file_list.width())
        children = widget.findChildren(QLabel, "file_name_label")
        if not children or len(children) > 1:
            logger.warning("Can't find file_name_label for %s", rel_path)
        else:
            file_name_label = children[0]
            file_name_label.setFixedWidth(max(file_list.width() - 80, 320))
            file_name_label.setText(elided(rel_path, file_name_label))

    def _set_revert_all_enabled(self):
        self._ui.revert_all_button.setEnabled(
            bool(set(self._downloads_items) - self._http_downloads)
            and self._paused_state != self.PAUSED)

    def _set_current_downloads_page(self):
        self._ui.downloads_pages.setCurrentIndex(
            0 if self._downloads_items else 1)

    def _set_current_uploads_page(self):
        self._ui.uploads_pages.setCurrentIndex(0 if self._uploads_items else 1)

    def update_speed_charts(self, download_speed, upload_speed):
        self._last_downloads_speeds.append(download_speed)
        self._last_uploads_speeds.append(upload_speed)
        max_speed = max(max(self._last_downloads_speeds),
                        max(self._last_uploads_speeds))
        self._download_speed_chart.update(download_speed, max_speed)
        self._upload_speed_chart.update(upload_speed, max_speed)

    def _on_add_to_sync_folder(self):
        logger.verbose("Add files to sync directory")
        title = tr('Choose files to copy to sync directory')
        selected_files_or_folders = QFileDialog.getOpenFileNames(
            self._dialog, title)[0]
        self._add_to_sync_folder(selected_files_or_folders)

    def _drag_enter_event(self, event):
        data = event.mimeData()
        if data.hasUrls():
            event.accept()
        else:
            event.ignore()

    def _drop_event(self, event):
        data = event.mimeData()
        dropped_files_or_folders = []
        if data.hasUrls():
            event.acceptProposedAction()
            event.accept()
            for url in data.urls():
                dropped_files_or_folders.append(url.toLocalFile())
            self._add_to_sync_folder(dropped_files_or_folders)
        else:
            event.ignore()

    def _on_insert_link(self):
        insert_link_dialog = InsertLinkDialog(self._dialog, self._dp,
                                              self._signalserver_address)
        link, is_shared = insert_link_dialog.show()
        logger.debug("link '%s'", link)
        if link:
            self._handle_link(link, is_shared)

    def set_signalserver_address(self, address):
        self._signalserver_address = address