Browse Source

Merge pull request #87 from ceccopierangiolieugenio/WindowFlags

Window flags #81
pull/75/head 0.14.0-a
Ceccopierangiolieugenio 3 years ago committed by GitHub
parent
commit
bfe4b4ab44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Makefile
  2. 25
      TermTk/TTkCore/canvas.py
  3. 16
      TermTk/TTkCore/constant.py
  4. 10
      TermTk/TTkCore/helper.py
  5. 6
      TermTk/TTkWidgets/TTkPickers/colorpicker.py
  6. 4
      TermTk/TTkWidgets/TTkPickers/filepicker.py
  7. 24
      TermTk/TTkWidgets/widget.py
  8. 89
      TermTk/TTkWidgets/window.py
  9. 8
      demo/demo.py
  10. 92
      demo/showcase/windowsflags.py
  11. 11
      tests/pytest/test_001_demo.py

2
Makefile

@ -97,5 +97,5 @@ test: .venv
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp ; \
pytest tests/pytest/test_003_string.py ; \
pytest tests/pytest/test_002_textedit.py ; \
pytest tests/pytest/test_001_demo.py ;
pytest -v tests/pytest/test_001_demo.py ;

25
TermTk/TTkCore/canvas.py

@ -192,10 +192,10 @@ class TTkCanvas:
else:
self._colors[y][x+i] = colors[i].mod(x+i,y)
# Check the full wide chars on the edge of the two canvasses
if self._data[y][x+a] == '':
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
if TTkString._isWideCharData(self._data[y][x+b-1]):
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
@ -598,15 +598,10 @@ class TTkCanvas:
w = min(w,self._width-x)
h = min(h,self._height-y)
# if x>=self._width: x=self._width-1
# if y>=self._height: y=self._height-1
# if w>=self._width-x: w=self._width-x
# if h>=self._height-y: h=self._height-y
xoffset = 0 if x>=bx else bx-x
yoffset = 0 if y>=by else by-y
wslice = w if x+w < bx+bw else bx+bw-x
hslice = h if y+h < by+bh else by+bh-y
xoffset = min(max(0,bx-x),canvas._width-1)
yoffset = min(max(0,by-y),canvas._height-1)
wslice = min(w if x+w < bx+bw else bx+bw-x,canvas._width)
hslice = min(h if y+h < by+bh else by+bh-y,canvas._height)
for iy in range(yoffset,hslice):
a, b = x+xoffset, x+wslice
@ -614,16 +609,16 @@ class TTkCanvas:
self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice]
# Check the full wide chars on the edge of the two canvasses
if self._data[y+iy][a]=='':
if ((0 <= a < self._width) and self._data[y+iy][a]==''):
self._data[y+iy][a] = TTkCfg.theme.unicodeWideOverflowCh[0]
self._colors[y+iy][a] = TTkCfg.theme.unicodeWideOverflowColor
if TTkString._isWideCharData(self._data[y+iy][b-1]):
if ((0 < b <= self._width) 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
if TTkString._isWideCharData(self._data[y+iy][a-1]):
if ((0 < a <= self._width) 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
if ( b<self._width-1 and self._data[y+iy][b]=='' ):
if ((0 <= b < self._width) and self._data[y+iy][b]==''):
self._data[y+iy][b] = TTkCfg.theme.unicodeWideOverflowCh[0]
self._colors[y+iy][b] = TTkCfg.theme.unicodeWideOverflowColor

16
TermTk/TTkCore/constant.py

@ -289,14 +289,16 @@ class TTkConstant:
# ''' Gives the window a title bar.'''
# WindowSystemMenuHint = 0x00002000
# ''' Adds a window system menu, and possibly a close button (for example on Mac). If you need to hide or show a close button, it is more portable to use WindowCloseButtonHint.'''
# WindowMinimizeButtonHint = 0x00004000
# ''' Adds a minimize button. On some platforms this implies Qt::WindowSystemMenuHint for it to work.'''
# WindowMaximizeButtonHint = 0x00008000
# ''' Adds a maximize button. On some platforms this implies Qt::WindowSystemMenuHint for it to work.'''
# WindowMinMaxButtonsHint = WindowMinimizeButtonHint
# ''' | WindowMaximizeButtonHint Adds a minimize and a maximize button. On some platforms this implies Qt::WindowSystemMenuHint for it to work.'''
WindowReduceButtonHint = 0x00000200
''' Adds a reduce button.'''
WindowMinimizeButtonHint = 0x00004000
''' Adds a minimize button.'''
WindowMaximizeButtonHint = 0x00008000
''' Adds a maximize button.'''
WindowMinMaxButtonsHint = WindowMinimizeButtonHint | WindowMaximizeButtonHint
''' Adds a minimize and a maximize button.'''
WindowCloseButtonHint = 0x08000000
''' Adds a close button. On some platforms this implies Qt::WindowSystemMenuHint for it to work.'''
''' Adds a close button.'''
# WindowFullscreenButtonHint = 0x80000000
# ''' On macOS adds a fullscreen button.'''
# WindowShadeButtonHint = 0x00020000

10
TermTk/TTkCore/helper.py

@ -62,7 +62,7 @@ class TTkHelper:
def execShortcut(letter, widget=None):
if not isinstance(letter, str): return
for sc in TTkHelper._shortcut:
if sc._letter == letter.lower() and sc._widget.isVisible():
if sc._letter == letter.lower() and sc._widget.isVisibleAndParent():
if not widget or TTkHelper.isParent(widget, sc._widget):
sc._widget.shortcutEvent()
return
@ -76,7 +76,7 @@ class TTkHelper:
@staticmethod
def addUpdateWidget(widget):
if not widget.isVisible(): return
if not widget.isVisibleAndParent(): return
if widget not in TTkHelper._updateWidget:
TTkHelper._updateWidget.append(widget)
@ -249,7 +249,7 @@ class TTkHelper:
# TTkLog.debug(f"{len(TTkHelper._updateBuffer)} {len(TTkHelper._updateWidget)}")
for widget in TTkHelper._updateWidget:
if not widget.isVisible(): continue
if not widget.isVisibleAndParent(): continue
parent = widget.parentWidget()
while parent is not None:
if parent not in updateBuffers:
@ -263,7 +263,7 @@ class TTkHelper:
# Paint all the canvas
for widget in updateBuffers:
if not widget.isVisible(): continue
if not widget.isVisibleAndParent(): continue
# Resize the canvas just before the paintEvent
# to avoid too many allocations
widget.getCanvas().updateSize()
@ -277,7 +277,7 @@ class TTkHelper:
sortedUpdateWidget = sorted(sortedUpdateWidget, key=lambda w: -w[1])
for w in sortedUpdateWidget:
widget = w[0]
if not widget.isVisible(): continue
if not widget.isVisibleAndParent(): continue
pushToTerminal = True
widget.paintChildCanvas()

6
TermTk/TTkWidgets/TTkPickers/colorpicker.py

@ -244,7 +244,7 @@ class TTkColorDialogPicker(TTkWindow,TColor):
self.colorSelected = pyTTkSignal(TTkColor)
TTkWindow.__init__(self, *args, **kwargs)
TColor.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'TTkColorPicker' )
self.setWindowFlag(TTkK.WindowFlag.WindowMaximizeButtonHint | TTkK.WindowFlag.WindowCloseButtonHint)
self.setLayout(TTkGridLayout())
colorLayout = TTkGridLayout() # Right
@ -308,8 +308,8 @@ class TTkColorDialogPicker(TTkWindow,TColor):
@pyTTkSlot()
def _leHTMLChanged():
text = leHTML.text()
if re.match('#[a-f0-9]{6}', text.lower()):
_controlSetRGBColor(int(text[1:], 16))
if re.match('#[a-f0-9]{6}', str(text).lower()):
_controlSetRGBColor(int(str(text)[1:], 16))
leHTML.returnPressed.connect(_leHTMLChanged)

4
TermTk/TTkWidgets/TTkPickers/filepicker.py

@ -72,8 +72,8 @@ class TTkFileDialogPicker(TTkWindow):
self.filesPicked = pyTTkSignal(list)
self.folderPicked = pyTTkSignal(str)
TTkWindow.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'TTkFileDialogPicker' )
super().__init__(*args, **kwargs)
self.setWindowFlag(TTkK.WindowFlag.WindowMaximizeButtonHint | TTkK.WindowFlag.WindowCloseButtonHint)
self._recentPathId = -1
self._recentPath = []

