# Source code for taurus.qt.qtgui.table.qlogtable

    #!/usr/bin/env python

    # ###########################################################################
    #
    # This file is part of Taurus
    #
    # http://taurus-scada.org
    #
    # Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
    #
    # Taurus is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Lesser General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # Taurus is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Lesser General Public License for more details.
    #
    # You should have received a copy of the GNU Lesser General Public License
    # along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ###########################################################################

    """This module provides Qt table widgets which display logging messages from
    the python :mod:`logging` module """

    from operator import attrgetter

    import logging
    import logging.handlers
    import datetime
    import threading
    import socket
    import click

    import taurus
    from taurus.core.util.log import Logger
    from taurus.core.util.remotelogmonitor import (
        LogRecordStreamHandler,
        LogRecordSocketReceiver,
    )
    from taurus.core.util.decorator.memoize import memoized

    from taurus.external.qt import Qt
    from taurus.qt.qtgui.model import FilterToolBar
    from taurus.qt.qtgui.util import ActionFactory
    import taurus.cli.common

    from .qtable import QBaseTableWidget

    __docformat__ = "restructuredtext"

    LEVEL, TIME, MSG, NAME, ORIGIN = list(range(5))
    HORIZ_HEADER = "Level", "Time", "Message", "By", "Origin"

    __LEVEL_BRUSH = {
        taurus.Trace: (Qt.Qt.lightGray, Qt.Qt.black),
        taurus.Debug: (Qt.Qt.green, Qt.Qt.black),
        taurus.Info: (Qt.Qt.blue, Qt.Qt.white),
        taurus.Warning: (Qt.QColor(255, 165, 0), Qt.Qt.black),
        taurus.Error: (Qt.Qt.red, Qt.Qt.black),
        taurus.Critical: (Qt.QColor(160, 32, 240), Qt.Qt.white),
    }

    def getBrushForLevel(level):
        elevel = taurus.Trace
        if level <= taurus.Trace:
            elevel = taurus.Trace
        elif level <= taurus.Debug:
            elevel = taurus.Debug
        elif level <= taurus.Info:
            elevel = taurus.Info
        elif level <= taurus.Warning:
            elevel = taurus.Warning
        elif level <= taurus.Error:
            elevel = taurus.Error
        elif level <= taurus.Critical:
            elevel = taurus.Critical
        f, g = list(map(Qt.QBrush, __LEVEL_BRUSH[elevel]))
        return f, g

    gethostname = memoized(socket.gethostname)

    def _get_record_origin(rec):
        host = getattr(rec, "hostName", "?" + gethostname() + "?")
        procName = getattr(rec, "processName", "?process?")
        procID = getattr(rec, "process", "?PID?")
        threadName = getattr(rec, "threadName", "?thread?")
        threadID = getattr(rec, "thread", "?threadID?")
        return host, procName, procID, threadName, threadID

    def _get_record_trace(rec):
        pathname = getattr(rec, "pathname", "")
        filename = getattr(rec, "filename", "")
        modulename = getattr(rec, "module", "")
        funcname = getattr(rec, "funcName", "")
        lineno = getattr(rec, "lineno", "")
        return pathname, filename, modulename, funcname, lineno

    def _get_record_origin_str(rec):
        return "{0}.{1}.{3}".format(*_get_record_origin(rec))

    def _get_record_origin_tooltip(rec):

        host, procName, procID, threadName, threadID = _get_record_origin(rec)
        pathname, filename, modulename, funcname, lineno = _get_record_trace(rec)
        timestamp = str(datetime.datetime.fromtimestamp(rec.created))
        bgcolor, fgcolor = list(
            map(Qt.QBrush.color, getBrushForLevel(rec.levelno))
        )
        bgcolor = "#%02x%02x%02x" % (
            bgcolor.red(),
            bgcolor.green(),
            bgcolor.blue(),
        )
        fgcolor = "#%02x%02x%02x" % (
            fgcolor.red(),
            fgcolor.green(),
            fgcolor.blue(),
        )

        return """<html><font face="monospace" size="1">
    <table border="0" cellpadding="0" cellspacing="0">
    <tr><td>Level:</td><td><font color="{level_bgcolor}">{level}</font></td></tr>
    <tr><td>Time:</td><td>{timestamp}</td></tr>
    <tr><td>Message:</td><td>{message}</td></tr>
    <tr><td>By:</td><td>{name}</td></tr>
    <tr><td>Host:</td><td>{host}</td></tr>
    <tr><td>Process:</td><td>{procname}({procID})</td></tr>
    <tr><td>Thread:</td><td>{threadname}({threadID})</td></tr>
    <tr><td>From:</td><td>File pathname({filename}), line {lineno}, in {funcname}</td></tr>
    </table></font></html>
    """.format(  # noqa
            level=rec.levelname,
            level_fgcolor=fgcolor,
            level_bgcolor=bgcolor,
            timestamp=timestamp,
            message=rec.getMessage(),
            name=rec.name,
            host=host,
            procname=procName,
            procID=procID,
            threadname=threadName,
            threadID=threadID,
            pathname=pathname,
            filename=filename,
            funcname=funcname,
            lineno=lineno,
        )

    class QLoggingTableModel(Qt.QAbstractTableModel, logging.Handler):

        DftFont = Qt.QFont("Mono", 8)
        DftColSize = (
            Qt.QSize(80, 20),
            Qt.QSize(200, 20),
            Qt.QSize(300, 20),
            Qt.QSize(180, 20),
            Qt.QSize(240, 20),
        )

        def __init__(self, parent=None, capacity=500000, freq=0.25):
            Qt.QAbstractTableModel.__init__(self, parent=parent)
            logging.Handler.__init__(self)
            self._capacity = capacity
            self._records = []
            self._accumulated_records = []
            Logger.addRootLogHandler(self)
            self.startTimer(int(freq * 1000))

        # ---------------------------------
        # Qt.QAbstractTableModel overwrite
        # ---------------------------------

        def sort(self, column, order=Qt.Qt.AscendingOrder):
            column2key_map = {
                LEVEL: attrgetter("levelno"),
                TIME: attrgetter("created"),
                MSG: attrgetter("msg"),
                NAME: attrgetter("name"),
                ORIGIN: attrgetter("process", "thread", "name"),
            }
            self._records = sorted(
                self._records,
                key=column2key_map[column],
                reverse=order == Qt.Qt.DescendingOrder,
            )

        def rowCount(self, index=Qt.QModelIndex()):
            return len(self._records)

        def columnCount(self, index=Qt.QModelIndex()):
            return len(HORIZ_HEADER)

        def getRecord(self, index):
            return self._records[index.row()]

        def data(self, index, role=Qt.Qt.DisplayRole):
            if not index.isValid() or not (0 <= index.row() < len(self._records)):
                return None
            record = self.getRecord(index)
            column = index.column()
            if role == Qt.Qt.DisplayRole:
                if column == LEVEL:
                    return record.levelname
                elif column == TIME:
                    dt = datetime.datetime.fromtimestamp(record.created)
                    return str(dt)
                    # return dt.strftime("%Y-%m-%d %H:%m:%S.%f")
                elif column == MSG:
                    return record.getMessage()
                elif column == NAME:
                    return record.name
                elif column == ORIGIN:
                    return _get_record_origin_str(record)
            elif role == Qt.Qt.TextAlignmentRole:
                if column in (LEVEL, MSG):
                    return Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter
                return Qt.Qt.AlignRight | Qt.Qt.AlignVCenter
            elif role == Qt.Qt.BackgroundRole:
                if column == LEVEL:
                    return getBrushForLevel(record.levelno)[0]
            elif role == Qt.Qt.ForegroundRole:
                if column == LEVEL:
                    return getBrushForLevel(record.levelno)[1]
            elif role == Qt.Qt.ToolTipRole:
                return _get_record_origin_tooltip(record)
            elif role == Qt.Qt.SizeHintRole:
                return self._getSizeHint(column)
            # elif role == Qt.Qt.StatusTipRole:
            # elif role == Qt.Qt.CheckStateRole:
            elif role == Qt.Qt.FontRole:
                return self.DftFont
            return None

        def _getSizeHint(self, column):
            return QLoggingTableModel.DftColSize[column]

        def headerData(self, section, orientation, role=Qt.Qt.DisplayRole):
            if role == Qt.Qt.TextAlignmentRole:
                if orientation == Qt.Qt.Horizontal:
                    return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter)
                return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter)
            elif role == Qt.Qt.SizeHintRole:
                if orientation == Qt.Qt.Vertical:
                    return Qt.QSize(50, 20)
                else:
                    return self._getSizeHint(section)
            elif role == Qt.Qt.FontRole:
                return Qt.QFont("Mono", 8)
            elif role == Qt.Qt.ToolTipRole:
                if section == LEVEL:
                    return "log level"
                elif section == TIME:
                    return "log time stamp"
                elif section == MSG:
                    return "log message"
                elif section == NAME:
                    return "object who recorded the log"
                elif section == ORIGIN:
                    return (
                        "the host, process and thread where the"
                        + " log was executed from"
                    )
            if role != Qt.Qt.DisplayRole:
                return None
            if orientation == Qt.Qt.Horizontal:
                return HORIZ_HEADER[section]
            return int(section + 1)

        def insertRows(self, position, rows=1, index=Qt.QModelIndex()):
            self.beginInsertRows(Qt.QModelIndex(), position, position + rows - 1)
            self.endInsertRows()

        def removeRows(self, position, rows=1, index=Qt.QModelIndex()):
            self.beginRemoveRows(Qt.QModelIndex(), position, position + rows - 1)
            self.endRemoveRows()

        # def setData(self, index, value, role=Qt.Qt.DisplayRole):
        #    pass

        # def flags(self, index)
        #    pass

        # def insertColumns(self):
        #    pass

        # def removeColumns(self):
        #    pass

        # --------------------------
        # logging.Handler overwrite
        # --------------------------

        def timerEvent(self, evt):
            self.updatePendingRecords()

        def updatePendingRecords(self):
            if not self._accumulated_records:
                return
            row_nb = self.rowCount()
            records = self._accumulated_records
            self._accumulated_records = []
            self._records.extend(records)
            self.insertRows(row_nb, len(records))
            if len(self._records) > self._capacity:
                start = len(self._records) - self._capacity
                self._records = self._records[start:]
                self.removeRows(0, start)

        def emit(self, record):
            self._accumulated_records.append(record)

        def flush(self):
            pass

        def close(self):
            self.flush()
            del self._records[:]
            logging.Handler.close(self)

    class _LogRecordStreamHandler(LogRecordStreamHandler):
        def handleLogRecord(self, record):
            self.server.data.get("model").emit(record)

    class QRemoteLoggingTableModel(QLoggingTableModel):
        """A remote Qt table that displays the taurus logging messages"""

        def connect_logging(
            self,
            host="localhost",
            port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
            handler=_LogRecordStreamHandler,
        ):
            self.log_receiver = LogRecordSocketReceiver(
                host=host, port=port, handler=handler, model=self
            )
            self.log_thread = threading.Thread(
                target=self.log_receiver.serve_until_stopped
            )
            self.log_thread.daemon = False
            self.log_thread.start()

        def disconnect_logging(self):
            if not hasattr(self, "log_receiver") or self.log_receiver is None:
                return
            self.log_receiver.stop()
            self.log_thread.join()
            del self.log_receiver

    class QLoggingTable(Qt.QTableView):
        """A Qt table that displays the taurus logging messages"""

        scrollLock = False

        def rowsInserted(self, index, start, end):
            """Overwrite of slot rows inserted to do proper resize and scroll to
            bottom if desired
            """
            Qt.QTableView.rowsInserted(self, index, start, end)
            for i in range(start, end + 1):
                self.resizeRowToContents(i)
            if start == 0:
                self.resizeColumnsToContents()
            if not self.scrollLock:
                self.scrollToBottom()

        def setScrollLock(self, scrollLock):
            """Sets the state for scrollLock"""
            self.scrollLock = scrollLock

        def getScrollLock(self):
            """Returns wheater or not the scrollLock is active"""
            return self.scrollLock

        def resetScrollLock(self):
            self.setScrollLock(QLoggingTable.ScrollLock)

    class LoggingToolBar(FilterToolBar):

        scrollLockToggled = Qt.pyqtSignal(bool)

        def __init__(self, view=None, parent=None, designMode=False):
            FilterToolBar.__init__(
                self, view=view, parent=parent, designMode=designMode
            )
            self.getFilterLineEdit().setToolTip("Quick filter by log name")

            self._logLevelComboBox = logLevelComboBox = Qt.QComboBox()
            levels = "Trace", "Debug", "Info", "Warning", "Error", "Critical"
            for level in levels:
                logLevelComboBox.addItem(level, getattr(taurus, level))
            logLevelComboBox.setCurrentIndex(0)
            logLevelComboBox.currentIndexChanged.connect(self.onLogLevelChanged)
            logLevelComboBox.setToolTip("Filter by log level")

            self._filterLevelAction = self.addWidget(logLevelComboBox)
            self.addSeparator()

            af = ActionFactory()
            self._scrollLockAction = af.createAction(
                self,
                "Refresh",
                icon=Qt.QIcon.fromTheme("system-lock-screen"),
                tip="Scroll lock",
                toggled=self.onToggleScrollLock,
            )

            self.addAction(self._scrollLockAction)

        def onToggleScrollLock(self, yesno):
            self.scrollLockToggled.emit(yesno)

        def onLogLevelChanged(self, index):
            self.onFilterChanged()

        def getLogLevelComboBox(self):
            return self._logLevelComboBox

        def getLogLevel(self):
            combo = self.getLogLevelComboBox()
            return combo.itemData(combo.currentIndex())

        def setLogLevel(self, level):
            combo = self.getLogLevelComboBox()
            for i in range(combo.count()):
                level = combo.itemData(i)
                if level == level:
                    combo.setCurrentIndex(i)

    class QLoggingFilterProxyModel(Qt.QSortFilterProxyModel):
        """A filter by log record object name"""

        def __init__(self, parent=None):
            Qt.QSortFilterProxyModel.__init__(self, parent)
            self._logLevel = taurus.Trace

            # filter configuration
            self.setFilterCaseSensitivity(Qt.Qt.CaseInsensitive)
            self.setFilterKeyColumn(0)
            self.setFilterRole(Qt.Qt.DisplayRole)

            # sort configuration
            # self.setSortCaseSensitivity(Qt.Qt.CaseInsensitive)
            # self.setSortRole(Qt.Qt.DisplayRole)

            # general configuration

        def setFilterLogLevel(self, level):
            self._logLevel = level

        def __getattr__(self, name):
            return getattr(self.sourceModel(), name)

        def filterAcceptsRow(self, sourceRow, sourceParent):
            sourceModel = self.sourceModel()
            idx = sourceModel.index(sourceRow, NAME, sourceParent)
            record = self.getRecord(idx)
            if record.levelno < self._logLevel:
                return False
            name = str(sourceModel.data(idx))
            regexp = self.filterRegExp()
            if regexp.indexIn(name) != -1:
                return True
            return False

    _W = (
        "Warning: Switching log perspective will erase previous log messages "
        "from current perspective!"
    )

    class QLoggingWidget(QBaseTableWidget):

        KnownPerspectives = {
            "Standard": {
                "label": "Local",
                "icon": "computer",
                "tooltip": "Local logging.\n" + _W,
                "model": [
                    QLoggingFilterProxyModel,
                    QLoggingTableModel,
                ],
            },
            "Remote": {
                "label": "Remote",
                "icon": "network-server",
                "tooltip": "Monitor remote logs.\n" + _W,
                "model": [
                    QLoggingFilterProxyModel,
                    QRemoteLoggingTableModel,
                ],
            },
        }

        DftPerspective = "Standard"

        def __init__(
            self,
            parent=None,
            designMode=False,
            with_filter_widget=LoggingToolBar,
            with_selection_widget=True,
            with_refresh_widget=True,
            perspective=None,
            proxy=None,
        ):
            QBaseTableWidget.__init__(
                self,
                parent=parent,
                designMode=designMode,
                with_filter_widget=with_filter_widget,
                with_selection_widget=False,
                with_refresh_widget=False,
                perspective=perspective,
                proxy=proxy,
            )

        def createViewWidget(self, klass=None):
            if klass is None:
                klass = QLoggingTable
            view = QBaseTableWidget.createViewWidget(self, klass=klass)
            hh = view.horizontalHeader()
            if hh.length() > 0:
                hh.setSectionResizeMode(MSG, Qt.QHeaderView.Stretch)
            view.setShowGrid(False)
            view.sortByColumn(TIME, Qt.Qt.AscendingOrder)
            return view

        def createToolArea(self):
            tb = QBaseTableWidget.createToolArea(self)
            filterBar = self.getFilterBar()
            filterBar.scrollLockToggled.connect(self.onScrollLockToggled)
            return tb

        def onScrollLockToggled(self, yesno):
            self.viewWidget().setScrollLock(yesno)

        def onFilterChanged(self, filter):
            if not self.usesProxyQModel():
                return
            proxy_model = self.getQModel()
            level = self.getFilterBar().getLogLevel()
            proxy_model.setFilterLogLevel(level)
            return QBaseTableWidget.onFilterChanged(self, filter)

        def onSwitchPerspective(self, perspective):
            self.stop_logging()
            if perspective == "Remote":
                if hasattr(self, "hostName") and hasattr(self, "port"):
                    host, port = self.hostName, self.port
                else:
                    isValid = False
                    dft = "%s:%d" % (
                        socket.gethostname(),
                        logging.handlers.DEFAULT_TCP_LOGGING_PORT,
                    )
                    while not isValid:
                        txt, res = Qt.QInputDialog.getText(
                            self,
                            "Please input remote logging host and port",
                            "Location (<host>:<port>):",
                            Qt.QLineEdit.Normal,
                            dft,
                        )
                        if not res:
                            return
                        try:
                            host, port = str(txt).split(":", 1)
                            port = int(port)
                            isValid = True
                        except Exception:
                            Qt.QMessageBox.information(
                                self,
                                "Invalid name",
                                "Please type a valid <host>:<port>",
                            )
                ret = QBaseTableWidget.onSwitchPerspective(self, perspective)
                qmodel = self.getQModel()
                qmodel.connect_logging(host=host, port=port)
            else:
                ret = QBaseTableWidget.onSwitchPerspective(self, perspective)
            return ret

        def destroy(self, destroyWindow=True, destroySubWindows=True):
            self.stop_logging()
            return QBaseTableWidget.destroy(self, destroyWindow, destroySubWindows)

        def stop_logging(self):
            model = self.getBaseQModel()
            if hasattr(model, "disconnect_logging"):
                model.disconnect_logging()

        @classmethod
        def getQtDesignerPluginInfo(cls):
            return {
                "module": "taurus.qt.qtgui.table",
                "group": "Taurus Views",
                "icon": "designer:table.png",
                "container": False,
            }

    def fill_log():
        import time
        import random

        for i in range(10):
            taurus.info("Hello world %04d" % i)

        loggers = ["Object%02d" % (i + 1) for i in range(10)]
        i = 0
        while True:
            time.sleep(random.random())
            logger = logging.getLogger(random.choice(loggers))
            level = random.randint(taurus.Trace, taurus.Critical)
            logger.log(level, "log message %04d" % i)
            i += 1

    def main():
        import taurus.qt.qtgui.application

        Application = taurus.qt.qtgui.application.TaurusApplication

        app = Application.instance()
        owns_app = app is None

        if owns_app:
            app = Application(
                app_name="Logging demo",
                app_version="1.0",
                org_domain="Taurus",
                org_name="Taurus community",
            )

        taurus.setLogLevel(taurus.Trace)
        taurus.disableLogOutput()
        w = QLoggingWidget()

        taurus.trace("trace message")
        taurus.debug("debug message")
        taurus.info("Hello world")
        taurus.warning("Warning message")
        taurus.error("error message")
        taurus.critical("critical message")
        w.setMinimumSize(1200, 600)
        w.show()
        app.exec_()
        w.stop_logging()

    @click.command("qlogmon")
    @click.option(
        "--port",
        "port",
        type=int,
        default=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
        show_default=True,
        help="Port where log server is running",
    )
    @click.option(
        "--log-name",
        "log_name",
        default=None,
        help="Filter specific log object",
    )
    @taurus.cli.common.log_level
    def qlogmon_cmd(port, log_name, log_level):
        """Show the Taurus Remote Log Monitor"""
        import taurus

        host = socket.gethostname()
        level = getattr(taurus, log_level.capitalize(), taurus.Trace)

        from taurus.qt.qtgui.application import TaurusApplication

        app = TaurusApplication(
            cmd_line_parser=None, app_name="Taurus remote logger"
        )
        w = QLoggingWidget(perspective="Remote")
        w.setMinimumSize(1024, 600)

        filterbar = w.getFilterBar()
        filterbar.setLogLevel(level)
        if log_name is not None:
            filterbar.setFilterText(log_name)
        w.getPerspectiveBar().setEnabled(False)
        w.getQModel().connect_logging(host, port)
        w.show()
        app.exec_()
        w.getQModel().disconnect_logging()

    if __name__ == "__main__":
        main()
        # qlogmon_cmd