From ebd13f94c5513daa356547a05a9ca93c2bd946e8 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Fri, 26 Dec 2025 06:51:40 +0000 Subject: [PATCH] chore(crossTools): improve typing (#571) --- .../dumbPaintTool/app/maintemplate.py | 56 ++- libs/pyTermTk/TermTk/TTkCore/constant.py | 4 +- .../TermTk/TTkCrossTools/savetools.py | 416 +++++++++++++++--- .../TTkWidgets/TTkPickers/messagebox.py | 6 +- tools/webExporter/js/ttkproxy.js | 16 +- 5 files changed, 398 insertions(+), 100 deletions(-) diff --git a/apps/dumbPaintTool/dumbPaintTool/app/maintemplate.py b/apps/dumbPaintTool/dumbPaintTool/app/maintemplate.py index a306dd55..636fbbe7 100644 --- a/apps/dumbPaintTool/dumbPaintTool/app/maintemplate.py +++ b/apps/dumbPaintTool/dumbPaintTool/app/maintemplate.py @@ -127,8 +127,8 @@ class PaintTemplate(ttk.TTkAppTemplate): glbls.clearSnapshot() glbls.saveSnapshot() - ttk.ttkConnectDragOpen(ttk.TTkEncoding.APPLICATION_JSON, self._openDragData) - ttk.ttkConnectDragOpen(ttk.TTkEncoding.IMAGE, self._openImageData) + ttk.TTkCrossTools.ttkConnectDragOpen(ttk.TTkCrossTools.Encoding.APPLICATION_JSON, self._openDragData) + ttk.TTkCrossTools.ttkConnectDragOpen(ttk.TTkCrossTools.Encoding.IMAGE, self._openImageData) ttk.TTkShortcut(ttk.TTkK.CTRL | ttk.TTkK.Key_Z).activated.connect(glbls.undo) ttk.TTkShortcut(ttk.TTkK.CTRL | ttk.TTkK.Key_Y).activated.connect(glbls.redo) @@ -147,56 +147,68 @@ class PaintTemplate(ttk.TTkAppTemplate): @ttk.pyTTkSlot() def _open(self): - ttk.ttkCrossOpen( + ttk.TTkCrossTools.open( path='.', - encoding=ttk.TTkEncoding.APPLICATION_JSON, + encoding=ttk.TTkCrossTools.Encoding.APPLICATION_JSON, filter="DumbPaintTool Files (*.DPT.json);;Json Files (*.json);;All Files (*)", cb=self._openDragData) @ttk.pyTTkSlot() def _openImage(self): - ttk.ttkCrossOpen( + ttk.TTkCrossTools.open( path='.', - encoding=ttk.TTkEncoding.IMAGE, + encoding=ttk.TTkCrossTools.Encoding.IMAGE, filter="Images (*.png *.jpg *.gif *.ico);;All Files (*)", cb=self._openImageData) @ttk.pyTTkSlot() def _save(self): doc = self._parea.exportDocument() - ttk.ttkCrossSave(glbls.fileName(), json.dumps(doc, indent=1), ttk.TTkEncoding.APPLICATION_JSON) + ttk.TTkCrossTools.save( + filePath=glbls.fileName(), + content=json.dumps(doc, indent=1), + encoding=ttk.TTkCrossTools.Encoding.APPLICATION_JSON) @ttk.pyTTkSlot() def _saveAs(self): doc = self._parea.exportDocument() - ttk.ttkCrossSaveAs(glbls.fileName(), json.dumps(doc, indent=1), ttk.TTkEncoding.APPLICATION_JSON, - filter="DumbPaintTool Files (*.DPT.json);;Json Files (*.json);;All Files (*)", - cb=lambda _d:glbls.setFilename(_d['name'])) + ttk.TTkCrossTools.saveAs( + filePath=glbls.fileName(), + content=json.dumps(doc, indent=1), + encoding=ttk.TTkCrossTools.Encoding.APPLICATION_JSON, + filter="DumbPaintTool Files (*.DPT.json);;Json Files (*.json);;All Files (*)", + cb=lambda _d:glbls.setFilename(_d.name)) @ttk.pyTTkSlot() def _saveAsAnsi(self): image = self._parea.exportImage() text = ttk.TTkString(image) - ttk.ttkCrossSaveAs(glbls.fileName(), text.toAnsi(), ttk.TTkEncoding.TEXT_PLAIN_UTF8, - filter="Ansi text Files (*.Ansi.txt);;Text Files (*.txt);;All Files (*)") + ttk.TTkCrossTools.saveAs( + filePath=glbls.fileName(), + content=text.toAnsi(), + encoding=ttk.TTkCrossTools.Encoding.TEXT_PLAIN_UTF8, + filter="Ansi text Files (*.Ansi.txt);;Text Files (*.txt);;All Files (*)") @ttk.pyTTkSlot() def _saveAsAscii(self): image = self._parea.exportImage() text = ttk.TTkString(image) - ttk.ttkCrossSaveAs('untitled.DPT.ASCII.txt', text.toAscii(), ttk.TTkEncoding.TEXT_PLAIN_UTF8, - filter="ASCII Text Files (*.ASCII.txt);;Text Files (*.txt);;All Files (*)") - - @ttk.pyTTkSlot(dict) - def _openImageData(self, data): - newWindow = ImportImage(data['data']) + ttk.TTkCrossTools.saveAs( + filePath='untitled.DPT.ASCII.txt', + content=text.toAscii(), + encoding=ttk.TTkCrossTools.Encoding.TEXT_PLAIN_UTF8, + filter="ASCII Text Files (*.ASCII.txt);;Text Files (*.txt);;All Files (*)") + + @ttk.pyTTkSlot(ttk.TTkCrossTools.CB_Data_Open) + def _openImageData(self, data:ttk.TTkCrossTools.CB_Data_Open): + newWindow = ImportImage(data.data) newWindow.exportedImage.connect(self._parea.pasteEvent) ttk.TTkHelper.overlay(None, newWindow, 10, 4, modal=True) - @ttk.pyTTkSlot(dict) - def _openDragData(self, data): - dd = json.loads(data['data']) - glbls.setFilename(data['name']) + @ttk.pyTTkSlot(ttk.TTkCrossTools.CB_Data_Open) + def _openDragData(self, data:ttk.TTkCrossTools.CB_Data_Open): + dd = json.loads(data.data) + glbls.setFilename(data.name) if 'layers' in dd: self.importDocument(dd) else: diff --git a/libs/pyTermTk/TermTk/TTkCore/constant.py b/libs/pyTermTk/TermTk/TTkCore/constant.py index 956b3816..4188f84b 100644 --- a/libs/pyTermTk/TermTk/TTkCore/constant.py +++ b/libs/pyTermTk/TermTk/TTkCore/constant.py @@ -471,7 +471,7 @@ class TTkConstant: CENTER_ALIGN = Alignment.CENTER_ALIGN JUSTIFY = Alignment.JUSTIFY - class FileMode(int): + class FileMode(IntEnum): '''FileMode .. autosummary:: @@ -492,7 +492,7 @@ class TTkConstant: # Directory = FileMode.Directory # ExistingFiles = FileMode.ExistingFiles - class AcceptMode(int): + class AcceptMode(IntEnum): '''AcceptMode .. autosummary:: diff --git a/libs/pyTermTk/TermTk/TTkCrossTools/savetools.py b/libs/pyTermTk/TermTk/TTkCrossTools/savetools.py index a69c131a..885493a4 100644 --- a/libs/pyTermTk/TermTk/TTkCrossTools/savetools.py +++ b/libs/pyTermTk/TermTk/TTkCrossTools/savetools.py @@ -20,34 +20,133 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__all__ = [ - 'ttkCrossOpen', - 'ttkCrossSave', 'ttkCrossSaveAs', - 'TTkEncoding', 'ImageData', - 'ttkConnectDragOpen', - 'ttkEmitDragOpen', 'ttkEmitFileOpen'] +'''savetools.py + +Cross-platform file operations and clipboard management for pyTermTk. + +This module provides a unified interface for file I/O operations that work seamlessly +across different platforms, including native desktop environments and web-based +Pyodide/WASM deployments. It automatically detects the runtime environment and +adapts its behavior accordingly. + +Key Features: + - **Cross-platform file operations**: Open, save, and saveAs methods that work + identically on desktop and web platforms + - **Multiple encoding support**: Text, JSON, and image formats with automatic + decoding and encoding + - **Drag and drop integration**: Native drag-drop support in web environments + - **File dialog integration**: Automatic file picker dialogs on desktop with + JavaScript-based file selection in browsers + - **Image handling**: PIL/Pillow integration for image file operations + - **Type-safe callbacks**: Protocol-based type hints for callback functions + +Platform Detection: + The module automatically detects whether it's running in a Pyodide/WASM environment + by checking for the 'pyodideProxy' module. Based on this detection, it provides + platform-specific implementations while maintaining a consistent API. + + - **Desktop mode**: Uses native file dialogs (:py:class:`TTkFileDialogPicker`) and + standard Python file I/O + - **Web mode**: Uses JavaScript interop via pyodideProxy for browser-based file + selection and download + +Usage Example: + .. code-block:: python + + from TermTk.TTkCrossTools import TTkCrossTools + + # Open a text file + def handle_open(data: TTkCrossTools.CB_Data_Open): + print(f"Opened: {data.name}") + print(f"Content: {data.data}") + + TTkCrossTools.open( + path=".", + encoding=TTkCrossTools.Encoding.TEXT_PLAIN, + filter="Text Files (*.txt)", + cb=handle_open + ) + + # Save a file + TTkCrossTools.save( + filePath="output.txt", + content="Hello, World!", + encoding=TTkCrossTools.Encoding.TEXT_PLAIN + ) + + # Save with dialog + def handle_save(data: TTkCrossTools.CB_Data_Save): + print(f"Saved to: {data.name}") + + TTkCrossTools.saveAs( + filePath="output.json", + content='{"key": "value"}', + encoding=TTkCrossTools.Encoding.APPLICATION_JSON, + filter="JSON Files (*.json)", + cb=handle_save + ) + +Supported Encodings: + See :py:class:`TTkCrossTools.Encoding` for the full list of supported MIME types and + encoding formats. + +Callback Data Structures: + - :py:class:`_CB_Data_Open`: Contains file name and decoded content + - :py:class:`_CB_Data_Save`: Contains the saved file name + +See Also: + - :py:class:`TTkCrossTools`: Main API class + - :py:class:`TTkCrossTools.Encoding`: Encoding type definitions + - :py:class:`TTkFileDialogPicker`: Native file dialog widget +''' + +from __future__ import annotations + +__all__ = ['TTkCrossTools', '_TTkEncoding'] import os import importlib.util -import json +from enum import Enum from dataclasses import dataclass +from typing import Callable,Optional,List,Tuple,Dict,Any,Protocol,Type + +try: + from typing import TypeAlias +except ImportError: + # TODO: Remove this workaround for Python 3.9 + TypeAlias = type # Fallback for Python < 3.10 without typing_extensions + from TermTk import pyTTkSlot, pyTTkSignal from TermTk import TTkLog from TermTk import TTkMessageBox, TTkFileDialogPicker, TTkHelper, TTkString, TTkK, TTkColor -class ImageData: - size:list[int,int] = (0,0) - data:list[list[list[int,int,int,int]]] = [] +class _TTkEncoding(str, Enum): + ''' Encoding types for cross-platform file operations. -ttkCrossOpen = None -ttkCrossSave = None -ttkCrossSaveAs = None -ttkEmitDragOpen = None -ttkEmitFileOpen = None -ttkConnectDragOpen = None + Defines MIME types and encoding identifiers used by :py:class:`TTkCrossTools` + for file operations. These encodings determine how file content is decoded + when opening and encoded when saving. -class TTkEncoding(str): + Text Encodings: + - **TEXT**: Generic text encoding + - **TEXT_PLAIN**: Plain text MIME type + - **TEXT_PLAIN_UTF8**: UTF-8 encoded plain text + + Application Encodings: + - **APPLICATION**: Generic application data + - **APPLICATION_JSON**: JSON format with automatic parsing + + Image Encodings: + - **IMAGE**: Generic image format + - **IMAGE_PNG**: PNG image format + - **IMAGE_SVG**: SVG vector image format + - **IMAGE_JPG**: JPEG image format + + Note: + Image encodings require PIL/Pillow to be installed. When opening image + files, the decoded data will be a PIL Image object. + ''' TEXT = "text" TEXT_PLAIN = "text/plain" TEXT_PLAIN_UTF8 = "text/plain;charset=utf-8" @@ -58,112 +157,187 @@ class TTkEncoding(str): IMAGE_SVG = 'image/svg+xml' IMAGE_JPG = 'image/jpeg' +class _OpenProtocol(Protocol): + def __call__( + self, + path: str, + encoding: _TTkEncoding, + filter: str, + cb: Optional[TTkCrossTools.TTkCross_Callback_Open] = None + ) -> None: ... + +class _SaveProtocol(Protocol): + def __call__( + self, + filePath: str, + content: str, + encoding: _TTkEncoding + ) -> None: ... + +class _SaveAsProtocol(Protocol): + def __call__( + self, + filePath: str, + content: str, + encoding: _TTkEncoding, + filter: str, + cb: Optional[TTkCrossTools.TTkCross_Callback_Save] = None + ) -> None: ... + +class _EmitDragOpenProtocol(Protocol): + def __call__( + self, + encoding: _TTkEncoding, + data: _CB_Data_Open + ) -> None: ... + +class _EmitFileOpenProtocol(Protocol): + def __call__( + self, + encoding: _TTkEncoding, + data: _CB_Data_Open + ) -> None: ... + +class _ConnectDragOpenProtocol(Protocol): + def __call__( + self, + encoding: _TTkEncoding, + cb: TTkCrossTools.TTkCross_Callback_Open + ) -> None: ... + +@dataclass +class _CB_Data_Save(): + ''' Callback data for save operations. + + :param name: The full path or name of the saved file + :type name: str + ''' + name:str + +@dataclass +class _CB_Data_Open(): + ''' Callback data for open/load operations. + + :param name: The full path or name of the opened file + :type name: str + :param data: The decoded file content. Type depends on the encoding: + - Text encodings: str + - JSON encodings: str (raw JSON content) + - Image encodings: PIL.Image.Image object + :type data: Any + ''' + name:str + data:Any + + if importlib.util.find_spec('pyodideProxy'): TTkLog.info("Using 'pyodideProxy' as clipboard manager") - import pyodideProxy - ttkDragOpen = {} - ttkFileOpen = pyTTkSignal(dict) + import pyodideProxy # type: ignore[import-not-found] + ttkDragOpen:Dict[_TTkEncoding,pyTTkSignal] = {} + ttkFileOpen = pyTTkSignal(_CB_Data_Open) - def _open(path, encoding, filter, cb=None): + def _open(path:str, encoding:_TTkEncoding, filter:str, cb: Optional[TTkCrossTools.TTkCross_Callback_Open] = None) -> None: if not cb: return ttkFileOpen.connect(cb) pyodideProxy.openFile(encoding) - def _save(filePath, content, encoding, filter=None, cb=lambda _d:None): + def _save(filePath: str, content: str, encoding: _TTkEncoding) -> None: pyodideProxy.saveFile(os.path.basename(filePath), content, encoding) - def _connectDragOpen(encoding, cb): - if encoding not in ttkDragOpen: - ttkDragOpen[encoding] = pyTTkSignal(dict) - return ttkDragOpen[encoding].connect(cb) + def _saveAs(filePath:str, content:str, encoding:_TTkEncoding, filter:str, cb: Optional[TTkCrossTools.TTkCross_Callback_Save] = None) -> None: + return _save( + filePath=filePath, + content=content, + encoding=encoding + ) - def _emitDragOpen(encoding, data): - if encoding.startswith(TTkEncoding.IMAGE): + def _emitDragOpen(encoding: _TTkEncoding, data: _CB_Data_Open) -> None: + if encoding.startswith(_TTkEncoding.IMAGE): from PIL import Image import io - im = Image.open(io.BytesIO(data['data'])) - data['data'] = im - for do in [ttkDragOpen[e] for e in ttkDragOpen if encoding.startswith(e)]: - do.emit(data) + im = Image.open(io.BytesIO(data.data)) + data.data = im + for _drag_open in [ttkDragOpen[e] for e in ttkDragOpen if encoding.startswith(e)]: + _drag_open.emit(data) - def _emitFileOpen(encoding, data): - if encoding.startswith(TTkEncoding.IMAGE): + def _emitFileOpen(encoding: _TTkEncoding, data: _CB_Data_Open) -> None: + if encoding.startswith(_TTkEncoding.IMAGE): from PIL import Image import io # TTkLog.debug(data['data']) # TTkLog.debug(type(data['data'])) # Image.open(data['data']) - im = Image.open(io.BytesIO(data['data'])) + im = Image.open(io.BytesIO(data.data)) # TTkLog.debug(f"{im.size}") - data['data'] = im + data.data = im ttkFileOpen.emit(data) ttkFileOpen.clear() - ttkCrossOpen = _open - ttkCrossSave = _save - ttkCrossSaveAs = _save - ttkEmitDragOpen = _emitDragOpen - ttkEmitFileOpen = _emitFileOpen - ttkConnectDragOpen = _connectDragOpen + def _connectDragOpen(encoding: _TTkEncoding, cb: TTkCrossTools.TTkCross_Callback_Open) -> None: + if encoding not in ttkDragOpen: + ttkDragOpen[encoding] = pyTTkSignal(_CB_Data_Open) + return ttkDragOpen[encoding].connect(cb) else: - def _crossDecoder_text(fileName) : + def _crossDecoder_text(fileName: str) -> str: with open(fileName) as fp: return fp.read() - def _crossDecoder_json(fileName) : + + def _crossDecoder_json(fileName: str) -> str: with open(fileName) as fp: # return json.load(fp) return fp.read() - def _crossDecoder_image(fileName): + + def _crossDecoder_image(fileName: str) -> Any: from PIL import Image pilImage = Image.open(fileName) # pilImage = pilImage.convert('RGBA') - # pilData = list(pilImage.getdata()) + # pilData = List(pilImage.getdata()) # data = ImageData() # w,h = data.size = pilImage.size # data.data = [pilData[i:i+w] for i in range(0, len(pilData), w)] return pilImage _crossDecoder = { - TTkEncoding.TEXT : _crossDecoder_text , - TTkEncoding.TEXT_PLAIN : _crossDecoder_text , - TTkEncoding.TEXT_PLAIN_UTF8 : _crossDecoder_text , - TTkEncoding.APPLICATION : _crossDecoder_json , - TTkEncoding.APPLICATION_JSON : _crossDecoder_json , - TTkEncoding.IMAGE : _crossDecoder_image , - TTkEncoding.IMAGE_PNG : _crossDecoder_image , - TTkEncoding.IMAGE_SVG : _crossDecoder_image , - TTkEncoding.IMAGE_JPG : _crossDecoder_image , + _TTkEncoding.TEXT : _crossDecoder_text , + _TTkEncoding.TEXT_PLAIN : _crossDecoder_text , + _TTkEncoding.TEXT_PLAIN_UTF8 : _crossDecoder_text , + _TTkEncoding.APPLICATION : _crossDecoder_json , + _TTkEncoding.APPLICATION_JSON : _crossDecoder_json , + _TTkEncoding.IMAGE : _crossDecoder_image , + _TTkEncoding.IMAGE_PNG : _crossDecoder_image , + _TTkEncoding.IMAGE_SVG : _crossDecoder_image , + _TTkEncoding.IMAGE_JPG : _crossDecoder_image , } - def _open(path, encoding:TTkEncoding, filter:str, cb=None): + def _open(path:str, encoding:_TTkEncoding, filter:str, cb: Optional[TTkCrossTools.TTkCross_Callback_Open] = None) -> None: if not cb: return - if encoding.startswith(TTkEncoding.IMAGE): + if encoding.startswith(_TTkEncoding.IMAGE): if not importlib.util.find_spec('PIL'): return - def __openFile(fileName): + def __openFile(fileName: str) -> None: _decoder = _crossDecoder.get(encoding,lambda _:None) content = _decoder(fileName) - cb({'name':fileName, 'data':content}) + cb(TTkCrossTools.CB_Data_Open(name=fileName, data=content)) filePicker = TTkFileDialogPicker(pos = (3,3), size=(100,30), caption="Open", path=path, fileMode=TTkK.FileMode.ExistingFile ,filter=filter) filePicker.pathPicked.connect(__openFile) TTkHelper.overlay(None, filePicker, 5, 5, True) - def _save(filePath, content, encoding): + def _save(filePath:str, content:str, encoding:_TTkEncoding) -> None: TTkLog.info(f"Saving to: {filePath}") with open(filePath,'w') as fp: fp.write(content) - def _saveAs(filePath, content, encoding, filter, cb=lambda _d:None): - def _approveFile(fileName): + def _saveAs(filePath:str, content:str, encoding:_TTkEncoding, filter:str, cb: Optional[TTkCrossTools.TTkCross_Callback_Save] = None) -> None: + def _approveFile(fileName: str) -> None: if os.path.exists(fileName): @pyTTkSlot(TTkMessageBox.StandardButton) def _cb(btn): if btn == TTkMessageBox.StandardButton.Save: - ttkCrossSave(fileName,content,encoding) + _save(fileName,content,encoding) elif btn == TTkMessageBox.StandardButton.Cancel: return if cb: - cb({'name':fileName}) + cb(TTkCrossTools.CB_Data_Save(name=fileName)) messageBox = TTkMessageBox( text= ( TTkString( f'A file named "{os.path.basename(fileName)}" already exists.\nDo you want to replace it?', TTkColor.BOLD) + @@ -173,9 +347,9 @@ else: messageBox.buttonSelected.connect(_cb) TTkHelper.overlay(None, messageBox, 5, 5, True) else: - ttkCrossSave(fileName,content,encoding) + _save(fileName,content,encoding) if cb: - cb({'name':fileName}) + cb(TTkCrossTools.CB_Data_Save(name=fileName)) filePicker = TTkFileDialogPicker( size=(100,30), path=filePath, acceptMode=TTkK.AcceptMode.AcceptSave, @@ -185,9 +359,107 @@ else: filePicker.pathPicked.connect(_approveFile) TTkHelper.overlay(None, filePicker, 5, 5, True) - ttkCrossOpen = _open - ttkCrossSave = _save - ttkCrossSaveAs = _saveAs - ttkEmitDragOpen = lambda a:None - ttkEmitFileOpen = lambda a:None - ttkConnectDragOpen = lambda a,b:None + def _emitDragOpen(encoding: _TTkEncoding, data: _CB_Data_Open) -> None: + pass + + def _emitFileOpen(encoding: _TTkEncoding, data: _CB_Data_Open) -> None: + pass + + def _connectDragOpen(encoding: _TTkEncoding, cb: TTkCrossTools.TTkCross_Callback_Open) -> None: + pass + + +class TTkCrossTools(): + ''' Cross-platform file operations and clipboard management. + + Provides a unified API for file I/O operations across desktop and web platforms. + Automatically adapts to the runtime environment (native Python or Pyodide/WASM) + and provides appropriate file dialogs and file handling mechanisms. + + Class Attributes: + Encoding: Alias for :py:class:`_TTkEncoding` enum + CB_Data_Save: Type alias for save callback data + CB_Data_Open: Type alias for open callback data + TTkCross_Callback: Generic callback type + TTkCross_Callback_Open: Callback type for open operations + TTkCross_Callback_Save: Callback type for save operations + + Methods: + open: Open a file with automatic dialog and decoding + save: Save content to a file + saveAs: Save with file picker dialog + ttkEmitDragOpen: Emit drag-drop open events (web only) + ttkEmitFileOpen: Emit file open events (web only) + ttkConnectDragOpen: Connect to drag-drop events (web only) + + Platform Behavior: + **Desktop Mode**: + - Uses :py:class:`TTkFileDialogPicker` for file selection + - Direct filesystem access via Python's built-in file operations + - Automatic confirmation dialogs for overwrite operations + + **Web Mode (Pyodide)**: + - Uses JavaScript file input elements via pyodideProxy + - Downloads trigger browser's save dialog + - Drag-drop integration for file loading + + Usage Example: + .. code-block:: python + + # Open a text file + def on_open(data: TTkCrossTools.CB_Data_Open): + print(f"File: {data.name}") + print(f"Content: {data.data}") + + TTkCrossTools.open( + path="/home/user", + encoding=TTkCrossTools.Encoding.TEXT_PLAIN, + filter="Text Files (*.txt);;All Files (*)", + cb=on_open + ) + + # Save with confirmation + def on_save(data: TTkCrossTools.CB_Data_Save): + print(f"Saved to: {data.name}") + + TTkCrossTools.saveAs( + filePath="document.txt", + content="Hello, World!", + encoding=TTkCrossTools.Encoding.TEXT_PLAIN, + filter="Text Files (*.txt)", + cb=on_save + ) + + # Open an image + def on_image(data: TTkCrossTools.CB_Data_Open): + pil_image = data.data # PIL.Image.Image object + print(f"Image size: {pil_image.size}") + + TTkCrossTools.open( + path=".", + encoding=TTkCrossTools.Encoding.IMAGE_PNG, + filter="PNG Images (*.png)", + cb=on_image + ) + + Note: + Image operations require PIL/Pillow to be installed. The library will + gracefully handle missing dependencies by skipping image operations. + ''' + Encoding = _TTkEncoding + CB_Data_Save: TypeAlias = _CB_Data_Save + CB_Data_Open: TypeAlias = _CB_Data_Open + + # Type alias for callback functions that receive file data + TTkCross_Callback: TypeAlias = Callable[[Dict[str, Any]], None] + TTkCross_Callback_Open: TypeAlias = Callable[[CB_Data_Open], None] + TTkCross_Callback_Save: TypeAlias = Callable[[CB_Data_Save], None] + + + + open: _OpenProtocol = _open + save: _SaveProtocol = _save + saveAs: _SaveAsProtocol = _saveAs + ttkEmitDragOpen: _EmitDragOpenProtocol = _emitDragOpen + ttkEmitFileOpen: _EmitFileOpenProtocol = _emitFileOpen + ttkConnectDragOpen: _ConnectDragOpenProtocol = _connectDragOpen diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkPickers/messagebox.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkPickers/messagebox.py index 3dd039c7..f62cde2d 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkPickers/messagebox.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkPickers/messagebox.py @@ -22,6 +22,8 @@ __all__ = ['TTkMessageBox'] +from enum import IntEnum, Flag + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.signal import pyTTkSignal,pyTTkSlot from TermTk.TTkCore.color import TTkColor @@ -37,7 +39,7 @@ from TermTk.TTkWidgets.label import TTkLabel from TermTk.TTkWidgets.button import TTkButton class TTkMessageBox(TTkWindow): - class Icon(int): + class Icon(IntEnum): NoIcon = 0 '''the message box does not have any icon.''' Question = 4 @@ -49,7 +51,7 @@ class TTkMessageBox(TTkWindow): Critical = 3 '''an icon indicating that the message represents a critical problem.''' - class StandardButton(int): + class StandardButton(Flag): Ok = 0x00000400 '''An "OK" button defined with the AcceptRole.''' Open = 0x00002000 diff --git a/tools/webExporter/js/ttkproxy.js b/tools/webExporter/js/ttkproxy.js index a6308343..14168ec1 100644 --- a/tools/webExporter/js/ttkproxy.js +++ b/tools/webExporter/js/ttkproxy.js @@ -228,12 +228,24 @@ class TTkProxy { def ttk_dragOpen(data): data = data.to_py() - ttk.ttkEmitDragOpen(data['type'],data) + ttk.TTkCrossTools.ttkEmitDragOpen( + data['type'], + ttk.TTkCrossTools.CB_Data_Open( + name=data['name'], + data=data['data'], + ) + ) # ttk_log(f"{type(data.to_py())=}, {str(data.to_py())}") def ttk_fileOpen(data): data = data.to_py() - ttk.ttkEmitFileOpen(data['type'],data) + ttk.TTkCrossTools.ttkEmitFileOpen( + data['type'], + ttk.TTkCrossTools.CB_Data_Open( + name=data['name'], + data=data['data'], + ) + ) # ttk_log(f"{type(data.to_py())=}, {str(data.to_py())}") def ttk_input(val):