24
TermTk/TTkWidgets/widget.py

@ -520,29 +520,12 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
self._minw = minw
self.update(updateLayout=True, updateParent=True)
@staticmethod
def _propagateShowToLayout(layout):
''' .. caution:: Don't touch this! '''
if layout is None: return
for item in layout.zSortedItems:
if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty():
child = item.widget()
child._propagateShow()
else:
TTkWidget._propagateShowToLayout(item)
def _propagateShow(self):
''' .. caution:: Don't touch this! '''
if not self._visible: return
self.update(updateLayout=True, updateParent=True)
TTkWidget._propagateShowToLayout(self.rootLayout())
@pyTTkSlot()
def show(self):
if self._visible: return
self._visible = True
self._canvas.show()
self._propagateShow()
self.update(updateLayout=True, updateParent=True)
@pyTTkSlot()
def hide(self):
@ -578,12 +561,15 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
if visible: self.show()
else: self.hide()
def isVisible(self):
def isVisibleAndParent(self):
if self._parent is None:
return self._visible
else:
return self._visible & self._parent.isVisible()
def isVisible(self):
return self._visible
# Event to be sent
# TODO: Remove This
def layoutUpdated(self): pass

89
TermTk/TTkWidgets/window.py

@ -29,14 +29,24 @@ from TermTk.TTkLayouts import TTkGridLayout, TTkLayout
from TermTk.TTkWidgets.button import TTkButton
from TermTk.TTkWidgets.resizableframe import TTkResizableFrame
class _MinimizedButton(TTkButton):
__slots__ = ('_windowWidget')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._windowWidget = kwargs.get('windowWidget')
def _cb():
self._windowWidget.show()
self.close()
self.clicked.connect(_cb)
class TTkWindow(TTkResizableFrame):
__slots__ = (
'_title', '_mouseDelta', '_draggable',
'_btnClose', '_btnMax', '_btnMin', '_btnReduce',
'_flags', '_winTopLayout' )
'_flags', '_winTopLayout',
'_maxBk', '_redBk' )
def __init__(self, *args, **kwargs):
TTkResizableFrame.__init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self._title = kwargs.get('title' , '' )
self._flags = TTkK.NONE
self.setPadding(3,1,1,1)
@ -48,31 +58,88 @@ class TTkWindow(TTkResizableFrame):
# Add the top Layout to keep the windows action buttons
self._winTopLayout = TTkGridLayout()
self.rootLayout().addItem(self._winTopLayout)
self._winTopLayout.setGeometry(1,1,self.width()-2,1)
# Close Button
self._btnClose = TTkButton(border=False, text="x", size=(3,1), maxWidth=3, minWidth=3, visible=False)
self._btnClose.clicked.connect(self.close)
# Max Button
self._maxBk = None
self._btnMax = TTkButton(border=False, text="^", size=(3,1), maxWidth=3, minWidth=3, visible=False)
self._btnMax.clicked.connect(self._maximize)
# Min Button
self._btnMin = TTkButton(border=False, text="_", size=(3,1), maxWidth=3, minWidth=3, visible=False)
self._btnMin.clicked.connect(self._minimize)
# Button Reduce_border
self._redBk = None
self._btnReduce = TTkButton(border=False, text=".", size=(3,1), maxWidth=3, minWidth=3, visible=False)
self._btnReduce.clicked.connect(self._reduce)
self._winTopLayout.addItem(TTkLayout(),0,0)
self._winTopLayout.addWidget(self._btnClose,0,1)
self._winTopLayout.addWidget(self._btnClose, 0,4)
self._winTopLayout.addWidget(self._btnMax, 0,3)
self._winTopLayout.addWidget(self._btnMin, 0,2)
self._winTopLayout.addWidget(self._btnReduce,0,1)
self._winTopLayout.setGeometry(1,1,self.width()-2,1)
self._winTopLayout.update()
self.setWindowFlag(kwargs.get('flags', TTkK.NONE))
self.setWindowFlag(kwargs.get('flags', TTkK.WindowFlag.WindowCloseButtonHint))
def _maximize(self):
if not (pw := self.parentWidget()): return
if self._maxBk:
self.setGeometry(*self._maxBk)
self._maxBk = None
else:
bk = self.geometry()
maxw,maxh = pw.layout().size()
self.setGeometry(0,0,maxw,maxh)
self._maxBk = bk
def _reduce(self):
if self._redBk:
self.resize(*self._redBk)
self._redBk = None
else:
bk = self.size()
self.resize(self.width(),4)
self._redBk = bk
def _minimize(self):
if not (pw := self.parentWidget()): return
stack = []
for li in pw.rootLayout().children():
if li.layoutItemType == TTkK.WidgetItem and issubclass(type(w:=li.widget()),_MinimizedButton):
stack.append(w.y())
stack = sorted(stack)
lx,ly = pw.layout().pos()
pos = ly
for v in stack:
if (pos+2) < v or (v+2) < pos:
break
pos += 3
mb = _MinimizedButton(windowWidget=self,text=self._title,border=True,pos=(lx,pos),size=(15,3))
pw.rootLayout().addWidget(mb)
self.hide()
def setTitle(self, title):
self._title = title
self.update()
def windowFlag(self):
return self._flags
def setWindowFlag(self, flag):
if self._flags == flag: return
self._flags = flag
if flag & TTkK.WindowFlag.WindowCloseButtonHint:
self._btnClose.show()
else:
self._btnClose.hide()
self.update()
self._btnClose.setVisible( bool(flag & TTkK.WindowFlag.WindowCloseButtonHint))
self._btnMax.setVisible( bool(flag & TTkK.WindowFlag.WindowMaximizeButtonHint))
self._btnMin.setVisible( bool(flag & TTkK.WindowFlag.WindowMinimizeButtonHint))
self._btnReduce.setVisible(bool(flag & TTkK.WindowFlag.WindowReduceButtonHint))
self._winTopLayout.update()
def resizeEvent(self, w, h):
self._winTopLayout.setGeometry(1,1,self.width()-2,1)
self._maxBk = None
self._redBk = None
self._winTopLayout.setGeometry(1,1,w-2,1)
super().resizeEvent(w,h)
def getTitle(self):

