31 changed files with 2323 additions and 3 deletions
@ -0,0 +1,35 @@
|
||||
.PHONY: doc runGittk runDemo build deploy buildTest deployTest |
||||
|
||||
.venv: |
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate ; \
|
||||
pip install -r docs/requirements.txt
|
||||
# Regen requirements;
|
||||
# pip freeze > docs/requirements.txt
|
||||
|
||||
build: .venv |
||||
. .venv/bin/activate ; \
|
||||
rm -rf dist ; \
|
||||
tools/prepareBuild.sh release ; \
|
||||
cd tmp ; \
|
||||
python3 -m build
|
||||
|
||||
buildTest: .venv |
||||
. .venv/bin/activate ; \
|
||||
rm -rf dist ; \
|
||||
tools/prepareBuild.sh test ; \
|
||||
cd tmp ; \
|
||||
python3 -m build ;
|
||||
|
||||
deployTest: .venv |
||||
. .venv/bin/activate ; \
|
||||
python3 -m twine upload --repository testpypi tmp/dist/* --verbose
|
||||
|
||||
deploy: .venv |
||||
. .venv/bin/activate ; \
|
||||
python3 -m twine upload tmp/dist/* --repository tlogg --verbose
|
||||
|
||||
#test: .venv
|
||||
# . .venv/bin/activate ; \
|
||||
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp ; \
|
||||
# pytest demo/demo.py
|
||||
@ -0,0 +1,56 @@
|
||||
 |
||||
 |
||||
 |
||||
 |
||||
[](https://pypi.org/project/tlogg) |
||||
[](https://twitter.com/hashtag/pyTermTk?src=hashtag_click&f=live) |
||||
|
||||
# tlogg |
||||
A fast, advanced [text-based](https://en.wikipedia.org/wiki/Text-based_user_interface) log explorer written in [pyTermTk](https://github.com/ceccopierangiolieugenio/pyTermTk), inspired by [glogg - the fast, smart log explorer](https://github.com/nickbnf/glogg) and [klogg - Faster log explorer](https://klogg.filimonov.dev)(fork of glogg) |
||||
|
||||
[](https://pypi.org/project/tlogg) |
||||
## Features |
||||
- Search Panel |
||||
- Highlight |
||||
- Bookmarks |
||||
- Shiny ASCII Red Peppers |
||||
|
||||
[](https://pypi.org/project/tlogg) |
||||
|
||||
- _Draggable_ **Tiling tabs** |
||||
|
||||
[screenshot](https://github.com/ceccopierangiolieugenio/tlogg/assets/8876552/b3db13d9-48b4-485e-bc19-d655021479b6) |
||||
|
||||
# Install from [pypi](https://pypi.org/project/tlogg) |
||||
```bash |
||||
pip install tlogg |
||||
``` |
||||
## Enable the system Clipboard |
||||
[pyTermTk](https://github.com/ceccopierangiolieugenio/pyTermTk) automatically support the system clipboard through [pyperclip](https://pypi.org/project/pyperclip/) |
||||
```bash |
||||
pip install pyperclip |
||||
``` |
||||
# QuickRun |
||||
```bash |
||||
$ tlogg -h |
||||
usage: tlogg [-h] [-c C] filename [filename ...] |
||||
|
||||
positional arguments: |
||||
filename the filename/s |
||||
|
||||
optional arguments: |
||||
-h, --help show this help message and exit |
||||
-c C config folder (default: "/home/user/.config/tlogg") |
||||
``` |
||||
|
||||
# Test |
||||
### Clone |
||||
```bash |
||||
git clone https://github.com/ceccopierangiolieugenio/tlogg.git |
||||
cd tlogg |
||||
``` |
||||
### Run |
||||
``` |
||||
python3 -m tlogg <File/s> |
||||
``` |
||||
|
||||
@ -0,0 +1,46 @@
|
||||
[build-system] |
||||
requires = ["setuptools>=45", "wheel"] |
||||
build-backend = "setuptools.build_meta" |
||||
|
||||
[project] |
||||
name = "tlogg" |
||||
dynamic = ["version"] |
||||
readme = {file = "README.md", content-type = "text/markdown"} |
||||
authors = [ |
||||
{name = "Eugenio Parodi", email = "ceccopierangiolieugenio@googlemail.com"}, |
||||
] |
||||
description = "A fast, advanced log explorer" |
||||
requires-python = ">=3.9" |
||||
license = {text = "MIT"} |
||||
classifiers = [ |
||||
"Programming Language :: Python :: 3", |
||||
"License :: OSI Approved :: MIT License", |
||||
"Operating System :: OS Independent", |
||||
"Development Status :: 3 - Alpha", |
||||
"Environment :: Console", |
||||
"Intended Audience :: Developers", |
||||
"Intended Audience :: Information Technology", |
||||
"Topic :: Terminals", |
||||
"Topic :: Software Development :: User Interfaces", |
||||
] |
||||
dependencies = [ |
||||
'pyTermTk>=0.41.17-a.0', |
||||
'appdirs', |
||||
'copykitten', |
||||
'pyyaml' |
||||
] |
||||
|
||||
[project.urls] |
||||
Homepage = "https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/tlogg" |
||||
Repository = "https://github.com/ceccopierangiolieugenio/pyTermTk.git" |
||||
Issues = "https://github.com/ceccopierangiolieugenio/pyTermTk/issues" |
||||
Changelog = "https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/apps/tlogg/CHANGELOG.md" |
||||
|
||||
[project.scripts] |
||||
tlogg = "tlogg.__main__:main" |
||||
|
||||
[tool.setuptools] |
||||
packages = ["tlogg", "tlogg.app", "tlogg.plugins"] |
||||
|
||||
[tool.setuptools.dynamic] |
||||
version = {attr = "tlogg.__version__"} |
||||
@ -0,0 +1,27 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__version__:str = '0.2.7-a.3' |
||||
|
||||
from .plugin import TloggPlugin |
||||
from .proxy import tloggProxy, TloggViewerProxy |
||||
from .helper import * |
||||
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
from .app import main |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
||||
@ -0,0 +1,28 @@
|
||||
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
from .cfg import * |
||||
from .glbl import * |
||||
from .main import * |
||||
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['About'] |
||||
|
||||
from TermTk.TTkCore.log import TTkLog |
||||
from TermTk.TTkCore.color import TTkColor |
||||
from TermTk import TTkAbout, TTkWindow |
||||
from .cfg import TloggCfg |
||||
|
||||
class About(TTkAbout): |
||||
tlogg = [ |
||||
" __ ___ ", |
||||
"/\ \__/\_ \ ", |
||||
"\ \ ,_\//\ \ ___ __ __ ", |
||||
" \ \ \/ \ \ \ / __`\ /'_ `\ /'_ `\ ", |
||||
" \ \ \_ \_\ \_/\ \L\ \/\ \L\ \/\ \L\ \ ", |
||||
" \ \__\/\____\ \____/\ \____ \ \____ \ ", |
||||
" \/__/\/____/\/___/ \/___L\ \/___L\ \\", |
||||
" /\____/ /\____/", |
||||
" \_/__/ \_/__/ "] |
||||
|
||||
__slots__=('_image') |
||||
def __init__(self, *args, **kwargs): |
||||
TTkAbout.__init__(self, *args, **kwargs) |
||||
self._name = kwargs.get('name' , 'About' ) |
||||
self.setTitle('[PierCecco Cecco] Eugenio Parodi proudly presents...') |
||||
self.resize(56,16) |
||||
|
||||
|
||||
|
||||
def paintEvent(self, canvas): |
||||
c = [0xFF,0xFF,0xFF] |
||||
for y, line in enumerate(About.tlogg): |
||||
canvas.drawText(pos=(13,3+y),text=line, color=TTkColor.fg(f'#{c[0]:02X}{c[1]:02X}{c[2]:02X}')) |
||||
c[2]-=0x18 |
||||
c[0]-=0x08 |
||||
canvas.drawText(pos=(26,4),text=f" Version: {TloggCfg.version}", color=TTkColor.fg('#AAAAFF')) |
||||
canvas.drawText(pos=(14,11),text=f"Powered By, pyTermTk") |
||||
canvas.drawText(pos=(2,13),text=f"https://github.com/ceccopierangiolieugenio/tlogg", color=TTkColor.fg('#44FFFF')) |
||||
canvas.drawText(pos=(2,14),text=f"https://github.com/ceccopierangiolieugenio/pyTermTk", color=TTkColor.fg('#44FFFF')) |
||||
|
||||
TTkWindow.paintEvent(self, canvas) |
||||
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['TloggCfg'] |
||||
|
||||
import os |
||||
import yaml |
||||
|
||||
from .. import __version__ |
||||
|
||||
class TloggCfg: |
||||
version=__version__ |
||||
name="tlogg" |
||||
cfgVersion = '1.0' |
||||
pathCfg="." |
||||
colors=[] |
||||
filters=[] |
||||
options={} |
||||
searches=[] |
||||
maxsearches=200 |
||||
|
||||
@staticmethod |
||||
def save(searches=True, filters=True, colors=True, options=True): |
||||
os.makedirs(TloggCfg.pathCfg, exist_ok=True) |
||||
colorsPath = os.path.join(TloggCfg.pathCfg,'colors.yaml') |
||||
filtersPath = os.path.join(TloggCfg.pathCfg,'filters.yaml') |
||||
optionsPath = os.path.join(TloggCfg.pathCfg,'options.yaml') |
||||
searchesPath = os.path.join(TloggCfg.pathCfg,'searches.yaml') |
||||
|
||||
def writeCfg(path, cfg): |
||||
fullCfg = { |
||||
'version':TloggCfg.cfgVersion, |
||||
'cfg':cfg } |
||||
with open(path, 'w') as f: |
||||
yaml.dump(fullCfg, f, sort_keys=False, default_flow_style=False) |
||||
|
||||
if colors: writeCfg(colorsPath, TloggCfg.colors) |
||||
if filters: writeCfg(filtersPath, TloggCfg.filters) |
||||
if options: writeCfg(optionsPath, TloggCfg.options) |
||||
if searches: writeCfg(searchesPath, TloggCfg.searches) |
||||
|
||||
@staticmethod |
||||
def load(): |
||||
colorsPath = os.path.join(TloggCfg.pathCfg,'colors.yaml') |
||||
filtersPath = os.path.join(TloggCfg.pathCfg,'filters.yaml') |
||||
optionsPath = os.path.join(TloggCfg.pathCfg,'options.yaml') |
||||
searchesPath = os.path.join(TloggCfg.pathCfg,'searches.yaml') |
||||
|
||||
if os.path.exists(colorsPath): |
||||
with open(colorsPath) as f: |
||||
TloggCfg.colors = yaml.load(f, Loader=yaml.SafeLoader)['cfg'] |
||||
if os.path.exists(filtersPath): |
||||
with open(filtersPath) as f: |
||||
TloggCfg.filters = yaml.load(f, Loader=yaml.SafeLoader)['cfg'] |
||||
if os.path.exists(optionsPath): |
||||
with open(optionsPath) as f: |
||||
TloggCfg.options = yaml.load(f, Loader=yaml.SafeLoader)['cfg'] |
||||
if os.path.exists(searchesPath): |
||||
with open(searchesPath) as f: |
||||
TloggCfg.searches = yaml.load(f, Loader=yaml.SafeLoader)['cfg'] |
||||
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this soself._ftware and associated documentation files (the "Soself._ftware"), to deal |
||||
# in the Soself._ftware without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Soself._ftware, and to permit persons to whom the Soself._ftware is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Soself._ftware. |
||||
# |
||||
# THE SOself._FTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOself._FTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOself._FTWARE. |
||||
|
||||
import os |
||||
|
||||
from TermTk import TTkK |
||||
from TermTk import TTkWidget, TTkCheckbox, TTkButton, TTkSpacer, TTkFileTree, TTkFileTreeWidgetItem |
||||
from TermTk import TTkVBoxLayout, TTkHBoxLayout |
||||
from TermTk import pyTTkSlot, pyTTkSignal |
||||
|
||||
from . import TloggCfg |
||||
|
||||
class FileTree(TTkWidget): |
||||
__slots__ = ( |
||||
'_recentPath', '_recentPathId', |
||||
'_controlsLayout', |
||||
'_ftCbFollow', '_ftBtnPrev', '_ftBtnNext', '_ftBtnUp', '_fileTree', |
||||
# Forwarded Methods |
||||
'fileActivated') |
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
TTkWidget.__init__(self, *args, **kwargs) |
||||
self._name = kwargs.get('name' , 'FileTree' ) |
||||
|
||||
self.setLayout(TTkVBoxLayout()) |
||||
|
||||
self._controlsLayout = TTkHBoxLayout() |
||||
self.layout().addItem(self._controlsLayout) |
||||
|
||||
self._ftCbFollow = TTkCheckbox(text="Follow", maxWidth=9, checked=False) |
||||
self._ftBtnPrev = TTkButton(text="<",maxWidth=3, enabled=False) |
||||
self._ftBtnNext = TTkButton(text=">",maxWidth=3, enabled=False) |
||||
self._ftBtnUp = TTkButton(text="^",maxWidth=3, enabled=True) |
||||
self._controlsLayout.addWidget(self._ftCbFollow) |
||||
self._controlsLayout.addWidget(TTkSpacer()) |
||||
self._controlsLayout.addWidget(self._ftBtnPrev) |
||||
self._controlsLayout.addWidget(self._ftBtnNext) |
||||
self._controlsLayout.addWidget(self._ftBtnUp) |
||||
|
||||
self._fileTree = TTkFileTree(parent=self, path=".") |
||||
|
||||
self._recentPath = [] |
||||
self._recentPathId = -1 |
||||
self._addFolder(os.path.abspath('.')) |
||||
|
||||
self._ftBtnPrev.clicked.connect(self._openPrev) |
||||
self._ftBtnNext.clicked.connect(self._openNext) |
||||
self._ftBtnUp.clicked.connect(self._openUp) |
||||
|
||||
self._fileTree.folderActivated.connect(self._openFolder) |
||||
|
||||
# Forward Functions |
||||
self.fileActivated = self._fileTree.fileActivated |
||||
|
||||
def follow(self, path): |
||||
if self._ftCbFollow.checkState() == TTkK.Checked: |
||||
if os.path.isfile(path): |
||||
path, _ = os.path.split(path) |
||||
self._fileTree.openPath(path) |
||||
|
||||
def _addFolder(self, dir): |
||||
if not self._recentPath or dir != self._recentPath[-1]: |
||||
self._recentPath.append(dir) |
||||
self._recentPathId = len(self._recentPath)-1 |
||||
if self._recentPathId: |
||||
self._ftBtnPrev.setEnabled() |
||||
self._ftBtnNext.setDisabled() |
||||
|
||||
@pyTTkSlot(TTkFileTreeWidgetItem) |
||||
def _openFolder(self, dir): |
||||
self._fileTree.openPath(dir.path()) |
||||
self._addFolder(dir.path()) |
||||
|
||||
@pyTTkSlot() |
||||
def _openPrev(self): |
||||
if self._recentPathId<=0 or self._recentPathId>=len(self._recentPath): |
||||
self._ftBtnPrev.setDisabled() |
||||
return |
||||
self._recentPathId -= 1 |
||||
self._fileTree.openPath(self._recentPath[self._recentPathId]) |
||||
if self._recentPathId<=0: |
||||
self._ftBtnPrev.setDisabled() |
||||
self._ftBtnNext.setEnabled() |
||||
|
||||
@pyTTkSlot() |
||||
def _openNext(self): |
||||
if self._recentPathId<0 or self._recentPathId>=len(self._recentPath)-1: |
||||
self._ftBtnNext.setDisabled() |
||||
return |
||||
self._recentPathId += 1 |
||||
self._fileTree.openPath(self._recentPath[self._recentPathId]) |
||||
if self._recentPathId>=len(self._recentPath)-1: |
||||
self._ftBtnNext.setDisabled() |
||||
self._ftBtnPrev.setEnabled() |
||||
|
||||
@pyTTkSlot() |
||||
def _openUp(self): |
||||
path = os.path.abspath(self._fileTree.getOpenPath()) |
||||
path, e = os.path.split(path) |
||||
if e: |
||||
self._addFolder(path) |
||||
self._fileTree.openPath(path) |
||||
@ -0,0 +1,307 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['FileViewer','FileViewerSearch','FileViewerArea'] |
||||
|
||||
import TermTk as ttk |
||||
|
||||
from tlogg import TloggHelper, tloggProxy |
||||
|
||||
from . import TloggCfg |
||||
|
||||
class FileViewer(ttk.TTkAbstractScrollView): |
||||
__slots__ = ( |
||||
'_fileBuffer', '_indexesMark', '_indexesSearched', |
||||
'_selected', '_indexing', '_searchRe', |
||||
'_selection', '_pressed' |
||||
# Signals |
||||
'selected', 'marked') |
||||
def __init__(self, *args, **kwargs): |
||||
self._indexesMark = [] |
||||
self._indexesSearched = [] |
||||
self._indexing = None |
||||
self._selected = -1 |
||||
self._selection = None |
||||
self._pressed = False |
||||
self._searchRe = "" |
||||
# Signals |
||||
self.selected = ttk.pyTTkSignal(int) |
||||
self.marked = ttk.pyTTkSignal(list) |
||||
self._fileBuffer = kwargs.get('filebuffer') |
||||
super().__init__(*args, **kwargs) |
||||
self.viewChanged.connect(self._viewChangedHandler) |
||||
self.setFocusPolicy(ttk.TTkK.ClickFocus) |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def _viewChangedHandler(self): |
||||
self.update() |
||||
|
||||
def _copy(self): |
||||
clipboard = ttk.TTkClipboard() |
||||
lines = ttk.TTkString().join([ |
||||
self.getLine(i) for i in range(min(self._selection),max(self._selection)+1) ]) |
||||
clipboard.setText(lines) |
||||
|
||||
@ttk.pyTTkSlot(float) |
||||
def fileIndexing(self, percentage): |
||||
self._indexing = percentage |
||||
self.viewChanged.emit() |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def fileIndexed(self): |
||||
self._indexing = None |
||||
self.viewChanged.emit() |
||||
|
||||
def markIndexes(self, indexes): |
||||
self._indexesMark = indexes |
||||
self.viewChanged.emit() |
||||
|
||||
def searchedIndexes(self, indexes): |
||||
self._indexesSearched = indexes |
||||
self.viewChanged.emit() |
||||
|
||||
def searchRe(self, searchRe): |
||||
self._searchRe = searchRe |
||||
self.update() |
||||
|
||||
def viewFullAreaSize(self) -> (int, int): |
||||
w = 10+self._fileBuffer.getWidth() |
||||
h = self._fileBuffer.getLen() |
||||
return ( w , h ) |
||||
|
||||
def viewDisplayedSize(self) -> (int, int): |
||||
return self.size() |
||||
|
||||
def keyEvent(self, evt): |
||||
# Enable CTRL+C = Copy |
||||
if ( evt.type == ttk.TTkK.SpecialKey and |
||||
evt.mod == ttk.TTkK.ControlModifier and |
||||
evt.key == ttk.TTkK.Key_C ): |
||||
self._copy() |
||||
|
||||
def mousePressEvent(self, evt): |
||||
x,y = evt.x, evt.y |
||||
ox,oy = self.getViewOffsets() |
||||
index = oy+y |
||||
self._selection = [index,index] |
||||
self._pressed = True |
||||
if 0 <= index < self._fileBuffer.getLen(): |
||||
if x<3: |
||||
if index in self._indexesMark: |
||||
self._indexesMark.pop(self._indexesMark.index(index)) |
||||
else: |
||||
self._indexesMark.append(index) |
||||
self.marked.emit(self._indexesMark) |
||||
else: |
||||
self._selected = index |
||||
self.selected.emit(self._selected) |
||||
tloggProxy.lineSelected.emit(self.getLine(self._selected)) |
||||
self.update() |
||||
return True |
||||
return super().mousePressEvent(evt) |
||||
|
||||
def mouseDragEvent(self, evt) -> bool: |
||||
if self._pressed and self._selection: |
||||
w,h = self.size() |
||||
x,y = evt.x, evt.y |
||||
lines=self._fileBuffer.getLen() |
||||
ox,oy = self.getViewOffsets() |
||||
index = oy+y |
||||
self._selection[1] = min(max(0,index),lines-1) |
||||
if y < 0: |
||||
self.viewMoveTo(ox, max(0,min(oy+min(y,3),lines-h))) |
||||
elif y >= h: |
||||
self.viewMoveTo(ox, max(0,min(oy+min(y-h,3),lines-h))) |
||||
else: |
||||
self.update() |
||||
return super().mouseDragEvent(evt) |
||||
|
||||
def mouseReleaseEvent(self, evt) -> bool: |
||||
self._pressed = False |
||||
if self._selection and self._selection[0] != self._selection[1]: |
||||
self._copy() |
||||
self.update() |
||||
return super().mouseReleaseEvent(evt) |
||||
|
||||
@ttk.pyTTkSlot(int) |
||||
def selectAndMove(self, line): |
||||
self._selected = line |
||||
self._selection = [line,line] |
||||
ox,_ = self.getViewOffsets() |
||||
self.viewMoveTo(ox, max(0,line-self.height()//2)) |
||||
self.update() |
||||
|
||||
def getLen(self): |
||||
return self._fileBuffer.getLen() |
||||
|
||||
def getLine(self, num) -> str: |
||||
return self._fileBuffer.getLine(num) |
||||
|
||||
def getLineNum(self, num) -> int: |
||||
return num |
||||
|
||||
def paintEvent(self, canvas): |
||||
ox,oy = self.getViewOffsets() |
||||
bufferLen = self.getLen() |
||||
for i in range(min(self.height(),bufferLen-oy)): |
||||
line = ttk.TTkString(self.getLine(i+oy).replace('\n','')).tab2spaces() |
||||
lineNum = self.getLineNum(i+oy) |
||||
if lineNum in self._indexesMark: |
||||
symbolcolor = ttk.TTkColor.fg("#00ffff") |
||||
numberColor = ttk.TTkColor.bg("#444444") |
||||
symbol='❥' |
||||
elif lineNum in self._indexesSearched: |
||||
symbolcolor = ttk.TTkColor.fg("#ff0000") |
||||
numberColor = ttk.TTkColor.bg("#444444") |
||||
symbol='●' |
||||
else: |
||||
symbolcolor = ttk.TTkColor.fg("#0000ff") |
||||
numberColor = ttk.TTkColor.bg("#444444") |
||||
symbol='○' |
||||
|
||||
if i+oy == self._selected: |
||||
selectedColor = ttk.TTkColor.bg("#008844") |
||||
searchedColor = ttk.TTkColor.fg("#FFFF00")+ttk.TTkColor.bg("#004400") |
||||
line = line.setColor(selectedColor) |
||||
elif self._selection and min(self._selection) <= i+oy <= max(self._selection): |
||||
selectedColor = ttk.TTkColor.bg("#008888") |
||||
searchedColor = ttk.TTkColor.fg("#FFFF00")+ttk.TTkColor.bg("#004400") |
||||
line = line.setColor(selectedColor) |
||||
else: |
||||
selectedColor = ttk.TTkColor.RST |
||||
searchedColor = ttk.TTkColor.fg("#000000")+ttk.TTkColor.bg("#AAAAAA") |
||||
# Check in the filters a matching color |
||||
for color in TloggCfg.colors: |
||||
#TTkLog.debug(f"{color['pattern']} - {line}") |
||||
if m := line.findall(regexp=color['pattern'], ignoreCase=color['ignorecase']): |
||||
selectedColor = ttk.TTkColor.fg(color['fg'])+ttk.TTkColor.bg(color['bg']) |
||||
searchedColor = ttk.TTkColor.fg(color['bg'])+ttk.TTkColor.bg(color['fg']) |
||||
line = line.setColor(selectedColor) |
||||
break |
||||
if self._searchRe: |
||||
if m := line.findall(regexp=self._searchRe, ignoreCase=True): |
||||
for match in m: |
||||
line = line.setColor(searchedColor, match=match) |
||||
|
||||
# Add Line Number |
||||
lenLineNumber = len(str(self.getLineNum(bufferLen-1))) |
||||
lineNumber = ttk.TTkString() + numberColor + str(lineNum).rjust(lenLineNumber) + ttk.TTkColor.RST + ' ' |
||||
# Compose print line |
||||
printLine = ttk.TTkString() + symbolcolor + symbol + ttk.TTkColor.RST + ' ' + lineNumber + line.substring(ox) |
||||
# stupid scramble |
||||
# printLine._text = ''.join([chr(121-(ord(l)-65)) if (65<=ord(l)<=121) else l for l in printLine._text]) |
||||
canvas.drawText(pos=(0,i), text=printLine, color=selectedColor, width=self.width(), ) |
||||
|
||||
# Draw the loading banner |
||||
if self._indexing is not None: |
||||
canvas.drawText(pos=(0,0), text=f" [ Indexed: {int(100*self._indexing)}% ] ") |
||||
|
||||
class FileViewerSearch(FileViewer): |
||||
__slots__ = ('_indexes') |
||||
def __init__(self, *args, **kwargs): |
||||
self._indexes = [] |
||||
FileViewer.__init__(self, *args, **kwargs) |
||||
self._name = kwargs.get('name' , 'FileViewerSearch' ) |
||||
|
||||
def markIndexes(self, indexes): |
||||
self._indexesMark = indexes |
||||
self._indexes = [i for i in sorted(set(self._indexesSearched+self._indexesMark))] |
||||
ox,oy = self.getViewOffsets() |
||||
self.viewMoveTo(ox,oy) |
||||
self.update() |
||||
|
||||
def searchedIndexes(self, indexes): |
||||
# Get the current lineSelected to be used to scroll |
||||
# the search viewer to the similar to previous position |
||||
lineSelected = -1 |
||||
if self._selected > -1: |
||||
lineSelected = self._indexes[self._selected] |
||||
|
||||
self._indexesSearched = indexes |
||||
self._indexes = [i for i in sorted(set(self._indexesSearched+self._indexesMark))] |
||||
ox,_ = self.getViewOffsets() |
||||
|
||||
lineToMove = 0 |
||||
if lineSelected > -1: |
||||
for i, lineNum in enumerate(self._indexes): |
||||
if lineNum >= lineSelected: |
||||
if lineNum == lineSelected: |
||||
self._selected = i |
||||
else: |
||||
self._selected = -1 |
||||
# Try to keep the selected area at the center of the widget |
||||
lineToMove = i if i <= self.height()/2 else int(i-self.height()/2) |
||||
break |
||||
|
||||
self.viewMoveTo(ox, lineToMove) |
||||
self.update() |
||||
|
||||
def viewFullAreaSize(self) -> (int, int): |
||||
if self._indexes is None: |
||||
w = 2+self._fileBuffer.getWidth() |
||||
h = self._fileBuffer.getLen() |
||||
else: |
||||
w = 2+self._fileBuffer.getWidth(self._indexes) |
||||
h = len(self._indexes) |
||||
return w , h |
||||
|
||||
def mousePressEvent(self, evt): |
||||
x,y = evt.x, evt.y |
||||
ox,oy = self.getViewOffsets() |
||||
index = oy+y |
||||
self._selection = [index,index] |
||||
self._pressed = True |
||||
if 0 <= index < len(self._indexes): |
||||
if x<3: |
||||
index = self._indexes[oy+y] |
||||
if index in self._indexesMark: |
||||
self._indexesMark.pop(self._indexesMark.index(index)) |
||||
else: |
||||
self._indexesMark.append(index) |
||||
self.markIndexes(self._indexesMark) |
||||
self.marked.emit(self._indexesMark) |
||||
else: |
||||
self._selected = index |
||||
self.selected.emit(self._indexes[self._selected]) |
||||
tloggProxy.lineSelected.emit(self.getLine(self._selected)) |
||||
self.update() |
||||
return True |
||||
return False |
||||
|
||||
def getLen(self): |
||||
return len(self._indexes) |
||||
|
||||
def getLine(self, num) -> str: |
||||
return self._fileBuffer.getLine(self._indexes[num]) |
||||
|
||||
def getLineNum(self, num) -> int: |
||||
return self._indexes[num] |
||||
|
||||
class FileViewerArea(ttk.TTkAbstractScrollArea): |
||||
__slots__ = ('_fileView') |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
self._name = kwargs.get('name' , 'FileViewer' ) |
||||
if 'parent' in kwargs: kwargs.pop('parent') |
||||
self._fileView = kwargs.get('fileView') |
||||
self.setFocusPolicy(ttk.TTkK.ClickFocus) |
||||
self.setViewport(self._fileView) |
||||
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['TloggGlbl'] |
||||
|
||||
class TloggGlbl: |
||||
_refViews = [] |
||||
@staticmethod |
||||
def addRefView(view): |
||||
TloggGlbl._refViews.append(view) |
||||
|
||||
@staticmethod |
||||
def refreshViews(): |
||||
for view in TloggGlbl._refViews: |
||||
view.update() |
||||
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['highlightersFormLayout'] |
||||
|
||||
import copy |
||||
|
||||
from . import TloggCfg, TloggGlbl |
||||
|
||||
from TermTk import * |
||||
|
||||
def highlightersFormLayout(win): |
||||
''' |
||||
This form is inspired by glogg "Filters..." form |
||||
''' |
||||
|
||||
colors = copy.deepcopy(TloggCfg.colors) |
||||
|
||||
leftRightLayout = TTkHBoxLayout() |
||||
leftLayout = TTkGridLayout() |
||||
rightLayout = TTkGridLayout() |
||||
bottomRightLayout = TTkGridLayout() |
||||
leftRightLayout.addItem(leftLayout) |
||||
leftRightLayout.addItem(rightLayout) |
||||
|
||||
frameColors = TTkFrame(border=True, layout=TTkGridLayout()) |
||||
leftLayout.addWidget(frameColors,0,0,1,5) |
||||
|
||||
listColors = TTkList(parent=frameColors, dragDropMode=TTkK.DragDropMode.AllowDragDrop) |
||||
|
||||
addButton = TTkButton(text="+",maxWidth=3) |
||||
removeButton = TTkButton(text="-",maxWidth=3) |
||||
upButton = TTkButton(text="▲",maxWidth=3) |
||||
downButton = TTkButton(text="▼",maxWidth=3) |
||||
|
||||
leftLayout.addWidget(addButton ,1,0) |
||||
leftLayout.addWidget(removeButton ,1,1) |
||||
leftLayout.addWidget(TTkSpacer() ,1,2) |
||||
leftLayout.addWidget(upButton ,1,3) |
||||
leftLayout.addWidget(downButton ,1,4) |
||||
|
||||
rightLayout.addWidget(TTkLabel(text="Pattern:"),0,0) |
||||
rightLayout.addWidget(pattern:=TTkLineEdit(text="-----"),0,1) |
||||
rightLayout.addWidget(TTkLabel(text="Ignore Case:"),1,0) |
||||
rightLayout.addWidget(ignoreCase:=TTkCheckbox(),1,1) |
||||
rightLayout.addWidget(TTkLabel(text="FG Color:"),2,0) |
||||
rightLayout.addWidget(fgColor := TTkColorButtonPicker(border=False, color=TTkColor.fg('#eeeeee') ),2,1) |
||||
rightLayout.addWidget(TTkLabel(text="BG Color:"),3,0) |
||||
rightLayout.addWidget(bgColor := TTkColorButtonPicker(border=False, color=TTkColor.bg('#333333') ),3,1) |
||||
rightLayout.addWidget(TTkSpacer() ,4,0,1,2) |
||||
|
||||
rightLayout.addItem(bottomRightLayout ,5,0,1,2) |
||||
bottomRightLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1) |
||||
bottomRightLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2) |
||||
bottomRightLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3) |
||||
|
||||
def _move(offset): |
||||
def _moveUpDown(): |
||||
if items := listColors.selectedItems(): |
||||
item = items[0] |
||||
index = listColors.indexOf(item) |
||||
listColors.moveItem(index,index+offset) |
||||
return _moveUpDown |
||||
upButton.clicked.connect(_move(-1)) |
||||
downButton.clicked.connect(_move(1)) |
||||
|
||||
def _addCallback(): |
||||
color = {'pattern':"<PATTERN>", 'ignorecase':True, 'fg':"#FFFFFF", 'bg':"#000000" } |
||||
colors.append(color) |
||||
listColors.addItem(item=color['pattern'],data=color) |
||||
addButton.clicked.connect(_addCallback) |
||||
|
||||
def _removeCallback(): |
||||
# Clear all the signals |
||||
pattern.textEdited.clear() |
||||
ignoreCase.clicked.clear() |
||||
fgColor.colorSelected.clear() |
||||
bgColor.colorSelected.clear() |
||||
if items := listColors.selectedItems(): |
||||
colors.remove(items[0].data) |
||||
listColors.removeItem(items[0]) |
||||
removeButton.clicked.connect(_removeCallback) |
||||
|
||||
def _saveColors(): |
||||
colors = [] |
||||
for item in listColors.items(): |
||||
colors.append(item.data()) |
||||
TloggCfg.colors = colors |
||||
TloggCfg.save(searches=False, filters=False, colors=True, options=False) |
||||
TloggGlbl.refreshViews() |
||||
# TTkHelper.updateAll() |
||||
|
||||
applyBtn.clicked.connect(_saveColors) |
||||
okBtn.clicked.connect(_saveColors) |
||||
okBtn.clicked.connect(win.close) |
||||
cancelBtn.clicked.connect(win.close) |
||||
|
||||
@ttk.pyTTkSlot(TTkAbstractListItem) |
||||
def _listCallback(item): |
||||
if color:=item.data(): |
||||
# Clear all the signals |
||||
pattern.textEdited.clear() |
||||
ignoreCase.clicked.clear() |
||||
fgColor.colorSelected.clear() |
||||
bgColor.colorSelected.clear() |
||||
# Config the color widgets |
||||
pattern.setText(color['pattern']) |
||||
ignoreCase.setCheckState(TTkK.Checked if color['ignorecase'] else TTkK.Unchecked) |
||||
fgColor.setColor(TTkColor.bg(color['fg'])) |
||||
bgColor.setColor(TTkColor.bg(color['bg'])) |
||||
# Connect the actions |
||||
## Pattern Line Edit |
||||
@pyTTkSlot(str) |
||||
def _setPattern(p:TTkString): |
||||
item.setText(str(p)) |
||||
color['pattern'] = str(p) |
||||
pattern.textEdited.connect(_setPattern) |
||||
## Case Sensitivity checkbox |
||||
def _setCase(c):color['ignorecase'] = c |
||||
ignoreCase.clicked.connect(_setCase) |
||||
## Color Button |
||||
def _setFg(c):color['fg'] = c.getHex(TTkK.Background) |
||||
def _setBg(c):color['bg'] = c.getHex(TTkK.Background) |
||||
fgColor.colorSelected.connect(_setFg) |
||||
bgColor.colorSelected.connect(_setBg) |
||||
|
||||
|
||||
listColors.itemClicked.connect(_listCallback) |
||||
|
||||
for i,color in enumerate(colors): |
||||
# ali = TTkAbstractListItem(text=color['pattern'],data=color) |
||||
listColors.addItem(item=color['pattern'],data=color) |
||||
|
||||
return leftRightLayout |
||||
|
||||
def highlightersForm(root=None): |
||||
preferencesLayout = TTkGridLayout(columnMinWidth=1) |
||||
frame = TTkFrame(parent=root, layout=preferencesLayout, border=0) |
||||
|
||||
frameColors = TTkFrame(border=True, title="Highlighters...", layout=highlightersFormLayout()) |
||||
preferencesLayout.addWidget(frameColors,0,0) |
||||
|
||||
return frame |
||||
@ -0,0 +1,106 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['LoggWidget'] |
||||
|
||||
import TermTk as ttk |
||||
|
||||
from .cfg import TloggCfg |
||||
from .glbl import TloggGlbl |
||||
from .fileviewer import FileViewer, FileViewerArea, FileViewerSearch |
||||
from .predefinedfilters import PredefinedFilters |
||||
|
||||
class LoggWidget(ttk.TTkSplitter): |
||||
__slots__ = ('_btn_filters', '_bls_label_1', '_bls_cb_icase', '_bls_search', '_bls_searchbox', |
||||
'_topViewport', '_bottomViewport', |
||||
'_fileBuffer') |
||||
def __init__(self, filename, *args, **kwargs): |
||||
super().__init__(*args, **kwargs|{'orientation':ttk.TTkK.VERTICAL}) |
||||
|
||||
topFrame = ttk.TTkFrame(parent=self, border=False, layout=ttk.TTkVBoxLayout()) |
||||
bottomFrame = ttk.TTkFrame(parent=self, border=False, layout=ttk.TTkVBoxLayout()) |
||||
|
||||
|
||||
# Define the bottom layout widgets |
||||
bottomLayoutSearch = ttk.TTkHBoxLayout() |
||||
self._btn_filters = ttk.TTkButton(text="Filters ^", maxWidth=11) |
||||
self._bls_label_1 = ttk.TTkLabel(text=" Txt:", maxWidth=5) |
||||
self._bls_cb_icase = ttk.TTkCheckbox(text="Aa", maxWidth=5, checked=True) |
||||
self._bls_search = ttk.TTkButton(text="Search", maxWidth=10) |
||||
self._bls_searchbox = ttk.TTkComboBox(editable=True) |
||||
self._bls_searchbox.addItems(TloggCfg.searches) |
||||
self._bls_searchbox.setCurrentIndex(0) |
||||
|
||||
bottomLayoutSearch.addWidget(self._btn_filters) |
||||
bottomLayoutSearch.addWidget(self._bls_label_1) |
||||
bottomLayoutSearch.addWidget(self._bls_searchbox) |
||||
bottomLayoutSearch.addWidget(self._bls_cb_icase) |
||||
bottomLayoutSearch.addWidget(self._bls_search) |
||||
|
||||
bottomFrame.layout().addItem(bottomLayoutSearch) |
||||
|
||||
# Define the main file Viewer |
||||
self._fileBuffer = ttk.TTkFileBuffer(filename, 0x100, 0x1000) |
||||
self._topViewport = FileViewer(filebuffer=self._fileBuffer) |
||||
topViewer = FileViewerArea(parent=topFrame, fileView=self._topViewport) |
||||
self._fileBuffer.indexUpdated.connect(self._topViewport.fileIndexing) |
||||
self._fileBuffer.indexed.connect(self._topViewport.fileIndexed) |
||||
# Define the Search Viewer |
||||
self._bottomViewport = FileViewerSearch(filebuffer=self._fileBuffer) |
||||
bottomViewer = FileViewerArea(parent=bottomFrame, fileView=self._bottomViewport) |
||||
self._bottomViewport.selected.connect(self._topViewport.selectAndMove) |
||||
self._bottomViewport.marked.connect(self._topViewport.markIndexes) |
||||
self._topViewport.marked.connect(self._bottomViewport.markIndexes) |
||||
|
||||
# Add those viewpoers to the global list to allow dynamic refresh |
||||
# TODO: Try to get rid of this |
||||
TloggGlbl.addRefView(self._topViewport) |
||||
TloggGlbl.addRefView(self._bottomViewport) |
||||
|
||||
self._bls_search.clicked.connect(self._search) |
||||
self._bls_searchbox.editTextChanged.connect(self._search) |
||||
|
||||
def _openPredefinedFilters(): |
||||
ttk.TTkHelper.overlay(self._btn_filters, PredefinedFilters(self._bls_searchbox), -2, 1) |
||||
self._btn_filters.clicked.connect(_openPredefinedFilters) |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def _search(self): |
||||
searchtext = str(self._bls_searchbox.currentText()) |
||||
ttk.TTkLog.debug(f"{searchtext=}") |
||||
indexes = self._fileBuffer.searchRe(searchtext, ignoreCase=self._bls_cb_icase.checkState() == ttk.TTkK.Checked) |
||||
self._bottomViewport.searchedIndexes(indexes) |
||||
self._bottomViewport.searchRe(searchtext) |
||||
self._topViewport.searchedIndexes(indexes) |
||||
self._topViewport.searchRe(searchtext) |
||||
if TloggCfg.searches: |
||||
x = set(TloggCfg.searches) |
||||
ttk.TTkLog.debug(f"{x}") |
||||
TloggCfg.searches = list(x) |
||||
if searchtext in TloggCfg.searches: |
||||
TloggCfg.searches.remove(searchtext) |
||||
TloggCfg.searches.insert(0, searchtext) |
||||
TloggCfg.searches = TloggCfg.searches[:TloggCfg.maxsearches] |
||||
TloggCfg.save(searches=True,filters=False) |
||||
self._bls_searchbox.clear() |
||||
self._bls_searchbox.addItems(TloggCfg.searches) |
||||
self._bls_searchbox.setCurrentIndex(0) |
||||
@ -0,0 +1,206 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import os |
||||
import re |
||||
import sys |
||||
import argparse |
||||
|
||||
import appdirs |
||||
|
||||
from TermTk import * |
||||
|
||||
from tlogg import TloggHelper, tloggProxy |
||||
|
||||
from .cfg import * |
||||
from .glbl import * |
||||
from .about import * |
||||
from .loggwidget import LoggWidget |
||||
from .options import optionsFormLayout, optionsLoadTheme |
||||
from .highlighters import highlightersFormLayout |
||||
from .predefinedfilters import PredefinedFiltersFormWindow |
||||
from .notepad import NotePad |
||||
|
||||
|
||||
class TLOGG(TTkGridLayout): |
||||
''' |
||||
┌──────────────────[Main Splitter]─────────┐ |
||||
│┌──────────╥────[File Tab Splitter]──────┐│ |
||||
││ F.Tree ║ Tab ││ |
||||
││ Controls ╟─────────────────────────────┤│ |
||||
│├──────────╢┌───────────────────────────┐││ |
||||
││ File ║│ File Viewer │││ |
||||
││ Tree ║│ │││ |
||||
││ ║╞═══════════════════════════╡││ |
||||
││ ║│ Search Widget │││ |
||||
││ ║│ │││ |
||||
││ ║└───────────────────────────┘││ |
||||
│└──────────╨─────────────────────────────┘│ |
||||
╞══════════════════════════════════════════╡ |
||||
│ Logger,Debug View │ |
||||
└──────────────────────────────────────────┘ |
||||
''' |
||||
__slots__ = ('_kodeTab', '_tloggProxy','_notepad') |
||||
def __init__(self, tloggProxy, *args, **kwargs) -> None: |
||||
self._tloggProxy = tloggProxy |
||||
self._notepad = NotePad() |
||||
|
||||
super().__init__(*args, **kwargs) |
||||
|
||||
self._tloggProxy.setOpenFile(self.openFile) |
||||
|
||||
self.addWidget(appTemplate:=TTkAppTemplate(border=False)) |
||||
|
||||
self._kodeTab = TTkKodeTab(border=False, closable=True) |
||||
|
||||
appTemplate.setMenuBar(appMenuBar:=TTkMenuBarLayout(), TTkAppTemplate.LEFT) |
||||
fileMenu = appMenuBar.addMenu("&File") |
||||
buttonOpen = fileMenu.addMenu("&Open") |
||||
buttonClose = fileMenu.addMenu("&Close") |
||||
fileMenu.addSpacer() |
||||
buttonColors = fileMenu.addMenu("C&olors...") |
||||
buttonFilters = fileMenu.addMenu("&Filters...") |
||||
buttonOptions = fileMenu.addMenu("O&ptions...") |
||||
fileMenu.addSpacer() |
||||
buttonExit = fileMenu.addMenu("E&xit") |
||||
buttonExit.menuButtonClicked.connect(TTkHelper.quit) |
||||
|
||||
extraMenu = appMenuBar.addMenu("E&xtra") |
||||
extraMenu.addMenu("Scratchpad").menuButtonClicked.connect(self.scratchpad) |
||||
extraMenu.addSpacer() |
||||
|
||||
helpMenu = appMenuBar.addMenu("&Help", alignment=TTkK.RIGHT_ALIGN) |
||||
helpMenu.addMenu("About ...").menuButtonClicked.connect(self.showAbout) |
||||
helpMenu.addMenu("About tlogg").menuButtonClicked.connect(self.showAboutTlogg) |
||||
|
||||
def _tabChanged(_,__,widget,data): |
||||
tloggProxy.tloggFocussed.emit(None, data) |
||||
|
||||
self._kodeTab.currentChanged.connect(_tabChanged) |
||||
|
||||
# fileTree.fileActivated.connect(lambda x: self.openFile(x.path())) |
||||
|
||||
buttonOpen.menuButtonClicked.connect(self.openFileCallback) |
||||
buttonColors.menuButtonClicked.connect(self.showColors) |
||||
buttonFilters.menuButtonClicked.connect(self.showFilters) |
||||
buttonOptions.menuButtonClicked.connect(self.showOptions ) |
||||
|
||||
for mod in TloggHelper._getPlugins(): |
||||
if mod.position: |
||||
appTemplate.setWidget(mod.widget, mod.position, 30) |
||||
if mod.menu: |
||||
_menu = extraMenu.addMenu(mod.name, checkable=True, checked=mod.visible) |
||||
mod.widget.setVisible(mod.visible) |
||||
_menu.toggled.connect(mod.widget.setVisible) |
||||
|
||||
|
||||
appTemplate.setWidget(self._kodeTab, TTkAppTemplate.MAIN) |
||||
appTemplate.setWidget(TTkLogViewer(),TTkAppTemplate.BOTTOM,size=1,title="Logs") |
||||
|
||||
@pyTTkSlot() |
||||
def scratchpad(self): |
||||
win = TTkWindow( |
||||
title="Mr Scratchpad", |
||||
size=(80,30), |
||||
layout=self._notepad, |
||||
flags=TTkK.WindowFlag.WindowMaximizeButtonHint|TTkK.WindowFlag.WindowCloseButtonHint) |
||||
TTkHelper.overlay(None, win, 2, 2, toolWindow=True) |
||||
|
||||
@pyTTkSlot(TTkMenuButton) |
||||
def openFileCallback(self, btn): |
||||
filePicker = TTkFileDialogPicker( |
||||
pos = (3,3), size=(90,30), |
||||
caption="Open a File", path=".", |
||||
filter="All Files (*);;Text Files (*.txt);;Log Files (*.log)") |
||||
filePicker.filePicked.connect(self.openFile) |
||||
TTkHelper.overlay(btn, filePicker, 10, 2, modal=True) |
||||
|
||||
@pyTTkSlot(TTkMenuButton) |
||||
def showColors(self, btn): |
||||
win = TTkWindow(title="Highlighters...", size=(70,20), border=True) |
||||
win.setLayout(highlightersFormLayout(win)) |
||||
TTkHelper.overlay(btn, win, 10,2, modal=True) |
||||
|
||||
@pyTTkSlot(TTkMenuButton) |
||||
def showFilters(self, btn): |
||||
win = PredefinedFiltersFormWindow(title="Predefined Filters...", size=(70,20), border=True) |
||||
TTkHelper.overlay(btn, win, 10,2, modal=True) |
||||
|
||||
@pyTTkSlot(TTkMenuButton) |
||||
def showOptions(self, btn): |
||||
win = TTkWindow(title="Options...", size=(70,20), border=True) |
||||
win.setLayout(optionsFormLayout(win)) |
||||
TTkHelper.overlay(btn, win, 10,2, modal=True) |
||||
|
||||
# tab.addTab(h |
||||
# @pyTTkSlot()ighlightersForm(), "-Setup-") |
||||
@pyTTkSlot(TTkMenuButton) |
||||
def showAbout(self, btn): |
||||
TTkHelper.overlay(btn, TTkAbout(), 20,5) |
||||
|
||||
@pyTTkSlot(TTkMenuButton) |
||||
def showAboutTlogg(self, btn): |
||||
TTkHelper.overlay(btn, About(), 20,5) |
||||
|
||||
def openFile(self, file): |
||||
# openedFiles.append(file) |
||||
loggWidget = LoggWidget(file) |
||||
self._kodeTab.addTab(widget=loggWidget, label=os.path.basename(file), data=file) |
||||
self._kodeTab.setCurrentWidget(loggWidget) |
||||
|
||||
def main(): |
||||
TloggCfg.pathCfg = appdirs.user_config_dir("tlogg") |
||||
|
||||
parser = argparse.ArgumentParser() |
||||
# parser.add_argument('-f', help='Full Screen', action='store_true') |
||||
parser.add_argument('-c', help=f'config folder (default: "{TloggCfg.pathCfg}")', default=TloggCfg.pathCfg) |
||||
parser.add_argument('filename', type=str, nargs='*', |
||||
help='the filename/s') |
||||
args = parser.parse_args() |
||||
|
||||
# TTkLog.use_default_file_logging() |
||||
|
||||
TloggCfg.pathCfg = args.c |
||||
TTkLog.debug(f"Config Path: {TloggCfg.pathCfg}") |
||||
|
||||
TloggCfg.load() |
||||
|
||||
if 'theme' not in TloggCfg.options: |
||||
TloggCfg.options['theme'] = 'UTF8' |
||||
optionsLoadTheme(TloggCfg.options['theme']) |
||||
|
||||
TloggHelper._loadPlugins() |
||||
|
||||
root = TTk( |
||||
title="tlogg", |
||||
layout=(tlogg:=TLOGG(tloggProxy=tloggProxy)), |
||||
sigmask=( |
||||
TTkTerm.Sigmask.CTRL_C | |
||||
TTkTerm.Sigmask.CTRL_Q | |
||||
TTkTerm.Sigmask.CTRL_S | |
||||
TTkTerm.Sigmask.CTRL_Z )) |
||||
TloggHelper._runPlugins() |
||||
|
||||
for file in args.filename: |
||||
tlogg.openFile(file) |
||||
|
||||
root.mainloop() |
||||
@ -0,0 +1,177 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['NotePad'] |
||||
|
||||
import TermTk as ttk |
||||
|
||||
class SuperSimpleHorizontalLine(ttk.TTkWidget): |
||||
def paintEvent(self, canvas): |
||||
w,h = self.size() |
||||
canvas.drawText(pos=(0,h-1), text='┕'+('━'*(w-2))+'┙',color=ttk.TTkColor.fg("#888888")) |
||||
|
||||
class NotePad(ttk.TTkGridLayout): |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
|
||||
te = ttk.TTkTextEdit(readOnly=False, lineNumber=True) |
||||
|
||||
self.addItem(wrapLayout := ttk.TTkGridLayout(), 0,0) |
||||
self.addItem(fontLayout := ttk.TTkGridLayout(columnMinWidth=1), 1,0) |
||||
self.addWidget(te,2,0) |
||||
|
||||
wrapLayout.addWidget(ttk.TTkLabel(text="Wrap: ", maxWidth=6),0,0) |
||||
wrapLayout.addWidget(lineWrap := ttk.TTkComboBox(list=['NoWrap','WidgetWidth','FixedWidth'], maxWidth=20),0,1) |
||||
wrapLayout.addWidget(ttk.TTkLabel(text=" Type: ",maxWidth=7),0,2) |
||||
wrapLayout.addWidget(wordWrap := ttk.TTkComboBox(list=['WordWrap','WrapAnywhere'], maxWidth=20, enabled=False),0,3) |
||||
wrapLayout.addWidget(ttk.TTkLabel(text=" FixW: ",maxWidth=7),0,4) |
||||
wrapLayout.addWidget(fixWidth := ttk.TTkSpinBox(value=te.wrapWidth(), maxWidth=5, maximum=500, minimum=10, enabled=False),0,5) |
||||
wrapLayout.addWidget(ttk.TTkSpacer(),0,10) |
||||
|
||||
# Empty columns/cells are 1 char wide due to "columnMinWidth=1" parameter in the GridLayout |
||||
# 1 3 8 11 |
||||
# 0 2 4 5 6 7 9 10 12 |
||||
# 0 [ ] FG [ ] BG [ ] LineNumber |
||||
# 1 ┌─────┐ ┌─────┐ ╒═══╕╒═══╕╒═══╕╒═══╕ ┌──────┐┌──────┐ |
||||
# 2 │ │ │ │ │ a ││ a ││ a ││ a │ │ UNDO ││ REDO │ |
||||
# 3 └─────┘ └─────┘ └───┘└───┘└───┘└───┘ ╘══════╛└──────┘ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙ |
||||
|
||||
# Char Fg/Bg buttons |
||||
fontLayout.addWidget(cb_fg := ttk.TTkCheckbox(text=" FG"),0,0) |
||||
fontLayout.addWidget(btn_fgColor := ttk.TTkColorButtonPicker(border=True, enabled=False, maxSize=(7,3)),1,0) |
||||
|
||||
fontLayout.addWidget(cb_bg := ttk.TTkCheckbox(text=" BG"),0,2) |
||||
fontLayout.addWidget(btn_bgColor := ttk.TTkColorButtonPicker(border=True, enabled=False, maxSize=(7 ,3)),1,2) |
||||
|
||||
fontLayout.addWidget(cb_linenumber := ttk.TTkCheckbox(text=" LineNumber", checked=True),0,4,1,3) |
||||
|
||||
# Char style buttons |
||||
fontLayout.addWidget(btn_bold := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString( 'a' , ttk.TTkColor.BOLD) ),1,4) |
||||
fontLayout.addWidget(btn_italic := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString( 'a' , ttk.TTkColor.ITALIC) ),1,5) |
||||
fontLayout.addWidget(btn_underline := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString(' a ', ttk.TTkColor.UNDERLINE) ),1,6) |
||||
fontLayout.addWidget(btn_strikethrough := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString(' a ', ttk.TTkColor.STRIKETROUGH)),1,7) |
||||
|
||||
# Undo/Redo buttons |
||||
fontLayout.addWidget(btn_undo := ttk.TTkButton(border=True, maxSize=(8,3), enabled=te.isUndoAvailable(), text=' UNDO '),1,9 ) |
||||
fontLayout.addWidget(btn_redo := ttk.TTkButton(border=True, maxSize=(8,3), enabled=te.isRedoAvailable(), text=' REDO '),1,10) |
||||
# Undo/Redo events |
||||
te.undoAvailable.connect(btn_undo.setEnabled) |
||||
te.redoAvailable.connect(btn_redo.setEnabled) |
||||
btn_undo.clicked.connect(te.undo) |
||||
btn_redo.clicked.connect(te.redo) |
||||
|
||||
# Useless custom horizontal bar for aestetic reason |
||||
fontLayout.addWidget(SuperSimpleHorizontalLine(),0,12,2,1) |
||||
|
||||
@ttk.pyTTkSlot(ttk.TTkColor) |
||||
def _currentColorChangedCB(format): |
||||
if fg := format.foreground(): |
||||
cb_fg.setCheckState(ttk.TTkK.Checked) |
||||
btn_fgColor.setEnabled() |
||||
btn_fgColor.setColor(fg.invertFgBg()) |
||||
else: |
||||
cb_fg.setCheckState(ttk.TTkK.Unchecked) |
||||
btn_fgColor.setDisabled() |
||||
|
||||
if bg := format.background(): |
||||
cb_bg.setCheckState(ttk.TTkK.Checked) |
||||
btn_bgColor.setEnabled() |
||||
btn_bgColor.setColor(bg) |
||||
else: |
||||
cb_bg.setCheckState(ttk.TTkK.Unchecked) |
||||
btn_bgColor.setDisabled() |
||||
|
||||
btn_bold.setChecked(format.bold()) |
||||
btn_italic.setChecked(format.italic()) |
||||
btn_underline.setChecked(format.underline()) |
||||
btn_strikethrough.setChecked(format.strikethrough()) |
||||
# ttk.TTkLog.debug(f"{fg=} {bg=} {bold=} {italic=} {underline=} {strikethrough= }") |
||||
|
||||
te.currentColorChanged.connect(_currentColorChangedCB) |
||||
|
||||
def _setStyle(): |
||||
color = ttk.TTkColor() |
||||
if cb_fg.checkState() == ttk.TTkK.Checked: |
||||
color += btn_fgColor.color().invertFgBg() |
||||
if cb_bg.checkState() == ttk.TTkK.Checked: |
||||
color += btn_bgColor.color() |
||||
if btn_bold.isChecked(): |
||||
color += ttk.TTkColor.BOLD |
||||
if btn_italic.isChecked(): |
||||
color += ttk.TTkColor.ITALIC |
||||
if btn_underline.isChecked(): |
||||
color += ttk.TTkColor.UNDERLINE |
||||
if btn_strikethrough.isChecked(): |
||||
color += ttk.TTkColor.STRIKETROUGH |
||||
cursor = te.textCursor() |
||||
cursor.applyColor(color) |
||||
cursor.setColor(color) |
||||
te.setFocus() |
||||
|
||||
cb_fg.stateChanged.connect(lambda x: btn_fgColor.setEnabled(x==ttk.TTkK.Checked)) |
||||
cb_bg.stateChanged.connect(lambda x: btn_bgColor.setEnabled(x==ttk.TTkK.Checked)) |
||||
cb_fg.clicked.connect(lambda _: _setStyle()) |
||||
cb_bg.clicked.connect(lambda _: _setStyle()) |
||||
|
||||
cb_linenumber.stateChanged.connect(lambda x: te.setLineNumber(x==ttk.TTkK.Checked)) |
||||
|
||||
btn_fgColor.colorSelected.connect(lambda _: _setStyle()) |
||||
btn_bgColor.colorSelected.connect(lambda _: _setStyle()) |
||||
|
||||
btn_bold.clicked.connect(_setStyle) |
||||
btn_italic.clicked.connect(_setStyle) |
||||
btn_underline.clicked.connect(_setStyle) |
||||
btn_strikethrough.clicked.connect(_setStyle) |
||||
|
||||
lineWrap.setCurrentIndex(0) |
||||
wordWrap.setCurrentIndex(1) |
||||
|
||||
fixWidth.valueChanged.connect(te.setWrapWidth) |
||||
|
||||
@ttk.pyTTkSlot(int) |
||||
def _lineWrapCallback(index): |
||||
if index == 0: |
||||
te.setLineWrapMode(ttk.TTkK.NoWrap) |
||||
wordWrap.setDisabled() |
||||
fixWidth.setDisabled() |
||||
elif index == 1: |
||||
te.setLineWrapMode(ttk.TTkK.WidgetWidth) |
||||
wordWrap.setEnabled() |
||||
fixWidth.setDisabled() |
||||
else: |
||||
te.setLineWrapMode(ttk.TTkK.FixedWidth) |
||||
te.setWrapWidth(fixWidth.value()) |
||||
wordWrap.setEnabled() |
||||
fixWidth.setEnabled() |
||||
|
||||
lineWrap.currentIndexChanged.connect(_lineWrapCallback) |
||||
|
||||
@ttk.pyTTkSlot(int) |
||||
def _wordWrapCallback(index): |
||||
if index == 0: |
||||
te.setWordWrapMode(ttk.TTkK.WordWrap) |
||||
else: |
||||
te.setWordWrapMode(ttk.TTkK.WrapAnywhere) |
||||
|
||||
wordWrap.currentIndexChanged.connect(_wordWrapCallback) |
||||
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['optionsLoadTheme', 'optionsFormLayout'] |
||||
|
||||
import copy |
||||
|
||||
from . import TloggCfg, TloggGlbl |
||||
|
||||
from TermTk import * |
||||
|
||||
def optionsLoadTheme(theme): |
||||
if theme == 'ASCII': |
||||
TTkTheme.loadTheme(TTkTheme.ASCII) |
||||
elif theme == 'UTF8': |
||||
TTkTheme.loadTheme(TTkTheme.UTF8) |
||||
elif theme == 'NERD': |
||||
TTkTheme.loadTheme(TTkTheme.NERD) |
||||
|
||||
def optionsFormLayout(win): |
||||
options = copy.deepcopy(TloggCfg.options) |
||||
|
||||
retLayout = TTkGridLayout() |
||||
bottomLayout = TTkGridLayout() |
||||
|
||||
themesFrame = TTkFrame(title="Theme", border=True, layout=TTkVBoxLayout(), maxHeight=5, minHeight=5) |
||||
# Themes |
||||
themesFrame.layout().addWidget(r1 := TTkRadioButton(text="ASCII", name="theme", checked=options['theme'] == 'ASCII')) |
||||
themesFrame.layout().addWidget(r2 := TTkRadioButton(text="UTF-8", name="theme", checked=options['theme'] == 'UTF8')) |
||||
themesFrame.layout().addWidget(r3 := TTkRadioButton(text="Nerd", name="theme", checked=options['theme'] == 'NERD')) |
||||
|
||||
retLayout.addWidget(themesFrame,0,0) |
||||
retLayout.addWidget(TTkSpacer() ,1,0,1,2) |
||||
|
||||
retLayout.addItem(bottomLayout ,2,0,1,2) |
||||
bottomLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1) |
||||
bottomLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2) |
||||
bottomLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3) |
||||
|
||||
def _saveOptions(): |
||||
if r1.checkState() == TTkK.Checked: options['theme'] = 'ASCII' |
||||
if r2.checkState() == TTkK.Checked: options['theme'] = 'UTF8' |
||||
if r3.checkState() == TTkK.Checked: options['theme'] = 'NERD' |
||||
TloggCfg.options = options |
||||
TloggCfg.save(searches=False, filters=False, colors=False, options=True) |
||||
optionsLoadTheme(options['theme']) |
||||
TloggGlbl.refreshViews() |
||||
TTkHelper.updateAll() |
||||
|
||||
applyBtn.clicked.connect(_saveOptions) |
||||
okBtn.clicked.connect(_saveOptions) |
||||
okBtn.clicked.connect(win.close) |
||||
cancelBtn.clicked.connect(win.close) |
||||
|
||||
return retLayout |
||||
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['PredefinedFilters','PredefinedFiltersFormWindow'] |
||||
|
||||
import copy |
||||
from readline import insert_text |
||||
|
||||
from . import TloggCfg, TloggGlbl |
||||
|
||||
from TermTk import * |
||||
|
||||
class PredefinedFiltersFormWindow(TTkWindow): |
||||
''' |
||||
This form is inspired by klogg "PredefinedFilters..." form |
||||
''' |
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
TTkWindow.__init__(self, *args, **kwargs) |
||||
self._name = kwargs.get('name' , 'PredefinedFiltersFormWindow' ) |
||||
|
||||
self._filters = copy.deepcopy(TloggCfg.filters) |
||||
self._selected = -1 |
||||
|
||||
self._retLayout = TTkVBoxLayout() # Full Layout |
||||
self._controlLayout = TTkGridLayout() # Layout including the add/remove/move buttons |
||||
self._filtersLayout = TTkGridLayout() # Layout having the filters form |
||||
self._bottomLayout = TTkGridLayout() # Layout including the OK,Cancel,Apply Buttons |
||||
|
||||
self.setLayout(self._retLayout) |
||||
|
||||
self._retLayout.addItem(self._controlLayout) |
||||
self._retLayout.addItem(self._filtersLayout) |
||||
self._retLayout.addItem(self._bottomLayout) |
||||
|
||||
self._addButton = TTkButton(text="+",maxWidth=3) |
||||
self._removeButton = TTkButton(text="-",maxWidth=3) |
||||
self._upButton = TTkButton(text="▲",maxWidth=3) |
||||
self._downButton = TTkButton(text="▼",maxWidth=3) |
||||
|
||||
self._controlLayout.addWidget(self._addButton ,1,0) |
||||
self._controlLayout.addWidget(self._removeButton ,1,1) |
||||
self._controlLayout.addWidget(TTkSpacer(maxHeight=1) ,1,2) |
||||
self._controlLayout.addWidget(self._upButton ,1,3) |
||||
self._controlLayout.addWidget(self._downButton ,1,4) |
||||
|
||||
self._bottomLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1) |
||||
self._bottomLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2) |
||||
self._bottomLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3) |
||||
|
||||
self._refreshFilters() |
||||
|
||||
def _move(offset): |
||||
def _moveUpDown(): |
||||
if self._selected < 0: return |
||||
if self._selected + offset < 0: return |
||||
if self._selected + offset >= len(self._filters): return |
||||
item = self._filters.pop(self._selected) |
||||
self._filters = self._filters[:self._selected+offset] + [item] + self._filters[self._selected+offset:] |
||||
self._refreshFilters() |
||||
self._selected += offset |
||||
return _moveUpDown |
||||
self._upButton.clicked.connect(_move(-1)) |
||||
self._downButton.clicked.connect(_move(1)) |
||||
|
||||
def _addCallback(): |
||||
filter = {'name':f'n:{len(self._filters)+1}', 'pattern':'' } |
||||
self._filters.append(filter) |
||||
self._selected = -1 |
||||
self._refreshFilters() |
||||
self._addButton.clicked.connect(_addCallback) |
||||
|
||||
def _removeCallback(): |
||||
if self._selected > -1: |
||||
self._filters.pop(self._selected) |
||||
self.selected = -1 |
||||
self._refreshFilters() |
||||
self._removeButton.clicked.connect(_removeCallback) |
||||
|
||||
def _saveFilters(): |
||||
TloggCfg.filters = self._filters |
||||
TloggCfg.save(searches=False, filters=True, colors=False, options=False) |
||||
|
||||
applyBtn.clicked.connect(_saveFilters) |
||||
okBtn.clicked.connect(_saveFilters) |
||||
okBtn.clicked.connect(self.close) |
||||
cancelBtn.clicked.connect(self.close) |
||||
|
||||
def _refreshFilters(self): |
||||
for item in self._filtersLayout.children(): |
||||
self._filtersLayout.removeItem(item) |
||||
splitter = TTkSplitter(border=True) |
||||
names = TTkVBoxLayout() |
||||
patterns = TTkVBoxLayout() |
||||
splitter.addWidget(TTkFrame(border=False, layout=names)) |
||||
splitter.addWidget(TTkFrame(border=False, layout=patterns)) |
||||
splitter.setSizes([20,100]) |
||||
for i, filter in enumerate(self._filters): |
||||
names.addWidget( nl := TTkLineEdit(text=filter['name'])) |
||||
patterns.addWidget(pl := TTkLineEdit(text=filter['pattern'])) |
||||
# Stupid way to attach the textEdited signal directly to the array modification |
||||
# It could have been made much better but Lazyness is an Issue |
||||
def _setName(pos): |
||||
def _set(txt): self._filters[pos]['name'] = txt |
||||
return _set |
||||
def _setPattern(pos): |
||||
def _set(txt): self._filters[pos]['pattern'] = txt |
||||
return _set |
||||
def _setSelected(pos): |
||||
def _set(focus): |
||||
if focus: |
||||
self._selected = pos |
||||
return _set |
||||
nl.textEdited.connect(_setName(i)) |
||||
pl.textEdited.connect(_setPattern(i)) |
||||
nl.focusChanged.connect(_setSelected(i)) |
||||
pl.focusChanged.connect(_setSelected(i)) |
||||
|
||||
names.addWidget( TTkSpacer()) |
||||
patterns.addWidget(TTkSpacer()) |
||||
self._filtersLayout.addWidget(splitter) |
||||
|
||||
|
||||
class PredefinedFilters(TTkResizableFrame): |
||||
__slots__ = ('checked', 'unchecked', '_searchbox') |
||||
def __init__(self, searchbox): |
||||
layout=TTkVBoxLayout() |
||||
TTkResizableFrame.__init__(self, layout=layout) |
||||
self._name = 'PredefinedFilters' |
||||
|
||||
self._searchbox = searchbox |
||||
|
||||
filters = copy.deepcopy(TloggCfg.filters) |
||||
for filter in filters: |
||||
def _cb(p, sb): |
||||
def _ret(state): |
||||
txt = sb.currentText() |
||||
if state == TTkK.Checked: |
||||
if not txt: |
||||
txt = p |
||||
elif txt.find(p) == -1: |
||||
txt += '|'+p |
||||
else: |
||||
p1 = f"|{p}" |
||||
p2 = f"{p}|" |
||||
if txt.find(p1) != -1: |
||||
txt = txt.replace(p1,'') |
||||
elif txt.find(p2) != -1: |
||||
txt = txt.replace(p2,'') |
||||
else: |
||||
txt = txt.replace(p,'') |
||||
sb.setCurrentText(txt) |
||||
return _ret |
||||
|
||||
layout.addWidget(cb := TTkCheckbox(text=filter['name'],checked=searchbox.currentText().find(filter['pattern'])!=-1)) |
||||
cb.stateChanged.connect(_cb(filter['pattern'], searchbox)) |
||||
w,h = self.minimumSize() |
||||
self.resize(w,h) |
||||
|
||||
@ -0,0 +1,82 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import importlib, pkgutil |
||||
import runpy |
||||
import sys, os |
||||
|
||||
import TermTk as ttk |
||||
from tlogg import TloggPlugin |
||||
|
||||
class TloggHelper(): |
||||
@staticmethod |
||||
def _loadPlugins(): |
||||
# Check for the plugin folder |
||||
pluginFolder = os.path.join(os.path.dirname(os.path.abspath(__file__)),'plugins') |
||||
if not os.path.exists(pluginFolder): |
||||
ttk.TTkLog.error("No 'plugins' folder found in the 'tlogg' main directory") |
||||
else: |
||||
for fn in os.listdir(pluginFolder): |
||||
filePath = os.path.join(pluginFolder,fn) |
||||
if not os.path.isfile(filePath): continue |
||||
absolute_name = importlib.util.resolve_name(filePath, None) |
||||
ttk.TTkLog.debug(absolute_name) |
||||
loader = importlib.machinery.SourceFileLoader(fn, filePath) |
||||
spec = importlib.util.spec_from_loader(loader.name, loader) |
||||
mod = importlib.util.module_from_spec(spec) |
||||
loader.exec_module(mod) |
||||
|
||||
# Check installed plugins |
||||
for finder, name, ispkg in pkgutil.iter_modules(): |
||||
if name.startswith("tlogg_"): |
||||
loader = importlib.find_loader(name) |
||||
spec = importlib.util.find_spec(name) |
||||
mod = importlib.util.module_from_spec(spec) |
||||
# runpy.run_module(name) |
||||
loader.exec_module(mod) |
||||
|
||||
# check the plugin folder |
||||
for mod in TloggPlugin.instances: |
||||
if mod.init is not None: |
||||
mod.init() |
||||
|
||||
@staticmethod |
||||
def _runPlugins(): |
||||
for mod in TloggPlugin.instances: |
||||
if mod.apply is not None: |
||||
mod.apply() |
||||
|
||||
@staticmethod |
||||
def _getPlugins(): |
||||
return TloggPlugin.instances |
||||
|
||||
@staticmethod |
||||
def _getPluginPlacements(): |
||||
ret = ttk.TTkK.NONE |
||||
for mod in TloggPlugin.instances: |
||||
ret |= mod.position |
||||
return ret |
||||
|
||||
@staticmethod |
||||
def _getPlacedPlugins(placement): |
||||
return [mod for mod in TloggPlugin.instances if mod.position & placement] |
||||
|
||||
@ -0,0 +1,41 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
from dataclasses import dataclass |
||||
from typing import Callable |
||||
|
||||
import TermTk as ttk |
||||
|
||||
@dataclass |
||||
class TloggPlugin: |
||||
instances = [] |
||||
name : str |
||||
init : Callable[[],None] = None |
||||
apply : Callable[[],None] = None |
||||
run : Callable[[],None] = None |
||||
position : int = ttk.TTkK.NONE # Accepted Values are ; NONE, LEFT, RIGHT |
||||
widget : ttk.TTkWidget = None # Required if a position is defined |
||||
menu : bool = False |
||||
visible: bool = False |
||||
|
||||
def __post_init__(self): |
||||
TloggPlugin.instances.append(self) |
||||
@ -0,0 +1,127 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import os |
||||
|
||||
import TermTk as ttk |
||||
|
||||
import tlogg |
||||
|
||||
class FileTree(ttk.TTkContainer): |
||||
__slots__ = ( |
||||
'_recentPath', '_recentPathId', |
||||
'_controlsLayout', |
||||
'_ftCbFollow', '_ftBtnPrev', '_ftBtnNext', '_ftBtnUp', '_fileTree', |
||||
# Forwarded Methods |
||||
'fileActivated') |
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
self._name = kwargs.get('name' , 'FileTree' ) |
||||
|
||||
self.setLayout(ttk.TTkVBoxLayout()) |
||||
|
||||
self._controlsLayout = ttk.TTkHBoxLayout() |
||||
self.layout().addItem(self._controlsLayout) |
||||
|
||||
self._ftCbFollow = ttk.TTkCheckbox(text="Follow", maxWidth=9, checked=False) |
||||
self._ftBtnPrev = ttk.TTkButton(text="<",maxWidth=3, enabled=False) |
||||
self._ftBtnNext = ttk.TTkButton(text=">",maxWidth=3, enabled=False) |
||||
self._ftBtnUp = ttk.TTkButton(text="^",maxWidth=3, enabled=True) |
||||
self._controlsLayout.addWidget(self._ftCbFollow) |
||||
self._controlsLayout.addWidget(ttk.TTkSpacer()) |
||||
self._controlsLayout.addWidget(self._ftBtnPrev) |
||||
self._controlsLayout.addWidget(self._ftBtnNext) |
||||
self._controlsLayout.addWidget(self._ftBtnUp) |
||||
|
||||
self._fileTree = ttk.TTkFileTree(parent=self, path=".") |
||||
|
||||
self._recentPath = [] |
||||
self._recentPathId = -1 |
||||
self._addFolder(os.path.abspath('.')) |
||||
|
||||
self._ftBtnPrev.clicked.connect(self._openPrev) |
||||
self._ftBtnNext.clicked.connect(self._openNext) |
||||
self._ftBtnUp.clicked.connect(self._openUp) |
||||
|
||||
self._fileTree.folderActivated.connect(self._openFolder) |
||||
|
||||
# Forward Functions |
||||
self._fileTree.fileActivated.connect(lambda x: tlogg.tloggProxy.openFile(x.path())) |
||||
|
||||
tlogg.tloggProxy.tloggFocussed.connect(self.follow) |
||||
|
||||
@ttk.pyTTkSlot(tlogg.TloggViewerProxy, str) |
||||
def follow(self, _, path): |
||||
if self._ftCbFollow.checkState() == ttk.TTkK.Checked: |
||||
if os.path.isfile(path): |
||||
path, _ = os.path.split(path) |
||||
self._fileTree.openPath(path) |
||||
|
||||
def _addFolder(self, dir): |
||||
if not self._recentPath or dir != self._recentPath[-1]: |
||||
self._recentPath.append(dir) |
||||
self._recentPathId = len(self._recentPath)-1 |
||||
if self._recentPathId: |
||||
self._ftBtnPrev.setEnabled() |
||||
self._ftBtnNext.setDisabled() |
||||
|
||||
@ttk.pyTTkSlot(ttk.TTkFileTreeWidgetItem) |
||||
def _openFolder(self, dir): |
||||
self._fileTree.openPath(dir.path()) |
||||
self._addFolder(dir.path()) |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def _openPrev(self): |
||||
if self._recentPathId<=0 or self._recentPathId>=len(self._recentPath): |
||||
self._ftBtnPrev.setDisabled() |
||||
return |
||||
self._recentPathId -= 1 |
||||
self._fileTree.openPath(self._recentPath[self._recentPathId]) |
||||
if self._recentPathId<=0: |
||||
self._ftBtnPrev.setDisabled() |
||||
self._ftBtnNext.setEnabled() |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def _openNext(self): |
||||
if self._recentPathId<0 or self._recentPathId>=len(self._recentPath)-1: |
||||
self._ftBtnNext.setDisabled() |
||||
return |
||||
self._recentPathId += 1 |
||||
self._fileTree.openPath(self._recentPath[self._recentPathId]) |
||||
if self._recentPathId>=len(self._recentPath)-1: |
||||
self._ftBtnNext.setDisabled() |
||||
self._ftBtnPrev.setEnabled() |
||||
|
||||
@ttk.pyTTkSlot() |
||||
def _openUp(self): |
||||
path = os.path.abspath(self._fileTree.getOpenPath()) |
||||
path, e = os.path.split(path) |
||||
if e: |
||||
self._addFolder(path) |
||||
self._fileTree.openPath(path) |
||||
|
||||
tlogg.TloggPlugin( |
||||
name="File Explorer", |
||||
position=ttk.TTkK.LEFT, |
||||
visible=True, |
||||
widget=FileTree()) |
||||
@ -0,0 +1,56 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import json |
||||
|
||||
from pygments import highlight |
||||
from pygments.lexers import PythonLexer, JavascriptLexer |
||||
from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter |
||||
|
||||
import TermTk as ttk |
||||
|
||||
import tlogg |
||||
|
||||
class JsonViewer(ttk.TTkTextEdit): |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
self.setLineWrapMode(ttk.TTkK.WidgetWidth) |
||||
self.setWordWrapMode(ttk.TTkK.WordWrap) |
||||
tlogg.tloggProxy.lineSelected.connect(self._showLine) |
||||
|
||||
ttk.pyTTkSlot(str) |
||||
def _showLine(self, text): |
||||
try: |
||||
text = json.loads(text) |
||||
text = json.dumps(text, indent=4) |
||||
text = highlight(text, JavascriptLexer(), TerminalTrueColorFormatter(style='material')) |
||||
|
||||
except ValueError as e: |
||||
pass |
||||
self.setText(text) |
||||
|
||||
tlogg.TloggPlugin( |
||||
name="Json Viewer", |
||||
position=ttk.TTkK.RIGHT, |
||||
menu=True, |
||||
visible=False, |
||||
widget=JsonViewer(lineNumber=True, readOnly=False, )) |
||||
@ -0,0 +1,33 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import TermTk as ttk |
||||
|
||||
import tlogg |
||||
|
||||
def init(): |
||||
ttk.TTkLog.debug("Test Plugin Init") |
||||
|
||||
def apply(): |
||||
ttk.TTkLog.debug("Test Plugin Apply") |
||||
|
||||
tlogg.TloggPlugin(name="Test Plugin", init=init, apply=apply) |
||||
@ -0,0 +1,36 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import TermTk as ttk |
||||
|
||||
import tlogg |
||||
|
||||
def init(): |
||||
ttk.TTkLog.debug("Test Plugin1 Init") |
||||
|
||||
def apply(): |
||||
ttk.TTkLog.debug("Test Plugin1 Apply") |
||||
|
||||
def run(): |
||||
ttk.TTkLog.debug("Test Plugin1 Run") |
||||
|
||||
tlogg.TloggPlugin(name="Test Plugin 1", init=init, apply=apply, run=run) |
||||
@ -0,0 +1,50 @@
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
__all__ = ['TloggViewerProxy', 'tloggProxy'] |
||||
|
||||
import TermTk as ttk |
||||
|
||||
class TloggViewerProxy(): |
||||
__slots__ = ('_fileName') |
||||
def __init__(self, fileName) -> None: |
||||
self._fileName = fileName |
||||
|
||||
def fileName(self): |
||||
return self._fileName |
||||
|
||||
class TloggProxy(): |
||||
__slots__ = ('_openFileCb', |
||||
# Signals |
||||
'tloggFocussed', 'lineSelected') |
||||
def __init__(self) -> None: |
||||
self._openFileCb = lambda _ : None |
||||
self.tloggFocussed = ttk.pyTTkSignal(TloggViewerProxy, str) |
||||
self.lineSelected = ttk.pyTTkSignal(str) |
||||
|
||||
def setOpenFile(self, cb): |
||||
self._openFileCb = cb |
||||
|
||||
def openFile(self, fileName): |
||||
return self._openFileCb(fileName) |
||||
|
||||
tloggProxy = TloggProxy() |
||||
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# MIT License |
||||
# |
||||
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
import sys |
||||
import random, json |
||||
|
||||
words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] |
||||
def getWord(): |
||||
return random.choice(words) |
||||
def getSentence(a,b): |
||||
return " ".join([getWord() for i in range(0,random.randint(a,b))]) |
||||
|
||||
|
||||
if len(sys.argv) != 3 : |
||||
print ("Missing filename") |
||||
print ("use %s <FILENAME> <LINES>" % sys.argv[0]) |
||||
exit(1) |
||||
|
||||
filename = sys.argv[1] |
||||
lines = int(sys.argv[2]) |
||||
print ("Lines=%d" % lines) |
||||
|
||||
with open(filename, 'a') as out: |
||||
for i in range(0,lines): |
||||
seconds = 1000 + i |
||||
m, s = divmod(seconds, 60) |
||||
h, m = divmod(m, 60) |
||||
if ((rnd:=random.random()) < 0.6): |
||||
out.write( "TEST;%d:%02d:%02d;COL1\tCOL2 COL3 c:COL4;LIN=%05X\tRND=%f %s %s\n" % (h, m, s, i, random.random(), getSentence(3,20), " Fill" * random.randint(1,5)) ) |
||||
elif (rnd < 0.9): |
||||
jsonLine = { |
||||
'time':f"{h:02d}:{m:02d}:{s:02d}", |
||||
'line':i, |
||||
'data':{ |
||||
'rnd':random.random(), |
||||
'sentence1':getSentence(3,6), |
||||
'sentence2':getSentence(3,6), |
||||
'sentence3':getSentence(3,6), |
||||
'sentence4':getSentence(3,6)}, |
||||
'extra': { |
||||
'text':getSentence(3,10), |
||||
'list':[getSentence(1,2) for _ in range(random.randint(2,7))], |
||||
'list2':[random.randint(2,128) for _ in range(random.randint(2,7))], |
||||
'fill':" Fill" * random.randint(1,5) |
||||
} } |
||||
out.write( json.dumps(jsonLine) + '\n') |
||||
else: |
||||
out.write( "TEST;%d:%02d:%02d;COL1 --- (BROKEN LINE) --- LIN=%05X\tRND=%f %s %s\n" % (h, m, s, i, random.random(), getSentence(3,20), " Fill" * random.randint(1,5)) ) |
||||
out.write( " END LINE (No Newline)" ) |
||||
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3 |
||||
# This is just a helper, don't rely on this script too much |
||||
# Usage: |
||||
# cat ~/.config/klogg/klogg.conf | tools/import.filters.from.klogg.py |
||||
|
||||
import re |
||||
import fileinput |
||||
|
||||
# I am trying o match things like: |
||||
# sets\1\HighlighterSet\highlighters\1\fore_colour=#ff000000 |
||||
# sets\1\HighlighterSet\highlighters\1\ignore_case=false |
||||
# sets\1\HighlighterSet\highlighters\1\match_only=false |
||||
|
||||
rr = re.compile('^sets\\\(\d*)\\\HighlighterSet\\\highlighters\\\(\d*)\\\([^=]*)=(.*)$') |
||||
|
||||
sets = {} |
||||
|
||||
for line in fileinput.input(): |
||||
# print(line.rstrip()) |
||||
if m:=rr.match(line.rstrip()): |
||||
set = m.group(1) |
||||
num = m.group(2) |
||||
name = m.group(3) |
||||
value = m.group(4) |
||||
if set not in sets: |
||||
sets[set] = {} |
||||
if num not in sets[set]: |
||||
sets[set][num] = {} |
||||
sets[set][num][name] = value |
||||
|
||||
for s in sets: |
||||
print(f"\nset {s}:") |
||||
for n in sets[s]: |
||||
f = sets[s][n] |
||||
# print (f) |
||||
print (f"- pattern: '{f['regexp']}'") |
||||
print (f" ignorecase: {f['ignore_case']}") |
||||
print (f" fg: '#{f['fore_colour'][3:]}'") |
||||
print (f" bg: '#{f['back_colour'][3:]}'") |
||||
@ -0,0 +1,30 @@
|
||||
version: '1.0' |
||||
cfg: |
||||
- pattern: ipsum dolore |
||||
ignorecase: true |
||||
fg: '#FFFFFF' |
||||
bg: '#0000ff' |
||||
- pattern: cillum sit |
||||
ignorecase: true |
||||
fg: '#101d46' |
||||
bg: '#ff00ff' |
||||
- pattern: excepteur |
||||
ignorecase: true |
||||
fg: '#ffff00' |
||||
bg: '#000000' |
||||
- pattern: dolore |
||||
ignorecase: true |
||||
fg: '#00ff00' |
||||
bg: '#002338' |
||||
- pattern: cupida |
||||
ignorecase: true |
||||
fg: '#00ffff' |
||||
bg: '#0000ff' |
||||
- pattern: consequa |
||||
ignorecase: true |
||||
fg: '#ffff00' |
||||
bg: '#0000ff' |
||||
- pattern: sunt ad |
||||
ignorecase: true |
||||
fg: '#ffff00' |
||||
bg: '#ff0000' |
||||
@ -0,0 +1,3 @@
|
||||
version: '1.0' |
||||
cfg: |
||||
theme: NERD |
||||
Loading…
Reference in new issue