diff --git a/TermTk/TTkAbstract/__init__.py b/TermTk/TTkAbstract/__init__.py index 3137a2e9..9ed30ad1 100644 --- a/TermTk/TTkAbstract/__init__.py +++ b/TermTk/TTkAbstract/__init__.py @@ -1,3 +1,3 @@ -from .abstractscrollview import TTkAbstractScrollViewInterface, TTkAbstractScrollView, TTkAbstractScrollViewGridLayout -from .abstractscrollarea import TTkAbstractScrollArea -from .abstractitemmodel import TTkAbstractItemModel +from .abstractscrollview import * +from .abstractscrollarea import * +from .abstractitemmodel import * diff --git a/TermTk/TTkAbstract/abstractitemmodel.py b/TermTk/TTkAbstract/abstractitemmodel.py index 24421a22..7abf8c87 100644 --- a/TermTk/TTkAbstract/abstractitemmodel.py +++ b/TermTk/TTkAbstract/abstractitemmodel.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkAbstractItemModel'] + from TermTk.TTkCore.signal import pyTTkSignal class TTkAbstractItemModel(): diff --git a/TermTk/TTkAbstract/abstractscrollarea.py b/TermTk/TTkAbstract/abstractscrollarea.py index aca09aa1..3278d742 100644 --- a/TermTk/TTkAbstract/abstractscrollarea.py +++ b/TermTk/TTkAbstract/abstractscrollarea.py @@ -20,15 +20,18 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkAbstractScrollArea'] + from TermTk.TTkCore.constant import TTkK # from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.scrollbar import TTkScrollBar from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollViewInterface -class TTkAbstractScrollArea(TTkWidget): +class TTkAbstractScrollArea(TTkContainer): __slots__ = ( '_processing', # this flag is required to avoid unnecessary loop on edge cases '_viewport', @@ -123,7 +126,12 @@ class TTkAbstractScrollArea(TTkWidget): raise TypeError("TTkAbstractScrollViewInterface is required in TTkAbstractScrollArea.setVewport(viewport)") if self._viewport: self._viewport.viewChanged.disconnect(self._viewportChanged) - self.layout().removeWidget(self._viewport) + # TODO: Remove this check once + # unified "addWidget" and "addItem" in the TTKGridLayout + if isinstance(viewport, TTkWidget): + self.layout().removeWidget(self._viewport) + else: + self.layout().removeItem(self._viewport) self._viewport = viewport self._viewport.viewChanged.connect(self._viewportChanged) self._verticalScrollBar.sliderMoved.connect(self._vscrollMoved) diff --git a/TermTk/TTkAbstract/abstractscrollview.py b/TermTk/TTkAbstract/abstractscrollview.py index d3f6831c..e85d60a6 100644 --- a/TermTk/TTkAbstract/abstractscrollview.py +++ b/TermTk/TTkAbstract/abstractscrollview.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,10 +20,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkAbstractScrollViewInterface', 'TTkAbstractScrollView', 'TTkAbstractScrollViewGridLayout'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer +from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkLayouts.gridlayout import TTkGridLayout class TTkAbstractScrollViewInterface(): @@ -44,7 +45,7 @@ class TTkAbstractScrollViewInterface(): def getViewOffsets(self): return self._viewOffsetX, self._viewOffsetY -class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): +class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): __slots__ = ( '_viewOffsetX', '_viewOffsetY', # Signals @@ -55,9 +56,9 @@ class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): self.viewMovedTo = pyTTkSignal(int, int) # x, y self.viewSizeChanged = pyTTkSignal(int, int) # w, h self.viewChanged = pyTTkSignal() - TTkWidget.__init__(self, *args, **kwargs) self._viewOffsetX = 0 self._viewOffsetY = 0 + TTkContainer.__init__(self, *args, **kwargs) @pyTTkSlot(int, int) def viewMoveTo(self, x: int, y: int): @@ -96,6 +97,38 @@ class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): self.viewChanged.emit() return super().update(repaint, updateLayout, updateParent) +class TTkAbstractScrollViewLayout(TTkLayout, TTkAbstractScrollViewInterface): + __slots__ = ( + '_viewOffsetX', '_viewOffsetY', + # Signals + 'viewMovedTo', 'viewSizeChanged', 'viewChanged', '_excludeEvent') + + def __init__(self, *args, **kwargs): + # Signals + self.viewMovedTo = pyTTkSignal(int, int) # x, y + self.viewSizeChanged = pyTTkSignal(int, int) # w, h + self.viewChanged = pyTTkSignal() + self._viewOffsetX = 0 + self._viewOffsetY = 0 + self._excludeEvent = False + TTkLayout.__init__(self, *args, **kwargs) + + def viewFullAreaSize(self) -> (int, int): + _,_,w,h = self.fullWidgetAreaGeometry() + return w,h + + def viewDisplayedSize(self) -> (int, int): + _,_,w,h = self.geometry() + return w,h + + @pyTTkSlot(int, int) + def viewMoveTo(self, x: int, y: int): + self.setOffset(-x,-y) + + def setGeometry(self, x, y, w, h): + TTkLayout.setGeometry(self, x, y, w, h) + self.viewChanged.emit() + class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterface): __slots__ = ( '_viewOffsetX', '_viewOffsetY', @@ -107,10 +140,10 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf self.viewMovedTo = pyTTkSignal(int, int) # x, y self.viewSizeChanged = pyTTkSignal(int, int) # w, h self.viewChanged = pyTTkSignal() - TTkGridLayout.__init__(self, *args, **kwargs) self._viewOffsetX = 0 self._viewOffsetY = 0 self._excludeEvent = False + TTkGridLayout.__init__(self, *args, **kwargs) @pyTTkSlot(int, int) def viewMoveTo(self, x: int, y: int): diff --git a/TermTk/TTkCore/TTkTerm/__init__.py b/TermTk/TTkCore/TTkTerm/__init__.py index 22928704..4b523bec 100644 --- a/TermTk/TTkCore/TTkTerm/__init__.py +++ b/TermTk/TTkCore/TTkTerm/__init__.py @@ -1,5 +1,5 @@ -from .inputkey import TTkKeyEvent -from .inputmouse import TTkMouseEvent -from .colors import TTkTermColor -from .term import TTkTerm -from .input import TTkInput +from .inputkey import * +from .inputmouse import * +from .colors import * +from .term import * +from .input import * diff --git a/TermTk/TTkCore/TTkTerm/colors.py b/TermTk/TTkCore/TTkTerm/colors.py index 1b596668..1c6f7d8a 100644 --- a/TermTk/TTkCore/TTkTerm/colors.py +++ b/TermTk/TTkCore/TTkTerm/colors.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTermColor'] + # Ansi Escape Codes: # https://conemu.github.io/en/AnsiEscapeCodes.html @@ -30,14 +30,38 @@ import re from .colors_ansi_map import ansiMap256, ansiMap16 class TTkTermColor(): - BOLD = 0x01 - ITALIC = 0x02 - UNDERLINE = 0x04 - STRIKETROUGH = 0x08 - BLINKING = 0x10 + BOLD = 0x01 # [1m + FAINT = 0x02 # [2m + ITALIC = 0x04 # [3m + UNDERLINE = 0x08 # [4m + BLINKING = 0x10 # [5m + REVERSED = 0x20 # [7m + HIDDEN = 0x40 # [8m + STRIKETROUGH = 0x80 # [9m + + SGR_SET = { # CSI Pm m Character Attributes (SGR). + # Ps = 0 ⇒ Normal (default), VT100. + 1: BOLD , # Ps = 1 ⇒ Bold, VT100. + 2: FAINT , # Ps = 2 ⇒ Faint, decreased intensity, ECMA-48 2nd. + 3: ITALIC , # Ps = 3 ⇒ Italicized, ECMA-48 2nd. + 4: UNDERLINE , # Ps = 4 ⇒ Underlined, VT100. + 5: BLINKING , # Ps = 5 ⇒ Blink, VT100. - This appears as Bold in X11R6 xterm. + 7: REVERSED , # Ps = 7 ⇒ Inverse, VT100. + 8: HIDDEN , # Ps = 8 ⇒ Invisible, i.e., hidden, ECMA-48 2nd, VT300. + 9: STRIKETROUGH } # Ps = 9 ⇒ Crossed-out characters, ECMA-48 3rd. + + SGR_RST = { # CSI Pm m Character Attributes (SGR). + # Ps = 2 1 ⇒ Doubly-underlined, ECMA-48 3rd. + 22: ~(BOLD|FAINT), # Ps = 2 2 ⇒ Normal (neither bold nor faint), ECMA-48 3rd. + 23: ~ITALIC, # Ps = 2 3 ⇒ Not italicized, ECMA-48 3rd. + 24: ~UNDERLINE, # Ps = 2 4 ⇒ Not underlined, ECMA-48 3rd. + 25: ~BLINKING, # Ps = 2 5 ⇒ Steady (not blinking), ECMA-48 3rd. + 27: ~REVERSED, # Ps = 2 7 ⇒ Positive (not inverse), ECMA-48 3rd. + 28: ~HIDDEN, # Ps = 2 8 ⇒ Visible, i.e., not hidden, ECMA-48 3rd, VT300. + 29: ~STRIKETROUGH } # Ps = 2 9 ⇒ Not crossed-out, ECMA-48 3rd. @staticmethod - def rgb2ansi(fg: tuple=None, bg:tuple=None, mod:int=0, clean:bool=False): + def rgb2ansi(fg: tuple=None, bg:tuple=None, mod:int=0, link:str='', clean:bool=False): ret = [] if clean: @@ -50,15 +74,20 @@ class TTkTermColor(): if mod & TTkTermColor.BOLD: ret.append('1') + if mod & TTkTermColor.FAINT: + ret.append('2') if mod & TTkTermColor.ITALIC: ret.append('3') if mod & TTkTermColor.UNDERLINE: ret.append('4') - if mod & TTkTermColor.STRIKETROUGH: - ret.append('9') if mod & TTkTermColor.BLINKING: ret.append('5') - + if mod & TTkTermColor.REVERSED: + ret.append('7') + if mod & TTkTermColor.HIDDEN: + ret.append('8') + if mod & TTkTermColor.STRIKETROUGH: + ret.append('9') if ret: return f'\033[{";".join(str(x) for x in ret)}m' else: @@ -108,11 +137,10 @@ class TTkTermColor(): bg = None mod = 0 clean = True - elif s==1: mod += TTkTermColor.BOLD - elif s==3: mod += TTkTermColor.ITALIC - elif s==4: mod += TTkTermColor.UNDERLINE - elif s==9: mod += TTkTermColor.STRIKETROUGH - elif s==5: mod += TTkTermColor.BLINKING + elif _sgr:=TTkTermColor.SGR_SET.get(s,None): + mod |= _sgr + elif _sgr:=TTkTermColor.SGR_RST.get(s,None): + mod &= _sgr return fg,bg,mod,clean diff --git a/TermTk/TTkCore/TTkTerm/colors_ansi_map.py b/TermTk/TTkCore/TTkTerm/colors_ansi_map.py index 3780417e..d98fc4f7 100644 --- a/TermTk/TTkCore/TTkTerm/colors_ansi_map.py +++ b/TermTk/TTkCore/TTkTerm/colors_ansi_map.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi diff --git a/TermTk/TTkCore/TTkTerm/input.py b/TermTk/TTkCore/TTkTerm/input.py index d32748bb..e8afcf34 100644 --- a/TermTk/TTkCore/TTkTerm/input.py +++ b/TermTk/TTkCore/TTkTerm/input.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkInput'] + import re from time import time @@ -46,12 +46,16 @@ class TTkInput: '_readInput', '_leftLastTime', '_midLastTime', '_rightLastTime', '_leftTap', '_midTap', '_rightTap', + '_pasteBuffer', '_bracketedPaste', # Signals - 'inputEvent' + 'inputEvent', 'pasteEvent' ) def __init__(self): self.inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent) + self.pasteEvent = pyTTkSignal(str) + self._pasteBuffer = "" + self._bracketedPaste = False self._readInput = None self._leftLastTime = 0 self._midLastTime = 0 @@ -79,7 +83,20 @@ class TTkInput: mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])") def key_process(self, stdinRead): + if self._bracketedPaste: + if stdinRead.endswith("\033[201~"): + self._pasteBuffer += stdinRead[:-6] + self._bracketedPaste = False + # due to the CRNL methos (don't ask me why) the terminal + # is substituting all the \n with \r + self.pasteEvent.emit(self._pasteBuffer.replace('\r','\n')) + self._pasteBuffer = "" + else: + self._pasteBuffer += stdinRead + return + mevt,kevt = None, None + if not stdinRead.startswith("\033[<"): # Key Event kevt = TTkKeyEvent.parse(stdinRead) @@ -153,12 +170,17 @@ class TTkInput: evt = TTkMouseEvent.Move mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "")) + if kevt or mevt: + self.inputEvent.emit(kevt, mevt) + return - if kevt is None and mevt is None: - hex = [f"0x{ord(x):02x}" for x in stdinRead] - TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","") + " - "+",".join(hex)) + if stdinRead.startswith("\033[200~"): + self._pasteBuffer = stdinRead[6:] + self._bracketedPaste = True + return - self.inputEvent.emit(kevt, mevt) + hex = [f"0x{ord(x):02x}" for x in stdinRead] + TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","") + " - "+",".join(hex)) def main(): diff --git a/TermTk/TTkCore/TTkTerm/inputkey.py b/TermTk/TTkCore/TTkTerm/inputkey.py index 7920bb03..7cbe7596 100644 --- a/TermTk/TTkCore/TTkTerm/inputkey.py +++ b/TermTk/TTkCore/TTkTerm/inputkey.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkKeyEvent'] + from TermTk.TTkCore.constant import TTkK class TTkKeyEvent: diff --git a/TermTk/TTkCore/TTkTerm/inputmouse.py b/TermTk/TTkCore/TTkTerm/inputmouse.py index fdb9a23d..7bf8fe37 100644 --- a/TermTk/TTkCore/TTkTerm/inputmouse.py +++ b/TermTk/TTkCore/TTkTerm/inputmouse.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkMouseEvent'] + from TermTk.TTkCore.constant import TTkK class TTkMouseEvent: diff --git a/TermTk/TTkCore/TTkTerm/readinputlinux.py b/TermTk/TTkCore/TTkTerm/readinputlinux.py index 1295ada5..0ba4b635 100644 --- a/TermTk/TTkCore/TTkTerm/readinputlinux.py +++ b/TermTk/TTkCore/TTkTerm/readinputlinux.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -57,6 +55,9 @@ class ReadInput(): # Split all the ansi sequences # or yield any separate input char + if stdinRead == '\033': + yield '\033' + continue for sr in rm.findall(stdinRead): if '\033' == sr[0]: yield sr diff --git a/TermTk/TTkCore/TTkTerm/readinputlinux_thread.py b/TermTk/TTkCore/TTkTerm/readinputlinux_thread.py index d2b12e49..c5502995 100644 --- a/TermTk/TTkCore/TTkTerm/readinputlinux_thread.py +++ b/TermTk/TTkCore/TTkTerm/readinputlinux_thread.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi diff --git a/TermTk/TTkCore/TTkTerm/term.py b/TermTk/TTkCore/TTkTerm/term.py index b2ad30b7..bed6bb1e 100644 --- a/TermTk/TTkCore/TTkTerm/term.py +++ b/TermTk/TTkCore/TTkTerm/term.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,9 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTerm'] + import importlib.util if importlib.util.find_spec('pyodideProxy'): from .term_pyodide import TTkTerm else: - from .term_unix import TTkTerm \ No newline at end of file + from .term_unix import TTkTerm diff --git a/TermTk/TTkCore/TTkTerm/term_base.py b/TermTk/TTkCore/TTkTerm/term_base.py index 073b3813..dd80a39a 100644 --- a/TermTk/TTkCore/TTkTerm/term_base.py +++ b/TermTk/TTkCore/TTkTerm/term_base.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -29,11 +27,14 @@ class TTkTermBase(): ALT_SCREEN = "\033[?1049h" #* Switch to alternate screen NORMAL_SCREEN = "\033[?1049l" #* Switch to normal screen + SET_BRACKETED_PM = "\033[?2004h" # Ps = 2 0 0 4 ⇒ Set bracketed paste mode, xterm. + RESET_BRACKETED_PM = "\033[?2004l" # Ps = 2 0 0 4 ⇒ Reset bracketed paste mode, xterm. + class Mouse(): - ON = "\033[?1002h\033[?1015h\033[?1006h" # Enable reporting of mouse position on click and release - OFF = "\033[?1002l" # Disable mouse reporting - DIRECT_ON = "\033[?1003h" # Enable reporting of mouse position at any movement - DIRECT_OFF = "\033[?1003l" # Disable direct mouse reporting + ON = "\033[?1002h\033[?1006h" # Enable reporting of mouse position on click and release + OFF = "\033[?1002l\033[?1006l" # Disable mouse reporting + DIRECT_ON = "\033[?1003h" # Enable reporting of mouse position at any movement + DIRECT_OFF = "\033[?1003l" # Disable direct mouse reporting class Cursor(): # from: @@ -95,7 +96,10 @@ class TTkTermBase(): TTkTermBase.mouse = mouse | directMouse TTkTermBase.directMouse = directMouse TTkTermBase.Cursor.hide() - TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title)) + TTkTermBase.push(TTkTermBase.escTitle(TTkTermBase.title)) + TTkTermBase.push(TTkTermBase.ALT_SCREEN) + TTkTermBase.push(TTkTermBase.SET_BRACKETED_PM) + TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE) if TTkTermBase.mouse: TTkTermBase.push(TTkTermBase.Mouse.ON) if TTkTermBase.directMouse: @@ -107,20 +111,20 @@ class TTkTermBase(): @staticmethod def exit(): TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF) - TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle()) + TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.RESET_BRACKETED_PM + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle()) TTkTermBase.setEcho(True) TTkTermBase.CRNL(True) @staticmethod def stop(): TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF) - TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle()) + TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.RESET_BRACKETED_PM + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle()) TTkTermBase.setEcho(True) TTkTermBase.CRNL(True) @staticmethod def cont(): - TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title)) + TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.SET_BRACKETED_PM + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title)) if TTkTermBase.mouse: TTkTermBase.push(TTkTermBase.Mouse.ON) if TTkTermBase.directMouse: diff --git a/TermTk/TTkCore/TTkTerm/term_pyodide.py b/TermTk/TTkCore/TTkTerm/term_pyodide.py index adac0608..f3799976 100644 --- a/TermTk/TTkCore/TTkTerm/term_pyodide.py +++ b/TermTk/TTkCore/TTkTerm/term_pyodide.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi diff --git a/TermTk/TTkCore/TTkTerm/term_unix.py b/TermTk/TTkCore/TTkTerm/term_unix.py index 274f24ea..e8b4cf1c 100644 --- a/TermTk/TTkCore/TTkTerm/term_unix.py +++ b/TermTk/TTkCore/TTkTerm/term_unix.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -31,6 +29,7 @@ except Exception as e: exit(1) from .term_base import TTkTermBase +from TermTk.TTkCore.log import TTkLog class TTkTerm(TTkTermBase): _sigWinChCb = None @@ -82,8 +81,13 @@ class TTkTerm(TTkTermBase): @staticmethod def _push(*args): - sys.stdout.write(str(*args)) - sys.stdout.flush() + try: + sys.stdout.write(str(*args)) + sys.stdout.flush() + except BlockingIOError as e: + TTkLog.fatal(f"{e=} {e.characters_written=}") + except Exception as e: + TTkLog.fatal(e) TTkTermBase.push = _push @staticmethod diff --git a/TermTk/TTkCore/__init__.py b/TermTk/TTkCore/__init__.py index 080f01ed..03f2bf99 100644 --- a/TermTk/TTkCore/__init__.py +++ b/TermTk/TTkCore/__init__.py @@ -1,13 +1,15 @@ -from .signal import pyTTkSlot, pyTTkSignal -from .log import TTkLog -from .cfg import TTkCfg,TTkGlbl -from .util import TTkUtil -from .helper import TTkHelper -from .propertyanimation import TTkPropertyAnimation, TTkEasingCurve -from .ttk import TTk -from .canvas import TTkCanvas -from .color import TTkColor, TTkColorGradient, TTkLinearGradient -from .string import TTkString -from .timer import TTkTimer -from .filebuffer import TTkFileBuffer -from .TTkTerm import * \ No newline at end of file +from .constant import * +from .signal import * +from .log import * +from .cfg import * +from .util import * +from .helper import * +from .propertyanimation import * +from .ttk import * +from .canvas import * +from .color import * +from .string import * +from .timer import * +from .filebuffer import * + +from .TTkTerm import * \ No newline at end of file diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 4a71eaed..a82de402 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkCanvas'] + from TermTk.TTkCore.TTkTerm.term import TTkTerm from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -36,21 +36,19 @@ class TTkCanvas: :param height: the height of the Canvas ''' __slots__ = ( - '_widget', '_width', '_height', '_newWidth', '_newHeight', '_theme', '_data', '_colors', '_bufferedData', '_bufferedColors', '_visible', '_transparent', '_doubleBuffer') def __init__(self, *args, **kwargs): - self._widget = kwargs.get('widget', None) self._visible = True self._transparent = False self._doubleBuffer = False self._width = 0 self._height = 0 - self._data = [[0]] - self._colors = [[TTkColor.RST]] + self._data = [[]] + self._colors = [[]] self._newWidth = kwargs.get('width', 0 ) self._newHeight = kwargs.get('height', 0 ) self.updateSize() @@ -63,8 +61,6 @@ class TTkCanvas: def setTransparent(self, tr): self._transparent = tr - def getWidget(self): return self._widget - def enableDoubleBuffer(self): self._doubleBuffer = True self._bufferedData, self._bufferedColors = self.copyBuffers() @@ -140,25 +136,23 @@ class TTkCanvas: w,h = self.size() if not size: size=(w,h) - fx,fy = pos + fxa,fya = pos fw,fh = size + fxb,fyb = fxa+fw, fya+fh # the fill area is outside the boundaries - if fx >= w or fy>=h: return - if fx<0: - fw += fx - fx = 0 - if fy<0: - fh += fy - fy = 0 - if fw<=0 or fh<=0: return - fw = min(fw, w+fx) - fh = min(fh, h+fy) + if ( fxa >= w or fya >= h or + fxb <= 0 or fyb <= 0): return + + fxa = max(0,fxa) + fya = max(0,fya) + fxb = min(w,fxb) + fyb = min(h,fyb) fillCh = [char]*fw fillColor = [color]*fw - for iy in range(fy,fy+fh): - self._data[iy][fx:fx+fw] = fillCh - self._colors[iy][fx:fx+fw] = fillColor + for iy in range(fya,fyb): + self._data[iy][fxa:fxb] = fillCh + self._colors[iy][fxa:fxb] = fillColor def drawVLine(self, pos, size, color=TTkColor.RST): if size == 0: return @@ -239,10 +233,10 @@ class TTkCanvas: # Check the full wide chars on the edge of the two canvasses if ((0 <= (x+a) < self._width) and self._data[y][x+a] == ''): self._data[y][x+a] = TTkCfg.theme.unicodeWideOverflowCh[0] - self._colors[y][x+a] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y][x+a] = TTkString.unicodeWideOverflowColor if ((0 <= (x+b-1) < self._width) and TTkString._isWideCharData(self._data[y][x+b-1])): self._data[y][x+b-1] = TTkCfg.theme.unicodeWideOverflowCh[1] - self._colors[y][x+b-1] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y][x+b-1] = TTkString.unicodeWideOverflowColor def drawText(self, text="", pos=(0,0), width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): ''' @@ -640,6 +634,14 @@ class TTkCanvas: if bx+bw<0 or by+bh<0 or bx>=cw or by>=ch: return if x+w<=bx or y+h<=by or bx+bw<=x or by+bh<=y: return + if (0,0,cw,ch)==geom==bound and (cw,ch)==canvas.size() and not canvas._transparent: + # fast Copy + # the canvas match exactly on top of the current one + for y in range(h): + self._data[y] = canvas._data[y].copy() + self._colors[y] = canvas._colors[y].copy() + return + x = min(x,cw-1) y = min(y,ch-1) w = min(w,cw-x) @@ -672,16 +674,32 @@ class TTkCanvas: # Check the full wide chars on the edge of the two canvasses if ((0 <= a < cw) and self._data[y+iy][a]==''): self._data[y+iy][a] = TTkCfg.theme.unicodeWideOverflowCh[0] - self._colors[y+iy][a] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y+iy][a] = TTkString.unicodeWideOverflowColor if ((0 < b <= cw) and self._data[y+iy][b-1] and TTkString._isWideCharData(self._data[y+iy][b-1])): self._data[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowCh[1] - self._colors[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y+iy][b-1] = TTkString.unicodeWideOverflowColor if ((0 < a <= cw) and self._data[y+iy][a-1] and TTkString._isWideCharData(self._data[y+iy][a-1])): self._data[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowCh[1] - self._colors[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y+iy][a-1] = TTkString.unicodeWideOverflowColor if ((0 <= b < cw) and self._data[y+iy][b]==''): self._data[y+iy][b] = TTkCfg.theme.unicodeWideOverflowCh[0] - self._colors[y+iy][b] = TTkCfg.theme.unicodeWideOverflowColor + self._colors[y+iy][b] = TTkString.unicodeWideOverflowColor + + def toAnsi(self): + # TTkLog.debug("pushToTerminal") + ret = "" + lastcolor = TTkColor.RST + for y in range(0, self._height): + ansi = str(TTkColor.RST) + for x in range(0, self._width): + ch = self._data[y][x] + color = self._colors[y][x] + if color != lastcolor: + ansi += str(color-lastcolor) + lastcolor = color + ansi+=ch + ret += ansi + '\n' + return ret def pushToTerminal(self, x, y, w, h): # TTkLog.debug("pushToTerminal") @@ -733,6 +751,56 @@ class TTkCanvas: empty=True # Reset the color at the end TTkTerm.push(TTkColor.RST) + # TTkTerm.flush() # Switch the buffer self._bufferedData, self._bufferedColors = data, colors self._data, self._colors = oldData, oldColors + + def pushToTerminalBufferedNew(self, x, y, w, h): + # TTkLog.debug("pushToTerminal") + data, colors = self._data, self._colors + oldData, oldColors = self._bufferedData, self._bufferedColors + lastcolor = TTkColor.RST + empty = True + ansi = "" + for y,(lda,ldb,lca,lcb) in enumerate(zip(data,oldData,colors,oldColors)): + count = 0 + chBk = '' + for x,(da,db,ca,cb) in enumerate(zip(lda,ldb,lca,lcb)): + if da==db and ca==cb: + if not empty: + ansi += "" if not chBk else chBk*count if count<=4 else f"{chBk}\033[{count-1}b" + TTkTerm.push(ansi) + count = 0 + chBk = '' + empty=True + continue + ch = da + color = ca + if empty: + ansi = ("" if not chBk else chBk*count if count<=4 else f"{chBk}\033[{count-1}b") + TTkTerm.Cursor.moveTo(y+1,x+1) + empty = False + count = 0 + chBk = '' + if color != lastcolor: + ansi += ("" if not chBk else chBk*count if count<=4 else f"{chBk}\033[{count-1}b") + str(color-lastcolor) + lastcolor = color + count = 0 + chBk = '' + # "Collect the consecutive characters" + if ch == chBk: + count+=1 + else: + ansi += "" if not chBk else chBk*count if count<=4 else f"{chBk}\033[{count-1}b" + chBk = ch + count=1 + # ansi+=ch + if not empty: + ansi += "" if not chBk else chBk*count if count<=4 else f"{chBk}\033[{count-1}b" + TTkTerm.push(ansi) + empty=True + # Reset the color at the end + TTkTerm.push(TTkColor.RST) + # Switch the buffer + self._bufferedData, self._bufferedColors = data, colors + self._data, self._colors = oldData, oldColors \ No newline at end of file diff --git a/TermTk/TTkCore/cfg.py b/TermTk/TTkCore/cfg.py index 88177487..59caa440 100644 --- a/TermTk/TTkCore/cfg.py +++ b/TermTk/TTkCore/cfg.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkCfg', 'TTkGlbl'] + from TermTk.TTkCore.constant import TTkK class TTkCfg: @@ -33,6 +33,7 @@ class TTkCfg: toolTipTime = 1 maxFps = 65 doubleBuffer = True + doubleBufferNew = False scrollDelta = 5 theme = None diff --git a/TermTk/TTkCore/color.py b/TermTk/TTkCore/color.py index f446cf59..19b4bae7 100644 --- a/TermTk/TTkCore/color.py +++ b/TermTk/TTkCore/color.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkColor', 'TTkColorGradient', 'TTkLinearGradient'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper @@ -61,12 +61,13 @@ from TermTk.TTkCore.helper import TTkHelper # [49m 2.53 set background color to default (black) class _TTkColor: - __slots__ = ('_fg','_bg','_mod', '_colorMod', '_buffer', '_clean') + __slots__ = ('_fg','_bg','_mod', '_colorMod', '_link', '_buffer', '_clean') _fg: tuple; _bg: tuple; _mod: int - def __init__(self, fg:tuple=None, bg:tuple=None, mod:int=0, colorMod=None, clean=False): + def __init__(self, fg:tuple=None, bg:tuple=None, mod:int=0, colorMod=None, link:str='', clean=False): self._fg = fg self._bg = bg self._mod = mod + self._link = link self._clean = clean or not (fg or bg or mod) self._colorMod = colorMod self._buffer = None @@ -181,15 +182,18 @@ class _TTkColor: def __str__(self): if not self._buffer: - self._buffer = TTkHelper.Color.rgb2ansi(self._fg,self._bg,self._mod,self._clean) + self._buffer = TTkHelper.Color.rgb2ansi( + fg=self._fg, bg=self._bg, mod=self._mod, + link=self._link, clean=self._clean) return self._buffer def __eq__(self, other): if other is None: return False - return \ - self._fg == other._fg and \ - self._bg == other._bg and \ - self._mod == other._mod + return ( + self._fg == other._fg and + self._bg == other._bg and + self._mod == other._mod and + self._link == other._link ) # self | other def __or__(self, other): @@ -200,8 +204,12 @@ class _TTkColor: fg: str = self._fg or other._fg bg: str = self._bg or other._bg mod: str = self._mod + other._mod + link:str = self._link or other._link colorMod = self._colorMod or other._colorMod - return TTkColor(fg,bg,mod,colorMod,clean) + return TTkColor( + fg=fg, bg=bg, mod=mod, + colorMod=colorMod, link=link, + clean=clean) # self + other def __add__(self, other): @@ -212,15 +220,20 @@ class _TTkColor: fg: str = other._fg or self._fg bg: str = other._bg or self._bg mod: str = self._mod + other._mod + link:str = self._link or other._link colorMod = other._colorMod or self._colorMod - return TTkColor(fg,bg,mod,colorMod,clean) + return TTkColor( + fg=fg, bg=bg, mod=mod, + colorMod=colorMod, link=link, + clean=clean) def __sub__(self, other): # TTkLog.debug("__sub__") # if other is None: return str(self) - if ( None == self._bg != other._bg or - None == self._fg != other._fg or - self._mod != other._mod ): + if ( None == self._bg != other._bg or + None == self._fg != other._fg or + self._link != other._link or + self._mod != other._mod ): ret = self.copy() ret._clean = True return ret @@ -241,6 +254,7 @@ class _TTkColor: ret._fg = self._fg ret._bg = self._bg ret._mod = self._mod + ret._link = self._link ret._clean = self._clean if modifier and self._colorMod: ret._colorMod = self._colorMod.copy() @@ -421,11 +435,12 @@ class TTkColor(_TTkColor): :type modifier: TTkColorModifier, optional ''' mod = kwargs.get('modifier', None ) + link = kwargs.get('link', '' ) if len(args) > 0: color = args[0] else: color = kwargs.get('color', "" ) - return TTkColor(fg=TTkColor.hexToRGB(color), colorMod=mod) + return TTkColor(fg=TTkColor.hexToRGB(color), colorMod=mod, link=link) @staticmethod def bg(*args, **kwargs): @@ -445,8 +460,9 @@ class TTkColor(_TTkColor): :type modifier: TTkColorModifier, optional ''' mod = kwargs.get('modifier', None ) + link = kwargs.get('link', '' ) if len(args) > 0: color = args[0] else: color = kwargs.get('color', "" ) - return TTkColor(bg=TTkColor.hexToRGB(color), colorMod=mod) + return TTkColor(bg=TTkColor.hexToRGB(color), colorMod=mod, link=link) diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index e5b77747..efd19d96 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkConstant', 'TTkK'] + class TTkConstant: '''Class container of all the constants used in :mod:`~TermTk`''' diff --git a/TermTk/TTkCore/filebuffer.py b/TermTk/TTkCore/filebuffer.py index fda49048..52754caa 100644 --- a/TermTk/TTkCore/filebuffer.py +++ b/TermTk/TTkCore/filebuffer.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileBuffer'] + import os import re import threading diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 8f9b1737..e1f88bda 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,12 +20,19 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkHelper'] + from TermTk.TTkCore.TTkTerm.colors import TTkTermColor from TermTk.TTkCore.TTkTerm.term import TTkTerm from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot class TTkHelper: + '''TTkHelper + + This is a collection of helper utilities to be used all around TermTk + ''' # TODO: Add Setter/Getter _focusWidget = None _rootCanvas = None @@ -83,8 +88,9 @@ class TTkHelper: @staticmethod def addUpdateWidget(widget): # if not widget.isVisibleAndParent(): return - TTkHelper._updateWidget.add(widget) - TTkHelper.unlockPaint() + if widget not in TTkHelper._updateWidget: + TTkHelper._updateWidget.add(widget) + TTkHelper.unlockPaint() @staticmethod def addUpdateBuffer(canvas): @@ -97,10 +103,15 @@ class TTkHelper: TTkHelper._rootWidget = widget TTkHelper._rootCanvas.enableDoubleBuffer() + quitEvent = pyTTkSignal() + @staticmethod + @pyTTkSlot() def quit(): + '''Quit TermTk''' + TTkHelper.quitEvent.emit() if TTkHelper._rootWidget: - TTkHelper._rootWidget.quit() + TTkHelper._rootWidget._quit() @staticmethod def getTerminalSize(): @@ -187,8 +198,9 @@ class TTkHelper: TTkHelper._rootWidget.rootLayout().addWidget(widget) widget.setFocus() widget.raiseWidget() - for w in widget.rootLayout().iterWidgets(onlyVisible=True): - w.update() + if hasattr(widget,'rootLayout'): + for w in widget.rootLayout().iterWidgets(onlyVisible=True): + w.update() @staticmethod def getOverlay(): @@ -318,6 +330,8 @@ class TTkHelper: TTkTerm.Cursor.hide() if TTkCfg.doubleBuffer: TTkHelper._rootCanvas.pushToTerminalBuffered(0, 0, TTkGlbl.term_w, TTkGlbl.term_h) + elif TTkCfg.doubleBufferNew: + TTkHelper._rootCanvas.pushToTerminalBufferedNew(0, 0, TTkGlbl.term_w, TTkGlbl.term_h) else: TTkHelper._rootCanvas.pushToTerminal(0, 0, TTkGlbl.term_w, TTkGlbl.term_h) if TTkHelper._cursor: @@ -362,7 +376,10 @@ class TTkHelper: wx,wy,ww,wh = widget.geometry() if wx <= x < wx+ww and wy <= y < wy+wh: - return TTkHelper.widgetAt(x-wx, y-wy, widget.rootLayout()) + if hasattr(widget,'rootLayout'): + return TTkHelper.widgetAt(x-wx, y-wy, widget.rootLayout()) + else: + return widget continue elif item.layoutItemType() == TTkK.LayoutItem: diff --git a/TermTk/TTkCore/log.py b/TermTk/TTkCore/log.py index d3798de9..a84ba17b 100644 --- a/TermTk/TTkCore/log.py +++ b/TermTk/TTkCore/log.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLog'] + # This code is inspired by # https://github.com/ceccopierangiolieugenio/pyCuT/blob/master/cupy/CuTCore/CuDebug.py diff --git a/TermTk/TTkCore/propertyanimation.py b/TermTk/TTkCore/propertyanimation.py index 4ec6f4ac..cd358892 100644 --- a/TermTk/TTkCore/propertyanimation.py +++ b/TermTk/TTkCore/propertyanimation.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkPropertyAnimation', 'TTkEasingCurve'] + import time, math from inspect import getfullargspec from types import LambdaType diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index fae5e59e..ea3a3142 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -57,8 +55,11 @@ Methods .. autodecorator:: TermTk.pyTTkSlot ''' +__all__ = ['pyTTkSlot', 'pyTTkSignal'] + from inspect import getfullargspec from types import LambdaType +from threading import Lock def pyTTkSlot(*args, **kwargs): def pyTTkSlot_d(func): @@ -72,7 +73,7 @@ def pyTTkSignal(*args, **kwargs): class _pyTTkSignal_obj(): _signals = [] - __slots__ = ('_types', '_name', '_revision', '_connected_slots') + __slots__ = ('_types', '_name', '_revision', '_connected_slots', '_mutex') def __init__(self, *args, **kwargs): # ref: http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#PyQt5.QtCore.pyqtSignal @@ -90,6 +91,7 @@ class _pyTTkSignal_obj(): self._name = kwargs.get('name', None) self._revision = kwargs.get('revision', 0) self._connected_slots = {} + self._mutex = Lock() _pyTTkSignal_obj._signals.append(self) def connect(self, slot): @@ -133,11 +135,13 @@ class _pyTTkSignal_obj(): del self._connected_slots[slot] def emit(self, *args, **kwargs): + if not self._mutex.acquire(False): return if len(args) != len(self._types): error = "func"+str(self._types)+" signal has "+str(len(self._types))+" argument(s) but "+str(len(args))+" provided" raise TypeError(error) for slot,sl in self._connected_slots.copy().items(): slot(*args[sl], **kwargs) + self._mutex.release() def clear(self): self._connected_slots = {} diff --git a/TermTk/TTkCore/string.py b/TermTk/TTkCore/string.py index 86785ffe..bc1ee1db 100644 --- a/TermTk/TTkCore/string.py +++ b/TermTk/TTkCore/string.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkString'] + import re import unicodedata @@ -58,6 +58,8 @@ class TTkString(): # Combination of constructors (Highly Unrecommended) str7 = TTkString("test 7", color=TTkColor.fg('#FF0000')) ''' + unicodeWideOverflowColor = TTkColor.fg("#888888")+TTkColor.bg("#000088") + __slots__ = ('_text','_colors','_baseColor','_hasTab','_hasSpecialWidth') def __init__(self, text="", color=None): @@ -72,6 +74,16 @@ class TTkString(): self._checkWidth() # raise AttributeError(f"{type(text)} not supported in TTkString") + @staticmethod + def _importString1(text, colors): + ret = TTkString() + ret._text = text + ret._colors = colors + ret._baseColor = colors[-1] + ret._hasTab = '\t' in text + ret._checkWidth() + return ret + @staticmethod def _parseAnsi(text, color = TTkColor.RST): pos = 0 @@ -348,7 +360,7 @@ class TTkString(): elif sz > width: ret._text = rt[:-1]+TTkCfg.theme.unicodeWideOverflowCh[1] ret._colors = self._colors[:len(ret._text)] - ret._colors[-1] = TTkCfg.theme.unicodeWideOverflowColor + ret._colors[-1] = TTkString.unicodeWideOverflowColor break else: # Legacy, trim the string diff --git a/TermTk/TTkCore/timer.py b/TermTk/TTkCore/timer.py index cac4a3bf..9aad2a88 100644 --- a/TermTk/TTkCore/timer.py +++ b/TermTk/TTkCore/timer.py @@ -20,9 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTimer'] + import importlib.util if importlib.util.find_spec('pyodideProxy'): from .timer_pyodide import TTkTimer else: from .timer_unix import TTkTimer + diff --git a/TermTk/TTkCore/timer_pyodide.py b/TermTk/TTkCore/timer_pyodide.py index 0536c703..342a7dc3 100644 --- a/TermTk/TTkCore/timer_pyodide.py +++ b/TermTk/TTkCore/timer_pyodide.py @@ -53,10 +53,6 @@ class TTkTimer(): rw._paintEvent.set() TTkTimer._timers[tid].timeout.emit() - @staticmethod - def quitAll(): - pass - @staticmethod def pyodideQuit(): for timer in TTkTimer._timers: diff --git a/TermTk/TTkCore/timer_unix.py b/TermTk/TTkCore/timer_unix.py index ec77d6b2..e9d85711 100644 --- a/TermTk/TTkCore/timer_unix.py +++ b/TermTk/TTkCore/timer_unix.py @@ -23,9 +23,9 @@ import threading from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkCore.helper import TTkHelper class TTkTimer(threading.Thread): - _timers = [] __slots__ = ( 'timeout', '_delay', '_timer', '_quit', '_start') @@ -36,12 +36,7 @@ class TTkTimer(threading.Thread): self._start = threading.Event() self._timer = threading.Event() super().__init__() - TTkTimer._timers.append(self) - - @staticmethod - def quitAll(): - for timer in TTkTimer._timers: - timer.quit() + TTkHelper.quitEvent.connect(self.quit) def quit(self): self._quit.set() diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 9f2dd84a..fe0347cf 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTk'] + import os import signal import time @@ -42,8 +42,9 @@ from TermTk.TTkCore.timer import TTkTimer from TermTk.TTkCore.color import TTkColor from TermTk.TTkTheme.theme import TTkTheme from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer -class TTk(TTkWidget): +class TTk(TTkContainer): class _mouseCursor(TTkWidget): __slots__ = ('_cursor','_color') def __init__(self, input): @@ -79,7 +80,7 @@ class TTk(TTkWidget): '_input', '_termMouse', '_termDirectMouse', '_title', '_showMouseCursor', - '_sigmask', + '_sigmask', '_timer', '_drawMutex', '_paintEvent', '_lastMultiTap', @@ -93,12 +94,14 @@ class TTk(TTkWidget): if ('TERMTK_FILE_LOG' in os.environ and (_logFile := os.environ['TERMTK_FILE_LOG'])): TTkLog.use_default_file_logging(_logFile) + self._timer = None self.paintExecuted = pyTTkSignal() super().__init__(*args, **kwargs) self._termMouse = True self._termDirectMouse = kwargs.get('mouseTrack',False) self._input = TTkInput() self._input.inputEvent.connect(self._processInput) + self._input.pasteEvent.connect(self._processPaste) self._title = kwargs.get('title','TermTk') self._sigmask = kwargs.get('sigmask', TTkK.NONE) self._showMouseCursor = os.environ.get("TTK_MOUSE",kwargs.get('mouseCursor', False)) @@ -110,6 +113,10 @@ class TTk(TTkWidget): w,h = TTkTerm.getTerminalSize() self.setGeometry(0,0,w,h) + if 'TERMTK_NEWRENDERER' in os.environ: + TTkCfg.doubleBuffer = False + TTkCfg.doubleBufferNew = True + TTkHelper.registerRootWidget(self) frame = 0 @@ -178,6 +185,12 @@ class TTk(TTkWidget): return self._input.start() + @pyTTkSlot(str) + def _processPaste(self, txt:str): + if focusWidget := TTkHelper.getFocus(): + while focusWidget and not focusWidget.pasteEvent(txt): + focusWidget = focusWidget.parentWidget() + @pyTTkSlot(TTkKeyEvent, TTkMouseEvent) def _processInput(self, kevt, mevt): self._drawMutex.acquire() @@ -289,11 +302,30 @@ class TTk(TTkWidget): self._drawMutex.release() TTkLog.info(f"Resize: w:{TTkGlbl.term_w}, h:{TTkGlbl.term_h}") - + @pyTTkSlot() def quit(self): + '''quit TermTk + + .. warning:: + Method Deprecated, + + use :class:`~TermTk.TTkCore.helper.TTkHelper` -> :class:`~TermTk.TTkCore.helper.TTkHelper.quit` instead + + i.e. + + .. code:: python + + buttonQuit = TTkButton(text="QUIT",border=True) + buttonQuit.clicked.connect(TTkHelper.quit) + ''' + TTkHelper.quit() + + @pyTTkSlot() + def _quit(self): '''Tells the application to exit with a return code.''' + if self._timer: + self._timer.timeout.disconnect(self._time_event) self._input.inputEvent.clear() - TTkTimer.quitAll() self._paintEvent.set() self._input.close() @@ -328,7 +360,7 @@ class TTk(TTkWidget): # Deregister the handler # so CTRL-C can be redirected to the default handler if the app does not exit signal.signal(signal.SIGINT, signal.SIG_DFL) - self.quit() + TTkHelper.quit() def isVisibleAndParent(self): return self.isVisible() diff --git a/TermTk/TTkCore/util.py b/TermTk/TTkCore/util.py index eb74f021..66aa7966 100644 --- a/TermTk/TTkCore/util.py +++ b/TermTk/TTkCore/util.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkUtil'] + import zlib, pickle, base64 class TTkUtil(): diff --git a/TermTk/TTkGui/__init__.py b/TermTk/TTkGui/__init__.py index 33cfe160..fed17c31 100644 --- a/TermTk/TTkGui/__init__.py +++ b/TermTk/TTkGui/__init__.py @@ -1,6 +1,6 @@ -from .drag import TTkDrag, TTkDropEvent -from .textwrap1 import TTkTextWrap -from .textcursor import TTkTextCursor -from .textdocument import TTkTextDocument -from .clipboard import TTkClipboard -from .tooltip import TTkToolTip +from .drag import * +from .textwrap1 import * +from .textcursor import * +from .textdocument import * +from .clipboard import * +from .tooltip import * diff --git a/TermTk/TTkGui/clipboard.py b/TermTk/TTkGui/clipboard.py index f98542ec..1c94b8ca 100644 --- a/TermTk/TTkGui/clipboard.py +++ b/TermTk/TTkGui/clipboard.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkClipboard'] + import importlib.util class TTkClipboard(): diff --git a/TermTk/TTkGui/drag.py b/TermTk/TTkGui/drag.py index fbf7a621..a38a2bec 100644 --- a/TermTk/TTkGui/drag.py +++ b/TermTk/TTkGui/drag.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkDrag', 'TTkDropEvent'] + from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkWidgets.widget import TTkWidget diff --git a/TermTk/TTkGui/textcursor.py b/TermTk/TTkGui/textcursor.py index 141d6e22..0ed2fc65 100644 --- a/TermTk/TTkGui/textcursor.py +++ b/TermTk/TTkGui/textcursor.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextCursor'] + from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString @@ -308,7 +308,7 @@ class TTkTextCursor(): l = self._document._dataLines[-1] self.setPosition(len(self._document._dataLines)-1, len(l), moveMode, cID=cID) - operations = { + op = { TTkTextCursor.Right : moveRight, TTkTextCursor.Left : moveLeft, TTkTextCursor.Up : moveUpDown(-1), @@ -316,18 +316,19 @@ class TTkTextCursor(): TTkTextCursor.EndOfLine : moveEndOfLine, TTkTextCursor.StartOfLine: moveHome, TTkTextCursor.End: moveEnd, - } + }.get(operation,lambda _:_) - for cID, prop in enumerate(self._properties): + for _ in range(n): + for cID, prop in enumerate(self._properties): p = prop.position - operations.get(operation,lambda _:_)(cID,p,n) + op(cID,p,n) self._checkCursors(notify=self.position().toNum()!=currPos) def document(self): return self._document - def replaceText(self, text): + def replaceText(self, text, moveCursor=False): # if there is no selection, just select the next n chars till the end of the line # the newline is not replaced for p in self._properties: @@ -340,9 +341,9 @@ class TTkTextCursor(): pos = self._document._dataLines[line].nextPos(pos) pos = min(size,pos) p.anchor.set(line,pos) - return self.insertText(text) + return self.insertText(text, moveCursor) - def insertText(self, text): + def insertText(self, text, moveCursor=False): _lineFirst = -1 if self.hasSelection(): _lineFirst, _lineRem, _lineAdd = self._removeSelectedText() @@ -395,6 +396,8 @@ class TTkTextCursor(): for nl in reversed(newLines[1:]): self._document._dataLines.insert(l+1, nl) + # Move/Shift the cursors based on the pasted content + # # 2 scenarios: # 1) No Newline(s) added # p p+1 p+2 @@ -417,7 +420,8 @@ class TTkTextCursor(): diffPos = len(text.split('\n')[-1]) - p else: diffPos = len(text) - for pp in self._properties[i+1:]: + # Realign all the cursos (move the same if required) + for pp in self._properties[i+(0 if moveCursor else 1):]: if pp.position.line == l: pp.position.pos += diffPos pp.anchor.pos += diffPos diff --git a/TermTk/TTkGui/textdocument.py b/TermTk/TTkGui/textdocument.py index 10a9a717..abd893b4 100644 --- a/TermTk/TTkGui/textdocument.py +++ b/TermTk/TTkGui/textdocument.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextDocument'] + from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkCore.string import TTkString diff --git a/TermTk/TTkGui/textwrap1.py b/TermTk/TTkGui/textwrap1.py index 21f3ded7..c903179e 100644 --- a/TermTk/TTkGui/textwrap1.py +++ b/TermTk/TTkGui/textwrap1.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextWrap'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSignal from TermTk.TTkCore.string import TTkString @@ -83,7 +83,7 @@ class TTkTextWrap(): if not (w := self._wrapWidth): return - def _process(i,l): + def _process(i,l:TTkString): fr = 0 to = 0 if not len(l): # if the line is empty append it diff --git a/TermTk/TTkGui/tooltip.py b/TermTk/TTkGui/tooltip.py index a0767c63..3182c1e5 100644 --- a/TermTk/TTkGui/tooltip.py +++ b/TermTk/TTkGui/tooltip.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkToolTip'] + # from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.canvas import TTkCanvas diff --git a/TermTk/TTkLayouts/__init__.py b/TermTk/TTkLayouts/__init__.py index b921e528..00d8c30e 100644 --- a/TermTk/TTkLayouts/__init__.py +++ b/TermTk/TTkLayouts/__init__.py @@ -4,6 +4,6 @@ Layouts .. image:: /../_images/Layout.HLD.001.svg ''' -from .layout import * +from .layout import * from .gridlayout import * -from .boxlayout import * +from .boxlayout import * diff --git a/TermTk/TTkLayouts/boxlayout.py b/TermTk/TTkLayouts/boxlayout.py index 259d0161..2646b1c0 100644 --- a/TermTk/TTkLayouts/boxlayout.py +++ b/TermTk/TTkLayouts/boxlayout.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -26,6 +24,8 @@ **Box Layout** [`Tutorial `__] ''' +__all__ = ['TTkHBoxLayout', 'TTkVBoxLayout'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkLayouts.gridlayout import TTkGridLayout diff --git a/TermTk/TTkLayouts/gridlayout.py b/TermTk/TTkLayouts/gridlayout.py index cf97b0aa..5077fa7a 100644 --- a/TermTk/TTkLayouts/gridlayout.py +++ b/TermTk/TTkLayouts/gridlayout.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -26,6 +24,8 @@ **Grid Layout** [`Tutorial `__] ''' +__all__ = ['TTkGridLayout'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkLayouts.layout import TTkLayout diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index 9d17a135..b648d4fc 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -26,6 +24,8 @@ **Layout** [`Tutorial `__] ''' +__all__ = ['TTkLayoutItem', 'TTkLayout'] + from TermTk.TTkCore.constant import TTkK class TTkLayoutItem: @@ -213,8 +213,8 @@ class TTkLayout(TTkLayoutItem): if child._layoutItemType == TTkK.WidgetItem: if onlyVisible and not child.widget().isVisible(): continue yield child.widget() - if recurse: - yield from child.widget().rootLayout().iterWidgets() + if recurse and hasattr(cw:=child.widget(),'rootLayout'): + yield from cw.rootLayout().iterWidgets() if child._layoutItemType == TTkK.LayoutItem and recurse: yield from child.iterWidgets() @@ -312,7 +312,7 @@ class TTkLayout(TTkLayoutItem): :param widgets: the widget to be removed :type widgets: list of :class:`~TermTk.TTkWidgets` ''' - for item in self._items: + for item in reversed(self._items): if item._layoutItemType == TTkK.WidgetItem and \ item.widget() in widgets: self.removeItem(item) @@ -340,7 +340,7 @@ class TTkLayout(TTkLayoutItem): def lowerWidget(self, widget): '''lowerWidget''' item = self._findBranchWidget(widget) - for item in self._items: item.z+=1 + for i in self._items: i._z+=1 item._z = item._layer if item._layoutItemType == TTkK.LayoutItem: item.lowerWidget(widget) @@ -366,13 +366,11 @@ class TTkLayout(TTkLayoutItem): def update(self, *args, **kwargs): ret = False - for i in self.children(): - if i._layoutItemType == TTkK.WidgetItem and not i.isEmpty(): - ret = ret or i.widget().update(*args, **kwargs) - # TODO: Have a look at this: - # i.getCanvas().top() + for i in self._items: + if i._layoutItemType == TTkK.WidgetItem and (_wid:=i._widget): + ret = ret or _wid.update(*args, **kwargs) elif i._layoutItemType == TTkK.LayoutItem: - ret= ret or i.update(*args, **kwargs) + ret = ret or i.update(*args, **kwargs) return ret class TTkWidgetItem(TTkLayoutItem): diff --git a/TermTk/TTkTemplates/__init__.py b/TermTk/TTkTemplates/__init__.py index 670cc925..ce358011 100644 --- a/TermTk/TTkTemplates/__init__.py +++ b/TermTk/TTkTemplates/__init__.py @@ -1 +1 @@ -from .lookandfeel import TTkLookAndFeel +# from .lookandfeel import TTkLookAndFeel diff --git a/TermTk/TTkTemplates/dragevents.py b/TermTk/TTkTemplates/dragevents.py index 1abf7ff3..eb963721 100644 --- a/TermTk/TTkTemplates/dragevents.py +++ b/TermTk/TTkTemplates/dragevents.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2022 Eugenio Parodi diff --git a/TermTk/TTkTemplates/keyevents.py b/TermTk/TTkTemplates/keyevents.py index 9695ef45..1ea3684c 100644 --- a/TermTk/TTkTemplates/keyevents.py +++ b/TermTk/TTkTemplates/keyevents.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkTemplates/mouseevents.py b/TermTk/TTkTemplates/mouseevents.py index 03b82819..b9e4336c 100644 --- a/TermTk/TTkTemplates/mouseevents.py +++ b/TermTk/TTkTemplates/mouseevents.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkTestWidgets/__init__.py b/TermTk/TTkTestWidgets/__init__.py index 1aca0fd9..7d7d6308 100644 --- a/TermTk/TTkTestWidgets/__init__.py +++ b/TermTk/TTkTestWidgets/__init__.py @@ -1,6 +1,6 @@ -from .logviewer import TTkLogViewer -from .testwidget import TTkTestWidget -from .testwidgetsizes import TTkTestWidgetSizes -from .testabstractscroll import TTkTestAbstractScrollWidget -from .keypressview import TTkKeyPressView -from .tominspector import TTkTomInspector +from .logviewer import * +from .testwidget import * +from .testwidgetsizes import * +from .testabstractscroll import * +from .keypressview import * +# from .tominspector import * diff --git a/TermTk/TTkTestWidgets/keypressview.py b/TermTk/TTkTestWidgets/keypressview.py index 04c4ef54..74bf1f3d 100644 --- a/TermTk/TTkTestWidgets/keypressview.py +++ b/TermTk/TTkTestWidgets/keypressview.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkKeyPressView'] + from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent, mod2str, key2str from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkCore.helper import TTkHelper diff --git a/TermTk/TTkTestWidgets/keypressviewfont.py b/TermTk/TTkTestWidgets/keypressviewfont.py index bdae9e84..3afe7878 100644 --- a/TermTk/TTkTestWidgets/keypressviewfont.py +++ b/TermTk/TTkTestWidgets/keypressviewfont.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkKeyPressViewFont'] + from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent, mod2str, key2str from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkCore.helper import TTkHelper diff --git a/TermTk/TTkTestWidgets/logviewer.py b/TermTk/TTkTestWidgets/logviewer.py index 645544f4..ab4f06df 100644 --- a/TermTk/TTkTestWidgets/logviewer.py +++ b/TermTk/TTkTestWidgets/logviewer.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLogViewer'] + import os from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog diff --git a/TermTk/TTkTestWidgets/testabstractscroll.py b/TermTk/TTkTestWidgets/testabstractscroll.py index 0d5e3845..f24499dd 100644 --- a/TermTk/TTkTestWidgets/testabstractscroll.py +++ b/TermTk/TTkTestWidgets/testabstractscroll.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,8 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTestAbstractScrollWidget'] + from TermTk.TTkCore.signal import pyTTkSlot -from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView class TTkTestAbstractScrollWidget(TTkAbstractScrollView): diff --git a/TermTk/TTkTestWidgets/testwidget.py b/TermTk/TTkTestWidgets/testwidget.py index ba7e55dd..91bddd59 100644 --- a/TermTk/TTkTestWidgets/testwidget.py +++ b/TermTk/TTkTestWidgets/testwidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTestWidget'] + from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString diff --git a/TermTk/TTkTestWidgets/testwidgetsizes.py b/TermTk/TTkTestWidgets/testwidgetsizes.py index d81223d7..73dec385 100644 --- a/TermTk/TTkTestWidgets/testwidgetsizes.py +++ b/TermTk/TTkTestWidgets/testwidgetsizes.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,7 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkWidgets.frame import * +__all__ = ['TTkTestWidgetSizes'] + +from TermTk.TTkWidgets.frame import TTkFrame class TTkTestWidgetSizes(TTkFrame): ID = 1 diff --git a/TermTk/TTkTestWidgets/tominspector.py b/TermTk/TTkTestWidgets/tominspector.py index 15354238..5313de1c 100644 --- a/TermTk/TTkTestWidgets/tominspector.py +++ b/TermTk/TTkTestWidgets/tominspector.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTomInspector'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper @@ -37,7 +39,7 @@ from TermTk.TTkWidgets.lineedit import TTkLineEdit from TermTk.TTkWidgets.combobox import TTkComboBox from TermTk.TTkWidgets.checkbox import TTkCheckbox from TermTk.TTkWidgets.spinbox import TTkSpinBox -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.splitter import TTkSplitter from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkWidgets.button import TTkButton @@ -142,7 +144,7 @@ class _TTkDomTreeWidgetItem(TTkTreeWidgetItem): def domWidget(self): return self._domWidget -class TTkTomInspector(TTkWidget): +class TTkTomInspector(TTkContainer): __slots__ = ('_domTree','_detail','_splitter') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/TermTk/TTkTheme/draw_ascii.py b/TermTk/TTkTheme/draw_ascii.py index f17d587b..8da96f1a 100644 --- a/TermTk/TTkTheme/draw_ascii.py +++ b/TermTk/TTkTheme/draw_ascii.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkTheme/draw_utf8.py b/TermTk/TTkTheme/draw_utf8.py index b7bd2a7b..88ffa97c 100644 --- a/TermTk/TTkTheme/draw_utf8.py +++ b/TermTk/TTkTheme/draw_utf8.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkTheme/theme.py b/TermTk/TTkTheme/theme.py index 68b76127..ab9b81a4 100644 --- a/TermTk/TTkTheme/theme.py +++ b/TermTk/TTkTheme/theme.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTheme'] + from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.helper import TTkHelper # from TermTk.TTkCore.string import TTkString @@ -31,7 +31,6 @@ import TermTk.TTkTheme.fileicon_ascii as fi_ascii import TermTk.TTkTheme.draw_utf8 as draw_utf8 import TermTk.TTkTheme.draw_ascii as draw_ascii - class TTkTheme(): '''Default Theme Class This class can be reimplemented/extended to include new themes and default colors @@ -90,168 +89,3 @@ class TTkTheme(): TTkTheme.fileIcon = theme['file'].FileIcon TTkHelper.updateAll() - - - frameBorderColor = TTkColor.RST - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.RST`''' - frameTitleColor = TTkColor.fg("#dddddd")+TTkColor.bg("#222222") - '''Default to **TTkColor.fg("#dddddd")+TTkColor.bg("#222222")**''' - - windowBorderColor = TTkColor.RST - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.RST`''' - windowBorderColorFocus = TTkColor.fg("#ffff55") - '''Default to **TTkColor.fg("#ffff55")**''' - - textColorDisabled = TTkColor.fg("#888888") - '''Default to **TTkColor.fg("#888888")**''' - borderColorDisabled= TTkColor.fg("#888888") - '''Default to **TTkColor.fg("#888888")**''' - - buttonBoxGrid = 1 - '''Default to **1**''' - buttonBoxGridClicked = 0 - '''Default to **0**''' - buttonBoxGridDisabled = 0 - '''Default to **0**''' - buttonBoxGridChecked = 1 - '''Default to **0**''' - buttonBoxGridUnchecked = 3 - '''Default to **2**''' - - buttonTextColor = TTkColor.fg("#dddd88")+TTkColor.bg("#000044") - '''Default to **TTkColor.fg("#dddd88")+TTkColor.bg("#000044")**''' - buttonBorderColor = TTkColor.RST - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.RST`''' - - buttonTextColorClicked = TTkColor.fg("#ffffdd")+TTkColor.BOLD - '''Default to **TTkColor.fg("#ffffdd")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - buttonBorderColorClicked = TTkColor.fg("#dddddd")+TTkColor.BOLD - '''Default to **TTkColor.fg("#dddddd")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - - buttonTextColorFocus = buttonTextColor + TTkColor.BOLD - '''Default to :class:`buttonTextColor` **+** :class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - buttonBorderColorFocus = TTkColor.fg("#ffff00") + TTkColor.BOLD - '''Default to **TTkColor.fg("#ffff00") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - - buttonTextColorHover = TTkColor.fg("#dddd88")+TTkColor.bg("#000050")+TTkColor.BOLD - '''Default to **TTkColor.fg("#dddd88")+TTkColor.bg("#000066")+** :class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - buttonBorderColorHover = TTkColor.fg("#ffffcc") + TTkColor.BOLD - '''Default to **TTkColor.fg("#ffff88") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - - buttonTextColorDisabled = textColorDisabled - '''Default to :class:`textColorDisabled`''' - buttonBorderColorDisabled= borderColorDisabled - '''Default to :class:`borderColorDisabled`''' - - buttonTextColorChecked = TTkColor.fg("#dddd88")+TTkColor.bg("#004488") - '''Default to **TTkColor.fg("#dddd88")+TTkColor.bg("#004488")**''' - buttonTextColorUnchecked = buttonTextColor - '''Default to :class:`buttonTextColor`''' - buttonBorderColorChecked= TTkColor.fg("#FFFFFF") - '''Default to **TTkColor.fg("#FFFFFF")**''' - buttonBorderColorUnchecked= buttonBorderColor - '''Default to :class:`buttonBorderColor`''' - - menuButtonShortcutColor = TTkColor.fg("#dddddd") + TTkColor.UNDERLINE - '''Default to **TTkColor.fg("#dddddd") + TTkColor.UNDERLINE**''' - menuButtonColor = TTkColor.BOLD - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - menuButtonBorderColor = frameBorderColor - '''Default to :class:`frameBorderColor`''' - menuButtonColorClicked = TTkColor.fg("#ffff88") - '''Default to **TTkColor.fg("#ffff88")**''' - menuButtonBorderColorClicked = frameBorderColor - '''Default to :class:`frameBorderColor`''' - - listColor = TTkColor.RST - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.RST`''' - listColorSelected = TTkColor.fg("#ffffdd")+TTkColor.bg("#000044") + TTkColor.BOLD - '''Default to **TTkColor.fg("#ffffdd")+TTkColor.bg("#000044") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - listColorHighlighted = TTkColor.bg("#000088") + TTkColor.BOLD - '''Default to **TTkColor.bg("#000088") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - - lineEditTextColor = TTkColor.fg("#dddddd")+TTkColor.bg("#222222") - '''Default to **TTkColor.fg("#dddddd")+TTkColor.bg("#222222")**''' - lineEditTextColorFocus = TTkColor.fg("#dddddd")+TTkColor.bg("#000044") - '''Default to **TTkColor.fg("#dddddd")+TTkColor.bg("#000044")**''' - lineEditTextColorSelected = TTkColor.fg("#ffffff")+TTkColor.bg("#008844") - '''Default to **TTkColor.fg("#ffffff")+TTkColor.bg("#008844")**''' - - comboboxContentColor = TTkColor.fg("#dddd88")+TTkColor.bg("#111111") - '''Default to **TTkColor.fg("#dddd88")+TTkColor.bg("#111111")**''' - comboboxBorderColor = buttonBorderColor - '''Default to :class:`buttonBorderColor`''' - comboboxContentColorFocus = TTkColor.fg("#ffff88")+TTkColor.bg("#111111") - '''Default to **TTkColor.fg("#ffff88")+TTkColor.bg("#111111")**''' - comboboxBorderColorFocus = buttonBorderColorFocus - '''Default to :class:`buttonBorderColorFocus`''' - comboboxContentColorDisabled = TTkColor.fg("#888888") - '''Default to **TTkColor.fg("#888888")**''' - comboboxBorderColorDisabled= TTkColor.fg("#888888") - '''Default to **TTkColor.fg("#888888")**''' - - checkboxContentColor = buttonTextColor - '''Default to :class:`buttonTextColor`''' - checkboxBorderColor = buttonBorderColor - '''Default to :class:`buttonBorderColor`''' - checkboxTextColor = TTkColor.RST - '''Default to :class:`~TermTk.TTkCore.color.TTkColor.RST`''' - checkboxContentColorFocus = buttonTextColorFocus - '''Default to :class:`buttonTextColorFocus`''' - checkboxBorderColorFocus = buttonBorderColorFocus - '''Default to :class:`buttonBorderColorFocus`''' - checkboxTextColorFocus = TTkColor.fg("#ffff88")+TTkColor.bg("#111111") - '''Default to **TTkColor.fg("#ffff88")+TTkColor.bg("#111111")**''' - - radioButtonContentColor = checkboxContentColor - '''Default to :class:`checkboxContentColor`''' - radioButtonBorderColor = checkboxBorderColor - '''Default to :class:`checkboxBorderColor`''' - radioButtonTextColor = checkboxTextColor - '''Default to :class:`checkboxTextColor`''' - radioButtonContentColorFocus = checkboxContentColorFocus - '''Default to :class:`checkboxContentColorFocus`''' - radioButtonBorderColorFocus = checkboxBorderColorFocus - '''Default to :class:`checkboxBorderColorFocus`''' - radioButtonTextColorFocus = checkboxTextColorFocus - '''Default to :class:`checkboxTextColorFocus`''' - - tabColor = TTkColor.fg("#aaaaaa") - '''Default to **TTkColor.fg("#aaaaaa")**''' - tabOffsetColor = TTkColor.RST - '''Default to **TTkColor.RST**''' - tabBorderColor = frameBorderColor - '''Default to :class:`frameBorderColor`''' - tabSelectColor = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD - '''Default to **TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - tabColorFocus = TTkColor.fg("#aaaaaa") - '''Default to **TTkColor.fg("#aaaaaa")**''' - tabOffsetColorFocus = tabOffsetColor - '''Default to :class:`tabOffsetColor`''' - tabBorderColorFocus = TTkColor.fg("#ffff88") - '''Default to **TTkColor.fg("#ffff88")**''' - tabSelectColorFocus = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD - '''Default to **TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - - treeHeaderColor = TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+TTkColor.BOLD - '''Default to **TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - treeSelectedColor = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD - '''Default to **TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+**:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' - treeLineColor = TTkColor.fg("#444444") - '''Default to **TTkColor.fg("#444444")**''' - - textEditLineNumberColor = TTkColor.fg("#88aaaa")+TTkColor.bg("#333333") - '''Default to **TTkColor.fg("#aaaaaa")+TTkColor.bg("#333333")**''' - textEditLineNumberWrapcharColor = TTkColor.fg("#888888")+TTkColor.bg("#333333") - '''Default to **TTkColor.fg("#aaaaaa")+TTkColor.bg("#333333")**''' - textEditLineNumberSeparatorColor = TTkColor.fg("#444444") - '''Default to **TTkColor.fg("#444444")**''' - - unicodeWideOverflowColor = TTkColor.fg("#888888")+TTkColor.bg("#000088") - '''Default to **TTkColor.fg("#888888")**+**TTkColor.bg("#000088")**''' - - progresssBarColor = TTkColor.fg('#0000aa')+TTkColor.bg("#000044") - '''Default to **TTkColor.fg('#0000aa')**+**TTkColor.bg("#000044")**''' - - progressBarTextColor = TTkColor.fg('#ffffff') - '''Default to **TTkColor.fg('#ffffff')**''' diff --git a/TermTk/TTkUiTools/__init__.py b/TermTk/TTkUiTools/__init__.py index daf3ff57..fd4f407e 100644 --- a/TermTk/TTkUiTools/__init__.py +++ b/TermTk/TTkUiTools/__init__.py @@ -1,2 +1,2 @@ -from .uiloader import TTkUiLoader -from .uiproperties import TTkUiProperties \ No newline at end of file +from .uiloader import * +from .uiproperties import * \ No newline at end of file diff --git a/TermTk/TTkUiTools/properties/__init__.py b/TermTk/TTkUiTools/properties/__init__.py index 78474643..d72a9825 100644 --- a/TermTk/TTkUiTools/properties/__init__.py +++ b/TermTk/TTkUiTools/properties/__init__.py @@ -1,32 +1,33 @@ # from .about import -from .button import TTkButtonProperties -from .checkbox import TTkCheckboxProperties -from .combobox import TTkComboBoxProperties -from .frame import TTkFrameProperties +from .button import * +from .checkbox import * +from .combobox import * +from .container import * +from .frame import * # from .graph import # from .image import -from .label import TTkLabelProperties -from .lineedit import TTkLineEditProperties -from .list_ import TTkListProperties +from .label import * +from .lineedit import * +from .list_ import * # from .listwidget import # from .menubar import -from .menu import TTkMenuButtonProperties +from .menu import * # from .progressbar import -from .radiobutton import TTkRadioButtonProperties -from .resizableframe import TTkResizableFrameProperties +from .radiobutton import * +from .resizableframe import * # from .scrollarea import -from .scrollbar import TTkScrollBarProperties +from .scrollbar import * # from .spacer import -from .spinbox import TTkSpinBoxProperties -from .splitter import TTkSplitterProperties +from .spinbox import * +from .splitter import * # from .tabwidget import -from .texedit import TTkTextEditProperties -from .widget import TTkWidgetProperties -from .window import TTkWindowProperties +from .texedit import * +from .widget import * +from .window import * # Pickers -from .colorpicker import TTkColorButtonPickerProperties -from .filepicker import TTkFileButtonPickerProperties +from .colorpicker import * +from .filepicker import * # Layouts -from .layout import TTkLayoutProperties +from .layout import * diff --git a/TermTk/TTkUiTools/properties/button.py b/TermTk/TTkUiTools/properties/button.py index 9f96448b..50a41ccb 100644 --- a/TermTk/TTkUiTools/properties/button.py +++ b/TermTk/TTkUiTools/properties/button.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkButtonProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.button import TTkButton diff --git a/TermTk/TTkUiTools/properties/checkbox.py b/TermTk/TTkUiTools/properties/checkbox.py index 1e320749..7f02c4b1 100644 --- a/TermTk/TTkUiTools/properties/checkbox.py +++ b/TermTk/TTkUiTools/properties/checkbox.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkCheckboxProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.checkbox import TTkCheckbox diff --git a/TermTk/TTkUiTools/properties/colorpicker.py b/TermTk/TTkUiTools/properties/colorpicker.py index 3916c0e9..5afa6ce9 100644 --- a/TermTk/TTkUiTools/properties/colorpicker.py +++ b/TermTk/TTkUiTools/properties/colorpicker.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkColorButtonPickerProperties'] + from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.TTkPickers.colorpicker import TTkColorButtonPicker diff --git a/TermTk/TTkUiTools/properties/combobox.py b/TermTk/TTkUiTools/properties/combobox.py index 683e6c47..29ba5d28 100644 --- a/TermTk/TTkUiTools/properties/combobox.py +++ b/TermTk/TTkUiTools/properties/combobox.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkComboBoxProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.combobox import TTkComboBox diff --git a/TermTk/TTkUiTools/properties/container.py b/TermTk/TTkUiTools/properties/container.py new file mode 100644 index 00000000..bed57d94 --- /dev/null +++ b/TermTk/TTkUiTools/properties/container.py @@ -0,0 +1,49 @@ +# MIT License +# +# Copyright (c) 2023 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. + +__all__ = ['TTkContainerProperties'] + +from TermTk.TTkCore.string import TTkString +from TermTk.TTkLayouts.layout import TTkLayout +from TermTk.TTkWidgets.container import TTkContainer + +TTkContainerProperties = { + 'properties' : { + 'Padding': { + 'get': { 'cb':TTkContainer.getPadding, 'type': [ + { 'name': 'top', 'type':int } , + { 'name': 'bottom', 'type':int } , + { 'name': 'left', 'type':int } , + { 'name': 'right', 'type':int } ] }, + 'set': { 'cb':TTkContainer.setPadding, 'type': [ + { 'name': 'top', 'type':int } , + { 'name': 'bottom', 'type':int } , + { 'name': 'left', 'type':int } , + { 'name': 'right', 'type':int } ] } }, + 'Layout' : { + 'init': {'name':'layout', 'type':TTkLayout} , + 'get': { 'cb':TTkContainer.layout, 'type':TTkLayout} , + 'set': { 'cb':TTkContainer.setLayout, 'type':TTkLayout} }, + },'signals' : { + },'slots' : { + } +} diff --git a/TermTk/TTkUiTools/properties/filepicker.py b/TermTk/TTkUiTools/properties/filepicker.py index aab82e8f..1e9d5e60 100644 --- a/TermTk/TTkUiTools/properties/filepicker.py +++ b/TermTk/TTkUiTools/properties/filepicker.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileButtonPickerProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.TTkPickers.filepicker import TTkFileButtonPicker diff --git a/TermTk/TTkUiTools/properties/frame.py b/TermTk/TTkUiTools/properties/frame.py index ae39de99..d3f62dd0 100644 --- a/TermTk/TTkUiTools/properties/frame.py +++ b/TermTk/TTkUiTools/properties/frame.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFrameProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.frame import TTkFrame diff --git a/TermTk/TTkUiTools/properties/label.py b/TermTk/TTkUiTools/properties/label.py index a5a0fee6..59848ea5 100644 --- a/TermTk/TTkUiTools/properties/label.py +++ b/TermTk/TTkUiTools/properties/label.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLabelProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString diff --git a/TermTk/TTkUiTools/properties/layout.py b/TermTk/TTkUiTools/properties/layout.py index 4f228de5..173b4022 100644 --- a/TermTk/TTkUiTools/properties/layout.py +++ b/TermTk/TTkUiTools/properties/layout.py @@ -20,27 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# MIT License -# -# Copyright (c) 2023 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. +__all__ = ['TTkLayoutProperties'] from TermTk.TTkLayouts.layout import TTkLayout diff --git a/TermTk/TTkUiTools/properties/lineedit.py b/TermTk/TTkUiTools/properties/lineedit.py index 9076d113..576f9a94 100644 --- a/TermTk/TTkUiTools/properties/lineedit.py +++ b/TermTk/TTkUiTools/properties/lineedit.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLineEditProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.lineedit import TTkLineEdit diff --git a/TermTk/TTkUiTools/properties/list_.py b/TermTk/TTkUiTools/properties/list_.py index d7053fc0..cb1c7693 100644 --- a/TermTk/TTkUiTools/properties/list_.py +++ b/TermTk/TTkUiTools/properties/list_.py @@ -20,4 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkListProperties'] + TTkListProperties = {'properties' : {},'signals' : {},'slots' : {}} \ No newline at end of file diff --git a/TermTk/TTkUiTools/properties/listwidget.py b/TermTk/TTkUiTools/properties/listwidget.py index f2708318..044a5b9e 100644 --- a/TermTk/TTkUiTools/properties/listwidget.py +++ b/TermTk/TTkUiTools/properties/listwidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi diff --git a/TermTk/TTkUiTools/properties/menu.py b/TermTk/TTkUiTools/properties/menu.py index 2ba289b4..c97e6c1a 100644 --- a/TermTk/TTkUiTools/properties/menu.py +++ b/TermTk/TTkUiTools/properties/menu.py @@ -19,6 +19,9 @@ # 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__ = ['TTkMenuButtonProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.menu import TTkMenuButton diff --git a/TermTk/TTkUiTools/properties/radiobutton.py b/TermTk/TTkUiTools/properties/radiobutton.py index 280183ea..47022e9f 100644 --- a/TermTk/TTkUiTools/properties/radiobutton.py +++ b/TermTk/TTkUiTools/properties/radiobutton.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkRadioButtonProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.radiobutton import TTkRadioButton diff --git a/TermTk/TTkUiTools/properties/resizableframe.py b/TermTk/TTkUiTools/properties/resizableframe.py index 1158424b..7312baaf 100644 --- a/TermTk/TTkUiTools/properties/resizableframe.py +++ b/TermTk/TTkUiTools/properties/resizableframe.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,4 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkResizableFrameProperties'] + TTkResizableFrameProperties = {'properties':{}, 'signals':{}, 'slots':{}} diff --git a/TermTk/TTkUiTools/properties/scrollarea.py b/TermTk/TTkUiTools/properties/scrollarea.py index 4174956b..ee0973b1 100644 --- a/TermTk/TTkUiTools/properties/scrollarea.py +++ b/TermTk/TTkUiTools/properties/scrollarea.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi diff --git a/TermTk/TTkUiTools/properties/scrollbar.py b/TermTk/TTkUiTools/properties/scrollbar.py index 43ae3fcc..6c292e32 100644 --- a/TermTk/TTkUiTools/properties/scrollbar.py +++ b/TermTk/TTkUiTools/properties/scrollbar.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkScrollBarProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.scrollbar import TTkScrollBar diff --git a/TermTk/TTkUiTools/properties/spacer.py b/TermTk/TTkUiTools/properties/spacer.py index 4174956b..ee0973b1 100644 --- a/TermTk/TTkUiTools/properties/spacer.py +++ b/TermTk/TTkUiTools/properties/spacer.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi diff --git a/TermTk/TTkUiTools/properties/spinbox.py b/TermTk/TTkUiTools/properties/spinbox.py index 785fdb59..449f708c 100644 --- a/TermTk/TTkUiTools/properties/spinbox.py +++ b/TermTk/TTkUiTools/properties/spinbox.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkSpinBoxProperties'] + from TermTk.TTkWidgets.spinbox import TTkSpinBox TTkSpinBoxProperties = { diff --git a/TermTk/TTkUiTools/properties/splitter.py b/TermTk/TTkUiTools/properties/splitter.py index 528ef077..3216d08d 100644 --- a/TermTk/TTkUiTools/properties/splitter.py +++ b/TermTk/TTkUiTools/properties/splitter.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkSplitterProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.splitter import TTkSplitter diff --git a/TermTk/TTkUiTools/properties/tabwidget.py b/TermTk/TTkUiTools/properties/tabwidget.py index 4174956b..ee0973b1 100644 --- a/TermTk/TTkUiTools/properties/tabwidget.py +++ b/TermTk/TTkUiTools/properties/tabwidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi diff --git a/TermTk/TTkUiTools/properties/texedit.py b/TermTk/TTkUiTools/properties/texedit.py index 9f460fe6..cc91717f 100644 --- a/TermTk/TTkUiTools/properties/texedit.py +++ b/TermTk/TTkUiTools/properties/texedit.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextEditProperties'] + # from TermTk.TTkCore.string import TTkString # from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor diff --git a/TermTk/TTkUiTools/properties/widget.py b/TermTk/TTkUiTools/properties/widget.py index 121629e0..0a8af632 100644 --- a/TermTk/TTkUiTools/properties/widget.py +++ b/TermTk/TTkUiTools/properties/widget.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkWidgetProperties'] + from TermTk.TTkCore.string import TTkString from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkWidgets.widget import TTkWidget @@ -78,21 +80,6 @@ TTkWidgetProperties = { 'init': {'name':'maxHeight', 'type':int } , 'get': { 'cb':TTkWidget.maximumHeight, 'type':int } , 'set': { 'cb':TTkWidget.setMaximumHeight,'type':int } }, - 'Padding': { - 'get': { 'cb':TTkWidget.getPadding, 'type': [ - { 'name': 'top', 'type':int } , - { 'name': 'bottom', 'type':int } , - { 'name': 'left', 'type':int } , - { 'name': 'right', 'type':int } ] }, - 'set': { 'cb':TTkWidget.setPadding, 'type': [ - { 'name': 'top', 'type':int } , - { 'name': 'bottom', 'type':int } , - { 'name': 'left', 'type':int } , - { 'name': 'right', 'type':int } ] } }, - 'Layout' : { - 'init': {'name':'layout', 'type':TTkLayout} , - 'get': { 'cb':TTkWidget.layout, 'type':TTkLayout} , - 'set': { 'cb':TTkWidget.setLayout, 'type':TTkLayout} }, 'Visible' : { 'init': {'name':'visible', 'type':bool } , 'get': { 'cb':TTkWidget.isVisible, 'type':bool } , @@ -106,6 +93,8 @@ TTkWidgetProperties = { 'get': { 'cb':TTkWidget.toolTip, 'type':TTkString } , 'set': { 'cb':TTkWidget.setToolTip, 'type':TTkString } }, },'signals' : { + 'closed(TTkWidget)' : {'name' : 'closed', 'type':TTkWidget}, + 'currentStyleChanged(style)' : {'name' : 'currentStyleChanged', 'type':dict}, 'focusChanged(bool)' : {'name' : 'focusChanged', 'type':bool}, 'sizeChanged(int,int)' : {'name' : 'sizeChanged', 'type':(int, int)} },'slots' : { diff --git a/TermTk/TTkUiTools/properties/window.py b/TermTk/TTkUiTools/properties/window.py index 317b6e86..a5ca2b7c 100644 --- a/TermTk/TTkUiTools/properties/window.py +++ b/TermTk/TTkUiTools/properties/window.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2023 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkWindowProperties'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.window import TTkWindow diff --git a/TermTk/TTkUiTools/uiloader.py b/TermTk/TTkUiTools/uiloader.py index 1836d39c..772b5c67 100644 --- a/TermTk/TTkUiTools/uiloader.py +++ b/TermTk/TTkUiTools/uiloader.py @@ -20,12 +20,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkUiLoader'] + # Yaml is not included by default # import yaml import os, json from TermTk import TTkLog -from TermTk import TTkCfg +from TermTk import TTkCfg, TTkColor from TermTk.TTkLayouts import TTkLayout, TTkGridLayout, TTkVBoxLayout, TTkHBoxLayout from TermTk.TTkWidgets import * from TermTk.TTkTestWidgets import * @@ -73,8 +75,33 @@ class TTkUiLoader(): ''' return TTkUiLoader.loadDict(json.loads(text), baseWidget, kwargs) + def _convert_1_0_1_to_2_0_0(ui): + def _processWidget(_wid): + if issubclass(globals()[_wid['class']],TTkContainer): + if hasattr(_wid,'layout'): + _wid['layout']['children'] = [_processWidget(_ch) for _ch in _wid['layout']['children']] + elif _wid['layout']['children']: + _wid['class'] = 'TTkContainer' + _wid['layout']['children'] = [_processWidget(_ch) for _ch in _wid['layout']['children']] + else: + _wid.pop('layout',None) + _wid['params'].pop("layout",None) + return _wid + + tui = _processWidget(ui['tui']) + + return { + "version": "2.0.0", + "connections" : ui['connections'], + "tui": tui } + @staticmethod - def _loadDict_1_0_0(ui, baseWidget:TTkWidget=None, args=None): + def _loadDict_1_0_0(ui, *args, **kwargs): + ui = TTkUiLoader._convert_1_0_1_to_2_0_0(ui) + return TTkUiLoader._loadDict_2_0_0(ui, *args, **kwargs) + + @staticmethod + def _loadDict_2_0_0(ui, baseWidget:TTkWidget=None, args=None): def _setMenuButton(_menuButtonProp, _menuButton:TTkMenuButton): if 'submenu' in _menuButtonProp: for _sm in _menuButtonProp['submenu']: @@ -259,6 +286,14 @@ class TTkUiLoader(): return widget + @staticmethod + def normalise(ui): + cb = {'1.0.0' : TTkUiLoader._convert_1_0_1_to_2_0_0, + '1.0.1' : TTkUiLoader._convert_1_0_1_to_2_0_0, + '2.0.0' : lambda x: x + }.get(ui['version'], lambda x: x) + return cb(ui) + @staticmethod def loadDict(ui, baseWidget:TTkWidget=None, kwargs=None) -> TTkWidget: '''load the dictionary representing the ui definition of the widget @@ -273,7 +308,8 @@ class TTkUiLoader(): :return: :class:`~TermTk.TTkWidgets.widget.TTkWidget` ''' cb = {'1.0.0' : TTkUiLoader._loadDict_1_0_0, - '1.0.1' : TTkUiLoader._loadDict_1_0_0 + '1.0.1' : TTkUiLoader._loadDict_1_0_0, + '2.0.0' : TTkUiLoader._loadDict_2_0_0 }.get(ui['version'], None) if cb: return cb(ui, baseWidget, kwargs) diff --git a/TermTk/TTkUiTools/uiproperties.py b/TermTk/TTkUiTools/uiproperties.py index 9e5ba22b..1f25e448 100644 --- a/TermTk/TTkUiTools/uiproperties.py +++ b/TermTk/TTkUiTools/uiproperties.py @@ -20,7 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkLayouts import TTkLayout, TTkGridLayout, TTkVBoxLayout, TTkHBoxLayout +__all__ = ['TTkUiProperties'] + +from TermTk.TTkLayouts import * from TermTk.TTkWidgets import * from .properties import * @@ -28,6 +30,7 @@ TTkUiProperties = { # Widgets TTkButton.__name__: TTkButtonProperties, TTkCheckbox.__name__: TTkCheckboxProperties, + TTkContainer.__name__: TTkContainerProperties, TTkComboBox.__name__: TTkComboBoxProperties, TTkFrame.__name__: TTkFrameProperties, TTkLabel.__name__: TTkLabelProperties, diff --git a/TermTk/TTkWidgets/Fancy/__init__.py b/TermTk/TTkWidgets/Fancy/__init__.py index 85b8abe8..4241fa15 100644 --- a/TermTk/TTkWidgets/Fancy/__init__.py +++ b/TermTk/TTkWidgets/Fancy/__init__.py @@ -4,3 +4,4 @@ from .tree import * from .treeview import * from .treewidget import * from .treewidgetitem import * +from .progressbar import * diff --git a/TermTk/TTkWidgets/progressbar.py b/TermTk/TTkWidgets/Fancy/progressbar.py similarity index 88% rename from TermTk/TTkWidgets/progressbar.py rename to TermTk/TTkWidgets/Fancy/progressbar.py index 020924c8..f0c1ba9e 100644 --- a/TermTk/TTkWidgets/progressbar.py +++ b/TermTk/TTkWidgets/Fancy/progressbar.py @@ -21,16 +21,26 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyProgressBar', 'TTkLookAndFeelFPBar'] + import math from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkTemplates.lookandfeel import TTkLookAndFeel -class TTkLookAndFeelPBar(TTkLookAndFeel): +class TTkLookAndFeel(): + __slots__ = ('modified') + def __init__(self, *args, **kwargs): + self.modified = pyTTkSignal() + +class TTkLookAndFeelFPBar(TTkLookAndFeel): + progresssBarColor = TTkColor.fg('#0000aa')+TTkColor.bg("#000044") + progressBarTextColor = TTkColor.fg('#ffffff') + __slots__ = ('_textWidth', '_showText') def __init__(self, showText=True, textWidth=4): super().__init__() @@ -56,21 +66,22 @@ class TTkLookAndFeelPBar(TTkLookAndFeel): self.modified.emit() def color(self, value, minimum, maximum): - return TTkCfg.theme.progresssBarColor + return self.progresssBarColor def text(self, value, minimum, maximum): percent = round(100*(value-minimum)/(maximum-minimum)) - return TTkString(f"{percent:3}%", color=TTkCfg.theme.progressBarTextColor) + return TTkString(f"{percent:3}%", color=self.progressBarTextColor) ''' Progressbar: |████████▌ | rest block ^ full blocks ^^^^^^^^ ''' -class TTkProgressBar(TTkWidget): - '''TTkProgressBar''' +class TTkFancyProgressBar(TTkWidget): + '''TTkFancyProgressBar''' __slots__ = ( + '_lookAndFeel', '_value', '_minimum', '_maximum', # Signals 'valueChanged') @@ -78,8 +89,8 @@ class TTkProgressBar(TTkWidget): def __init__(self, *args, **kwargs): self.valueChanged = pyTTkSignal(float) TTkWidget.__init__(self, *args, **kwargs) - if not kwargs.get('lookAndFeel'): - self.setLookAndFeel(TTkLookAndFeelPBar()) + self._lookAndFeel = kwargs.get('lookAndFeel',TTkLookAndFeelFPBar()) + self._lookAndFeel.modified.connect(self.update) self._value_min, self._value_max, self._value = 0.0, 1.0, 0.0 self.setValue(kwargs.get('value', 0.0)) self.setMinimumSize(3, 1) @@ -154,7 +165,7 @@ class TTkProgressBar(TTkWidget): def paintEvent(self, canvas): width, height = self.size() - laf = self.lookAndFeel() + laf = self._lookAndFeel text = laf.text(self._value, self._value_min, self._value_max) color_bar = laf.color(self._value, self._value_min, self._value_max) blocks = TTkCfg.theme.progressbarBlocks diff --git a/TermTk/TTkWidgets/Fancy/table.py b/TermTk/TTkWidgets/Fancy/table.py index 76b5e0db..4233f272 100644 --- a/TermTk/TTkWidgets/Fancy/table.py +++ b/TermTk/TTkWidgets/Fancy/table.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTable'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.Fancy.tableview import TTkFancyTableView from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea diff --git a/TermTk/TTkWidgets/Fancy/tableview.py b/TermTk/TTkWidgets/Fancy/tableview.py index 9cbd9db5..afc5b5ed 100644 --- a/TermTk/TTkWidgets/Fancy/tableview.py +++ b/TermTk/TTkWidgets/Fancy/tableview.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTableView'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor diff --git a/TermTk/TTkWidgets/Fancy/tree.py b/TermTk/TTkWidgets/Fancy/tree.py index 87ce18a8..975e2be6 100644 --- a/TermTk/TTkWidgets/Fancy/tree.py +++ b/TermTk/TTkWidgets/Fancy/tree.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTree'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.Fancy.treewidget import TTkFancyTreeWidget from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea diff --git a/TermTk/TTkWidgets/Fancy/treeview.py b/TermTk/TTkWidgets/Fancy/treeview.py index a72d75e2..b06eb24f 100644 --- a/TermTk/TTkWidgets/Fancy/treeview.py +++ b/TermTk/TTkWidgets/Fancy/treeview.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTreeView'] + from TermTk.TTkWidgets.Fancy.tableview import TTkFancyTableView class TTkFancyTreeView(TTkFancyTableView): diff --git a/TermTk/TTkWidgets/Fancy/treewidget.py b/TermTk/TTkWidgets/Fancy/treewidget.py index c8e3c42b..80117c60 100644 --- a/TermTk/TTkWidgets/Fancy/treewidget.py +++ b/TermTk/TTkWidgets/Fancy/treewidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,11 +20,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTreeWidget'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.checkbox import TTkCheckbox from TermTk.TTkWidgets.Fancy.tableview import TTkFancyTableView from TermTk.TTkWidgets.Fancy.treewidgetitem import TTkFancyTreeWidgetItem @@ -43,7 +44,7 @@ class _TTkDisplayedTreeItemControl(TTkCheckbox): canvas.drawText(pos=(0,0), text="▶") -class _TTkDisplayedTreeItem(TTkWidget): +class _TTkDisplayedTreeItem(TTkContainer): __slots__ = ('_depth', '_control', '_text', '_id', '_clicked', '_treeWidgetItem', '_isLeaf' ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/TermTk/TTkWidgets/Fancy/treewidgetitem.py b/TermTk/TTkWidgets/Fancy/treewidgetitem.py index 84cbf059..367becc5 100644 --- a/TermTk/TTkWidgets/Fancy/treewidgetitem.py +++ b/TermTk/TTkWidgets/Fancy/treewidgetitem.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFancyTreeWidgetItem'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSignal diff --git a/TermTk/TTkWidgets/TTkModelView/__init__.py b/TermTk/TTkWidgets/TTkModelView/__init__.py index f37a83a7..acb9c5c0 100644 --- a/TermTk/TTkWidgets/TTkModelView/__init__.py +++ b/TermTk/TTkWidgets/TTkModelView/__init__.py @@ -1,6 +1,6 @@ -from .tree import TTkTree -from .treewidget import TTkTreeWidget -from .treewidgetitem import TTkTreeWidgetItem -from .filetree import TTkFileTree -from .filetreewidget import TTkFileTreeWidget -from .filetreewidgetitem import TTkFileTreeWidgetItem +from .tree import * +from .treewidget import * +from .treewidgetitem import * +from .filetree import * +from .filetreewidget import * +from .filetreewidgetitem import * diff --git a/TermTk/TTkWidgets/TTkModelView/filetree.py b/TermTk/TTkWidgets/TTkModelView/filetree.py index 8d8768a5..c129c694 100644 --- a/TermTk/TTkWidgets/TTkModelView/filetree.py +++ b/TermTk/TTkWidgets/TTkModelView/filetree.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileTree'] + from TermTk.TTkWidgets.TTkModelView.tree import TTkTree from TermTk.TTkWidgets.TTkModelView.filetreewidget import TTkFileTreeWidget diff --git a/TermTk/TTkWidgets/TTkModelView/filetreewidget.py b/TermTk/TTkWidgets/TTkModelView/filetreewidget.py index 8b3fbda9..3ef9f87d 100644 --- a/TermTk/TTkWidgets/TTkModelView/filetreewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/filetreewidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileTreeWidget'] + import os import datetime diff --git a/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py b/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py index 349e638a..bffd6ef1 100644 --- a/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py +++ b/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileTreeWidgetItem'] + import re from TermTk.TTkCore.constant import TTkK diff --git a/TermTk/TTkWidgets/TTkModelView/tree.py b/TermTk/TTkWidgets/TTkModelView/tree.py index 3eeda770..5e6dfc53 100644 --- a/TermTk/TTkWidgets/TTkModelView/tree.py +++ b/TermTk/TTkWidgets/TTkModelView/tree.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTree'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.TTkModelView.treewidget import TTkTreeWidget from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea @@ -33,7 +33,7 @@ class TTkTree(TTkAbstractScrollArea): 'itemActivated', 'itemChanged', 'itemClicked', 'itemExpanded', 'itemCollapsed', 'itemDoubleClicked', # Forwarded Methods 'setAlignment', 'setHeader', 'setHeaderLabels', 'setColumnSize', 'setColumnColors', 'appendItem', - 'addTopLevelItem', 'takeTopLevelItem', 'topLevelItem', 'indexOfTopLevelItem', 'selectedItems', 'clear' ) + 'addTopLevelItem', 'addTopLevelItems', 'takeTopLevelItem', 'topLevelItem', 'indexOfTopLevelItem', 'selectedItems', 'clear' ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -58,6 +58,7 @@ class TTkTree(TTkAbstractScrollArea): #self.setColumnColors = self._treeView.setColumnColors #self.appendItem = self._treeView.appendItem self.addTopLevelItem = self._treeView.addTopLevelItem + self.addTopLevelItems = self._treeView.addTopLevelItems self.takeTopLevelItem = self._treeView.takeTopLevelItem self.topLevelItem = self._treeView.topLevelItem self.indexOfTopLevelItem = self._treeView.indexOfTopLevelItem diff --git a/TermTk/TTkWidgets/TTkModelView/treewidget.py b/TermTk/TTkWidgets/TTkModelView/treewidget.py index 322e425f..ef0d8b6c 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTreeWidget'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString @@ -33,9 +33,25 @@ from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from dataclasses import dataclass class TTkTreeWidget(TTkAbstractScrollView): + '''TTkTreeWidget''' + + classStyle = { + 'default': { + 'color': TTkColor.RST, + 'lineColor': TTkColor.fg("#444444"), + 'headerColor': TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+TTkColor.BOLD, + 'selectedColor': TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD, + 'separatorColor': TTkColor.fg("#444444")}, + 'disabled': { + 'color': TTkColor.fg("#888888"), + 'lineColor': TTkColor.fg("#888888"), + 'headerColor': TTkColor.fg("#888888"), + 'selectedColor': TTkColor.fg("#888888"), + 'separatorColor': TTkColor.fg("#888888")}, + } + __slots__ = ( '_rootItem', '_header', '_columnsPos', '_cache', '_selectedId', '_selected', '_separatorSelected', '_mouseDelta', - '_headerColor', '_selectedColor', '_lineColor', '_sortColumn', '_sortOrder', # Signals 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' @@ -66,9 +82,6 @@ class TTkTreeWidget(TTkAbstractScrollView): self._cache = [] self._sortColumn = -1 self._sortOrder = TTkK.AscendingOrder - self._headerColor = kwargs.get('headerColor', TTkCfg.theme.treeHeaderColor) - self._selectedColor = kwargs.get('selectedColor', TTkCfg.theme.treeSelectedColor) - self._lineColor = kwargs.get('lineColor', TTkCfg.theme.treeLineColor) self.setMinimumHeight(1) self.setFocusPolicy(TTkK.ClickFocus) self._rootItem = TTkTreeWidgetItem(expanded=True) @@ -96,7 +109,7 @@ class TTkTreeWidget(TTkAbstractScrollView): def clear(self): # Remove all the widgets for ri in self._rootItem.children(): - ri.setParent(None) + ri.setTreeItemParent(None) if self._rootItem: self._rootItem.dataChanged.disconnect(self._refreshCache) self._rootItem = TTkTreeWidgetItem(expanded=True) @@ -108,7 +121,16 @@ class TTkTreeWidget(TTkAbstractScrollView): def addTopLevelItem(self, item): self._rootItem.addChild(item) - item.setParent(self) + item.setTreeItemParent(self) + self._refreshCache() + self.viewChanged.emit() + self.update() + + def addTopLevelItems(self, items): + self._rootItem.addChildren(items) + self._rootItem.setTreeItemParent(self) + #for item in items: + # item.setTreeItemParent(self) self._refreshCache() self.viewChanged.emit() self.update() @@ -334,6 +356,14 @@ class TTkTreeWidget(TTkAbstractScrollView): self.viewChanged.emit() def paintEvent(self, canvas): + style = self.currentStyle() + + color= style['color'] + lineColor= style['lineColor'] + headerColor= style['headerColor'] + selectedColor= style['selectedColor'] + separatorColor= style['separatorColor'] + x,y = self.getViewOffsets() w,h = self.size() tt = TTkCfg.theme.tree @@ -342,15 +372,15 @@ class TTkTreeWidget(TTkAbstractScrollView): for i,l in enumerate(self._header): hx = 0 if i==0 else self._columnsPos[i-1]+1 hx1 = self._columnsPos[i] - canvas.drawText(pos=(hx-x,0), text=l, width=hx1-hx, color=self._headerColor) + canvas.drawText(pos=(hx-x,0), text=l, width=hx1-hx, color=headerColor) if i == self._sortColumn: s = tt[6] if self._sortOrder == TTkK.AscendingOrder else tt[7] - canvas.drawText(pos=(hx1-x-1,0), text=s, color=self._headerColor) + canvas.drawText(pos=(hx1-x-1,0), text=s, color=headerColor) # Draw header separators for sx in self._columnsPos: - canvas.drawChar(pos=(sx-x,0), char=tt[5], color=self._headerColor) + canvas.drawChar(pos=(sx-x,0), char=tt[5], color=headerColor) for sy in range(1,h): - canvas.drawChar(pos=(sx-x,sy), char=tt[4], color=self._lineColor) + canvas.drawChar(pos=(sx-x,sy), char=tt[4], color=lineColor) # Draw cache for i, c in enumerate(self._cache): @@ -362,6 +392,6 @@ class TTkTreeWidget(TTkAbstractScrollView): text = c.data[il] if item.isSelected(): - canvas.drawText(pos=(lx-x,i-y+1), text=text.completeColor(self._selectedColor), width=lx1-lx, alignment=item.textAlignment(il), color=self._selectedColor) + canvas.drawText(pos=(lx-x,i-y+1), text=text.completeColor(selectedColor), width=lx1-lx, alignment=item.textAlignment(il), color=selectedColor) else: canvas.drawText(pos=(lx-x,i-y+1), text=text, width=lx1-lx, alignment=item.textAlignment(il)) diff --git a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py index 75633874..bfd24d79 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTreeWidgetItem'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString @@ -29,7 +29,6 @@ from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets import TTkWidget from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel - class TTkTreeWidgetItem(TTkAbstractItemModel): __slots__ = ('_parent', '_data', '_widgets', '_height', '_alignment', '_children', '_expanded', '_selected', '_hidden', '_childIndicatorPolicy', '_icon', '_defaultIcon', @@ -76,7 +75,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): widget.sizeChanged.connect(self._widgetSizeChanged) self._height = max(self._height,widget.height()) if self._parentWidget: - widget.setParent(self._parentWidget) + widget.setTreeItemParent(self._parentWidget) if hasattr(widget, 'text'): ret = widget.text() if hasattr(widget,'textChanged'): @@ -130,16 +129,40 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def height(self): return self._height - def setParent(self, parent): + def _clearTreeItemParent(self): + widgets = [] + if self._hasWidgets: + widgets += [w for w in self._widgets if w and w.parentWidget()] + # for widget in widgets: + # if pw := widget.parentWidget(): + # pw.rootLayout().removeWidgets([w for w in self._widgets if w]) + if self._parentWidget: + self._parentWidget.rootLayout().removeWidgets(widgets) + self._parentWidget = None + for c in self._children: + widgets += c._clearTreeItemParent() + return widgets + + def _setTreeItemParent(self, parent): self._parentWidget = parent + widgets = [] if self._hasWidgets: - for widget in [w for w in self._widgets if w]: - if parent: - parent.layout().addWidget(widget) - elif pw := widget.parentWidget(): - pw.rootLayout().removeWidget(widget) + widgets += [w for w in self._widgets if w] + # parent.layout().addWidgets(widgets) for c in self._children: - c.setParent(parent) + widgets += c._setTreeItemParent(parent) + return widgets + + def setTreeItemParent(self, parent): + if parent: + widgets = self._setTreeItemParent(parent) + parent.layout().addWidgets(widgets) + else: + # pw = self._parentWidget + widgets = self._clearTreeItemParent() + # pw.rootLayout().removeWidgets(widgets) + + def hasWidgets(self): return self._hasWidgets @@ -167,7 +190,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._setDefaultIcon() self._sort(children=False) if self._parentWidget: - child.setParent(self._parentWidget) + child.setTreeItemParent(self._parentWidget) child.dataChanged.connect(self.emitDataChanged) def addChild(self, child): @@ -188,7 +211,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return None child = self._children.pop(index) child.dataChanged.disconnect(self.emitDataChanged) - child.setParent(None) + child.setTreeItemParent(None) self.dataChanged.emit() return child @@ -196,7 +219,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): children = self._children for child in children: child.dataChanged.disconnect(self.emitDataChanged) - child.setParent(None) + child.setTreeItemParent(None) self._children = [] self.dataChanged.emit() return children diff --git a/TermTk/TTkWidgets/TTkPickers/__init__.py b/TermTk/TTkWidgets/TTkPickers/__init__.py index 0067baa2..dd03c1d1 100644 --- a/TermTk/TTkWidgets/TTkPickers/__init__.py +++ b/TermTk/TTkWidgets/TTkPickers/__init__.py @@ -1,4 +1,4 @@ -from .colorpicker import TTkColorButtonPicker, TTkColorDialogPicker -from .filepicker import TTkFileDialog, TTkFileDialogPicker, TTkFileButtonPicker -from .textpicker import TTkTextPicker, TTkTextDialogPicker -from .messagebox import TTkMessageBox \ No newline at end of file +from .colorpicker import * +from .filepicker import * +from .textpicker import * +from .messagebox import * \ No newline at end of file diff --git a/TermTk/TTkWidgets/TTkPickers/colorpicker.py b/TermTk/TTkWidgets/TTkPickers/colorpicker.py index 4691006e..a8d44df7 100644 --- a/TermTk/TTkWidgets/TTkPickers/colorpicker.py +++ b/TermTk/TTkWidgets/TTkPickers/colorpicker.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkColorButtonPicker', 'TTkColorDialogPicker'] + import re from TermTk.TTkCore.constant import TTkK @@ -180,16 +180,19 @@ class _TTkColorButton(TTkButton): self._color = kwargs.get('color', TTkColor.RST ) self._custom = kwargs.get('custom', False) self.clicked.connect(self._clicked) - self._textColorFocus = self._textColor + self.setColor(self._color) @pyTTkSlot(TTkColor) def setColor(self, color): - self._textColor = color - self._textColorFocus = color - self.update() + style = self.style() + self._color = color + for t in style: + if 'color' in style[t]: + style[t]['color'] = color + self.setStyle(style) def color(self): - return self._textColor + return self._color def isCustom(self): return self._custom @@ -198,7 +201,7 @@ class _TTkColorButton(TTkButton): def _clicked(self): if self._custom: _TTkColorButton.lastClicked = self - self.colorClicked.emit(self._textColor) + self.colorClicked.emit(self._color) class TTkColorDialogPicker(TTkWindow): ''' Color Picker Layout sizes: @@ -232,6 +235,19 @@ class TTkColorDialogPicker(TTkWindow): │└──────────────────────┘└──────────────────────┘│ └────────────────────────────────────────────────┘ ''' + + classStyle = { + 'default': {'color': TTkColor.RST, + 'borderColor': TTkColor.RST, + 'titleColor': TTkColor.fg("#dddddd")+TTkColor.bg("#222222")}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888'), + 'titleColor': TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff55"), + 'titleColor': TTkColor.fg("#ffffdd")+TTkColor.bg("#222222")}, + } + customButtons = None __slots__ = ( '_color', @@ -419,10 +435,10 @@ class TTkColorDialogPicker(TTkWindow): def paintEvent(self, canvas): TTkWindow.paintEvent(self, canvas) - if self.hasFocus(): - color = TTkCfg.theme.windowBorderColorFocus - else: - color = TTkCfg.theme.windowBorderColor + style = self.currentStyle() + color = style['borderColor'] + titleColor = style['titleColor'] + canvas.drawGrid( pos=(0,2),size=(26,self._height-2), hlines=(10,15), vlines=(), @@ -431,9 +447,9 @@ class TTkColorDialogPicker(TTkWindow): canvas.drawChar(pos=(0,2), char=gg[0x08], color=color) canvas.drawChar(pos=(25,2), char=gg[0x02], color=color) canvas.drawChar(pos=(25,self._height-1), char=gg[0x0E], color=color) - canvas.drawBoxTitle(pos=(0,2) , size=(26,0), text=TTkString(" Basic colors "), align=TTkK.CENTER_ALIGN, color=color, colorText=TTkCfg.theme.frameTitleColor) - canvas.drawBoxTitle(pos=(0,12), size=(26,0), text=TTkString(" Custom colors "), align=TTkK.CENTER_ALIGN, color=color, colorText=TTkCfg.theme.frameTitleColor) - canvas.drawBoxTitle(pos=(0,17), size=(26,0), text=TTkString(" Conrols "), align=TTkK.CENTER_ALIGN, color=color, colorText=TTkCfg.theme.frameTitleColor) + canvas.drawBoxTitle(pos=(0,2) , size=(26,0), text=TTkString(" Basic colors "), align=TTkK.CENTER_ALIGN, color=color, colorText=titleColor) + canvas.drawBoxTitle(pos=(0,12), size=(26,0), text=TTkString(" Custom colors "), align=TTkK.CENTER_ALIGN, color=color, colorText=titleColor) + canvas.drawBoxTitle(pos=(0,17), size=(26,0), text=TTkString(" Conrols "), align=TTkK.CENTER_ALIGN, color=color, colorText=titleColor) class TTkColorButtonPicker(_TTkColorButton): __slots__ = ('_type', 'colorSelected') @@ -443,8 +459,8 @@ class TTkColorButtonPicker(_TTkColorButton): _TTkColorButton.__init__(self, *args, **kwargs) self._custom = False self.clicked.connect(self._colorClicked) - self._type = self._textColor.colorType() - hexColor = self._textColor.getHex(self._type) + self._type = self.color().colorType() + hexColor = self.color().getHex(self._type) self.setColor(TTkColor.bg(hexColor)) @pyTTkSlot() @@ -454,3 +470,13 @@ class TTkColorButtonPicker(_TTkColorButton): colorPicker.colorSelected.connect(self.colorSelected.emit) TTkHelper.overlay(self, colorPicker, -1,-1, True) + def setFocus(self): + lastFocus = TTkHelper.getFocus() + return super().setFocus() + + def clearFocus(self): + lastFocus = TTkHelper.getFocus() + return super().clearFocus() + + def paintEvent(self, canvas): + return super().paintEvent(canvas) diff --git a/TermTk/TTkWidgets/TTkPickers/dateTimePicker.py b/TermTk/TTkWidgets/TTkPickers/dateTimePicker.py index f6922bfc..613023f1 100644 --- a/TermTk/TTkWidgets/TTkPickers/dateTimePicker.py +++ b/TermTk/TTkWidgets/TTkPickers/dateTimePicker.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkWidgets/TTkPickers/filepicker.py b/TermTk/TTkWidgets/TTkPickers/filepicker.py index e2158f27..723240e9 100644 --- a/TermTk/TTkWidgets/TTkPickers/filepicker.py +++ b/TermTk/TTkWidgets/TTkPickers/filepicker.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkFileDialog', 'TTkFileDialogPicker', 'TTkFileButtonPicker'] + import os import re @@ -43,7 +43,6 @@ from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkWidgets.TTkModelView.filetree import TTkFileTree from TermTk.TTkWidgets.TTkModelView.filetreewidgetitem import TTkFileTreeWidgetItem - ''' :: diff --git a/TermTk/TTkWidgets/TTkPickers/messagebox.py b/TermTk/TTkWidgets/TTkPickers/messagebox.py index 7c547a72..2e50da9b 100644 --- a/TermTk/TTkWidgets/TTkPickers/messagebox.py +++ b/TermTk/TTkWidgets/TTkPickers/messagebox.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkMessageBox'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.signal import pyTTkSignal,pyTTkSlot from TermTk.TTkCore.color import TTkColor diff --git a/TermTk/TTkWidgets/TTkPickers/textpicker.py b/TermTk/TTkWidgets/TTkPickers/textpicker.py index 94a8117c..691b157f 100644 --- a/TermTk/TTkWidgets/TTkPickers/textpicker.py +++ b/TermTk/TTkWidgets/TTkPickers/textpicker.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextPicker', 'TTkTextDialogPicker'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg @@ -35,6 +37,7 @@ from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.resizableframe import TTkResizableFrame from TermTk.TTkWidgets.texedit import TTkTextEditView, TTkTextEdit from TermTk.TTkWidgets.splitter import TTkSplitter @@ -165,8 +168,7 @@ class TTkTextDialogPicker(TTkWindow): def _showEmojiPicker(): ep = _emojiPicker(size=(40,10)) def _addEmoji(e): - self._textEdit.textCursor().insertText(e) - self._textEdit.textCursor().movePosition(TTkTextCursor.Right) + self._textEdit.textCursor().insertText(e, moveCursor=True) ep.emojiClicked.connect(_addEmoji) TTkHelper.overlay(btn_emoji, ep, 0, 0) @@ -255,7 +257,7 @@ class TTkTextDialogPicker(TTkWindow): return super().resize(w, th+t+b+4) -class TTkTextPicker(TTkWidget): +class TTkTextPicker(TTkContainer): '''TTkTextPicker .. note:: This is an early unstable prototype Do not use it unless you know what you are doing diff --git a/TermTk/TTkWidgets/__init__.py b/TermTk/TTkWidgets/__init__.py index 5cb7a80d..6a15e830 100644 --- a/TermTk/TTkWidgets/__init__.py +++ b/TermTk/TTkWidgets/__init__.py @@ -1,30 +1,34 @@ -from .widget import TTkWidget -from .frame import TTkFrame -from .resizableframe import TTkResizableFrame -from .button import TTkButton +# Base widhgets +from .widget import * +from .container import * -from .about import TTkAbout -from .checkbox import TTkCheckbox -from .combobox import TTkComboBox -from .Fancy import * -from .graph import TTkGraph -from .image import TTkImage -from .label import TTkLabel -from .lineedit import TTkLineEdit -from .list_ import TTkList -from .listwidget import TTkListWidget -from .menubar import TTkMenuBarLayout -from .menu import TTkMenu, TTkMenuButton -from .progressbar import TTkProgressBar, TTkLookAndFeelPBar -from .radiobutton import TTkRadioButton -from .scrollarea import TTkScrollArea -from .scrollbar import TTkScrollBar -from .spacer import TTkSpacer -from .spinbox import TTkSpinBox -from .splitter import TTkSplitter -from .tabwidget import TTkTabWidget, TTkTabButton, TTkTabBar -from .kodetab import TTkKodeTab -from .texedit import TTkTextEdit, TTkTextEditView +# Containerised widgets +from .frame import * +from .resizableframe import * +from .window import * +from .splitter import * + +# Everything else +from .about import * +from .button import * +from .checkbox import * +from .combobox import * +from .graph import * +from .image import * +from .label import * +from .lineedit import * +from .list_ import * +from .listwidget import * +from .menubar import * +from .menu import * +from .radiobutton import * +from .scrollarea import * +from .scrollbar import * +from .spacer import * +from .spinbox import * +from .tabwidget import * +from .kodetab import * +from .texedit import * from .TTkModelView import * from .TTkPickers import * -from .window import TTkWindow +from .Fancy import * diff --git a/TermTk/TTkWidgets/about.py b/TermTk/TTkWidgets/about.py index 5512d6eb..3bf782d2 100644 --- a/TermTk/TTkWidgets/about.py +++ b/TermTk/TTkWidgets/about.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkAbout'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.window import TTkWindow diff --git a/TermTk/TTkWidgets/button.py b/TermTk/TTkWidgets/button.py index d63b01a8..80b7bfe4 100644 --- a/TermTk/TTkWidgets/button.py +++ b/TermTk/TTkWidgets/button.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,10 +20,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkButton'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal +from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.widget import TTkWidget class TTkButton(TTkWidget): @@ -63,11 +64,6 @@ class TTkButton(TTkWidget): :param bool checkable: define if the button is checkable, defaults to "False" :type checkable: bool, optional - :param TTkColor color: the color of the border of the button, defaults to :class:`~TermTk.TTkTheme.theme.TTkTheme.buttonTextColor` - :type color: :class:`~TermTk.TTkCore.color.TTkColor`, optional - :param TTkColor borderColor: the color of the border of the button, defaults to :class:`~TermTk.TTkTheme.theme.TTkTheme.buttonBorderColor` - :type borderColor: :class:`~TermTk.TTkCore.color.TTkColor`, optional - +-----------------------------------------------------------------------------------------------+ | `Signals `_ | +-----------------------------------------------------------------------------------------------+ @@ -87,13 +83,33 @@ class TTkButton(TTkWidget): ''' + classStyle = { + 'default': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044"), + 'borderColor': TTkColor.RST, + 'grid':1}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888'), + 'grid':0}, + 'hover': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000050")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#FFFFCC")+TTkColor.BOLD, + 'grid':1}, + 'checked': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#004488"), + 'borderColor': TTkColor.fg("#FFFFFF"), + 'grid':0}, + 'unchecked': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044"), + 'borderColor': TTkColor.RST, + 'grid':3}, + 'clicked': {'color': TTkColor.fg("#FFFFDD")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#DDDDDD")+TTkColor.BOLD, + 'grid':0}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD, + 'grid':1}, + } + __slots__ = ( - '_text', '_border', '_pressed', '_keyPressed', + '_text', '_border', '_checkable', '_checked', - '_borderColor', '_textColor', - '_borderColorClicked', '_textColorClicked', - '_borderColorFocus', '_textColorFocus', - '_borderColorDisabled','_textColorDisabled', # Signals 'clicked', 'toggled' ) @@ -106,24 +122,14 @@ class TTkButton(TTkWidget): else: self.setDefaultSize(kwargs, textWidth+2, len(self._text)) - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) # Define Signals self.clicked = pyTTkSignal() self.toggled = pyTTkSignal(bool) self._checked = kwargs.get('checked', False ) self._checkable = kwargs.get('checkable', False ) - self._borderColor = kwargs.get('borderColor', TTkCfg.theme.buttonBorderColor ) - self._textColor = kwargs.get('color', TTkCfg.theme.buttonTextColor ) - self._borderColorClicked = TTkCfg.theme.buttonBorderColorClicked - self._textColorClicked = TTkCfg.theme.buttonTextColorClicked - self._borderColorFocus = TTkCfg.theme.buttonBorderColorFocus - self._textColorFocus = TTkCfg.theme.buttonTextColorFocus - self._borderColorDisabled= TTkCfg.theme.buttonBorderColorDisabled - self._textColorDisabled = TTkCfg.theme.buttonTextColorDisabled - - self._pressed = False - self._keyPressed = False + if self._border: if 'minSize' not in kwargs: if 'minWidth' not in kwargs: @@ -180,20 +186,6 @@ class TTkButton(TTkWidget): self.toggled.emit(self._checked) self.update() - def color(self): - return self._textColor - - def setColor(self, color): - self._textColor = color - self.update() - - def borderColor(self): - return self._borderColor - - def setBorderColor(self, color): - self._borderColor = color - self.update() - def text(self): ''' This property holds the text shown on the button @@ -219,13 +211,11 @@ class TTkButton(TTkWidget): def mousePressEvent(self, evt): # TTkLog.debug(f"{self._text} Test Mouse {evt}") - self._pressed = True self.update() return True def mouseReleaseEvent(self, evt): # TTkLog.debug(f"{self._text} Test Mouse {evt}") - self._pressed = False if self._checkable: self._checked = not self._checked self.toggled.emit(self._checked) @@ -236,55 +226,23 @@ class TTkButton(TTkWidget): def keyEvent(self, evt): if ( evt.type == TTkK.Character and evt.key==" " ) or \ ( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ): - self._keyPressed = True - self._pressed = True self.update() self.clicked.emit() return True return False - def enterEvent(self, evt) -> bool: - self.update() - - def leaveEvent(self, evt) -> bool: - self.update() - - def mouseMoveEvent(self, evt) -> bool: - self.update() - return super().mouseMoveEvent(evt) - def paintEvent(self, canvas): - if not self.isEnabled(): - borderColor = self._borderColorDisabled - textColor = self._textColorDisabled - grid = TTkCfg.theme.buttonBoxGridDisabled - elif self._pressed: - borderColor = self._borderColorClicked - textColor = self._textColorClicked - grid = TTkCfg.theme.buttonBoxGridClicked - else: - if self._checkable: - if self._checked: - grid = TTkCfg.theme.buttonBoxGridChecked - borderColor = TTkCfg.theme.buttonBorderColorChecked - textColor = TTkCfg.theme.buttonTextColorChecked - else: - grid = TTkCfg.theme.buttonBoxGridUnchecked - borderColor = TTkCfg.theme.buttonBorderColorUnchecked - textColor = TTkCfg.theme.buttonTextColorUnchecked - if self.hasFocus(): - borderColor = self._borderColorFocus + if self.isEnabled() and self._checkable: + if self._checked: + style = self.style()['checked'] else: - grid = TTkCfg.theme.buttonBoxGrid - if self.hasFocus(): - textColor = self._textColorFocus - borderColor = self._borderColorFocus - elif self.isEntered(): - textColor = TTkCfg.theme.buttonTextColorHover - borderColor = TTkCfg.theme.buttonBorderColorHover - else: - textColor = self._textColor - borderColor = self._borderColor + style = self.style()['unchecked'] + else: + style = self.currentStyle() + + borderColor = style['borderColor'] + textColor = style['color'] + grid = style['grid'] w,h = self.size() diff --git a/TermTk/TTkWidgets/checkbox.py b/TermTk/TTkWidgets/checkbox.py index 2a9579f9..fb25dd55 100644 --- a/TermTk/TTkWidgets/checkbox.py +++ b/TermTk/TTkWidgets/checkbox.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,7 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkCheckbox'] + +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkWidgets.widget import * @@ -80,6 +82,19 @@ class TTkCheckbox(TTkWidget): :type checked: bool ''' + + classStyle = { + 'default': {'color': TTkColor.RST, + 'borderColor':TTkColor.RST, + 'cbContentColor': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888'), + 'cbContentColor': TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD, + 'cbContentColor': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD}, + } + __slots__ = ( '_checkStatus', '_text', '_tristate', # Signals @@ -175,18 +190,12 @@ class TTkCheckbox(TTkWidget): self.update() def paintEvent(self, canvas): - if not self.isEnabled(): - textColor = TTkCfg.theme.checkboxTextColor - borderColor = TTkCfg.theme.textColorDisabled - xColor = TTkCfg.theme.textColorDisabled - elif self.hasFocus(): - borderColor = TTkCfg.theme.checkboxBorderColorFocus - textColor = TTkCfg.theme.checkboxTextColorFocus - xColor = TTkCfg.theme.checkboxContentColorFocus - else: - borderColor = TTkCfg.theme.checkboxBorderColor - textColor = TTkCfg.theme.checkboxTextColor - xColor = TTkCfg.theme.checkboxContentColor + style = self.currentStyle() + + borderColor = style['borderColor'] + textColor = style['color'] + xColor = style['cbContentColor'] + canvas.drawText(pos=(0,0), color=borderColor ,text="[ ]") canvas.drawText(pos=(3,0), color=textColor ,text=self._text) text = { diff --git a/TermTk/TTkWidgets/combobox.py b/TermTk/TTkWidgets/combobox.py index ce214f07..b480d35d 100644 --- a/TermTk/TTkWidgets/combobox.py +++ b/TermTk/TTkWidgets/combobox.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,19 +20,23 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkComboBox'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.color import TTkColor from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.list_ import TTkList from TermTk.TTkWidgets.lineedit import TTkLineEdit from TermTk.TTkWidgets.resizableframe import TTkResizableFrame -class TTkComboBox(TTkWidget): +class TTkComboBox(TTkContainer): ''' TTkComboBox: Editable = False @@ -59,6 +61,16 @@ class TTkComboBox(TTkWidget): :param editable: This property holds whether the combo box can be edited by the user, defaults to False :type editable: bool, optional ''' + + classStyle = { + 'default': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#222222"), + 'borderColor':TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#ffff88")+TTkColor.bg("#222222"), + 'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD}, + } + __slots__ = ('_list', '_id', '_lineEdit', '_listw', '_editable', '_insertPolicy', '_textAlign', '_popupFrame', #signals 'currentIndexChanged', 'currentTextChanged', 'editTextChanged') @@ -67,7 +79,7 @@ class TTkComboBox(TTkWidget): self.currentIndexChanged = pyTTkSignal(int) self.currentTextChanged = pyTTkSignal(str) self.editTextChanged = pyTTkSignal(str) - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) # self.checked = pyTTkSignal() self._lineEdit = TTkLineEdit(parent=self) self._list = kwargs.get('list', [] ) @@ -153,15 +165,11 @@ class TTkComboBox(TTkWidget): self._lineEdit.setGeometry(1,0,w-4,h) def paintEvent(self, canvas): - if not self.isEnabled(): - borderColor = TTkCfg.theme.comboboxBorderColorDisabled - color = TTkCfg.theme.comboboxContentColorDisabled - elif self.hasFocus(): - borderColor = TTkCfg.theme.comboboxBorderColorFocus - color = TTkCfg.theme.comboboxContentColorFocus - else: - borderColor = TTkCfg.theme.comboboxBorderColor - color = TTkCfg.theme.comboboxContentColor + style = self.currentStyle() + + color = style['color'] + borderColor = style['borderColor'] + if self._id == -1: text = "- select -" else: diff --git a/TermTk/TTkWidgets/container.py b/TermTk/TTkWidgets/container.py new file mode 100644 index 00000000..f19e7443 --- /dev/null +++ b/TermTk/TTkWidgets/container.py @@ -0,0 +1,413 @@ +# MIT License +# +# Copyright (c) 2023 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. + +__all__ = ['TTkContainer'] + +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkLayouts.layout import TTkLayout +from TermTk.TTkWidgets.widget import TTkWidget + +class TTkContainer(TTkWidget): + ''' TTkContainer Layout sizes: + + :: + + Terminal area (i.e. XTerm) = TTk + ┌─────────────────────────────────────────┐ + │ │ + │ TTkContainer width │ + │ (x,y)┌─────────────────────────┐ │ + │ │ padt (Top Padding) │ │ + │ │ ┌───────────────┐ │ height │ + │ │padl│ Layout/child │padr│ │ + │ │ └───────────────┘ │ │ + │ │ padb (Bottom Pad.) │ │ + │ └─────────────────────────┘ │ + └─────────────────────────────────────────┘ + + :param bool forwardStyle: any change of style will reflect the children, defaults to False + :type forwardStyle: bool + + :param int padding: the padding (top, bottom, left, right) of the widget, defaults to 0 + :param int paddingTop: the Top padding, override Top padding if already defined, optional, default=padding + :param int paddingBottom: the Bottom padding, override Bottom padding if already defined, optional, default=padding + :param int paddingLeft: the Left padding, override Left padding if already defined, optional, default=padding + :param int paddingRight: the Right padding, override Right padding if already defined, optional, default=padding + + :param layout: the layout of this widget, optional, defaults to :class:`~TermTk.TTkLayouts.layout.TTkLayout` + :type layout: :mod:`TermTk.TTkLayouts` + ''' + + __slots__ = ( + '_padt', '_padb', '_padl', '_padr', + '_forwardStyle', + '_layout') + + def __init__(self, *, padding=(0,0,0,0), forwardStyle=False,**kwargs): + + self._forwardStyle = forwardStyle + padding = kwargs.get('padding', 0 ) + self._padt = kwargs.get('paddingTop', padding ) + self._padb = kwargs.get('paddingBottom', padding ) + self._padl = kwargs.get('paddingLeft', padding ) + self._padr = kwargs.get('paddingRight', padding ) + + self._layout = TTkLayout() # root layout + self._layout.addItem(kwargs.get('layout',TTkLayout())) # main layout + + super().__init__(**kwargs) + + self._layout.setParent(self) + + def addWidget(self, widget): + ''' + .. warning:: + Method Deprecated, + + use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.addWidget` + + i.e. + + .. code:: python + + parentWidget.layout().addWidget(childWidget) + ''' + TTkLog.error(".addWidget(...) is deprecated, use .layout().addWidget(...)") + if self.layout(): self.layout().addWidget(widget) + + def removeWidget(self, widget): + ''' + .. warning:: + Method Deprecated, + + use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.removeWidget` + + i.e. + + .. code:: python + + parentWidget.layout().removeWidget(childWidget) + ''' + TTkLog.error(".removeWidget(...) is deprecated, use .layout().removeWidget(...)") + if self.layout(): self.layout().removeWidget(widget) + + # def forwardStyleTo(self, widget:TTkWidget): + # widget._currentStyle |= self._currentStyle + # widget.update() + + def _processForwardStyle(self): + if not self._forwardStyle: return + def _getChildren(): + for w in self.rootLayout().iterWidgets(onlyVisible=True, recurse=False): + yield w + for w in self.layout().iterWidgets(onlyVisible=True, recurse=False): + yield w + + for w in _getChildren(): + self.setCurrentStyle(w._currentStyle | self._currentStyle) + if issubclass(type(w),TTkContainer): + w._processForwardStyle() + + def setCurrentStyle(self, *args, **kwargs): + super().setCurrentStyle(*args, **kwargs) + self._processForwardStyle() + + @staticmethod + def _paintChildCanvas(canvas, item, geometry, offset): + ''' .. caution:: Don't touch this! ''' + lx,ly,lw,lh = geometry + ox, oy = offset + if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): + child = item.widget() + cx,cy,cw,ch = child.geometry() + canvas.paintCanvas( + child.getCanvas(), + (cx+ox, cy+oy, cw, ch), # geometry + ( 0, 0, cw, ch), # slice + ( lx, ly, lw, lh)) # bound + else: + for child in item.zSortedItems: + # The Parent Layout Geometry (lx,ly,lw,lh) include the padding of the layout + igx, igy, igw, igh = item.geometry() + iox, ioy = item.offset() + # Moved Layout to the new geometry (ix,iy,iw,ih) + ix = igx+ox # + iox + iy = igy+oy # + ioy + iw = igw # -iox + ih = igh # -ioy + # return if Child outside the bound + if ix+iw < lx and ix > lx+lw and iy+ih < ly and iy > ly+lh: continue + # Crop the Layout based on the Parent Layout Geometry + bx = max(ix,lx) + by = max(iy,ly) + bw = min(ix+iw,lx+lw)-bx + bh = min(iy+ih,ly+lh)-by + TTkContainer._paintChildCanvas(canvas, child, (bx,by,bw,bh), (ix+iox,iy+ioy)) + + def paintChildCanvas(self): + ''' .. caution:: Don't touch this! ''' + TTkContainer._paintChildCanvas(self._canvas, self.rootLayout(), self.rootLayout().geometry(), self.rootLayout().offset()) + + def getPadding(self) -> (int, int, int, int): + ''' Retrieve the widget padding sizes + + :return: list[top, bottom, left, right]: the 4 padding sizes + ''' + return self._padt, self._padb, self._padl, self._padr + + def setPadding(self, top: int, bottom: int, left: int, right: int): + ''' Set the padding of the widget + + :param int top: top padding + :param int bottom: bottom padding + :param int left: left padding + :param int right: right padding + ''' + if self._padt == top and self._padb == bottom and \ + self._padl == left and self._padr == right: return + self._padt = top + self._padb = bottom + self._padl = left + self._padr = right + self.update(repaint=True, updateLayout=True) + + @staticmethod + def _mouseEventLayoutHandle(evt, layout): + ''' .. caution:: Don't touch this! ''' + x, y = evt.x, evt.y + lx,ly,lw,lh =layout.geometry() + lox, loy = layout.offset() + lx,ly,lw,lh = lx+lox, ly+loy, lw-lox, lh-loy + # opt of bounds + if x=lx+lw or y=lh+ly: + return False + x-=lx + y-=ly + for item in reversed(layout.zSortedItems): + # for item in layout.zSortedItems: + if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): + widget = item.widget() + if not widget._visible: continue + wx,wy,ww,wh = widget.geometry() + # Skip the mouse event if outside this widget + if not (wx <= x < wx+ww and wy <= y < wy+wh): continue + wevt = evt.clone(pos=(x-wx, y-wy)) + if widget.mouseEvent(wevt): + return True + elif item.layoutItemType() == TTkK.LayoutItem: + levt = evt.clone(pos=(x, y)) + if TTkContainer._mouseEventLayoutHandle(levt, item): + return True + return False + + _mouseOver = None + _mouseOverTmp = None + _mouseOverProcessed = False + def mouseEvent(self, evt): + ''' .. caution:: Don't touch this! ''' + if not self._enabled: return False + + # Saving self in this global variable + # So that after the "_mouseEventLayoutHandle" + # this tmp value will hold the last widget below the mouse + TTkWidget._mouseOverTmp = self + + # Mouse Drag has priority because it + # should be handled by the focused widget and + # not pushed to the unfocused childs + # unless there is a Drag and Drop event ongoing + if evt.evt == TTkK.Drag and not TTkHelper.isDnD(): + if self.mouseDragEvent(evt): + return True + + if self.rootLayout() is not None: + if TTkContainer._mouseEventLayoutHandle(evt, self.rootLayout()): + return True + + # If there is an overlay and it is modal, + # return False if this widget is not part of any + # of the widgets above the modal + if not TTkHelper.checkModalOverlay(self): + return False + + # Handle Drag and Drop Events + if TTkHelper.isDnD(): + ret = False + if evt.evt == TTkK.Drag: + dndw = TTkHelper.dndWidget() + if dndw == self: + if self.dragMoveEvent(TTkHelper.dndGetDrag().getDragMoveEvent(evt)): + return True + else: + if self.dragEnterEvent(TTkHelper.dndGetDrag().getDragEnterEvent(evt)): + if dndw: + ret = dndw.dragLeaveEvent(TTkHelper.dndGetDrag().getDragLeaveEvent(evt)) + TTkHelper.dndEnter(self) + return True + if evt.evt == TTkK.Release: + if self.dropEvent(TTkHelper.dndGetDrag().getDropEvent(evt)): + return True + return ret + + # handle Enter/Leave Events + # _mouseOverTmp hold the top widget under the mouse + # if different than self it means that it is a child + if evt.evt == TTkK.Move: + if not TTkWidget._mouseOverProcessed: + if TTkWidget._mouseOver != TTkWidget._mouseOverTmp == self: + if TTkWidget._mouseOver: + # TTkLog.debug(f"Leave: {TTkWidget._mouseOver._name}") + TTkWidget._mouseOver.leaveEvent(evt) + TTkWidget._mouseOver = self + # TTkLog.debug(f"Enter: {TTkWidget._mouseOver._name}") + TTkHelper.toolTipClose() + if self._toolTip and self._toolTip != '': + TTkHelper.toolTipTrigger(self._toolTip) + # TTkHelper.triggerToolTip(self._name) + TTkWidget._mouseOver.enterEvent(evt) + TTkWidget._mouseOverProcessed = True + if self.mouseMoveEvent(evt): + return True + else: + TTkHelper.toolTipClose() + + if evt.evt == TTkK.Release: + self._pendingMouseRelease = False + self._processStyleEvent(TTkWidget._S_NONE) + if self.mouseReleaseEvent(evt): + return True + + if evt.evt == TTkK.Press: + # in case of parent focus, check the parent that can accept the focus + w = self + while w._parent and (w.focusPolicy() & TTkK.ParentFocus) == TTkK.ParentFocus: + w = w._parent + if w.focusPolicy() & TTkK.ClickFocus == TTkK.ClickFocus: + w.setFocus() + w.raiseWidget() + self._processStyleEvent(TTkWidget._S_PRESSED) + if evt.tap == 2 and self.mouseDoubleClickEvent(evt): + #self._pendingMouseRelease = True + return True + if evt.tap > 1 and self.mouseTapEvent(evt): + return True + if evt.tap == 1 and self.mousePressEvent(evt): + # TTkLog.debug(f"Click {self._name}") + self._pendingMouseRelease = True + return True + + if evt.key == TTkK.Wheel: + if self.wheelEvent(evt): + return True + + return False + + def setLayout(self, layout): + self._layout.replaceItem(layout, 0) + #self.layout().setParent(self) + self.update(repaint=True, updateLayout=True) + + def layout(self): + ''' Get the layout + + :return: The layout used + :rtype: :class:`TTkLayout` or derived + ''' + return self._layout.itemAt(0) + + def rootLayout(self): return self._layout + + def maximumHeight(self): + wMaxH = self._maxh + if self.layout() is not None: + lMaxH = self.layout().maximumHeight() + self._padt + self._padb + if lMaxH < wMaxH: + return lMaxH + return wMaxH + def maximumWidth(self): + wMaxW = self._maxw + if self.layout() is not None: + lMaxW = self.layout().maximumWidth() + self._padl + self._padr + if lMaxW < wMaxW: + return lMaxW + return wMaxW + + def minimumSize(self): + return self.minimumWidth(), self.minimumHeight() + def minDimension(self, orientation) -> int: + if orientation == TTkK.HORIZONTAL: + return self.minimumWidth() + else: + return self.minimumHeight() + def minimumHeight(self): + wMinH = self._minh + if self.layout() is not None: + lMinH = self.layout().minimumHeight() + self._padt + self._padb + if lMinH > wMinH: + return lMinH + return wMinH + def minimumWidth(self): + wMinW = self._minw + if self.layout() is not None: + lMinW = self.layout().minimumWidth() + self._padl + self._padr + if lMinW > wMinW: + return lMinW + return wMinW + + @pyTTkSlot() + def show(self): + '''show''' + if self._visible: return + self._visible = True + self._canvas.show() + self.update(updateLayout=True, updateParent=True) + for w in self.rootLayout().iterWidgets(onlyVisible=True): + w.update() + + @pyTTkSlot() + def hide(self): + '''hide''' + if not self._visible: return + self._visible = False + self._canvas.hide() + self.update(repaint=False, updateParent=True) + + def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False): + super().update(repaint=repaint, updateLayout=updateLayout, updateParent=updateParent) + if updateLayout and self.rootLayout() is not None: + self.rootLayout().setGeometry(0,0,self._width,self._height) + self.layout().setGeometry( + self._padl, self._padt, + self._width - self._padl - self._padr, + self._height - self._padt - self._padb) + self.rootLayout().update() + + def getWidgetByName(self, name: str): + if name == self._name: + return self + for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): + if w._name == name: + return w + return None diff --git a/TermTk/TTkWidgets/frame.py b/TermTk/TTkWidgets/frame.py index 422a04d8..4d5ba29e 100644 --- a/TermTk/TTkWidgets/frame.py +++ b/TermTk/TTkWidgets/frame.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,11 +20,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkCore.cfg import * +__all__ = ['TTkFrame'] + +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkWidgets.container import TTkContainer -class TTkFrame(TTkWidget): +class TTkFrame(TTkContainer): ''' :: @@ -49,12 +50,18 @@ class TTkFrame(TTkWidget): :type border: bool, optional ''' + + classStyle = { + 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), + 'borderColor': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888')} + } + __slots__ = ( - '_border','_title', '_titleColor', '_titleAlign','_borderColor', + '_border','_title', '_titleAlign', '_menubarTop', '_menubarTopPosition', '_menubarBottom', '_menubarBottomPosition') def __init__(self, *args, **kwargs): - self._borderColor = kwargs.get('borderColor', TTkCfg.theme.frameBorderColor ) - self._titleColor = kwargs.get('titleColor', TTkCfg.theme.frameTitleColor ) self._titleAlign = kwargs.get('titleAlign' , TTkK.CENTER_ALIGN ) self._title = TTkString(kwargs.get('title' , '' )) self._border = kwargs.get('border', True ) @@ -62,13 +69,29 @@ class TTkFrame(TTkWidget): self._menubarBottomPosition = 0 self._menubarTop = None self._menubarBottom = None - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.setBorder(self._border) def newMenubarTop(self): + '''newMenubarTop + + .. warning:: + Method Deprecated, + + use :class:`~TermTk.TTkWidgets.frame.setMenuBar` instead + + i.e. + + .. code:: python + + menuBar = TTkMenuBarLayout() + frame.setMenuBar(menuBar) + menuBar.addMenu("File") + + ''' if not self._menubarTop: from TermTk.TTkWidgets.menubar import TTkMenuBarLayout - self._menubarTop = TTkMenuBarLayout(borderColor=self._borderColor) + self._menubarTop = TTkMenuBarLayout() self.rootLayout().addItem(self._menubarTop) self._menubarTop.setGeometry(1,self._menubarTopPosition,self.width()-2,1) if not self._border and self._padt == 0: @@ -146,26 +169,23 @@ class TTkFrame(TTkWidget): '''border''' return self._border - def borderColor(self): - return self._borderColor - - def setBorderColor(self, color): - self._borderColor = color - self.update() - def paintEvent(self, canvas): + style = self.currentStyle() + color = style['color'] + borderColor = style['borderColor'] + if self._border: - canvas.drawBox(pos=(0,0),size=(self._width,self._height), color=self._borderColor) + canvas.drawBox(pos=(0,0),size=(self._width,self._height), color=borderColor) if len(self._title) != 0: canvas.drawBoxTitle( pos=(0,0), size=(self._width,self._height), text=self._title, align=self._titleAlign, - color=self._borderColor, - colorText=self._titleColor) + color=borderColor, + colorText=color) else: if self._menubarTop: - canvas.drawMenuBarBg(pos=(0,0),size=self.width(),color=self._borderColor) + canvas.drawMenuBarBg(pos=(0,0),size=self.width(),color=borderColor) if self._menubarBottom: - canvas.drawMenuBarBg(pos=(0,self.height()-1),size=self.width(),color=self._borderColor) + canvas.drawMenuBarBg(pos=(0,self.height()-1),size=self.width(),color=borderColor) diff --git a/TermTk/TTkWidgets/graph.py b/TermTk/TTkWidgets/graph.py index 69c6ff9d..e6bc775d 100644 --- a/TermTk/TTkWidgets/graph.py +++ b/TermTk/TTkWidgets/graph.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkGraph'] + # inspired by: # https://grafana.com/docs/grafana/latest/panels/visualizations/graph-panel/ # And of course: diff --git a/TermTk/TTkWidgets/image.py b/TermTk/TTkWidgets/image.py index bec06ac5..6dc41f44 100644 --- a/TermTk/TTkWidgets/image.py +++ b/TermTk/TTkWidgets/image.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkImage'] + from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.widget import TTkWidget diff --git a/TermTk/TTkWidgets/kodetab.py b/TermTk/TTkWidgets/kodetab.py index 2026962d..2cc2f9c9 100644 --- a/TermTk/TTkWidgets/kodetab.py +++ b/TermTk/TTkWidgets/kodetab.py @@ -20,9 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkKodeTab'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.color import TTkColor, TTkColorGradient from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget @@ -58,6 +61,23 @@ class _TTkKodeTab(TTkTabWidget): self.tabCloseRequested.connect(lambda i:self._baseWidget.tabCloseRequested.emit(self, i)) self.tabCloseRequested.connect( self._kodeTabClosed) + def _hasMenu(self): + return True if (self._topLeftLayout or self._topRightLayout) else False + + def _importMenu(self, kt): + kt._tabBarTopLayout.removeItem(ll := kt._topLeftLayout) + kt._tabBarTopLayout.removeItem(rl := kt._topRightLayout) + kt._topLeftLayout = None + kt._topRightLayout = None + self._topLeftLayout = ll + self._topRightLayout = rl + if ll: + self._tabBarTopLayout.addItem(ll, 1 if self.border() else 0,0) + if rl: + self._tabBarTopLayout.addItem(rl,1 if self.border() else 0,2) + self._tabBarTopLayout.update() + kt._tabBarTopLayout.update() + def dragEnterEvent(self, evt) -> bool: TTkLog.debug(f"leave") return True @@ -106,6 +126,7 @@ class _TTkKodeTab(TTkTabWidget): widget = tw._tabWidgets[index] def _processDrop(index, orientation, offset): + fwold = self._baseWidget._getFirstWidget() tw.removeTab(index) splitter = self.parentWidget() index = splitter.indexOf(self) @@ -115,6 +136,8 @@ class _TTkKodeTab(TTkTabWidget): index=offset splitter.insertWidget(index+offset, kt:=_TTkKodeTab(baseWidget=self._baseWidget, border=self.border(), closable=self.tabsClosable())) kt.addTab(widget,tb.text()) + if fwold!=(fwnew := self._baseWidget._getFirstWidget()) and fwold._hasMenu(): + fwnew._importMenu(fwold) if xw*3//4: @@ -134,6 +157,7 @@ class _TTkKodeTab(TTkTabWidget): @pyTTkSlot() def _kodeTabClosed(self, widget=None): # Remove the widget and/or all the cascade empty splitters + fwold = self._baseWidget._getFirstWidget() widget = widget if type(widget) is _TTkKodeTab else self if not widget._tabWidgets: if splitter := widget.parentWidget(): @@ -143,6 +167,8 @@ class _TTkKodeTab(TTkTabWidget): splitter.removeWidget(widget) if splitter == self._baseWidget and splitter.count() == 0: splitter.addWidget(self) + if fwold!=(fwnew := self._baseWidget._getFirstWidget()) and fwold._hasMenu(): + fwnew._importMenu(fwold) # Stupid hack to paint on top of the child widgets def paintChildCanvas(self): @@ -169,6 +195,12 @@ class TTkKodeTab(TTkSplitter): self._lastKodeTabWidget = _TTkKodeTab(baseWidget=self, **kwargs) self.addWidget(self._lastKodeTabWidget) + def _getFirstWidget(self): + kt = self + item = None + while type(item:=kt.widget(0)) != _TTkKodeTab: kt = item + return item if type(item)==_TTkKodeTab else None + @pyTTkSlot(TTkWidget) def setCurrentWidget(self, *args, **kwargs): return self._lastKodeTabWidget.setCurrentWidget(*args, **kwargs) @@ -181,3 +213,9 @@ class TTkKodeTab(TTkSplitter): return self._lastKodeTabWidget.addTab(*args, **kwargs) raise Exception("No TTkKodeTab found to be used") return self._lastKodeTabWidget.addTab(*args, **kwargs) + + # def addMenu(self, text, position=TTkK.LEFT, data=None): + def addMenu(self, text:TTkString, data:object=None, checkable:bool=False, checked:bool=False, position=TTkK.LEFT): + '''addMenu''' + # return self._lastKodeTabWidget.addMenu(text=text, data=data, checkable=checkable, checked=checked, position=position) + return self._lastKodeTabWidget.addMenu(text=text, data=data, position=position) diff --git a/TermTk/TTkWidgets/label.py b/TermTk/TTkWidgets/label.py index 9aca1d82..1d1de991 100644 --- a/TermTk/TTkWidgets/label.py +++ b/TermTk/TTkWidgets/label.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLabel'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString @@ -30,9 +30,14 @@ from TermTk.TTkWidgets.widget import TTkWidget class TTkLabel(TTkWidget): '''TTkLabel''' - __slots__ = ('_text','_color','_alignment') + + classStyle = { + 'default': {'color': TTkColor.RST }, + 'disabled': {'color': TTkColor.fg('#888888')}, + } + + __slots__ = ('_text', '_alignment') def __init__(self, *args, **kwargs): - self._color = kwargs.get('color', TTkColor.RST ) text = kwargs.get('text', TTkString() ) if issubclass(type(text), TTkString): self._text = text.split('\n') @@ -56,13 +61,11 @@ class TTkLabel(TTkWidget): def color(self): '''color''' - return self._color + return self.style()['default']['color'] def setColor(self, color): '''setColor''' - if self._color != color: - self._color = color - self.update() + self.mergeStyle({'default':{'color':color}}) def text(self): '''text''' @@ -79,11 +82,15 @@ class TTkLabel(TTkWidget): self._textUpdated() def paintEvent(self, canvas): - forceColor = self.color()!=TTkColor.RST + style = self.currentStyle() + color = style['color'] + + forceColor = color!=TTkColor.RST + w = self.width() for y,text in enumerate(self._text): - canvas.drawText(pos=(0,y), text=' '*w, color=self.color(), forceColor=forceColor) - canvas.drawText(pos=(0,y), text=text, width=w, alignment=self._alignment, color=self.color(), forceColor=forceColor) + canvas.drawText(pos=(0,y), text=' '*w, color=color, forceColor=forceColor) + canvas.drawText(pos=(0,y), text=text, width=w, alignment=self._alignment, color=color, forceColor=forceColor) def _textUpdated(self): w, h = self.size() @@ -93,5 +100,3 @@ class TTkLabel(TTkWidget): self.setMinimumSize(textWidth, 1) self.update() - def colorUpdated(self, color): - self.update() diff --git a/TermTk/TTkWidgets/lineedit.py b/TermTk/TTkWidgets/lineedit.py index a46429ed..d916e7c1 100644 --- a/TermTk/TTkWidgets/lineedit.py +++ b/TermTk/TTkWidgets/lineedit.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,16 +20,18 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkLineEdit'] + import re from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget - ''' Line Edit: |_________-___________| Text "abcdefbhijklmnopqrstuvwxyz12345" @@ -40,8 +40,17 @@ from TermTk.TTkWidgets.widget import TTkWidget ''' class TTkLineEdit(TTkWidget): '''TTkLineEdit''' + + classStyle = { + 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), + 'selectedColor': TTkColor.fg("#ffffff")+TTkColor.bg("#008844")}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'selectedColor': TTkColor.fg("#888888")+TTkColor.bg("#444444")}, + 'focus': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#000044")}, + } + __slots__ = ( - '_text', '_cursorPos', '_offset', '_replace', '_inputType', '_selectionFrom', '_selectionTo', '_color', + '_text', '_cursorPos', '_offset', '_replace', '_inputType', '_selectionFrom', '_selectionTo', # Signals 'returnPressed', 'textChanged', 'textEdited' ) def __init__(self, *args, **kwargs): @@ -57,9 +66,9 @@ class TTkLineEdit(TTkWidget): self._text = TTkString(kwargs.get('text' , '' )) self._inputType = kwargs.get('inputType' , TTkK.Input_Text ) super().__init__(*args, **kwargs) - if self._inputType & TTkK.Input_Number and\ - not self._text.lstrip('-').isdigit(): self._text = TTkString() - self._color = TTkCfg.theme.lineEditTextColor + if ( self._inputType & TTkK.Input_Number and + not self._isFloat(self._text)): + self._text = TTkString('0') self.setMaximumHeight(1) self.setMinimumSize(1,1) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) @@ -108,17 +117,13 @@ class TTkLineEdit(TTkWidget): self.update() def paintEvent(self, canvas): - if not self.isEnabled(): - color = TTkCfg.theme.textColorDisabled - selectColor = TTkCfg.theme.textColorDisabled - elif self.hasFocus(): - color = TTkCfg.theme.lineEditTextColorFocus - selectColor = TTkCfg.theme.lineEditTextColorSelected - else: - color = self._color - selectColor = TTkCfg.theme.lineEditTextColorSelected + style = self.currentStyle() + + color = style['color'] + selectColor = style['selectedColor'] + w = self.width() - text = TTkString() + color + text = TTkString('', color) if self._inputType & TTkK.Input_Password: text += ("*"*(len(self._text))) else: @@ -177,6 +182,41 @@ class TTkLineEdit(TTkWidget): self.update() return True + @staticmethod + def _isFloat(num): + try: + float(num) + return True + except: + return False + + + def pasteEvent(self, txt:str): + txt = TTkString().join(txt.split('\n')) + + text = self._text + + if self._selectionFrom < self._selectionTo: + pre = text.substring(to=self._selectionFrom) + post = text.substring(fr=self._selectionTo) + self._cursorPos = self._selectionFrom + else: + pre = text.substring(to=self._cursorPos) + if self._replace: + post = text.substring(fr=self._cursorPos+1) + else: + post = text.substring(fr=self._cursorPos) + + text = pre + txt + post + if ( self._inputType & TTkK.Input_Number and + not self._isFloat(text) ): + return True + self.setText(text, self._cursorPos+txt.termWidth()) + + self._pushCursor() + self.textEdited.emit(self._text) + return True + def keyEvent(self, evt): baseText = self._text if evt.type == TTkK.SpecialKey: @@ -214,8 +254,8 @@ class TTkLineEdit(TTkWidget): self._text = self._text.substring(to=prev) + self._text.substring(fr=self._cursorPos) self._cursorPos = prev - if self._inputType & TTkK.Input_Number and \ - not self._text.lstrip('-').isdigit(): + if ( self._inputType & TTkK.Input_Number and + not self._isFloat(self._text) ): self.setText('0', 1) self._pushCursor() @@ -237,8 +277,8 @@ class TTkLineEdit(TTkWidget): post = text.substring(fr=self._cursorPos) text = pre + evt.key + post - if self._inputType & TTkK.Input_Number and \ - not text.lstrip('-').isdigit(): + if ( self._inputType & TTkK.Input_Number and + not self._isFloat(text) ): return True self.setText(text, self._cursorPos+1) diff --git a/TermTk/TTkWidgets/list_.py b/TermTk/TTkWidgets/list_.py index 67658698..52850f2a 100644 --- a/TermTk/TTkWidgets/list_.py +++ b/TermTk/TTkWidgets/list_.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkList'] + from TermTk.TTkWidgets.listwidget import TTkListWidget from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 1bd1912f..05de8644 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkAbstractListItem', 'TTkListWidget'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -32,29 +32,39 @@ from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.label import TTkLabel from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView -class TTkAbstractListItem(TTkLabel): +class TTkAbstractListItem(TTkWidget): '''TTkAbstractListItem''' - __slots__ = ('_pressed', '_selected', '_highlighted', 'listItemClicked', '_data') - def __init__(self, *args, **kwargs): - TTkLabel.__init__(self, *args, **kwargs) - self._data = kwargs.get('data', "" ) - # Define Signals + + classStyle = TTkWidget.classStyle | { + 'default': {'color': TTkColor.RST}, + 'highlighted': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')+TTkColor.UNDERLINE}, + 'hover': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0088FF')}, + 'selected': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')}, + 'clicked': {'color': TTkColor.fg('#FFFF00')}, + 'disabled': {'color': TTkColor.fg('#888888')}, + } + + __slots__ = ('_text', '_selected', '_highlighted', '_data', + 'listItemClicked') + def __init__(self, *, text='', data=None, **kwargs): self.listItemClicked = pyTTkSignal(TTkAbstractListItem) + self._selected = False - self._pressed = False self._highlighted = False - self.setFocusPolicy(TTkK.ClickFocus) - def _updateColor(self): - if self._highlighted: - if self._selected: - self.setColor(TTkCfg.theme.listColorHighlighted + TTkColor.UNDERLINE) - else: - self.setColor(TTkCfg.theme.listColorHighlighted) - elif self._selected: - self.setColor(TTkCfg.theme.listColorSelected) - else: - self.setColor(TTkCfg.theme.listColor) + self._text = TTkString(text) + self._data = data + + super().__init__(**kwargs) + + self.setFocusPolicy(TTkK.ParentFocus) + + def text(self): + return self._text + + def setText(self, text): + self._text = TTkString(text) + self.update() def data(self): '''data''' @@ -62,57 +72,47 @@ class TTkAbstractListItem(TTkLabel): def setData(self, data): '''setData''' - if self._data != data: - self._data = data - self.update() - - def keyEvent(self, evt): - return self.parentWidget().keyEvent(evt) - - def mousePressEvent(self, evt): - self._pressed = True - self.highlighted = True + if self._data == data: return + self._data = data self.update() - return True - def mouseReleaseEvent(self, evt): - self._pressed = False + def mousePressEvent(self, evt) -> bool: self.listItemClicked.emit(self) - self.update() return True - @property - def selected(self): - return self._selected + def _setSelected(self, selected): + if self._selected == selected: return + self._selected = selected + self._highlighted = not selected + self.update() - @selected.setter - def selected(self, selected): - if self._selected != selected: - self._selected = selected - self._updateColor() + def _setHighlighted(self, highlighted): + if self._highlighted == highlighted: return + self._highlighted = highlighted + self.update() - @property - def highlighted(self): - return self._highlighted + def paintEvent(self, canvas): + style = self.currentStyle() + if style == self.classStyle['hover']: + pass + elif self._highlighted: + style = self.style()['highlighted'] + elif self._selected: + style = self.style()['selected'] - @highlighted.setter - def highlighted(self, highlighted): - if self._highlighted != highlighted: - self._highlighted = highlighted - self._updateColor() + w = self.width() + canvas.drawTTkString(pos=(0,0), width=w, color=style['color'] ,text=self._text) class TTkListWidget(TTkAbstractScrollView): '''TTkListWidget''' - __slots__ = ('itemClicked', 'textClicked', '_color', '_selectedColor', '_selectedItems', '_selectionMode', '_highlighted', '_items') + __slots__ = ('itemClicked', 'textClicked', '_selectedItems', '_selectionMode', '_highlighted', '_items') def __init__(self, *args, **kwargs): # Default Class Specific Values self._selectionMode = kwargs.get("selectionMode", TTkK.SingleSelection) self._selectedItems = [] self._items = [] self._highlighted = None - self._color = TTkCfg.theme.listColor - self._selectedColor = TTkCfg.theme.listColorSelected # Signals self.itemClicked = pyTTkSignal(TTkWidget) self.textClicked = pyTTkSignal(str) @@ -127,26 +127,25 @@ class TTkListWidget(TTkAbstractScrollView): self.layout().setOffset(-x,-y) @pyTTkSlot(TTkAbstractListItem) - def _labelSelectedHandler(self, label): + def _labelSelectedHandler(self, label:TTkAbstractListItem): if self._selectionMode == TTkK.SingleSelection: for item in self._selectedItems: - item.selected = False - item.highlighted = False + item._setSelected(False) + item._setHighlighted(False) self._selectedItems = [label] - label.selected = True + label._setSelected(True) elif self._selectionMode == TTkK.MultiSelection: for item in self._selectedItems: - item.highlighted = False - label.selected = not label.selected - if label.selected: + item._setHighlighted(False) + label._setSelected(not label._selected) + if label._selected: self._selectedItems.append(label) else: self._selectedItems.remove(label) if self._highlighted: - self._highlighted.highlighted = False - label.highlighted = True + self._highlighted._setHighlighted(False) + label._setHighlighted(True) self._highlighted = label - self.setFocus() self.itemClicked.emit(label) self.textClicked.emit(label.text()) @@ -200,9 +199,8 @@ class TTkListWidget(TTkAbstractScrollView): if isinstance(item, str) or isinstance(item, TTkString): #label = TTkAbstractListItem(text=item, width=max(len(item),self.width())) label = TTkAbstractListItem(text=item, data=data) - label.listItemClicked.connect(self._labelSelectedHandler) return self.addItemAt(label,pos) - # item.listItemClicked.connect(self._labelSelectedHandler) + item.listItemClicked.connect(self._labelSelectedHandler) self._items.insert(pos,item) self.layout().addWidget(item) self._placeItems() @@ -289,9 +287,9 @@ class TTkListWidget(TTkAbstractScrollView): elif evt.key == TTkK.Key_End: self.viewMoveTo(0x10000, offy) - self._highlighted.highlighted = False + self._highlighted._setHighlighted(False) self._highlighted = self._items[index] - self._highlighted.highlighted = True + self._highlighted._setHighlighted(True) self._moveToHighlighted() return True return False @@ -300,9 +298,9 @@ class TTkListWidget(TTkAbstractScrollView): if not self._items: return if not self._highlighted: self._highlighted = self._items[0] - self._highlighted.highlighted=True + self._highlighted._setHighlighted(True) self._moveToHighlighted() def focusOutEvent(self): if self._highlighted: - self._highlighted.highlighted=False + self._highlighted._setHighlighted(False) diff --git a/TermTk/TTkWidgets/menu.py b/TermTk/TTkWidgets/menu.py index 0fc3ee3e..aa290a33 100644 --- a/TermTk/TTkWidgets/menu.py +++ b/TermTk/TTkWidgets/menu.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkMenuButton', 'TTkMenu'] + from TermTk.TTkCore.cfg import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.color import TTkColor diff --git a/TermTk/TTkWidgets/menubar.py b/TermTk/TTkWidgets/menubar.py index 5871494d..879ddc80 100644 --- a/TermTk/TTkWidgets/menubar.py +++ b/TermTk/TTkWidgets/menubar.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,17 +20,17 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkCore.cfg import * +__all__ = ['TTkMenuBarButton', 'TTkMenuBarLayout'] + +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.color import TTkColor -from TermTk.TTkCore.log import TTkLog +# from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkCore.string import TTkString -from TermTk.TTkWidgets.button import TTkButton -from TermTk.TTkWidgets.listwidget import TTkListWidget, TTkAbstractListItem from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkLayouts.boxlayout import TTkHBoxLayout -from TermTk.TTkWidgets.menu import TTkMenu, TTkMenuButton, _TTkMenuSpacer +from TermTk.TTkWidgets.menu import TTkMenuButton class TTkMenuBarButton(TTkMenuButton): classStyle = TTkMenuButton.classStyle | { @@ -65,11 +63,6 @@ class TTkMenuBarButton(TTkMenuButton): self.setMaximumSize(txtlen+2,1) return super().setCheckable(ch) - def setBorderColor(self, color): - style = self.style() - style['default'] |= {'borderColor':color} - self.setStyle(style) - def paintEvent(self, canvas): style = self.currentStyle() borderColor = style['borderColor'] @@ -93,11 +86,10 @@ class TTkMenuBarButton(TTkMenuButton): class TTkMenuBarLayout(TTkHBoxLayout): '''TTkMenuBarLayout''' - __slots__ = ('_borderColor', '_itemsLeft', '_itemsCenter', '_itemsRight', '_buttons') + __slots__ = ('_itemsLeft', '_itemsCenter', '_itemsRight', '_buttons') def __init__(self, *args, **kwargs): self._buttons = [] TTkHBoxLayout.__init__(self, *args, **kwargs) - self._borderColor = kwargs.get('borderColor', TTkCfg.theme.frameBorderColor ) self._itemsLeft = TTkHBoxLayout() self._itemsCenter = TTkHBoxLayout() self._itemsRight = TTkHBoxLayout() @@ -107,16 +99,9 @@ class TTkMenuBarLayout(TTkHBoxLayout): self.addItem(TTkLayout()) self.addItem(self._itemsRight) - def setBorderColor(self, color): - self._borderColor = color - for b in self._buttons: - b.setBorderColor(color) - self.update() - def addMenu(self,text:TTkString, data:object=None, checkable:bool=False, checked:bool=False, alignment=TTkK.LEFT_ALIGN): '''addMenu''' button = TTkMenuBarButton(text=text, data=data, checkable=checkable, checked=checked) - # button = TTkMenuButton(text=text, borderColor=self._borderColor, border=True) self._mbItems(alignment).addWidget(button) self._buttons.append(button) self.update() diff --git a/TermTk/TTkWidgets/radiobutton.py b/TermTk/TTkWidgets/radiobutton.py index 3231101c..9834c73a 100644 --- a/TermTk/TTkWidgets/radiobutton.py +++ b/TermTk/TTkWidgets/radiobutton.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,7 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkRadioButton'] + +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal from TermTk.TTkWidgets.widget import * @@ -59,6 +61,18 @@ class TTkRadioButton(TTkWidget): ''' + classStyle = { + 'default': {'color': TTkColor.RST, + 'borderColor':TTkColor.RST, + 'rbContentColor': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888'), + 'rbContentColor': TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD, + 'rbContentColor': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD}, + } + _radioLists = {} __slots__ = ( '_checked', '_text', '_radiogroup' @@ -144,14 +158,12 @@ class TTkRadioButton(TTkWidget): self.update() def paintEvent(self, canvas): - if self.hasFocus(): - borderColor = TTkCfg.theme.radioButtonBorderColorFocus - textColor = TTkCfg.theme.radioButtonTextColorFocus - xColor = TTkCfg.theme.radioButtonContentColorFocus - else: - borderColor = TTkCfg.theme.radioButtonBorderColor - textColor = TTkCfg.theme.radioButtonTextColor - xColor = TTkCfg.theme.radioButtonContentColor + style = self.currentStyle() + + borderColor = style['borderColor'] + textColor = style['color'] + xColor = style['rbContentColor'] + canvas.drawText(pos=(0,0), color=borderColor ,text="( )") canvas.drawText(pos=(3,0), color=textColor ,text=self._text) if self._checked: diff --git a/TermTk/TTkWidgets/resizableframe.py b/TermTk/TTkWidgets/resizableframe.py index 3b138b20..e408a161 100644 --- a/TermTk/TTkWidgets/resizableframe.py +++ b/TermTk/TTkWidgets/resizableframe.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkResizableFrame'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkWidgets.frame import TTkFrame diff --git a/TermTk/TTkWidgets/scrollarea.py b/TermTk/TTkWidgets/scrollarea.py index 34802350..597e04d1 100644 --- a/TermTk/TTkWidgets/scrollarea.py +++ b/TermTk/TTkWidgets/scrollarea.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkScrollArea'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea diff --git a/TermTk/TTkWidgets/scrollbar.py b/TermTk/TTkWidgets/scrollbar.py index d925257c..2780251b 100644 --- a/TermTk/TTkWidgets/scrollbar.py +++ b/TermTk/TTkWidgets/scrollbar.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkScrollBar'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor @@ -32,11 +32,18 @@ from TermTk.TTkWidgets.widget import TTkWidget ''' class TTkScrollBar(TTkWidget): '''TTkScrollBar''' + + classStyle = { + 'default': {'color': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg('#cccc00')}, + } + __slots__ = ( '_orientation', '_minimum', '_maximum', '_singleStep', '_pageStep', - '_value', '_color', '_focusColor', + '_value', '_draggable', '_mouseDelta', # Those Vars are required to handle the mouseclick # |-----| Screen Pg Down @@ -67,8 +74,6 @@ class TTkScrollBar(TTkWidget): self._singleStep = kwargs.get('singleStep' , 1 ) self._pageStep = kwargs.get('pageStep' , 10 ) self._value = kwargs.get('value' , 0 ) - self._color = kwargs.get('color', TTkColor.RST ) - self._focusColor = kwargs.get('focusColor', TTkColor.fg('#cccc00') ) self._screenPgDown = (0,0) self._screenPgUp = (0,0) self._screenScroller = (0,0) @@ -91,15 +96,14 @@ class TTkScrollBar(TTkWidget): ''' def paintEvent(self, canvas): + style = self.currentStyle() + color = style['color'] + if self._orientation == TTkK.VERTICAL: size=self._height else: size=self._width - if self.hasFocus(): - color = self._focusColor - else: - color = self._color if self._maximum == self._minimum: # Special case where no scroll is needed aa=0 @@ -122,12 +126,11 @@ class TTkScrollBar(TTkWidget): def wheelEvent(self, evt): if evt.evt == TTkK.WHEEL_Up: - self._value -= self._pageStep + value = self._value-self._pageStep else: - self._value += self._pageStep - self._value = max(self._minimum,min(self._maximum,self._value)) + value = self._value+self._pageStep + self.setValue(max(self._minimum,min(self._maximum,value))) self.sliderMoved.emit(self._value) - self.update() return True def mousePressEvent(self, evt): @@ -139,21 +142,20 @@ class TTkScrollBar(TTkWidget): mouse = evt.x if mouse == 0: # left/up arrow pressed - self._value = self._value - self._singleStep + self.setValue(self._value - self._singleStep) elif mouse == size-1: # right/down arrow pressed - self._value = self._value + self._singleStep + self.setValue(self._value + self._singleStep) elif self._screenPgDown[0] <= mouse < self._screenPgDown[1]: - self._value = self._value - self._pageStep + self.setValue(self._value - self._pageStep) elif self._screenPgUp[0] <= mouse < self._screenPgUp[1]: - self._value = self._value + self._pageStep + self.setValue(self._value + self._pageStep) elif self._screenScroller[0] <= mouse < self._screenScroller[1]: self._mouseDelta = mouse-self._screenScroller[0] self._draggable = True else: return False - self._value = max(self._minimum,min(self._maximum,self._value)) + self.setValue(max(self._minimum,min(self._maximum,self._value))) self.sliderMoved.emit(self._value) - self.update() # TTkLog.debug(f"m={mouse}, md:{self._mouseDelta}, d:{self._screenPgDown},u:{self._screenPgUp},s:{self._screenScroller}") return True @@ -172,11 +174,9 @@ class TTkScrollBar(TTkWidget): asciiDrawingSize = size2 - asciiStep a = aa * (self._maximum - self._minimum) // asciiDrawingSize - self._value = a + self._minimum - self._value = max(self._minimum,min(self._maximum,self._value)) + self.setValue(max(self._minimum,min(self._maximum,a+self._minimum))) self.sliderMoved.emit(self._value) - self.update() # TTkLog.debug(f"m={mouse}, md:{self._mouseDelta}, aa:{aa}") return True diff --git a/TermTk/TTkWidgets/spacer.py b/TermTk/TTkWidgets/spacer.py index fd8cf6dd..58872819 100644 --- a/TermTk/TTkWidgets/spacer.py +++ b/TermTk/TTkWidgets/spacer.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkSpacer'] + from TermTk.TTkWidgets.widget import TTkWidget class TTkSpacer(TTkWidget): diff --git a/TermTk/TTkWidgets/spinbox.py b/TermTk/TTkWidgets/spinbox.py index 5acdf286..4e4c419d 100644 --- a/TermTk/TTkWidgets/spinbox.py +++ b/TermTk/TTkWidgets/spinbox.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,17 +20,25 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkSpinBox'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkLayouts import TTkGridLayout -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.lineedit import TTkLineEdit - -class TTkSpinBox(TTkWidget): +class TTkSpinBox(TTkContainer): '''TTkSpinBox''' + + classStyle = { + 'default': {'color': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD}, + } + __slots__= ( '_lineEdit', '_value', '_maximum', '_minimum', '_mouseDelta', '_valueDelta', '_draggable', @@ -41,7 +47,7 @@ class TTkSpinBox(TTkWidget): def __init__(self, *args, **kwargs): # Signals self.valueChanged=pyTTkSignal(int) - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self._value = kwargs.get("value",0) self._maximum = kwargs.get("maximum",99) self._minimum = kwargs.get("minimum",0) @@ -143,20 +149,13 @@ class TTkSpinBox(TTkWidget): return super().setEnabled(enabled) def paintEvent(self, canvas): - if not self.isEnabled(): - textColor = TTkCfg.theme.textColorDisabled - else: - textColor = TTkColor.RST + style = self.currentStyle() + color = style['color'] w = self.width() - canvas.drawChar(pos=(w-2,0),char="▲", color=textColor) - canvas.drawChar(pos=(w-1,0),char="▼", color=textColor) - - def focusInEvent(self): - self._lineEdit._color = TTkCfg.theme.lineEditTextColorFocus - self._lineEdit.update() + canvas.drawChar(pos=(w-2,0),char="▲", color=color) + canvas.drawChar(pos=(w-1,0),char="▼", color=color) def focusOutEvent(self): self._draggable = False - self._lineEdit._color = TTkCfg.theme.lineEditTextColor self._lineEdit.focusOutEvent() self._lineEdit.update() \ No newline at end of file diff --git a/TermTk/TTkWidgets/splitter.py b/TermTk/TTkWidgets/splitter.py index 427b43a6..714e8e12 100644 --- a/TermTk/TTkWidgets/splitter.py +++ b/TermTk/TTkWidgets/splitter.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,15 +20,28 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkSplitter'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.frame import TTkFrame +from TermTk.TTkWidgets.container import TTkContainer -class TTkSplitter(TTkWidget): +class TTkSplitter(TTkContainer): '''TTkSplitter''' + + classStyle = { + 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), + 'borderColor': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#ffddff")+TTkColor.bg("#222222"), + 'borderColor': TTkColor.fg("#ffffaa")} + } + __slots__ = ( '_orientation', '_separators', '_refSizes', '_items', '_titles', '_separatorSelected', @@ -414,19 +425,23 @@ class TTkSplitter(TTkWidget): return ret def paintEvent(self, canvas): + style = self.currentStyle() + color = style['color'] + borderColor = style['borderColor'] + off = 0 w,h = self.size() if self._border: off= 1 - canvas.drawBox(pos=(0,0),size=(w,h)) + canvas.drawBox(pos=(0,0),size=(w,h),color=borderColor) if self._orientation == TTkK.HORIZONTAL: for i in self._separators[:-1]: - canvas.drawVLine(pos=(i+off,0), size=h) + canvas.drawVLine(pos=(i+off,0), size=h,color=borderColor) else: for i in self._separators[:-1]: - canvas.drawHLine(pos=(0,i+off), size=w) + canvas.drawHLine(pos=(0,i+off), size=w,color=borderColor) if self._orientation == TTkK.HORIZONTAL and self._border: for i,t in enumerate(self._titles): @@ -437,7 +452,8 @@ class TTkSplitter(TTkWidget): pos=(a,0), size=(b-a+1,1), text=t, - colorText=TTkCfg.theme.frameTitleColor) + color=borderColor, + colorText=color) elif self._orientation == TTkK.VERTICAL: for i,t in enumerate(self._titles): if i == 0 and not self._border: continue @@ -449,5 +465,6 @@ class TTkSplitter(TTkWidget): size=(w,1), grid=grid, text=t, - colorText=TTkCfg.theme.frameTitleColor) + color=borderColor, + colorText=color) diff --git a/TermTk/TTkWidgets/tabwidget.py b/TermTk/TTkWidgets/tabwidget.py index 077f46e8..45a82eb7 100644 --- a/TermTk/TTkWidgets/tabwidget.py +++ b/TermTk/TTkWidgets/tabwidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,15 +20,19 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTabButton', 'TTkTabBar', 'TTkTabWidget'] + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.string import TTkString from TermTk.TTkGui.drag import TTkDrag from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.spacer import TTkSpacer from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkWidgets.button import TTkButton @@ -38,7 +40,28 @@ from TermTk.TTkWidgets.menubar import TTkMenuBarButton from TermTk.TTkLayouts.boxlayout import TTkHBoxLayout from TermTk.TTkLayouts.gridlayout import TTkGridLayout +_tabStyle = { + 'default': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044"), + 'borderColor': TTkColor.RST, + 'tabOffsetColor': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888'), + 'tabOffsetColor': TTkColor.RST}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD, + 'tabOffsetColor': TTkColor.RST}, + } + +_tabStyleNormal = { + 'default': {'borderColor': TTkColor.RST}, + } + +_tabStyleFocussed = { + 'default': {'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD}, + } + class _TTkTabWidgetDragData(): + __slots__ = ('_tabButton', '_tabWidget') def __init__(self, b, tw): self._tabButton = b @@ -54,21 +77,56 @@ class _TTkTabBarDragData(): def tabButton(self): return self._tabButton def tabBar(self): return self._tabBar -class TTkTabButton(TTkButton): +class _TTkTabColorButton(TTkContainer): + classStyle = _tabStyle | { + 'hover': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000050")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#AAFFFF")+TTkColor.BOLD}, + } + + __slots__ = ( + '_text', '_border', + # Signals + 'clicked' + ) + def __init__(self, *, text="", border=True, **kwargs): + self.clicked = pyTTkSignal() + + self._text = TTkString(text.replace('\n','')) + self._border = border + + super().__init__(forwardStyle=True, **kwargs) + + def text(self) -> TTkString: + return self._text + + def mouseReleaseEvent(self, evt): + self.clicked.emit() + return True + + def keyEvent(self, evt): + if ( evt.type == TTkK.Character and evt.key==" " ) or \ + ( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ): + self._keyPressed = True + self._pressed = True + self.update() + self.clicked.emit() + return True + return False + +class TTkTabButton(_TTkTabColorButton): '''TTkTabButton''' - __slots__ = ('_sideEnd', '_tabStatus', '_closable', 'closeClicked', '_closeButton') - def __init__(self, *args, **kwargs): + __slots__ = ('_data','_sideEnd', '_tabStatus', '_closable', 'closeClicked', '_closeButtonPressed','_data') + def __init__(self, *, data=None, closable=False, **kwargs): self._sideEnd = TTkK.NONE self._tabStatus = TTkK.Unchecked - self._closable = kwargs.get('closable', False) + self._data = data + self._closable = closable self.closeClicked = pyTTkSignal() - TTkButton.__init__(self, *args, **kwargs) + super().__init__(**kwargs) + self._closeButtonPressed = False size = self.text().termWidth() + 2 if self._closable: size += 3 - self._closeButton = TTkButton(parent=self, border=False, text="x", pos=(size-4,1 if self._border else 0), size=(3,1)) - self._closeButton.setFocusPolicy(TTkK.ParentFocus) - self._closeButton.clicked.connect(self.closeClicked.emit) if self._border: self.resize(size, 3) self.setMinimumSize(size, 3) @@ -79,6 +137,12 @@ class TTkTabButton(TTkButton): self.setMaximumSize(size, 2) self.setFocusPolicy(TTkK.ParentFocus) + def data(self): + return self._data + + def setData(self, data): + self._data=data + def sideEnd(self): return self._sideEnd @@ -96,14 +160,28 @@ class TTkTabButton(TTkButton): # This is a hack to force the action aftet the keypress # And not key release as normally happen to the button def mousePressEvent(self, evt): + x,y = evt.x,evt.y + w,h = self.size() + self._closeButtonPressed = False if self._closable and evt.key == TTkK.MidButton: self.closeClicked.emit() return True + if self._closable and y == (1 if self._border else 0) and w-4<=x bool: drag = TTkDrag() + self._closeButtonPressed = False if tb := self.parentWidget(): if issubclass(type(tb),TTkTabBar): if tw:= tb.parentWidget(): @@ -114,7 +192,7 @@ class TTkTabButton(TTkButton): data = _TTkTabBarDragData(self,tb) pm = TTkCanvas(width=self.width(),height=3) pm.drawBox(pos=(0,0),size=(self.width(),3)) - pm.drawText(pos=(1,1), text=self.text(), color=self.color()) + pm.drawText(pos=(1,1), text=self.text(), color=self.currentStyle()['color']) drag.setPixmap(pm) # drag.setPixmap(self) drag.setData(data) @@ -123,17 +201,23 @@ class TTkTabButton(TTkButton): return super().mouseDragEvent(evt) def paintEvent(self, canvas): + style = self.currentStyle() + + borderColor = style['borderColor'] + textColor = style['color'] + + w,h = self.size() + canvas.drawTabButton( pos=(0,0), size=self.size(), small=(not self._border), sideEnd=self._sideEnd, status=self._tabStatus, - color=self._borderColor ) - canvas.drawText(pos=(1,1 if self._border else 0), text=self.text(), color=self.color()) + color=borderColor ) + canvas.drawText(pos=(1,1 if self._border else 0), text=self.text(), color=textColor) + if self._closable: + canvas.drawText(pos=(w-4,1 if self._border else 0), text="[X]", color=textColor) class _TTkTabMenuButton(TTkMenuBarButton): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - def paintEvent(self, canvas): style = self.currentStyle() borderColor = style['borderColor'] @@ -141,12 +225,13 @@ class _TTkTabMenuButton(TTkMenuBarButton): text = TTkString('[',borderColor) + TTkString(self.text(),textColor) + TTkString(']',borderColor) canvas.drawText(pos=(0,0),text=text) -class _TTkTabScrollerButton(TTkButton): +class _TTkTabScrollerButton(_TTkTabColorButton): + classStyle = _tabStyle __slots__ = ('_side', '_sideEnd') def __init__(self, *args, **kwargs): self._side = kwargs.get('side',TTkK.LEFT) self._sideEnd = self._side - TTkButton.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) if self._border: self.resize(2, 3) self.setMinimumSize(2, 3) @@ -182,29 +267,34 @@ class _TTkTabScrollerButton(TTkButton): return True def paintEvent(self, canvas): + style = self.currentStyle() + borderColor = style['borderColor'] + offsetColor = style['tabOffsetColor'] + # textColor = style['color'] + tt = TTkCfg.theme.tab if self._border: lse = tt[11] if self._sideEnd & TTkK.LEFT else tt[13] rse = tt[15] if self._sideEnd & TTkK.RIGHT else tt[13] if self._side == TTkK.LEFT: - canvas.drawText(pos=(0,0), color=self._borderColor, text=tt[7] +tt[1]) - canvas.drawText(pos=(0,1), color=self._borderColor, text=tt[9] +tt[31]) - canvas.drawText(pos=(0,2), color=self._borderColor, text=lse +tt[12]) - canvas.drawChar(pos=(1,1), char=tt[31], color=TTkCfg.theme.tabOffsetColor) + canvas.drawText(pos=(0,0), color=borderColor, text=tt[7] +tt[1]) + canvas.drawText(pos=(0,1), color=borderColor, text=tt[9] +tt[31]) + canvas.drawText(pos=(0,2), color=borderColor, text=lse +tt[12]) + canvas.drawChar(pos=(1,1), char=tt[31], color=offsetColor) else: - canvas.drawText(pos=(0,0), color=self._borderColor, text=tt[1] +tt[8]) - canvas.drawText(pos=(0,1), color=self._borderColor, text=tt[32]+tt[9]) - canvas.drawText(pos=(0,2), color=self._borderColor, text=tt[12]+rse) - canvas.drawChar(pos=(0,1), char=tt[32], color=TTkCfg.theme.tabOffsetColor) + canvas.drawText(pos=(0,0), color=borderColor, text=tt[1] +tt[8]) + canvas.drawText(pos=(0,1), color=borderColor, text=tt[32]+tt[9]) + canvas.drawText(pos=(0,2), color=borderColor, text=tt[12]+rse) + canvas.drawChar(pos=(0,1), char=tt[32], color=offsetColor) else: if self._side == TTkK.LEFT: - canvas.drawText(pos=(0,0), color=self._borderColor, text=tt[9] +tt[31]) - canvas.drawText(pos=(0,1), color=self._borderColor, text=tt[23]+tt[1]) - canvas.drawChar(pos=(1,0), char=tt[31], color=TTkCfg.theme.tabOffsetColor) + canvas.drawText(pos=(0,0), color=borderColor, text=tt[9] +tt[31]) + canvas.drawText(pos=(0,1), color=borderColor, text=tt[23]+tt[1]) + canvas.drawChar(pos=(1,0), char=tt[31], color=offsetColor) else: - canvas.drawText(pos=(0,0), color=self._borderColor, text=tt[32]+tt[9]) - canvas.drawText(pos=(0,1), color=self._borderColor, text=tt[1] +tt[24]) - canvas.drawChar(pos=(0,0), char=tt[32], color=TTkCfg.theme.tabOffsetColor) + canvas.drawText(pos=(0,0), color=borderColor, text=tt[32]+tt[9]) + canvas.drawText(pos=(0,1), color=borderColor, text=tt[1] +tt[24]) + canvas.drawChar(pos=(0,0), char=tt[32], color=offsetColor) ''' _curentIndex = 2 _tabButtons = [0],[1], [2], [3], [4], @@ -214,37 +304,33 @@ _labels= │◀│La│Label1║Label2║Label3│Label4│▶│ leftscroller rightScroller ''' -class TTkTabBar(TTkWidget): +class TTkTabBar(TTkContainer): '''TTkTabBar''' + classStyle = _tabStyle __slots__ = ( - '_tabButtons', '_tabData', '_tabMovable', '_small', + '_tabButtons', '_tabMovable', '_small', '_highlighted', '_currentIndex','_lastIndex', '_leftScroller', '_rightScroller', - '_borderColor', '_tabClosable', + '_tabClosable', '_sideEnd', #Signals 'currentChanged', 'tabBarClicked', 'tabCloseRequested') - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): self._tabButtons = [] - self._tabData = [] self._currentIndex = -1 self._lastIndex = -1 self._highlighted = -1 self._tabMovable = False self._tabClosable = kwargs.get('closable',False) self._sideEnd = TTkK.LEFT | TTkK.RIGHT - self._borderColor = TTkCfg.theme.tabBorderColor self._small = kwargs.get('small',True) self._leftScroller = _TTkTabScrollerButton(border=not self._small,side=TTkK.LEFT) self._rightScroller = _TTkTabScrollerButton(border=not self._small,side=TTkK.RIGHT) self._leftScroller.clicked.connect( self._moveToTheLeft) self._rightScroller.clicked.connect(self._andMoveToTheRight) - TTkWidget.__init__(self, *args, **kwargs) - self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) - self.focusChanged.connect(self._focusChanged) - + super().__init__(forwardStyle=False, **kwargs) # Add and connect the scrollers self.layout().addWidget(self._leftScroller) @@ -257,6 +343,13 @@ class TTkTabBar(TTkWidget): self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) + def mergeStyle(self, style): + super().mergeStyle(style) + for t in self._tabButtons: + t.mergeStyle(style) + self._leftScroller.mergeStyle(style) + self._rightScroller.mergeStyle(style) + def sideEnd(self): return self._sideEnd @@ -268,16 +361,14 @@ class TTkTabBar(TTkWidget): def addTab(self, label, data=None): '''addTab''' - return self.insertTab(len(self._tabButtons), label, data) + return self.insertTab(len(self._tabButtons), label=label, data=data) def insertTab(self, index, label, data=None): '''insertTab''' if index <= self._currentIndex: self._currentIndex += 1 - button = TTkTabButton(parent=self, text=label, border=not self._small, closable=self._tabClosable) - button._borderColor = self._borderColor + button = TTkTabButton(parent=self, text=label, border=not self._small, closable=self._tabClosable, data=data) self._tabButtons.insert(index,button) - self._tabData.insert(index,data) button.clicked.connect(lambda :self.setCurrentIndex(self._tabButtons.index(button))) button.clicked.connect(lambda :self.tabBarClicked.emit(self._tabButtons.index(button))) button.closeClicked.connect(lambda :self.tabCloseRequested.emit(self._tabButtons.index(button))) @@ -292,7 +383,6 @@ class TTkTabBar(TTkWidget): button.closeClicked.clear() self.layout().removeWidget(button) self._tabButtons.pop(index) - self._tabData.pop(index) if self._currentIndex == index: self._lastIndex = -2 if self._currentIndex >= index: @@ -300,26 +390,18 @@ class TTkTabBar(TTkWidget): self._highlighted = self._currentIndex self._updateTabs() + def currentData(self): + return self.tabData(self._currentIndex) + def tabData(self, index): '''tabData''' - if 0 <= index < len(self._tabData): - return self._tabData[index] + if 0 <= index < len(self._tabButtons): + return self._tabButtons[index].data() return None def setTabData(self, index, data): '''setTabData''' - self._tabData[index] = data - - def borderColor(self): - return self._borderColor - - def setBorderColor(self, color): - self._borderColor = color - for b in self._tabButtons: - b.setBorderColor(color) - self._leftScroller.setBorderColor(color) - self._rightScroller.setBorderColor(color) - self.update() + self._tabButtons[index].setData(data) def tabsClosable(self): '''tabsClosable''' @@ -392,14 +474,11 @@ class TTkTabBar(TTkWidget): # set the buttons text color based on the selection/offset for i,b in enumerate(self._tabButtons): if i == self._highlighted != self._currentIndex: - b.setColor(TTkCfg.theme.tabOffsetColor) b.setTabStatus(TTkK.PartiallyChecked) b.raiseWidget() elif i == self._currentIndex: - b.setColor(TTkCfg.theme.tabSelectColor) b.setTabStatus(TTkK.Checked) else: - b.setColor(TTkCfg.theme.tabColor) b.setTabStatus(TTkK.Unchecked) self.update() @@ -438,26 +517,19 @@ class TTkTabBar(TTkWidget): return True return False - @pyTTkSlot(bool) - def _focusChanged(self, focus): - if focus: - borderColor = TTkCfg.theme.tabBorderColorFocus - else: - borderColor = TTkCfg.theme.tabBorderColor - self.setBorderColor(borderColor) - - def paintEvent(self, canvas): + style = self.currentStyle() + borderColor = style['borderColor'] w = self.width() tt = TTkCfg.theme.tab if self._small: lse = tt[23] if self._sideEnd & TTkK.LEFT else tt[19] rse = tt[24] if self._sideEnd & TTkK.RIGHT else tt[19] - canvas.drawText(pos=(0,1),text=lse + tt[19]*(w-2) + rse, color=self._borderColor) + canvas.drawText(pos=(0,1),text=lse + tt[19]*(w-2) + rse, color=borderColor) else: lse = tt[11] if self._sideEnd & TTkK.LEFT else tt[12] rse = tt[15] if self._sideEnd & TTkK.RIGHT else tt[12] - canvas.drawText(pos=(0,2),text=lse + tt[12]*(w-2) + rse, color=self._borderColor) + canvas.drawText(pos=(0,2),text=lse + tt[12]*(w-2) + rse, color=borderColor) ''' @@ -479,6 +551,7 @@ class TTkTabBar(TTkWidget): class TTkTabWidget(TTkFrame): '''TTkTabWidget''' + classStyle = _tabStyle __slots__ = ( '_tabBarTopLayout', '_tabBar', '_topLeftLayout', '_topRightLayout', '_tabWidgets', '_spacer', @@ -486,15 +559,14 @@ class TTkTabWidget(TTkFrame): 'currentChanged', 'tabBarClicked', # forward methods 'tabsClosable', 'setTabsClosable', - 'tabData', 'setData', 'setTabData', + 'tabData', 'setTabData', 'currentData', 'currentIndex', 'setCurrentIndex', 'tabCloseRequested') - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): self._tabWidgets = [] self._tabBarTopLayout = TTkGridLayout() - self._borderColor = TTkCfg.theme.tabBorderColor - TTkFrame.__init__(self, *args, **kwargs) + super().__init__(forwardStyle=False, **kwargs) self._tabBar = TTkTabBar(small = not self.border(), closable=kwargs.get('closable', False)) self._topLeftLayout = None @@ -504,7 +576,6 @@ class TTkTabWidget(TTkFrame): self._tabBar.currentChanged.connect(self._tabChanged) self.setFocusPolicy(self._tabBar.focusPolicy()) self._tabBar.setFocusPolicy(TTkK.ParentFocus) - self.focusChanged.connect(self._focusChanged) self._spacer = TTkSpacer(parent=self) @@ -520,14 +591,26 @@ class TTkTabWidget(TTkFrame): # forwarded methods self.currentIndex = self._tabBar.currentIndex self.setCurrentIndex = self._tabBar.setCurrentIndex - self.tabData = self._tabBar.tabData - self.setTabData = self._tabBar.setTabData + self.tabData = self._tabBar.tabData + self.setTabData = self._tabBar.setTabData + self.currentData = self._tabBar.currentData self.tabsClosable = self._tabBar.tabsClosable self.setTabsClosable = self._tabBar.setTabsClosable # forwarded Signals self.currentChanged = self._tabBar.currentChanged self.tabBarClicked = self._tabBar.tabBarClicked - self.tabCloseRequested = self._tabBar.tabCloseRequested + self.tabCloseRequested = pyTTkSignal(int) + + self.focusChanged.connect(self._focusChanged) + + def _focusChanged(self, focus): + if focus: + self._tabBar.mergeStyle(_tabStyleFocussed) + else: + self._tabBar.mergeStyle(_tabStyleNormal) + + def count(self) -> int: + return len(self._tabWidgets) def widget(self, index): '''widget''' @@ -560,15 +643,6 @@ class TTkTabWidget(TTkFrame): else: widget.hide() - @pyTTkSlot(bool) - def _focusChanged(self, focus): - if focus: - borderColor = TTkCfg.theme.tabBorderColorFocus - else: - borderColor = TTkCfg.theme.tabBorderColor - self.setBorderColor(borderColor) - self._tabBar.setBorderColor(borderColor) - def keyEvent(self, evt) -> bool: return self._tabBar.keyEvent(evt) @@ -607,9 +681,9 @@ class TTkTabWidget(TTkFrame): TTkLog.debug(f"Drop -> pos={evt.pos()}") return True - def addMenu(self, text, position=TTkK.LEFT): + def addMenu(self, text, position=TTkK.LEFT, data=None) -> TTkMenuBarButton: '''addMenu''' - button = _TTkTabMenuButton(text=text, borderColor=TTkCfg.theme.tabBorderColor) + button = _TTkTabMenuButton(text=text, data=data) self._tabBar.setSideEnd(self._tabBar.sideEnd() & ~position) if position==TTkK.LEFT: if not self._topLeftLayout: @@ -642,6 +716,7 @@ class TTkTabWidget(TTkFrame): @pyTTkSlot(int) def removeTab(self, index): '''removeTab''' + self.tabCloseRequested.emit(index) self.layout().removeWidget(self._tabWidgets[index]) self._tabWidgets.pop(index) self._tabBar.removeTab(index) @@ -650,8 +725,10 @@ class TTkTabWidget(TTkFrame): self._tabBarTopLayout.setGeometry(0,0,w,self._padt) def paintEvent(self, canvas): + style = self.currentStyle() + borderColor = style['borderColor'] tt = TTkCfg.theme.tab if self.border(): - canvas.drawBox(pos=(0,2),size=(self.width(),self.height()-2), color=self._borderColor, grid=9) + canvas.drawBox(pos=(0,2),size=(self.width(),self.height()-2), color=borderColor, grid=9) else: - canvas.drawText(pos=(0,1),text=tt[36] + tt[19]*(self.width()-2) + tt[35], color=self._borderColor) + canvas.drawText(pos=(0,1),text=tt[36] + tt[19]*(self.width()-2) + tt[35], color=borderColor) diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index a329221a..014dcaba 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkTextEditView', 'TTkTextEdit'] + from math import log10, floor from TermTk.TTkCore.color import TTkColor @@ -39,6 +39,17 @@ from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView, TTkAbstractScrollViewGridLayout class _TTkTextEditViewLineNumber(TTkAbstractScrollView): + classStyle = { + 'default': { + 'color': TTkColor.fg("#88aaaa")+TTkColor.bg("#333333"), + 'wrapColor': TTkColor.fg("#888888")+TTkColor.bg("#333333"), + 'separatorColor': TTkColor.fg("#444444")}, + 'disabled': { + 'color': TTkColor.fg('#888888'), + 'wrapColor': TTkColor.fg('#888888'), + 'separatorColor': TTkColor.fg("#888888")}, + } + __slots__ = ('_textWrap') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -69,24 +80,39 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView): if not self._textWrap: return _, oy = self.getViewOffsets() w, h = self.size() + + style = self.currentStyle() + color = style['color'] + wrapColor = style['wrapColor'] + separatorColor = style['separatorColor'] + if self._textWrap: for i, (dt, (fr, _)) in enumerate(self._textWrap._lines[oy:oy+h]): if fr: - txt = "<" - color = TTkCfg.theme.textEditLineNumberWrapcharColor + canvas.drawText(pos=(0,i), text='<', width=w, color=wrapColor) else: - txt = f"{dt}" - color = TTkCfg.theme.textEditLineNumberColor - canvas.drawText(pos=(0,i), text=txt, width=w, color=color) - canvas.drawChar(pos=(w-1,i), char='▌', color=TTkCfg.theme.textEditLineNumberSeparatorColor) + canvas.drawText(pos=(0,i), text=f"{dt}", width=w, color=color) + canvas.drawChar(pos=(w-1,i), char='▌', color=separatorColor) else: - color = TTkCfg.theme.textEditLineNumberColor for y in range(h): canvas.drawText(pos=(0,y), text=f"{y+oy}", width=w, color=color) - canvas.drawChar(pos=(w-1,y), char='▌', color=TTkCfg.theme.textEditLineNumberSeparatorColor) + canvas.drawChar(pos=(w-1,y), char='▌', color=separatorColor) class TTkTextEditView(TTkAbstractScrollView): '''TTkTextEditView''' + + classStyle = { + 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), + 'selectedColor': TTkColor.fg("#ffffff")+TTkColor.bg("#008844"), + 'lineColor': TTkColor.fg("#444444"), + 'wrapLineColor': TTkColor.fg("#888888")+TTkColor.bg("#333333")}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'selectedColor': TTkColor.bg("#888888"), + 'lineColor': TTkColor.fg("#888888"), + 'wrapLineColor': TTkColor.fg('#888888')}, + 'focus': {'selectedColor': TTkColor.fg("#ffffff")+TTkColor.bg("#008888")}, + } + __slots__ = ( '_textDocument', '_hsize', '_textCursor', '_textColor', '_cursorParams', @@ -289,9 +315,7 @@ class TTkTextEditView(TTkAbstractScrollView): @pyTTkSlot() def paste(self): txt = self._clipboard.text() - if not self._multiLine: - txt = TTkString().join(txt.split('\n')) - self._textCursor.insertText(txt) + self.pasteEvent(txt) @pyTTkSlot() def _documentChanged(self): @@ -421,6 +445,23 @@ class TTkTextEditView(TTkAbstractScrollView): self.update() return True + def pasteEvent(self, txt:str): + txt = TTkString(txt) + if not self._multiLine: + txt = TTkString().join(txt.split('\n')) + if self._replace: + self._textCursor.replaceText(txt, moveCursor=True) + else: + self._textCursor.insertText(txt, moveCursor=True) + # Scroll to align to the cursor + p = self._textCursor.position() + cx, cy = self._textWrap.dataToScreenPosition(p.line, p.pos) + self._updateSize() + self._scrolToInclude(cx,cy) + self._pushCursor() + self.update() + return True + def keyEvent(self, evt): if self._readOnly: return super().keyEvent(evt) @@ -523,8 +564,7 @@ class TTkTextEditView(TTkAbstractScrollView): self._textCursor.removeSelectedText() elif evt.key == TTkK.Key_Enter: if self._multiLine: - self._textCursor.insertText('\n') - self._textCursor.movePosition(TTkTextCursor.Right) + self._textCursor.insertText('\n', moveCursor=True) # Scroll to align to the cursor p = self._textCursor.position() cx, cy = self._textWrap.dataToScreenPosition(p.line, p.pos) @@ -535,10 +575,9 @@ class TTkTextEditView(TTkAbstractScrollView): return True else: # Input char if self._replace: - self._textCursor.replaceText(evt.key) + self._textCursor.replaceText(evt.key, moveCursor=True) else: - self._textCursor.insertText(evt.key) - self._textCursor.movePosition(TTkTextCursor.Right) + self._textCursor.insertText(evt.key, moveCursor=True) # Scroll to align to the cursor p = self._textCursor.position() cx, cy = self._textWrap.dataToScreenPosition(p.line, p.pos) @@ -552,10 +591,11 @@ class TTkTextEditView(TTkAbstractScrollView): def paintEvent(self, canvas): ox, oy = self.getViewOffsets() - if self.hasFocus(): - selectColor = TTkCfg.theme.lineEditTextColorSelected - else: - selectColor = TTkCfg.theme.lineEditTextColorSelected + + style = self.currentStyle() + color = style['color'] + selectColor = style['selectedColor'] + lineColor = style['lineColor'] h = self.height() subLines = self._textWrap._lines[oy:oy+h] @@ -567,7 +607,7 @@ class TTkTextEditView(TTkAbstractScrollView): canvas.drawTTkString(pos=(-ox,y), text=t.substring(l[1][0],l[1][1]).tab2spaces(self._textWrap._tabSpaces)) if self._lineWrapMode == TTkK.FixedWidth: - canvas.drawVLine(pos=(self._textWrap._wrapWidth,0), size=h, color=TTkCfg.theme.treeLineColor) + canvas.drawVLine(pos=(self._textWrap._wrapWidth,0), size=h, color=lineColor) class TTkTextEdit(TTkAbstractScrollArea): '''TTkTextEdit diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index f3ecfdb2..18372076 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkWidget'] + from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -30,7 +30,6 @@ from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot -from TermTk.TTkTemplates.lookandfeel import TTkLookAndFeel from TermTk.TTkTemplates.dragevents import TDragEvents from TermTk.TTkTemplates.mouseevents import TMouseEvents from TermTk.TTkTemplates.keyevents import TKeyEvents @@ -38,20 +37,20 @@ from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): - ''' Widget Layout sizes: + ''' Widget sizes: :: - Terminal area (i.e. XTerm) + Terminal area (i.e. XTerm) = TTk ┌─────────────────────────────────────────┐ │ │ │ TTkWidget width │ │ (x,y)┌─────────────────────────┐ │ - │ │ padt (Top Padding) │ │ - │ │ ┌───────────────┐ │ height │ - │ │padl│ Layout/child │padr│ │ - │ │ └───────────────┘ │ │ - │ │ padb (Bottom Pad.) │ │ + │ │ │ │ + │ │ │ height │ + │ │ │ │ + │ │ │ │ + │ │ │ │ │ └─────────────────────────┘ │ └─────────────────────────────────────────┘ @@ -70,11 +69,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): :param int height: the height of the widget, defaults to 0 :param [int,int] size: the size [width, height] of the widget (override the previously defined sizes), optional, default=[0,0] - :param int padding: the padding (top, bottom, left, right) of the widget, defaults to 0 - :param int paddingTop: the Top padding, override Top padding if already defined, optional, default=padding - :param int paddingBottom: the Bottom padding, override Bottom padding if already defined, optional, default=padding - :param int paddingLeft: the Left padding, override Left padding if already defined, optional, default=padding - :param int paddingRight: the Right padding, override Right padding if already defined, optional, default=padding :param int maxWidth: the maxWidth of the widget, optional, defaults to 0x10000 :param int maxHeight: the maxHeight of the widget, optional, defaults to 0x10000 :param [int,int] maxSize: the max [width,height] of the widget, optional @@ -85,43 +79,40 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): :param toolTip: This property holds the widget's tooltip :type toolTip: :class:`~TermTk.TTkCore.string.TTkString` - :param lookAndFeel: the style helper to be used for any customization - :type lookAndFeel: :class:`~TermTk.TTkTemplates.lookandfeel.TTkTTkLookAndFeel` - :param bool,optional visible: the visibility, optional, defaults to True :param bool,optional enabled: the ability to handle input events, optional, defaults to True - :param layout: the layout of this widget, optional, defaults to :class:`~TermTk.TTkLayouts.layout.TTkLayout` - :type layout: :mod:`TermTk.TTkLayouts` ''' classStyle = { - 'default': {'color': TTkColor.RST}, - 'disabled': {'color': TTkColor.fg('#888888')}, + 'default': {'color': TTkColor.RST, 'borderColor': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), 'borderColor': TTkColor.fg('#888888')}, # 'hover': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0077FF')}, # 'checked': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#00FFFF')}, # 'clicked': {'color': TTkColor.fg('#FFFF00')}, + # 'focus': {'color': TTkColor.fg('#FFFF88')}, } __slots__ = ( '_name', '_parent', '_x', '_y', '_width', '_height', - '_padt', '_padb', '_padl', '_padr', '_maxw', '_maxh', '_minw', '_minh', '_focus','_focus_policy', - '_layout', '_canvas', '_widgetItem', + '_canvas', '_widgetItem', '_visible', '_transparent', '_pendingMouseRelease', '_enabled', - '_lookAndFeel', '_style', '_currentStyle', + '_style', '_currentStyle', '_toolTip', '_widgetCursor', '_widgetCursorEnabled', '_widgetCursorType', #Signals - 'focusChanged', 'sizeChanged') + 'focusChanged', 'sizeChanged', 'currentStyleChanged', 'closed') def __init__(self, *args, **kwargs): #Signals self.focusChanged = pyTTkSignal(bool) self.sizeChanged = pyTTkSignal(int,int) + self.currentStyleChanged = pyTTkSignal(dict) + self.closed = pyTTkSignal(TTkWidget) # self.sizeChanged.connect(self.resizeEvent) self._widgetCursor = (0,0) @@ -131,10 +122,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._name = kwargs.get('name', self.__class__.__name__) self._parent = kwargs.get('parent', None ) - self._lookAndFeel = None - self.setLookAndFeel(kwargs.get('lookAndFeel', TTkLookAndFeel())) - - self._pendingMouseRelease = False self._x = kwargs.get('x', 0 ) @@ -144,12 +131,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._height = kwargs.get('height', 0 ) self._width, self._height = kwargs.get('size', (self._width, self._height)) - padding = kwargs.get('padding', 0 ) - self._padt = kwargs.get('paddingTop', padding ) - self._padb = kwargs.get('paddingBottom', padding ) - self._padl = kwargs.get('paddingLeft', padding ) - self._padr = kwargs.get('paddingRight', padding ) - self._maxw = kwargs.get('maxWidth', 0x10000) self._maxh = kwargs.get('maxHeight', 0x10000) self._maxw, self._maxh = kwargs.get('maxSize', (self._maxw, self._maxh)) @@ -157,33 +138,33 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._minh = kwargs.get('minHeight', 0x00000) self._minw, self._minh = kwargs.get('minSize', (self._minw, self._minh)) + self._focus = False + self._focus_policy = TTkK.NoFocus + self._visible = kwargs.get('visible', True) self._enabled = kwargs.get('enabled', True) - self.setStyle(self.classStyle) - self._processStyleEvent(TTkWidget._S_DEFAULT) self._toolTip = TTkString(kwargs.get('toolTip','')) - self._focus = False - self._focus_policy = TTkK.NoFocus - self._widgetItem = TTkWidgetItem(widget=self) - self._layout = TTkLayout() # root layout - self._layout.setParent(self) - self._layout.addItem(kwargs.get('layout',TTkLayout())) # main layout + self._currentStyle = TTkWidget.classStyle['default'] + self.setStyle(self.classStyle) + self._processStyleEvent(TTkWidget._S_DEFAULT) self._canvas = TTkCanvas( - widget = self, width = self._width , height = self._height ) - - if self._parent and self._parent.layout(): - self._parent.layout().addWidget(self) - self._parent.update(repaint=True, updateLayout=True) - - self.update(repaint=True, updateLayout=True) + # TODO: Check this, + # The parent should always have a layout + if self._parent: + if not hasattr(self._parent,'layout'): + TTkLog.warn(f"The parent={self._parent} is not a container") + else: + if self._parent.layout(): + self._parent.layout().addWidget(self) + self._parent.update(repaint=True, updateLayout=True) def __del__(self): ''' .. caution:: Don't touch this! ''' @@ -194,7 +175,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): # att = self.__getattribute__(an) # # TODO: TBD, I need to find the time to do this - if self._parent and self._parent.layout(): + if hasattr(self._parent,'layout') and self._parent.layout(): self._parent.layout().removeWidget(self) self._parent = None @@ -206,41 +187,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '''setName''' self._name = name - def widgetItem(self): return self._widgetItem - def addWidget(self, widget): - ''' - .. warning:: - Method Deprecated, - - use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.addWidget` - - i.e. - - .. code:: python - - parentWidget.layout().addWidget(childWidget) - ''' - TTkLog.error(".addWidget(...) is deprecated, use .layout().addWidget(...)") - if self.layout(): self.layout().addWidget(widget) - - def removeWidget(self, widget): - ''' - .. warning:: - Method Deprecated, - - use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.removeWidget` - - i.e. - - .. code:: python - - parentWidget.layout().removeWidget(childWidget) - ''' - TTkLog.error(".removeWidget(...) is deprecated, use .layout().removeWidget(...)") - if self.layout(): self.layout().removeWidget(widget) - def paintEvent(self, canvas:TTkCanvas): ''' Paint Event callback, @@ -255,39 +203,10 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): @staticmethod def _paintChildCanvas(canvas, item, geometry, offset): - ''' .. caution:: Don't touch this! ''' - lx,ly,lw,lh = geometry - ox, oy = offset - if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): - child = item.widget() - cx,cy,cw,ch = child.geometry() - canvas.paintCanvas( - child.getCanvas(), - (cx+ox, cy+oy, cw, ch), # geometry - ( 0, 0, cw, ch), # slice - ( lx, ly, lw, lh)) # bound - else: - for child in item.zSortedItems: - # The Parent Layout Geometry (lx,ly,lw,lh) include the padding of the layout - igx, igy, igw, igh = item.geometry() - iox, ioy = item.offset() - # Moved Layout to the new geometry (ix,iy,iw,ih) - ix = igx+ox # + iox - iy = igy+oy # + ioy - iw = igw # -iox - ih = igh # -ioy - # return if Child outside the bound - if ix+iw < lx and ix > lx+lw and iy+ih < ly and iy > ly+lh: continue - # Crop the Layout based on the Parent Layout Geometry - bx = max(ix,lx) - by = max(iy,ly) - bw = min(ix+iw,lx+lw)-bx - bh = min(iy+ih,ly+lh)-by - TTkWidget._paintChildCanvas(canvas, child, (bx,by,bw,bh), (ix+iox,iy+ioy)) + pass def paintChildCanvas(self): - ''' .. caution:: Don't touch this! ''' - TTkWidget._paintChildCanvas(self._canvas, self.rootLayout(), self.rootLayout().geometry(), self.rootLayout().offset()) + pass def moveEvent(self, x: int, y: int): ''' Event Callback triggered after a successful move''' @@ -343,56 +262,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.resize(w, h) self.move(x, y) - def getPadding(self) -> (int, int, int, int): - ''' Retrieve the widget padding sizes - - :return: list[top, bottom, left, right]: the 4 padding sizes - ''' - return self._padt, self._padb, self._padl, self._padr - - def setPadding(self, top: int, bottom: int, left: int, right: int): - ''' Set the padding of the widget - - :param int top: top padding - :param int bottom: bottom padding - :param int left: left padding - :param int right: right padding - ''' - if self._padt == top and self._padb == bottom and \ - self._padl == left and self._padr == right: return - self._padt = top - self._padb = bottom - self._padl = left - self._padr = right - self.update(repaint=True, updateLayout=True) - - @staticmethod - def _mouseEventLayoutHandle(evt, layout): - ''' .. caution:: Don't touch this! ''' - x, y = evt.x, evt.y - lx,ly,lw,lh =layout.geometry() - lox, loy = layout.offset() - lx,ly,lw,lh = lx+lox, ly+loy, lw-lox, lh-loy - # opt of bounds - if x=lx+lw or y=lh+ly: - return False - x-=lx - y-=ly - for item in reversed(layout.zSortedItems): - # for item in layout.zSortedItems: - if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): - widget = item.widget() - if not widget._visible: continue - wx,wy,ww,wh = widget.geometry() - # Skip the mouse event if outside this widget - if not (wx <= x < wx+ww and wy <= y < wy+wh): continue - wevt = evt.clone(pos=(x-wx, y-wy)) - if widget.mouseEvent(wevt): - return True - elif item.layoutItemType() == TTkK.LayoutItem: - levt = evt.clone(pos=(x, y)) - if TTkWidget._mouseEventLayoutHandle(levt, item): - return True + def pasteEvent(self, txt:str): return False _mouseOver = None @@ -415,9 +285,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if self.mouseDragEvent(evt): return True - if self.rootLayout() is not None: - if TTkWidget._mouseEventLayoutHandle(evt, self.rootLayout()): - return True + # if self.rootLayout() is not None: + # if TTkWidget._mouseEventLayoutHandle(evt, self.rootLayout()): + # return True # If there is an overlay and it is modal, # return False if this widget is not part of any @@ -477,10 +347,10 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): w = self while w._parent and (w.focusPolicy() & TTkK.ParentFocus) == TTkK.ParentFocus: w = w._parent + self._processStyleEvent(TTkWidget._S_PRESSED) if w.focusPolicy() & TTkK.ClickFocus == TTkK.ClickFocus: w.setFocus() w.raiseWidget() - self._processStyleEvent(TTkWidget._S_PRESSED) if evt.tap == 2 and self.mouseDoubleClickEvent(evt): #self._pendingMouseRelease = True return True @@ -497,20 +367,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return False - def setLayout(self, layout): - self._layout.replaceItem(layout, 0) - #self.layout().setParent(self) - self.update(repaint=True, updateLayout=True) - - def layout(self): - ''' Get the layout - - :return: The layout used - :rtype: :class:`TTkLayout` or derived - ''' - return self._layout.itemAt(0) - def rootLayout(self): return self._layout - def setParent(self, parent): self._parent = parent def parentWidget(self): @@ -533,19 +389,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): else: return self.maximumHeight() def maximumHeight(self): - wMaxH = self._maxh - if self.layout() is not None: - lMaxH = self.layout().maximumHeight() + self._padt + self._padb - if lMaxH < wMaxH: - return lMaxH - return wMaxH + return self._maxh def maximumWidth(self): - wMaxW = self._maxw - if self.layout() is not None: - lMaxW = self.layout().maximumWidth() + self._padl + self._padr - if lMaxW < wMaxW: - return lMaxW - return wMaxW + return self._maxw def minimumSize(self): return self.minimumWidth(), self.minimumHeight() @@ -555,19 +401,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): else: return self.minimumHeight() def minimumHeight(self): - wMinH = self._minh - if self.layout() is not None: - lMinH = self.layout().minimumHeight() + self._padt + self._padb - if lMinH > wMinH: - return lMinH - return wMinH + return self._minh def minimumWidth(self): - wMinW = self._minw - if self.layout() is not None: - lMinW = self.layout().minimumWidth() + self._padl + self._padr - if lMinW > wMinW: - return lMinW - return wMinW + return self._minw def setMaximumSize(self, maxw: int, maxh: int): self.setMaximumWidth(maxw) @@ -600,8 +436,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._visible = True self._canvas.show() self.update(updateLayout=True, updateParent=True) - for w in self.rootLayout().iterWidgets(onlyVisible=True): - w.update() @pyTTkSlot() def hide(self): @@ -631,12 +465,14 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): @pyTTkSlot() def close(self): '''close''' - if self._parent is not None and \ - self._parent.rootLayout() is not None: - self._parent.rootLayout().removeWidget(self) + if _p := self._parent: + if _rl := _p.rootLayout(): + _rl.removeWidget(self) + _p.update() TTkHelper.removeOverlayAndChild(self) self._parent = None self.hide() + self.closed.emit(self) @pyTTkSlot(bool) def setVisible(self, visible: bool): @@ -656,16 +492,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if repaint: TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) - if updateLayout and self.rootLayout() is not None: - self.rootLayout().setGeometry(0,0,self._width,self._height) - self.layout().setGeometry( - self._padl, self._padt, - self._width - self._padl - self._padr, - self._height - self._padt - self._padb) if updateParent and self._parent is not None: self._parent.update(updateLayout=True) - if updateLayout and self.rootLayout() is not None: - self.rootLayout().update() @pyTTkSlot() def setFocus(self): @@ -676,12 +504,13 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if tmp == self: return if tmp is not None: tmp.clearFocus() - TTkHelper.removeOverlayChild(self) TTkHelper.setFocus(self) self._focus = True self.focusChanged.emit(self._focus) self.focusInEvent() + TTkHelper.removeOverlayChild(self) self._pushWidgetCursor() + self._processStyleEvent(TTkWidget._S_DEFAULT) def clearFocus(self): # TTkLog.debug(f"clearFocus: {self._name} - {self._focus}") @@ -690,6 +519,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._focus = False self.focusChanged.emit(self._focus) self.focusOutEvent() + self._processStyleEvent(TTkWidget._S_DEFAULT) self.update(repaint=True, updateLayout=False) def hasFocus(self): @@ -726,17 +556,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '''setDisabled''' self.setEnabled(not disabled) - def lookAndFeel(self): - return self._lookAndFeel - - def setLookAndFeel(self, laf): - if self._lookAndFeel: - self._lookAndFeel.modified.disconnect(self.update) - if not laf: - laf = TTkLookAndFeel() - self._lookAndFeel = laf - self._lookAndFeel.modified.connect(self.update) - def toolTip(self): return self._toolTip @@ -746,9 +565,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): def getWidgetByName(self, name: str): if name == self._name: return self - for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): - if w._name == name: - return w return None _BASE_STYLE = {'default' : {'color': TTkColor.RST}} @@ -768,6 +584,12 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): def currentStyle(self): return self._currentStyle + def setCurrentStyle(self, style): + if style == self._currentStyle: return + self._currentStyle = style + self.currentStyleChanged.emit(style) + self.update() + def setStyle(self, style): if 'default' not in style: # find the closest subclass/parent holding the style @@ -784,31 +606,42 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._style = mergeStyle self._processStyleEvent(TTkWidget._S_DEFAULT) - def _processStyleEvent(self, evt): + def mergeStyle(self, style): + cs = None + for t in self._style: + if self._style[t] == self._currentStyle: + cs = t + if t in style: + self._style[t] = self._style[t] | style[t] + if cs: + self.setCurrentStyle(self._style[cs]) + + def _processStyleEvent(self, evt=_S_DEFAULT): if not self._style: return False if not self._enabled and 'disabled' in self._style: - self._currentStyle = self._style['disabled'] - self.update() + self.setCurrentStyle(self._style['disabled']) return True - self._currentStyle = self._style['default'] if evt in (TTkWidget._S_DEFAULT, TTkWidget._S_NONE, - TTkWidget._S_ACTIVE) and 'default' in self._style: - self._currentStyle = self._style['default'] - self.update() - return True + TTkWidget._S_ACTIVE): + if self.hasFocus() and 'focus' in self._style: + self.setCurrentStyle(self._style['focus']) + return True + elif 'default' in self._style: + self.setCurrentStyle(self._style['default']) + return True elif evt & TTkWidget._S_HOVER and 'hover' in self._style: - self._currentStyle = self._style['hover'] - self.update() + self.setCurrentStyle(self._style['hover']) return True elif evt & TTkWidget._S_PRESSED and 'clicked' in self._style: - self._currentStyle = self._style['clicked'] - self.update() + self.setCurrentStyle(self._style['clicked']) return True elif evt & TTkWidget._S_DISABLED and 'disabled' in self._style: - self._currentStyle = self._style['disabled'] - self.update() + self.setCurrentStyle(self._style['disabled']) + return True + if self.hasFocus() and 'focus' in self._style: + self.setCurrentStyle(self._style['focus']) return True return False diff --git a/TermTk/TTkWidgets/window.py b/TermTk/TTkWidgets/window.py index ebfa01b4..509ccc28 100644 --- a/TermTk/TTkWidgets/window.py +++ b/TermTk/TTkWidgets/window.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -22,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +__all__ = ['TTkWindow'] + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor @@ -41,8 +41,26 @@ class _MinimizedButton(TTkButton): class TTkWindow(TTkResizableFrame): '''TTkWindow''' + + _windowStyleNormal = { + 'default': {'borderColor': TTkColor.RST}, + } + + _windowStyleFocussed = { + 'default': {'borderColor': TTkColor.fg("#ffff55")}, + } + + classStyle = { + 'default': {'color': TTkColor.RST, + 'borderColor': TTkColor.RST}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor':TTkColor.fg('#888888')}, + 'focus': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#ffff55")} + } + __slots__ = ( - '_title', '_mouseDelta', '_draggable', + '_mouseDelta', '_draggable', '_btnClose', '_btnMax', '_btnMin', '_btnReduce', '_flags', '_winTopLayout', '_maxBk', '_redBk' ) @@ -83,6 +101,7 @@ class TTkWindow(TTkResizableFrame): self._winTopLayout.update() self.setWindowFlag(kwargs.get('flags', TTkK.WindowFlag.WindowCloseButtonHint)) + self.focusChanged.connect(self._focusChanged) def _maximize(self): if not (pw := self.parentWidget()): return @@ -141,17 +160,6 @@ class TTkWindow(TTkResizableFrame): self._winTopLayout.setGeometry(1,1,w-2,1) super().resizeEvent(w,h) - def paintEvent(self, canvas): - if self.hasFocus(): - color = TTkCfg.theme.windowBorderColorFocus - else: - color = TTkCfg.theme.windowBorderColor - canvas.drawText(pos=(2,1),text=self._title) - canvas.drawGrid( - color=color, - pos=(0,0), size=self.size(), - hlines=[2], grid=2) - def mousePressEvent(self, evt): self._mouseDelta = (evt.x, evt.y) self._draggable = False @@ -172,13 +180,32 @@ class TTkWindow(TTkResizableFrame): return True return TTkResizableFrame.mouseDragEvent(self, evt) - def focusInEvent(self): - if self._menubarTop: - self._menubarTop.setBorderColor(TTkColor.fg("#ffff55")) - self.update() + def _focusChanged(self, focus): + if focus: + styleToMerge = TTkWindow._windowStyleFocussed + else: + styleToMerge = TTkWindow._windowStyleNormal + + def _applyStyle(_mb): + if not _mb: return + for m in _mb._menus(TTkK.LEFT_ALIGN): m.mergeStyle(styleToMerge) + for m in _mb._menus(TTkK.RIGHT_ALIGN): m.mergeStyle(styleToMerge) + for m in _mb._menus(TTkK.CENTER_ALIGN): m.mergeStyle(styleToMerge) + + _applyStyle(self.menuBar(TTkK.TOP)) + _applyStyle(self.menuBar(TTkK.BOTTOM)) + def focusOutEvent(self): self._draggable = False - if self._menubarTop: - self._menubarTop.setBorderColor(TTkColor.RST) - self.update() + + def paintEvent(self, canvas): + style = self.currentStyle() + color = style['color'] + borderColor = style['borderColor'] + + canvas.drawText(pos=(2,1),text=self._title, color=color) + canvas.drawGrid( + color=borderColor, + pos=(0,0), size=self.size(), + hlines=[2], grid=2) \ No newline at end of file diff --git a/demo/demo.py b/demo/demo.py index 8a755738..b6c787ba 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from cgitb import text import sys, os, argparse import re import random @@ -39,7 +38,7 @@ from showcase.graph import demoGraph from showcase.splitter import demoSplitter from showcase.windows import demoWindows from showcase.windowsflags import demoWindowsFlags -from showcase.formwidgets import demoFormWidgets +from showcase.formwidgets02 import demoFormWidgets from showcase.scrollarea01 import demoScrollArea01 from showcase.scrollarea02 import demoScrollArea02 from showcase.list import demoList @@ -89,6 +88,7 @@ def stupidPythonHighlighter(txt): return txt def showSource(file): + if not file: return ttk.TTkLog.debug(f"Placeholder for the Sources - {file}") content = "Nothing" with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),file)) as f: @@ -105,9 +105,9 @@ def demoShowcase(root=None, border=True): logInput = ttk.TTkKeyPressView(visible=False, maxHeight=3, minHeight=3) root.layout().addWidget(logInput, 1, 0) - domTree = ttk.TTkFrame(title="Tom Inspector", border=True, visible=False, layout=ttk.TTkGridLayout()) - ttk.TTkTomInspector(parent=domTree) - root.layout().addWidget(domTree, 0, 1) + # domTree = ttk.TTkFrame(title="Tom Inspector", border=True, visible=False, layout=ttk.TTkGridLayout()) + # ttk.TTkTomInspector(parent=domTree) + # root.layout().addWidget(domTree, 0, 1) leftFrame = ttk.TTkFrame(parent=splitter, layout=ttk.TTkGridLayout(), border=False) @@ -116,7 +116,7 @@ def demoShowcase(root=None, border=True): logInputToggler = ttk.TTkCheckbox(text='ShowInput') logInputToggler.stateChanged.connect(lambda x: logInput.setVisible(x==ttk.TTkK.Checked)) tomTreeToggler = ttk.TTkCheckbox(text='Tom View', enabled=False) - tomTreeToggler.stateChanged.connect(lambda x: domTree.setVisible(x==ttk.TTkK.Checked)) + # tomTreeToggler.stateChanged.connect(lambda x: domTree.setVisible(x==ttk.TTkK.Checked)) mouseToggler = ttk.TTkCheckbox(text='Mouse 🐀', checked=True) mouseToggler.stateChanged.connect(lambda x: ttk.TTkTerm.push(ttk.TTkTerm.Mouse.ON if x==ttk.TTkK.Checked else ttk.TTkTerm.Mouse.OFF)) quitButton = ttk.TTkButton(text="Quit", border=True, maxHeight=3) @@ -153,79 +153,53 @@ def demoShowcase(root=None, border=True): listMenu.addItem(f"Layouts") tabLayouts = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabLayouts.addTab(demoLayout(), " Layout Test ") - tabLayouts.addTab(demoLayoutNested()," Nested Layout Test ") - tabLayouts.addTab(demoLayoutSpan(), " Layout Span Test ") - tabLayouts.addTab(demoSplitter(), " Splitter Test ") - tabLayoutsSources = [ - 'showcase/layout_basic.py', - 'showcase/layout_nested.py', - 'showcase/layout_span.py', - 'showcase/splitter.py' ] - tabLayouts.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabLayoutsSources[tabLayouts.currentIndex()])) + tabLayouts.addTab(demoLayout(), " Layout Test ", 'showcase/layout_basic.py') + tabLayouts.addTab(demoLayoutNested()," Nested Layout Test ", 'showcase/layout_nested.py') + tabLayouts.addTab(demoLayoutSpan(), " Layout Span Test ", 'showcase/layout_span.py') + tabLayouts.addTab(demoSplitter(), " Splitter Test ", 'showcase/splitter.py') + tabLayouts.addMenu("sources", ttk.TTkK.RIGHT, tabLayouts).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"MenuBar") tabMenuBar = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabMenuBar.addTab(demoMenuBar(), " MenuBar Test ") - tabMenuBarSources = [ 'showcase/menubar.py' ] - tabMenuBar.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabMenuBarSources[tabMenuBar.currentIndex()])) + tabMenuBar.addTab(demoMenuBar(), " MenuBar Test ", 'showcase/menubar.py') + tabMenuBar.addMenu("sources", ttk.TTkK.RIGHT, tabMenuBar).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"Widgets") tabWidgets = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabWidgets.addTab(demoFormWidgets(), " Form Test ") - tabWidgets.addTab(demoTextEdit(), " Text Edit ") - tabWidgets.addTab(demoList(), " List Test ") - tabWidgets.addTab(demoTree(), " Tree Test") - tabWidgets.addTab(demoTab(), " Tab Test ") - tabWidgets.addTab(demoFancyTable(), " Old Table ") - tabWidgets.addTab(demoFancyTree(), " Old Tree ") - tabWidgetsSources = [ - 'showcase/formwidgets.py', - 'showcase/textedit.py', - 'showcase/list.py', - 'showcase/tree.py', - 'showcase/tab.py', - 'showcase/fancytable.py', - 'showcase/fancytree.py' ] - tabWidgets.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabWidgetsSources[tabWidgets.currentIndex()])) + tabWidgets.addTab(demoFormWidgets(), " Form Test ", 'showcase/formwidgets02.py') + tabWidgets.addTab(demoTextEdit(), " Text Edit ", 'showcase/textedit.py') + tabWidgets.addTab(demoList(), " List Test ", 'showcase/list.py') + tabWidgets.addTab(demoTree(), " Tree Test", 'showcase/tree.py') + tabWidgets.addTab(demoTab(), " Tab Test ", 'showcase/tab.py') + tabWidgets.addTab(demoFancyTable(), " Old Table ", 'showcase/fancytable.py') + tabWidgets.addTab(demoFancyTree(), " Old Tree ", 'showcase/fancytree.py') + tabWidgets.addMenu("sources", ttk.TTkK.RIGHT, tabWidgets).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"Pickers") tabPickers = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabPickers.addTab(demoFilePicker(), " File Picker ") - tabPickers.addTab(demoColorPicker(), " Color Picker ") - tabPickersSources = [ - 'showcase/filepicker.py', - 'showcase/colorpicker.py' ] - tabPickers.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabPickersSources[tabPickers.currentIndex()])) + tabPickers.addTab(demoFilePicker(), " File Picker ", 'showcase/filepicker.py') + tabPickers.addTab(demoColorPicker(), " Color Picker ", 'showcase/colorpicker.py') + tabPickers.addMenu("sources", ttk.TTkK.RIGHT, tabPickers).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"Graphs") tabGraphs = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabGraphs.addTab(demoGraph(), " Graph Test ") - tabGraphsSources = [ 'showcase/graph.py' ] - tabGraphs.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabGraphsSources[tabGraphs.currentIndex()])) + tabGraphs.addTab(demoGraph(), " Graph Test ", 'showcase/graph.py') + tabGraphs.addMenu("sources", ttk.TTkK.RIGHT, tabGraphs).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"Windows") tabWindows = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabWindows.addTab(demoWindows(), " Windows Test ") - tabWindows.addTab(demoWindowsFlags()," Windows Flags ") - tabWindowsSources = [ 'showcase/windows.py', 'showcase/windowsflags.py' ] - tabWindows.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabWindowsSources[tabWindows.currentIndex()])) + tabWindows.addTab(demoWindows(), " Windows Test ", 'showcase/windows.py') + tabWindows.addTab(demoWindowsFlags()," Windows Flags ", 'showcase/windowsflags.py') + tabWindows.addMenu("sources", ttk.TTkK.RIGHT, tabWindows).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) listMenu.addItem(f"Extra") tabArea = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) - tabArea.addTab(demoScrollArea01()," Scroll Area 1 ") - tabArea.addTab(demoScrollArea02()," Scroll Area 2 ") - tabArea.addTab(demoDnD(), " Drag'n Drop ") - tabArea.addTab(demoDnDTabs(), " D'n D Tabs ") - tabArea.addTab(demoSigmask(), " Sigmask ") - tabAreaSources = [ - 'showcase/scrollarea01.py', - 'showcase/scrollarea02.py', - 'showcase/dragndrop.py', - 'showcase/dndtabs.py', - 'showcase/sigmask.py' ] - tabArea.addMenu("sources", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabAreaSources[tabArea.currentIndex()])) - + tabArea.addTab(demoScrollArea01()," Scroll Area 1 ", 'showcase/scrollarea01.py') + tabArea.addTab(demoScrollArea02()," Scroll Area 2 ", 'showcase/scrollarea02.py') + tabArea.addTab(demoDnD(), " Drag'n Drop ", 'showcase/dragndrop.py') + tabArea.addTab(demoDnDTabs(), " D'n D Tabs ", 'showcase/dndtabs.py') + tabArea.addTab(demoSigmask(), " Sigmask ", 'showcase/sigmask.py') + tabArea.addMenu("sources", ttk.TTkK.RIGHT, tabArea).menuButtonClicked.connect(lambda _menuButton : showSource(_menuButton.data().currentData())) @ttk.pyTTkSlot(str) def _listCallback(label): diff --git a/demo/showcase/formwidgets.py b/demo/showcase/formwidgets01.py similarity index 100% rename from demo/showcase/formwidgets.py rename to demo/showcase/formwidgets01.py diff --git a/demo/showcase/formwidgets02.py b/demo/showcase/formwidgets02.py new file mode 100755 index 00000000..ab99cab8 --- /dev/null +++ b/demo/showcase/formwidgets02.py @@ -0,0 +1,152 @@ +#!/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 sys, os, argparse +import random + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfSentence + +def demoFormWidgets(root=None): + win_form1_grid_layout = ttk.TTkGridLayout(columnMinWidth=1) + frame = ttk.TTkFrame(parent=root, layout=win_form1_grid_layout, border=0) + + win_form1_grid_layout.addWidget(_b1 := ttk.TTkButton(text='Button 1'),0,0) + win_form1_grid_layout.addWidget(_b2 := ttk.TTkButton(text='Button 2', border=True),1,0) + win_form1_grid_layout.addWidget(_b3 := ttk.TTkButton(text='Checkable 1', checkable=True, maxHeight=3),0,2) + win_form1_grid_layout.addWidget(_b4 := ttk.TTkButton(text='Checkable 2', checkable=True, border=True, maxHeight=3),1,2) + + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True, maxWidth=11),1,3) + _en_dis_cb.clicked.connect(_b1.setEnabled) + _en_dis_cb.clicked.connect(_b2.setEnabled) + _en_dis_cb.clicked.connect(_b3.setEnabled) + _en_dis_cb.clicked.connect(_b4.setEnabled) + + row = 2 + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkComboBox(list=['One','Two','Some Long Sentence That Is Not a Written Number','Three']),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo long Box'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(100)]),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box Edit. Bottom Insert'),row,0) + win_form1_grid_layout.addWidget(comboEdit1 := ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(10)]),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(comboEdit1.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box Edit. Top Insert'),row,0) + win_form1_grid_layout.addWidget(comboEdit2 := ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(10)]),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(comboEdit2.setEnabled) + + comboEdit1.setEditable(True) + comboEdit2.setEditable(True) + comboEdit2.setInsertPolicy(ttk.TTkK.InsertAtTop) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 1'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Line Edit Test 1'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 2'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Line Edit Test 2 😎 -'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 3'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Line Edit Test 3 oަ -'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Number'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Wrong Number'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Password'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Number Password'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password+ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Spinbox (default [0,99])'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkSpinBox(),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Spinbox (-20, [-50,+50])'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkSpinBox(value=-20, maximum=50, minimum=-50),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Checkbox'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkCheckbox(text='CheckBox 1'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Checkbox Checked'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkCheckbox(text='CheckBox 2', checked=True),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Checkbox Tristate'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkCheckbox(text='CheckBox 3', checkStatus=ttk.TTkK.PartiallyChecked, tristate=True),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Default)'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton 1'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Default)'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton 2'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Default)'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton 3'),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Name One) 1'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton A', radiogroup="Name One"),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Name One) 1'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton B', radiogroup="Name One"),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Name Two) 2'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton x',radiogroup="Name Two"),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Name One) 1'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton C', radiogroup="Name One"),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Radio Button (Name Two) 2'),row,0) + win_form1_grid_layout.addWidget(_wid := ttk.TTkRadioButton(text='RadioButton y',radiogroup="Name Two"),row,2) + win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) + row += 1; win_form1_grid_layout.addWidget(ttk.TTkSpacer(),row,0) + return frame + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen', action='store_true') + args = parser.parse_args() + + root = ttk.TTk() + if args.f: + rootForm = root + root.setLayout(ttk.TTkGridLayout()) + else: + rootForm = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test List", border=True, layout=ttk.TTkGridLayout()) + demoFormWidgets(rootForm) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/demo/showcase/list.py b/demo/showcase/list.py index 01de91c0..c72b060d 100755 --- a/demo/showcase/list.py +++ b/demo/showcase/list.py @@ -74,16 +74,20 @@ def demoList(root= None): def main(): parser = argparse.ArgumentParser() - parser.add_argument('-f', help='Full Screen', action='store_true') + parser.add_argument('-f', help='Full Screen (default)', action='store_true') + parser.add_argument('-w', help='Windowed', action='store_true') + parser.add_argument('-t', help='Track Mouse', action='store_true') args = parser.parse_args() + windowed = args.w + mouseTrack = args.t - root = ttk.TTk() - if args.f: - rootGraph = root - root.setLayout(ttk.TTkGridLayout()) + root = ttk.TTk(title="pyTermTk List Demo", mouseTrack=mouseTrack) + if windowed: + rootTab = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test Tab", border=True, layout=ttk.TTkGridLayout()) else: - rootGraph = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test List", border=True, layout=ttk.TTkGridLayout()) - demoList(rootGraph) + rootTab = root + root.setLayout(ttk.TTkGridLayout()) + demoList(rootTab) root.mainloop() if __name__ == "__main__": diff --git a/demo/showcase/menubar.py b/demo/showcase/menubar.py index efeb3801..807901dd 100755 --- a/demo/showcase/menubar.py +++ b/demo/showcase/menubar.py @@ -99,16 +99,22 @@ def demoMenuBar(root=None): def main(): parser = argparse.ArgumentParser() - parser.add_argument('-f', help='Full Screen', action='store_true') + parser.add_argument('-f', help='Full Screen (default)', action='store_true') + parser.add_argument('-w', help='Windowed', action='store_true') + parser.add_argument('-t', help='Track Mouse', action='store_true') args = parser.parse_args() + windowed = args.w + mouseTrack = args.t - root = ttk.TTk() - if args.f: - rootLayout = root - root.setLayout(ttk.TTkGridLayout()) + root = ttk.TTk(title="pyTermTk MenuiBar Demo", mouseTrack=mouseTrack) + if windowed: + rootTab = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test Tab", border=True, layout=ttk.TTkGridLayout()) + border=True else: - rootLayout = ttk.TTkWindow(title="Test MenuBar", parent=root,pos=(1,1), size=(100,40), border=True, layout=ttk.TTkGridLayout()) - demoMenuBar(rootLayout) + rootTab = root + root.setLayout(ttk.TTkGridLayout()) + border=False + demoMenuBar(rootTab) root.mainloop() if __name__ == "__main__": diff --git a/demo/showcase/tab.py b/demo/showcase/tab.py index a3c53424..f042671b 100755 --- a/demo/showcase/tab.py +++ b/demo/showcase/tab.py @@ -57,17 +57,21 @@ def demoTab(root=None, border=True): def main(): parser = argparse.ArgumentParser() - parser.add_argument('-f', help='Full Screen', action='store_true') + parser.add_argument('-f', help='Full Screen (default)', action='store_true') + parser.add_argument('-w', help='Windowed', action='store_true') + parser.add_argument('-t', help='Track Mouse', action='store_true') args = parser.parse_args() + windowed = args.w + mouseTrack = args.t - root = ttk.TTk() - if args.f: + root = ttk.TTk(title="pyTermTk Tab Demo", mouseTrack=mouseTrack) + if windowed: + rootTab = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test Tab", border=True, layout=ttk.TTkGridLayout()) + border=True + else: rootTab = root root.setLayout(ttk.TTkGridLayout()) border=False - else: - rootTab = ttk.TTkWindow(parent=root,pos=(1,1), size=(100,40), title="Test Tab", border=True, layout=ttk.TTkGridLayout()) - border=True demoTab(rootTab, border) root.mainloop() diff --git a/demo/ttkode.py b/demo/ttkode.py index e75fa81b..e20ec393 100755 --- a/demo/ttkode.py +++ b/demo/ttkode.py @@ -85,6 +85,17 @@ def main(): kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#888888", modifier=TTkColorGradient(increment=-6)), title="sette"),"sette") kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#444444", modifier=TTkColorGradient(increment= 3)), title="otto"),"otto") + m1 = kt.addMenu('Test1') + m2 = kt.addMenu('Test2') + + m1.addMenu("Open",checkable=True) + m1.addMenu("Save",checkable=True,checked=True) + m1.addMenu("Save as").setDisabled() + + m2.addMenu("m2 Open",checkable=True) + m2.addMenu("m2 Save",checkable=True,checked=True) + m2.addMenu("m2 Save as").setDisabled() + fileTree.fileActivated.connect(lambda item:kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#888888", modifier=TTkColorGradient(increment=-6)), title=item.path()),"File") ) diff --git a/tests/pytest/mock_input.py b/tests/pytest/mock_input.py index c59c2da4..0f64eefa 100644 --- a/tests/pytest/mock_input.py +++ b/tests/pytest/mock_input.py @@ -29,8 +29,15 @@ class Mock_TTkInput(): def cont(self): pass def get_key(self, callback=None): pass def start(self): pass + class inputEvent(): def connect(*args): pass def clear(): pass + + class pasteEvent(): + def connect(*args): + pass + def clear(): + pass \ No newline at end of file diff --git a/tests/pytest/test_003_string.py b/tests/pytest/test_003_string.py index 09fcd137..f784bab1 100644 --- a/tests/pytest/test_003_string.py +++ b/tests/pytest/test_003_string.py @@ -26,7 +26,6 @@ import sys, os sys.path.append(os.path.join(sys.path[0],'../..')) import TermTk -TermTk.TTk() # in order to have TTkCfg.theme def test_stringAlign1(): test1 = TermTk.TTkString('Yes\u231b\u231b\u231b') # 'Yes⌛⌛⌛' diff --git a/tests/stress/02.many.tree.nodes.py b/tests/stress/02.many.tree.nodes.py new file mode 100755 index 00000000..bf0d97dd --- /dev/null +++ b/tests/stress/02.many.tree.nodes.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os, argparse, math, random + +sys.path.append(os.path.join(sys.path[0],'../..')) + +import TermTk as ttk + +class _OnOff(ttk.TTkButton): + classStyle = ttk.TTkButton.classStyle | { + 'checked': {'color': ttk.TTkColor.fg("#00FF00")+ttk.TTkColor.bg("#444400"), + 'borderColor': ttk.TTkColor.fg("#FFFFFF"), + 'text': 'On', + 'grid':0}, + 'unchecked': {'color': ttk.TTkColor.fg("#FF0000")+ttk.TTkColor.bg("#440000"), + 'borderColor': ttk.TTkColor.RST, + 'text': 'Off', + 'grid':3}, + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def text(self): + if self._checked: + return self.style()['checked']['text'] + else: + return self.style()['unchecked']['text'] + + def paintEvent(self, canvas): + if self._checked: + style = self.style()['checked'] + else: + style = self.style()['unchecked'] + + borderColor = style['borderColor'] + textColor = style['color'] + text = style['text'] + + w,h = self.size() + + # Draw the border and bgcolor + canvas.drawChar(pos=(0 ,0), color=borderColor ,char='[') + canvas.drawChar(pos=(w-1,0), color=borderColor ,char=']') + canvas.drawText(pos=(1,0) ,text=text, color=textColor, width=w-2) + + + +ttk.TTkLog.use_default_file_logging() + +root = ttk.TTk(layout=ttk.TTkGridLayout()) + +root.layout().addWidget(btnadd:=ttk.TTkButton(text="Add Nodes",border=True,maxHeight=3),0,0) +root.layout().addWidget(btndel:=ttk.TTkButton(text="Clean",border=True,maxHeight=3),0,1) +root.layout().addWidget(tw:=ttk.TTkTree(),1,0,1,2) +root.layout().addWidget(ttk.TTkLogViewer(maxHeight=10),2,0,1,2) + +tw.setHeaderLabels( ["Name" , "Console" , "Fatal" , "Error" , "Mil" , "Warning" , "Info" , "Entry" , "Exit"]) +tw._treeView._columnsPos = [ _a:=50 , _b:=_a+10 , _b+6 , _b+12 , _b+18 , _b+24 , _b+30 , _b+36 , _b+42] +types = ("Fatal", "Error", "Mil", "Warning", "Info", "Entry", "Exit") + +def _addMany(): + ttk.TTkLog.info("Start Stress!!!") + items = [] + for i in range(500): + _item_elements = [f"n: {i=}", 'console'] + for _t in types: + _btn_t = _OnOff(checkable=True, checked=(i%0x07==0)) + _item_elements.append(_btn_t) + # _item_elements.append(_t) + _item = ttk.TTkTreeWidgetItem(_item_elements) + items.append(_item) + tw.addTopLevelItems(items) + ttk.TTkLog.info("End Stress!!!") + +btnadd.clicked.connect(_addMany) +btndel.clicked.connect(tw.clear) + +root.mainloop() diff --git a/tests/test.input.py b/tests/test.input.py index 10e1d41b..12c72d14 100755 --- a/tests/test.input.py +++ b/tests/test.input.py @@ -46,6 +46,7 @@ TTkLog.info("Press q or to exit") TTkTerm.push(TTkTerm.Mouse.ON) TTkTerm.push(TTkTerm.Mouse.DIRECT_ON) +TTkTerm.push(TTkTerm.SET_BRACKETED_PM) TTkTerm.setEcho(False) def winCallback(width, height): @@ -68,7 +69,12 @@ def keyCallback(kevt=None, mevt=None): return False return True +def pasteCallback(txt:str): + TTkLog.info(f"PASTE = {txt}") + return True + input.inputEvent.connect(keyCallback) +input.pasteEvent.connect(pasteCallback) try: input.start() diff --git a/tests/test.input.raw.py b/tests/test.input.raw.py index bc362e6a..e74f4cf7 100755 --- a/tests/test.input.raw.py +++ b/tests/test.input.raw.py @@ -22,7 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import sys, os, select +import sys, os, select, signal import logging try: import fcntl, termios, tty @@ -31,12 +31,29 @@ except Exception as e: exit(1) sys.path.append(os.path.join(sys.path[0],'..')) -from TermTk import TTkLog, TTkK, TTkInput, TTkTerm +from TermTk import TTkTerm -TTkLog.info("Retrieve Keyboard, Mouse press/drag/wheel Events") -TTkLog.info("Press q or to exit") +print("Retrieve Keyboard, Mouse press/drag/wheel Events") +print("Press q or to exit") -TTkTerm.push(TTkTerm.Mouse.ON) +def reset(): + # Reset + TTkTerm.push("\033[?1000l") + TTkTerm.push("\033[?1002l") + TTkTerm.push("\033[?1015l") + TTkTerm.push("\033[?1006l") + TTkTerm.push("\033[?1049l") # Switch to normal screen + TTkTerm.push("\033[?2004l") # Paste Bracketed mode + +reset() + +TTkTerm.push("\033[?2004h") # Paste Bracketed mode +# TTkTerm.push("\033[?1000h") +# TTkTerm.push("\033[?1002h") +# TTkTerm.push("\033[?1006h") +# TTkTerm.push("\033[?1015h") +TTkTerm.push("\033[?1049h") # Switch to alternate screen +# TTkTerm.push(TTkTerm.Mouse.ON) # TTkTerm.push(TTkTerm.Mouse.DIRECT_ON) TTkTerm.setEcho(False) @@ -44,6 +61,12 @@ TTkTerm.setEcho(False) _attr = termios.tcgetattr(sys.stdin) tty.setcbreak(sys.stdin) +# Capture Terminal Resize: +def _sigwinch(a,b): + print(f"SIGWINCH: {os.get_terminal_size()=} {a=} {b=}") + +signal.signal(signal.SIGWINCH, _sigwinch) + def read(): rlist, _, _ = select.select( [sys.stdin], [], [] ) @@ -63,22 +86,29 @@ def read_new(): while rlist := select.select( [sys.stdin], [], [] )[0]: _fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL) fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl | os.O_NONBLOCK) # Set the input as NONBLOCK to read the full sequence - stdinRead = sys.stdin.read() + stdinRead = sys.stdin.buffer.read() fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl) + try: + stdinRead = stdinRead.decode() + except Exception as e: + yield f"bin: {stdinRead}" + continue print(f"{len(stdinRead)=}") if '\033' in stdinRead: stdinSplit = stdinRead.split('\033') for ansi in stdinSplit[1:]: print(f"{ansi=}") - yield '\033'+ansi + yield ''+ansi else: for ch in stdinRead: yield ch - +# print("--->\033[?1h<---") try: for stdinRead in read_new(): print(f"{stdinRead=}") finally: + # Reset + reset() termios.tcsetattr(sys.stdin, termios.TCSANOW, _attr) TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF) TTkTerm.setEcho(True) diff --git a/tests/test.input.win.py b/tests/test.input.win.py new file mode 100755 index 00000000..5e835751 --- /dev/null +++ b/tests/test.input.win.py @@ -0,0 +1,57 @@ +#!/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 sys, os +import logging + +sys.path.append(os.path.join(sys.path[0],'..')) +from TermTk import TTkLog, TTkK, TTkGridLayout, TTk, TTkLogViewer, TTkHelper + +def keyCallback(kevt=None, mevt=None): + if mevt is not None: + TTkLog.info(f"Mouse Event: {mevt}") + if kevt is not None: + if kevt.type == TTkK.Character: + TTkLog.info(f"Key Event: char '{kevt.key}' {kevt}") + else: + TTkLog.info(f"Key Event: Special '{kevt}'") + if kevt.key == "q": + input.close() + return False + return True + +def pasteCallback(txt:str): + TTkLog.info(f"PASTE:") + for s in txt.split('\n'): + TTkLog.info(f" | {s}") + return True + +root = TTk(layout=TTkGridLayout()) + +TTkLogViewer(parent=root) + +TTkHelper._rootWidget._input.inputEvent.connect(keyCallback) +TTkHelper._rootWidget._input.pasteEvent.connect(pasteCallback) + +root.mainloop() diff --git a/tests/test.ui.006.scroll.py b/tests/test.ui.006.scroll.01.py similarity index 100% rename from tests/test.ui.006.scroll.py rename to tests/test.ui.006.scroll.01.py diff --git a/TermTk/TTkTemplates/lookandfeel.py b/tests/test.ui.006.scroll.02.tryLoop.py old mode 100644 new mode 100755 similarity index 59% rename from TermTk/TTkTemplates/lookandfeel.py rename to tests/test.ui.006.scroll.02.tryLoop.py index 8bd49eae..c624849b --- a/TermTk/TTkTemplates/lookandfeel.py +++ b/tests/test.ui.006.scroll.02.tryLoop.py @@ -1,6 +1,8 @@ +#!/usr/bin/env python3 + # MIT License # -# Copyright (c) 2022 Eugenio Parodi +# 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 @@ -20,9 +22,24 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +import sys, os + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +root = ttk.TTk() + +sb1 = ttk.TTkScrollBar(parent=root, pos=(10,2), size=(1,10)) +sb2 = ttk.TTkScrollBar(parent=root, pos=(12,2), size=(1,12)) +sb3 = ttk.TTkScrollBar(parent=root, pos=(14,2), size=(1,14)) +sb4 = ttk.TTkScrollBar(parent=root, pos=(16,2), size=(1,16)) +sb5 = ttk.TTkScrollBar(parent=root, pos=( 5,0), size=(30,1), orientation=ttk.TTkK.HORIZONTAL) + +sb1.valueChanged.connect(sb2.setValue) +sb2.valueChanged.connect(sb3.setValue) +sb3.valueChanged.connect(sb4.setValue) +sb4.valueChanged.connect(sb5.setValue) +sb5.valueChanged.connect(sb1.setValue) + -class TTkLookAndFeel(): - __slots__ = ('modified') - def __init__(self, *args, **kwargs): - self.modified = pyTTkSignal() +root.mainloop() \ No newline at end of file diff --git a/tests/test.ui.022.progressbar.py b/tests/test.ui.022.progressbar.py index 398f7662..d1bee8b3 100755 --- a/tests/test.ui.022.progressbar.py +++ b/tests/test.ui.022.progressbar.py @@ -44,11 +44,11 @@ def main(): parent=root,pos=(1,1), size=(55,15), border=True, layout=layout, title="Test Progressbar (resize me)") - class TextLAF(ttk.TTkLookAndFeelPBar): + class TextLAF(ttk.TTkLookAndFeelFPBar): def text(self, value, max, min): return 'low' if value < 0.5 else 'high' - class ColorLAF(ttk.TTkLookAndFeelPBar): + class ColorLAF(ttk.TTkLookAndFeelFPBar): def color(self, value,max,min): red, green = round(value*255), round((1-value)*255) fg = f"#{red:02x}{green:02x}00" @@ -57,12 +57,12 @@ def main(): claf = ColorLAF(textWidth=5) - rootW.layout().addWidget(pb1 := ttk.TTkProgressBar(), row=0, col=0) - rootW.layout().addWidget(pb2 := ttk.TTkProgressBar(lookAndFeel=ColorLAF(showText=False)), row=2, col=0) - rootW.layout().addWidget(pb3 := ttk.TTkProgressBar(lookAndFeel=TextLAF(textWidth=6)), row=3, col=0) + rootW.layout().addWidget(pb1 := ttk.TTkFancyProgressBar(), row=0, col=0) + rootW.layout().addWidget(pb2 := ttk.TTkFancyProgressBar(lookAndFeel=ColorLAF(showText=False)), row=2, col=0) + rootW.layout().addWidget(pb3 := ttk.TTkFancyProgressBar(lookAndFeel=TextLAF(textWidth=6)), row=3, col=0) - rootW.layout().addWidget(pb4 := ttk.TTkProgressBar(lookAndFeel=claf), row=4, col=0) - rootW.layout().addWidget(pb5 := ttk.TTkProgressBar(lookAndFeel=claf, value=0.5), row=5, col=0) + rootW.layout().addWidget(pb4 := ttk.TTkFancyProgressBar(lookAndFeel=claf), row=4, col=0) + rootW.layout().addWidget(pb5 := ttk.TTkFancyProgressBar(lookAndFeel=claf, value=0.5), row=5, col=0) rootW.layout().addWidget(cbt := ttk.TTkCheckbox(text=" - Rem/Add Text", checked=True), row=6, col=0) diff --git a/tests/timeit/15.functools.01.reduce.py b/tests/timeit/15.functools.01.reduce.py new file mode 100755 index 00000000..127776f4 --- /dev/null +++ b/tests/timeit/15.functools.01.reduce.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os + +import timeit +import random +from functools import reduce + +txt = "akjhakjhakjhakjhakjhakjahkjahkjahakjhalkhjalkahjlkahjadd" +txt += "1111111111111111111111111111111111111111111111111111111" +txt += "skljhdlhjdlkhjslkfhjdsljhfdlkjshfldkjshdflkjhfdsljhdslj" +txt += "skljhdlhjdlkhjslkfhjdsljhfdlkjshfldkjshdflkjhfdsljhdslj" +txt += "skljhdlhjdlkhjslkfhjdsljhfdlkjshfldkjshdflkjhfdsljhdslj" +txt += "skljhdlhjdlkhjslkfhjdsljhfdlkjshfldkjshdflkjhfdsljhdslj" + +def test1(t=txt): + ret = 0 + cbk = None + for ch in t: + if ch == cbk: + ret += 1 + cbk = ch + return ret + +def _fr2(val, it): + if val[1] == it: + val[0] += 1 + val[1] = it + return val + +def test2(t=txt): + return reduce(_fr2, t, [0,None])[0] + +_fr3Ret = 0 +def _fr3(val, it): + global _fr3Ret + if _fr3Ret == it: + val += 1 + _fr3Ret = it + return val + +def test3(t=txt): + global _fr3Ret + _fr3Ret = None + return reduce(_fr3, t, 0) + +def test4(t=txt): + val = [0,None] + for ch in t: + val = _fr2(val, ch) + return val[0] + +def test5(t=txt): + global _fr3Ret + _fr3Ret = None + val = 0 + for ch in t: + val = _fr3(val, ch) + return val + +def test6(t=txt): + it = iter(t) + ret = 0 + cbk = None + for ch in it: + if ch == cbk: + ret += 1 + cbk = ch + return ret + +# def test7(): return 7 +# def test8(): return 8 +# def test9(): return 9 + +loop = 10000 + +a={} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 + diff --git a/tests/timeit/16.event.vs.set.py b/tests/timeit/16.event.vs.set.py new file mode 100755 index 00000000..2d90616d --- /dev/null +++ b/tests/timeit/16.event.vs.set.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os + +import timeit +import threading, random +from functools import reduce + +data = [random.randint(0,10) for _ in range(500)] +pe = threading.Event() +pe.set() + +def test1(d=data, p=pe): + s = set() + for x in d: + s.add(x) + p.set() + return sum(s) + +def test2(d=data, p=pe): + s = set() + for x in d: + if x not in s: + p.set() + s.add(x) + return sum(s) + +def test3(d=data, p=pe): + s = set() + for x in d: + if x not in s: + s.add(x) + p.set() + return sum(s) + +def test4(d=data, p=pe): + s = set() + for x in d: + if x not in s: + s.add(x) + return sum(s) + +def test5(d=data, p=pe): + s = set() + for x in d: + s.add(x) + return sum(s) + +loop = 10000 + +a={} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 + diff --git a/tests/timeit/21.weakref.01.py b/tests/timeit/21.weakref.01.py new file mode 100755 index 00000000..4eb4b1e5 --- /dev/null +++ b/tests/timeit/21.weakref.01.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os, weakref + +import timeit +from threading import Lock + +class Foo(): + def a(self,v): + return v+v*v + +f1 = Foo() +f21 = Foo() +f22 = Foo() + +a1 = f1.a +a21 = weakref.WeakMethod(f21.a) +a22 = weakref.WeakMethod(f22.a) +a31 = weakref.ref(_a31:=f21.a) +a32 = weakref.ref(_a32:=f22.a) + +del f22,_a32 + +def test1(v=a1,ff=f1): return sum([ v(x) for x in range(100)]) + +def test2(v=a21,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)]) +def test3(v=a22,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)]) +def test4(v=a21,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)]) +def test5(v=a22,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)]) + +def test6(v=a31,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)]) +def test7(v=a32,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)]) +def test8(v=a31,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)]) +def test9(v=a32,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)]) + +loop = 10000 + +a = {} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 + diff --git a/tests/timeit/22.queue.01.py b/tests/timeit/22.queue.01.py new file mode 100755 index 00000000..0406005c --- /dev/null +++ b/tests/timeit/22.queue.01.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os, weakref + +import timeit +from queue import Queue + +qu = Queue() +r = 100 + +def test1(q=qu): + ret = 0 + for i in range(r): + ret += i + return ret + +def test2(q=qu): + ret = 0 + for i in range(r): + qu.put(i) + while x := q.get(): + ret += x + return ret + +def test3(q=qu): + ret = 0 + ar = [] + for i in range(r): + ar.append(i) + for x in ar: + ret += x + return ret + +def test4(q=qu): + return sum(i for i in range(r)) + +def test5(q=qu): + ar = [] + for i in range(r): + ar.append(i) + return sum(ar) + +def test6(q=qu): + ret = 0 + ar = [] + for i in range(r): + ar.append(i) + while ar: + ret += ar.pop() + return ret + +loop = 1000 + +a = {} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 + diff --git a/tests/test.generic.006.weakref.01.py b/tests/weakref/test.01.py similarity index 100% rename from tests/test.generic.006.weakref.01.py rename to tests/weakref/test.01.py diff --git a/tests/test.generic.006.weakref.02.py b/tests/weakref/test.02.py similarity index 100% rename from tests/test.generic.006.weakref.02.py rename to tests/weakref/test.02.py diff --git a/tests/test.generic.006.weakref.03.py b/tests/weakref/test.03.py similarity index 100% rename from tests/test.generic.006.weakref.03.py rename to tests/weakref/test.03.py diff --git a/tests/weakref/test.04.gc.01.py b/tests/weakref/test.04.gc.01.py new file mode 100755 index 00000000..d41e6883 --- /dev/null +++ b/tests/weakref/test.04.gc.01.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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. + +# Example inspired by +# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find + +import gc, weakref + +class Foo(object): + __slots__ = ('a','b') + def __init__(self, a=1234) -> None: + self.a = a + self.b = lambda : self.a + + def f(self): + return self.a + + # def __del__(self): + # print(f"Deleted {self}") + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +v1 = {'b':2345} + +print(f"\nStart {gc.isenabled()=}") +# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}") + +print("\n############# Phase 1 ##################") +foo = Foo(v1) +print(f"{gc.get_referents(foo)=}") +print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=}") +del foo +print(f"{gc.collect()=}") + +print("\n############# Phase 2 ##################") +foo = Foo(v1) +bar = foo.a +print(f"{gc.get_referents(foo)=}") +print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar=}") +del foo +print(f"{gc.collect()=}") +print(f"{bar=}") + +print("\n############# Phase 3 ##################") +foo = Foo(v1) +bar = foo.b +print(f"{gc.get_referents(foo)=}") +print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}") +del foo +print(f"{gc.collect()=}") +print(f"{bar()=}") + +print("\n############# Phase 4 ##################") +foo = Foo(v1) +bar = foo.b +print(f"{gc.get_referents(foo)=}") +print(f"{gc.get_referents(v1)=}") +print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}") +del foo +pobjs() +print(f"{gc.collect()=}") +print(f"{bar()=}") +del bar +pobjs() +print(f"{gc.collect()=}") +pobjs() + +print("\n############# Phase 5 ##################") +foo = Foo(v1) +bar = weakref.ref(foo.b) +xx = foo.f +baz = weakref.ref(xx) +print(f"{gc.get_referents(foo)=}") +print(f"{gc.get_referents(v1)=}") +print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()()=}") +del foo +pobjs() +print(f"{gc.collect()=}") +print(f"{bar()() if bar() else None=}") +del bar +pobjs() +print(f"{gc.collect()=}") +pobjs() + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tests/weakref/test.04.gc.02.py b/tests/weakref/test.04.gc.02.py new file mode 100755 index 00000000..9131f429 --- /dev/null +++ b/tests/weakref/test.04.gc.02.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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. + +# Example inspired by +# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find + +import gc, weakref, time + +class Foo(): + __slots__ = ('__weakref__','a','b') + def __init__(self, a=1234) -> None: + self.a = a + self.b = lambda : self.a + + def f(self): + return self.a + + # def __del__(self): + # print(f"Deleted {self}") + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +v1 = {'b':2345} + +print(f"\nStart {gc.isenabled()=}") +# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}") + +def _gccb(phase,info): + print(f" ---> {gc.garbage=}") + print(f" ---> {phase=} {info=}") + +# gc.callbacks.append(_gccb) + +print("\n############# Phase 1 ##################") +foo = Foo(v1) +bar =foo.b + +wrfoo = weakref.ref(foo) +wrbar = weakref.ref(bar) +wrf = weakref.WeakMethod(foo.f) + +# print(f"{gc.get_referents(foo)=}") +# print(f"{gc.get_referrers(foo)=}") +# print(f"{gc.get_referents(v1)=}") +# print(f"{gc.get_referrers(v1)=}") +# print(f"{gc.get_count()=}") +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +del foo +print(f"{gc.collect()=}") +print(f"{bar()}") +# print(f"{gc.get_referents(v1)=}") +# print(f"{gc.get_referrers(v1)=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +bar = None +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +time.sleep(4) +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +print(f"{gc.collect()=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tests/weakref/test.04.gc.03.py b/tests/weakref/test.04.gc.03.py new file mode 100755 index 00000000..e678cb66 --- /dev/null +++ b/tests/weakref/test.04.gc.03.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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. + +# Example inspired by +# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find + +import gc, weakref, time + +class Bar(): + __slots__ = ('_foo') + def __init__(self, foo) -> None: + self._foo = foo + +class Foo(): + __slots__ = ('__weakref__','a','b','_bar') + def __init__(self, a=1234) -> None: + self._bar = Bar(self) + self.a = a + self.b = lambda : self.a + + def f(self): + return self.a + + # def __del__(self): + # print(f"Deleted {self}") + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +def _ref(o): + print(f"\n### -> Referents - {o}") + for i,r in enumerate(gc.get_referents(o)): + print(f" - {i} ) {r}") + print(f"\n### -> Referrers - {o}") + for i,r in enumerate(gc.get_referrers(o)): + print(f" - {i} ) {r}") + print("") + +v1 = {'b':2345} + +print(f"\nStart {gc.isenabled()=}") +# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}") + +def _gccb(phase,info): + print(f" ---> {gc.garbage=}") + print(f" ---> {phase=} {info=}") + +# gc.callbacks.append(_gccb) + +print("\n############# Phase 1 ##################") +foo = Foo(v1) +bar =foo.b + +wrfoo = weakref.ref(foo) +wrbar = weakref.ref(bar) +wrf = weakref.WeakMethod(foo.f) + +# print(f"{gc.get_referents(foo)=}") +# print(f"{gc.get_referrers(foo)=}") +# print(f"{gc.get_referents(v1)=}") +# print(f"{gc.get_referrers(v1)=}") +# print(f"{gc.get_count()=}") +_ref(foo) + +print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +del foo +print(f"{gc.collect()=}") +print(f"{bar()}") +# print(f"{gc.get_referents(v1)=}") +# print(f"{gc.get_referrers(v1)=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +bar = None +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +time.sleep(4) +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") +print(f"{gc.collect()=}") +print(f"{wrfoo()=} {wrbar()=} {wrf()=}") + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tests/weakref/test.05.TermTk.01.py b/tests/weakref/test.05.TermTk.01.py new file mode 100755 index 00000000..e924dd3f --- /dev/null +++ b/tests/weakref/test.05.TermTk.01.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os +import gc, weakref, time + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +print(f"\nStart {gc.isenabled()=}") + +def _gccb(phase,info): + print(f" ---> {gc.garbage=}") + print(f" ---> {phase=} {info=}") +# gc.callbacks.append(_gccb) + +def _ref(o): + print(f"\n### -> Referents - {o}") + for i,r in enumerate(gc.get_referents(o)): + print(f" - {i} ) {r}") + print(f"\n### -> Referrers - {o}") + for i,r in enumerate(gc.get_referrers(o)): + print(f" - {i} ) {r}") + for ii,rr in enumerate(gc.get_referrers(r)): + print(f" | {ii} ) {rr}") + print("") + print("") + +print("\n############# Phase 1 ##################") +# wid = ttk.TTkWidget() +wid = ttk.TTkButton() +# wid = ttk.TTkLabel() +# wid = ttk.TTkGraph() +# wid = ttk.TTkSpacer() +# wid = ttk.TTkSplitter() +# wid = ttk.TTkCanvas() +# sizef = wid.size +sizef = [] + +ttk.TTkHelper._updateWidget = set() +ttk.TTkHelper._updateBuffer = set() + +wrwid = weakref.ref(wid) +# wrsizef = weakref.ref(sizef) +wrsizef = wrwid +# wrsizef2 = weakref.WeakMethod(wid.size) +wrsizef2 = wrwid + +_ref(wid) + +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +del wid +print(f"{gc.collect()=}") +# print(f"{sizef()}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +sizef = None +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +# time.sleep(4) +# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +print(f"{gc.collect()=}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tests/weakref/test.05.TermTk.02.py b/tests/weakref/test.05.TermTk.02.py new file mode 100755 index 00000000..e12812ad --- /dev/null +++ b/tests/weakref/test.05.TermTk.02.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os +import gc, weakref, time + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +print(f"\nStart {gc.isenabled()=}") + +def _gccb(phase,info): + print(f" ---> {gc.garbage=}") + print(f" ---> {phase=} {info=}") +# gc.callbacks.append(_gccb) + +def _ref(o): + print(f"\n### -> Referents - {o}") + for i,r in enumerate(gc.get_referents(o)): + print(f" - {i} ) {r}") + print(f"\n### -> Referrers - {o}") + for i,r in enumerate(gc.get_referrers(o)): + print(f" - {i} ) {r}") + for ii,rr in enumerate(gc.get_referrers(r)): + print(f" | {ii} ) {rr}") + print("") + print("") + + +class TestWid(ttk.TTkWidget): + __slots__ = ('_a','_b') + def __init__(self, *args, **kwargs): + self.setDefaultSize(kwargs, 10, 10) + super().__init__(*args, **kwargs) + self._b = ttk.pyTTkSignal(bool) + self.setFocusPolicy(ttk.TTkK.ClickFocus + ttk.TTkK.TabFocus) + + def mousePressEvent(self, evt): + # TTkLog.debug(f"{self._text} Test Mouse {evt}") + self.update() + return True + + def paintEvent(self, canvas): + canvas.fill(pos=(0,0), size=(2,2)) + +print("\n############# Phase 1 ##################") +# wid = ttk.TTkWidget() +wid = ttk.TTkButton() +# wid = ttk.TTkGraph() +# wid = ttk.TTkSpacer() +# wid = ttk.TTkSplitter() +# wid = TestWid() +# sizef = wid.size +sizef = [] + +wrwid = weakref.ref(wid) +# wrsizef = weakref.ref(sizef) +wrsizef = wrwid +# wrsizef2 = weakref.WeakMethod(wid.size) +wrsizef2 = wrwid + +_ref(wid) + +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +del wid +print(f"{gc.collect()=}") +# print(f"{sizef()}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +sizef = None +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +# time.sleep(4) +# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +print(f"{gc.collect()=}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tests/weakref/test.05.TermTk.03.signals.py b/tests/weakref/test.05.TermTk.03.signals.py new file mode 100755 index 00000000..8446847a --- /dev/null +++ b/tests/weakref/test.05.TermTk.03.signals.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 sys, os +import gc, weakref, time + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +def pobjs(): + for i,o in enumerate(gc.get_objects()[-100:]): + ss = str(o) + if "Foo" in ss: + print(f" * {i} - {ss}") + +print(f"\nStart {gc.isenabled()=}") + +def _gccb(phase,info): + print(f" ---> {gc.garbage=}") + print(f" ---> {phase=} {info=}") +# gc.callbacks.append(_gccb) + +def _ref(o): + print(f"\n### -> Referents - {o}") + for i,r in enumerate(gc.get_referents(o)): + print(f" - {i} ) {r}") + print(f"\n### -> Referrers - {o}") + for i,r in enumerate(gc.get_referrers(o)): + print(f" - {i} ) {r}") + for ii,rr in enumerate(gc.get_referrers(r)): + print(f" | {ii} ) {rr}") + print("") + print("") + +print("\n############# Phase 1 ##################") + +root = ttk.TTkWidget() +# wid = ttk.TTkWidget() +wid = ttk.TTkButton() +# wid = ttk.TTkLabel() +# wid = ttk.TTkGraph() +# wid = ttk.TTkSpacer() +# wid = ttk.TTkSplitter() +# sizef = wid.size +sizef = [] + +root.closed.connect(wid.close) + +ttk.TTkHelper._updateWidget = set() +ttk.TTkHelper._updateBuffer = set() + +wrwid = weakref.ref(wid) +# wrsizef = weakref.ref(sizef) +wrsizef = wrwid +# wrsizef2 = weakref.WeakMethod(wid.size) +wrsizef2 = wrwid + +_ref(wid) + +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +del wid +print(f"{gc.collect()=}") +# print(f"{sizef()}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +sizef = None +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +# time.sleep(4) +# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") +print(f"{gc.collect()=}") +print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}") + +print(f"{gc.garbage=}") +print(f"End {gc.get_count()=}") diff --git a/tools/check.import.sh b/tools/check.import.sh index 3de1d394..e37b3f6a 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -9,6 +9,7 @@ __check(){ -e "from dataclasses" \ -e "signal.py:from inspect import getfullargspec" \ -e "signal.py:from types import LambdaType" \ + -e "signal.py:from threading import Lock" \ -e "colors.py:from .colors_ansi_map" \ -e "log.py:import inspect" \ -e "log.py:import logging" \ diff --git a/tools/dumb.image.tool.py b/tools/dumb.image.tool.py index 1fc39864..1aa0d234 100755 --- a/tools/dumb.image.tool.py +++ b/tools/dumb.image.tool.py @@ -33,16 +33,190 @@ import TermTk as ttk ttk.TTkTheme.loadTheme(ttk.TTkTheme.NERD) +class Ansieditor(ttk.TTkGridLayout): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._te = ttk.TTkTextEdit(lineNumber=True, readOnly=False) + self._te.setText(ttk.TTkString("Ansi editor\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD+ttk.TTkColor.ITALIC)) + + self.addItem(wrapLayout := ttk.TTkGridLayout(), 0,0) + self.addItem(fontLayout := ttk.TTkGridLayout(columnMinWidth=1), 1,0) + self.addWidget(self._te,2,0,1,2) + + 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=self._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=self._te.isUndoAvailable(), text=' UNDO '),1,9 ) + fontLayout.addWidget(btn_redo := ttk.TTkButton(border=True, maxSize=(8,3), enabled=self._te.isRedoAvailable(), text=' REDO '),1,10) + # Undo/Redo events + self._te.undoAvailable.connect(btn_undo.setEnabled) + self._te.redoAvailable.connect(btn_redo.setEnabled) + btn_undo.clicked.connect(self._te.undo) + btn_redo.clicked.connect(self._te.redo) + + @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= }") + + self._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 = self._te.textCursor() + cursor.applyColor(color) + cursor.setColor(color) + self._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: self._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(self._te.setWrapWidth) + + @ttk.pyTTkSlot(int) + def _lineWrapCallback(index): + if index == 0: + self._te.setLineWrapMode(ttk.TTkK.NoWrap) + wordWrap.setDisabled() + fixWidth.setDisabled() + elif index == 1: + self._te.setLineWrapMode(ttk.TTkK.WidgetWidth) + wordWrap.setEnabled() + fixWidth.setDisabled() + else: + self._te.setLineWrapMode(ttk.TTkK.FixedWidth) + self._te.setWrapWidth(fixWidth.value()) + wordWrap.setEnabled() + fixWidth.setEnabled() + + lineWrap.currentIndexChanged.connect(_lineWrapCallback) + + @ttk.pyTTkSlot(int) + def _wordWrapCallback(index): + if index == 0: + self._te.setWordWrapMode(ttk.TTkK.WordWrap) + else: + self._te.setWordWrapMode(ttk.TTkK.WrapAnywhere) + + wordWrap.currentIndexChanged.connect(_wordWrapCallback) + + def te(self): + return self._te + +class SigmaskTool(ttk.TTkGridLayout): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.addWidget(cb_c := ttk.TTkCheckbox(text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked),1,0) + self.addWidget(cb_s := ttk.TTkCheckbox(text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked),2,0) + self.addWidget(cb_z := ttk.TTkCheckbox(text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked),3,0) + self.addWidget(cb_q := ttk.TTkCheckbox(text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked),4,0) + + cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked)) + cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked)) + cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked)) + cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked)) + + self.addWidget(quitBtn := ttk.TTkButton(text="QUIT", border=True, maxHeight=3),5,0) + quitBtn.clicked.connect(ttk.TTkHelper.quit) + + + root = ttk.TTk(title="Image Tool", layout=ttk.TTkGridLayout()) splitter = ttk.TTkSplitter(parent=root) -splitter.addWidget(fileTree:=ttk.TTkFileTree(path='tmp'), 15) +# splitter.addWidget(fileTree:=ttk.TTkFileTree(path='tmp'), 15) +splitter.addWidget(smt := SigmaskTool(), 25) splitter.addWidget(mainSplitter := ttk.TTkSplitter(orientation=ttk.TTkK.VERTICAL)) -mainSplitter.addWidget(sa := ttk.TTkScrollArea()) -mainSplitter.addWidget(controlsWidget := ttk.TTkWidget(layout=ttk.TTkGridLayout()),6) -mainSplitter.addWidget(te := ttk.TTkTextEdit(lineNumber=True)) +mainSplitter.addWidget(imageSplitter := ttk.TTkSplitter(orientation=ttk.TTkK.HORIZONTAL)) +mainSplitter.addWidget(controlsWidget := ttk.TTkContainer(layout=ttk.TTkGridLayout()),6) +mainSplitter.addWidget(te := ttk.TTkTextEdit(lineNumber=True, readOnly=False)) mainSplitter.addWidget(ttk.TTkLogViewer(),6) +smt.addWidget(fileTree:=ttk.TTkFileTree(path='tmp'),0,0) + + + + +imageSplitter.addWidget(sa := ttk.TTkScrollArea()) +imageSplitter.addWidget(ansiEdit := Ansieditor()) + controlsWidget.layout().addWidget(resizeFrame := ttk.TTkFrame(title='Resize',layout=ttk.TTkGridLayout())) controlsWidget.layout().addWidget(propertiesFrame := ttk.TTkFrame(title='Image Properties',layout=ttk.TTkGridLayout())) @@ -59,18 +233,18 @@ resizeFrame.layout().addWidget(b_resize := ttk.TTkButton(text='Resize', border=T cb_resample.addItems(['NEAREST','BOX','BILINEAR','HAMMING','BICUBIC','LANCZOS']) cb_resample.setCurrentIndex(0) -propertiesFrame.layout().addWidget(ttk.TTkLabel(text='Resolution:'),0,0) -propertiesFrame.layout().addWidget(cb_resolution := ttk.TTkComboBox(),0,1) -propertiesFrame.layout().addWidget(b_export := ttk.TTkButton(text="\nExport",),1,0,1,2) +propertiesFrame.layout().addWidget(ttk.TTkLabel(text='Resolution:'), 0,0) +propertiesFrame.layout().addWidget(cb_resolution := ttk.TTkComboBox(), 0,1) +propertiesFrame.layout().addWidget(ttk.TTkLabel(text='BgColor:'), 1,0) +propertiesFrame.layout().addWidget(b_color := ttk.TTkColorButtonPicker(color=ttk.TTkColor.fg("#000000")), 1,1) +propertiesFrame.layout().addWidget(b_export := ttk.TTkButton(text="Export"),2,0,1,2) # propertiesFrame.layout().addItem(ttk.TTkLayout(),3,0,1,2) cb_resolution.addItems(['FULLBLOCK','HALFBLOCK','QUADBLOCK']) cb_resolution.setCurrentIndex(2) - # te.setLineWrapMode(ttk.TTkK.WidgetWidth) # te.setWordWrapMode(ttk.TTkK.WordWrap) -te.setReadOnly(False) te.setText("Select an Image") @@ -92,7 +266,13 @@ def _export(): te.append('data = TTkUtil.base64_deflate_2_obj(') b64list = ' "' + '" +\n "'.join([b64str[i:i+128] for i in range(0,len(b64str),128)]) + '")' te.append(b64list) - + te.append(f'# ANSII {os.path.basename(__file__)}') + # ansi = ttkImage.getCanvas().toAnsi() + # te.append(ansi.replace('\033','')) + b64str = ttk.TTkUtil.obj_inflate_2_base64(ansiEdit.te().toAnsi()) + te.append('data = TTkUtil.base64_deflate_2_obj(') + b64list = ' "' + '" +\n "'.join([b64str[i:i+128] for i in range(0,len(b64str),128)]) + '")' + te.append(b64list) b_export.clicked.connect(_export) @@ -131,10 +311,16 @@ def _resize(): pilImage = pilImage.resize((width,height),resample) data = list(pilImage.getdata()) # rgbList = [(r,g,b) for r,g,b,a in data] - rgbList = [(r*a//255,g*a//255,b*a//255) for r,g,b,a in data] + br,bg,bb = b_color.color().bgToRGB() + rgbList = [ + ((r*a+(255-a)*br)//255, + (g*a+(255-a)*bg)//255, + (b*a+(255-a)*bb)//255) + for r,g,b,a in data] imageList = [rgbList[i:i+width] for i in range(0, len(rgbList), width)] ttkImage.setData(imageList) + ansiEdit.te().setText(ttkImage.getCanvas().toAnsi()) b_resize.clicked.connect(_resize) @@ -147,7 +333,12 @@ def _openFile(file): #te.append(str(list(pilImage.getdata()))) data = list(pilImage.getdata()) # rgbList = list(zip(data[::3],data[1::3],data[2::3])) - rgbList = [(r*a//255,g*a//255,b*a//255) for r,g,b,a in data] + br,bg,bb = b_color.color().bgToRGB() + rgbList = [ + ((r*a+(255-a)*br)//255, + (g*a+(255-a)*bg)//255, + (b*a+(255-a)*bb)//255) + for r,g,b,a in data] te.append("rgbList") #te.append(str(rgbList)) width, height = pilImage.size @@ -159,6 +350,7 @@ def _openFile(file): #te.append(str(imageList)) ttkImage.setData(imageList) + ansiEdit.te().setText(ttkImage.getCanvas().toAnsi()) #pilImage.size #pilImage.size diff --git a/tools/reformat.__all__.py b/tools/reformat.__all__.py new file mode 100755 index 00000000..f8340a1a --- /dev/null +++ b/tools/reformat.__all__.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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. + +# usage: +# +# # Prepare the folders: +# cp -a TermTk tmp +# +# # Run the script +# for i in $(find TermTk -name "*.py") ; +# do echo $i ; +# tools/reformat.__all__.py $i > tmp/$i ; +# done +# + +__all__ = [] +__version__ = '1.0' +__author__ = 'Eugenio Parodi' + +import os +import sys +import argparse + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('filename', type=str, help='the filename') + args = parser.parse_args() + + lines = [] + all = '' + + with open(args.filename) as f: + while l := f.readline(): + # removing the python header + if "env python3" in l: + while (l:=f.readline()) == '\n': pass + + # Process the __all__ directive + if l.startswith('__all__'): + all = l + l = f.readline() + if l != '\n': + lines.append(l) + else: + lines.append(l) + + for l in lines: + if l == '# SOFTWARE.\n' and all: + print(f"{l}\n{all}",end='') + else: + print(l,end='') + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/tailSession.sh b/tools/tailSession.sh index b7342f5b..5a8eed5c 100755 --- a/tools/tailSession.sh +++ b/tools/tailSession.sh @@ -29,5 +29,6 @@ while read -r line; do sed "s,/home.*/TermTk/,TermTk/," | sed "s,^\(INFO:\),${Green}\1${_RST_}," | sed "s,^\(ERROR:\),${Red}\1${_RST_}," | + sed "s,^\(WARNING:\),${Magenta}\1${_RST_}," | sed "s,^\(DEBUG:\),${Blue}\1${_RST_}," done < <(tail -F session.log) \ No newline at end of file diff --git a/ttkDesigner/app/designer.py b/ttkDesigner/app/designer.py index d7a982b8..763cb40a 100644 --- a/ttkDesigner/app/designer.py +++ b/ttkDesigner/app/designer.py @@ -36,8 +36,7 @@ from TermTk import TTkFileTree, TTkTextEdit from TermTk import TTkLayout, TTkGridLayout, TTkVBoxLayout, TTkHBoxLayout from TermTk import TTkSplitter -from TermTk import TTkLogViewer, TTkTomInspector - +from TermTk import TTkLogViewer from TermTk import TTkUiLoader, TTkUtil from .cfg import * @@ -224,7 +223,7 @@ class TTkDesigner(TTkGridLayout): tui = self._windowEditor.dumpDict() connections = self._sigslotEditor.dumpDict() data = { - 'version':'1.0.1', + 'version':'2.0.0', 'tui':tui, 'connections':connections} @@ -247,7 +246,7 @@ class TTkDesigner(TTkGridLayout): # for line in jj.split('\n'): # TTkLog.debug(f"{line}") newUI = { - 'version':'1.0.1', + 'version':'2.0.0', 'tui':tui, 'connections':connections} jj = json.dumps(newUI, indent=1) @@ -278,6 +277,7 @@ class TTkDesigner(TTkGridLayout): with open(fileName) as fp: dd = json.load(fp) + dd = TTkUiLoader.normalise(dd) sw = SuperWidget.loadDict(self, self._windowEditor.viewport(), dd['tui']) self._windowEditor.importSuperWidget(sw) self._sigslotEditor.importConnections(dd['connections']) @@ -299,7 +299,7 @@ class TTkDesigner(TTkGridLayout): tui = self._windowEditor.dumpDict() connections = self._sigslotEditor.dumpDict() newUI = { - 'version':'1.0.0', + 'version':'2.0.0', 'tui':tui, 'connections':connections} jj = json.dumps(newUI, indent=1) diff --git a/ttkDesigner/app/signalsloteditor.py b/ttkDesigner/app/signalsloteditor.py index a12d6b0e..4417b6bb 100644 --- a/ttkDesigner/app/signalsloteditor.py +++ b/ttkDesigner/app/signalsloteditor.py @@ -189,7 +189,7 @@ class _SignalSlotItem(ttk.TTkTreeWidgetItem): signals[ccName] = ttk.TTkUiProperties[ccName]['signals'] return signals,slots -class SignalSlotEditor(ttk.TTkWidget): +class SignalSlotEditor(ttk.TTkContainer): __slots__ = ('_items', '_designer') def __init__(self, designer, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/ttkDesigner/app/superobj/__init__.py b/ttkDesigner/app/superobj/__init__.py index 11eaf060..97c78875 100644 --- a/ttkDesigner/app/superobj/__init__.py +++ b/ttkDesigner/app/superobj/__init__.py @@ -24,6 +24,7 @@ from .supercontrol import SuperControlWidget from .superwidget import SuperWidget +from .superwidgetcontainer import SuperWidgetContainer from .superwidgettextedit import SuperWidgetTextEdit from .superwidgetradiobutton import SuperWidgetRadioButton from .superwidgetframe import SuperWidgetFrame diff --git a/ttkDesigner/app/superobj/superlayout.py b/ttkDesigner/app/superobj/superlayout.py index 1f852d97..c0f02689 100644 --- a/ttkDesigner/app/superobj/superlayout.py +++ b/ttkDesigner/app/superobj/superlayout.py @@ -26,7 +26,7 @@ import TermTk as ttk import ttkDesigner.app.superobj as so from .superobj import SuperObject -class SuperLayout(ttk.TTkWidget): +class SuperLayout(ttk.TTkContainer): __slots__ = ('_lay', '_dropBorder', '_superRootWidget', '_selectable', '_designer') def __init__(self, lay, designer, *args, **kwargs): self._designer = designer @@ -49,6 +49,11 @@ class SuperLayout(ttk.TTkWidget): self.setFocusPolicy(ttk.TTkK.ClickFocus) so.SuperWidget.toggleHighlightLayout.connect(self._toggleHighlightLayout) + # TODO: Find a better way to handle this exception + # It may require some major rewrite + def hasControlWidget(self): + return True + def getSuperProperties(self): additions = {} exceptions = {} @@ -183,28 +188,46 @@ class SuperLayout(ttk.TTkWidget): self._lay.addItem(sl._lay) sl.show() sl.move(evt.x-hsx, evt.y-hsy) - elif issubclass(type(data), so.SuperWidget): + elif issubclass(type(data), so.SuperWidgetContainer): sw = data self.addSuperWidget(sw) self._lay.addWidget(sw._wid) sl = sw._superLayout sw.move(evt.x-hsx, evt.y-hsy) sw.show() - elif issubclass(type(data),ttk.TTkWidget): + elif issubclass(type(data), so.SuperWidget): + sw = data + self.addSuperWidget(sw) + self._lay.addWidget(sw._wid) + sl = None + sw.move(evt.x-hsx, evt.y-hsy) + sw.show() + elif issubclass(type(data),ttk.TTkTextEdit): + self.addSuperWidget(sw := so.SuperWidget.swFromWidget(designer=self._designer, wid=data, pos=(evt.x-hsx, evt.y-hsy))) + self._lay.addWidget(data) + sw.move(evt.x-hsx, evt.y-hsy) + sl = None + elif issubclass(type(data),ttk.TTkContainer): self.addSuperWidget(sw := so.SuperWidget.swFromWidget(designer=self._designer, wid=data, pos=(evt.x-hsx, evt.y-hsy))) self._lay.addWidget(data) sw.move(evt.x-hsx, evt.y-hsy) sl = sw._superLayout + elif issubclass(type(data),ttk.TTkWidget): + self.addSuperWidget(sw := so.SuperWidget.swFromWidget(designer=self._designer, wid=data, pos=(evt.x-hsx, evt.y-hsy))) + self._lay.addWidget(data) + sw.move(evt.x-hsx, evt.y-hsy) + sl = None else: return False self.layout().update() # set the Drop Border in case this layout auto resize - if issubclass(type(self.layout()),ttk.TTkGridLayout): - sl.setDropBorder(1) - else: - sl.setDropBorder(0) + if sl: + if issubclass(type(self.layout()),ttk.TTkGridLayout): + sl.setDropBorder(1) + else: + sl.setDropBorder(0) self.update() self._designer.weModified.emit() diff --git a/ttkDesigner/app/superobj/superlayoutgrid.py b/ttkDesigner/app/superobj/superlayoutgrid.py index 20b7a22f..14f8ba1b 100644 --- a/ttkDesigner/app/superobj/superlayoutgrid.py +++ b/ttkDesigner/app/superobj/superlayoutgrid.py @@ -330,11 +330,17 @@ class SuperLayoutGrid(SuperLayout): ix, iw = horSizes[col] iy, ih = verSizes[row] + dt = y-iy + db = iy+ih-y-1 + dl = x-ix + dr = ix+iw-x-1 + dmin = min(dt,db,dl,dr) + if self.layout().itemAtPosition(row,col) == None: ret = (ix, iy, iw, ih) else: - #Top - if ((y==iy) and (self._orientation & ttk.TTkK.VERTICAL) and + #Top - we are closer to this edge + if ((dt==dmin) and (self._orientation & ttk.TTkK.VERTICAL) and ( row==0 or ( row>0 and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row-1,col)))): dir = ttk.TTkK.VERTICAL @@ -342,16 +348,16 @@ class SuperLayoutGrid(SuperLayout): ret = (ix, iy-1, iw, 2) else: ret = (ix, iy, iw, 1) - #Bottom - if ((iy+ih==y+1) and (self._orientation & ttk.TTkK.VERTICAL) and + #Bottom - we are closer to this edge + if ((db==dmin) and (self._orientation & ttk.TTkK.VERTICAL) and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row+1,col)): dir = ttk.TTkK.VERTICAL if row0 and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row,col-1)))): dir = ttk.TTkK.HORIZONTAL @@ -359,8 +365,8 @@ class SuperLayoutGrid(SuperLayout): ret = (ix-1, iy, 2, ih) else: ret = (ix, iy, 1, ih) - #Right - if ((ix+iw==x+1) and (self._orientation & ttk.TTkK.HORIZONTAL) and + #Right - we are closer to this edge + if ((dr==dmin) and (self._orientation & ttk.TTkK.HORIZONTAL) and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row,col+1)): dir = ttk.TTkK.HORIZONTAL if col bool: - padt, padb, padl, padr = self._wid.getPadding() - # evt = evt.copy() - evt.x-=padl - evt.y-=padt - return self._superLayout.dropEvent(evt) - def move(self, x: int, y: int): self._wid.move(x,y) self.update() @@ -274,11 +244,10 @@ class SuperWidget(ttk.TTkWidget): def paintEvent(self, canvas): w,h = self.size() if SuperWidget._showLayout: - t,b,l,r = self._wid.getPadding() for y in range(h): canvas.drawText(pos=(0,y),text='',width=w,color=self._layoutColor) - for y in range(t,h-b): - canvas.drawText(pos=(l,y),text='',width=w-r-l,color=self._layoutPadColor) + for y in range(0,h): + canvas.drawText(pos=(0,y),text='',width=w,color=self._layoutPadColor) # canvas.fill(color=self._layoutColor) # canvas.fill(pos=(l,t), size=(w-r-l,h-b-t), color=self._layoutPadColor) else: diff --git a/ttkDesigner/app/superobj/superwidgetcontainer.py b/ttkDesigner/app/superobj/superwidgetcontainer.py new file mode 100644 index 00000000..d082c8c9 --- /dev/null +++ b/ttkDesigner/app/superobj/superwidgetcontainer.py @@ -0,0 +1,123 @@ +# MIT License +# +# Copyright (c) 2023 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. + +from random import randint + +import TermTk as ttk + +import ttkDesigner.app.superobj as so +from .superobj import SuperObject + +class SuperWidgetContainer(so.SuperWidget): + __slots__ = ('_superLayout') + def __init__(self, designer, wid, *args, **kwargs): + + self._superLayout = so.SuperLayout(designer=designer, lay=wid.layout(),) + self._superRootWidget = kwargs.get('superRootWidget',False) + kwargs['layout'] = ttk.TTkGridLayout() + kwargs['layout'].addWidget(self._superLayout) + + padt, padb, padl, padr = wid.getPadding() + kwargs['paddingTop'] = padt + kwargs['paddingBottom'] = padb + kwargs['paddingLeft'] = padl + kwargs['paddingRight'] = padr + super().__init__(designer, wid, *args, **kwargs) + + def getSuperProperties(self): + additions, exceptions, exclude = super().getSuperProperties() + exceptions |= { + 'Layout' : { + 'get': { 'cb': lambda _: self._superLayout.layout().__class__ , 'type':'singleflag', + 'flags': { + 'TTkLayout' : ttk.TTkLayout , + 'TTkGridLayout' : ttk.TTkGridLayout , + 'TTkVBoxLayout' : ttk.TTkVBoxLayout , + 'TTkHBoxLayout' : ttk.TTkHBoxLayout } }, + 'set': { 'cb': lambda _,l: self.changeSuperLayout(l) , 'type':'singleflag', + 'flags': { + 'TTkLayout' : ttk.TTkLayout , + 'TTkGridLayout' : ttk.TTkGridLayout , + 'TTkVBoxLayout' : ttk.TTkVBoxLayout , + 'TTkHBoxLayout' : ttk.TTkHBoxLayout } }, + } + } + return additions, exceptions, exclude + + def dumpDict(self): + ret = super().dumpDict() + ret |= { + 'layout': self._superLayout.dumpDict() + } + return ret + + @staticmethod + def _swFromWidget(wid, swClass, *args, **kwargs): + sw = swClass(wid=wid, *args, **kwargs) + sw.changeSuperLayout(type(wid.layout())) + return sw + + def dropEvent(self, evt) -> bool: + padt, padb, padl, padr = self._wid.getPadding() + # evt = evt.copy() + evt.x-=padl + evt.y-=padt + return self._superLayout.dropEvent(evt) + + def changeSuperLayout(self, layout): + sl = self._superLayout + self.layout().removeWidget(sl) + if layout == ttk.TTkVBoxLayout: + sl = so.SuperLayoutVBox(designer=self._designer, lay=self._wid.layout(), selectable=False) + elif layout == ttk.TTkHBoxLayout: + sl = so.SuperLayoutHBox(designer=self._designer, lay=self._wid.layout(), selectable=False) + elif layout == ttk.TTkGridLayout: + sl = so.SuperLayoutGrid(designer=self._designer, lay=self._wid.layout(), selectable=False) + else: + sl = so.SuperLayout( designer=self._designer, lay=self._wid.layout(), selectable=False) + self._superLayout = sl + self._wid.setLayout(layout()) + self.layout().addWidget(sl) + self._designer.weModified.emit() + + def updateAll(self): + self.setPadding(*(self._wid.getPadding())) + super().updateAll() + + def paintEvent(self, canvas): + w,h = self.size() + if so.SuperWidget._showLayout: + t,b,l,r = self._wid.getPadding() + for y in range(h): + canvas.drawText(pos=(0,y),text='',width=w,color=self._layoutColor) + for y in range(t,h-b): + canvas.drawText(pos=(l,y),text='',width=w-r-l,color=self._layoutPadColor) + # canvas.fill(color=self._layoutColor) + # canvas.fill(pos=(l,t), size=(w-r-l,h-b-t), color=self._layoutPadColor) + else: + self._wid.getCanvas().updateSize() + self._wid.paintEvent(self._wid.getCanvas()) + canvas.paintCanvas( + self._wid.getCanvas(), + ( 0, 0, w, h), # geometry + ( 0, 0, w, h), # slice + ( 0, 0, w, h)) # bound \ No newline at end of file diff --git a/ttkDesigner/app/superobj/superwidgetframe.py b/ttkDesigner/app/superobj/superwidgetframe.py index 798a150c..7a02c463 100644 --- a/ttkDesigner/app/superobj/superwidgetframe.py +++ b/ttkDesigner/app/superobj/superwidgetframe.py @@ -24,7 +24,7 @@ import TermTk as ttk import ttkDesigner.app.superobj as so from ttkDesigner.app.menuBarEditor import MenuBarEditor -class SuperWidgetFrame(so.SuperWidget): +class SuperWidgetFrame(so.SuperWidgetContainer): def getSuperProperties(self): additions, exceptions, exclude = super().getSuperProperties() additions |= { diff --git a/ttkDesigner/app/superobj/superwidgetsplitter.py b/ttkDesigner/app/superobj/superwidgetsplitter.py index df6dae08..44b1149a 100644 --- a/ttkDesigner/app/superobj/superwidgetsplitter.py +++ b/ttkDesigner/app/superobj/superwidgetsplitter.py @@ -25,7 +25,7 @@ import ttkDesigner.app.superobj as so from .superobj import SuperObject -class SuperWidgetSplitter(so.SuperWidget): +class SuperWidgetSplitter(so.SuperWidgetContainer): def getSuperProperties(self): additions, exceptions, exclude = super().getSuperProperties() exclude += ['layout'] diff --git a/ttkDesigner/app/treeinspector.py b/ttkDesigner/app/treeinspector.py index 006d0d5b..cd33744f 100644 --- a/ttkDesigner/app/treeinspector.py +++ b/ttkDesigner/app/treeinspector.py @@ -23,6 +23,7 @@ import TermTk as ttk from .superobj.superwidget import SuperWidget +from .superobj.superwidgetcontainer import SuperWidgetContainer from .superobj.superlayout import SuperLayout from .superobj.superwidgetframe import SuperWidgetFrame from .superobj.superwidgetmenubutton import SuperWidgetMenuButton @@ -151,7 +152,7 @@ class TreeInspector(ttk.TTkGridLayout): elif issubclass(type(superThing), SuperLayout): thing = thing._lay expanded = True # ttk.TTkHelper.isParent(widSelected,thing) if widSelected else False - if issubclass(type(superThing), SuperWidget): + if issubclass(type(superThing), SuperWidgetContainer): top = _TTkTomTreeWidgetItem([ thing._name, thing.__class__.__name__, str(thing.isVisible()), @@ -159,6 +160,13 @@ class TreeInspector(ttk.TTkGridLayout): tomWidget=thing, tomSuperWidget=superThing, expanded=expanded) + elif issubclass(type(superThing), SuperWidget): + top = _TTkTomTreeWidgetItem([ + thing._name, thing.__class__.__name__, + str(thing.isVisible()),""], + tomWidget=thing, + tomSuperWidget=superThing, + expanded=expanded) elif issubclass(type(superThing), SuperLayout): top = _TTkTomTreeWidgetItem([ 'Layout', thing.__class__.__name__, @@ -169,7 +177,7 @@ class TreeInspector(ttk.TTkGridLayout): expanded=expanded) if issubclass(type(superThing), SuperWidgetFrame): _processMenuBars(thing, top) - if issubclass(type(superThing), SuperWidget): + if issubclass(type(superThing), SuperWidgetContainer): for c in superThing._superLayout.layout().children(): top.addChild(TreeInspector._getTomTreeItem(c,widSelected,designer)) elif issubclass(type(superThing), SuperLayout): diff --git a/ttkDesigner/app/widgetbox.py b/ttkDesigner/app/widgetbox.py index 02abce11..3d68d518 100644 --- a/ttkDesigner/app/widgetbox.py +++ b/ttkDesigner/app/widgetbox.py @@ -33,6 +33,12 @@ dWidgets = { "Grid Layout" : { "class":ttk.TTkGridLayout, "params":{'size':(30,10)}}, "Splitter" : { "class":ttk.TTkSplitter , "params":{'size':(40,10)}}, }, + 'Containers':{ + "Container" : { "class":ttk.TTkContainer, "params":{'size':(20,10)}}, + "Window" : { "class":ttk.TTkWindow, "params":{'size':(20,10)}}, + "Frame" : { "class":ttk.TTkFrame, "params":{'size':(20,5), 'border':True}}, + "Resizable Frame" : { "class":ttk.TTkResizableFrame, "params":{'size':(20,5)}}, + }, 'Buttons':{ "Button" : { "class":ttk.TTkButton, "params":{'size':(20,3), 'text':'Button', 'border':True, }}, "Button Slim" : { "class":ttk.TTkButton, "params":{'size':(20,1), 'text':'Button', 'border':False, }}, @@ -54,10 +60,7 @@ dWidgets = { "Scroll Area" : { "class":ttk.TTkScrollArea, "params":{'size':(20,5)}, "disabled": True}, "Spacer" : { "class":ttk.TTkSpacer, "params":{'size':(10,5)}}, "Tab Widget" : { "class":ttk.TTkTabWidget, "params":{'size':(20,3)}, "disabled": True}, - "Window" : { "class":ttk.TTkWindow, "params":{'size':(20,10)}}, "Widget" : { "class":ttk.TTkWidget, "params":{'size':(20,5)}}, - "Frame" : { "class":ttk.TTkFrame, "params":{'size':(20,5), 'border':True}}, - "Resizable Frame" : { "class":ttk.TTkResizableFrame, "params":{'size':(20,5)}}, }, 'Pickers':{ "Color Picker" : { "class":ttk.TTkColorButtonPicker, "params":{'size':( 6,3), 'border':True}}, @@ -68,13 +71,27 @@ dWidgets = { 'Debug':{ "Log Viewer" : { "class":ttk.TTkLogViewer, "params":{'size':(60,10)}}, "Input View" : { "class":ttk.TTkKeyPressView, "params":{'size':(60,3)}}, - "Tom Inspector" : { "class":ttk.TTkTomInspector, "params":{'size':(40,10)}, "disabled": True}, + # "Tom Inspector" : { "class":ttk.TTkTomInspector, "params":{'size':(40,10)}, "disabled": True}, "Test Widget" : { "class":ttk.TTkTestWidgets, "params":{'size':(40,10)}, "disabled": True}, "Test Widget info" : { "class":ttk.TTkTestWidgetSizes, "params":{'size':(40,10)}}, } } class DragDesignItem(ttk.TTkWidget): + classStyle = { + 'default': {'color': TTkColor.fg("#dddd88")+TTkColor.bg("#000044"), + 'borderColor': TTkColor.RST, + 'shadow': TTkColor.RST+TTkColor.bg('#444444')}, + 'disabled': {'color': TTkColor.fg('#888888'), + 'borderColor': TTkColor.fg('#888888'), + 'shadow': TTkColor.RST}, + 'hover': {'color': TTkColor.fg("#dddd00")+TTkColor.bg("#004488")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#FFFF00")+TTkColor.bg("#000088")+TTkColor.BOLD, + 'shadow': TTkColor.fg("#FFFF00")+TTkColor.bg('#444444')}, + 'clicked': {'color': TTkColor.fg("#FFFFDD")+TTkColor.BOLD, + 'borderColor': TTkColor.fg("#DDDDDD")+TTkColor.BOLD, + 'shadow': TTkColor.fg("#DDDDDD")+TTkColor.bg('#444444')}, + } __slots__ = ('_itemName', '_widgetClass', '_designer') def __init__(self, itemName, widgetClass, designer, *args, **kwargs): super().__init__(*args, **kwargs) @@ -112,12 +129,26 @@ class DragDesignItem(ttk.TTkWidget): return True def paintEvent(self, canvas): - if self.isEnabled(): - color=ttk.TTkColor.RST - else: - color=ttk.TTkColor.fg('#AAAAAA') - canvas.drawText(text=self._itemName, pos=(1,1), color=color) - canvas.drawBox(pos=(0,0),size=self.size(), color=color) + style = self.currentStyle() + + textColor = style['color'] + borderColor = style['borderColor'] + shadowColor = style['shadow'] + + w,h = self.size() + + canvas.drawText(text=self._itemName, pos=(1,1), width=w-2, color=textColor) + canvas.drawBox(pos=(0,0),size=self.size(), color=borderColor) + + # canvas.drawText(text=self._itemName, pos=(2,1), width=w-2, color=textColor) + # txt = '▗' + ('▄'*(w-2)) + '▖' + # canvas.drawText(text=txt, pos=(0,0), color=borderColor) + # txt = '▝' + ('▀'*(w-2)) + # canvas.drawText(text=txt, pos=(0,2), color=shadowColor) + # canvas.drawText(text='▐', pos=(0, 1), color=shadowColor) + # canvas.drawText(text='▌', pos=(w-1,1), color=borderColor) + # canvas.drawText(text='▘', pos=(w-1,2), color=borderColor) + class WidgetBox(ttk.TTkVBoxLayout): def __init__(self, designer, *args, **kwargs): diff --git a/ttkDesigner/app/windoweditor.py b/ttkDesigner/app/windoweditor.py index a3074d9a..bdeda442 100644 --- a/ttkDesigner/app/windoweditor.py +++ b/ttkDesigner/app/windoweditor.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .superobj import SuperWidget +from .superobj import SuperWidget,SuperWidgetContainer import TermTk as ttk @@ -40,7 +40,7 @@ class WindowEditorView(ttk.TTkAbstractScrollView): self._ttk.superResized.disconnect(self._superChanged) self._ttk.superMoved.disconnect(self._superChanged) self.layout().removeWidget(self._ttk) - self._ttk = SuperWidget(wid=ttk.TTkWindow(name = 'MainWindow'), designer=self._designer, pos=(4,2), superRootWidget=True) + self._ttk = SuperWidget.swFromWidget(wid=ttk.TTkWindow(name = 'MainWindow'), designer=self._designer, pos=(4,2), superRootWidget=True) self._ttk.resize(self.width()-8,self.height()-4) self._snapRootWidget = True self.layout().addWidget(self._ttk) @@ -53,7 +53,7 @@ class WindowEditorView(ttk.TTkAbstractScrollView): self._ttk.superResized.disconnect(self._superChanged) self._ttk.superMoved.disconnect(self._superChanged) self.layout().removeWidget(self._ttk) - self._ttk = SuperWidget(wid=ttk.TTkWidget(name = 'MainWidget'), designer=self._designer, pos=(4,2), superRootWidget=True) + self._ttk = SuperWidget.swFromWidget(wid=ttk.TTkContainer(name = 'MainWidget'), designer=self._designer, pos=(4,2), superRootWidget=True) self._ttk.resize(self.width()-8,self.height()-4) self._snapRootWidget = True self.layout().addWidget(self._ttk)