Пример #1
0
class OutputPreviewWidget(QWidget):
    """
    A .py preview widget that appears as a drawer on the side of the main
    import widget.
    """

    def __init__(self, parent=None):
        super(OutputPreviewWidget, self).__init__(parent=parent)
        self.setWindowFlags(Qt.Sheet)
        self.text_editor = QPlainTextEdit()
        self.close_button = QPushButton('Close')
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.text_editor)
        self.layout.addWidget(self.close_button)
        self.setLayout(self.layout)
        self.close_button.clicked.connect(self.hide)
        self.resize(400, 500)
        font = QFont('Courier')
        self.text_editor.setFont(font)
        self.text_editor.setReadOnly(True)

    def set_text(self, text):
        """
        Set the text in the loader preview widget. This will display the text that
        will be saved to the output loader python file.

        Parameters
        ----------
        text: str
            Text that will be saved to the final loader file
        """

        self.text_editor.setPlainText(text)
Пример #2
0
 def setupUi(self, path):
     f = open(path, 'r', encoding='utf-8')
     jsonData = json.loads(f.read())
     noticia = jsonData["noticia"]
     titulo = jsonData["title"]
     fecha = jsonData["fecha"]
     tags = ""
     for tag in jsonData["tags"]:
         tags += tag + " | "
     f.close()
     textArea = QPlainTextEdit(noticia, self)
     textArea.setReadOnly(True)
     textArea.move(10, 60)
     textArea.setFixedSize(1200, 600)
     labelTit = QLabel(titulo, self)
     labelTit.move(10, 30)
     labelFecha = QLabel(fecha, self)
     labelFecha.move(10, 670)
     labelTags = QLabel(tags[:-3], self)
     labelTags.move(300, 670)
     buttonSearchSim = QPushButton("Buscar noticias similares (text)", self)
     buttonSearchSim.move(1000, 30)
     buttonSearchSim.clicked.connect(self.make_searchSim(path))
     buttonTagsSim = QPushButton("Buscar noticias similares (tags)", self)
     buttonTagsSim.move(1000, 5)
     buttonTagsSim.clicked.connect(self.make_tagsSim(path))
     self.setWindowTitle("Texto de la noticia")
     self.setMinimumSize(1220, 705)
     self.show()
     self.exec_()
Пример #3
0
class OutputPreviewWidget(QWidget):
    """
    A .py preview widget that appears as a drawer on the side of the main
    import widget.
    """
    def __init__(self, parent=None):
        super(OutputPreviewWidget, self).__init__(parent=parent)
        self.setWindowFlags(Qt.Sheet)
        self.text_editor = QPlainTextEdit()
        self.close_button = QPushButton('Close')
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.text_editor)
        self.layout.addWidget(self.close_button)
        self.setLayout(self.layout)
        self.close_button.clicked.connect(self.hide)
        self.resize(400, 500)
        font = QFont('Courier')
        self.text_editor.setFont(font)
        self.text_editor.setReadOnly(True)

    def set_text(self, text):
        """
        Set the text in the loader preview widget. This will display the text that
        will be saved to the output loader python file.

        Parameters
        ----------
        text: str
            Text that will be saved to the final loader file
        """

        self.text_editor.setPlainText(text)
Пример #4
0
class ShellWidget(QWidget):
    run_shell = Signal(Bot, str)

    def __init__(self, bot, parent):
        super(ShellWidget, self).__init__(parent)
        self.bot = bot

        self.widget_layout = QVBoxLayout(self)
        self.setLayout(self.widget_layout)

        self.output_widget = QPlainTextEdit(self)
        self.output_widget.setReadOnly(True)
        self.highlighter = ShellHighlighter(self.output_widget.document())
        self.widget_layout.addWidget(self.output_widget)

        self.input_widget = QLineEdit(self)
        self.widget_layout.addWidget(self.input_widget)

        self.send_button = QPushButton(self)
        self.send_button.setText("Send")
        self.send_button.clicked.connect(self.on_send_button_clicked)
        self.widget_layout.addWidget(self.send_button)

    @Slot()
    def on_send_button_clicked(self):
        text = self.input_widget.text()
        self.output_widget.appendPlainText("$: {}".format(text))
        self.run_shell.emit(self.bot, text)
        self.input_widget.clear()

    @Slot(Bot, str)
    def append_shell(self, bot, message):
        if self.bot == bot:
            self.output_widget.appendPlainText(message)
Пример #5
0
class QtFontSizePreview(QFrame):
    """
    Widget that displays a preview text.

    Parameters
    ----------
    parent : QWidget, optional
        Parent widget.
    text : str, optional
        Preview text to display. Default is None.
    """

    def __init__(self, parent: QWidget = None, text: str = None):
        super().__init__(parent)

        self._text = text or ""

        # Widget
        self._preview = QPlainTextEdit(self)

        # Widget setup
        self._preview.setReadOnly(True)
        self._preview.setPlainText(self._text)

        # Layout
        layout = QHBoxLayout()
        layout.addWidget(self._preview)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def sizeHint(self):
        """Override Qt method."""
        return QSize(100, 80)

    def text(self) -> str:
        """Return the current preview text.

        Returns
        -------
        str
            The current preview text.
        """
        return self._text

    def setText(self, text: str):
        """Set the current preview text.

        Parameters
        ----------
        text : str
            The current preview text.
        """
        self._text = text
        self._preview.setPlainText(text)
Пример #6
0
 def __init__(self, parent, history):
     super().__init__(parent=parent)
     self.setWindowTitle("History")
     layout = QVBoxLayout()
     text = QPlainTextEdit()
     text.setReadOnly(True)
     text.setPlainText(history)
     layout.addWidget(text)
     buttonbox = QDialogButtonBox(QDialogButtonBox.Ok)
     layout.addWidget(buttonbox)
     self.setLayout(layout)
     buttonbox.accepted.connect(self.accept)
     self.resize(700, 500)
Пример #7
0
class DisplayDialog(QDialog):
    """
    This is a simple dialog display which can be configured by users
    """
    def __init__(self, parent=None, name='Test'):
        """ init
        :param parent:
        """
        super(DisplayDialog, self).__init__(parent)

        layout = QVBoxLayout(self)

        # nice widget for editing the date
        self.message_edit = QPlainTextEdit(self)
        self.message_edit.setReadOnly(True)
        layout.addWidget(self.message_edit)

        self.setWindowTitle('Merged Scans Workspace Names')

        # OK and Cancel buttons
        buttons = QDialogButtonBox(QDialogButtonBox.Ok, QtCore.Qt.Horizontal,
                                   self)

        buttons.accepted.connect(self.accept)
        layout.addWidget(buttons)

        self.name = name

        return

    def set_name(self, new_name):

        self.name = new_name

    def show_message(self, message):
        """
        show message
        :param message:
        :return:
        """
        self.message_edit.setPlainText(message)

        return
