Compare commits

...

6 Commits

Author SHA1 Message Date
d4b962769f install isort and black for code formatting 2025-05-16 08:40:43 +02:00
d9c362419b handle tabs correctly when highlighting text
With the old solution I replaced tabs with four spaces and then did
some calculations to get the correct byte positions for a coordinate.
With the new solution (using FontMetric.horizontalAdvance) this is not
necessary anymore and we can use tha native tab.
But I cannot change the tab width. It is always 8 characters.
You can set tabStopDistance on horizontalAdvance(), but not on
elided_text() and I did not find a way to use it while rendering text.
2025-05-06 20:45:19 +02:00
bfd8ce841f add .envrc so that direnv can set the venv dir to the path 2025-05-06 20:17:10 +02:00
a41e5b79a3 add 'about qt' dialog 2025-05-06 20:14:59 +02:00
75578b6126 rename venv 2025-04-06 19:02:46 +02:00
9afc4d1d9c fix exception when selecting text by clicking behind the last character 2025-04-06 19:02:33 +02:00
12 changed files with 124 additions and 32 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
PATH=$(pwd)/venv312/bin:$PATH

3
.idea/misc.xml generated
View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Black"> <component name="Black">
<option name="enabledOnSave" value="true" />
<option name="sdkName" value="Python 3.12 (krowlog)" /> <option name="sdkName" value="Python 3.12 (krowlog)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (krowlog) (2)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (krowlog)" project-jdk-type="Python SDK" />
</project> </project>

2
.idea/ravenlog.iml generated
View File

@@ -10,7 +10,7 @@
<excludeFolder url="file://$MODULE_DIR$/icons-not-used" /> <excludeFolder url="file://$MODULE_DIR$/icons-not-used" />
<excludeFolder url="file://$MODULE_DIR$/venv312" /> <excludeFolder url="file://$MODULE_DIR$/venv312" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.12 (krowlog) (2)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 (krowlog)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@@ -1,6 +1,7 @@
import os import os
krow_icon = "icons" + os.sep + "krowlog.svg" krow_icon = "icons" + os.sep + "krowlog.svg"
qt_icon = "icons" + os.sep + "qt-logo.png"
license_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "LICENSE" license_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "LICENSE"
changelog_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "changelog.txt" changelog_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "changelog.txt"

View File

@@ -1,22 +1,26 @@
import argparse import argparse
import gettext
import logging import logging
import os
import signal import signal
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
import sys import sys
from pathlib import Path
from PySide6 import QtCore
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication
import constants import constants
from src import install from src import install
from src.pluginregistry import PluginRegistry from src.pluginregistry import PluginRegistry
import gettext
from src.ui.icon import Icon from src.ui.icon import Icon
from pathlib import Path
import os
version_file = Path(os.path.dirname(os.path.realpath(__file__)) + os.sep + "version.txt") version_file = Path(
os.path.dirname(os.path.realpath(__file__)) + os.sep + "version.txt"
)
__version__ = version_file.read_text() if version_file.is_file() else "0.0.0" __version__ = version_file.read_text() if version_file.is_file() else "0.0.0"
gettext.install('krowlog', 'locale') gettext.install("krowlog", "locale")
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
log = logging.getLogger("main") log = logging.getLogger("main")
@@ -68,7 +72,9 @@ class CmdArgs:
def parse_command_line_parameters() -> CmdArgs: def parse_command_line_parameters() -> CmdArgs:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('files', metavar='F', type=str, nargs='*', help='file(s) to open') parser.add_argument(
"files", metavar="F", type=str, nargs="*", help="file(s) to open"
)
namespace = parser.parse_args() namespace = parser.parse_args()
return CmdArgs(files=namespace.files) return CmdArgs(files=namespace.files)
@@ -77,7 +83,9 @@ if __name__ == "__main__":
cmd_args = parse_command_line_parameters() cmd_args = parse_command_line_parameters()
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setWindowIcon(Icon(constants.krow_icon)) # works only for Linux (but only X11, not Wayland) app.setWindowIcon(
Icon(constants.krow_icon)
) # works only for Linux (but only X11, not Wayland)
# install stuff, e.g. a desktop file, set icon on Windows # install stuff, e.g. a desktop file, set icon on Windows
install.install() install.install()

