diff --git a/README.md b/README.md index 5841602bc890de1008bfda4da24e9e40ee060d3a..b204b20589bce52fa787a72a0f975383870e56c3 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,9 @@ Afterwards it's recommended to designate different loggers for different parts o This will create "log namespaces" which allow you to filter out messages from various subsystems of your program. ## Planned features -* [ ] Indication that the connection has been closed * [ ] Presets for colors -* [ ] Presets for the logger header +* [ ] Presets for the logger header (with option to add columns for extra data) * [ ] Modify how rows are arranged in the detail table (like the header dialog) -* [ ] Improve the way existence of an exception is shown * [ ] Fix double-search on the last matched result (or indicate that the last result was reached) * [ ] Ability to save and load logs (as text or as full records) * [ ] Alarms/notifications triggered by specified messages diff --git a/cutelog/config.py b/cutelog/config.py index 462aa1aa70428ee6f74c5c42d017634837d05f69..12658bf89febccab1e14c7cef4d7bfe8ffd1f152 100644 --- a/cutelog/config.py +++ b/cutelog/config.py @@ -1,3 +1,4 @@ +import enum import logging import os import sys @@ -24,6 +25,15 @@ else: QT55_COMPAT = False +# Maybe there should be one common enum with all options instead of +# one enum for each thing? I guess I'll decide when there will be +# more than one thing in total. +class Exc_Indication(enum.IntEnum): + RED_BG = 0 + MSG_ICON = 1 + ICON_AND_RED_BG = 2 + + # There must be a better way to do this, right? Option = namedtuple('Option', ['name', 'type', 'default']) OPTION_SPEC = ( @@ -34,6 +44,7 @@ OPTION_SPEC = ( ('text_view_dialog_font', str, 'Courier New'), ('text_view_dialog_font_size', int, 12), ('logger_row_height', int, 20), + ('exception_indication', int, Exc_Indication.RED_BG), # Search ('search_open_default', bool, False), diff --git a/cutelog/logger_tab.py b/cutelog/logger_tab.py index ac9dfe1210c1032e519bc90fb87823b9847ea757..2255a44e5b5a64f18e59739953e1c2cc6b5ef3b7 100644 --- a/cutelog/logger_tab.py +++ b/cutelog/logger_tab.py @@ -7,11 +7,11 @@ from functools import partial from PyQt5 import uic from PyQt5.QtCore import (QAbstractItemModel, QAbstractTableModel, QEvent, QModelIndex, QSortFilterProxyModel, Qt) -from PyQt5.QtGui import QFont +from PyQt5.QtGui import QBrush, QColor, QFont from PyQt5.QtWidgets import (QCheckBox, QHBoxLayout, QMenu, QShortcut, QStyle, QTableWidgetItem, QWidget) -from .config import CONFIG +from .config import CONFIG, Exc_Indication from .level_edit_dialog import LevelEditDialog from .log_levels import LevelFilter, LogLevel from .logger_table_header import HeaderEditDialog, LoggerTableHeader @@ -152,6 +152,8 @@ class LogRecordModel(QAbstractTableModel): result = None record = self.records[index.row()] + if getattr(record, '_cutelog', False): + return self.data_internal(index, record, role) level = self.levels[record.levelno] if role == Qt.DisplayRole: @@ -160,7 +162,10 @@ class LogRecordModel(QAbstractTableModel): elif role == Qt.DecorationRole: if self.headerData(index.column()) == 'Message': if record.exc_text: - result = self.parent_widget.style().standardIcon(QStyle.SP_BrowserStop) + mode = CONFIG['exception_indication'] + should = mode in (Exc_Indication.MSG_ICON, Exc_Indication.ICON_AND_RED_BG) + if should: + result = self.parent_widget.style().standardIcon(QStyle.SP_BrowserStop) elif role == Qt.FontRole: result = None styles = level.styles if not self.dark_theme else level.stylesDark @@ -181,6 +186,16 @@ class LogRecordModel(QAbstractTableModel): else: result = level.fgDark elif role == Qt.BackgroundRole: + if record.exc_text: + mode = CONFIG['exception_indication'] + should = mode in (Exc_Indication.RED_BG, Exc_Indication.ICON_AND_RED_BG) + if should: + if not self.dark_theme: + color = QColor(255, 180, 180) + else: + color = Qt.darkRed + result = QBrush(color, Qt.DiagCrossPattern) + return result if not self.dark_theme: result = level.bg else: @@ -189,20 +204,45 @@ class LogRecordModel(QAbstractTableModel): result = record.message return result + def data_internal(self, index, record, role): + result = None + if role == Qt.DisplayRole: + if index.column() == self.columnCount(INVALID_INDEX) - 1: # if last column + result = record._cutelog + else: + column = self.table_header[index.column()] + if column.name == 'asctime': + result = record.asctime + elif role == Qt.FontRole: + result = QFont(CONFIG.logger_table_font, CONFIG.logger_table_font_size) + elif role == Qt.ForegroundRole: + if not self.dark_theme: + result = QColor(Qt.black) + else: + result = QColor(Qt.white) + elif role == Qt.BackgroundRole: + if not self.dark_theme: + color = QColor(Qt.lightGray) + else: + color = QColor(Qt.darkGray) + result = QBrush(color, Qt.BDiagPattern) + return result + def headerData(self, section, orientation=Qt.Horizontal, role=Qt.DisplayRole): result = None if orientation == Qt.Horizontal and role == Qt.DisplayRole: result = self.table_header[section].title return result - def add_record(self, record): - self.trim_if_needed() + def add_record(self, record, internal=False): + if not internal: + self.trim_if_needed() + self.date_formatter.format(record) row = len(self.records) + self.beginInsertRows(INVALID_INDEX, row, row) - self.date_formatter.format(record) self.records.append(record) self.endInsertRows() - return row def trim_except_last_n(self, n): from itertools import islice @@ -360,7 +400,7 @@ class LoggerTab(*LoggerTabBase): self.main_window = main_window self.loop = loop self.level_filter = LevelFilter() - self.level_filter.set_all_pass(False) + self.level_filter.set_all_pass(True) self.filter_model_enabled = True self.detail_model = DetailTableModel() self.namespace_tree_model = LogNamespaceTreeModel() @@ -566,7 +606,13 @@ class LoggerTab(*LoggerTabBase): self.register_logger(record.name) self.record_count += 1 self.monitor_count += 1 - # self.loggerTable.resizeRowsToContents() + + def add_conn_closed_record(self, conn): + d = {'_cutelog': 'Connection "{}" closed'.format(conn.name)} + record = logging.makeLogRecord(d) + self.record_model.add_record(record) + if self.autoscroll: + self.loggerTable.scrollToBottom() def get_record(self, index): if self.filter_model_enabled: @@ -797,6 +843,7 @@ class LoggerTab(*LoggerTabBase): def remove_connection(self, connection): self.log.debug('Removing connection "{}"'.format(connection)) self.connections.remove(connection) + self.add_conn_closed_record(connection) def stop_all_connections(self): for conn in self.connections: diff --git a/cutelog/resources/ui/settings_dialog.ui b/cutelog/resources/ui/settings_dialog.ui index 6fbd967de045e5d386b20a8dbcadf67e5f7f8f39..be5577d6431c2dfcc33d9a680dc01d73dabbb2e0 100644 --- a/cutelog/resources/ui/settings_dialog.ui +++ b/cutelog/resources/ui/settings_dialog.ui @@ -83,13 +83,6 @@ <property name="spacing"> <number>10</number> </property> - <item row="1" column="2"> - <widget class="QSpinBox" name="loggerTableFontSize"> - <property name="minimum"> - <number>1</number> - </property> - </widget> - </item> <item row="0" column="1"> <widget class="QCheckBox" name="darkThemeDefaultCheckBox"> <property name="text"> @@ -100,13 +93,6 @@ <item row="2" column="1"> <widget class="QFontComboBox" name="textViewFont"/> </item> - <item row="2" column="2"> - <widget class="QSpinBox" name="textViewFontSize"> - <property name="minimum"> - <number>1</number> - </property> - </widget> - </item> <item row="1" column="0"> <widget class="QLabel" name="label_9"> <property name="text"> @@ -148,7 +134,7 @@ </property> </widget> </item> - <item row="4" column="1"> + <item row="5" column="1"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -161,6 +147,46 @@ </property> </spacer> </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Indicate exception with</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSpinBox" name="loggerTableFontSize"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QSpinBox" name="textViewFontSize"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QComboBox" name="excIndicationComboBox"> + <item> + <property name="text"> + <string>Red crossed background</string> + </property> + </item> + <item> + <property name="text"> + <string>Cross icon in the message column</string> + </property> + </item> + <item> + <property name="text"> + <string>Both</string> + </property> + </item> + </widget> + </item> </layout> </widget> </widget> diff --git a/cutelog/settings_dialog.py b/cutelog/settings_dialog.py index 0c007638e88e56c24334393ae38a346cb4ee83ad..fedc1433359820dfaeca71ad04012b5443e19a29 100644 --- a/cutelog/settings_dialog.py +++ b/cutelog/settings_dialog.py @@ -50,6 +50,7 @@ class SettingsDialog(*SettingsDialogBase): self.textViewFont.setCurrentFont(QFont(CONFIG['text_view_dialog_font'])) self.textViewFontSize.setValue(CONFIG['text_view_dialog_font_size']) self.loggerTableRowHeight.setValue(CONFIG['logger_row_height']) + self.excIndicationComboBox.setCurrentIndex(CONFIG['exception_indication']) # Search self.searchOpenDefaultCheckBox.setChecked(CONFIG['search_open_default']) @@ -74,9 +75,6 @@ class SettingsDialog(*SettingsDialogBase): self.lightThemeNativeCheckBox.setChecked(CONFIG['light_theme_is_native']) self.server_restart_needed = False - def server_options_changed(self): - self.server_restart_needed = True - def save_to_config(self): o = {} # Appearance @@ -85,6 +83,7 @@ class SettingsDialog(*SettingsDialogBase): o['logger_table_font_size'] = self.loggerTableFontSize.value() o['text_view_dialog_font'] = self.textViewFont.currentFont().family() o['text_view_dialog_font_size'] = self.textViewFontSize.value() + o['exception_indication'] = self.excIndicationComboBox.currentIndex() new_row_height = self.loggerTableRowHeight.value() if new_row_height != CONFIG['logger_row_height']: # to prevent resizing unnecessarily o['logger_row_height'] = new_row_height @@ -120,6 +119,9 @@ class SettingsDialog(*SettingsDialogBase): # print('rejecting') self.done(0) + def server_options_changed(self): + self.server_restart_needed = True + def display_warning(self): m = QMessageBox(self.parent_widget) m.setText('You need to restart the server for the changes to take effect') diff --git a/setup.py b/setup.py index 4feb7aebc4d32e87c46eb027262370fa7b6acbaa..f6e10592234e6ebe2b818b0e32af7b825a17b59d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup from setuptools.command.install import install -VERSION = '1.1.3' +VERSION = '1.1.4' def build_qt_resources():