diff --git a/Makefile b/Makefile index c6b55ac6..3abed263 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: doc, runGittk, runDemo, build, testdeploy, deploy +.PHONY: doc, runGittk, runDemo, build, deploy, buildTest, deployTest, .venv: python3 -m venv .venv @@ -24,6 +24,13 @@ runDemo: .venv build: .venv . .venv/bin/activate rm -rf dist + tools/prepareBuild.sh release + python3 -m build + +buildTest: .venv + . .venv/bin/activate + rm -rf dist + tools/prepareBuild.sh test python3 -m build deployDoc: @@ -35,7 +42,7 @@ deployDoc: git push origin gh-pages git checkout main -testDeploy: .venv +deployTest: .venv . .venv/bin/activate python3 -m twine upload --repository testpypi dist/* --verbose diff --git a/TermTk/TTkCore/cfg.py b/TermTk/TTkCore/cfg.py index 365b4b5a..ca0184b2 100644 --- a/TermTk/TTkCore/cfg.py +++ b/TermTk/TTkCore/cfg.py @@ -24,6 +24,9 @@ from TermTk.TTkCore.constant import TTkK class TTkCfg: + version="__VERSION__" + name="__NAME__" + color_depth: int = TTkK.DEP_24 maxFps = 35 diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index f116651c..f84f0269 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -67,7 +67,16 @@ class TTk(TTkWidget): self.time = curtime def mainloop(self): - TTkLog.debug("Starting Main Loop...") + TTkLog.debug( "" ) + TTkLog.debug( "████████╗███████╗██████╗ ███╗ ███╗████████╗██╗ ██╗" ) + TTkLog.debug( "╚══██╔══╝██╔════╝██╔══██╗████╗ ████║╚══██╔══╝██║ ██╔╝" ) + TTkLog.debug( " ██║ █████╗ ██████╔╝██╔████╔██║ ██║ █████╔╝ " ) + TTkLog.debug( " ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║ ██║ ██╔═██╗ " ) + TTkLog.debug( "py ██║ ███████╗██║ ██║██║ ╚═╝ ██║ ██║ ██║ ██╗" ) + TTkLog.debug( " ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝" ) + TTkLog.debug(f" Version: {TTkCfg.version}" ) + TTkLog.debug( "" ) + TTkLog.debug( "Starting Main Loop..." ) # Register events try: signal.signal(signal.SIGTSTP, self._SIGSTOP) # Ctrl-Z diff --git a/TermTk/TTkWidgets/lineedit.py b/TermTk/TTkWidgets/lineedit.py index 2a1ad7b0..f7e11e18 100644 --- a/TermTk/TTkWidgets/lineedit.py +++ b/TermTk/TTkWidgets/lineedit.py @@ -37,8 +37,15 @@ from TermTk.TTkWidgets.widget import * <--> Offset ''' class TTkLineEdit(TTkWidget): - __slots__ = ('_text', '_cursorPos', '_offset', '_replace', '_inputType') + __slots__ = ( + '_text', '_cursorPos', '_offset', '_replace', '_inputType', + # Signals + 'returnPressed', 'textChanged', 'textEdited' ) def __init__(self, *args, **kwargs): + # Signals + self.returnPressed = pyTTkSignal() + self.textChanged = pyTTkSignal(str) + self.textEdited = pyTTkSignal(str) TTkWidget.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkLineEdit' ) self._inputType = kwargs.get('inputType' , TTkK.Input_Text ) @@ -52,6 +59,14 @@ class TTkLineEdit(TTkWidget): self.setMinimumSize(10,1) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) + def setText(self, text): + if text != self._text: + self.textChanged.emit(text) + self._text = text + + def text(self): + return self._text + def _pushCursor(self): TTkHelper.moveCursor(self,self._cursorPos-self._offset,0) if self._replace: @@ -114,6 +129,9 @@ class TTkLineEdit(TTkWidget): elif self._cursorPos - self._offset < 0: self._offset = self._cursorPos self._pushCursor() + + if evt.key == TTkK.Key_Enter: + self.returnPressed.emit() else: if self._inputType & TTkK.Input_Number and \ not evt.key.isdigit(): @@ -126,12 +144,13 @@ class TTkLineEdit(TTkWidget): post = text[self._cursorPos:] text = pre + evt.key + post - self._text = text + self.setText(text) self._cursorPos += 1 # Scroll to the right if reached the edge if self._cursorPos - self._offset > w: self._offset += 1 self._pushCursor() + self.textEdited.emit(self._text) def focusInEvent(self): self._pushCursor() diff --git a/demo/tlogg.py b/demo/tlogg.py new file mode 100755 index 00000000..f0e12d2f --- /dev/null +++ b/demo/tlogg.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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 + +sys.path.append(os.path.join(sys.path[0],'..')) + +from TermTk import * + +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.ttk import TTk +from TermTk.TTkWidgets.frame import TTkFrame +from TermTk.TTkTemplates.color import TColor +from TermTk.TTkTemplates.text import TText +from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea +from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView + +class _FileBuffer(): + __slots__ = ('_filename', '_indexes', '_fd', '_lastline') + def __init__(self, filename, window): + self._filename = filename + self._indexes = [] + self.createIndex() + self._fd = open(self._filename,'r') + self._lastline = {'line':0, 'txt':self._fd.readline()} + + def __del__(self): + self._fd.close() + + def getLen(self): + return len(self._indexes) + + def getLine(self, line): + if line >= self.getLen(): + return "" + if self._lastline['line'] != line : + self._fd.seek(self._indexes[line]) + self._lastline = {'line':line, 'txt':self._fd.readline()} + return self._lastline['txt'] + + + def createIndex(self): + self._indexes = [] + lines = 0 + offset = 0 + with open(self._filename,'r') as infile: + for line in infile: + lines += 1 + self._indexes.append(offset) + offset += len(line) + + def search(self, regex): + indexes = [] + id = 0 + rr = re.compile(regex) + with open(self._filename,'r') as infile: + for line in infile: + ma = rr.match(line) + if ma: + indexes.append(id) + id += 1 + return indexes + +class _FileViewer(TTkAbstractScrollView): + __slots__ = ('_fileBuffer', '_indexes', '_indexesMark') + def __init__(self, *args, **kwargs): + self._indexes = None + self._indexesMark = [] + TTkAbstractScrollView.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , '_FileViewer' ) + self._fileBuffer = kwargs.get('filebuffer') + self.viewChanged.connect(self._viewChangedHandler) + + @pyTTkSlot() + def _viewChangedHandler(self): + self.update() + + def showIndexes(self, indexes): + self._indexes = indexes + self.viewChanged.emit() + + def markIndexes(self, indexes): + self._indexesMark = indexes + self.viewChanged.emit() + + + def viewFullAreaSize(self) -> (int, int): + if self._indexes is None: + w = 300 + h = self._fileBuffer.getLen() + else: + w = 300 + h = len(self._indexes) + return w , h + + def viewDisplayedSize(self) -> (int, int): + return self.size() + + def paintEvent(self): + ox,oy = self.getViewOffsets() + if self._indexes is None: + for i in range(self.height()): + if (i+oy) in self._indexesMark: + color = TTkColor.fg("#ff0000") + else: + color = TTkColor.fg("#0000ff") + self.getCanvas().drawText(pos=(0,i), text="⏺", color=color) + self.getCanvas().drawText(pos=(2,i), text=self._fileBuffer.getLine(i+oy).replace('\t',' ').replace('\n','') ) + else: + for i in range(min(self.height(),len(self._indexes))): + if self._indexes[i+oy] in self._indexesMark: + color = TTkColor.fg("#ff0000") + else: + color = TTkColor.fg("#0000ff") + self.getCanvas().drawText(pos=(0,i), text="⏺", color=color) + self.getCanvas().drawText( + pos=(2,i), + text=self._fileBuffer.getLine(self._indexes[i+oy]).replace('\t',' ').replace('\n','') ) + + + +class FileViewer(TTkAbstractScrollArea): + __slots__ = ('_fileView') + def __init__(self, *args, **kwargs): + TTkAbstractScrollArea.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'FileViewer' ) + if 'parent' in kwargs: kwargs.pop('parent') + self._fileView = _FileViewer(filebuffer=kwargs.get('filebuffer')) + self.setFocusPolicy(TTkK.ClickFocus) + self.setViewport(self._fileView) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen', action='store_true') + parser.add_argument('filename', type=str, nargs='+', + help='the filename') + args = parser.parse_args() + + TTkLog.use_default_file_logging() + + root = TTk(layout=TTkGridLayout()) + splitter = TTkSplitter(parent=root, orientation=TTkK.VERTICAL) + tab = TTkTabWidget(parent=splitter, border=False) + TTkLogViewer(parent=splitter) + + for file in args.filename: + tabSplitter = TTkSplitter(orientation=TTkK.VERTICAL) + tab.addTab(tabSplitter, file) + topFrame = TTkFrame(parent=tabSplitter, border=False, layout=TTkVBoxLayout()) + bottomFrame = TTkFrame(parent=tabSplitter, border=False, layout=TTkVBoxLayout()) + + + # Define the bottom layout widgets + bottomLayoutSearch = TTkHBoxLayout() + bls_label = TTkLabel(text="Text:", maxWidth=6) + bls_textbox = TTkLineEdit() + bls_search = TTkButton(text="Search", maxWidth=7) + + bottomLayoutSearch.addWidget(bls_label) + bottomLayoutSearch.addWidget(bls_textbox) + bottomLayoutSearch.addWidget(bls_search) + + bottomFrame.layout().addItem(bottomLayoutSearch) + + # Define the main file Viewer + fileBuffer = _FileBuffer(file, 5000) + topViewer = FileViewer(parent=topFrame, filebuffer=fileBuffer) + # Define the Search Viewer + bottomViewer = FileViewer(parent=bottomFrame, filebuffer=fileBuffer) + bottomViewport = bottomViewer.viewport() + # indexes = fileBuffer.search(r'.*1234.*') + bottomViewport.showIndexes([]) + + class _search: + def __init__(self,tb,fb,tvp,bvp): + self.tb=tb + self.fb=fb + self.tvp=tvp + self.bvp=bvp + def search(self): + searchtext = self.tb.text() + indexes = self.fb.search(searchtext) + self.bvp.showIndexes(indexes) + self.bvp.markIndexes(indexes) + self.tvp.markIndexes(indexes) + _s = _search(bls_textbox,fileBuffer,topViewer.viewport(),bottomViewport) + bls_search.clicked.connect(_s.search) + bls_textbox.returnPressed.connect(_s.search) + + + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/docs/TODO.md b/docs/TODO.md index 6d5058d8..e356f7f3 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -106,6 +106,7 @@ - [ ] Snap on min/max sizes - [ ] Events (Signal/Slots) - [x] Themes + - [ ] Support addItem #### Tab Widget - [x] Basic Implementation - [ ] Events (Signal/Slots) @@ -124,6 +125,6 @@ - [ ] Events (Signal/Slots) - [x] Themes #### Header Menu - - [ ] Basic Implementation - - [ ] Events (Signal/Slots) - - [ ] Themes + - [x] Basic Implementation + - [x] Events (Signal/Slots) + - [x] Themes diff --git a/setup.py b/setup.py index 87a8cca6..abff6e49 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,21 @@ -import setuptools, os, subprocess +import setuptools, os +from tmp.TermTk.TTkCore.cfg import TTkCfg with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() -# Retrieve the version -out = subprocess.Popen( - ['git','describe'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) -version, stderr = out.communicate() -version = "".join(version.decode("utf-8").strip().split('-')[:2]) - -print(f"Version: {version}") +print(f"Version: {TTkCfg.version}") +print(f"Name: {TTkCfg.name}") setuptools.setup( - name='pyTermTk', + # name='pyTermTk', # name='example-pkg-ceccopierangiolieugenio', - version=version, + # version=version, # version="0.1.0a1", + name=TTkCfg.name, + version=TTkCfg.version, author='Eugenio Parodi', author_email='ceccopierangiolieugenio@googlemail.com', - # packages=['TermTk'], description='Python Terminal Toolkit', long_description=long_description, long_description_content_type="text/markdown", @@ -37,8 +32,7 @@ setuptools.setup( "Topic :: Terminals", "Topic :: Software Development :: User Interfaces"], # packages=setuptools.find_packages(), - packages = setuptools.find_packages(), - #where = '.', - #include = ['TermTk',]), + packages = setuptools.find_packages(where="tmp"), + package_dir = {"":"tmp"}, python_requires=">=3.6", ) \ No newline at end of file diff --git a/tools/prepareBuild.sh b/tools/prepareBuild.sh new file mode 100755 index 00000000..140eda11 --- /dev/null +++ b/tools/prepareBuild.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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. + + +_PWD=`pwd` +_TOOLS_BASE_PATH=$(dirname $(readlink -f $0)) +_BASE_PATH=${_TOOLS_BASE_PATH}/.. +_TMP_PATH=${_BASE_PATH}/tmp +_VERSION=$(git describe | sed 's,\([^-]*\)-\([^-]*\)-[^-]*,\1\2,') + +_tools_usage() +{ + echo "usage:" + echo " $0 " + echo "" + echo "The $0 commands are:" + echo " test - prepare test build environment" + echo " release - prepare release build environment" +} + + +if [ "$#" -ne 1 ]; then + _tools_usage + exit 1 +fi + +case $1 in + -h | --help) + _tools_usage + exit 1 + ;; + test) + _NAME='example-pkg-ceccopierangiolieugenio' + ;; + release) + _NAME='pyTermTk' + ;; + *) + echo "Option \"$2\" not recognized" + echo "" + _tools_usage + exit 0 + ;; +esac + +echo Version: ${_VERSION} +echo Name: ${_NAME} + +mkdir -p ${_TMP_PATH} +rm -rf ${_TMP_PATH}/* + +cp -a ${_BASE_PATH}/TermTk ${_TMP_PATH} +sed "s,__VERSION__,${_VERSION}," -i ${_TMP_PATH}/TermTk/TTkCore/cfg.py +sed "s,__NAME__,${_NAME}," -i ${_TMP_PATH}/TermTk/TTkCore/cfg.py