8
demo/demo.py

@ -38,6 +38,7 @@ from showcase.tab import demoTab
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.scrollarea import demoScrollArea
from showcase.list import demoList
@ -91,7 +92,7 @@ def showSource(file):
content = "Nothing"
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),file)) as f:
content = stupidPythonHighlighter( ttk.TTkString() + f.read() )
sourceWin = ttk.TTkWindow(size=(100,40), title=file, layout=ttk.TTkGridLayout())
sourceWin = ttk.TTkWindow(size=(100,40), title=file, layout=ttk.TTkGridLayout(), flags=ttk.TTkK.WindowFlag.WindowMaximizeButtonHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint)
texEdit = ttk.TTkTextEdit(parent=sourceWin, lineNumber=True)
texEdit.setText(content)
ttk.TTkHelper.overlay(None, sourceWin, 2, 2)
@ -198,7 +199,8 @@ def demoShowcase(root=None, border=True):
listMenu.addItem(f"Windows")
tabWindows = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False)
tabWindows.addTab(demoWindows(), " Windows Test ")
tabWindowsSources = [ 'showcase/windows.py' ]
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()]))
listMenu.addItem(f"Extra")
@ -250,7 +252,7 @@ def main():
root = ttk.TTk(title="pyTermTk Demo")
if windowed:
winTabbed1 = ttk.TTkWindow(parent=root,pos=(0,0), size=(120,40), title="pyTermTk Showcase", border=True, layout=ttk.TTkGridLayout())
winTabbed1 = ttk.TTkWindow(parent=root,pos=(0,0), size=(120,40), title="pyTermTk Showcase", border=True, layout=ttk.TTkGridLayout(), flags=ttk.TTkK.NONE)
border = True
else:
root.setLayout(ttk.TTkGridLayout())

