17 Commits
0.1.0 ... 0.2.0

Author SHA1 Message Date
32028a54b9 make version accessible without having to read a file
This works when krowlog is bundled into a single executable
2022-08-28 08:44:45 +02:00
b6aa3083d2 find icons if cwd is not in the program root 2022-08-27 17:59:37 +02:00
0c55fdc44b Update 'README.md' 2022-08-27 09:16:31 +00:00
750456642c use krowlog svg as icon 2022-08-27 11:08:56 +02:00
27dbc9085d cleanup 2022-08-26 19:24:16 +02:00
7b0d7f8807 use PySide6-Essentials instead of full install
This reduces the size to 'just' 220mb.
2022-08-26 19:00:37 +02:00
bbedaf73de highlight other matches of selected text 2022-08-26 15:31:45 +02:00
6040b1633d cleanup 2022-08-26 14:31:51 +02:00
1ddd589cc2 support Windows file share locations in drag&drop 2022-08-26 14:09:34 +02:00
5e81d90c1f update urllib3 2022-08-25 19:54:28 +02:00
46a49f1b90 get more library versions programmatically 2022-08-25 19:38:18 +02:00
3772e696ce make version label selectable 2022-08-25 19:27:05 +02:00
f9d10d37ec translate the number of matched lines text 2022-08-25 19:23:46 +02:00
4a082ab8ee fix a few warnings 2022-08-25 19:19:30 +02:00
fcc570d75f show how many lines match 2022-08-25 19:15:30 +02:00
7732d95626 remove abstract method annotation from copy method
The method was not abstract.
2022-08-25 19:13:59 +02:00
fc0922c661 rename folder filesbrowserplugin is in 2022-08-25 19:12:16 +02:00
22 changed files with 240 additions and 153 deletions

View File

@@ -1,4 +1,4 @@
# ![Logo for KrowLog](icons/icon.png "Logo for KrowLog") KrowLog
# ![Logo for KrowLog](icons/krowlog.svg "Logo for KrowLog") KrowLog
KrowLog is a viewer for text files of arbitrary size.

View File

@@ -1 +0,0 @@
0.2-alpha

View File

@@ -1,3 +1,3 @@
krow_icon = "icons/icon.png"
krow_icon = "icons/krowlog.svg"
tab_width = 4

46
icons/krowlog.svg Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="64" height="64" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg1" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#9f8700"/>
<stop offset="100%" style="stop-color:#ffdb00"/>
</linearGradient>
<linearGradient id="fg1" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#8caec7"/>
<stop offset="100%" style="stop-color:#6289a3"/>
</linearGradient>
<radialGradient id="rg1">
<stop offset="0%" stop-color="white"/>
<stop offset="100%" stop-color="black"/>
</radialGradient>
</defs>
<rect x="8" y="8" width="112" height="112" style="fill:url(#fg1)" rx="10"/>
<path style="fill:black"
d="M76,113
L15,113
L15,70
C15,70 25,55 45,36
C53,29 60,26 78,32
C85,34 90,32 90,32
C95,30 108,35 110,40
L110,40
C83,48 80,60 76,71
C74,80 76,113 76,113
z"/>
<rect x="8" y="8" width="112" height="112"
style="fill:none; stroke:url(#bg1); stroke-width:14" rx="10"/>
<circle cx="65" cy="40" r="6" fill="url(#rg1)"/>
<circle cx="65" cy="40" r="3" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,16 +1,15 @@
import logging
import signal
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
from PySide6.QtGui import QIcon
import sys
import constants
from src.pluginregistry import PluginRegistry
import gettext
from src.ui.icon import Icon
__version__ = '0.2.0'
gettext.install('krowlog', 'locale')
@@ -31,7 +30,7 @@ def stop_signal(signum, _stackframe):
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(constants.krow_icon)) # works only for Linux
app.setWindowIcon(Icon(constants.krow_icon)) # works only for Linux
# make icon appear in Windows
# see https://stackoverflow.com/questions/1551605/how-to-set-applications-taskbar-icon-in-windows-7/1552105#1552105

View File