Пример #8
0
class DisplayDialog(QDialog):
    """
    This is a simple dialog display which can be configured by users
    """
    def __init__(self, parent=None, name='Test'):
        """ init
        :param parent:
        """
        super(DisplayDialog, self).__init__(parent)

        layout = QVBoxLayout(self)

        # nice widget for editing the date
        self.message_edit = QPlainTextEdit(self)
        self.message_edit.setReadOnly(True)
        layout.addWidget(self.message_edit)

        self.setWindowTitle('Merged Scans Workspace Names')

        # OK and Cancel buttons
        buttons = QDialogButtonBox(QDialogButtonBox.Ok, QtCore.Qt.Horizontal, self)

        buttons.accepted.connect(self.accept)
        layout.addWidget(buttons)

        self.name = name

        return

    def set_name(self, new_name):

        self.name = new_name

    def show_message(self, message):
        """
        show message
        :param message:
        :return:
        """
        self.message_edit.setPlainText(message)

        return
Пример #9
0
    def preview_layout(self, show_hidden_areas=False):
        """
        Show the layout with placeholder texts using a QWidget.
        """
        from spyder.utils.qthelpers import qapplication

        app = qapplication()
        widget = QWidget()
        layout = QGridLayout()
        for area in self._areas:
            label = QPlainTextEdit()
            label.setReadOnly(True)
            label.setPlainText("\n".join(area["plugin_ids"]))
            if area["visible"] or show_hidden_areas:
                layout.addWidget(
                    label,
                    area["row"],
                    area["column"],
                    area["row_span"],
                    area["col_span"],
                )

            # label.setVisible(area["visible"])
            if area["default"]:
                label.setStyleSheet(
                    "QPlainTextEdit {background-color: #ff0000;}")

            if not area["visible"]:
                label.setStyleSheet(
                    "QPlainTextEdit {background-color: #eeeeee;}")

        for row, stretch in self._row_stretchs.items():
            layout.setRowStretch(row, stretch)

        for col, stretch in self._column_stretchs.items():
            layout.setColumnStretch(col, stretch)

        widget.setLayout(layout)
        widget.showMaximized()
        app.exec_()
Пример #10
0
class YAMLPreviewWidget(QWidget):
    """
    A YAML preview widget that appears as a drawer on the side of the main
    import widget.
    """
    def __init__(self, parent=None):
        super(YAMLPreviewWidget, self).__init__(parent=parent)
        self.setWindowFlags(Qt.Sheet)
        self.text_editor = QPlainTextEdit()
        self.close_button = QPushButton('Close')
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.text_editor)
        self.layout.addWidget(self.close_button)
        self.setLayout(self.layout)
        self.close_button.clicked.connect(self.hide)
        self.resize(400, 500)
        font = QFont('Courier')
        self.text_editor.setFont(font)
        self.text_editor.setReadOnly(True)

    def set_text(self, text):
        self.text_editor.setPlainText(text)
Пример #11
0
 def __init__(self, parent, history):
     super().__init__(parent=parent)
     self.setWindowTitle("History")
     layout = QVBoxLayout()
     text = QPlainTextEdit()
     font = QFont()
     font.setFamily("monospace")
     font.setStyleHint(QFont.Monospace)
     text.setFont(font)
     highlighter = PythonHighlighter(text.document())  # noqa: F841
     text.setReadOnly(True)
     text.setPlainText(history)
     layout.addWidget(text)
     buttonbox = QDialogButtonBox(QDialogButtonBox.Ok)
     clipboardbutton = QPushButton("Copy to clipboard")
     buttonbox.addButton(clipboardbutton, QDialogButtonBox.ActionRole)
     clipboard = QGuiApplication.clipboard()
     clipboardbutton.clicked.connect(
         lambda: clipboard.setText(history + "\n"))
     layout.addWidget(buttonbox)
     self.setLayout(layout)
     buttonbox.accepted.connect(self.accept)
     self.resize(700, 500)
Пример #12
0
class InfoDialog(QDialog):
    def __init__(self, content=None, description=None):
        super(InfoDialog, self).__init__()

        self.wikiLink = "https://github.com/eliranwong/UniqueBible/wiki"

        self.setMinimumWidth(350)
        self.setWindowTitle(config.thisTranslation["info"])
        self.layout = QVBoxLayout()

        self.appName = QLabel("UniqueBible.app [{0} {1}]".format(
            config.thisTranslation["version"], config.version))
        self.appName.mouseReleaseEvent = self.openWiki
        self.layout.addWidget(self.appName)

        if content is None:
            with open("latest_changes.txt", "r",
                      encoding="utf-8") as fileObject:
                text = fileObject.read()
        else:
            text = content
        self.layout.addWidget(
            QLabel("{0}:".format(config.thisTranslation["latest_changes"]
                                 if description is None else description)))
        self.latestChanges = QPlainTextEdit()
        self.latestChanges.setPlainText(text)
        self.latestChanges.setReadOnly(True)
        self.layout.addWidget(self.latestChanges)

        buttons = QDialogButtonBox.Ok
        self.buttonBox = QDialogButtonBox(buttons)
        self.buttonBox.accepted.connect(self.accept)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)

    def openWiki(self, event):
        webbrowser.open(self.wikiLink)
Пример #13
0
class EventViewerPanel(QPlainTextEdit):
    def __init__(self, log_handler: GUILogHandler):
        self.log_handler = log_handler
        QPlainTextEdit.__init__(self)

        self.setMinimumWidth(500)
        self.setMinimumHeight(200)
        self._dynamic = False

        self.setWindowTitle("Event viewer")
        self.activateWindow()

        layout = QVBoxLayout()
        self.text_box = QPlainTextEdit()
        self.text_box.setReadOnly(True)
        self.text_box.setMaximumBlockCount(1000)
        layout.addWidget(self.text_box)

        self.setLayout(layout)
        log_handler.append_log_statement.connect(self.val_changed)

    @QtCore.Slot(str)
    def val_changed(self, value):
        self.text_box.appendPlainText(value)
