diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index c577256b..056c3ea7 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -468,7 +468,7 @@ class TTkCanvas: self.drawText(pos=(x+1,y), color=color ,text=text) off = 1 else: - self.drawText(pos=(x,y), color=color ,text=text) + self.drawText(pos=(x,y), width=width, color=color ,text=text) if submenu: self._set(y,x+width-1, mb[5], color) off = 0 diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 75e96291..ad34c505 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -42,6 +42,7 @@ class TTkHelper: def __init__(self,x,y,widget): self._widget = widget widget.move(x,y) + _savedFocus = None _overlay = [] class _Shortcut(): @@ -111,6 +112,9 @@ class TTkHelper: def overlay(caller, widget, x, y): wx, wy = TTkHelper.absPos(caller) w,h = widget.size() + if not TTkHelper._savedFocus and \ + not TTkHelper.isOverlay(TTkHelper._focusWidget): + TTkHelper._savedFocus = TTkHelper._focusWidget # Try to keep the overlay widget inside the terminal wx = max(0, wx+x if wx+x+w < TTkGlbl.term_w else TTkGlbl.term_w-w ) wy = max(0, wy+y if wy+y+h < TTkGlbl.term_h else TTkGlbl.term_h-h ) @@ -130,6 +134,24 @@ class TTkHelper: for widget in TTkHelper._overlay: TTkHelper._rootWidget.rootLayout().removeWidget(widget._widget) TTkHelper._overlay = [] + if TTkHelper._focusWidget: + TTkHelper._focusWidget.clearFocus() + if TTkHelper._savedFocus: + bk = TTkHelper._savedFocus + TTkHelper._savedFocus = None + bk.setFocus() + + @staticmethod + def removeSingleOverlay(widget): + if len(TTkHelper._overlay) <= 1: + return TTkHelper.removeOverlay() + rootWidget = TTkHelper.rootOverlay(widget) + rootWidget + for o in TTkHelper._overlay: + if o._widget == rootWidget: + TTkHelper._overlay.remove(o) + TTkHelper._rootWidget.rootLayout().removeWidget(rootWidget) + TTkHelper._overlay[-1]._widget.setFocus() @staticmethod def paintAll(): diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 15a71ff8..ed542dd7 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -120,6 +120,10 @@ class TTk(TTkWidget): nmevt = mevt.clone(pos=(mevt.x-x, mevt.y-y)) focusWidget.mouseEvent(nmevt) else: + # Sometimes the release event is not retrieved + if focusWidget and focusWidget._pendingMouseRelease: + focusWidget.mouseEvent(nmevt.clone(evt=TTkK.Release)) + focusWidget._pendingMouseRelease = False self.mouseEvent(mevt) elif evt is TTkK.KEY_EVENT: keyHandled = False diff --git a/TermTk/TTkWidgets/list.py b/TermTk/TTkWidgets/list.py index 964869d6..31d79656 100644 --- a/TermTk/TTkWidgets/list.py +++ b/TermTk/TTkWidgets/list.py @@ -35,7 +35,7 @@ class TTkList(TTkAbstractScrollArea): TTkAbstractScrollArea.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkList' ) if 'parent' in kwargs: kwargs.pop('parent') - self._listView = TTkListWidget(*args, **kwargs) + self._listView = kwargs.get('listWidget',TTkListWidget(*args, **kwargs)) self.setViewport(self._listView) self.itemClicked = self._listView.itemClicked self.textClicked = self._listView.textClicked diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 13ed8562..c6959a5e 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -31,15 +31,17 @@ from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.label import TTkLabel from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView -class _TTkListWidgetText(TTkLabel): - __slots__ = ('clicked', '_selected', '_highlighted') +class TTkAbstractListItem(TTkLabel): + __slots__ = ('_pressed', '_selected', '_highlighted', 'listItemClicked') def __init__(self, *args, **kwargs): TTkLabel.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , '_TTkListWidgetText' ) + self._name = kwargs.get('name' , 'TTkAbstractListItem' ) # Define Signals - self.clicked = pyTTkSignal(_TTkListWidgetText) + self.listItemClicked = pyTTkSignal(TTkAbstractListItem) self._selected = False + self._pressed = False self._highlighted = False + self.setFocusPolicy(TTkK.ClickFocus) def _updateColor(self): if self._highlighted: @@ -52,8 +54,19 @@ class _TTkListWidgetText(TTkLabel): else: self.color = TTkCfg.theme.listColor + def keyEvent(self, evt): + return self.parentWidget().keyEvent(evt) + + def mousePressEvent(self, evt): + self._pressed = True + self.highlighted = True + self.update() + return True + def mouseReleaseEvent(self, evt): - self.clicked.emit(self) + self._pressed = False + self.listItemClicked.emit(self) + self.update() return True @property @@ -101,19 +114,25 @@ class TTkListWidget(TTkAbstractScrollView): x,y = self.getViewOffsets() self.layout().groupMoveTo(-x,-y) - @pyTTkSlot(_TTkListWidgetText) + @pyTTkSlot(TTkAbstractListItem) def _labelSelectedHandler(self, label): if self._selectionMode == TTkK.SingleSelection: - for i in self._selectedItems: - i.selected = False - i.color = TTkCfg.theme.listColor + for item in self._selectedItems: + item.selected = False + item.highlighted = False + self._selectedItems = [label] label.selected = True elif self._selectionMode == TTkK.MultiSelection: + for item in self._selectedItems: + item.highlighted = False label.selected = not label.selected - if label.selected: - self._selectedItems.append(label) - else: - self._selectedItems.remove(label) + if label.selected: + self._selectedItems.append(label) + else: + self._selectedItems.remove(label) + if self._highlighted: + self._highlighted.highlighted = False + label.highlighted = True self._highlighted = label self.setFocus() self.textClicked.emit(label.text) @@ -143,8 +162,8 @@ class TTkListWidget(TTkAbstractScrollView): def addItem(self, item): if isinstance(item, str): - label = _TTkListWidgetText(text=item, width=max(len(item),self.width())) - label.clicked.connect(self._labelSelectedHandler) + label = TTkAbstractListItem(text=item, width=max(len(item),self.width())) + label.listItemClicked.connect(self._labelSelectedHandler) return self.addItem(label) self._items.append(item) _,y,_,h = self.layout().fullWidgetAreaGeometry() @@ -168,25 +187,27 @@ class TTkListWidget(TTkAbstractScrollView): self.viewMoveTo(offx, index) def keyEvent(self, evt): - if not self._highlighted: return + if not self._highlighted: return False if ( evt.type == TTkK.Character and evt.key==" " ) or \ ( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ): if self._highlighted: - self._highlighted.clicked.emit(self._highlighted) + # TTkLog.debug(self._highlighted) + self._highlighted.listItemClicked.emit(self._highlighted) return True elif evt.type == TTkK.SpecialKey: if evt.key == TTkK.Key_Tab: return False index = self._items.index(self._highlighted) offx,offy = self.getViewOffsets() + h = self.height() if evt.key == TTkK.Key_Up: index = max(0, index-1) elif evt.key == TTkK.Key_Down: index = min(len(self._items)-1, index+1) elif evt.key == TTkK.Key_PageUp: - index = 0 + index = max(0, index-h) elif evt.key == TTkK.Key_PageDown: - index = len(self._items)-1 + index = min(len(self._items)-1, index+h) elif evt.key == TTkK.Key_Right: self.viewMoveTo(offx+1, offy) elif evt.key == TTkK.Key_Left: diff --git a/TermTk/TTkWidgets/menubar.py b/TermTk/TTkWidgets/menubar.py index 3461ac31..7b4aefb3 100644 --- a/TermTk/TTkWidgets/menubar.py +++ b/TermTk/TTkWidgets/menubar.py @@ -28,31 +28,52 @@ from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkWidgets.widget import TTkWidget 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 -class _TTkMenuSpacer(TTkWidget): - __slots__ = ('clicked') + +class _TTkMenuListWidget(TTkListWidget): + __slots__ = ('_previous') + def __init__(self, *args, **kwargs): + TTkListWidget.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , '_TTkMenuListWidget' ) + self._previous = kwargs.get('previous',TTkHelper.getFocus()) + + def keyEvent(self, evt): + if evt.type == TTkK.SpecialKey: + if evt.key == TTkK.Key_Left: + TTkHelper.removeSingleOverlay(self) + if self._previous: + self._previous.setFocus() + return True + elif evt.key == TTkK.Key_Right: + if self._highlighted and \ + isinstance(self._highlighted,_TTkMenuButton) and \ + self._highlighted._menu: + self._highlighted.menuButtonEvent() + return True + return TTkListWidget.keyEvent(self, evt) + + +class _TTkMenuSpacer(TTkAbstractListItem): def __init__(self, *args, **kwargs): - TTkWidget.__init__(self, *args, **kwargs) + TTkAbstractListItem.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkMenuSpacer' ) - # Define Signals - self.clicked = pyTTkSignal() self.resize(1,1) - #self.setMinimumHeight(1) def paintEvent(self): - TTkLog.debug("pippo") self._canvas.drawText(pos=(0,0), text="-"*self.width()) -class _TTkMenuButton(TTkButton): - __slot__ = ('_color', '_borderColor', '_shortcut', '_menu', 'menuButtonClicked') +class _TTkMenuButton(TTkAbstractListItem): + __slots__ = ('_border', '_borderColor', '_shortcut', '_menu', 'menuButtonClicked') def __init__(self, *args, **kwargs): - TTkButton.__init__(self, *args, **kwargs) + TTkAbstractListItem.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkMenuButton' ) # signals self.menuButtonClicked = pyTTkSignal(TTkButton) self._color = kwargs.get('color', TTkCfg.theme.menuButtonColor ) + self._border = kwargs.get('border', TTkCfg.theme.menuButtonColor ) self._borderColor = kwargs.get('borderColor', TTkCfg.theme.menuButtonBorderColor ) self._shortcut = [] self._menu = [] @@ -66,7 +87,7 @@ class _TTkMenuButton(TTkButton): self.resize(txtlen,1) self.setMinimumSize(txtlen+2,1) self.setMaximumSize(txtlen+2,1) - self.clicked.connect(self.menuButtonEvent) + self.listItemClicked.connect(self.menuButtonEvent) def addMenu(self, text): button = _TTkMenuButton(text=text, borderColor=self._borderColor, border=False) @@ -93,11 +114,11 @@ class _TTkMenuButton(TTkButton): #self._id = self._list.index(label) TTkLog.debug(f"Bind Clicked {button._text}") self.menuButtonClicked.emit(button) - self.setFocus() + TTkHelper.removeOverlay() self.update() - @pyTTkSlot() - def menuButtonEvent(self): + @pyTTkSlot(TTkAbstractListItem) + def menuButtonEvent(self, listItem=None): if not self._menu: self.menuButtonClicked.emit(self) return @@ -117,20 +138,22 @@ class _TTkMenuButton(TTkButton): else: frame = TTkResizableFrame(layout=TTkHBoxLayout(), size=(frameWidth,frameHeight), title=self._text, titleAlign=TTkK.LEFT_ALIGN) pos = (-1, 0) - listw = TTkList(parent=frame) + menuListWidget = _TTkMenuListWidget() + listw = TTkList(parent=frame, listWidget = menuListWidget) # listw.textClicked.connect(self._menuCallback) # listw.textClicked.connect(self._menuCallback) TTkLog.debug(f"{self._menu}") for item in self._menu: listw.addItem(item) TTkHelper.overlay(self, frame, pos[0], pos[1]) + listw.viewport().setFocus() self.update() def paintEvent(self): if self._pressed: borderColor = self._borderColor textColor = TTkCfg.theme.menuButtonColorClicked - scColor = TTkCfg.theme.menuButtonShortcutColor + scColor = TTkCfg.theme.menuButtonShortcutColor else: borderColor = self._borderColor textColor = self._color @@ -145,6 +168,12 @@ class _TTkMenuButton(TTkButton): borderColor=borderColor, shortcutColor=scColor ) + def focusInEvent(self): + self.highlighted=True + + def focusOutEvent(self): + self.highlighted=False + class TTkMenuLayout(TTkHBoxLayout): __slots__ = ('_itemsLeft', '_itemsCenter', '_itemsRight', '_buttons') def __init__(self, *args, **kwargs): diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index d52b756c..d9a68fa3 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -56,7 +56,8 @@ class TTkWidget(TMouseEvents,TKeyEvents): '_padt', '_padb', '_padl', '_padr', '_maxw', '_maxh', '_minw', '_minh', '_focus','_focus_policy', - '_layout', '_canvas', '_visible', '_transparent') + '_layout', '_canvas', '_visible', '_transparent', + '_pendingMouseRelease') def __init__(self, *args, **kwargs): ''' @@ -92,6 +93,8 @@ class TTkWidget(TMouseEvents,TKeyEvents): self._name = kwargs.get('name', 'TTkWidget' ) self._parent = kwargs.get('parent', None ) + self._pendingMouseRelease = False + self._x = kwargs.get('x', 0 ) self._y = kwargs.get('y', 0 ) self._x, self._y = kwargs.get('pos', (self._x, self._y)) @@ -334,6 +337,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): if evt.evt == TTkK.Release: #if self.hasFocus(): # self.clearFocus() + self._pendingMouseRelease = False if self.mouseReleaseEvent(evt): return True @@ -343,6 +347,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): self.raiseWidget() if self.mousePressEvent(evt): # TTkLog.debug(f"Click {self._name}") + self._pendingMouseRelease = True return True if evt.key == TTkK.Wheel: @@ -543,6 +548,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): self.layoutUpdated() def setFocus(self): + if self._focus: return TTkLog.debug(self._name) tmp = TTkHelper.getFocus() if tmp == self: return @@ -557,6 +563,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): self.focusInEvent() def clearFocus(self): + if not self._focus: return TTkHelper.clearFocus() self._focus = False self.focusOutEvent() diff --git a/TermTk/libbpytop/input.py b/TermTk/libbpytop/input.py index 20b56eb9..4135128c 100644 --- a/TermTk/libbpytop/input.py +++ b/TermTk/libbpytop/input.py @@ -58,9 +58,11 @@ class MouseEvent: self.evt = evt self.raw = raw - def clone(self, pos=None): + def clone(self, pos=None, evt=None): x,y = pos if pos != None else (self.x, self.y) - return MouseEvent(x, y, self.key, self.evt, self.raw) + if not evt: + evt = self.evt + return MouseEvent(x, y, self.key, evt, self.raw) def key2str(self): return { diff --git a/demo/showcase/menubar.py b/demo/showcase/menubar.py index d927fb3c..b7f84baf 100755 --- a/demo/showcase/menubar.py +++ b/demo/showcase/menubar.py @@ -49,7 +49,7 @@ def demoMenuBar(root=None): frameTop.menubarTop().addMenu("X", alignment=ttk.TTkK.RIGHT_ALIGN) window = ttk.TTkWindow(title="Test MenuBar", parent=frameTop,pos=(1,1), size=(60,10), border=True) - fileMenu2 = window.menubarTop().addMenu("&File") + fileMenu2 = window.menubarTop().addMenu("&Fi&le") fileMenu2.addMenu("New File") fileMenu2.addMenu("Old File") fileMenu2.addSpacer() @@ -69,7 +69,7 @@ def demoMenuBar(root=None): fileMenu2.addSpacer() fileMenu2.addMenu("Exit") - editMenu2 = window.menubarTop().addMenu("&Edit") + editMenu2 = window.menubarTop().addMenu("&E&dit") editMenu2.addMenu("Undo") editMenu2.addMenu("Redo") editMenu2.addMenu("Cut") diff --git a/docs/TODO.md b/docs/TODO.md index 594e4280..7124f314 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -147,6 +147,10 @@ - [x] Events (Signal/Slots) - [ ] Themes - [x] Use Spinbox for R G B +#### Date Picker + - [ ] Basic Implementation + - [ ] Events (Signal/Slots) + - [ ] Themes #### File Picker - [ ] Basic Implementation - [ ] Events (Signal/Slots)