@@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: RavenLog\n"
"POT-Creation-Date: 2022-08-23 20:08+0200\n"
"PO-Revision-Date: 2022-08-23 20:09+0200\n"
"POT-Creation-Date: 2022-08-25 19:21+0200\n"
"PO-Revision-Date: 2022-08-25 19:23+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
@@ -54,30 +54,30 @@ msgstr "&Suchtreffer Hervorheben"
msgid "Open Tab on Save As File"
msgstr "Öffne neues Tab wenn Selektion als neue Datei gespeichert wird"
#: src/plugins/filesbrowserplugin.py:35
msgid "&Files Browser"
msgstr "&Dateibrowser"
#: src/plugins/filesbrowserplugin.py:41
msgid "Files Browser"
msgstr "Dateibrowser"
#: src/plugins/findInFiles/filesbrowserwidget.py:37
#: src/plugins/filesbrowser/filesbrowserwidget.py:37
msgid "Focus on current file"
msgstr "Auf aktuelle Datei fokussieren"
#: src/plugins/findInFiles/filesbrowserwidget.py:40
#: src/plugins/filesbrowser/filesbrowserwidget.py:40
msgid "Folder:"
msgstr "Ordner:"
#: src/plugins/findInFiles/filesbrowserwidget.py:45
#: src/plugins/filesbrowser/filesbrowserwidget.py:45
msgid "Filter:"
msgstr "Filter:"
#: src/plugins/findInFiles/filesbrowserwidget.py:73
#: src/plugins/filesbrowser/filesbrowserwidget.py:73
msgid "Open Directory"
msgstr "Ordner öffnen"
#: src/plugins/filesbrowserplugin.py:30
msgid "&Files Browser"
msgstr "&Dateibrowser"
#: src/plugins/filesbrowserplugin.py:36
msgid "Files Browser"
msgstr "Dateibrowser"
#: src/plugins/krowlog/aboutdialog.py:19
msgid "About KrowLog"
msgstr "Über KrowLog"
@@ -146,22 +146,26 @@ msgstr "&Über KrowLog"
msgid "E&xit"
msgstr "&Beenden"
#: src/plugins/logfile/filterwidget.py:137
#: src/plugins/logfile/filterwidget.py:149
msgid "Cancel"
msgstr "Abbrechen"
#: src/plugins/logfile/filterwidget.py:143
#: src/plugins/logfile/filterwidget.py:155
msgid "save query"
msgstr "suche speichern"
#: src/plugins/logfile/filterwidget.py:148
#: src/plugins/logfile/filterwidget.py:160
msgid "ignore case"
msgstr "Groß-/Kleinschreibung ignorieren"
#: src/plugins/logfile/filterwidget.py:152
#: src/plugins/logfile/filterwidget.py:164
msgid "regex"
msgstr "RegExp"
#: src/plugins/logfile/filterwidget.py:251
msgid "({hits} lines)"
msgstr "({hits} Zeilen)"
#: src/plugins/logfileplugin.py:26
msgid "File not found"
msgstr "Datei nicht gefunden"
@@ -312,11 +316,11 @@ msgstr "Echtes Blau"
#: src/ui/colorbutton.py:33
msgid "Fairy Topia"
msgstr ""
msgstr "Fairy Topia"
#: src/ui/colorbutton.py:34
msgid "Magenta Bachiego"
msgstr ""
msgstr "Magenta Bachiego"
#: src/ui/colorbutton.py:36
msgid "Breeze of Mist"

View File

@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2022-08-23 20:08+0200\n"
"POT-Creation-Date: 2022-08-25 19:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -52,30 +52,30 @@ msgstr ""
msgid "Open Tab on Save As File"
msgstr ""
#: src/plugins/filesbrowserplugin.py:35
msgid "&Files Browser"
msgstr ""
#: src/plugins/filesbrowserplugin.py:41
msgid "Files Browser"
msgstr ""
#: src/plugins/findInFiles/filesbrowserwidget.py:37
#: src/plugins/filesbrowser/filesbrowserwidget.py:37
msgid "Focus on current file"
msgstr ""
#: src/plugins/findInFiles/filesbrowserwidget.py:40
#: src/plugins/filesbrowser/filesbrowserwidget.py:40
msgid "Folder:"
msgstr ""
#: src/plugins/findInFiles/filesbrowserwidget.py:45
#: src/plugins/filesbrowser/filesbrowserwidget.py:45
msgid "Filter:"
msgstr ""
#: src/plugins/findInFiles/filesbrowserwidget.py:73
#: src/plugins/filesbrowser/filesbrowserwidget.py:73
msgid "Open Directory"
msgstr ""
#: src/plugins/filesbrowserplugin.py:30
msgid "&Files Browser"
msgstr ""
#: src/plugins/filesbrowserplugin.py:36
msgid "Files Browser"
msgstr ""
#: src/plugins/krowlog/aboutdialog.py:19
msgid "About KrowLog"
msgstr ""
@@ -140,22 +140,26 @@ msgstr ""
msgid "E&xit"
msgstr ""
#: src/plugins/logfile/filterwidget.py:137
#: src/plugins/logfile/filterwidget.py:149
msgid "Cancel"
msgstr ""
#: src/plugins/logfile/filterwidget.py:143
#: src/plugins/logfile/filterwidget.py:155
msgid "save query"
msgstr ""
#: src/plugins/logfile/filterwidget.py:148
#: src/plugins/logfile/filterwidget.py:160
msgid "ignore case"
msgstr ""
#: src/plugins/logfile/filterwidget.py:152
#: src/plugins/logfile/filterwidget.py:164
msgid "regex"
msgstr ""
#: src/plugins/logfile/filterwidget.py:251
msgid "({hits} lines)"
msgstr ""
#: src/plugins/logfileplugin.py:26
msgid "File not found"
msgstr ""