Пример #14
0
class ZusatzFensterKerndaten(QWidget):
    def __init__(self, nummer, text):
        super().__init__()
        self.initMe(nummer, text)

    def initMe(self, nummer, text):
        self.l1 = QLabel(self)
        self.l1.setText('Inhalt der eingelesenen Zelle')
        self.l1.move(20, 5)

        self.nummer = nummer
        self.setGeometry(400, 300, 500, 700)
        self.zelle = QPlainTextEdit(self)
        self.zelle.setGeometry(0, 40, 500, 250)
        self.zelle.setPlainText(text)
        self.zelle.setReadOnly(True)

        self.l2 = QLabel(self)
        self.l2.setText(
            """Bitte geben Sie hier den Wert ein nach dem in der Zelle gesucht werden soll.
Bsp. Wollen Sie einen Lastpunkt auslesen, welcher mit 5000 rpm angegeben ist, geben Sie 5000 ein.
Achtung: keine Einheiten mit angeben. Nur Zahlen!""")
        self.l2.move(10, 330)

        self.eing = QLineEdit(self)
        self.eing.move(10, 410)

        p = QPushButton('Prüfen', self)
        p.clicked.connect(self.pruefen)
        p.move(180, 409)

        self.l3 = QLabel(self)
        self.l3.setText('vorangehende Zeichenkette')
        self.l3.move(10, 460)

        self.suchstring = QLineEdit(self)
        self.suchstring.move(180, 459)
        self.suchstring.setDisabled(True)

        self.l5 = QLabel(self)
        self.l5.setStyleSheet("background-color: yellow")
        self.l5.setText(
            "Prüfen Sie die vorrangehende Zeichenkette.\nSollte diese nicht stimmen, können Sie selbst eine angeben und erneut prüfen.\nAchtung: Leerzeichen nicht vergessen "
        )
        self.l5.move(10, 490)
        self.l5.setVisible(False)

        self.l4 = QLabel(self)
        self.l4.setText('gefundener Eintrag')
        self.l4.move(10, 540)

        self.gefundener_string = QLineEdit(self)
        self.gefundener_string.move(180, 539)
        self.gefundener_string.setReadOnly(True)

        frage = QPushButton(self)
        frage.setIcon(QIcon("bilder_vorlagenersteller\\FrageIcon.png"))
        frage.move(450, 10)
        frage.clicked.connect(self.Hilfe)

        self.weiter = QPushButton('Weiter', self)
        self.weiter.move(420, 650)
        self.weiter.setDisabled(True)
        self.weiter.clicked.connect(self.weiter_gehts)

    def suchstring_finden(self):
        startindex = self.zelle.toPlainText().find(self.eing.text())
        if startindex == 0:
            suchstring = '##Anfang###'

        elif startindex == -1:
            suchstring = 'ungültige Eingabe'

        else:
            suchstring = ''
            for i in range(0, 11):
                suchstring = self.zelle.toPlainText()[startindex -
                                                      i] + suchstring
                if (startindex - i) == 0:
                    break

        return suchstring[:-1]

    def pruefen(self):
        suchstring = self.suchstring.text()

        if suchstring == '':
            suchstring = self.suchstring_finden()
        print(suchstring)

        self.suchstring.setDisabled(False)
        self.l5.setVisible(True)
        self.weiter.setDisabled(False)
        self.suchstring.setText(suchstring)

        startindex = self.zelle.toPlainText().find(suchstring) + len(
            suchstring)
        ende = startindex + len(self.eing.text())

        self.gefundener_string.setText(
            self.zelle.toPlainText()[startindex:ende])

    def weiter_gehts(self):
        w.findChild(QLabel, self.nummer).setVisible(True)
        w.findChild(QLineEdit, 'suchstr' + self.nummer).setVisible(True)
        w.findChild(QLineEdit,
                    'suchstr' + self.nummer).setText(self.suchstring.text())
        self.close()

    def Hilfe(self):
        self.h = HilfeFenster(
            "bilder_vorlagenersteller\\erweitertes_einlesen.png")
        self.h.show()
Пример #15
0
class ProfilePreviewDialog(QDialog):
    def __init__(
        self,
        profile_dict: typing.Dict[str, ROIExtractionProfile],
        algorithm_dict: Register,
        settings: BaseSettings,
        parent=None,
    ):
        super().__init__(parent=parent)
        self.profile_dict = profile_dict
        self.algorithm_dict = algorithm_dict
        self.settings = settings

        self.profile_list = SearchableListWidget()
        self.profile_list.addItems(self.profile_dict.keys())
        self.profile_list.currentTextChanged.connect(self.profile_selected)
        self.profile_view = QPlainTextEdit()
        self.profile_view.setReadOnly(True)

        self.delete_btn = QPushButton("Delete")
        self.delete_btn.clicked.connect(self.delete_action)
        self.rename_btn = QPushButton("Rename")
        self.rename_btn.clicked.connect(self.rename_action)
        self.export_btn = QPushButton("Export")
        self.export_btn.clicked.connect(self.export_action)
        self.import_btn = QPushButton("Import")
        self.import_btn.clicked.connect(self.import_action)
        layout = QGridLayout()
        layout.addWidget(self.profile_list, 0, 0)
        layout.addWidget(self.profile_view, 0, 1)
        layout.addWidget(self.delete_btn, 1, 0)
        layout.addWidget(self.rename_btn, 1, 1)
        layout.addWidget(self.export_btn, 2, 0)
        layout.addWidget(self.import_btn, 2, 1)

        self.setLayout(layout)

    def profile_selected(self, text):
        if text not in self.profile_dict:
            return
        profile = self.profile_dict[text]
        self.profile_view.setPlainText(str(profile))

    def delete_action(self):
        if self.profile_list.currentItem() is None:
            return  # pragma: no cover
        if self.profile_list.currentItem().text() in self.profile_dict:
            del self.profile_dict[self.profile_list.currentItem().text()]
        self.profile_list.clear()
        self.profile_list.addItems(self.profile_dict.keys())

    def rename_action(self):
        if self.profile_list.currentItem() is None:
            return  # pragma: no cover
        old_name = self.profile_list.currentItem().text()
        if old_name not in self.profile_dict:
            return  # pragma: no cover

        text, ok = QInputDialog.getText(self, "Profile Name",
                                        "Input profile name here")
        if not ok:
            return  # pragma: no cover

        if text in self.profile_dict:  # pragma: no cover
            QMessageBox.warning(
                self,
                "Already exists",
                "Profile with this name already exist.",
                QMessageBox.Ok,
                QMessageBox.Ok,
            )
            self.rename_action()
            return
        profile = self.profile_dict[old_name]
        del self.profile_dict[old_name]
        profile.name = text
        self.profile_dict[text] = profile
        self.profile_list.clear()
        self.profile_list.addItems(self.profile_dict.keys())

    def export_action(self):
        exp = ExportDialog(self.profile_dict, ProfileDictViewer, parent=self)
        if not exp.exec_():
            return  # pragma: no cover
        dial = PSaveDialog(SaveProfilesToJSON,
                           settings=self.settings,
                           parent=self,
                           path=IO_SAVE_DIRECTORY)
        if dial.exec_():
            save_location, _selected_filter, save_class, values = dial.get_result(
            )
            data = {x: self.profile_dict[x] for x in exp.get_export_list()}
            save_class.save(save_location, data, values)

    def import_action(self):
        dial = PLoadDialog(LoadProfileFromJSON,
                           settings=self.settings,
                           parent=self,
                           path=IO_SAVE_DIRECTORY)
        if not dial.exec_():
            return  # pragma: no cover
        file_list, _, load_class = dial.get_result()
        profs, err = load_class.load(file_list)
        if err:
            show_warning("Import error",
                         "error during importing, part of data were filtered."
                         )  # pragma: no cover
        imp = ImportDialog(profs,
                           self.profile_dict,
                           ProfileDictViewer,
                           parent=self)
        if not imp.exec_():
            return  # pragma: no cover
        for original_name, final_name in imp.get_import_list():
            self.profile_dict[final_name] = profs[original_name]
        self.settings.dump()
        self.profile_list.clear()
        self.profile_list.addItems(self.profile_dict.keys())
