Compare commits

...

6 Commits

Author SHA1 Message Date
8ce0c1bf9e switch direction of the range start/end icons
With the old direction they were overlapping each other,
which made it impossible to move the end slider.
2025-03-24 20:07:35 +01:00
04a0310eee bump copyright year 2025-03-24 19:37:37 +01:00
3a2aa4f109 get version from git tag 2025-03-24 19:36:43 +01:00
9902be0a48 feature: follow the file and show always the end when new data comes 2025-03-24 19:20:49 +01:00
61132d242f fix: graphemes are not correctly highlighted
Graphemes don't all have the same width, not even when you use a monospace font.
For latin characters it usually works find to assume the same width. But emojis,
japanese or chinese characters have have different width. There are even some
ultra wide characters like 𒐫 or ﷽. There is also a thing
called 'half-width' character. E.g. the japanese 'a' can be ア or ア.

Fixed by actually computing the width of graphemes and using pixel.
2025-03-24 17:49:27 +01:00
21b2da1e69 update pyside and pyinstaller 2025-03-23 20:54:03 +01:00
19 changed files with 323 additions and 173 deletions

View File

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 16 16">
<path style="fill: #dddddd; stroke: black; stroke-linejoin:round;" <path style="fill: #dddddd; stroke: black; stroke-linejoin:round;"
d="M8,13 d="M8,3
L1,3 L1,13
L15,3 L15,13
z z
"/> "/>
<line x1="0.5" y1="13.5" x2="15.5" y2="13.5" style="stroke:black;"/> <line x1="0.5" y1="2.5" x2="15.5" y2="2.5" style="stroke:black;"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 287 B

After

Width:  |  Height:  |  Size: 286 B

View File

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 16 16">
<path style="fill: #dddddd; stroke: black; stroke-linejoin:round;" <path style="fill: #dddddd; stroke: black; stroke-linejoin:round;"
d="M8,3 d="M8,13
L1,13 L1,3
L15,13 L15,3
z z
"/> "/>
<line x1="0.5" y1="2.5" x2="15.5" y2="2.5" style="stroke:black;"/> <line x1="0.5" y1="13.5" x2="15.5" y2="13.5" style="stroke:black;"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -10,8 +10,10 @@ from src import install
from src.pluginregistry import PluginRegistry from src.pluginregistry import PluginRegistry
import gettext import gettext
from src.ui.icon import Icon from src.ui.icon import Icon
from pathlib import Path
import os
__version__ = '0.2.1' __version__ = Path(os.path.dirname(os.path.realpath(__file__)) + os.sep + "version.txt").read_text()
gettext.install('krowlog', 'locale') gettext.install('krowlog', 'locale')

View File

@@ -5,8 +5,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: RavenLog\n" "Project-Id-Version: RavenLog\n"
"POT-Creation-Date: 2024-03-24 12:00+0100\n" "POT-Creation-Date: 2025-03-24 19:02+0100\n"
"PO-Revision-Date: 2024-03-24 12:02+0100\n" "PO-Revision-Date: 2025-03-24 19:17+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: de\n" "Language: de\n"
@@ -15,10 +15,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.0.1\n" "X-Generator: Poedit 3.5\n"
#: src/mainwindow.py:32 src/plugins/krowlog/aboutdialog.py:30 #: src/mainwindow.py:32 src/new_big_text/bigger_text.py:75
#: src/plugins/krowlogplugin.py:85 #: src/plugins/krowlog/aboutdialog.py:30 src/plugins/krowlogplugin.py:85
msgid "KrowLog" msgid "KrowLog"
msgstr "KrowLog" msgstr "KrowLog"
@@ -146,31 +146,31 @@ msgstr "&Über KrowLog"
msgid "E&xit" msgid "E&xit"
msgstr "&Beenden" msgstr "&Beenden"
#: src/plugins/logfile/filterwidget.py:181 #: src/plugins/logfile/filterwidget.py:192
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: src/plugins/logfile/filterwidget.py:187 #: src/plugins/logfile/filterwidget.py:198
msgid "save query" msgid "save query"
msgstr "suche speichern" msgstr "suche speichern"
#: src/plugins/logfile/filterwidget.py:192 #: src/plugins/logfile/filterwidget.py:203
msgid "ignore case" msgid "ignore case"
msgstr "Groß-/Kleinschreibung ignorieren" msgstr "Groß-/Kleinschreibung ignorieren"
#: src/plugins/logfile/filterwidget.py:196 #: src/plugins/logfile/filterwidget.py:207
msgid "regex" msgid "regex"
msgstr "RegExp" msgstr "RegExp"
#: src/plugins/logfile/filterwidget.py:206 #: src/plugins/logfile/filterwidget.py:217
msgid "only matches" msgid "only matches"
msgstr "nur Treffer" msgstr "nur Treffer"
#: src/plugins/logfile/filterwidget.py:298 #: src/plugins/logfile/filterwidget.py:309
msgid "({hits} lines)" msgid "({hits} lines)"
msgstr "({hits} Zeilen)" msgstr "({hits} Zeilen)"
#: src/plugins/logfileplugin.py:35 src/ui/bigtext/bigtext.py:258 #: src/plugins/logfileplugin.py:35 src/ui/bigtext/bigtext.py:263
msgid "&Highlighter" msgid "&Highlighter"
msgstr "&Hervorhebungen" msgstr "&Hervorhebungen"
@@ -200,65 +200,69 @@ msgstr "Öffne Datei"
#: src/plugins/timediff/time_diff_menu_widget.py:32 #: src/plugins/timediff/time_diff_menu_widget.py:32
msgid "ms" msgid "ms"
msgstr "" msgstr "ms"
#: src/plugins/timediff/time_diff_menu_widget.py:33 #: src/plugins/timediff/time_diff_menu_widget.py:33
msgid "s" msgid "s"
msgstr "" msgstr "s"
#: src/plugins/timediff/time_diff_menu_widget.py:34 #: src/plugins/timediff/time_diff_menu_widget.py:34
msgid "m" msgid "m"
msgstr "" msgstr "m"
#: src/plugins/timediff/time_diff_menu_widget.py:35 #: src/plugins/timediff/time_diff_menu_widget.py:35
msgid "h" msgid "h"
msgstr "" msgstr "h"
#: src/ui/bigtext/bigtext.py:238 #: src/ui/bigtext/bigtext.py:243
msgid "&Copy to Clipboard" msgid "&Copy to Clipboard"
msgstr "In Zwischenablage &Kopieren" msgstr "In Zwischenablage &Kopieren"
#: src/ui/bigtext/bigtext.py:246 #: src/ui/bigtext/bigtext.py:251
msgid "Copy to &File" msgid "Copy to &File"
msgstr "In &Datei Kopieren" msgstr "In &Datei Kopieren"
#: src/ui/bigtext/bigtext.py:252 #: src/ui/bigtext/bigtext.py:257
msgid "Select &All" msgid "Select &All"
msgstr "&Alles Selektieren" msgstr "&Alles Selektieren"
#: src/ui/bigtext/bigtext.py:268 #: src/ui/bigtext/bigtext.py:270
msgid "&Follow"
msgstr "&Folgen"
#: src/ui/bigtext/bigtext.py:280
msgid "Set Range Start" msgid "Set Range Start"
msgstr "Setze Start des Anzeigebereichs" msgstr "Setze Start des Anzeigebereichs"
#: src/ui/bigtext/bigtext.py:276 #: src/ui/bigtext/bigtext.py:288
msgid "Set Range End" msgid "Set Range End"
msgstr "Setze Ende des Anzeigebereichs" msgstr "Setze Ende des Anzeigebereichs"
#: src/ui/bigtext/bigtext.py:283 #: src/ui/bigtext/bigtext.py:295
msgid "Reset Range" msgid "Reset Range"
msgstr "Anzeigebereich Zurücksetzen" msgstr "Anzeigebereich Zurücksetzen"
#: src/ui/bigtext/bigtext.py:458 #: src/ui/bigtext/bigtext.py:526
msgid "warning" msgid "warning"
msgstr "Achtung" msgstr "Achtung"
#: src/ui/bigtext/bigtext.py:459 #: src/ui/bigtext/bigtext.py:527
msgid "You have selected <b>{0}</b> of data." msgid "You have selected <b>{0}</b> of data."
msgstr "Du hast <b>{0}</b> selektiert." msgstr "Du hast <b>{0}</b> selektiert."
#: src/ui/bigtext/bigtext.py:464 #: src/ui/bigtext/bigtext.py:532
msgid "Copy {0} to Clipboard" msgid "Copy {0} to Clipboard"
msgstr "Kopiere {0} in die Zwischenablage" msgstr "Kopiere {0} in die Zwischenablage"
#: src/ui/bigtext/bigtext.py:468 #: src/ui/bigtext/bigtext.py:536
msgid "Write to File" msgid "Write to File"
msgstr "Schreibe in Datei" msgstr "Schreibe in Datei"
#: src/ui/bigtext/bigtext.py:492 #: src/ui/bigtext/bigtext.py:560
msgid "Save File" msgid "Save File"
msgstr "Speichere Datei" msgstr "Speichere Datei"
#: src/ui/bigtext/bigtext.py:529 #: src/ui/bigtext/bigtext.py:605
msgid "selected {0} - {1:,.0f}:{2:,.0f}" msgid "selected {0} - {1:,.0f}:{2:,.0f}"
msgstr "selektiert {0} - {1:,.0f}:{2:,.0f}" msgstr "selektiert {0} - {1:,.0f}:{2:,.0f}"
@@ -306,6 +310,10 @@ msgstr "Zeilenhintergrund"
msgid "Regular Expression" msgid "Regular Expression"
msgstr "Regulärer Ausdruck" msgstr "Regulärer Ausdruck"
#: src/ui/bigtext/newhighlightingdialog.py:185
msgid "File Type:"
msgstr "Dateityp:"
#: src/ui/colorbutton.py:20 #: src/ui/colorbutton.py:20
msgid "Strawberry Cream" msgid "Strawberry Cream"
msgstr "Strawberry Cream" msgstr "Strawberry Cream"
@@ -370,7 +378,7 @@ msgstr "Grau"
msgid "transparent" msgid "transparent"
msgstr "Transparent" msgstr "Transparent"
#: src/ui/rangeslider.py:180 #: src/ui/rangeslider.py:190
msgid "showing bytes {0} to {1} ({2})" msgid "showing bytes {0} to {1} ({2})"
msgstr "Anzeigebereich: Bytes {0} bis {1} ({2})" msgstr "Anzeigebereich: Bytes {0} bis {1} ({2})"