View File

@@ -1,5 +1,5 @@
pip==22.2.2
PySide6==6.3.1
setuptools==62.1.0
urllib3==1.26.11
PySide6-Essentials==6.3.1
setuptools==65.3.0
urllib3==1.26.12
watchdog==2.1.9

View File

@@ -1,11 +1,7 @@
from abc import abstractmethod
class PluginBase():
def __init__(self):
pass
@abstractmethod
def copy(self):
"""
Subclasses that use state must implement this method and return a new instance of themselves.

View File

@@ -3,6 +3,8 @@ from typing import Callable
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QMenu, QPushButton, QWidget
from src.ui.icon import Icon
class RAction():
@@ -65,9 +67,9 @@ class RAction():
def _update_check_state(self):
if self._action:
if self.checked:
self._action.setIcon(QIcon("icons/ionicons/checkbox-outline.svg"))
self._action.setIcon(Icon("icons/ionicons/checkbox-outline.svg"))
else:
self._action.setIcon(QIcon("icons/ionicons/square-outline.svg"))
self._action.setIcon(Icon("icons/ionicons/square-outline.svg"))
def set_label(self, label: str):
if self._action:
@@ -77,9 +79,9 @@ class RAction():
action = QAction(self.label, qmenu)
self._action = action
if self.icon_from_theme:
action.setIcon(QIcon.fromTheme(self.icon_from_theme))
action.setIcon(Icon.fromTheme(self.icon_from_theme))
if self.icon_file:
action.setIcon(QIcon(self.icon_file))
action.setIcon(Icon(self.icon_file))
if self.shortcut:
action.setShortcut(self.shortcut)
if self.action:
@@ -95,9 +97,9 @@ class RAction():
if self.label:
button.setText(self.label)
if self.icon_from_theme:
button.setIcon(QIcon.fromTheme(self.icon_from_theme))
button.setIcon(Icon.fromTheme(self.icon_from_theme))
if self.icon_file:
button.setIcon(QIcon(self.icon_file))
button.setIcon(Icon(self.icon_file))
if self.shortcut:
button.setShortcut(self.shortcut)
if self.action:

View File

@@ -1,12 +1,10 @@
from abc import ABC
from PySide6.QtCore import Qt
from src.pluginbase import PluginBase
from src.pluginregistry import PluginRegistry
from src.plugins.domain.menucontribution import MenuContribution
from src.plugins.domain.raction import RAction
from src.plugins.findInFiles.filesbrowserwidget import FilesBrowserWidget
from src.plugins.filesbrowser.filesbrowserwidget import FilesBrowserWidget
from src.i18n import _
from src.settings.settings import Settings
@@ -17,9 +15,6 @@ class FilesBrowserPlugin(PluginBase):
super(FilesBrowserPlugin, self).__init__()
self.settings = None
def copy(self):
return self
def set_settings(self, settings: Settings):
self.settings = settings
if not self.settings.session.has_section("filesBrowser"):

View File

@@ -1,11 +1,16 @@
import textwrap
import PySide6
import urllib3
from watchdog import version as watchdog_version
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPixmap
from PySide6.QtGui import QFont
from PySide6.QtWidgets import *
import constants
import krowlog
from src.ui.icon import Icon
from src.ui.label import Label
from src.ui.vbox import VBox
from src.i18n import _
@@ -21,16 +26,16 @@ class AboutDialog(QDialog):
self.layout = QVBoxLayout(self)
heading_app_name = QLabel(_("KrowLog"))
heading_app_name = Label(_("KrowLog"))
heading_app_name.setAlignment(Qt.AlignmentFlag.AlignLeft)
heading_app_name.setFont(QFont("default", 25))
heading_app_name.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
# heading_app_name.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
version = QLabel(_("Version: {0}").format(self._version()))
version = Label(_("Version: {0}").format(krowlog.__version__))
version.setAlignment(Qt.AlignmentFlag.AlignLeft)
app_icon = QLabel()
app_icon.setPixmap(QPixmap(constants.krow_icon))
app_icon.setPixmap(Icon(constants.krow_icon).pixmap(64, 64))
heading = QWidget(self)
hbox = QHBoxLayout(heading)
hbox.addWidget(app_icon)
@@ -66,21 +71,18 @@ class AboutDialog(QDialog):
dependencies = """
<ul>
<li>Ionicons (MIT) - <a href="https://github.com/ionic-team/ionicons">https://github.com/ionic-team/ionicons</a></li>
<li>PySide6 {pyside} (LGPL v3) - <a href="https://doc.qt.io/qtforpython-6/">https://doc.qt.io/qtforpython-6/</a></li>
<li>PySide6-Essentials {pyside} (LGPL v3) - <a href="https://doc.qt.io/qtforpython-6/">https://doc.qt.io/qtforpython-6/</a></li>
<li>Qt6 {qt} (LGPL v3) - <a href="https://code.qt.io/cgit/qt/qtbase.git/">https://code.qt.io/cgit/qt/qtbase.git/</a></li>
<li>urllib3 (MIT) - <a href="https://github.com/urllib3/urllib3">https://github.com/urllib3/urllib3</a></li>
<li>watchdog 2.16 (Apache 2.0) - <a href="https://github.com/gorakhargosh/watchdog">https://github.com/gorakhargosh/watchdog</a></li>
</ul>""".format(pyside=PySide6.__version__, qt=PySide6.QtCore.__version__)
<li>urllib3 {urllib3} (MIT) - <a href="https://github.com/urllib3/urllib3">https://github.com/urllib3/urllib3</a></li>
<li>watchdog {watchdog} (Apache 2.0) - <a href="https://github.com/gorakhargosh/watchdog">https://github.com/gorakhargosh/watchdog</a></li>
</ul>""".format(
pyside=PySide6.__version__,
qt=PySide6.QtCore.__version__,
urllib3=urllib3.__version__,
watchdog=watchdog_version.VERSION_STRING)
label = textwrap.dedent(dependencies)
result = QWidget()
result.layout = QVBoxLayout(result)
result.layout.addWidget(Label(label))
return result
def _version(self):
with open('VERSION.info', "rt") as f:
line = f.readline()
version = line.strip()
return version

