Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
7caae71
Add new Web backend
Mar 28, 2026
087ea1d
Add web backend support to the selector
Mar 28, 2026
b1159f9
Add web backend to the advertised subpackages
Mar 28, 2026
935e23e
Fixed icon button rendering via XDG icon lookup endpoint
Mar 28, 2026
dd5ebbe
Fixed event listener loss after widget DOM replacement
Mar 28, 2026
d1d4c04
Added busy overlay via busyCursor/normalCursor WebSocket message
Mar 28, 2026
b32bb1c
Fixed busy overlay dismissed before widget updates arrive in browser
Mar 28, 2026
8949d2a
Silenced BrokenPipeError on static/icon serving (client disconnect)
Mar 28, 2026
c0a3abb
Added client-side pagination, search and sticky header to YTableWeb
Mar 29, 2026
3d1f500
Fixed table disappearing on narrow viewport (missing min-width:0 on f…
Mar 29, 2026
0eaed78
Rendered treeweb as paginated table; added responsive mobile layout
Mar 29, 2026
b47dc0f
Improved responsiveness: yhbox wraps, paned sections padded, mobile d…
Mar 29, 2026
08b401f
Fixed About dialog button unresponsive after resize: removed harmful …
Mar 29, 2026
f7df329
Intercept YRichText link clicks: prevent navigation, post YMenuEvent
Mar 29, 2026
d9a9e0a
Replace YUI_BACKEND with MUI_BACKEND
Mar 29, 2026
facf7d2
Add checkbox column support to YTableWeb
Mar 30, 2026
8f8f8f9
Fixed YRichText shifting on update: use innerHTML action to avoid re-…
Mar 30, 2026
e946dd2
Extended _flush_update to support 3-tuple render_update() with custom…
Mar 30, 2026
ff7fb13
Fixed paned divider collapsing and improved YRichText responsiveness
Mar 30, 2026
506b454
Collapse paned section and divider when child widget is hidden
Mar 30, 2026
3eee365
YLabelWeb now has setAutoWrap(on) and autoWrap()
Mar 30, 2026
ac36dba
Add thread-local initial-render context flag to commonweb
Mar 30, 2026
c76fcca
Set initial-render flag around dialog.render() in HTTP handler
Mar 30, 2026
200238e
Handle ready message and push deferred table rows on WS connect
Mar 30, 2026
84ea32c
Defer table row rendering: skeleton on initial load, rows pushed via …
Mar 30, 2026
20ae200
Send ready on WS open; handle rows action to inject deferred table co…
Mar 30, 2026
b48c125
Add loading row style for deferred table skeleton
Mar 30, 2026
0a61d66
Harden WebSocket: origin check, recv_exact, frame size cap, CSRF guar…
Mar 30, 2026
4222c50
Add shutdown overlay element to base template
Apr 3, 2026
4b13b28
Add shutdown overlay style
Apr 3, 2026
1cb4cba
Handle shutdown message: show overlay, suppress reconnect loop
Apr 3, 2026
9c45ba5
Guard _open_dialogs with RLock to fix thread-safety race condition
Apr 3, 2026
a740bd8
Drop redundant header
Apr 3, 2026
447db97
Add new Web backend
Mar 28, 2026
bfe4011
Add web backend support to the selector
Mar 28, 2026
b84fdda
Add web backend to the advertised subpackages
Mar 28, 2026
a4ff156
Fixed icon button rendering via XDG icon lookup endpoint
Mar 28, 2026
3e871ec
Fixed event listener loss after widget DOM replacement
Mar 28, 2026
fb005b4
Added busy overlay via busyCursor/normalCursor WebSocket message
Mar 28, 2026
692d8f2
Fixed busy overlay dismissed before widget updates arrive in browser
Mar 28, 2026
a961e4a
Silenced BrokenPipeError on static/icon serving (client disconnect)
Mar 28, 2026
73df3a0
Added client-side pagination, search and sticky header to YTableWeb
Mar 29, 2026
cb19a01
Fixed table disappearing on narrow viewport (missing min-width:0 on f…
Mar 29, 2026
fc864db
Rendered treeweb as paginated table; added responsive mobile layout
Mar 29, 2026
335b507
Improved responsiveness: yhbox wraps, paned sections padded, mobile d…
Mar 29, 2026
62fdaa5
Fixed About dialog button unresponsive after resize: removed harmful …
Mar 29, 2026
4f81a52
Intercept YRichText link clicks: prevent navigation, post YMenuEvent
Mar 29, 2026
98ff7a1
Replace YUI_BACKEND with MUI_BACKEND
Mar 29, 2026
be1b6c7
Add checkbox column support to YTableWeb
Mar 30, 2026
037bb6e
Fixed YRichText shifting on update: use innerHTML action to avoid re-…
Mar 30, 2026
fb43f38
Extended _flush_update to support 3-tuple render_update() with custom…
Mar 30, 2026
3f5e047
Fixed paned divider collapsing and improved YRichText responsiveness
Mar 30, 2026
a194e3f
Collapse paned section and divider when child widget is hidden
Mar 30, 2026
127d615
YLabelWeb now has setAutoWrap(on) and autoWrap()
Mar 30, 2026
bcc2162
Add thread-local initial-render context flag to commonweb
Mar 30, 2026
909295d
Set initial-render flag around dialog.render() in HTTP handler
Mar 30, 2026
7765b4d
Handle ready message and push deferred table rows on WS connect
Mar 30, 2026
3206ab9
Defer table row rendering: skeleton on initial load, rows pushed via …
Mar 30, 2026
2f1d196
Send ready on WS open; handle rows action to inject deferred table co…
Mar 30, 2026
100dca0
Add loading row style for deferred table skeleton
Mar 30, 2026
a2e296d
Harden WebSocket: origin check, recv_exact, frame size cap, CSRF guar…
Mar 30, 2026
5607231
Add shutdown overlay element to base template
Apr 3, 2026
5acc91a
Add shutdown overlay style
Apr 3, 2026
8f81895
Handle shutdown message: show overlay, suppress reconnect loop
Apr 3, 2026
af10b97
Guard _open_dialogs with RLock to fix thread-safety race condition
Apr 3, 2026
9237900
Drop redundant header
Apr 3, 2026
ee3ecc7
merged from main
Apr 6, 2026
23401d5
Fixed typo
Apr 6, 2026
574524b
Merge origin/main: updated translation files
Apr 7, 2026
4367ef2
Merge origin/main: wayland desktop_file_name, translations
Apr 7, 2026
933adea
Merge origin/web_backend: rebase reconciliation, keep local main-merg…
Apr 7, 2026
d569339
Fix comment wording and alignment in backend map
Apr 7, 2026
5cab5b5
Remove page-size selector from treeweb controls bar
Apr 7, 2026
5f760d6
Align table search input to the right; fix controls overlap with sibl…
Apr 7, 2026
589ad06
Add missing setProperty/getProperty/_set_backend_enabled to YFrameWeb…
Apr 7, 2026
3c84806
Fix YAlignment vertical stretch and YFrame responsiveness/overlap
Apr 7, 2026
0f95320
Track skeleton state per table; only push deferred rows when skeleton…
Apr 8, 2026
743bd59
Guard _push_deferred_tables: skip non-skeleton tables, catch per-widg…
Apr 8, 2026
91337ea
Fix YImageWeb: resolve file paths as data URIs, icon names via /icon/…
Apr 8, 2026
b93f1e0
Pass fallBackName in createImage factory method
Apr 8, 2026
1e6f4a2
Fix YImage CSS: container uses flexbox, img element carries sizing rules
Apr 8, 2026
45a6d1d
Make YFrame and YAlignment responsive: wrap in HBox instead of collap…
Apr 8, 2026
694fbae
Rewrite YDumbTabWeb: extend YSelectionWidget, Bootstrap 5.3 nav-tabs,…
Apr 9, 2026
a3804e5
Replace custom tab CSS with Bootstrap 5.3 nav-tabs; add dumbtab conte…
Apr 9, 2026
7bd5265
Update handleTabClick to toggle Bootstrap active class and aria-selec…
Apr 9, 2026
9bef400
Cap /event POST body at 64 KB to prevent memory exhaustion
Apr 15, 2026
220b100
Fix ComboBox option value= escaping: use html.escape(quote=True) to h…
Apr 15, 2026
28cf8a3
Send selectedValue in handleSelectChange so server label-based lookup…
Apr 15, 2026
51ee5d5
Add HTTP security headers: CSP, X-Frame-Options, X-Content-Type-Optio…
Apr 15, 2026
ce3c7f7
Replace inline onclick on reload button with id for CSP compliance
Apr 15, 2026
eaec3d1
Wire reload button via JS; add paned splitter drag with mouse and tou…
Apr 15, 2026
af9e7ba
Add drag-state cursor lock CSS for paned splitter
Apr 15, 2026
0eeb559
Fix YTableQt popup: init attributes before super() so _create_backend…
Apr 15, 2026
d5170bc
Fix YTreeQt: init attrs before super() to survive early _create_backe…
Apr 15, 2026
d4c762e
Fix popup: never setParent(None) on visible widget in _attach_child_b…
Apr 15, 2026
f1f964d
Guard _items access in rebuildTable with getattr for early _create_ba…
Apr 15, 2026
9a7d2ee
Merge origin/main: tooltips, paned multi-layout, backend detection, p…
Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manatools/aui/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
_logger = logging.getLogger("manatools.aui.backends")

# advertised subpackages (may be absent if not installed)
__all__ = ["gtk", "qt", "ncurses"]
__all__ = ["gtk", "qt", "ncurses", "web"]

def __getattr__(name: str) -> ModuleType:
"""
Expand Down
12 changes: 6 additions & 6 deletions manatools/aui/backends/qt/replacepointqt.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,13 @@ def _attach_child_backend(self):
except Exception:
pass
except Exception:
# fallback: try remove any previous parent then add
# fallback: widget may already be in the layout; check indexOf
# before re-adding. Never call setParent(None) on a visible
# widget — that promotes it to a top-level window (popup).
try:
try:
cw.setParent(None)
except Exception:
pass
self._layout.addWidget(cw)
if self._layout.indexOf(cw) < 0:
cw.setParent(self._backend_widget)
self._layout.addWidget(cw)
except Exception:
self._logger.exception("_attach_child_backend: addWidget fallback failed")
# Encourage the child to expand so content becomes visible
Expand Down
31 changes: 18 additions & 13 deletions manatools/aui/backends/qt/tableqt.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,20 +383,23 @@ class YTableQt(YSelectionWidget):
"""

def __init__(self, parent, header: YTableHeader, multiSelection=False):
super().__init__(parent)
# All instance attributes must be set BEFORE super().__init__(parent).
# YWidget.__init__ calls parent.addChild(self), which causes
# YDumbTabQt.addChild → get_backend_widget() → _create_backend_widget()
# while this constructor is still executing. _create_backend_widget()
# must find every attribute it reads already in place.
if header is None:
raise ValueError("YTableQt requires a YTableHeader")
self._header = header
self._multi = bool(multiSelection)
# Force single-selection when any checkbox column is present.
if self._header is not None:
try:
for c_idx in range(self._header.columns()):
if self._header.isCheckboxColumn(c_idx):
self._multi = False
break
except Exception:
pass
else:
raise ValueError("YTableQt requires a YTableHeader")
try:
for c_idx in range(self._header.columns()):
if self._header.isCheckboxColumn(c_idx):
self._multi = False
break
except Exception:
pass

self._view = None # QTableView
self._model = None # _YTableModel
Expand All @@ -407,6 +410,8 @@ def __init__(self, parent, header: YTableHeader, multiSelection=False):
self._logger = logging.getLogger(f"manatools.aui.qt.{self.__class__.__name__}")
self._changed_item = None

super().__init__(parent) # may trigger _create_backend_widget via addChild

def widgetClass(self):
return "YTable"

Expand Down Expand Up @@ -512,8 +517,8 @@ def rebuildTable(self):
beginResetModel/endResetModel does not create any widgets — the view
repaints only the visible viewport (O(visible×cols)).
"""
self._logger.debug("rebuildTable: %d items",
len(self._items) if self._items else 0)
_items = getattr(self, '_items', None) or []
self._logger.debug("rebuildTable: %d items", len(_items))
if self._model is None:
self._create_backend_widget()
return # _create_backend_widget calls rebuildTable recursively
Expand Down
14 changes: 10 additions & 4 deletions manatools/aui/backends/qt/treeqt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ class YTreeQt(YSelectionWidget):
- recursiveSelection if it should select children recursively
"""
def __init__(self, parent=None, label="", multiSelection=False, recursiveSelection=False):
super().__init__(parent)
# All instance attributes must be set BEFORE super().__init__(parent).
# YDumbTabQt.addChild calls get_backend_widget() synchronously when
# its content area already exists, which triggers _create_backend_widget()
# before the subclass constructor has had a chance to set its own attrs.
self._label = label
self._multi = bool(multiSelection)
self._recursive = bool(recursiveSelection)
if self._recursive:
self._multi = True # recursive selection implies multi-selection
self._immediate = self.notify()
self._backend_widget = None
self._tree_widget = None
# mappings between QTreeWidgetItem and logical YTreeItem (python objects in self._items)
Expand All @@ -42,7 +44,10 @@ def __init__(self, parent=None, label="", multiSelection=False, recursiveSelecti
self._last_selected_qitems = set()
# track logical selected item ids to preserve across rebuilds/swaps
self._last_selected_ids = set()
self._logger = logging.getLogger(f"manatools.aui.qt.{self.__class__.__name__}")
self._logger = logging.getLogger(f"manatools.aui.qt.{self.__class__.__name__}")

super().__init__(parent) # may trigger _create_backend_widget via YDumbTabQt.addChild
self._immediate = self.notify()

def widgetClass(self):
return "YTree"
Expand Down Expand Up @@ -83,7 +88,8 @@ def rebuildTree(self):

def _rebuildTree(self):
"""Rebuild the QTreeWidget from self._items (calls helper recursively)."""
self._logger.debug("rebuildTree: rebuilding tree with %d items", len(self._items) if self._items else 0)
_items = getattr(self, '_items', None) or []
self._logger.debug("rebuildTree: rebuilding tree with %d items", len(_items))
self._suppress_selection_handler = True
if self._tree_widget is None:
# ensure backend exists
Expand Down
212 changes: 212 additions & 0 deletions manatools/aui/backends/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# ManaTools Web Backend

A web-based backend for python-manatools AUI that renders applications in a web browser via HTTP/WebSocket.

## Overview

This package adds a new `web` backend to python-manatools, allowing any ManaTools application to be accessed through a web browser without any code changes.

## Usage

### Running with Web Backend

Set the `MUI_BACKEND` environment variable to `web`:

```bash
# Linux/macOS
export MUI_BACKEND=web
python your_app.py

# Windows (Command Prompt)
set MUI_BACKEND=web
python your_app.py

# Windows (PowerShell)
$env:MUI_BACKEND="web"
python your_app.py
```

When the application starts, it will display a URL:

```
==================================================
Dialog available at: http://127.0.0.1:8080/
Open this URL in your web browser
==================================================
```

Open this URL in any modern web browser to interact with the application.

### Application Code

No changes are needed to your application code! The same code works with Qt, GTK, curses, or web backend:

```python
from manatools.aui.yui import YUI

factory = YUI.widgetFactory()
dialog = factory.createMainDialog()
vbox = factory.createVBox(dialog)

factory.createLabel(vbox, "Hello, World!")
button = factory.createPushButton(vbox, "&OK")

dialog.open()

while True:
event = dialog.waitForEvent()
if event.widget() == button:
break

dialog.destroy()
```

## Architecture

```
Browser (HTML/CSS/JS)
│ WebSocket / HTTP
┌─────────────────────────────┐
│ WebServer (Python) │
│ - HTTP: Serve HTML/CSS/JS │
│ - WebSocket: Real-time │
└─────────────┬───────────────┘
┌─────────────────────────────┐
│ YDialogWeb │
│ - Event queue │
│ - Widget tree → HTML │
│ - waitForEvent() blocks │
└─────────────────────────────┘
```

## Widget Support

All standard ManaTools widgets are supported:

| Widget | Status | Notes |
|--------|--------|-------|
| YDialog | ✅ | Main and popup dialogs |
| YVBox, YHBox | ✅ | Layout containers |
| YLabel | ✅ | Text and headings |
| YPushButton | ✅ | With icons and shortcuts |
| YInputField | ✅ | Text and password mode |
| YCheckBox | ✅ | |
| YRadioButton | ✅ | |
| YComboBox | ✅ | Dropdown selection |
| YSelectionBox | ✅ | List selection |
| YFrame | ✅ | Grouped content |
| YCheckBoxFrame | ✅ | Toggleable frame |
| YProgressBar | ✅ | |
| YSlider | ✅ | |
| YTable | ✅ | |
| YTree | ✅ | |
| YRichText | ✅ | HTML content |
| YMenuBar | ✅ | |
| YImage | ✅ | |
| YIntField | ✅ | Number input |
| YDateField | ✅ | Date picker |
| YTimeField | ✅ | Time picker |
| YMultiLineEdit | ✅ | Textarea |
| YLogView | ✅ | Log display |
| YDumbTab | ✅ | Tab bar |
| YPaned | ✅ | Split panes |
| YSpacing | ✅ | Layout spacing |
| YAlignment | ✅ | Content alignment |
| YReplacePoint | ✅ | Dynamic content |

## File Structure

```
manatools/aui/
├── yui.py # Modified: Added Backend.WEB
├── yui_web.py # NEW: YUIWeb, YWidgetFactoryWeb, YApplicationWeb
└── backends/
├── __init__.py # Modified: Added "web" to __all__
└── web/ # NEW: All web backend files
├── __init__.py
├── commonweb.py
├── server.py
├── dialogweb.py
├── vboxweb.py
├── hboxweb.py
├── labelweb.py
├── pushbuttonweb.py
├── inputfieldweb.py
├── checkboxweb.py
├── comboboxweb.py
├── selectionboxweb.py
├── frameweb.py
├── progressbarweb.py
├── alignmentweb.py
├── spacingweb.py
├── treeweb.py
├── tableweb.py
├── richtextweb.py
├── menubarweb.py
├── replacepointweb.py
├── checkboxframeweb.py
├── radiobuttonweb.py
├── intfieldweb.py
├── multilineditweb.py
├── imageweb.py
├── dumbtabweb.py
├── sliderweb.py
├── datefieldweb.py
├── timefieldweb.py
├── logviewweb.py
├── panedweb.py
└── static/
├── css/
│ └── manatools.css
└── js/
└── manatools.js
```

## Browser Support

- Chrome/Chromium (recommended)
- Firefox
- Safari
- Edge

WebSocket is required for real-time updates. Falls back to HTTP POST for older browsers.

## Configuration

The web server binds to `127.0.0.1` (localhost only) by default. To allow remote access, modify `server.py`:

```python
self._server = WebServer(self, host="0.0.0.0", port=8080)
```

⚠️ **Security Warning**: Allowing remote access exposes your application to the network. Consider adding authentication for production use.

## Dependencies

**None!** The web backend uses only Python standard library:
- `http.server` - HTTP serving
- `threading` - Background server
- `queue` - Event queue
- `json` - WebSocket messages
- `hashlib`, `base64`, `struct` - WebSocket protocol

## Limitations

- File dialogs (`askForExistingFile`, etc.) are not supported in the browser
- Window positioning/sizing is handled by the browser
- System tray integration is not available
- Native menus use HTML menus instead

## License

LGPLv2+ (same as python-manatools)

## Author

Matteo Pasotti <xquiet@coriolite.com>

Based on python-manatools by Angelo Naselli <anaselli@linux.it>
Loading