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.

418 lines
16 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 TermTk as ttk
import ttkDesigner.app.superobj as so
from .superlayout import SuperLayout
class _SuperExpandButton(ttk.TTkButton):
NONE = 0x00
PRESSED = 0x01
RELEASED = 0x02
__slots__ = ('superExpandButtonDragged','superExpandButtonHidden','_mouseStatus')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._direction = ttk.TTkK.TOP
# This signal is required by the superGridLayout to identify a dragging
# that may change the padding of the linked widget
self.superExpandButtonDragged = ttk.pyTTkSignal(int,int,bool)
self.superExpandButtonHidden = ttk.pyTTkSignal()
self._mouseStatus = self.NONE
def setDirection(self, d):
self._direction = d
def _processInput(self, kevt, mevt):
if not mevt:
self.hide()
return
ax,ay = ttk.TTkHelper.absPos(self)
w,h = self.size()
# x,y,w,h = self._boundaries
mx,my = mevt.x, mevt.y
if ( self._mouseStatus == self.NONE and
( not (0 <= (mx-ax) < w) or
not (0 <= (my-ay) < h) ) ) :
self.hide()
def mousePressEvent(self, evt):
self._mouseStatus = self.PRESSED
return super().mousePressEvent(evt)
def mouseReleaseEvent(self, evt):
self._mouseStatus = self.NONE
return super().mouseReleaseEvent(evt)
def mouseDragEvent(self, evt) -> bool:
# ttk.TTkLog.debug(f"Drag - {evt}")
x,y,w,h = self.geometry()
self.superExpandButtonDragged.emit(evt.x+x, evt.y+y, 0<=evt.x<w and 0<=evt.y<h)
return True
def show(self):
ttk.TTkInput.inputEvent.connect(self._processInput)
return super().show()
def hide(self):
self.superExpandButtonHidden.emit()
ttk.TTkInput.inputEvent.disconnect(self._processInput)
return super().hide()
def paintEvent(self, canvas):
# '▶','◀','▼','▲'
w,h = self.size()
if w==1:
canvas.drawText(text='', pos=(0,0), color=ttk.TTkColor.fg("FFFF00"))
canvas.drawText(text='', pos=(0,h-1), color=ttk.TTkColor.fg("FFFF00"))
for yy in range(1,h-1):
canvas.drawText(text='', pos=(0, yy), color=ttk.TTkColor.fg("FFFF00"))
elif h==1:
txt = ''+''*(w-2)+''
canvas.drawText(text=txt, pos=(0,0), width=w, color=ttk.TTkColor.fg("FFFF00"))
ch = {
ttk.TTkK.TOP : '',
ttk.TTkK.BOTTOM : '',
ttk.TTkK.LEFT : '',
ttk.TTkK.RIGHT : '',
ttk.TTkK.HORIZONTAL : '',
ttk.TTkK.VERTICAL : '',
}.get(self._direction, 'X')
x,y = 0,0
if w==1 and h>4:
y = h//2-2
h = 4
elif h==1 and w>4:
x = w//2-2
w = 4
canvas.fill(pos=(x,y), size=(w,h), char=ch, color=ttk.TTkColor.fg("#FF0000")+ttk.TTkColor.bg("#FFFF44"))
class SuperLayoutGrid(SuperLayout):
__slots__ = ('_expandButton','_dragOver','_spanOver','_expandStuff','_orientation','_snappId')
def __init__(self, *args, **kwargs):
kwargs['layout'] = ttk.TTkGridLayout()
super().__init__(*args, **kwargs)
self._expandButton = _SuperExpandButton()
self.rootLayout().addWidget(self._expandButton)
self._expandButton.hide()
self._expandButton.clicked.connect(self._clickExpand)
self._expandButton.superExpandButtonDragged.connect(self._expandButtonDragged)
self._expandButton.superExpandButtonHidden.connect(self._expandButtonHidden)
# self._expandButton.superExpandButtonDragged.connect()
self._dragOver = None
self._spanOver = None
self._snappId = None
self._expandStuff = None
self._orientation = ttk.TTkK.HORIZONTAL|ttk.TTkK.VERTICAL
def dragEnterEvent(self, evt) -> bool:
# ttk.TTkLog.debug(f"Enter")
_, __, ___, self._dragOver = self._processDragOver(evt.x,evt.y)
return True
def dragLeaveEvent(self, evt) -> bool:
# ttk.TTkLog.debug(f"Leave")
self._dragOver = None
self.update()
return True
def dragMoveEvent(self, evt) -> bool:
# ttk.TTkLog.debug(f"Move")
_, __, ___, self._dragOver = self._processDragOver(evt.x,evt.y)
return True
def dropEvent(self, evt) -> bool:
self._dragOver = None
self._pushRow, self._pushCol, self._direction, self._dragOver = self._processDragOver(evt.x,evt.y)
return super().dropEvent(evt)
@ttk.pyTTkSlot()
def _expandButtonHidden(self):
self._spanOver = None
self.update()
def removeSuperWidget(self, sw):
super().removeSuperWidget(sw)
self.layout().repack()
def mouseMoveEvent(self, evt) -> bool:
# ttk.TTkLog.debug(f"Move {evt}")
dir, pos, wid, spanOver = self._processMouseOver(evt.x, evt.y)
if not wid or not dir:
self._expandButton.hide()
return super().mouseMoveEvent(evt)
x,y,w,h = wid.geometry()
ebs = {
ttk.TTkK.TOP : (x, y, w, 1),
ttk.TTkK.BOTTOM : (x, y+h-1, w, 1),
ttk.TTkK.LEFT : (x, y, 1, h),
ttk.TTkK.RIGHT : (x+w-1, y, 1, h),
}.get(pos, None)
if not ebs:
self._expandButton.hide()
else:
self._expandButton.setGeometry(*ebs)
self._expandButton.setDirection(dir)
self._expandButton.raiseWidget(raiseParent=False)
self._expandButton.show()
self._expandStuff = (dir,wid)
self._spanOver = spanOver
return True
def addSuperWidget(self, sw):
self._dragOver = None
if self._direction == ttk.TTkK.HORIZONTAL or self._orientation == ttk.TTkK.HORIZONTAL:
self.layout().insertColumn(self._pushCol)
elif self._direction == ttk.TTkK.VERTICAL or self._orientation == ttk.TTkK.VERTICAL:
self.layout().insertRow(self._pushRow)
self.layout().addWidget(sw, self._pushRow, self._pushCol,1,1)
@ttk.pyTTkSlot(int, int)
def _expandButtonDragged(self, dx, dy, expand):
# ttk.TTkLog.debug(f"Drag - {(dx,dy)}")
self._snappId = None
self.update()
if expand:
self._snappId = self._expandButton
elif self._spanOver:
for snapId in self._spanOver:
((x,y,w,h),_) = snapId
ttk.TTkLog.debug(f"{snapId=} {(x,dx,w)=} {(y,dy,h)=}")
if x<=dx<x+w and y<=dy<y+h:
# ttk.TTkLog.debug(f"XXXX -> {snapId=}")
self._snappId = snapId
return
ttk.pyTTkSlot()
def _clickExpand(self):
if not self._expandButton.isVisible(): return
self._expandButton.hide()
dir, wid = self._expandStuff
row = wid._row
col = wid._col
rowspan = wid._rowspan
colspan = wid._colspan
self.layout().removeItem(wid)
if ttk.TTkK.TOP and row==0:
self.layout().insertRow(0)
row+=1
elif ttk.TTkK.LEFT and col==0:
self.layout().insertColumn(0)
col+=1
rc = (row,col,rowspan,colspan)
if self._snappId == self._expandButton:
rc = {
ttk.TTkK.TOP : (row-1,col, rowspan+1,colspan),
ttk.TTkK.BOTTOM : (row, col, rowspan+1,colspan),
ttk.TTkK.LEFT : (row, col-1,rowspan,colspan+1),
ttk.TTkK.RIGHT : (row, col, rowspan,colspan+1),
}.get(dir, (row,col,rowspan,colspan))
elif self._snappId:
(_,rc) = self._snappId
self._snappId = None
self.layout().addItem(wid,*rc)
def _processMouseOver(self, x, y):
# cehck the closest edge
col, row, dir, pos, placesSpan = 0, 0, None, None, []
wid = None
if type(self.layout()) != ttk.TTkGridLayout:
return dir,pos,wid,placesSpan
# Retrieve a list of widths,heights
rows,cols = self.layout().gridSize()
if not rows or not cols: return dir,pos,wid,placesSpan
horSizes, verSizes = self.layout().getSizes()
# Find the row/col where the pointer is in
for col,(a,b) in enumerate(horSizes):
if a <= x < a+b: break
for row,(a,b) in enumerate(verSizes):
if a <= y < a+b: break
# ix, iw = horSizes[col]
# iy, ih = verSizes[row]
wid = self.layout().itemAtPosition(row,col)
if wid == None:
return dir,pos,wid,placesSpan
col = wid._col
row = wid._row
rowspan = wid._rowspan
colspan = wid._colspan
widX,widY,widW,widH = wid.geometry()
widMaxW,widMaxH = wid.maximumSize()
#Top
if ( y==widY ):
placesSpan = [[[widX,iy,widW,1],[row+i,col,rowspan-i,colspan]] for i,(iy,ih) in enumerate(verSizes[row+1:row+rowspan],1)]
pos = ttk.TTkK.TOP if placesSpan else None
dir = ttk.TTkK.VERTICAL
if ( widMaxH>widH and
not any([self.layout().itemAtPosition(row-1,col+cs) for cs in range(colspan)])):
dir = pos = ttk.TTkK.TOP
#Bottom
elif (widY+widH==y+1):
placesSpan = [[[widX,iy+ih-1,widW,1],[row,col,i,colspan]] for i,(iy,ih) in enumerate(verSizes[row:row+rowspan-1],1)]
pos = ttk.TTkK.BOTTOM if placesSpan else None
dir = ttk.TTkK.VERTICAL
if ( widMaxH>widH and
not any([self.layout().itemAtPosition(row+rowspan,col+cs) for cs in range(colspan)])):
dir = pos = ttk.TTkK.BOTTOM
#Left
elif (x==widX):
placesSpan = [[[ix,widY,1,widH],[row,col+i,rowspan,colspan-i]] for i,(ix,iw) in enumerate(horSizes[col+1:col+colspan],1)]
pos = ttk.TTkK.LEFT if placesSpan else None
dir = ttk.TTkK.HORIZONTAL
if ( widMaxW>widW and
not any([self.layout().itemAtPosition(row+rs,col-1) for rs in range(rowspan)])):
dir = pos = ttk.TTkK.LEFT
#Right
elif (widX+widW==x+1):
placesSpan = [[[ix+iw-1,widY,1,widH],[row,col,rowspan,i]] for i,(ix,iw) in enumerate(horSizes[col:col+colspan-1],1)]
pos = ttk.TTkK.RIGHT if placesSpan else None
dir = ttk.TTkK.HORIZONTAL
if ( widMaxW>widW and
not any([self.layout().itemAtPosition(row+rs,col+colspan) for rs in range(rowspan)])):
dir = pos = ttk.TTkK.RIGHT
else:
wid=None
self._snappId=self._expandButton
# ttk.TTkLog.debug(f"Move {dir} {wid}")
# ttk.TTkLog.debug(f"{horSizes=}")
# ttk.TTkLog.debug(f"{verSizes=}")
# ttk.TTkLog.debug(f"{row=} {col=} {dir=} {self._dragOver=}")
self.update()
return dir,pos,wid,placesSpan
def _processDragOver(self, x, y):
# cehck the closest edge
col, row, dir = 0,0,None
ret = None
# Retrieve a list of widths,heights
rows,cols = self.layout().gridSize()
if not rows or not cols: return col,row,dir,ret
horSizes, verSizes = self.layout().getSizes()
# Find the row/col where the pointer is in
for col,(a,b) in enumerate(horSizes):
if a <= x < a+b: break
for row,(a,b) in enumerate(verSizes):
if a <= y < a+b: break
ix, iw = horSizes[col]
iy, ih = verSizes[row]
dt = y-iy
db = iy+ih-y-1
dl = x-ix
dr = ix+iw-x-1
dmin = min(dt,db,dl,dr)
if self.layout().itemAtPosition(row,col) == None:
ret = (ix, iy, iw, ih)
else:
#Top - we are closer to this edge
if ((dt==dmin) and (self._orientation & ttk.TTkK.VERTICAL) and
( row==0 or
( row>0 and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row-1,col)))):
dir = ttk.TTkK.VERTICAL
if row>0 and self.layout().itemAtPosition(row-1,col):
ret = (ix, iy-1, iw, 2)
else:
ret = (ix, iy, iw, 1)
#Bottom - we are closer to this edge
if ((db==dmin) and (self._orientation & ttk.TTkK.VERTICAL) and
self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row+1,col)):
dir = ttk.TTkK.VERTICAL
if row<rows-1 and self.layout().itemAtPosition(row+1,col):
ret = (ix, iy+ih-1, iw, 2)
else:
ret = (ix, iy+ih-1, iw, 1)
#Left - we are closer to this edge
if ((dl==dmin) and (self._orientation & ttk.TTkK.HORIZONTAL) and
( col==0 or
( col>0 and self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row,col-1)))):
dir = ttk.TTkK.HORIZONTAL
if col>0 and self.layout().itemAtPosition(row,col-1):
ret = (ix-1, iy, 2, ih)
else:
ret = (ix, iy, 1, ih)
#Right - we are closer to this edge
if ((dr==dmin) and (self._orientation & ttk.TTkK.HORIZONTAL) and
self.layout().itemAtPosition(row,col) != self.layout().itemAtPosition(row,col+1)):
dir = ttk.TTkK.HORIZONTAL
if col<cols-1 and self.layout().itemAtPosition(row,col+1):
ret = (ix+iw-1, iy, 2, ih)
else:
ret = (ix+iw-1, iy, 1, ih)
# If we are on the edge of the item push to the next spot
if dir == ttk.TTkK.HORIZONTAL and ix+iw==x+1:
col+=1
if dir == ttk.TTkK.VERTICAL and iy+ih==y+1:
row+=1
# ttk.TTkLog.debug(f"{horSizes=}")
# ttk.TTkLog.debug(f"{verSizes=}")
# ttk.TTkLog.debug(f"{row=} {col=} {dir=} {self._dragOver=}")
self.update()
return row, col, dir, ret
# Stupid hack to paint on top of the child widgets
def paintChildCanvas(self):
super().paintChildCanvas()
canvas = self.getCanvas()
def _lineDraw(x,y,w,h,color):
if h==1 and w==1:
canvas.drawText(text='', pos=(x,y), color=color)
elif w==1:
canvas.drawText(text='', pos=(x,y), color=color)
canvas.drawText(text='', pos=(x,y+h-1), color=color)
for yy in range(y+1,y+h-1):
canvas.drawText(text='', pos=(x, yy), color=color)
elif h==1:
txt = ''+''*(w-2)+''
canvas.drawText(text=txt, pos=(x,y), width=w, color=color)
else:
canvas.drawBox(pos=(x,y), size=(w,h), color=color)
if self._dragOver is not None:
_lineDraw(*self._dragOver, ttk.TTkColor.fg("FFFF00"))
if self._spanOver:
for (geom,_) in self._spanOver:
_lineDraw(*geom, ttk.TTkColor.fg("FFFF00"))
if self._snappId and self._snappId != self._expandButton:
(geom,_) = self._snappId
_lineDraw(*geom, ttk.TTkColor.bg("88FF88")+ttk.TTkColor.fg("#000044"))