View File

@@ -8,7 +8,7 @@ from typing import Optional, Callable
from PySide6.QtCore import QRunnable, QThreadPool, Signal
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QComboBox, \
QSizePolicy, QProgressBar, QMenu, QMenuBar
QSizePolicy, QProgressBar
from src.plugins.domain.raction import RAction
from src.plugins.logfile.preprocesslineshook import PreProcessLinesHook
@@ -18,6 +18,8 @@ from src.ui.bigtext.logFileModel import LogFileModel
from src.i18n import _
from src.pluginregistry import PluginRegistry
from src.ui.hbox import HBox
from src.ui.icon import Icon
from src.ui.label import Label
from src.zonedpluginregistry import ZonedPluginRegistry
@@ -33,6 +35,7 @@ class FilterTask(QRunnable):
filter_match_found_listeners: Callable[[int], None],
pre_process_lines_hooks: [PreProcessLinesHook],
progress_handler: Callable[[float], None],
update_hits_handler: Callable[[int], None],
on_before: Callable[[], None],
on_finish: Callable[[], None]
):
@@ -41,6 +44,7 @@ class FilterTask(QRunnable):
self.filter_model = filter_model
self.regex = regex
self.progress_handler = progress_handler
self.update_hits_handler = update_hits_handler
self.pre_process_lines_hooks = pre_process_lines_hooks
self.on_before = on_before
self.on_finish = on_finish
@@ -61,19 +65,20 @@ class FilterTask(QRunnable):
for listener in self.filter_match_found_listeners:
listener(-1, -1) # notify listeners that a new search started
hits_count = 0
last_progress_report = time.time()
try:
with open(self.source_model.get_file(), "rb") as source:
with open(self.filter_model.get_file(), "w+b") as target:
line_count = 0
lines_written = 0
while l := source.readline():
while line_encoded := source.readline():
line_count = line_count + 1
line = l.decode("utf8", errors="ignore")
line = line_encoded.decode("utf8", errors="ignore")
if self.regex.findall(line):
lines_written = lines_written + 1
source_line_offset = source.tell() - len(l)
source_line_offset = source.tell() - len(line_encoded)
target_line_offset = target.tell()
for listener in self.filter_match_found_listeners:
listener(target_line_offset, source_line_offset)
@@ -83,6 +88,7 @@ class FilterTask(QRunnable):
line = h.pre_process_line(line, target)
target.write(line.encode("utf8"))
hits_count = hits_count + 1
# sometime buffering can hide results for a while
# We force a flush periodically.
@@ -95,12 +101,15 @@ class FilterTask(QRunnable):
if now - last_progress_report > 0.2:
progress = source.tell() / os.stat(self.source_model.get_file()).st_size
self.progress_handler(progress)
self.update_hits_handler(hits_count)
last_progress_report = now
if self.aborted:
self.update_hits_handler(hits_count)
# print("aborted ", time.time())
break
finally:
self.update_hits_handler(hits_count)
self.on_finish()
# print("dome thread ", threading.currentThread())
@@ -110,6 +119,7 @@ class FilterWidget(QWidget):
filter_task: Optional[FilterTask] = None
search_is_running = Signal(bool)
signal_update_progress = Signal(float)
signal_update_hits = Signal(int)
def __init__(self, source_model: LogFileModel, zoned_plugin_registry: ZonedPluginRegistry):
super(FilterWidget, self).__init__()
@@ -129,6 +139,9 @@ class FilterWidget(QWidget):
self.query_field.lineEdit().returnPressed.connect(self.filter_changed)
self.query_field.setInsertPolicy(QComboBox.NoInsert)
self.hits_field = Label("")
self.signal_update_hits.connect(self._update_hits)
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setMaximumWidth(50)
@@ -139,7 +152,7 @@ class FilterWidget(QWidget):
self.btn_cancel_search.pressed.connect(self._cancel_search)
self.search_is_running.connect(self.search_running_status_changed)
self.btn_bookmark = QPushButton(QIcon("icons/ionicons/star.svg"), "")
self.btn_bookmark = QPushButton(Icon("icons/ionicons/star.svg"), "")
self.btn_bookmark.setToolTip(_("save query"))
self.btn_bookmark.pressed.connect(self._save_query)
@@ -157,16 +170,17 @@ class FilterWidget(QWidget):
filter_bar.layout = QHBoxLayout(filter_bar)
filter_bar.layout.setContentsMargins(0, 0, 0, 0)
filter_bar.layout.addWidget(self.query_field)
filter_bar.layout.addWidget(self.btn_bookmark)
filter_bar.layout.addWidget(self.hits_field)
filter_bar.layout.addWidget(self.progress_bar)
filter_bar.layout.addWidget(self.btn_cancel_search)
filter_bar.layout.addWidget(self.btn_bookmark)
filter_bar.layout.addWidget(self.menu)
filter_bar.layout.addWidget(self.ignore_case)
filter_bar.layout.addWidget(self.is_regex)
(handle, self.tmpfilename) = tempfile.mkstemp()
(handle, self.tmp_filename) = tempfile.mkstemp()
os.close(handle)
self.filter_model = LogFileModel(self.tmpfilename, self.source_model.settings)
self.filter_model = LogFileModel(self.tmp_filename, self.source_model.settings)
self.hits_view = BigText(self.filter_model)
self.layout.addWidget(filter_bar)
@@ -208,9 +222,8 @@ class FilterWidget(QWidget):
self._reload_save_queries()
def destruct(self):
# print("cleanup: ", self.tmpfilename)
self._cancel_search()
os.remove(self.tmpfilename)
os.remove(self.tmp_filename)
def _cancel_search(self):
if self.filter_task:
@@ -224,6 +237,7 @@ class FilterWidget(QWidget):
self.filter_model.truncate()
self.source_model.clear_query_highlight()
self.filter_model.clear_query_highlight()
self._update_hits(-1)
PluginRegistry.execute("update_ui")
def search_running_status_changed(self, is_running: bool):
@@ -233,9 +247,18 @@ class FilterWidget(QWidget):
def update_progress(self, progress: float):
self.progress_bar.setValue(progress * 100)
def _update_hits(self, hits: int):
if hits >= 0:
self.hits_field.setText(_("({hits} lines)").format(hits=hits))
else:
self.hits_field.setText("")
def progress_handler(self, progress: float):
self.signal_update_progress.emit(progress)
def update_hits_handler(self, hits: int):
self.signal_update_hits.emit(hits)
def filter_changed(self):
query = self.query_field.currentText()
ignore_case = self.ignore_case.isChecked()
@@ -260,6 +283,7 @@ class FilterWidget(QWidget):
return
self.progress_bar.setValue(0)
self._update_hits(-1)
self.source_model.set_query_highlight(query, ignore_case, is_regex)
self.filter_model.set_query_highlight(query, ignore_case, is_regex)
@@ -274,6 +298,7 @@ class FilterWidget(QWidget):
self.filter_match_found_listeners,
pre_process_lines_hooks,
self.progress_handler,
self.update_hits_handler,
lambda: self.search_is_running.emit(True),
lambda: self.search_is_running.emit(False)
)