View File

@@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-03-24 12:00+0100\n" "POT-Creation-Date: 2025-03-24 19:02+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -15,8 +15,8 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
#: src/mainwindow.py:32 src/plugins/krowlog/aboutdialog.py:30 #: src/mainwindow.py:32 src/new_big_text/bigger_text.py:75
#: src/plugins/krowlogplugin.py:85 #: src/plugins/krowlog/aboutdialog.py:30 src/plugins/krowlogplugin.py:85
msgid "KrowLog" msgid "KrowLog"
msgstr "" msgstr ""
@@ -140,31 +140,31 @@ msgstr ""
msgid "E&xit" msgid "E&xit"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:181 #: src/plugins/logfile/filterwidget.py:192
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:187 #: src/plugins/logfile/filterwidget.py:198
msgid "save query" msgid "save query"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:192 #: src/plugins/logfile/filterwidget.py:203
msgid "ignore case" msgid "ignore case"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:196 #: src/plugins/logfile/filterwidget.py:207
msgid "regex" msgid "regex"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:206 #: src/plugins/logfile/filterwidget.py:217
msgid "only matches" msgid "only matches"
msgstr "" msgstr ""
#: src/plugins/logfile/filterwidget.py:298 #: src/plugins/logfile/filterwidget.py:309
msgid "({hits} lines)" msgid "({hits} lines)"
msgstr "" msgstr ""
#: src/plugins/logfileplugin.py:35 src/ui/bigtext/bigtext.py:258 #: src/plugins/logfileplugin.py:35 src/ui/bigtext/bigtext.py:263
msgid "&Highlighter" msgid "&Highlighter"
msgstr "" msgstr ""
@@ -208,51 +208,55 @@ msgstr ""
msgid "h" msgid "h"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:238 #: src/ui/bigtext/bigtext.py:243
msgid "&Copy to Clipboard" msgid "&Copy to Clipboard"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:246 #: src/ui/bigtext/bigtext.py:251
msgid "Copy to &File" msgid "Copy to &File"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:252 #: src/ui/bigtext/bigtext.py:257
msgid "Select &All" msgid "Select &All"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:268 #: src/ui/bigtext/bigtext.py:270
msgid "&Follow"
msgstr ""
#: src/ui/bigtext/bigtext.py:280
msgid "Set Range Start" msgid "Set Range Start"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:276 #: src/ui/bigtext/bigtext.py:288
msgid "Set Range End" msgid "Set Range End"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:283 #: src/ui/bigtext/bigtext.py:295
msgid "Reset Range" msgid "Reset Range"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:458 #: src/ui/bigtext/bigtext.py:526
msgid "warning" msgid "warning"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:459 #: src/ui/bigtext/bigtext.py:527
msgid "You have selected <b>{0}</b> of data." msgid "You have selected <b>{0}</b> of data."
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:464 #: src/ui/bigtext/bigtext.py:532
msgid "Copy {0} to Clipboard" msgid "Copy {0} to Clipboard"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:468 #: src/ui/bigtext/bigtext.py:536
msgid "Write to File" msgid "Write to File"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:492 #: src/ui/bigtext/bigtext.py:560
msgid "Save File" msgid "Save File"
msgstr "" msgstr ""
#: src/ui/bigtext/bigtext.py:529 #: src/ui/bigtext/bigtext.py:605
msgid "selected {0} - {1:,.0f}:{2:,.0f}" msgid "selected {0} - {1:,.0f}:{2:,.0f}"
msgstr "" msgstr ""
@@ -300,6 +304,10 @@ msgstr ""
msgid "Regular Expression" msgid "Regular Expression"
msgstr "" msgstr ""
#: src/ui/bigtext/newhighlightingdialog.py:185
msgid "File Type:"
msgstr ""
#: src/ui/colorbutton.py:20 #: src/ui/colorbutton.py:20
msgid "Strawberry Cream" msgid "Strawberry Cream"
msgstr "" msgstr ""
@@ -364,7 +372,7 @@ msgstr ""
msgid "transparent" msgid "transparent"
msgstr "" msgstr ""
#: src/ui/rangeslider.py:180 #: src/ui/rangeslider.py:190
msgid "showing bytes {0} to {1} ({2})" msgid "showing bytes {0} to {1} ({2})"
msgstr "" msgstr ""

