#!/usr/bin/env python3 # # QTVcp Widget - Machine Log # Copyright (c) 2017 Chris Morley with additions by Steve Richardson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. ############################################################################### import os import hashlib import time from qtpy.QtWidgets import QWidget, QTextEdit, QTableWidget, QTableWidgetItem, QVBoxLayout from qtpy.QtCore import QFile, Property import qtpy.QtWidgets as QtWidgets from qtpy.QtGui import QColor from qtvcp.widgets.widget_baseclass import _HalWidgetBase from qtvcp.core import Status, Info, Action from qtvcp import logger # Instiniate the libraries with global reference # STATUS gives us status messages from linuxcnc # AUX_PRGM holds helper program loader # INI holds ini details # ACTION gives commands to linuxcnc # LOG is for running code logging STATUS = Status() INFO = Info() ACTION = Action() LOG = logger.getLogger(__name__) class MachineLog(QWidget, _HalWidgetBase): def __init__(self, parent=None): super(MachineLog, self).__init__(parent) self._delay = 0 self._hash_code = None self._log_on = True self._machine_log = True self._machine_log_severity = False self._integrator_log = False self._critical_fg_color = QColor(255, 255, 255) self._critical_bg_color = QColor(255, 0, 0) self._error_fg_color = QColor(255, 0, 0) self._error_bg_color = QColor(255,255,255) self._warning_fg_color = QColor(255, 255, 0) self._warning_bg_color = QColor(0, 0, 0) self._debug_fg_color = QColor(128, 128, 128) self._debug_bg_color = QColor(255, 255, 255) self._info_fg_color = QColor(0, 0, 0) self._info_bg_color = QColor(255, 255, 255) self.integratorPath = os.path.expanduser(INFO.QTVCP_LOG_HISTORY_PATH) self.machineLogPath = os.path.expanduser(INFO.MACHINE_LOG_HISTORY_PATH) self.logText = QTextEdit(self) self.logTable = QTableWidget(self) layout = QVBoxLayout(self) layout.addWidget(self.logText) layout.addWidget(self.logTable) self.setLayout(layout) self.logTable.setColumnCount(3) self.logTable.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.logTable.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.logTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.logTable.setShowGrid(False) self.logTable.setSortingEnabled(False) horizontalHeader = self.logTable.horizontalHeader() horizontalHeader.resizeSection(0, 100) horizontalHeader.resizeSection(1, 100) horizontalHeader.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) horizontalHeader.setVisible(False) verticalHeader = self.logTable.verticalHeader() verticalHeader.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) verticalHeader.setVisible(False) def _hal_init(self): if self._machine_log: STATUS.connect('machine-log-changed',lambda w: self.loadLog()) self.logTable.hide() self.logText.show() elif self._machine_log_severity: STATUS.connect('update-machine-log', lambda w, d, o: self.updateMachineLog(d, o)) self.logTable.show() self.logText.hide() elif self._integrator_log: STATUS.connect('periodic', self._periodicCheck) self.logTable.hide() self.logText.show() def _periodicCheck(self, w): if self._delay < 9: self._delay += 1 return if STATUS.is_status_valid() == False: return self._delay = 0 m1 = self.md5sum(self.integratorPath) if m1 and self._hash_code != m1: self._hash_code = m1 self.loadIntegratorLog() # create a hash code def md5sum(self,filename): try: f = open(filename, "rb") except: return None else: return hashlib.md5(f.read()).hexdigest() def remove_options(self, option, to_remove): options = option.split(',') options = [opt for opt in options if opt not in to_remove] return ','.join(options) def updateMachineLog(self, message, option): if option == 'DELETE': self.clear() return if option == 'ON': self._log_on = True return if option == 'OFF': self._log_on = False return if self._log_on != True: return if message: if option is None: option = '' if 'DATE' in option: dateItem = QTableWidgetItem(time.strftime("%a, %b %d %Y %X")) else: dateItem = QTableWidgetItem(time.strftime("%H:%M:%S")) option = self.remove_options(option, ['DATE', 'TIME', 'INITIAL']) severityItem = QTableWidgetItem(option.strip()) msgItem = QTableWidgetItem(message.strip()) if 'DEBUG' in option: msgItem.setForeground(self._debug_fg_color) dateItem.setForeground(self._debug_fg_color) severityItem.setForeground(self._debug_fg_color) msgItem.setBackground(self._debug_bg_color) dateItem.setBackground(self._debug_bg_color) severityItem.setBackground(self._debug_bg_color) elif 'SUCCESS' in option: msgItem.setForeground(QColor(0, 128, 0)) dateItem.setForeground(QColor(0, 128, 0)) severityItem.setForeground(QColor(0, 128, 0)) elif 'WARNING' in option: msgItem.setForeground(self._warning_fg_color) dateItem.setForeground(self._warning_fg_color) severityItem.setForeground(self._warning_fg_color) msgItem.setBackground(self._warning_bg_color) dateItem.setBackground(self._warning_bg_color) severityItem.setBackground(self._warning_bg_color) if self.QTVCP_INSTANCE_.OPTIONS_.play_sounds: ACTION.PLAY_WARNING() elif 'ERROR' in option: msgItem.setForeground(self._error_fg_color) dateItem.setForeground(self._error_fg_color) severityItem.setForeground(self._error_fg_color) msgItem.setBackground(self._error_bg_color) dateItem.setBackground(self._error_bg_color) severityItem.setBackground(self._error_bg_color) if self.QTVCP_INSTANCE_.OPTIONS_.play_sounds: ACTION.PLAY_ATTENTION() elif 'CRITICAL' in option: msgItem.setForeground(self._critical_fg_color) dateItem.setForeground(self._critical_fg_color) severityItem.setForeground(self._critical_fg_color) msgItem.setBackground(self._critical_bg_color) dateItem.setBackground(self._critical_bg_color) severityItem.setBackground(self._critical_bg_color) if self.QTVCP_INSTANCE_.OPTIONS_.play_sounds: ACTION.PLAY_ERROR() else: msgItem.setForeground(self._info_fg_color) dateItem.setForeground(self._info_fg_color) severityItem.setForeground(self._info_fg_color) msgItem.setBackground(self._info_bg_color) dateItem.setBackground(self._info_bg_color) severityItem.setBackground(self._info_bg_color) self.logTable.insertRow(self.logTable.rowCount()) self.logTable.setItem(self.logTable.rowCount()-1, 0, dateItem) self.logTable.setItem(self.logTable.rowCount()-1, 1, severityItem) self.logTable.setItem(self.logTable.rowCount()-1, 2, msgItem) self.logTable.scrollToBottom() def loadLog(self): file = QFile(self.machineLogPath) file.open(QFile.ReadOnly) logText = file.readAll() file.close() self.logText.setPlainText(str(logText, encoding='utf8')) # scroll down to show last entry self.logText.verticalScrollBar().setSliderPosition(self.logText.verticalScrollBar().maximum()) def loadIntegratorLog(self): file = QFile(self.integratorPath) file.open(QFile.ReadOnly) logText = file.readAll() file.close() if str(logText, encoding='utf8') == "": self.logText.setPlainText('No Logging found. Is QtVcp in info, debugging or verbose mode (-i, -d or -v)?') return self.logText.setHtml(str(logText, encoding='utf8')) # scroll down to show last entry self.logText.verticalScrollBar().setSliderPosition(self.logText.verticalScrollBar().maximum()) def showEvent(self, ev): self.scrollToBottom() def scrollToBottom(self): # scroll down to show last entry self.logText.verticalScrollBar().setSliderPosition(self.logText.verticalScrollBar().maximum()) self.logTable.scrollToBottom() def hideCursor(self): self.logText.setCursorWidth(0) def getLogText(self): if self._machine_log_severity: return self.readTableData() else: return self.logText.toPlainText() def clear(self): self.logTable.clearContents() self.logTable.setRowCount(0) self.logText.setPlainText('') def readTableData(self): data = '' for row in range(self.logTable.rowCount()): row_data = [] for col in range(self.logTable.columnCount()): item = self.logTable.item(row, col).text() if col == 1: # pad the severity column for ease of reading row_data.append("{:^10}".format(item)) elif item != '': row_data.append(item) data += ' '.join(row_data) data +='\n' return data ################## properties ################### def _toggle_properties(self, picked): data = ('machine_log', 'machine_log_severity', 'integrator_log') for i in data: if not i == picked: self[i+'_option'] = False def set_machine_log(self, value): self._machine_log = value if value: self._toggle_properties('machine_log') def get_machine_log(self): return self._machine_log def reset_machine_log(self): self._machine_log = True machine_log_option = Property(bool, get_machine_log, set_machine_log, reset_machine_log) def set_machine_log_severity(self, value): self._machine_log_severity = value if value: self._toggle_properties('machine_log_severity') def get_machine_log_severity(self): return self._machine_log_severity def reset_machine_log_severity(self): self._machine_log_severity = False machine_log_severity_option = Property(bool, get_machine_log_severity, set_machine_log_severity, reset_machine_log_severity) def set_integrator_log(self, value): self._integrator_log = value if value: self._toggle_properties('integrator_log') def get_integrator_log(self): return self._integrator_log def reset_integrator_log(self): self._integrator_log = False integrator_log_option = Property(bool, get_integrator_log, set_integrator_log, reset_integrator_log) def set_critical_fg_color(self, value): self._critical_fg_color = value def get_critical_fg_color(self): return self._critical_fg_color def reset_critical_fg_color(self): self._critical_fg_color = QColor(255, 255, 255) critical_fg_color_option = Property(QColor, get_critical_fg_color, set_critical_fg_color, reset_critical_fg_color) def set_critical_bg_color(self, value): self._critical_bg_color = value def get_critical_bg_color(self): return self._critical_bg_color def reset_critical_bg_color(self): self._critical_bg_color = QColor(255, 0, 0) critical_bg_color_option = Property(QColor, get_critical_bg_color, set_critical_bg_color, reset_critical_bg_color) def set_error_fg_color(self, value): self._error_fg_color = value def get_error_fg_color(self): return self._error_fg_color def reset_error_fg_color(self): self._error_fg_color = QColor(255, 0, 0) error_fg_color_option = Property(QColor, get_error_fg_color, set_error_fg_color, reset_error_fg_color) def set_error_bg_color(self, value): self._error_bg_color = value def get_error_bg_color(self): return self._error_bg_color def reset_error_bg_color(self): self._error_bg_color = QColor(255, 255, 255) error_bg_color_option = Property(QColor, get_error_bg_color, set_error_bg_color, reset_error_bg_color) def set_warning_fg_color(self, value): self._warning_fg_color = value def get_warning_fg_color(self): return self._warning_fg_color def reset_warning_fg_color(self): self._warning_fg_color = QColor(255, 255, 0) warning_fg_color_option = Property(QColor, get_warning_fg_color, set_warning_fg_color, reset_warning_fg_color) def set_warning_bg_color(self, value): self._warning_bg_color = value def get_warning_bg_color(self): return self._warning_bg_color def reset_warning_bg_color(self): self._warning_bg_color = QColor(255, 255, 255) warning_bg_color_option = Property(QColor, get_warning_bg_color, set_warning_bg_color, reset_warning_bg_color) def set_debug_fg_color(self, value): self._debug_fg_color = value def get_debug_fg_color(self): return self._debug_fg_color def reset_debug_fg_color(self): self._debug_fg_color = QColor(128, 128, 128) debug_fg_color_option = Property(QColor, get_debug_fg_color, set_debug_fg_color, reset_debug_fg_color) def set_debug_bg_color(self, value): self._debug_bg_color = value def get_debug_bg_color(self): return self._debug_bg_color def reset_debug_bg_color(self): self._debug_bg_color = QColor(255, 255, 255) debug_bg_color_option = Property(QColor, get_debug_bg_color, set_debug_bg_color, reset_debug_bg_color) def set_info_fg_color(self, value): self._info_fg_color = value def get_info_fg_color(self): return self._info_fg_color def reset_info_fg_color(self): self._info_fg_color = QColor(0, 0, 0) info_fg_color_option = Property(QColor, get_info_fg_color, set_info_fg_color, reset_info_fg_color) def set_info_bg_color(self, value): self._info_bg_color = value def get_info_bg_color(self): return self._info_bg_color def reset_info_bg_color(self): self._info_bg_color = QColor(255, 255, 255) info_bg_color_option = Property(QColor, get_info_bg_color, set_info_bg_color, reset_info_bg_color) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # for testing without editor: def main(): import sys from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) widget = MachineLog() widget.show() sys.exit(app.exec()) if __name__ == "__main__": main()