Пример #16
0
class FileDialog(QDialog):
    def __init__(self,
                 file_name,
                 job_name,
                 job_number,
                 realization,
                 iteration,
                 parent=None):
        super(FileDialog, self).__init__(parent)

        self.setWindowTitle("{} # {} Realization: {} Iteration: {}".format(
            job_name, job_number, realization, iteration))

        try:
            self._file = open(file_name, "r")
        except OSError as error:
            self._mb = QMessageBox(
                QMessageBox.Critical,
                "Error opening file",
                error.strerror,
                QMessageBox.Ok,
                self,
            )
            self._mb.finished.connect(self.accept)
            self._mb.show()
            return

        self._view = QPlainTextEdit()
        self._view.setReadOnly(True)
        self._view.setWordWrapMode(QTextOption.NoWrap)
        # for moving the actual slider
        self._view.verticalScrollBar().sliderMoved.connect(self._update_cursor)
        # for mouse wheel and keyboard arrows
        self._view.verticalScrollBar().valueChanged.connect(
            self._update_cursor)

        self._view.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))

        self._follow_mode = False

        self._init_layout()
        self._init_thread()

        self.show()

    @Slot()
    def _stop_thread(self):
        self._thread.quit()
        self._thread.wait()

    def _init_layout(self):
        self.setMinimumWidth(600)
        self.setMinimumHeight(400)

        dialog_buttons = QDialogButtonBox(QDialogButtonBox.Ok)
        dialog_buttons.accepted.connect(self.accept)

        self._copy_all_button = dialog_buttons.addButton(
            "Copy All", QDialogButtonBox.ActionRole)
        self._copy_all_button.clicked.connect(self._copy_all)

        self._follow_button = dialog_buttons.addButton(
            "Follow", QDialogButtonBox.ActionRole)
        self._follow_button.setCheckable(True)
        self._follow_button.toggled.connect(self._enable_follow_mode)
        self._enable_follow_mode(self._follow_mode)

        layout = QVBoxLayout(self)
        layout.addWidget(self._view)
        layout.addWidget(dialog_buttons)

    def _init_thread(self):
        self._thread = QThread()

        self._worker = FileUpdateWorker(self._file)
        self._worker.moveToThread(self._thread)
        self._worker.read.connect(self._append_text)

        self._thread.started.connect(self._worker.setup)
        self._thread.finished.connect(self._worker.stop)
        self._thread.finished.connect(self._worker.deleteLater)
        self.finished.connect(self._stop_thread)

        self._thread.start()

    def _copy_all(self) -> None:
        text = self._view.toPlainText()
        QApplication.clipboard().setText(text, QClipboard.Clipboard)
        pass

    def _update_cursor(self, value: int) -> None:
        if not self._view.textCursor().hasSelection():
            block = self._view.document().findBlockByLineNumber(value)
            cursor = QTextCursor(block)
            self._view.setTextCursor(cursor)

    def _enable_follow_mode(self, enable: bool) -> None:
        if enable:
            self._view.moveCursor(QTextCursor.End)
            self._view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self._view.verticalScrollBar().setDisabled(True)
            self._view.setTextInteractionFlags(Qt.NoTextInteraction)
            self._follow_mode = True
        else:
            self._view.verticalScrollBar().setDisabled(False)
            self._view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
            self._view.setTextInteractionFlags(Qt.TextSelectableByMouse
                                               | Qt.TextSelectableByKeyboard)
            self._follow_mode = False

    def _append_text(self, text: str) -> None:
        # Remove trailing newline as appendPlainText adds this
        if text[-1:] == "\n":
            text = text[:-1]
        if self._follow_mode:
            self._view.moveCursor(QTextCursor.End)
        self._view.appendPlainText(text)