View File

@@ -1,5 +1,3 @@
import sys
import math
import os
import time
@@ -12,6 +10,7 @@ from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import *
from src.ui.ScaledScrollBar import ScaledScrollBar
from src.ui.bigtext.highlight_regex import HighlightRegex
from src.ui.bigtext.highlight_selection import HighlightSelection
from src.ui.bigtext.highlighted_range import HighlightedRange
from src.ui.bigtext.highlightingdialog import HighlightingDialog
@@ -97,7 +96,6 @@ class BigText(QWidget):
def add_line_click_listener(self, listener: Callable[[int], None]):
"""
:param listener: a callable, the parameter is the byte offset of the clicked line
:return:
"""
@@ -111,6 +109,7 @@ class BigText(QWidget):
pass
# noinspection PyArgumentList,PyTypeChecker
class InnerBigText(QWidget):
_byte_offset = 0
_left_offset = 0
@@ -119,6 +118,8 @@ class InnerBigText(QWidget):
def __init__(self, parent: BigText, model: LogFileModel):
super(InnerBigText, self).__init__()
self.char_height = None
self.char_width = None
self.model = model
self.parent = parent
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
@@ -132,6 +133,12 @@ class InnerBigText(QWidget):
self._last_double_click_time = 0
self._last_double_click_line_number = -1
self.highlight_selected_text = HighlightRegex(
"",
is_regex=False,
ignore_case=True,
hit_background_color="d7efffc0") # same blue as the selection hightlight, but with lower saturation
self.line_click_listeners: [Callable[[int], None]] = []
def keyPressEvent(self, e: QKeyEvent) -> None:
@@ -203,10 +210,12 @@ class InnerBigText(QWidget):
self.update()
self.parent.v_scroll_bar.setValue(self._byte_offset)
# noinspection PyTypeChecker
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.ShiftModifier:
offset = self.to_byte_offset(e)
self.selection_highlight.set_end_byte(offset)
self._update_highlight_selected_text()
self.update()
return
@@ -217,6 +226,7 @@ class InnerBigText(QWidget):
line: Line = self.lines[line_number]
self.selection_highlight.set_start(line.byte_offset())
self.selection_highlight.set_end_byte(line.byte_end())
self._update_highlight_selected_text()
self.update()
return
@@ -224,6 +234,7 @@ class InnerBigText(QWidget):
offset = self.to_byte_offset(e)
self.selection_highlight.set_start(offset)
self.selection_highlight.set_end_byte(offset)
self._update_highlight_selected_text()
self.update()
line_number = self.y_pos_to_line(e.pos().y())
@@ -245,6 +256,8 @@ class InnerBigText(QWidget):
else:
self.selection_highlight.set_start(offset)
self.selection_highlight.set_end_byte(offset)
self._update_highlight_selected_text()
self.update()
def mouseMoveEvent(self, e: QMouseEvent):
@@ -256,8 +269,10 @@ class InnerBigText(QWidget):
if self.selection_highlight.end_byte != current_byte:
self.selection_highlight.set_end_byte(current_byte)
self._update_highlight_selected_text()
self.update()
# print("-> %s,%s" %(self._selection_start_byte, self._selection_end_byte))
line_number = self.y_pos_to_line(e.pos().y())
column_in_line = self.x_pos_to_column(e.pos().x())
if line_number < 0:
@@ -310,7 +325,7 @@ class InnerBigText(QWidget):
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 lcolumn_in_line=%s" % (char_in_line, line_number, 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))
@@ -332,6 +347,7 @@ class InnerBigText(QWidget):
_("data selection"),
_(
"You have selected <b>{0}</b> of data.").format(bytes_human_readable))
# noinspection PyTypeChecker
you_sure.setStandardButtons(QMessageBox.Cancel)
copy_btn = you_sure.addButton(_("Copy {0} to Clipboard").format(bytes_human_readable),
QMessageBox.ActionRole)
@@ -357,6 +373,7 @@ class InnerBigText(QWidget):
end = max(self.selection_highlight.start_byte, self.selection_highlight.end_byte)
dialog = QFileDialog(self)
(selected_file, _filter) = dialog.getSaveFileName(
parent=self,
caption=_("Save File"),
dir=os.path.dirname(self.model.get_file())
)
@@ -369,19 +386,28 @@ class InnerBigText(QWidget):
def _select_all(self):
self.selection_highlight.start_byte = 0
self.selection_highlight.end_byte = self.model.byte_count()
self._update_highlight_selected_text()
self.update()
def _update_highlight_selected_text(self):
start_byte=self.selection_highlight.start_byte
end_byte=self.selection_highlight.end_byte
if abs(start_byte - end_byte) < 1024:
query = self.model.read_range(start_byte, end_byte)
if query.find("\n") < 0:
self.highlight_selected_text.set_query(query)
return
self.highlight_selected_text.set_query("")
def paintEvent(self, event: QPaintEvent) -> None:
# print("paintEvent %s" % (self.model.get_file()))
painter = QPainter(self)
# painter.setFont(self.model.settings.font())
# Courier New, DejaVu Sans Mono, Monospace, Liberation Mono, Noto Mono, Nimbus Mono L, Tlwg Mono, Ubuntu Mono, FreeMono, Mitra Mono
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")))
painter.setPen(QColor(0, 0, 0))
self.update_font_metrics(painter)
lines_to_show = self.lines_shown()
lines_to_show = math.ceil(self.lines_shown())
# print("%s / %s = %s" %(self.height(), float(self.char_height), lines_to_show))
self.lines = self.model.data(self._byte_offset, self.scroll_lines, lines_to_show)
@@ -392,36 +418,33 @@ class InnerBigText(QWidget):
# document length == maximum + pageStep + aFewBytesSoThatTheLastLineIsShown
self.parent.v_scroll_bar.setMaximum(self.model.byte_count() - 1)
for l in self.lines:
self.update_longest_line(len(l.line()))
for line in self.lines:
self.update_longest_line(len(line.line()))
highlighters = self.model.highlighters()
if self.model.get_query_highlight():
highlighters = highlighters + [self.model.get_query_highlight()]
highlighters = highlighters + [self.highlight_selected_text]
highlighters = highlighters + [self.selection_highlight] # selection highlight should be last
# draw hightlights first - some characters may overlap to the next line
# by drawing the background hightlights first we prevent that the hightlight
# draw highlights first - some characters may overlap to the next line
# by drawing the background highlights first we prevent that the highlight
# draws over a character
start = time.time()
y_line_offset = self.char_height;
for l in self.lines:
y_line_offset = self.char_height
for line in self.lines:
highlight_ranges = []
for h in highlighters:
optional_highlight_range = h.compute_highlight(l)
optional_highlight_range = h.compute_highlight(line)
if optional_highlight_range:
highlight_ranges = highlight_ranges + optional_highlight_range
self.draw_highlights(highlight_ranges, painter, y_line_offset)
y_line_offset = y_line_offset + self.char_height
end = time.time()
# print("highlight duration: %.3f" %((end-start)*1000))
left_offset = int(-1 * self._left_offset * self.char_width)
y_line_offset = self.char_height;
for l in self.lines:
text = l.line_prepared_for_display()
y_line_offset = self.char_height
for line in self.lines:
text = line.line_prepared_for_display()
painter.drawText(left_offset, y_line_offset, text)
y_line_offset = y_line_offset + self.char_height
@@ -461,6 +484,6 @@ class InnerBigText(QWidget):
fm: QFontMetrics = painter.fontMetrics()
self.char_height = fm.height()
self.char_width = fm.averageCharWidth() # all chars have same width for monospace font
text = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
text = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012"
self.char_width = fm.horizontalAdvance(text) / float(len(text))
# print("font width=%s height=%s" % (self.char_width, self.char_height))