View File

@@ -2,3 +2,5 @@ pip==25.0.1
PySide6_Essentials==6.8.2.1 PySide6_Essentials==6.8.2.1
setuptools==77.0.3 setuptools==77.0.3
pyinstaller==6.12.0 pyinstaller==6.12.0
isort==6.0.1
black==25.1.0

View File

@@ -0,0 +1,79 @@
import textwrap
import PySide6
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPalette
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 _
class AboutQTDialog(QDialog):
"""Dialog for showing info about KrowLog"""
def __init__(self, parent=None):
super(AboutQTDialog, self).__init__(parent)
self.setWindowTitle(_("About QT"))
self.setModal(True)
# self.setMinimumWidth(850)
# self.setFixedHeight(400)
self.layout = QVBoxLayout(self)
text = f"""
<b>About QT</b>
<p>This program uses QT version {PySide6.QtCore.__version__}.</p>
<p>QT is a C++ toolkit for cross-platform application development.</p>
<p>Qt provides single-source portability across all major desktop
operating systems. It is also available for embedded Linux and other
embedded and mobile operating systems.</p>
<p>Qt is available under multiple licensing options designed to accommodate
the needs of our various users.</p>
<p>Qt licensed under our commercial license agreement is appropriate for
development of proprietary/commercial software where you do not want to
share any source code with third parties or otherwise cannot comply with
the terms of GNU (L)GPL.</p>
<p>Qt licensed under GNU (L)GPL is appropriate for the development of Qt
applications provided you can comply with the terms and conditions of the
respective licenses.</p>
Please see <a href="http://qt.io/licensing">qt.io/licensing</a> for an<
overview of Qt licensing.
<p>Copyright (C) 2025 The Qt Company Ltd and other contributors.</p>
<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>
<p>Qt is The Qt Company Ltd product developed as an open source project.
See <a href="http://qt.io">qt.io</a> for more information.</p>
"""
label = Label(text)
label.setWordWrap(True)
app_icon = QLabel()
app_icon.setPixmap(Icon(constants.qt_icon).pixmap(64, 64))
heading = QWidget(self)
hbox = QHBoxLayout(heading)
hbox.addWidget(app_icon)
hbox.addWidget(label)
hbox.addSpacerItem(QSpacerItem(1, 1, hData=QSizePolicy.Policy.Expanding))
heading.layout = hbox
self.layout.addWidget(heading)
buttons = QDialogButtonBox(self)
buttons.setStandardButtons(QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(self.close)
self.layout.addWidget(buttons)
self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)

View File

