You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
17 KiB
417 lines
17 KiB
# MIT License |
|
# |
|
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
# of this software and associated documentation files (the "Software"), to deal |
|
# in the Software without restriction, including without limitation the rights |
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
# copies of the Software, and to permit persons to whom the Software is |
|
# furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included in all |
|
# copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
# SOFTWARE. |
|
|
|
import os |
|
|
|
import TermTk as ttk |
|
from TermTk.TTkCore.canvas import TTkCanvas |
|
|
|
from .superobj.superwidgetmenubutton import SuperWidgetMenuButton |
|
|
|
|
|
class _MenuItem(ttk.TTkWidget): |
|
''' Generic Control Button for the MenuEditor |
|
|
|
[Menu[+][x] |
|
|
|
''' |
|
__slots__ = ('_text', '_lineEdit', '_autoResize', '_menuButton', '_superMenuButton', '_designer', |
|
# Signals |
|
'closeClicked') |
|
def __init__(self, menuButton, designer, autoResize=True, *args, **kwargs): |
|
self._text = ttk.TTkString(menuButton.text()) |
|
self._menuButton = menuButton |
|
self._autoResize = autoResize |
|
self._designer = designer |
|
self._superMenuButton = SuperWidgetMenuButton.factoryGetSuperWidgetMenuButton(wid=menuButton, designer=self._designer) |
|
self.closeClicked = ttk.pyTTkSignal(_MenuItem) |
|
super().__init__(*args, **(kwargs|{'size':(self._text.termWidth()+7,1)})) |
|
self.processWidgetName(self._menuButton.name()) |
|
self._lineEdit = ttk.TTkLineEdit(parent=self, visible=False, text=self._text) |
|
self._lineEdit.returnPressed.connect(self._lineEdit.hide) |
|
self._lineEdit.textEdited.connect(self._textEdited) |
|
self._lineEdit.focusChanged.connect(self._lineEdit.setVisible) |
|
|
|
def processWidgetName(self, name): |
|
names = {w.name() for w in self._designer.getWidgets() if w is not self._menuButton} |
|
index = 1 |
|
className = name |
|
while className in names: |
|
className = f"{name}-{index}" |
|
index += 1 |
|
self._menuButton.setName(className) |
|
|
|
@ttk.pyTTkSlot(str) |
|
def _textEdited(self, text): |
|
self._text = text |
|
self._menuButton.setText(text) |
|
width = text.termWidth() |
|
if self._autoResize: |
|
self.resize(width+7,1) |
|
self._lineEdit.setGeometry(1,0,width,1) |
|
self.processWidgetName(f"menu_{text}") |
|
self._designer.weModified.emit() |
|
self.update() |
|
|
|
def mouseDoubleClickEvent(self, evt) -> bool: |
|
w,h = self.size() |
|
if evt.x <= w-7: |
|
self._lineEdit.setText(self._text) |
|
self._lineEdit.setGeometry(1,0,w-7,1) |
|
self._lineEdit.show() |
|
self._lineEdit.setFocus() |
|
return True |
|
|
|
def expandMenuItem(self): |
|
subMenuEditor = _SubMenuEditor(size=(20,10), menuButton=self._menuButton, designer=self._designer) |
|
subMenuEditor.itemsChanged.connect(self.update) |
|
wi = subMenuEditor.widgetItem() |
|
wi.setLayer(wi.LAYER1) |
|
w = self.width() |
|
ttk.TTkHelper.overlay(self, subMenuEditor, w-3, 0) |
|
|
|
def mouseReleaseEvent(self, evt) -> bool: |
|
w = self.width() |
|
if evt.x > w-4: |
|
self.closeClicked.emit(self) |
|
elif evt.x > w-7: |
|
self.expandMenuItem() |
|
else: |
|
self._designer.thingSelected.emit(self._menuButton,self._superMenuButton) |
|
return True |
|
|
|
def paintEvent(self, canvas: TTkCanvas): |
|
w = self.width() |
|
text = ( |
|
ttk.TTkString( "[", ttk.TTkColor.fg("#FFFF66"))+ |
|
ttk.TTkString( self._text, ttk.TTkColor.RST)) |
|
canvas.drawText(text=text) |
|
expandIcon = ">" if len(self._menuButton._submenu)> 0 else "+" |
|
text = ( |
|
ttk.TTkString( "[", ttk.TTkColor.fg("#AAAA44"))+ |
|
ttk.TTkString( expandIcon, ttk.TTkColor.fg("#00FF00"))+ |
|
ttk.TTkString( "][", ttk.TTkColor.fg("#AAAA44"))+ |
|
ttk.TTkString( "x", ttk.TTkColor.fg("#FF0000"))+ |
|
ttk.TTkString( "]", ttk.TTkColor.fg("#FFFF66"))) |
|
canvas.drawText(text=text, pos=(w-6,0)) |
|
|
|
|
|
class _SubMenuSpacer(ttk.TTkWidget): |
|
''' Generic Control Splitter for the MenuEditor |
|
|
|
---------[x] |
|
|
|
''' |
|
__slots__ = ('closeClicked', '_menuButton') |
|
def __init__(self, menuSpacer, *args, **kwargs): |
|
self._menuButton = menuSpacer |
|
self.closeClicked = ttk.pyTTkSignal(_MenuItem) |
|
super().__init__(*args, **kwargs) |
|
|
|
def mouseReleaseEvent(self, evt) -> bool: |
|
w = self.width() |
|
if evt.x > w-4: |
|
self.closeClicked.emit(self) |
|
return True |
|
|
|
def paintEvent(self, canvas): |
|
w = self.width() |
|
canvas.drawText(pos=(0,0), text="-"*self.width()) |
|
text = ( |
|
ttk.TTkString( "[", ttk.TTkColor.fg("#AAAA44"))+ |
|
ttk.TTkString( "x", ttk.TTkColor.fg("#FF0000"))+ |
|
ttk.TTkString( "]", ttk.TTkColor.fg("#FFFF66"))) |
|
canvas.drawText(text=text, pos=(w-3,0)) |
|
|
|
|
|
class _SubMenuAreaWidget(ttk.TTkAbstractScrollView): |
|
''' |
|
┌────────┤Left├────────╥──────── |
|
│╿+╿[menu[>]┌──────────────────┐ |
|
│╽+╽<XXXXXXX│[menu1 [+][x]│ |
|
└───────────│[menu2 [+][x]│ |
|
════════════│---------------[x]│ |
|
│[menu3 [+][x]│ |
|
────────────│[menu4 [+][x]│ |
|
│[ Add Menu ]│ |
|
────────────│[ Add Spacer ]│ |
|
────────────│ │ |
|
└──────────────────┘ |
|
''' |
|
__slots__ = ('_items', '_menuButton', '_minWith', |
|
'_btnAddSpacer', '_btnAddMenu', '_designer', |
|
#Signals |
|
'itemsChanged') |
|
def __init__(self, menuButton, designer, **kwargs): |
|
self.itemsChanged = ttk.pyTTkSignal(list) |
|
self._items = [] |
|
self._designer = designer |
|
self._menuButton = menuButton |
|
self._minWidth = 0 |
|
self._btnAddSpacer = ttk.TTkButton(text="Add Spacer") |
|
self._btnAddMenu = ttk.TTkButton(text="Add Menu") |
|
super().__init__(**kwargs) |
|
self.layout().addWidget(self._btnAddSpacer) |
|
self.layout().addWidget(self._btnAddMenu ) |
|
self.setFocusPolicy(ttk.TTkK.ClickFocus + ttk.TTkK.TabFocus) |
|
self.viewChanged.connect(self._viewChangedHandler) |
|
self._btnAddSpacer.clicked.connect(self.addSpacer) |
|
self._btnAddMenu.clicked.connect(self.addMenu) |
|
for item in self._menuButton._submenu: |
|
self._importMenuItem(item) |
|
|
|
def close(self): |
|
self.itemsChanged.clear() |
|
return super().close() |
|
|
|
def _resizeEvent(self): |
|
w,h = self.size() |
|
w = max(w,self._minWidth) |
|
for i,wid in enumerate(self._items): |
|
wid.setGeometry(0,i,w,1) |
|
yy = len(self._items) |
|
self._btnAddMenu.setGeometry( 0, yy, w, 1) |
|
self._btnAddSpacer.setGeometry(0, yy+1, w, 1) |
|
|
|
def resizeEvent(self, w, h): |
|
self._resizeEvent() |
|
|
|
def _importMenuItem(self,item): |
|
if issubclass(type(item),ttk.TTkMenuButton): |
|
item = _MenuItem(menuButton=item, autoResize=True, designer=self._designer) |
|
else: |
|
item = _SubMenuSpacer(menuSpacer=item) |
|
self._items.append(item) |
|
self._addMenuItem(item) |
|
|
|
@ttk.pyTTkSlot(_MenuItem) |
|
def removeMenuItem(self, item): |
|
self._items.pop(self._items.index(item)) |
|
self.layout().removeWidget(item) |
|
self._menuButton.removeMenuItem(item._menuButton) |
|
item.closeClicked.disconnect(self.removeMenuItem) |
|
self._resizeEvent() |
|
self.itemsChanged.emit(self._items) |
|
self._designer.weModified.emit() |
|
|
|
def _addMenuItem(self, item): |
|
item.closeClicked.clear() |
|
item.closeClicked.connect(self.removeMenuItem) |
|
self.layout().addWidget(item) |
|
self._minWidth = max(self._minWidth,item.minimumWidth()) |
|
self._resizeEvent() |
|
self.itemsChanged.emit(self._items) |
|
self._designer.weModified.emit() |
|
|
|
def addMenuItem(self, item): |
|
self._items.append(item) |
|
self._addMenuItem(item) |
|
|
|
@ttk.pyTTkSlot() |
|
def addMenu(self): |
|
mb = self._menuButton.addMenu(text="menu") |
|
mb.setName("menuButton") |
|
self.addMenuItem(_MenuItem(menuButton=mb, autoResize=False, designer=self._designer)) |
|
|
|
@ttk.pyTTkSlot() |
|
def addSpacer(self): |
|
self._menuButton.addSpacer() |
|
ms = self._menuButton._submenu[-1] |
|
self.addMenuItem(_SubMenuSpacer(menuSpacer=ms)) |
|
|
|
@ttk.pyTTkSlot() |
|
def _viewChangedHandler(self): |
|
x,y = self.getViewOffsets() |
|
self.layout().setOffset(-x,-y) |
|
|
|
def maximumWidth(self): return 0x10000 |
|
def maximumHeight(self): return 0x10000 |
|
def minimumWidth(self): return 0 |
|
def minimumHeight(self): return 0 |
|
|
|
|
|
class _SubMenuEditor(ttk.TTkResizableFrame): |
|
__slots__ = ('_scrollView' |
|
# Forwarded Signals |
|
'itemsChanged') |
|
def __init__(self, menuButton, designer, **kwargs): |
|
super().__init__(**kwargs|{'layout':ttk.TTkGridLayout()}) |
|
sa = ttk.TTkScrollArea(parent=self) |
|
self._scrollView = _SubMenuAreaWidget(menuButton=menuButton, designer=designer) |
|
sa.setViewport(self._scrollView) |
|
|
|
self.itemsChanged = self._scrollView.itemsChanged |
|
|
|
|
|
class _MenuBarItemEditorView(ttk.TTkAbstractScrollView): |
|
''' |
|
┌────────┤Left├────────╥──────┤Center├───────╥───────┤Right├───────┐ |
|
│╿+╿[menu[+][x] ║╿+╿ ║╿+╿ │ |
|
│╽+╽ ║╽+╽ ║╽+╽ │ |
|
└──────────────────────╨─────────────────────╨─────────────────────┘ |
|
''' |
|
__slots__ = ('_itemsLayout', '_items', '_designer') |
|
def __init__(self, itemsLayout, designer, *args, **kwargs): |
|
self._items = [] |
|
self._itemsLayout = itemsLayout |
|
self._designer = designer |
|
super().__init__(*args, **kwargs) |
|
self.viewChanged.connect(self._viewChangedHandler) |
|
for item in self._itemsLayout.children(): |
|
self._importMenuItem(item.widget()) |
|
|
|
@ttk.pyTTkSlot() |
|
def _viewChangedHandler(self): |
|
x,y = self.getViewOffsets() |
|
self.layout().setOffset(-x,-y) |
|
|
|
def _importMenuItem(self,menuButton): |
|
item = _MenuItem(menuButton=menuButton, autoResize=True, designer=self._designer) |
|
self._items.append(item) |
|
self._addMenuItem(item) |
|
|
|
@ttk.pyTTkSlot(_MenuItem) |
|
def removeMenuItem(self, item:_MenuItem): |
|
item.sizeChanged.disconnect(self._refreshItems) |
|
item.closeClicked.disconnect(self.removeMenuItem) |
|
self._itemsLayout.removeWidget(item._menuButton) |
|
self.layout().removeWidget(item) |
|
self._items.pop(self._items.index(item)) |
|
self._refreshItems() |
|
self._designer.weModified.emit() |
|
|
|
def _addMenuItem(self, item): |
|
item.sizeChanged.clear() |
|
item.closeClicked.clear() |
|
item.sizeChanged.connect(self._refreshItems) |
|
item.closeClicked.connect(self.removeMenuItem) |
|
self.layout().addWidget(item) |
|
self._refreshItems() |
|
self._designer.weModified.emit() |
|
|
|
@ttk.pyTTkSlot() |
|
def addMenuItem(self): |
|
button = ttk.TTkMenuButton(text="menu", name="menuButton") |
|
self._itemsLayout.addWidget(button) |
|
self._importMenuItem(button) |
|
|
|
def _refreshItems(self): |
|
x = 0 |
|
for mi in self._items: |
|
w = mi.width() |
|
mi.move(x,0) |
|
x+=w |
|
_,__,w,h = self.layout().fullWidgetAreaGeometry() |
|
self.resize(w,h) |
|
|
|
|
|
class _MenuItemEditor(ttk.TTkGridLayout): |
|
__slots__ = ('_addButton', '_scrollPart','_menuBarView') |
|
def __init__(self, itemsLayout, designer): |
|
super().__init__() |
|
self._addButton =ttk.TTkButton(text="+\n+",border=False, maxWidth=3, minWidth=3, minHeight=2, maxHeight=2) |
|
self._scrollPart = ttk.TTkAbstractScrollArea( |
|
verticalScrollBarPolicy = ttk.TTkK.ScrollBarAlwaysOff , |
|
horizontalScrollBarPolicy = ttk.TTkK.ScrollBarAlwaysOn ) |
|
self._menuBarView = _MenuBarItemEditorView(itemsLayout, designer=designer) |
|
self._scrollPart.setViewport(self._menuBarView) |
|
self.addWidget(self._addButton,0,0) |
|
self.addWidget(self._scrollPart,0,1) |
|
self._addButton.clicked.connect(self._menuBarView.addMenuItem) |
|
|
|
|
|
class MenuBarEditor(ttk.TTkWindow): |
|
__slots__ = ('_editorMenuBar', '_frameOptions', '_widget', '_designer', |
|
'_btnTop', '_btnBottom', '_cbTop', '_cbBottom', |
|
'_itemsTop', '_itemsBottom', '_mbTop', '_mbBottom') |
|
def __init__(self, widget:ttk.TTkFrame, designer): |
|
self._widget = widget |
|
self._designer = designer |
|
ttk.TTkUiLoader.loadFile( |
|
os.path.join(os.path.dirname(os.path.abspath(__file__)),"../tui/menuBarEditor.tui.json"), |
|
self) |
|
self._editorMenuBar = self.getWidgetByName('EditorMenuBar') |
|
self._cbTop = self.getWidgetByName('CbTop') |
|
self._cbBottom = self.getWidgetByName('CbBottom') |
|
self._btnTop = self.getWidgetByName('BtnEditTop') |
|
self._btnBottom = self.getWidgetByName('BtnEditBottom') |
|
self._frameOptions = self.getWidgetByName("FrameOptions") |
|
|
|
self._btnTop.clicked.connect( lambda : self._showEditor(ttk.TTkK.TOP)) |
|
self._btnBottom.clicked.connect(lambda : self._showEditor(ttk.TTkK.BOTTOM)) |
|
|
|
self._cbTop.clicked.connect(lambda en: self._enableMenuBar(en, ttk.TTkK.TOP)) |
|
self._cbBottom.clicked.connect(lambda en: self._enableMenuBar(en, ttk.TTkK.BOTTOM)) |
|
|
|
self._mbTop = self._widget.menuBar(ttk.TTkK.TOP) |
|
self._mbBottom = self._widget.menuBar(ttk.TTkK.BOTTOM) |
|
|
|
if self._mbTop: |
|
self._cbTop.setChecked(True) |
|
self._btnTop.setEnabled(True) |
|
if self._mbBottom: |
|
self._cbBottom.setChecked(True) |
|
self._btnBottom.setEnabled(True) |
|
|
|
if self._mbTop: |
|
self._showEditor(ttk.TTkK.TOP) |
|
elif self._mbBottom: |
|
self._showEditor(ttk.TTkK.BOTTOM) |
|
|
|
def _enableMenuBar(self, enable, place): |
|
if enable: |
|
if place==ttk.TTkK.TOP: |
|
self._mbTop = mb = self._mbTop if self._mbTop else ttk.TTkMenuBarLayout() |
|
else: |
|
self._mbBottom = mb = self._mbBottom if self._mbBottom else ttk.TTkMenuBarLayout() |
|
self._widget.setMenuBar(mb, place) |
|
else: |
|
self._widget.setMenuBar(None, place) |
|
self._designer.weModified.emit() |
|
|
|
def _showEditor(self, place): |
|
if place==ttk.TTkK.TOP: |
|
self._btnTop.setChecked(True) |
|
self._btnBottom.setChecked(False) |
|
self.setTitle("MenuBar Editor (TOP)") |
|
mb = self._mbTop if self._mbTop else ttk.TTkMenuBarLayout() |
|
else: |
|
self._btnTop.setChecked(False) |
|
self._btnBottom.setChecked(True) |
|
self.setTitle("MenuBar Editor (BOTTOM)") |
|
mb = self._mbBottom if self._mbBottom else ttk.TTkMenuBarLayout() |
|
|
|
meL = _MenuItemEditor(mb._mbItems(ttk.TTkK.LEFT_ALIGN), designer=self._designer) |
|
meC = _MenuItemEditor(mb._mbItems(ttk.TTkK.CENTER_ALIGN), designer=self._designer) |
|
meR = _MenuItemEditor(mb._mbItems(ttk.TTkK.RIGHT_ALIGN), designer=self._designer) |
|
|
|
self._editorMenuBar.replaceItem(0,meL,title="Left") |
|
self._editorMenuBar.replaceItem(1,meC,title="Center") |
|
self._editorMenuBar.replaceItem(2,meR,title="Right") |
|
|
|
|
|
@staticmethod |
|
def spawnMenuBarEditor(designer): |
|
def _spawnMenuBarEditor(widget): |
|
menuBarEditor = MenuBarEditor(widget=widget, designer=designer) |
|
ttk.TTkHelper.overlay(None, menuBarEditor, 10, 5, toolWindow=True) |
|
return _spawnMenuBarEditor
|
|
|