Пример #17
0
class PyDMLogDisplay(QWidget, LogLevels):
    """
    Standard display for Log Output

    This widget handles instantating a ``GuiHandler`` and displaying log
    messages to a ``QPlainTextEdit``. The level of the log can be changed from
    inside the widget itself, allowing users to select from any of the
    ``.levels`` specified by the widget.

    Parameters
    ----------
    parent : QObject, optional

    logname : str
        Name of log to display in widget

    level : logging.Level
        Initial level of log display

    """
    Q_ENUMS(LogLevels)
    LogLevels = LogLevels
    terminator = '\n'
    default_format = '%(asctime)s %(message)s'
    default_level = logging.INFO

    def __init__(self, parent=None, logname=None, level=logging.NOTSET):
        QWidget.__init__(self, parent=parent)
        # Create Widgets
        self.label = QLabel('Minimum displayed log level: ', parent=self)
        self.combo = QComboBox(parent=self)
        self.text = QPlainTextEdit(parent=self)
        self.text.setReadOnly(True)
        self.clear_btn = QPushButton("Clear", parent=self)
        # Create layout
        layout = QVBoxLayout()
        level_control = QHBoxLayout()
        level_control.addWidget(self.label)
        level_control.addWidget(self.combo)
        layout.addLayout(level_control)
        layout.addWidget(self.text)
        layout.addWidget(self.clear_btn)
        self.setLayout(layout)
        # Allow QCombobox to control log level
        for log_level, value in LogLevels.as_dict().items():
            self.combo.addItem(log_level, value)
        self.combo.currentIndexChanged[str].connect(self.setLevel)
        # Allow QPushButton to clear log text
        self.clear_btn.clicked.connect(self.clear)
        # Create a handler with the default format
        self.handler = GuiHandler(level=level, parent=self)
        self.logFormat = self.default_format
        self.handler.message.connect(self.write)
        # Create logger. Either as a root or given logname
        self.log = None
        self.level = None
        self.logName = logname or ''
        self.logLevel = level
        self.destroyed.connect(functools.partial(logger_destroyed, self.log))

    def sizeHint(self):
        return QSize(400, 300)

    @Property(LogLevels)
    def logLevel(self):
        return self.level

    @logLevel.setter
    def logLevel(self, level):
        if level != self.level:
            self.level = level
            idx = self.combo.findData(level)
            self.combo.setCurrentIndex(idx)

    @Property(str)
    def logName(self):
        """Name of associated log"""
        return self.log.name

    @logName.setter
    def logName(self, name):
        # Disconnect prior log from handler
        if self.log:
            self.log.removeHandler(self.handler)
        # Reattach handler to new handler
        self.log = logging.getLogger(name)
        # Ensure that the log matches level of handler
        # only if the handler level is less than the log.
        if self.log.level < self.handler.level:
            self.log.setLevel(self.handler.level)
        # Attach preconfigured handler
        self.log.addHandler(self.handler)

    @Property(str)
    def logFormat(self):
        """Format for log messages"""
        return self.handler.formatter._fmt

    @logFormat.setter
    def logFormat(self, fmt):
        self.handler.setFormatter(logging.Formatter(fmt))

    @Slot(str)
    def write(self, message):
        """Write a message to the log display"""
        # We split the incoming message by new lines. In prior iterations of
        # this widget it was discovered that large blocks of text cause issues
        # at the Qt level.
        for msg in message.split(self.terminator):
            self.text.appendPlainText(msg)

    @Slot()
    def clear(self):
        """Clear the text area."""
        self.text.clear()

    @Slot(str)
    def setLevel(self, level):
        """Set the level of the contained logger"""
        # Get the level from the incoming string specification
        try:
            level = getattr(logging, level.upper())
        except AttributeError as exc:
            logger.exception("Invalid logging level specified %s",
                             level.upper())
        else:
            # Set the existing handler and logger to this level
            self.handler.setLevel(level)
            if self.log.level > self.handler.level or self.log.level == logging.NOTSET:
                self.log.setLevel(self.handler.level)
Пример #18
0
class AmiGui(QWidget):

    loadFile = Signal(str, name='loadFile')
    saveFile = Signal(str, name='saveFile')
    statusUpdate = Signal(str, name='statusUpdate')

    def __init__(self, queue, graphmgr_addr, ami_save, parent=None):
        super(__class__, self).__init__(parent)
        self.setWindowTitle("AMI Client")
        self.comm_handler = AsyncGraphCommHandler(graphmgr_addr.name,
                                                  graphmgr_addr.comm)

        self.save_button = QPushButton('Save', self)
        self.save_button.clicked.connect(self.save)
        self.load_button = QPushButton('Load', self)
        self.load_button.clicked.connect(self.load)
        self.clear_button = QPushButton('Clear', self)
        self.clear_button.clicked.connect(self.clear)
        self.reset_button = QPushButton('Reset Plots', self)
        self.reset_button.clicked.connect(self.reset)
        self.status_box = QPlainTextEdit(self)
        self.status_box.setReadOnly(True)
        self.amilist = DetectorList(queue, self.comm_handler)
        if ami_save is not None:
            self.comm_handler.update(ami_save)

        self.setup = QGroupBox("Setup")
        self.setup_layout = QHBoxLayout(self.setup)
        self.setup_layout.addWidget(self.save_button)
        self.setup_layout.addWidget(self.load_button)
        self.setup_layout.addWidget(self.clear_button)
        self.setup.setLayout(self.setup_layout)

        self.data = QGroupBox("Data")
        self.data_layout = QVBoxLayout(self)
        self.data_layout.addWidget(self.reset_button)
        self.data_layout.addWidget(self.amilist)
        self.data.setLayout(self.data_layout)

        self.status = QGroupBox("Status")
        self.status_layout = QVBoxLayout(self)
        self.status_layout.addWidget(self.status_box)
        self.status.setLayout(self.status_layout)

        self.ami_layout = QVBoxLayout(self)
        self.ami_layout.addWidget(self.setup, 1)
        self.ami_layout.addWidget(self.data, 4)
        self.ami_layout.addWidget(self.status, 2)

        self.loadFile.connect(self.load_async)
        self.saveFile.connect(self.save_async)
        self.statusUpdate.connect(self.status_box.appendPlainText)

        # the status box to logging
        add_logging_handler(self.statusUpdate)

        # create a qthread that listens for info messages from the cluster to log them.
        self.info_thread = AmiInfo(graphmgr_addr.info, self.log_message)
        self.info_thread.start()

    @Slot(str, str)
    def log_message(self, topic, msg):
        # see if the topic name is a log level then use that otherwise use info
        log_func = getattr(logger, topic, 'info')
        log_func(msg)

    @Slot()
    def load(self):
        load_file = QFileDialog.getOpenFileName(
            self, "Open file", "", "AMI Autosave files (*.ami);;All Files (*)")
        if load_file[0]:
            logger.info("Loading graph configuration from file (%s)",
                        load_file[0])
            self.loadFile.emit(load_file[0])

    @Slot()
    def save(self):
        save_file = QFileDialog.getSaveFileName(
            self, "Save file", "autosave.ami",
            "AMI Autosave files (*.ami);;All Files (*)")
        if save_file[0]:
            logger.info("Saving graph configuration to file (%s)",
                        save_file[0])
            self.saveFile.emit(save_file[0])

    @asyncqt.asyncSlot()
    async def reset(self):
        if not (await self.comm_handler.reset()):
            logger.error("Unable to reset feature store of the manager!")

    @asyncqt.asyncSlot()
    async def clear(self):
        if not (await self.comm_handler.clear()):
            logger.error(
                "Unable to clear the graph configuration of the manager!")

    @asyncqt.asyncSlot(str)
    async def load_async(self, filename):
        await self.comm_handler.load(filename)

    @asyncqt.asyncSlot(str)
    async def save_async(self, filename):
        await self.comm_handler.save(filename)