View File

@@ -29,6 +29,10 @@ class HighlightRegex(Highlight):
else:
return re.compile(re.escape(self.query), flags=flags)
def set_query(self, query: str) -> None:
self.query = query
self.regex = self._get_regex()
def compute_highlight(self, line: Line) -> Optional[List[HighlightedRange]]:
result = []
# print("execute regex: %s in %s" % (self.regex, line.line()))
@@ -56,9 +60,15 @@ class HighlightRegex(Highlight):
@staticmethod
def brush(color: str) -> QBrush:
if re.match("[0-9a-f]{6}", color, flags=re.IGNORECASE):
if re.match("^[0-9a-f]{6}$", color, flags=re.IGNORECASE):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
return QBrush(QColor(red, green, blue))
if re.match("^[0-9a-f]{8}$", color, flags=re.IGNORECASE):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
alpha = int(color[6:8], 16)
return QBrush(QColor(red, green, blue, alpha))
return QBrush()

View File

@@ -1,4 +1,3 @@
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QDialog, QLineEdit, QLabel, QGridLayout, QCheckBox, QListWidget, QListWidgetItem, \
QPushButton, QDialogButtonBox, QMessageBox, QSizePolicy
@@ -9,6 +8,7 @@ from src.ui.hbox import HBox
from src.settings.settings import Settings
from src.i18n import _
from src.ui.icon import Icon
class PayloadItem(QListWidgetItem):
@@ -32,23 +32,23 @@ class HighlightingDialog(QDialog):
form_grid.addWidget(self.list, row, 0, 1, 2)
row = row + 1
self.btn_add = QPushButton(QIcon.fromTheme("list-add"), _("Add"))
self.btn_add = QPushButton(Icon.fromTheme("list-add"), _("Add"))
self.btn_add.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.btn_add.pressed.connect(self._add)
self.btn_update = QPushButton(QIcon.fromTheme("stock_edit"), _("Update"))
self.btn_update = QPushButton(Icon.fromTheme("stock_edit"), _("Update"))
self.btn_update.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.btn_update.pressed.connect(self._update)
self.btn_delete = QPushButton(QIcon.fromTheme("list-remove"), _("Remove"))
self.btn_delete = QPushButton(Icon.fromTheme("list-remove"), _("Remove"))
self.btn_delete.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.btn_delete.pressed.connect(self._delete)
self.btn_move_up = QPushButton(QIcon.fromTheme("go-up"), _("Up"))
self.btn_move_up = QPushButton(Icon.fromTheme("go-up"), _("Up"))
self.btn_move_up.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.btn_move_up.pressed.connect(self._move_up)
self.btn_move_down = QPushButton(QIcon.fromTheme("go-down"), _("Down"))
self.btn_move_down = QPushButton(Icon.fromTheme("go-down"), _("Down"))
self.btn_move_down.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.btn_move_down.pressed.connect(self._move_down)
button_box = HBox(self.btn_update, self.btn_add, self.btn_delete, self.btn_move_up, self.btn_move_down)