92
demo/showcase/windowsflags.py

@ -0,0 +1,92 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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 sys, os
sys.path.append(os.path.join(sys.path[0],'../../tmp'))
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
# Testing Window with a checkbox to enable/disable any control button
class WindowFlagsTest(ttk.TTkWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
rb = ttk.TTkCheckbox(
parent=self, pos=(0,0), size=(20,1), text='Reduce Button',
checked=bool(self.windowFlag()&ttk.TTkK.WindowFlag.WindowReduceButtonHint))
minb = ttk.TTkCheckbox(
parent=self, pos=(0,1), size=(20,1), text='Minimize Button',
checked=bool(self.windowFlag()&ttk.TTkK.WindowFlag.WindowMinimizeButtonHint))
maxb = ttk.TTkCheckbox(
parent=self, pos=(0,2), size=(20,1), text='Maximize Button',
checked=bool(self.windowFlag()&ttk.TTkK.WindowFlag.WindowMaximizeButtonHint))
cb = ttk.TTkCheckbox(
parent=self, pos=(0,3), size=(20,1), text='Close Button',
checked=bool(self.windowFlag()&ttk.TTkK.WindowFlag.WindowCloseButtonHint))
# Set the window flag/field based on the checkbox state
def _cbStateChanged(state,field):
if state==ttk.TTkK.Checked:
self.setWindowFlag(self.windowFlag()|field)
else:
self.setWindowFlag(self.windowFlag()&(~field))
rb.stateChanged.connect( lambda x: _cbStateChanged(x,ttk.TTkK.WindowFlag.WindowReduceButtonHint))
minb.stateChanged.connect(lambda x: _cbStateChanged(x,ttk.TTkK.WindowFlag.WindowMinimizeButtonHint))
maxb.stateChanged.connect(lambda x: _cbStateChanged(x,ttk.TTkK.WindowFlag.WindowMaximizeButtonHint))
cb.stateChanged.connect( lambda x: _cbStateChanged(x,ttk.TTkK.WindowFlag.WindowCloseButtonHint))
def demoWindowsFlags(root=None):
frame = ttk.TTkFrame(parent=root, border=False)
# Standard window (the close button is enabled by default)
WindowFlagsTest(parent=frame, pos = (0,0), size=(40,8), title="Test Window 1")
# Enable Max anc Close button
WindowFlagsTest(parent=frame, pos = (2,2), size=(40,8), title="Test Window 2",
flags = ttk.TTkK.WindowFlag.WindowMaximizeButtonHint | ttk.TTkK.WindowFlag.WindowCloseButtonHint)
# Disable all the control buttons
WindowFlagsTest(parent=frame, pos = (4,4), size=(40,8), title="Test Window 3",
flags = ttk.TTkK.NONE)
# Enable only the Max and Min Buttons
WindowFlagsTest(parent=frame, pos = (6,6), size=(40,8), title="Test Window 4",
flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint)
# Enable only the Minimize button
WindowFlagsTest(parent=frame, pos = (8,8), size=(40,8), title="Test Window 5",
flags = ttk.TTkK.WindowFlag.WindowReduceButtonHint)
return frame
def main():
ttk.TTkLog.use_default_file_logging()
root = ttk.TTk()
win1 = ttk.TTkWindow(parent=root,pos = (1,1), size=(60,30), title="Test Window Flags", border=True, layout=ttk.TTkGridLayout(), flags=ttk.TTkK.NONE)
demoWindowsFlags(win1)
root.mainloop()
if __name__ == "__main__":
main()

11
tests/pytest/test_001_demo.py

@ -153,7 +153,18 @@ def test_demo():
assert demo.demoShowcase(root) != None
root.quit()
def message_handler(mode, context, message):
msgType = "NONE"
if mode == demo.ttk.TTkLog.InfoMsg: msgType = "[INFO]"
elif mode == demo.ttk.TTkLog.WarningMsg: msgType = "[WARNING]"
elif mode == demo.ttk.TTkLog.CriticalMsg: msgType = "[CRITICAL]"
elif mode == demo.ttk.TTkLog.FatalMsg: msgType = "[FATAL]"
elif mode == demo.ttk.TTkLog.ErrorMsg: msgType = "[ERROR]"
print(f"{msgType} {context.file} {message}")
def test_recording1():
# demo.ttk.TTkLog.use_default_file_logging()
demo.ttk.TTkLog.installMessageHandler(message_handler)
root = TTkRecord(title="pyTermTk Demo Record", record=False)
root.loadQueue(open('tmp/test.input.bin', 'rb'))
winTabbed1 = demo.ttk.TTkWindow(parent=root,pos=(0,0), size=(80,24), title="pyTermTk Showcase", border=True, layout=demo.ttk.TTkGridLayout())

Loading…
Cancel
Save