@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = [ ' TTKode ' ]
__all__ = [ ' TTKode ' , ' TTKodeWidget ' ]
import os
from typing import List , Tuple , Optional , Any
@ -31,6 +31,10 @@ from TermTk.TTkWidgets.tabwidget import _TTkNewTabWidgetDragData
from . about import About
from . activitybar import TTKodeActivityBar
class TTKodeWidget ( ) :
def closeRequested ( self , tab : ttk . TTkTabWidget , num : int ) :
raise NotImplementedError ( )
class TTKodeFileWidgetItem ( ttk . TTkTreeWidgetItem ) :
__slots__ = ( ' _path ' , ' _lineNumber ' )
def __init__ ( self , * args , path : str , lineNumber : int = 0 , * * kwargs ) - > None :
@ -45,16 +49,24 @@ class TTKodeFileWidgetItem(ttk.TTkTreeWidgetItem):
class _TextDocument ( ttk . TextDocumentHighlight ) :
__slots__ = ( ' _filePath ' , ' _tabText ' ,
' fileChangedStatus ' , ' _changedStatus ' , ' _savedSnapshot ' )
def __init__ ( self , filePath : str = " " , tabText : ttk . TTkString = ttk . TTkString ( ) , * * kwargs ) :
def __init__ ( self , filePath : str = " " , * * kwargs ) :
self . fileChangedStatus : ttk . pyTTkSignal = ttk . pyTTkSignal ( bool , _TextDocument )
self . _filePath : str = filePath
self . _tabText = tabText
self . _changedStatus : bool = False
self . _genTabText ( )
super ( ) . __init__ ( * * kwargs )
self . _savedSnapshot = self . snapshootId ( )
self . guessLexerFromFilename ( filePath )
self . contentsChanged . connect ( self . _handleContentChanged )
def _genTabText ( self ) :
self . _tabText = (
ttk . TTkString ( ttk . TTkCfg . theme . fileIcon . getIcon ( self . _filePath ) , ttk . TTkCfg . theme . fileIconColor ) +
ttk . TTkColor . RST + " " + os . path . basename ( self . _filePath ) )
def isChanged ( self ) - > bool :
return self . _changedStatus
def getTabButtonStyle ( self ) - > dict :
if self . _changedStatus :
return { ' default ' : { ' closeGlyph ' : ' ● ' } }
@ -70,17 +82,67 @@ class _TextDocument(ttk.TextDocumentHighlight):
ttk . TTkLog . debug ( f " { self . isUndoAvailable ( ) =} == { self . _changedStatus =} " )
self . fileChangedStatus . emit ( self . _changedStatus , self )
def save ( self ) :
self . _changedStatus = False
self . _savedSnapshot = self . snapshootId ( )
self . fileChangedStatus . emit ( self . _changedStatus , self )
pass
class _TextEdit ( ttk . TTkTextEdit ) :
def save ( self ) - > None :
try :
with open ( self . _filePath , ' w ' ) as f :
f . write ( self . toPlainText ( ) )
self . _changedStatus = False
self . _savedSnapshot = self . snapshootId ( )
self . fileChangedStatus . emit ( self . _changedStatus , self )
except Exception as e :
ttk . TTkLog . error ( f " Error saving file: { e } " )
def saveToFile ( self , fileName : str ) - > None :
self . _filePath = fileName
self . _genTabText ( )
self . save ( )
class _TextEdit ( ttk . TTkTextEdit , TTKodeWidget ) :
__slots__ = ( ' docFocussed ' )
def __init__ ( self , * * kwargs ) :
self . docFocussed = ttk . pyTTkSignal ( _TextDocument )
super ( ) . __init__ ( * * kwargs )
self . cursorPositionChanged . connect ( self . _positionChanged )
self . textEditView ( ) . focusChanged . connect ( self . _handleFocusChanged )
@ttk . pyTTkSlot ( bool )
def _handleFocusChanged ( self , focus : bool ) - > None :
if focus :
self . docFocussed . emit ( self . document ( ) )
def closeRequested ( self , tab : ttk . TTkTabWidget , num : int ) :
from ttkode import ttkodeProxy
doc = self . document ( )
docs = [ wid . document ( ) for wid in ttkodeProxy . iterWidgets ( _TextEdit ) if wid is not self ]
if not doc . isChanged ( ) or doc in docs :
ttkodeProxy . closeTab ( self )
else :
pass
# Do you want to save the changes you made to ""?
# Your saves will be lost if you don't save them.
# Save, Don't Save, Cancel
messageBox = ttk . TTkMessageBox (
title = " 🚨 Close? 🚨 " ,
text = ttk . TTkString ( f " Do you want to save the change \n you made to { os . path . basename ( doc . _filePath ) } ? \n \n Your saves will be lost \n if you don ' t save them. " ) ,
icon = ttk . TTkMessageBox . Icon . Warning ,
standardButtons =
ttk . TTkMessageBox . StandardButton . Discard |
ttk . TTkMessageBox . StandardButton . Save |
ttk . TTkMessageBox . StandardButton . Cancel )
@ttk . pyTTkSlot ( ttk . TTkMessageBox . StandardButton )
def _cb ( btn ) :
if btn == ttk . TTkMessageBox . StandardButton . Save :
doc . save ( )
self . textEditView ( ) . focusChanged . clear ( )
ttkodeProxy . closeTab ( self )
if btn == ttk . TTkMessageBox . StandardButton . Discard :
self . textEditView ( ) . focusChanged . clear ( )
ttkodeProxy . closeTab ( self )
elif btn == ttk . TTkMessageBox . StandardButton . Cancel :
return
messageBox . buttonSelected . clear ( )
messageBox . buttonSelected . connect ( _cb )
ttk . TTkHelper . overlay ( None , messageBox , 5 , 5 , True )
@ttk . pyTTkSlot ( ttk . TTkTextCursor )
def _positionChanged ( self , cursor : ttk . TTkTextCursor ) :
@ -106,8 +168,10 @@ class _TextEdit(ttk.TTkTextEdit):
tedit . textCursor ( ) . setPosition ( line = linenum , pos = 0 )
class TTKode ( ttk . TTkGridLayout ) :
__slots__ = ( ' _kodeTab ' , ' _activityBar ' )
__slots__ = ( ' _kodeTab ' , ' _activityBar ' , ' _lastDoc ' )
_lastDoc : Optional [ _TextDocument ]
def __init__ ( self , * * kwargs ) :
self . _lastDoc = None
super ( ) . __init__ ( * * kwargs )
appTemplate = ttk . TTkAppTemplate ( border = False )
@ -119,15 +183,17 @@ class TTKode(ttk.TTkGridLayout):
appTemplate . setMenuBar ( appMenuBar := ttk . TTkMenuBarLayout ( ) , ttk . TTkAppTemplate . MAIN )
fileMenu = appMenuBar . addMenu ( " &File " )
fileMenu . addMenu ( " Open " ) . menuButtonClicked . connect ( self . _showFileDialog )
fileMenu . addMenu ( " &Save " ) . menuButtonClicked . connect ( self . saveLastDoc )
fileMenu . addMenu ( " Save &As... " ) . menuButtonClicked . connect ( self . saveLastDocAs )
fileMenu . addMenu ( " Close " ) # .menuButtonClicked.connect(self._closeFile)
fileMenu . addMenu ( " Exit " ) . menuButtonClicked . connect ( lambda _ : ttk . TTkHelper . quit ( ) )
fileMenu . addMenu ( " Exit " ) . menuButtonClicked . connect ( self . _quit )
def _showAbout ( btn ) :
ttk . TTkHelper . overlay ( None , About ( ) , 30 , 10 )
def _showAboutTTk ( btn ) :
ttk . TTkHelper . overlay ( None , ttk . TTkAbout ( ) , 30 , 10 )
appMenuBar . addMenu ( " &Quit " , alignment = ttk . TTkK . RIGHT_ALIGN ) . menuButtonClicked . connect ( ttk . TTkHelper . quit )
appMenuBar . addMenu ( " &Quit " , alignment = ttk . TTkK . RIGHT_ALIGN ) . menuButtonClicked . connect ( self . _ quit)
helpMenu = appMenuBar . addMenu ( " &Help " , alignment = ttk . TTkK . RIGHT_ALIGN )
helpMenu . addMenu ( " About ... " ) . menuButtonClicked . connect ( _showAbout )
helpMenu . addMenu ( " About ttk " ) . menuButtonClicked . connect ( _showAboutTTk )
@ -161,14 +227,86 @@ class TTKode(ttk.TTkGridLayout):
self . _kodeTab . tabAdded . connect ( self . _tabAdded )
self . _kodeTab . kodeTabCloseRequested . connect ( self . _handleTabCloseRequested )
ttk . TTkShortcut ( ttk . TTkK . CTRL | ttk . TTkK . Key_S ) . activated . connect ( self . saveLastDoc )
@ttk . pyTTkSlot ( _TextDocument )
def _handleDocFocussed ( self , doc : _TextDocument ) :
self . _lastDoc = doc
@ttk . pyTTkSlot ( )
def _quit ( self ) :
from ttkode import ttkodeProxy
docs = set ( os . path . basename ( wid . document ( ) . _filePath ) for wid in ttkodeProxy . iterWidgets ( _TextEdit ) if wid . document ( ) . isChanged ( ) )
if docs :
messageBox = ttk . TTkMessageBox (
title = " 🚨 Quit? 🚨 " ,
text = ttk . TTkString ( " Do you want to quit? \n There are still unsaved documents, \n if you quit those changes will be lost " + ' ' . join ( [ f " \n - { _d } " for _d in docs ] ) ) ,
icon = ttk . TTkMessageBox . Icon . Warning ,
standardButtons =
ttk . TTkMessageBox . StandardButton . Ok |
ttk . TTkMessageBox . StandardButton . Cancel )
@ttk . pyTTkSlot ( ttk . TTkMessageBox . StandardButton )
def _cb ( btn ) :
if btn == ttk . TTkMessageBox . StandardButton . Ok :
ttk . TTkHelper . quit ( )
elif btn == ttk . TTkMessageBox . StandardButton . Cancel :
return
messageBox . buttonSelected . clear ( )
messageBox . buttonSelected . connect ( _cb )
ttk . TTkHelper . overlay ( None , messageBox , 5 , 5 , True )
else :
ttk . TTkHelper . quit ( )
@ttk . pyTTkSlot ( )
def saveLastDoc ( self ) :
if self . _lastDoc :
self . _lastDoc . save ( )
@ttk . pyTTkSlot ( )
def saveLastDocAs ( self ) :
if not self . _lastDoc :
return
def _approveFile ( fileName ) :
if os . path . exists ( fileName ) :
@ttk . pyTTkSlot ( ttk . TTkMessageBox . StandardButton )
def _cb ( btn ) :
if btn == ttk . TTkMessageBox . StandardButton . Save :
self . _lastDoc . saveToFile ( fileName )
elif btn == ttk . TTkMessageBox . StandardButton . Cancel :
return
messageBox = ttk . TTkMessageBox (
text = (
ttk . TTkString ( f ' A file named " { os . path . basename ( fileName ) } " already exists. \n Do you want to replace it? ' , ttk . TTkColor . BOLD ) +
ttk . TTkString ( f ' \n \n Replacing it will overwrite its contents. ' ) ) ,
icon = ttk . TTkMessageBox . Icon . Warning ,
standardButtons =
ttk . TTkMessageBox . StandardButton . Discard |
ttk . TTkMessageBox . StandardButton . Save |
ttk . TTkMessageBox . StandardButton . Cancel )
messageBox . buttonSelected . connect ( _cb )
ttk . TTkHelper . overlay ( None , messageBox , 5 , 5 , True )
else :
self . _lastDoc . saveToFile ( fileName )
filePicker = ttk . TTkFileDialogPicker (
pos = ( 3 , 3 ) , size = ( 80 , 30 ) ,
acceptMode = ttk . TTkK . AcceptMode . AcceptSave ,
caption = " Save As... " ,
path = self . _lastDoc . _filePath ,
fileMode = ttk . TTkK . FileMode . AnyFile ,
filter = " All Files (*) " )
filePicker . pathPicked . connect ( _approveFile )
ttk . TTkHelper . overlay ( None , filePicker , 5 , 5 , True )
@ttk . pyTTkSlot ( ttk . TTkTabWidget , int )
def _handleTabCloseRequested ( self , tab : ttk . TTkTabWidget , num : int ) :
tab . removeTab ( num )
# tab.removeTab(num)
tab . widget ( num ) . closeRequested ( tab , num )
def _getTabButtonFromWidget ( self , widget : ttk . TTkWidget ) - > ttk . TTkTabButton :
for item , tab in self . _kodeTab . iterWidgets ( ) :
if item == widget :
return tab
for kt , index in self . _kodeTab . iterItem s ( ) :
if kt . widget ( index ) == widget :
return kt . tabButton ( index )
return None
ttk . pyTTkSlot ( ttk . TTkTabWidget , int )
@ -185,26 +323,26 @@ class TTKode(ttk.TTkGridLayout):
ttk . TTkHelper . overlay ( None , filePicker , 20 , 5 , True )
def _getDocument ( self , filePath ) - > Tuple [ _TextDocument , Optional [ _TextEdit ] ] :
for item , _ in self . _kodeTab . iterWidget s ( ) :
if issubclass ( type ( item ) , _TextEdit ) :
doc = item . document ( )
for kt , index in self . _kodeTab . iterItem s ( ) :
if issubclass ( type ( wid := kt . widget ( index ) ) , _TextEdit ) :
doc = wid . document ( )
if issubclass ( type ( doc ) , _TextDocument ) :
if filePath == doc . _filePath :
return doc , item
return doc , wid
with open ( filePath , ' r ' ) as f :
content = f . read ( )
tabText = ttk . TTkString ( ttk . TTkCfg . theme . fileIcon . getIcon ( filePath ) , ttk . TTkCfg . theme . fileIconColor ) + ttk . TTkColor . RST + " " + os . path . basename ( filePath )
td = _TextDocument ( text = content , filePath = filePath , tabText = tabText )
td = _TextDocument ( text = content , filePath = filePath )
td . fileChangedStatus . connect ( self . _handleFileChangedStatus )
return td , None
ttk . pyTTkSlot ( bool , _TextDocument )
def _handleFileChangedStatus ( self , status : bool , doc : _TextDocument ) - > None :
# ttk.TTkLog.debug(f"Status ({status}) -> {doc._filePath}")
for item , tab in self . _kodeTab . iterWidgets ( ) :
if issubclass ( type ( item ) , _TextEdit ) :
if doc == item . document ( ) :
tab . mergeStyle ( doc . getTabButtonStyle ( ) )
for kt , index in self . _kodeTab . iterItems ( ) :
if issubclass ( type ( wid := kt . widget ( index ) ) , _TextEdit ) :
if doc == wid . document ( ) :
kt . tabButton ( index ) . mergeStyle ( doc . getTabButtonStyle ( ) )
kt . tabButton ( index ) . setText ( doc . _tabText )
def _openFile ( self , filePath , line : int = 0 , pos : int = 0 ) :
filePath = os . path . realpath ( filePath )
@ -213,6 +351,7 @@ class TTKode(ttk.TTkGridLayout):
self . _kodeTab . setCurrentWidget ( tedit )
else :
tedit = _TextEdit ( document = doc , readOnly = False , lineNumber = True )
tedit . docFocussed . connect ( self . _handleDocFocussed )
self . _kodeTab . addTab ( tedit , doc . _tabText )
self . _kodeTab . setCurrentWidget ( tedit )
tedit . goToLine ( line )
@ -235,6 +374,7 @@ class TTKode(ttk.TTkGridLayout):
if filePath :
doc , _ = self . _getDocument ( filePath = filePath )
tedit = _TextEdit ( document = doc , readOnly = False , lineNumber = True )
tedit . docFocussed . connect ( self . _handleDocFocussed )
tedit . goToLine ( linenum )
newData = _TTkNewTabWidgetDragData (
widget = tedit ,