@@ -5,6 +5,7 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDockWidget, QMessageBox from PySide6.QtWidgets import QDockWidget, QMessageBox
import constants import constants
from src.plugins.krowlog.about_qt_dialog import AboutQTDialog
from src.plugins.krowlog.aboutdialog import AboutDialog from src.plugins.krowlog.aboutdialog import AboutDialog
from src.mainwindow import MainWindow from src.mainwindow import MainWindow
from src.pluginbase import PluginBase from src.pluginbase import PluginBase
@@ -36,6 +37,7 @@ class KrowLogPlugin(PluginBase):
return [ return [
MenuContribution("file", action=self._action_close(), action_id="close application", after="<last>"), MenuContribution("file", action=self._action_close(), action_id="close application", after="<last>"),
MenuContribution("help", action=self._action_about(), action_id="open about dialog", after="<last>"), MenuContribution("help", action=self._action_about(), action_id="open about dialog", after="<last>"),
MenuContribution("help", action=self._action_about_qt(), action_id="open about QT dialog", after="<last>"),
MenuContribution("settings", menu=self._sub_menu_languages(), action_id="recent files menu"), MenuContribution("settings", menu=self._sub_menu_languages(), action_id="recent files menu"),
] ]
@@ -108,6 +110,14 @@ class KrowLogPlugin(PluginBase):
) )
return about_action return about_action
def _action_about_qt(self) -> RAction:
action = RAction(
_("&About QT"),
action=lambda: AboutQTDialog().exec(),
icon_file=constants.qt_icon
)
return action
def _action_close(self) -> RAction: def _action_close(self) -> RAction:
icon = "close" if sys.platform == 'win32' or sys.platform == 'cygwin' else "exit" icon = "close" if sys.platform == 'win32' or sys.platform == 'cygwin' else "exit"
close_action = RAction(_("E&xit"), action=lambda: self.main_window.destruct(), shortcut='Ctrl+X', close_action = RAction(_("E&xit"), action=lambda: self.main_window.destruct(), shortcut='Ctrl+X',

View File

@@ -479,7 +479,7 @@ class InnerBigText(QWidget):
# # print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte)) # # 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 SelectionPos(current_byte, True, 1)
def elided_text(self, text: str, width: int): def elided_text(self, text: str, width: int):
w = width + self.font_metric.horizontalAdvance("") w = width + self.font_metric.horizontalAdvance("")
@@ -557,7 +557,6 @@ class InnerBigText(QWidget):
def _toggle_follow(self): def _toggle_follow(self):
self._follow = not self._follow self._follow = not self._follow
print(f"follow={self._follow}")
self.update() self.update()
def _update_highlight_selected_text(self): def _update_highlight_selected_text(self):

View File

@@ -15,7 +15,7 @@ class Line:
self._cache_char_to_column() self._cache_char_to_column()
def get_width_in_px(self, font_metric: QFontMetrics): def get_width_in_px(self, font_metric: QFontMetrics):
return font_metric.horizontalAdvance(self._line) return font_metric.horizontalAdvance(self.line_prepared_for_display())
def byte_offset(self) -> int: def byte_offset(self) -> int:
return self._byte_offset return self._byte_offset
@@ -43,7 +43,8 @@ class Line:
return len(prefix_chars) return len(prefix_chars)
def line_prepared_for_display(self) -> str: def line_prepared_for_display(self) -> str:
line = self._line_tabs_replaced() # line = self._line_tabs_replaced()
line = self._line
line = self._replace_control_chars_with_pictures(line) line = self._replace_control_chars_with_pictures(line)
return line return line
@@ -108,7 +109,7 @@ class Line:
if not result in self._column_to_char_cache: if not result in self._column_to_char_cache:
self._column_to_char_cache[result] = i self._column_to_char_cache[result] = i
current_char = self._line[i] current_char = self._line[i]
if current_char == "\t": if False and current_char == "\t":
result = result + constants.tab_width - result % constants.tab_width result = result + constants.tab_width - result % constants.tab_width
else: else:
result = result + 1 result = result + 1

View File

@@ -78,20 +78,6 @@ class MyTestCase(unittest.TestCase):
self.assertEqual(2, line.char_to_column(4)) # z̈ self.assertEqual(2, line.char_to_column(4)) # z̈
self.assertEqual(2, line.char_to_column(5)) # z̈ self.assertEqual(2, line.char_to_column(5)) # z̈
def test_line_tabs_replaced(self):
byte_offset = 123
text = "\ta\tb" # will be rendered as: ....abc where . represents a whitespace column
expected = " a b"
line = Line(byte_offset=byte_offset, byte_end=byte_offset + len(text.encode("utf8")), line=text)
self.assertEqual(expected, line.line_prepared_for_display())
def test_line_tabs_replaced_performance(self):
byte_offset = 123
text = "a\t" * 10000
expected = "a " * 10000
line = Line(byte_offset=byte_offset, byte_end=byte_offset + len(text.encode("utf8")), line=text)
self.assertEqual(expected, line.line_prepared_for_display())
def test_byte_index_to_char_index(self): def test_byte_index_to_char_index(self):
byte_offset = 123 byte_offset = 123
text = "x\u0308y\u0308z\u0308\t\u0308a" text = "x\u0308y\u0308z\u0308\t\u0308a"

View File

@@ -177,3 +177,7 @@ ä---------ä----------ä---------ä----------ä---------ä----------ä--
17 17
18 18
19 19
アンドレアス
アンドレアス