11
src/ui/icon.py Normal file
View File

@@ -0,0 +1,11 @@
from PySide6.QtGui import QIcon
from pathlib import Path
class Icon(QIcon):
def __init__(self, file_name: str):
super(Icon, self).__init__("%s" % Path(__file__).parent.parent.parent.joinpath(file_name).absolute())
print("%s -> %s" % (file_name, Path(__file__).parent.parent.parent.joinpath(file_name).absolute()))
def fromTheme(icon_from_theme: str) -> QIcon:
return QIcon.fromTheme(icon_from_theme)

View File

@@ -11,9 +11,12 @@ def urls_to_path(urls: str) -> [str]:
result.append(path)
return result
def url_to_path(url: str) -> str:
p = urlparse(url)
if sys.platform == 'win32' or sys.platform == 'cygwin':
if p.netloc:
return f"//{p.netloc}{p.path}"
return os.path.abspath(p.path[1:])
return os.path.abspath(os.path.join(p.netloc, p.path))

View File

@@ -1,32 +0,0 @@
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QDockWidget, QLabel, QTextEdit
from PyQt6.QtCore import Qt
class DockWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(DockWindow, self).__init__(*args, **kwargs)
self.dock_1 = QDockWidget("Dock1", self)
self.dock_1.layout().addWidget(QLabel("dock1"))
self.dock_2 = QDockWidget("Dock2", self)
self.dock_2.layout().addWidget(QLabel("dock2"))
self.dock_3 = QDockWidget("Dock3", self)
self.dock_3.layout().addWidget(QLabel("dock3"))
self.setCentralWidget(QTextEdit())
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dock_1)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.dock_2)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.dock_3)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DockWindow()
window.show()
app.exec()