Пример #19
0
class SegmentationInfoDialog(QWidget):
    def __init__(self,
                 settings: StackSettings,
                 set_parameters: Callable[[str, dict], None],
                 additional_text=None):
        """

        :param settings:
        :param set_parameters: Function which set parameters of chosen in dialog.
        :param additional_text: Additional text on top of Window.
        """
        super().__init__()
        self.settings = settings
        self.parameters_dict = None
        self.set_parameters = set_parameters
        self.components = QListWidget()
        self.components.currentItemChanged.connect(self.change_component_info)
        self.description = QPlainTextEdit()
        self.description.setReadOnly(True)
        self.close_btn = QPushButton("Close")
        self.close_btn.clicked.connect(self.close)
        self.set_parameters_btn = QPushButton("Reuse parameters")
        self.set_parameters_btn.clicked.connect(self.set_parameter_action)
        self.additional_text_label = QLabel(additional_text)
        layout = QGridLayout()
        layout.addWidget(self.additional_text_label, 0, 0, 1, 2)
        if not additional_text:
            self.additional_text_label.setVisible(False)

        layout.addWidget(QLabel("Components:"), 1, 0)
        layout.addWidget(QLabel("segmentation parameters:"), 1, 1)
        layout.addWidget(self.components, 2, 0)
        layout.addWidget(self.description, 2, 1)
        layout.addWidget(self.close_btn, 3, 0)
        layout.addWidget(self.set_parameters_btn, 3, 1)
        self.setLayout(layout)
        self.setWindowTitle("Parameters preview")

    def set_parameters_dict(self, val: Optional[Dict[int,
                                                     SegmentationProfile]]):
        self.parameters_dict = val

    def set_additional_text(self, text):
        self.additional_text_label.setText(text)
        self.additional_text_label.setVisible(bool(text))

    @property
    def get_parameters(self):
        if self.parameters_dict:
            return self.parameters_dict
        return self.settings.components_parameters_dict

    def change_component_info(self):
        if self.components.currentItem() is None:
            return
        text = self.components.currentItem().text()
        parameters = self.get_parameters[int(text)]
        if parameters is None:
            self.description.setPlainText("None")
        else:
            self.description.setPlainText(
                f"Component {text}\n" +
                parameters.pretty_print(mask_algorithm_dict))

    def set_parameter_action(self):
        if self.components.currentItem() is None:
            return
        text = self.components.currentItem().text()
        parameters = self.get_parameters[int(text)]
        self.set_parameters(parameters.algorithm, parameters.values)

    def event(self, event: QEvent):
        if event.type() == QEvent.WindowActivate:
            index = self.components.currentRow()
            self.components.clear()
            self.components.addItems(list(map(str,
                                              self.get_parameters.keys())))
            self.components.setCurrentRow(index)
        return super().event(event)
