# 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.
__all__ = ['TTkTextPicker', 'TTkTextDialogPicker']
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkGui.textcursor import TTkTextCursor
from TermTk.TTkGui.textdocument import TTkTextDocument
from TermTk.TTkLayouts.gridlayout import TTkGridLayout
from TermTk.TTkLayouts.boxlayout import TTkHBoxLayout
from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView
from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer
from TermTk.TTkWidgets.resizableframe import TTkResizableFrame
from TermTk.TTkWidgets.texedit import TTkTextEditView, TTkTextEdit
from TermTk.TTkWidgets.splitter import TTkSplitter
from TermTk.TTkWidgets.button import TTkButton
from TermTk.TTkWidgets.label import TTkLabel
from TermTk.TTkWidgets.checkbox import TTkCheckbox
from TermTk.TTkWidgets.window import TTkWindow
from TermTk.TTkWidgets.TTkModelView.filetree import TTkFileTree
from TermTk.TTkWidgets.TTkModelView.filetreewidgetitem import TTkFileTreeWidgetItem
from TermTk.TTkWidgets.TTkPickers.colorpicker import TTkColorButtonPicker, TTkColorDialogPicker
class _superSimpleHorizontalLine(TTkWidget):
def paintEvent(self, canvas):
w,h = self.size()
canvas.drawText(pos=(0,h-1), text='โ'+('โ'*(w-2))+'โ',color=TTkColor.fg("#888888"))
# List taken from:
# https://emojipicker.com
emoji = {
'Smileys': "๐๐๐๐คฃ๐๐๐
๐๐๐๐๐๐๐๐๐๐๐ค๐ค๐๐๐ถ๐๐๐ฃ๐ฅ๐ฎ๐ค๐ฏ๐ช๐ซ๐ด๐๐ค๐๐๐๐คค๐๐๐๐๐๐ค๐ฒ๐๐๐๐๐ค๐ข๐ญ๐ฆ๐ง๐จ๐ฉ๐ฌ๐ฐ๐ฑ๐ณ๐ต๐ก๐ ๐๐ค ๐คก๐คฅ๐ท๐ค๐ค๐คข๐คง๐๐ฟ๐น๐บ๐๐ป๐ฝ๐พ๐ค๐ฉ๐บ๐ธ๐น๐ป๐ผ๐ฝ๐๐ฟ๐พ๐๐๐๐ฆ๐ง๐จ๐ฉ๐ด๐ต๐ถ๐ผ",
'Body': "๐ช๐คณ๐๐๐๐๐๐ค๐๐ค๐คโ๐๐๐โ๐๐ค๐ค๐ค๐๐๐๐๐๐ค๐
๐๐๐ฃ๐๐
๐",
# 'Flags': "๐ฆ๐จ๐ฆ๐ฉ๐ฆ๐ช๐ฆ๐ซ๐ฆ๐ฌ๐ฆ๐ฎ๐ฆ๐ฑ๐ฆ๐ฒ๐ฆ๐ด๐ฆ๐ถ๐ฆ๐ท๐ฆ๐ธ๐ฆ๐น๐ฆ๐บ๐ฆ๐ผ๐ฆ๐ฝ๐ฆ๐ฟ๐ง๐ฆ๐ง๐ง๐ง๐ฉ๐ง๐ช๐ง๐ซ๐ง๐ฌ๐ง๐ญ๐ง๐ฎ๐ง๐ฏ๐ง๐ฑ๐ง๐ฒ๐ง๐ณ๐ง๐ด๐ง๐ถ๐ง๐ท๐ง๐ธ๐ง๐น๐ง๐ป๐ง๐ผ๐ง๐พ๐ง๐ฟ๐จ๐ฆ๐จ๐จ๐จ๐ฉ๐จ๐ซ๐จ๐ฌ๐จ๐ญ๐จ๐ฎ๐จ๐ฐ๐จ๐ฑ๐จ๐ฒ๐จ๐ณ๐จ๐ด๐จ๐ต๐จ๐ท๐จ๐บ๐จ๐ป๐จ๐ผ๐จ๐ฝ๐จ๐พ๐จ๐ฟ๐ฉ๐ช๐ฉ๐ฌ๐ฉ๐ฏ๐ฉ๐ฐ๐ฉ๐ฒ๐ฉ๐ด๐ฉ๐ฟ๐ช๐ฆ๐ช๐จ๐ช๐ช๐ช๐ฌ๐ช๐ญ๐ช๐ท๐ช๐ธ๐ช๐น๐ช๐บ๐ซ๐ฎ๐ซ๐ฏ๐ซ๐ฐ๐ซ๐ฒ๐ซ๐ด๐ซ๐ท๐ฌ๐ฆ๐ฌ๐ง๐ฌ๐ฉ๐ฌ๐ช๐ฌ๐ซ๐ฌ๐ฌ๐ฌ๐ญ๐ฌ๐ฎ๐ฌ๐ฑ๐ฌ๐ฒ๐ฌ๐ณ๐ฌ๐ต๐ฌ๐ถ๐ฌ๐ท๐ฌ๐ธ๐ฌ๐น๐ฌ๐บ๐ฌ๐ผ๐ฌ๐พ๐ญ๐ฐ๐ญ๐ฒ๐ญ๐ณ๐ญ๐ท๐ญ๐น๐ญ๐บ๐ฎ๐จ๐ฎ๐ฉ๐ฎ๐ช๐ฎ๐ฑ๐ฎ๐ฒ๐ฎ๐ณ๐ฎ๐ด๐ฎ๐ถ๐ฎ๐ท๐ฎ๐ธ๐ฎ๐น๐ฏ๐ช๐ฏ๐ฒ๐ฏ๐ด๐ฏ๐ต๐ฐ๐ช๐ฐ๐ฌ๐ฐ๐ญ๐ฐ๐ฎ๐ฐ๐ฒ๐ฐ๐ณ๐ฐ๐ต๐ฐ๐ท๐ฐ๐ผ๐ฐ๐พ๐ฐ๐ฟ๐ฑ๐ฆ๐ฑ๐ง๐ฑ๐จ๐ฑ๐ฎ๐ฑ๐ฐ๐ฑ๐ท๐ฑ๐ธ๐ฑ๐น๐ฑ๐บ๐ฑ๐ป๐ฑ๐พ๐ฒ๐ฆ๐ฒ๐จ๐ฒ๐ฉ๐ฒ๐ช๐ฒ๐ซ๐ฒ๐ฌ๐ฒ๐ญ๐ฒ๐ฐ๐ฒ๐ฑ๐ฒ๐ฒ๐ฒ๐ณ๐ฒ๐ด๐ฒ๐ต๐ฒ๐ถ๐ฒ๐ท๐ฒ๐ธ๐ฒ๐น๐ฒ๐บ๐ฒ๐ป๐ฒ๐ผ๐ฒ๐ฝ๐ฒ๐พ๐ฒ๐ฟ๐ณ๐ฆ๐ณ๐จ๐ณ๐ช๐ณ๐ซ๐ณ๐ฌ๐ณ๐ฎ๐ณ๐ฑ๐ณ๐ด๐ณ๐ต๐ณ๐ท๐ณ๐บ๐ณ๐ฟ๐ด๐ฒ๐ต๐ฆ๐ต๐ช๐ต๐ซ๐ต๐ฌ๐ต๐ญ๐ต๐ฐ๐ต๐ฑ๐ต๐ฒ๐ต๐ณ๐ต๐ท๐ต๐ธ๐ต๐น๐ต๐ผ๐ต๐พ๐ถ๐ฆ๐ท๐ช๐ท๐ด๐ท๐ธ๐ท๐บ๐ท๐ผ๐ธ๐ฆ๐ธ๐ง๐ธ๐จ๐ธ๐ฉ๐ธ๐ช๐ธ๐ฌ๐ธ๐ญ๐ธ๐ฎ๐ธ๐ฏ๐ธ๐ฐ๐ธ๐ฑ๐ธ๐ฒ๐ธ๐ณ๐ธ๐ด๐ธ๐ท๐ธ๐ธ๐ธ๐น๐ธ๐ป๐ธ๐ฝ๐ธ๐พ๐ธ๐ฟ๐น๐ฆ๐น๐จ๐น๐ฉ๐น๐ซ๐น๐ฌ๐น๐ญ๐น๐ฏ๐น๐ฐ๐น๐ฑ๐น๐ฒ๐น๐ณ๐น๐ด๐น๐ท๐น๐น๐น๐ป๐น๐ผ๐น๐ฟ๐บ๐ฆ๐บ๐ฌ๐บ๐ฒ๐บ๐ณ๐บ๐ธ๐บ๐พ๐บ๐ฟ๐ป๐ฆ๐ป๐จ๐ป๐ช๐ป๐ฌ๐ป๐ฎ๐ป๐ณ๐ป๐บ๐ผ๐ซ๐ผ๐ธ๐ฝ๐ฐ๐พ๐ช๐พ๐น๐ฟ๐ฆ๐ฟ๐ฒ๐ฟ๐ผ",
}
class _emojiPickerView(TTkAbstractScrollView):
__slots__ = ('_btns', '_labels', 'emojiClicked')
def __init__(self, *args, **kwargs) -> None:
self.emojiClicked = pyTTkSignal(str)
super().__init__(*args, **kwargs)
self.viewChanged.connect(self._viewChangedHandler)
self._btns = {}
self._labels = {}
for t in emoji:
self._btns[t]=[]
self._labels[t] = TTkLabel(parent=self, text=t,size=(len(t),1))
for e in emoji[t]:
self._btns[t].append(btn := TTkButton(parent=self, text=e,size=(4,3),border=True))
def _cbEmoji(ch):
def _ccb(): self.emojiClicked.emit(ch)
return _ccb
btn.clicked.connect(_cbEmoji(e))
def resizeEvent(self, w, h):
self._placeEmojis()
return super().resizeEvent(w, h)
def _placeEmojis(self):
x,y=0,0
w,h=self.size()
for t in self._btns:
if x:
y+=3
self._labels[t].move(0,y)
x=0
y+=1
for e in self._btns[t]:
e.move(x,y)
if x+7>=w:
x=0
y+=3
else:
x+=4
@pyTTkSlot()
def _viewChangedHandler(self):
x,y = self.getViewOffsets()
self.layout().setOffset(-x,-y)
def viewFullAreaSize(self) -> tuple[int,int]:
_,_,w,h = self.layout().fullWidgetAreaGeometry()
return w , h
def maximumWidth(self): return 0x10000
def maximumHeight(self): return 0x10000
def minimumWidth(self): return 0
def minimumHeight(self): return 0
class _emojiPickerArea(TTkAbstractScrollArea):
__slots__ = ('_areaView')
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
kwargs.pop('parent',None)
kwargs.pop('visible',None)
self._areaView = _emojiPickerView(*args, **kwargs)
self.setFocusPolicy(TTkK.ClickFocus)
self.setViewport(self._areaView)
class _emojiPicker(TTkResizableFrame):
__slots__ = ('emojiClicked')
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs|{'layout':TTkGridLayout()})
self.layout().addWidget(epa := _emojiPickerArea())
self.emojiClicked = epa.viewport().emojiClicked
[docs]
class TTkTextDialogPicker(TTkWindow):
__slots__ = ('_textEdit', '_autoSize')
def __init__(self, *, autoSize=False, multiLine=True, wrapMode=TTkK.WidgetWidth, **kwargs) -> None:
self._autoSize = autoSize
super().__init__(**kwargs)
fontLayout = TTkGridLayout(columnMinWidth=1)
# Char Fg/Bg buttons
fontLayout.addWidget(cb_fg := TTkCheckbox(text=" FG"),0,0)
fontLayout.addWidget(btn_fgColor := TTkColorButtonPicker(border=True, enabled=False, maxSize=(7,3), minSize=(7,3), returnType=TTkK.ColorPickerReturnType.Foreground),1,0)
fontLayout.addWidget(cb_bg := TTkCheckbox(text=" BG"),0,2)
fontLayout.addWidget(btn_bgColor := TTkColorButtonPicker(border=True, enabled=False, maxSize=(7,3), minSize=(7,3), returnType=TTkK.ColorPickerReturnType.Background),1,2)
# Char style buttons
fontLayout.addWidget(btn_bold := TTkButton(border=True, maxSize=(5,3), minSize=(5,3), checkable=True, text=TTkString( 'a' , TTkColor.BOLD) ),1,4)
fontLayout.addWidget(btn_italic := TTkButton(border=True, maxSize=(5,3), minSize=(5,3), checkable=True, text=TTkString( 'a' , TTkColor.ITALIC) ),1,5)
fontLayout.addWidget(btn_underline := TTkButton(border=True, maxSize=(5,3), minSize=(5,3), checkable=True, text=TTkString(' a ', TTkColor.UNDERLINE) ),1,6)
fontLayout.addWidget(btn_strikethrough := TTkButton(border=True, maxSize=(5,3), minSize=(5,3), checkable=True, text=TTkString(' a ', TTkColor.STRIKETROUGH)),1,7)
fontLayout.addWidget(btn_emoji := TTkButton(border=True, maxSize=(6,4), minSize=(6,3), text=TTkString('๐')),1,9)
fontLayout.addWidget(_superSimpleHorizontalLine(),0,10,2,1)
self._textEdit = TTkTextEdit(document=kwargs.get('document',TTkTextDocument()),multiLine=multiLine)
self._textEdit.setReadOnly(False)
self._textEdit.setLineWrapMode(wrapMode)
self._textEdit.setLineNumber('\n' in self._textEdit.toPlainText())
@pyTTkSlot()
def _showEmojiPicker():
ep = _emojiPicker(size=(40,10))
def _addEmoji(e):
self._textEdit.textCursor().insertText(e, moveCursor=True)
ep.emojiClicked.connect(_addEmoji)
TTkHelper.overlay(btn_emoji, ep, 0, 0)
btn_emoji.clicked.connect(_showEmojiPicker)
@pyTTkSlot(TTkColor)
def _currentColorChangedCB(format:TTkColor):
if format.hasForeground():
cb_fg.setCheckState(TTkK.Checked)
btn_fgColor.setEnabled()
btn_fgColor.setColor(format.foreground())
else:
cb_fg.setCheckState(TTkK.Unchecked)
btn_fgColor.setDisabled()
if format.hasBackground():
cb_bg.setCheckState(TTkK.Checked)
btn_bgColor.setEnabled()
btn_bgColor.setColor(format.background())
else:
cb_bg.setCheckState(TTkK.Unchecked)
btn_bgColor.setDisabled()
btn_bold.setChecked(format.bold())
btn_italic.setChecked(format.italic())
btn_underline.setChecked(format.underline())
btn_strikethrough.setChecked(format.strikethrough())
# TTkLog.debug(f"{fg=} {bg=} {bold=} {italic=} {underline=} {strikethrough= }")
_currentColorChangedCB(self._textEdit.textCursor().positionColor())
self._textEdit.currentColorChanged.connect(_currentColorChangedCB)
def _setStyle():
color = TTkColor()
if cb_fg.checkState() == TTkK.Checked:
color += btn_fgColor.color()
if cb_bg.checkState() == TTkK.Checked:
color += btn_bgColor.color()
if btn_bold.isChecked():
color += TTkColor.BOLD
if btn_italic.isChecked():
color += TTkColor.ITALIC
if btn_underline.isChecked():
color += TTkColor.UNDERLINE
if btn_strikethrough.isChecked():
color += TTkColor.STRIKETROUGH
cursor = self._textEdit.textCursor()
cursor.applyColor(color)
cursor.setColor(color)
self._textEdit.setFocus()
cb_fg.toggled.connect(btn_fgColor.setEnabled)
cb_bg.toggled.connect(btn_bgColor.setEnabled)
cb_fg.clicked.connect(_setStyle)
cb_bg.clicked.connect(_setStyle)
btn_fgColor.colorSelected.connect(_setStyle)
btn_bgColor.colorSelected.connect(_setStyle)
btn_bold.clicked.connect(_setStyle)
btn_italic.clicked.connect(_setStyle)
btn_underline.clicked.connect(_setStyle)
btn_strikethrough.clicked.connect(_setStyle)
layout = TTkGridLayout()
layout.addItem(fontLayout,0,0)
layout.addWidget(self._textEdit,1,0)
self.setLayout(layout)
self._textEdit.viewport().viewChanged.connect(self._textPickerViewChanged)
[docs]
def focusTextEdit(self):
self._textEdit.setFocus()
@pyTTkSlot()
def _textPickerViewChanged(self):
w,h = self.size()
self.resize(w,h)
def resize(self, w: int, h: int):
tw,th = self._textEdit.viewport().viewFullAreaSize()
self._textEdit.setLineNumber(th>1)
if not self._autoSize:
return super().resize(w,h)
t,b,l,r = self.getPadding()
return super().resize(w, th+t+b+4)
[docs]
class TTkTextPicker(TTkContainer):
'''TTkTextPicker
.. note:: This is an early unstable prototype
Do not use it unless you know what you are doing
And I've no idea what I am doing
'''
__slots__ = ('_teButton','_textEdit', 'documentViewChanged', 'textChanged', '_autoSize')
def __init__(self, *, text='', autoSize=False, multiLine=True, wrapMode=TTkK.WidgetWidth, **kwargs) -> None:
self.documentViewChanged = pyTTkSignal(int,int)
self._autoSize = autoSize
super().__init__(**kwargs|{'layout':TTkHBoxLayout()})
self._textEdit = TTkTextEdit(pos=(0,0), size=(self.width()-2,self.height()),multiLine=multiLine)
self._textEdit.setText(text)
self._textEdit.setReadOnly(False)
self._textEdit.setLineWrapMode(wrapMode)
self.textChanged = self._textEdit.textChanged
self._teButton = TTkButton(border=True, text='โ',
addStyle={'default':{'borderColor':TTkColor.fg("#AAAAFF")+TTkColor.bg("#002244")}} ,
pos=(self.width()-2,0),
size=(2,self.height()), minSize=(3,1),maxWidth=3)
self.layout().addWidget(self._textEdit)
self.layout().addWidget(self._teButton)
@pyTTkSlot()
def _showTextDialogPicker():
w,h = self.size()
tdp = TTkTextDialogPicker(size=(50,8+h),
document=self._textEdit.document(),
autoSize=autoSize, multiLine=multiLine, wrapMode=wrapMode)
TTkHelper.overlay(self, tdp, -1, -7, modal=True)
tdp.focusTextEdit()
self._teButton.clicked.connect(_showTextDialogPicker)
self._textEdit.viewport().viewChanged.connect(self._textPickerViewChanged)
def setFocus(self):
return self._textEdit.setFocus()
[docs]
def getTTkString(self):
return self._textEdit.toRawText()
@pyTTkSlot()
def _textPickerViewChanged(self):
wa,ha = self._textEdit.viewport().viewFullAreaSize()
tw,th = self._textEdit.size()
bw,bh = self._teButton.size()
w,h = self.size()
self._textEdit.setLineNumber(ha>1)
self.documentViewChanged.emit(tw+bw,ha)
if self._autoSize:
self.resize(w,ha)