View File

@@ -1,6 +1,8 @@
import PyInstaller.__main__ import PyInstaller.__main__
import os import os
os.system("git -C . describe --match \"*.*.*\" --tags > version.txt")
PyInstaller.__main__.run([ PyInstaller.__main__.run([
'krowlog.py', 'krowlog.py',
# '--onefile', # '--onefile',
@@ -12,6 +14,7 @@ PyInstaller.__main__.run([
'--add-binary', 'icons' + os.pathsep + 'icons', '--add-binary', 'icons' + os.pathsep + 'icons',
'--add-binary', 'locales' + os.pathsep + 'locales', '--add-binary', 'locales' + os.pathsep + 'locales',
'--add-binary', 'LICENSE' + os.pathsep + '.', '--add-binary', 'LICENSE' + os.pathsep + '.',
'--add-binary', 'version.txt' + os.pathsep + '.',
'--hidden-import=krowlog', '--hidden-import=krowlog',
'--hidden-import=watchdog', '--hidden-import=watchdog',
'--hidden-import=watchdog.observers', '--hidden-import=watchdog.observers',

View File

@@ -1,5 +1,5 @@
pip==24.0 pip==25.0.1
PySide6_Essentials==6.7.0 PySide6_Essentials==6.8.2.1
setuptools==69.5.1 setuptools==77.0.3
watchdog==4.0.0 watchdog==6.0.0
pyinstaller==6.6.0 pyinstaller==6.12.0

View File

@@ -63,7 +63,7 @@ class AboutDialog(QDialog):
result.layout = QVBoxLayout(result) result.layout = QVBoxLayout(result)
label = Label("{0}<br>{1}<br>{2}".format( label = Label("{0}<br>{1}<br>{2}".format(
_("KrowLog is a viewer for log files of arbitrary size."), _("KrowLog is a viewer for log files of arbitrary size."),
_("(c) 2022-2024 Andreas Huber"), _("(c) 2022-2025 Andreas Huber"),
_("License: LGPL v3") _("License: LGPL v3")
)) ))
result.layout.addWidget(label) result.layout.addWidget(label)

View File

@@ -237,7 +237,7 @@ class FilterWidget(QWidget):
(handle, self.tmp_filename) = tempfile.mkstemp() (handle, self.tmp_filename) = tempfile.mkstemp()
os.close(handle) os.close(handle)
self.filter_model = LogFileModel(self.tmp_filename, self.source_model.settings, source_model.get_file()) self.filter_model = LogFileModel(self.tmp_filename, self.source_model.settings, source_model.get_file())
self.hits_view = BigText(self.filter_model, show_range_slider=False) self.hits_view = BigText(self.filter_model, show_range_slider=False, show_follow_action=False)
self.layout.addWidget(filter_bar) self.layout.addWidget(filter_bar)
self.layout.addWidget(self.hits_view) self.layout.addWidget(self.hits_view)

View File

@@ -14,6 +14,9 @@ class BigScrollBar(QWidget):
code involved. We work around this by converting the python int code involved. We work around this by converting the python int
into a string.""" into a string."""
user_interaction = Signal()
"""signals that the user changed a value (the opposite is that some code changed the value) """
class ScrollEvent(enum.IntEnum): class ScrollEvent(enum.IntEnum):
PageUp = 1 PageUp = 1
PageDown = 2 PageDown = 2
@@ -138,21 +141,27 @@ class BigScrollBar(QWidget):
trigger_repeat_action = True trigger_repeat_action = True
match self.repeat_action_control: match self.repeat_action_control:
case QStyle.SubControl.SC_ScrollBarAddPage: case QStyle.SubControl.SC_ScrollBarAddPage:
self.user_interaction.emit()
self.scroll_event.emit(self.ScrollEvent.PageDown) self.scroll_event.emit(self.ScrollEvent.PageDown)
case QStyle.SubControl.SC_ScrollBarSubPage: case QStyle.SubControl.SC_ScrollBarSubPage:
self.user_interaction.emit()
self.scroll_event.emit(self.ScrollEvent.PageUp) self.scroll_event.emit(self.ScrollEvent.PageUp)
if self.value <= self.minimum: if self.value <= self.minimum:
trigger_repeat_action = False trigger_repeat_action = False
case QStyle.SubControl.SC_ScrollBarAddLine: case QStyle.SubControl.SC_ScrollBarAddLine:
self.user_interaction.emit()
self.scroll_event.emit(self.ScrollEvent.LinesDown) self.scroll_event.emit(self.ScrollEvent.LinesDown)
case QStyle.SubControl.SC_ScrollBarSubLine: case QStyle.SubControl.SC_ScrollBarSubLine:
self.user_interaction.emit()
self.scroll_event.emit(self.ScrollEvent.LinesUp) self.scroll_event.emit(self.ScrollEvent.LinesUp)
if self.value <= self.minimum: if self.value <= self.minimum:
trigger_repeat_action = False trigger_repeat_action = False
case QStyle.SubControl.SC_ScrollBarFirst: case QStyle.SubControl.SC_ScrollBarFirst:
self.user_interaction.emit()
self.set_value(self.minimum) self.set_value(self.minimum)
trigger_repeat_action = False trigger_repeat_action = False
case QStyle.SubControl.SC_ScrollBarLast: case QStyle.SubControl.SC_ScrollBarLast:
self.user_interaction.emit()
self.set_value(self.maximum) self.set_value(self.maximum)
trigger_repeat_action = False trigger_repeat_action = False
case _: case _:
@@ -180,6 +189,7 @@ class BigScrollBar(QWidget):
if not r.contains(event.position().toPoint()): if not r.contains(event.position().toPoint()):
new_position = self.snap_back_position new_position = self.snap_back_position
self.user_interaction.emit()
# print(f"move to value: {new_position}") # print(f"move to value: {new_position}")
self.set_value(new_position) self.set_value(new_position)
@@ -227,7 +237,10 @@ class BigScrollBar(QWidget):
self.repeat_action_timer.stop() self.repeat_action_timer.stop()
#self.update() #self.update()
def set_value(self, value: int): def set_value(self, value: int, emit_change_event=True):
changed = self.value != value
self.value = value self.value = value
self.value_changed.emit(str(self.value)) if emit_change_event and changed:
# print(f"emitting value changed: {self.value}")
self.value_changed.emit(str(self.value))
self.update() self.update()

View File

@@ -18,6 +18,7 @@ from src.ui.bigtext.highlighted_range import HighlightedRange
from src.ui.bigtext.line import Line from src.ui.bigtext.line import Line
from src.ui.bigtext.logFileModel import LogFileModel from src.ui.bigtext.logFileModel import LogFileModel
from src.ui.bigtext.newhighlightingdialog import NewHighlightingDialog from src.ui.bigtext.newhighlightingdialog import NewHighlightingDialog
from src.ui.bigtext.selectionPos import SelectionPos
from src.ui.icon import Icon from src.ui.icon import Icon
from src.ui.rangeslider import RangeSlider from src.ui.rangeslider import RangeSlider
from src.util.conversion import humanbytes from src.util.conversion import humanbytes
@@ -30,6 +31,7 @@ from src.i18n import _
log = logging.getLogger("bigtext") log = logging.getLogger("bigtext")
class FileObserver(FileSystemEventHandler): class FileObserver(FileSystemEventHandler):
def __init__(self, big_text): def __init__(self, big_text):
@@ -75,10 +77,11 @@ class FileWatchdogThread(QRunnable):
class BigText(QWidget): class BigText(QWidget):
trigger_update = Signal() trigger_update = Signal()
def __init__(self, model: LogFileModel, show_range_slider=True): def __init__(self, model: LogFileModel, show_range_slider=True, show_follow_action=True):
super(BigText, self).__init__() super(BigText, self).__init__()
self.show_range_slider = show_range_slider self.show_range_slider = show_range_slider
self.show_follow_action = show_follow_action
self.model = model self.model = model
self.grid = QGridLayout() self.grid = QGridLayout()
@@ -100,6 +103,7 @@ class BigText(QWidget):
# self.v_scroll_bar.setPageStep(1) # self.v_scroll_bar.setPageStep(1)
self.v_scroll_bar.value_changed.connect(self.big_text.v_scroll_value_changed) self.v_scroll_bar.value_changed.connect(self.big_text.v_scroll_value_changed)
self.v_scroll_bar.scroll_event.connect(self.big_text.v_scroll_event) self.v_scroll_bar.scroll_event.connect(self.big_text.v_scroll_event)
self.v_scroll_bar.user_interaction.connect(self.big_text.user_scroll_interaction)
if show_range_slider: if show_range_slider:
self.range_limit = RangeSlider() self.range_limit = RangeSlider()
@@ -145,13 +149,15 @@ class BigText(QWidget):
# noinspection PyArgumentList,PyTypeChecker # noinspection PyArgumentList,PyTypeChecker
class InnerBigText(QWidget): class InnerBigText(QWidget):
_byte_offset = 0 _byte_offset = 0
_left_offset = 0 # number of characters the horizontal scrollbar was moved to the right _left_offset = 0 # number of pixels the horizontal scrollbar was moved to the right
scroll_lines = 0 scroll_lines = 0
longest_line = 0 longest_line = 0
_range_start = 0 _range_start = 0
_range_end = -1 _range_end = -1
_follow = False
def __init__(self, parent: BigText, model: LogFileModel, v_scaled_scrollbar: ScaledScrollBar): def __init__(self, parent: BigText, model: LogFileModel, v_scaled_scrollbar: ScaledScrollBar):
super(InnerBigText, self).__init__() super(InnerBigText, self).__init__()
self.char_height = None self.char_height = None
@@ -260,6 +266,12 @@ class InnerBigText(QWidget):
manage_highlighting.setShortcut("CTRL+H") manage_highlighting.setShortcut("CTRL+H")
menu.addAction(manage_highlighting) menu.addAction(manage_highlighting)
if self.parent.show_follow_action:
follow = QAction(_("&Follow"), self, triggered=self._toggle_follow)
follow.setCheckable(True)
follow.setChecked(self._follow)
menu.addAction(follow)
if self.parent.show_range_slider: if self.parent.show_range_slider:
menu.addSeparator() menu.addSeparator()
@@ -307,8 +319,12 @@ class InnerBigText(QWidget):
self.parent.range_limit.set_range_start(0) self.parent.range_limit.set_range_start(0)
self.parent.range_limit.set_range_end(self.model.byte_count()) self.parent.range_limit.set_range_end(self.model.byte_count())
def user_scroll_interaction(self):
self._follow = False
def scroll_by_lines(self, scroll_lines: int): def scroll_by_lines(self, scroll_lines: int):
self.scroll_lines = scroll_lines self.scroll_lines = scroll_lines
self._follow = False
self.update() self.update()
self.parent.v_scroll_bar.set_value(self._byte_offset) self.parent.v_scroll_bar.set_value(self._byte_offset)
@@ -320,8 +336,8 @@ class InnerBigText(QWidget):
# noinspection PyTypeChecker # noinspection PyTypeChecker
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.ShiftModifier: if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.ShiftModifier:
offset = self.to_byte_offset(e) selection_pos = self.to_byte_offset(e)
self.selection_highlight.set_end_byte(offset) self.selection_highlight.set_end_byte(selection_pos)
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
return return
@@ -331,16 +347,16 @@ class InnerBigText(QWidget):
line_number = self.y_pos_to_line(e.pos().y()) line_number = self.y_pos_to_line(e.pos().y())
if line_number == self._last_double_click_line_number and line_number < len(self.lines): if line_number == self._last_double_click_line_number and line_number < len(self.lines):
line: Line = self.lines[line_number] line: Line = self.lines[line_number]
self.selection_highlight.set_start(line.byte_offset()) self.selection_highlight.set_start(SelectionPos(line.byte_offset(), True, 1))
self.selection_highlight.set_end_byte(line.byte_end()) self.selection_highlight.set_end_byte(SelectionPos(line.byte_end() - 1, False, 1))
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
return return
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier: if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier:
offset = self.to_byte_offset(e) selection_pos = self.to_byte_offset(e)
self.selection_highlight.set_start(offset) self.selection_highlight.set_start(selection_pos)
self.selection_highlight.set_end_byte(offset) self.selection_highlight.set_end_byte(selection_pos)
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
@@ -355,14 +371,17 @@ class InnerBigText(QWidget):
self._last_double_click_time = time.time() self._last_double_click_time = time.time()
self._last_double_click_line_number = self.y_pos_to_line(e.pos().y()) self._last_double_click_line_number = self.y_pos_to_line(e.pos().y())
offset = self.to_byte_offset(e) selection_pos = self.to_byte_offset(e)
(_word, start_byte, end_byte) = self.model.read_word_at(offset) (word, start_byte, end_byte) = self.model.read_word_at(selection_pos.pos())
if start_byte >= 0 and end_byte >= 0: if start_byte >= 0 and end_byte >= 0:
self.selection_highlight.set_start(start_byte) bytes_of_first_char = len(f"{word[0]}".encode("utf8"))
self.selection_highlight.set_end_byte(end_byte) self.selection_highlight.set_start(SelectionPos(start_byte, True, bytes_of_first_char))
bytes_of_last_char = len(f"{word[-1]}".encode("utf8"))
self.selection_highlight.set_end_byte(
SelectionPos(end_byte - bytes_of_last_char, False, bytes_of_last_char))
else: else:
self.selection_highlight.set_start(offset) self.selection_highlight.set_start(selection_pos)
self.selection_highlight.set_end_byte(offset) self.selection_highlight.set_end_byte(selection_pos)
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
@@ -372,10 +391,10 @@ class InnerBigText(QWidget):
if e.buttons() != Qt.MouseButton.LeftButton: if e.buttons() != Qt.MouseButton.LeftButton:
return return
current_byte = self.to_byte_offset(e) selection_pos = self.to_byte_offset(e)
if self.selection_highlight.end_byte != current_byte: if self.selection_highlight.max_byte() != selection_pos.pos():
self.selection_highlight.set_end_byte(current_byte) self.selection_highlight.set_end_byte(selection_pos)
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
# print("-> %s,%s" %(self._selection_start_byte, self._selection_end_byte)) # print("-> %s,%s" %(self._selection_start_byte, self._selection_end_byte))
@@ -386,10 +405,16 @@ class InnerBigText(QWidget):
self.scroll_by_lines(-1) self.scroll_by_lines(-1)
if line_number > int(self.lines_shown()): if line_number > int(self.lines_shown()):
self.scroll_by_lines(1) self.scroll_by_lines(1)
if column_in_line <= 1: # if column_in_line <= 1:
# self._left_offset = max(0, self._left_offset - 2)
# self.update()
if e.pos().x() <= 1:
self._left_offset = max(0, self._left_offset - 2) self._left_offset = max(0, self._left_offset - 2)
self.update() self.update()
if column_in_line + 1 >= self.columns_shown(): # if column_in_line + 1 >= self.columns_shown():
# self._left_offset = self._left_offset + 2
# self.update()
if e.pos().x() + 1 >= self.width():
self._left_offset = self._left_offset + 2 self._left_offset = self._left_offset + 2
self.update() self.update()
@@ -416,12 +441,13 @@ class InnerBigText(QWidget):
case BigScrollBar.ScrollEvent.PageDown: case BigScrollBar.ScrollEvent.PageDown:
self.scroll_by_lines(int(self.lines_shown()) - 1) self.scroll_by_lines(int(self.lines_shown()) - 1)
def update_longest_line(self, length: int): def update_longest_line(self, line: Line):
width_in_chars = self.width() / self.char_width
# print("width_in_chars: %d" % width_in_chars) # print("width_in_chars: %d" % width_in_chars)
if self.longest_line < length: text_width_in_px = line.get_width_in_px(self.font_metric);
self.longest_line = length if self.longest_line < text_width_in_px:
maximum = max(0, length - width_in_chars + 1) self.longest_line = text_width_in_px
maximum = max(0, text_width_in_px - self.width() + 1)
self.parent.h_scroll_bar.setMaximum(round(maximum)) self.parent.h_scroll_bar.setMaximum(round(maximum))
def y_pos_to_line(self, y: int) -> int: def y_pos_to_line(self, y: int) -> int:
@@ -436,29 +462,63 @@ class InnerBigText(QWidget):
def columns_shown(self) -> float: def columns_shown(self) -> float:
return self.width() / float(self.char_width) return self.width() / float(self.char_width)
def to_byte_offset(self, e: QMouseEvent) -> int: def to_byte_offset(self, e: QMouseEvent) -> SelectionPos:
x = e.pos().x() + self._left_offset
line_number = self.y_pos_to_line(e.pos().y()) line_number = self.y_pos_to_line(e.pos().y())
if line_number < len(self.lines): if line_number < len(self.lines):
line: Line = self.lines[line_number] line: Line = self.lines[line_number]
column_in_line = self.x_pos_to_column(e.pos().x()) + self._left_offset
column_in_line = min(column_in_line, line.length_in_columns()) # x was behind the last column of this line text: str = line.line()
char_in_line = line.column_to_char(column_in_line) text = text.replace("\n", "").replace("\r", "")
# print("%s in line %s column_in_line=%s" % (char_in_line, line_number, column_in_line))
byte_in_line = line.char_index_to_byte(char_in_line) elided_text: str = self.elided_text(text, x)
current_byte = line.byte_offset() + byte_in_line byte_offset = line.byte_offset() + len(elided_text.encode("utf8"))
# print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte))
left_x_offset = self.font_metric.horizontalAdvance((elided_text))
next_char = ""
pos_is_in_left_half = False
bytes_of_char = 0
if len(text) > len(elided_text): # has another character
next_char = text[len(elided_text)]
char_width = self.font_metric.horizontalAdvance(next_char)
pos_is_in_left_half = x < (left_x_offset + char_width / 2)
bytes_of_char = len(next_char.encode("utf8"))
else:
# print(f"{x} is after last char, elided_text={elided_text}")
# the position is after the last character / behind the end of the line
pass
# print(f"{x} -> {byte_offset} {'left' if pos_is_in_left_half else 'right'} bytes_of_char={bytes_of_char}")
return SelectionPos(byte_offset, pos_is_in_left_half, bytes_of_char)
# column_in_line = self.x_pos_to_column(e.pos().x()) + self._left_offset
# column_in_line = min(column_in_line, line.length_in_columns()) # x was behind the last column of this line
# char_in_line = line.column_to_char(column_in_line)
# # print("%s in line %s column_in_line=%s" % (char_in_line, line_number, column_in_line))
# byte_in_line = line.char_index_to_byte(char_in_line)
# current_byte = line.byte_offset() + byte_in_line
# # print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte))
else: else:
current_byte = self.model.byte_count() current_byte = self.model.byte_count()
return current_byte return current_byte
def elided_text(self, text: str, width: int):
w = width + self.font_metric.horizontalAdvance("")
elided_text = self.font_metric.elidedText(text + "", Qt.TextElideMode.ElideRight, w,
Qt.TextFlag.TextWrapAnywhere)
elided_text = elided_text[0:-1] if elided_text.endswith('') else elided_text # remove the trailing '…'
return elided_text
def _has_selection(self): def _has_selection(self):
return self.selection_highlight.start_byte != self.selection_highlight.end_byte return self.selection_highlight.min_byte() != self.selection_highlight.max_byte()
def copy_selection(self): def copy_selection(self):
if self._has_selection(): if self._has_selection():
start = min(self.selection_highlight.start_byte, self.selection_highlight.end_byte) start = self.selection_highlight.min_byte()
end = max(self.selection_highlight.start_byte, self.selection_highlight.end_byte) end = self.selection_highlight.max_byte()
bytes_human_readable = humanbytes(end - start) bytes_human_readable = humanbytes(end - start)
if end - start > (1024 ** 2) * 5: if end - start > (1024 ** 2) * 5:
you_sure = QMessageBox( you_sure = QMessageBox(
@@ -492,8 +552,8 @@ class InnerBigText(QWidget):
def _copy_selection_to_file(self): def _copy_selection_to_file(self):
if self._has_selection(): if self._has_selection():
start = min(self.selection_highlight.start_byte, self.selection_highlight.end_byte) start = self.selection_highlight.min_byte()
end = max(self.selection_highlight.start_byte, self.selection_highlight.end_byte) end = self.selection_highlight.max_byte()
dialog = QFileDialog(self) dialog = QFileDialog(self)
(selected_file, _filter) = dialog.getSaveFileName( (selected_file, _filter) = dialog.getSaveFileName(
parent=self, parent=self,
@@ -507,18 +567,26 @@ class InnerBigText(QWidget):
PluginRegistry.execute("open_file", selected_file) PluginRegistry.execute("open_file", selected_file)
def _select_all(self): def _select_all(self):
self.selection_highlight.start_byte = self.model.get_line_start_at(self._range_start) start_byte = self.model.get_line_start_at(self._range_start)
if self._range_end < 0 or self.model.byte_count() <= self._range_end: if self._range_end < 0 or self.model.byte_count() <= self._range_end:
self.selection_highlight.end_byte = self.model.byte_count() end_byte = self.model.byte_count()
else: else:
self.selection_highlight.end_byte = self.model.get_line_start_at(self._range_end) end_byte = self.model.get_line_start_at(self._range_end)
self.selection_highlight.set_start(SelectionPos(start_byte, True, 1))
self.selection_highlight.set_end_byte(SelectionPos(end_byte, False, 1))
self._update_highlight_selected_text() self._update_highlight_selected_text()
self.update() self.update()
def _toggle_follow(self):
self._follow = not self._follow
print(f"follow={self._follow}")
self.update()
def _update_highlight_selected_text(self): def _update_highlight_selected_text(self):
start_byte = min(self.selection_highlight.start_byte, self.selection_highlight.end_byte) start_byte = self.selection_highlight.min_byte()
end_byte = max(self.selection_highlight.start_byte, self.selection_highlight.end_byte) end_byte = self.selection_highlight.max_byte()
self._update_status_bar(start_byte, end_byte) self._update_status_bar(start_byte, end_byte)
@@ -545,14 +613,30 @@ class InnerBigText(QWidget):
def paintEvent(self, event: QPaintEvent) -> None: def paintEvent(self, event: QPaintEvent) -> None:
start_ns = time.process_time_ns() start_ns = time.process_time_ns()
painter = QPainter(self) painter = QPainter(self)
# font = "Courier New" if sys.platform == 'win32' or sys.platform == 'cygwin' else "Monospace" # font = "Courier New" if sys.platform == 'win32' or sys.platform == 'cygwin' else "Monospace"
painter.setFont(QFont("Courier New", self.model.settings.getint_session('general', "font_size"))) # "Courier New"
# "JetBrains Mono"
# "Noto Sans Mono"
# "Noto Color Emoji"
# "Andale Mono"
qfont = QFont("Monospace", self.model.settings.getint_session('general', "font_size"))
qfont.setStyleHint(QFont.StyleHint.Monospace)
painter.setFont(qfont)
self.font_metric = painter.fontMetrics()
painter.setPen(QColor(0, 0, 0)) painter.setPen(QColor(0, 0, 0))
self.update_font_metrics(painter) self.update_font_metrics(painter)
lines_to_show = math.ceil(self.lines_shown()) lines_to_show = math.ceil(self.lines_shown())
# print("%s / %s = %s" %(self.height(), float(self.char_height), lines_to_show)) # print("%s / %s = %s" %(self.height(), float(self.char_height), lines_to_show))
if self._range_end >= 0 and self._follow:
self.scroll_lines = 0
self._byte_offset = self.model.byte_count() - 1
self.parent.v_scroll_bar.set_value(self._byte_offset)
self.lines = self.model.data(self._byte_offset, self.scroll_lines, lines_to_show, self._range_start, self.lines = self.model.data(self._byte_offset, self.scroll_lines, lines_to_show, self._range_start,
self._range_end) self._range_end)
# print("lines_to_show: %d returned: %d" % (lines_to_show, len(self.lines))) # print("lines_to_show: %d returned: %d" % (lines_to_show, len(self.lines)))
@@ -567,7 +651,7 @@ class InnerBigText(QWidget):
self.parent.range_limit.set_maximum(byte_count) self.parent.range_limit.set_maximum(byte_count)
for line in self.lines: for line in self.lines:
self.update_longest_line(len(line.line())) self.update_longest_line(line)
highlighters = self.model.highlighters() highlighters = self.model.highlighters()
if self.model.get_query_highlight(): if self.model.get_query_highlight():
@@ -586,23 +670,24 @@ class InnerBigText(QWidget):
if optional_highlight_range: if optional_highlight_range:
highlight_ranges = highlight_ranges + optional_highlight_range highlight_ranges = highlight_ranges + optional_highlight_range
self.draw_highlights(highlight_ranges, painter, y_line_offset) self.draw_highlights(highlight_ranges, painter, y_line_offset, line)
y_line_offset = y_line_offset + self.char_height y_line_offset = y_line_offset + self.char_height
left_offset = int(-1 * self._left_offset * self.char_width) # left_offset = int(-1 * self._left_offset * self.char_width)
left_offset = int(-1 * self._left_offset)
y_line_offset = self.char_height y_line_offset = self.char_height
for line in self.lines: for line in self.lines:
text = line.line_prepared_for_display() text = line.line_prepared_for_display()
text = text[self._left_offset:self._left_offset + math.ceil( # text = text[self._left_offset:self._left_offset + math.ceil(
self.columns_shown())] # reduce string to the visible section before drawing # self.columns_shown())] # reduce string to the visible section before drawing
painter.drawText(0, y_line_offset, text) painter.drawText(-self._left_offset, y_line_offset, text)
y_line_offset = y_line_offset + self.char_height y_line_offset = y_line_offset + self.char_height
painter.end() painter.end()
end_ns = time.process_time_ns() end_ns = time.process_time_ns()
# print(f"paint took {(end_ns - start_ns) / 1000000.0}") # print(f"paint took {(end_ns - start_ns) / 1000000.0}")
def draw_highlights(self, highlights: [HighlightedRange], painter: QPainter, y_line_offset: int): def draw_highlights(self, highlights: [HighlightedRange], painter: QPainter, y_line_offset: int, line: Line):
for highlight in highlights: for highlight in highlights:
if highlight.is_highlight_full_line(): if highlight.is_highlight_full_line():
@@ -612,15 +697,17 @@ class InnerBigText(QWidget):
self.highlight_background(painter, rect, highlight.get_brush_full_line()) self.highlight_background(painter, rect, highlight.get_brush_full_line())
for highlight in highlights: for highlight in highlights:
left_offset = self._left_offset * self.char_width
x1 = highlight.get_start() * self.char_width x1 = self.font_metric.horizontalAdvance(
width = highlight.get_width() * self.char_width line.prefix_bytes(highlight.get_start()).decode("utf8", errors="replace"))
width = self.font_metric.horizontalAdvance(
line.substr_bytes(highlight.get_start(), highlight.get_width()).decode("utf8", errors="replace"))
y1 = y_line_offset - self.char_height + self.char_height / 7 y1 = y_line_offset - self.char_height + self.char_height / 7
height = self.char_height height = self.char_height
left = round(x1 - left_offset) left = round(x1 - self._left_offset)
if x1 + width < left_offset \ if x1 + width < self._left_offset \
or x1 > left_offset + self.width(): or x1 > self._left_offset + self.width():
# too far left or too far right # too far left or too far right
continue continue

View File

@@ -71,12 +71,15 @@ class HighlightRegex(Highlight):
# but we only want to highlight the groups # but we only want to highlight the groups
first_group = 1 if len(match.groups()) > 0 else 0 first_group = 1 if len(match.groups()) > 0 else 0
for i in range(first_group, len(match.groups()) + 1): for i in range(first_group, len(match.groups()) + 1):
start_column = line.char_to_column(match.start(i)) start_char_index = match.start(i)
end_column = line.char_to_column(match.end(i)) start_byte_index = len(line.prefix(start_char_index).encode("utf8"))
end_char_index = match.end(i)
width = len(line.substr(start_char_index, end_char_index - start_char_index).encode("utf8"))
# print(f"highlight: {start_column}:{end_column} - {match.group(i)}") # print(f"highlight: {start_column}:{end_column} - {match.group(i)}")
result.append(HighlightedRange( result.append(HighlightedRange(
start_column, start_byte_index,
end_column - start_column, width,
highlight_full_line=True, highlight_full_line=True,
brush=self._brush_hit, brush=self._brush_hit,
brush_full_line=self._brush_line brush_full_line=self._brush_line

View File

@@ -7,50 +7,45 @@ from PySide6.QtCore import Qt
from PySide6.QtGui import QBrush, QColor from PySide6.QtGui import QBrush, QColor
from src.settings.settings import Settings from src.settings.settings import Settings
from src.ui.bigtext.selectionPos import SelectionPos
class HighlightSelection(Highlight): class HighlightSelection(Highlight):
start_byte = 0 start = SelectionPos(0, False, 0)
end_byte = 0 end = SelectionPos(0, False, 0)
def set_start(self, start_byte): def set_start(self, start: SelectionPos):
self.start_byte = start_byte self.start = start
def set_end_byte(self, end_byte): def set_end_byte(self, end: SelectionPos):
self.end_byte = end_byte self.end = end
def min_byte(self) -> int:
return min(self.start.pos(), self.end.pos())
def max_byte(self) -> int:
return max(self.start.pos(), self.end.pos())
def compute_highlight(self, line: Line) -> Optional[List[HighlightedRange]]: def compute_highlight(self, line: Line) -> Optional[List[HighlightedRange]]:
begin = min(self.start_byte, self.end_byte) begin = self.min_byte()
end = max(self.start_byte, self.end_byte) end = self.max_byte()
if line.intersects(begin, end): if line.intersects(begin, end):
if line.includes_byte(begin): if line.includes_byte(begin):
start_byte_in_line = begin - line.byte_offset() start_byte_in_line = begin - line.byte_offset()
else: else:
start_byte_in_line = 0 start_byte_in_line = 0
start_char = line.byte_index_to_char_index(start_byte_in_line)
if line.includes_byte(end): if line.includes_byte(end):
length_in_bytes = end - line.byte_offset() - start_byte_in_line length_in_bytes = end - line.byte_offset() - start_byte_in_line
end_char = line.byte_index_to_char_index(start_byte_in_line + length_in_bytes)
else: else:
# renders the highlighting to the end of the line # renders the highlighting to the end of the line
# this is how selections usually behave # this is how selections usually behave
length_in_bytes = Settings.max_line_length() - start_byte_in_line length_in_bytes = Settings.max_line_length() - start_byte_in_line
# note: this mixes chars and bytes, but that should not matter, because # print(f"compute_highlight: {line.substr_bytes(begin, end)} begin={begin} end={end} start_byte_in_line={start_byte_in_line} length_in_bytes={length_in_bytes}")
# it just means that we render the highlight into the invisible range on the right return [HighlightedRange(start_byte_in_line, length_in_bytes, brush=QBrush(QColor(156, 215, 255, 192)),
end_char = start_char + length_in_bytes
start_column = line.char_to_column(start_char)
end_column = line.char_to_column(end_char)
if end_column >= 0:
length_in_columns = end_column - start_column
else:
length_in_columns = 4096
return [HighlightedRange(start_column, length_in_columns, brush=QBrush(QColor(156, 215, 255, 192)),
pen=Qt.PenStyle.NoPen)] pen=Qt.PenStyle.NoPen)]
else: else:
return None return None

View File

@@ -1,16 +1,22 @@
import unicodedata import unicodedata
from PySide6.QtGui import QFontMetrics
import constants import constants
class Line: class Line:
def __init__(self, byte_offset: int, byte_end: int, line: str): def __init__(self, byte_offset: int, byte_end: int, line: str, bytes: str):
self._byte_offset = byte_offset self._byte_offset = byte_offset
self._byte_end = byte_end self._byte_end = byte_end
self._line = line self._line = line
self._bytes = bytes
self._cache_char_to_column() self._cache_char_to_column()
def get_width_in_px(self, font_metric: QFontMetrics):
return font_metric.horizontalAdvance(self._line)
def byte_offset(self) -> int: def byte_offset(self) -> int:
return self._byte_offset return self._byte_offset
@@ -131,11 +137,20 @@ class Line:
def prefix(self, index: int) -> str: def prefix(self, index: int) -> str:
return self._line[0:index] return self._line[0:index]
def prefix_bytes(self, byte_index: int) -> str:
return self._bytes[0:byte_index]
def substr(self, offset: int, length: int) -> str: def substr(self, offset: int, length: int) -> str:
return self._line[offset:offset+length] return self._line[offset:offset+length]
def substr_bytes(self, byte_offset: int, byte_length: int) -> str:
return self._bytes[byte_offset:byte_offset + byte_length]
def suffix(self, index: int) -> str: def suffix(self, index: int) -> str:
return self._line[index:] return self._line[index:]
def suffix_bytes(self, byte_index: int) -> str:
return self._bytes[byte_index:]
def __str__(self): def __str__(self):
return "%s (%d->%d)" % (self._line, self._byte_offset, self._byte_end) return "%s (%d->%d)" % (self._line, self._byte_offset, self._byte_end)

View File

@@ -136,7 +136,7 @@ class LogFileModel:
return re.match(r"\w", char) is not None return re.match(r"\w", char) is not None
def prune_cache(self, range_start: int, range_end: int): def prune_cache(self, range_start: int, range_end: int):
print(f"cache size: {len(self._line_cache.keys())}") # print(f"cache size: {len(self._line_cache.keys())}")
for key in list(self._line_cache.keys()): for key in list(self._line_cache.keys()):
line = self._line_cache[key] line = self._line_cache[key]
if range_start > line.byte_end() or line.byte_offset() > range_end: if range_start > line.byte_end() or line.byte_offset() > range_end:
@@ -171,7 +171,7 @@ class LogFileModel:
new_offset = f.tell() new_offset = f.tell()
if 0 <= range_end < new_offset: if 0 <= range_end < new_offset:
break break
line = Line(offset, new_offset, line_bytes.decode("utf8", errors="ignore")) line = Line(offset, new_offset, line_bytes.decode("utf8", errors="ignore"), line_bytes)
if previous_line_is_complete: # only cache lines when we know they are complete if previous_line_is_complete: # only cache lines when we know they are complete
self._line_cache[offset] = line self._line_cache[offset] = line
offset = new_offset offset = new_offset

View File

@@ -12,6 +12,7 @@ from src.util import conversion
from src.util.color import to_qcolor from src.util.color import to_qcolor
from src.i18n import _ from src.i18n import _
class RangeSliderHandle(): class RangeSliderHandle():
def __init__(self, value: int): def __init__(self, value: int):
self.value = value self.value = value
@@ -74,26 +75,29 @@ class RangeSlider(QWidget):
painter = QPainter(self) painter = QPainter(self)
self._draw_background(painter) self._draw_background(painter)
self._draw_hits(painter) self._draw_hits(painter)
self._draw_handle(painter, self.lower_value) self._draw_handle(painter, self.lower_value, direction=-1)
self._draw_handle(painter, self.upper_value, direction=-1) self._draw_handle(painter, self.upper_value)
painter.end() painter.end()
def _draw_background(self, painter: QPainter) -> None: def _draw_background(self, painter: QPainter) -> None:
painter.setBrush(to_qcolor("50ade8")) painter.setBrush(to_qcolor("50ade8"))
painter.setPen(to_qcolor("dddddd")) painter.setPen(to_qcolor("dddddd"))
# the 1px wide grey center line
rect = QRect(round(10), self._handle_width, round(1), rect = QRect(round(10), self._handle_width, round(1),
self.height() - 2 * self._handle_width) self.height() - 2 * self._handle_width)
painter.drawRoundedRect(rect, 3.0, 3.0) painter.drawRoundedRect(rect, 3.0, 3.0)
# the blue line
rect = QRect(round(7), rect = QRect(round(7),
self._value_to_pixel(self.lower_value.value) + self._handle_width, self._value_to_pixel(self.lower_value.value),
round(6), round(6),
self._value_to_pixel(self.upper_value.value - self.lower_value.value) - 2 * self._handle_width) self._value_to_pixel(self.upper_value.value - self.lower_value.value) - 1 * self._handle_width)
painter.drawRoundedRect(rect, 3.0, 3.0) painter.drawRoundedRect(rect, 3.0, 3.0)
def _draw_handle(self, painter: QPainter, handle: RangeSliderHandle, direction=1) -> None: def _draw_handle(self, painter: QPainter, handle: RangeSliderHandle, direction=1) -> None:
y_pixel = self._value_to_pixel(handle.value) y_pixel = self._value_to_pixel(handle.value)
h = self._handle_width - 1 # height of the handle
painter.setBrush(to_qcolor("dddddd")) painter.setBrush(to_qcolor("dddddd"))
painter.setPen(to_qcolor("444444")) painter.setPen(to_qcolor("444444"))
@@ -101,15 +105,7 @@ class RangeSlider(QWidget):
painter.drawLine(2, y_pixel, 18, y_pixel) painter.drawLine(2, y_pixel, 18, y_pixel)
painter.setRenderHint(PySide6.QtGui.QPainter.RenderHint.Antialiasing, True) painter.setRenderHint(PySide6.QtGui.QPainter.RenderHint.Antialiasing, True)
painter.drawPolygon( painter.drawPolygon(
(QPoint(10, y_pixel), QPoint(18, y_pixel + 12 * direction), QPoint(2, y_pixel + 12 * direction))) (QPoint(10, y_pixel), QPoint(18, y_pixel + h * direction), QPoint(2, y_pixel + h * direction)))
def _draw_handle_circle(self, painter: QPainter, handle: RangeSliderHandle) -> None:
y_pixel = self._value_to_pixel(handle.value)
painter.setBrush(to_qcolor("dddddd"))
painter.setPen(to_qcolor("444444"))
painter.setRenderHint(PySide6.QtGui.QPainter.RenderHint.Antialiasing, True)
painter.drawEllipse(QPoint(self._width / 2, y_pixel), self._handle_width / 2 - 1, self._handle_width / 2 - 1)
def _value_to_pixel(self, value: int) -> int: def _value_to_pixel(self, value: int) -> int:
value_percent = value / self.max_value value_percent = value / self.max_value
@@ -149,11 +145,11 @@ class RangeSlider(QWidget):
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier: if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier:
pos: QPoint = e.pos() pos: QPoint = e.pos()
if self._is_on_handle(self.lower_value, pos.y(), direction=1): if self._is_on_handle(self.lower_value, pos.y(), direction=-1):
self.selected_handle = self.lower_value self.selected_handle = self.lower_value
self.selection_drag_range = (self.min_value, self.upper_value.value) self.selection_drag_range = (self.min_value, self.upper_value.value)
self.drag_y_offset_in_handle = self.selected_handle.value - self._pixel_to_value(pos.y()) self.drag_y_offset_in_handle = self.selected_handle.value - self._pixel_to_value(pos.y())
if self._is_on_handle(self.upper_value, pos.y(), direction=-1): if self._is_on_handle(self.upper_value, pos.y(), direction=1):
self.selected_handle = self.upper_value self.selected_handle = self.upper_value
self.selection_drag_range = (self.lower_value.value, self.max_value) self.selection_drag_range = (self.lower_value.value, self.max_value)
self.drag_y_offset_in_handle = self.selected_handle.value - self._pixel_to_value(pos.y()) self.drag_y_offset_in_handle = self.selected_handle.value - self._pixel_to_value(pos.y())

View File

@@ -1,4 +1,4 @@
01234 012345
01234567890123456789 01234567890123456789
012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789
tab indentation: tab indentation:
@@ -22,6 +22,25 @@ x◌᷍◌◌᷍◌x
Control characters: Control characters:
 
------------------------------ ------------------------------
wide and half width characters:
123456789
アンドレアス
アンドレアス アンドレアス アンドレアス アンドレアス アンドレアス アンドレアス アンドレアス
アンドレアス
canadian aboriginal:
ᑭᓇᑐᐃᓐᓇᑦᑎᐊᖅᒥᒃ
simplified chinese:
任何人不得使为奴隶或奴
Thai:
ทุกคนมีสิทธิที่จะได้
Nastaliq Urdu (rl):
چونکہ یہ تمام
Braille:
⠑⠧⠑⠗⠽⠕⠝⠑
Arabic (rl):
ولما كانت
------------------------------
👍🏿 dark thumbs up (U+1F44D + U+1F3FF - THUMBS UP SIGN + EMOJI MODIFIER FITZPATRICK TYPE-6) 👍🏿 dark thumbs up (U+1F44D + U+1F3FF - THUMBS UP SIGN + EMOJI MODIFIER FITZPATRICK TYPE-6)
ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------< ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------ä---------ä----------<
2019-08-07 00:00:10,391 [catalina-exec-40] INFO c.r.c.u.l.PerformancePointcut - Executed HealthCheckController.checkOperativeness in 1 ms successful. [jv3fw7r2.m1u5] 2019-08-07 00:00:10,391 [catalina-exec-40] INFO c.r.c.u.l.PerformancePointcut - Executed HealthCheckController.checkOperativeness in 1 ms successful. [jv3fw7r2.m1u5]

1
version.txt Normal file
View File

@@ -0,0 +1 @@
0.2.1-36-g9902be0