Пример #20
0
class Properties(QWidget):
    def __init__(self, settings: PartSettings):
        super().__init__()
        self._settings = settings
        self.export_btn = QPushButton("Export profile")
        self.export_btn.clicked.connect(self.export_profile)
        self.import_btn = QPushButton("Import profile")
        self.import_btn.clicked.connect(self.import_profiles)
        self.export_pipeline_btn = QPushButton("Export pipeline")
        self.export_pipeline_btn.clicked.connect(self.export_pipeline)
        self.import_pipeline_btn = QPushButton("Import pipeline")
        self.import_pipeline_btn.clicked.connect(self.import_pipeline)
        self.delete_btn = QPushButton("Delete profile")
        self.delete_btn.setDisabled(True)
        self.delete_btn.clicked.connect(self.delete_profile)
        self.multiple_files_chk = QCheckBox("Show multiple files panel")
        self._update_measurement_chk()
        self.multiple_files_chk.stateChanged.connect(
            self.multiple_files_visibility)
        self.rename_btn = QPushButton("Rename profile")
        self.rename_btn.clicked.connect(self.rename_profile)
        self.rename_btn.setDisabled(True)
        self.voxel_size_label = QLabel()
        self.info_label = QPlainTextEdit()
        self.info_label.setReadOnly(True)
        self.profile_list = SearchableListWidget()
        self.profile_list.currentTextChanged.connect(self.profile_chosen)
        self.pipeline_list = SearchableListWidget()
        self.pipeline_list.currentTextChanged.connect(self.profile_chosen)
        self.spacing = [QDoubleSpinBox() for _ in range(3)]
        self.lock_spacing = LockCheckBox()
        self.lock_spacing.stateChanged.connect(self.spacing[1].setDisabled)
        self.lock_spacing.stateChanged.connect(self.synchronize_spacing)
        # noinspection PyUnresolvedReferences
        self.spacing[2].valueChanged.connect(self.synchronize_spacing)

        self._settings.roi_profiles_changed.connect(self.update_profile_list)
        self._settings.roi_pipelines_changed.connect(self.update_profile_list)
        self._settings.connect_("multiple_files_widget",
                                self._update_measurement_chk)

        units_value = self._settings.get("units_value", Units.nm)
        for el in self.spacing:
            el.setAlignment(Qt.AlignRight)
            el.setButtonSymbols(QAbstractSpinBox.NoButtons)
            el.setRange(0, 1000000)
            # noinspection PyUnresolvedReferences
            el.valueChanged.connect(self.image_spacing_change)
        self.units = QEnumComboBox(enum_class=Units)
        self.units.setCurrentEnum(units_value)
        # noinspection PyUnresolvedReferences
        self.units.currentIndexChanged.connect(self.update_spacing)

        spacing_layout = QHBoxLayout()
        spacing_layout.addWidget(self.lock_spacing)
        for txt, el in zip(["x", "y", "z"], self.spacing[::-1]):
            spacing_layout.addWidget(QLabel(txt + ":"))
            spacing_layout.addWidget(el)
        spacing_layout.addWidget(self.units)
        spacing_layout.addStretch(1)
        voxel_size_layout = QHBoxLayout()
        voxel_size_layout.addWidget(self.voxel_size_label)
        voxel_size_layout.addSpacing(30)
        profile_layout = QGridLayout()
        profile_layout.setSpacing(0)
        profile_layout.addWidget(QLabel("Profiles:"), 0, 0)
        profile_layout.addWidget(self.profile_list, 1, 0)
        profile_layout.addWidget(QLabel("Pipelines:"), 2, 0)
        profile_layout.addWidget(self.pipeline_list, 3, 0, 4, 1)
        profile_layout.addWidget(self.info_label, 1, 1, 3, 2)
        profile_layout.addWidget(self.export_btn, 4, 1)
        profile_layout.addWidget(self.import_btn, 4, 2)
        profile_layout.addWidget(self.export_pipeline_btn, 5, 1)
        profile_layout.addWidget(self.import_pipeline_btn, 5, 2)
        profile_layout.addWidget(self.delete_btn, 6, 1)
        profile_layout.addWidget(self.rename_btn, 6, 2)
        layout = QVBoxLayout()
        layout.addLayout(spacing_layout)
        layout.addLayout(voxel_size_layout)
        layout.addWidget(self.multiple_files_chk)

        layout.addLayout(profile_layout, 1)
        self.setLayout(layout)
        self.update_profile_list()

    @Slot(int)
    def multiple_files_visibility(self, val: int):
        self._settings.set("multiple_files_widget", val)

    # @Slot(str)  # PySide bug
    def profile_chosen(self, text):
        if text == "":
            self.delete_btn.setEnabled(False)
            self.rename_btn.setEnabled(False)
            self.info_label.setPlainText("")
            return
        try:
            if self.sender() == self.profile_list.list_widget:
                profile = self._settings.roi_profiles[text]
                self.pipeline_list.selectionModel().clear()
                self.delete_btn.setText("Delete profile")
                self.rename_btn.setText("Rename profile")
            elif self.sender() == self.pipeline_list.list_widget:
                profile = self._settings.roi_pipelines[text]
                self.profile_list.selectionModel().clear()
                self.delete_btn.setText("Delete pipeline")
                self.rename_btn.setText("Rename pipeline")
            else:
                return
        except KeyError:
            return

        # TODO update with knowledge from profile dict
        self.delete_btn.setEnabled(True)
        self.rename_btn.setEnabled(True)
        self.info_label.setPlainText(
            profile.pretty_print(analysis_algorithm_dict))

    def synchronize_spacing(self):
        if self.lock_spacing.isChecked():
            self.spacing[1].setValue(self.spacing[2].value())

    def image_spacing_change(self):
        spacing = [
            el.value() / UNIT_SCALE[self.units.currentIndex()]
            for i, el in enumerate(self.spacing)
        ]
        if not self.spacing[0].isEnabled():
            spacing = spacing[1:]
        self._settings.image_spacing = spacing

        voxel_size = 1
        for el in self._settings.image_spacing:
            voxel_size *= el * UNIT_SCALE[self.units.currentIndex()]
        self.voxel_size_label.setText(
            f"Voxel_size: {voxel_size} {self.units.currentEnum().name} <sup>{len(self._settings.image_spacing)}</sup>"
        )

    def update_spacing(self, index=None):
        voxel_size = 1
        value = self.units.currentEnum()
        if index is not None:
            self._settings.set("units_value", value)
        for el, sp in zip(self.spacing[::-1],
                          self._settings.image_spacing[::-1]):
            el.blockSignals(True)
            current_size = sp * UNIT_SCALE[self.units.currentIndex()]
            voxel_size *= current_size
            el.setValue(current_size)
            el.blockSignals(False)
        self.spacing[0].setDisabled(len(self._settings.image_spacing) == 2)
        self.voxel_size_label.setText(
            f"Voxel_size: {voxel_size} {value.name} <sup>{len(self._settings.image_spacing)}</sup>"
        )

    def update_profile_list(self):
        current_names = set(self._settings.roi_profiles.keys())
        self.profile_list.clear()
        self.profile_list.addItems(sorted(current_names))
        self.pipeline_list.clear()
        self.pipeline_list.addItems(
            sorted(set(self._settings.roi_pipelines.keys())))
        self.delete_btn.setDisabled(True)
        self.rename_btn.setDisabled(True)
        self.info_label.setPlainText("")

    def showEvent(self, _event):
        self.update_spacing()

    def event(self, event: QEvent):
        if event.type() == QEvent.WindowActivate and self.isVisible():
            self.update_spacing()
        return super().event(event)

    @ensure_main_thread
    def _update_measurement_chk(self):
        self.multiple_files_chk.setChecked(
            self._settings.get("multiple_files_widget", False))

    def delete_profile(self):
        text, dkt = "", {}
        if self.profile_list.selectedItems():
            text = self.profile_list.selectedItems()[0].text()
            dkt = self._settings.roi_profiles
        elif self.pipeline_list.selectedItems():
            text = self.pipeline_list.selectedItems()[0].text()
            dkt = self._settings.roi_pipelines
        if text != "":
            self.delete_btn.setDisabled(True)
            del dkt[text]
            self.update_profile_list()

    def export_profile(self):
        exp = ExportDialog(self._settings.roi_profiles, ProfileDictViewer)
        if not exp.exec_():
            return
        dial = PSaveDialog(
            "Segment profile (*.json)",
            settings=self._settings,
            path=IO_SAVE_DIRECTORY,
            caption="Export profile segment",
        )
        dial.selectFile("segment_profile.json")
        if dial.exec_():
            file_path = dial.selectedFiles()[0]
            data = {
                x: self._settings.roi_profiles[x]
                for x in exp.get_export_list()
            }
            with open(file_path, "w", encoding="utf-8") as ff:
                json.dump(data,
                          ff,
                          cls=self._settings.json_encoder_class,
                          indent=2)

    def import_profiles(self):
        dial = PLoadDialog(
            "Segment profile (*.json)",
            settings=self._settings,
            path=IO_SAVE_DIRECTORY,
            caption="Import profile segment",
        )
        if dial.exec_():
            file_path = dial.selectedFiles()[0]
            profs, err = self._settings.load_part(file_path)
            if err:
                QMessageBox.warning(
                    self, "Import error",
                    "error during importing, part of data were filtered.")
            profiles_dict = self._settings.roi_profiles
            imp = ImportDialog(profs, profiles_dict, ProfileDictViewer)
            if not imp.exec_():
                return
            for original_name, final_name in imp.get_import_list():
                profiles_dict[final_name] = profs[original_name]
            self._settings.dump()
            self.update_profile_list()

    def export_pipeline(self):
        exp = ExportDialog(self._settings.roi_pipelines, ProfileDictViewer)
        if not exp.exec_():
            return
        dial = PSaveDialog(
            "Segment pipeline (*.json)",
            settings=self._settings,
            path=IO_SAVE_DIRECTORY,
            caption="Export pipeline segment",
        )
        dial.selectFile("segment_pipeline.json")
        if dial.exec_():
            file_path = dial.selectedFiles()[0]
            data = {
                x: self._settings.roi_pipelines[x]
                for x in exp.get_export_list()
            }
            with open(file_path, "w", encoding="utf-8") as ff:
                json.dump(data,
                          ff,
                          cls=self._settings.json_encoder_class,
                          indent=2)

    def import_pipeline(self):
        dial = PLoadDialog(
            "Segment pipeline (*.json)",
            settings=self._settings,
            path=IO_SAVE_DIRECTORY,
            caption="Import pipeline segment",
        )
        if dial.exec_():
            file_path = dial.selectedFiles()[0]
            profs, err = self._settings.load_part(file_path)
            if err:
                QMessageBox.warning(
                    self, "Import error",
                    "error during importing, part of data were filtered.")
            profiles_dict = self._settings.roi_pipelines
            imp = ImportDialog(profs, profiles_dict, ProfileDictViewer)
            if not imp.exec_():
                return
            for original_name, final_name in imp.get_import_list():
                profiles_dict[final_name] = profs[original_name]
            self._settings.dump()
            self.update_profile_list()

    def rename_profile(self):
        profile_name, profiles_dict = "", {}
        if self.profile_list.selectedItems():
            profile_name = self.profile_list.selectedItems()[0].text()
            profiles_dict = self._settings.roi_profiles
        elif self.pipeline_list.selectedItems():
            profile_name = self.pipeline_list.selectedItems()[0].text()
            profiles_dict = self._settings.roi_pipelines
        if profile_name == "":
            return
        text, ok = QInputDialog.getText(self,
                                        "New profile name",
                                        f"New name for {profile_name}",
                                        text=profile_name)
        if ok:
            text = text.strip()
            if text in profiles_dict.keys():
                res = QMessageBox.warning(
                    self,
                    "Already exist",
                    f"Profile with name {text} already exist. Would you like to overwrite?",
                    QMessageBox.Yes | QMessageBox.No,
                    QMessageBox.No,
                )
                if res == QMessageBox.No:
                    self.rename_profile()
                    return
            profiles_dict[text] = profiles_dict.pop(profile_name)
            self._settings.dump()
            self.update_profile_list()
Пример #21
0
class PyDMLogDisplay(QWidget, LogLevels):
    """
    Standard display for Log Output

    This widget handles instantating a ``GuiHandler`` and displaying log
    messages to a ``QPlainTextEdit``. The level of the log can be changed from
    inside the widget itself, allowing users to select from any of the
    ``.levels`` specified by the widget.

    Parameters
    ----------
    parent : QObject, optional

    logname : str
        Name of log to display in widget

    level : logging.Level
        Initial level of log display

    """
    Q_ENUMS(LogLevels)
    LogLevels = LogLevels
    terminator = '\n'
    default_format = '%(asctime)s %(message)s'
    default_level = logging.INFO

    def __init__(self, parent=None, logname=None, level=logging.NOTSET):
        QWidget.__init__(self, parent=parent)
        # Create Widgets
        self.label = QLabel('Minimum displayed log level: ', parent=self)
        self.combo = QComboBox(parent=self)
        self.text = QPlainTextEdit(parent=self)
        self.text.setReadOnly(True)
        self.clear_btn = QPushButton("Clear", parent=self)
        # Create layout
        layout = QVBoxLayout()
        level_control = QHBoxLayout()
        level_control.addWidget(self.label)
        level_control.addWidget(self.combo)
        layout.addLayout(level_control)
        layout.addWidget(self.text)
        layout.addWidget(self.clear_btn)
        self.setLayout(layout)
        # Allow QCombobox to control log level
        for log_level, value in LogLevels.as_dict().items():
            self.combo.addItem(log_level, value)
        self.combo.currentIndexChanged[str].connect(self.setLevel)
        # Allow QPushButton to clear log text
        self.clear_btn.clicked.connect(self.clear)
        # Create a handler with the default format
        self.handler = GuiHandler(level=level, parent=self)
        self.logFormat = self.default_format
        self.handler.message.connect(self.write)
        # Create logger. Either as a root or given logname
        self.log = None
        self.level = None
        self.logName = logname or ''
        self.logLevel = level
        self.destroyed.connect(functools.partial(logger_destroyed, self.log))

    def sizeHint(self):
        return QSize(400, 300)

    @Property(LogLevels)
    def logLevel(self):
        return self.level

    @logLevel.setter
    def logLevel(self, level):
        if level != self.level:
            self.level = level
            idx = self.combo.findData(level)
            self.combo.setCurrentIndex(idx)

    @Property(str)
    def logName(self):
        """Name of associated log"""
        return self.log.name

    @logName.setter
    def logName(self, name):
        # Disconnect prior log from handler
        if self.log:
            self.log.removeHandler(self.handler)
        # Reattach handler to new handler
        self.log = logging.getLogger(name)
        # Ensure that the log matches level of handler
        # only if the handler level is less than the log.
        if self.log.level < self.handler.level:
            self.log.setLevel(self.handler.level)
        # Attach preconfigured handler
        self.log.addHandler(self.handler)

    @Property(str)
    def logFormat(self):
        """Format for log messages"""
        return self.handler.formatter._fmt

    @logFormat.setter
    def logFormat(self, fmt):
        self.handler.setFormatter(logging.Formatter(fmt))

    @Slot(str)
    def write(self, message):
        """Write a message to the log display"""
        # We split the incoming message by new lines. In prior iterations of
        # this widget it was discovered that large blocks of text cause issues
        # at the Qt level.
        for msg in message.split(self.terminator):
            self.text.appendPlainText(msg)

    @Slot()
    def clear(self):
        """Clear the text area."""
        self.text.clear()

    @Slot(str)
    def setLevel(self, level):
        """Set the level of the contained logger"""
        # Get the level from the incoming string specification
        try:
            level = getattr(logging, level.upper())
        except AttributeError as exc:
            logger.exception("Invalid logging level specified %s",
                             level.upper())
        else:
            # Set the existing handler and logger to this level
            self.handler.setLevel(level)
            if self.log.level > self.handler.level or self.log.level == logging.NOTSET:
                self.log.setLevel(self.handler.level)