diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 1233f4e8..b94bb317 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -188,7 +188,7 @@ class TTkCanvas: x,y = pos self._set(y, x, char, color) - def drawText(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE): + def drawText(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): ''' NOTE: drawText is one of the most abused functions, @@ -209,7 +209,9 @@ class TTkCanvas: if isinstance(text, TTkString): text = text.align(width=width, alignment=alignment, color=color) txt, colors = text.getData() - for i in range(0, min(len(txt),self._width-x)): + if forceColor: + colors=[color]*len(colors) + for i in range(max(0,-x), min(len(txt),self._width-x)): #self._set(y, x+i, txt[i-x], colors[i-x]) self._data[y][x+i] = txt[i] self._colors[y][x+i] = colors[i].mod(x+i,y) diff --git a/TermTk/TTkCore/string.py b/TermTk/TTkCore/string.py index 75d52e9f..7721ad83 100644 --- a/TermTk/TTkCore/string.py +++ b/TermTk/TTkCore/string.py @@ -25,7 +25,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal -from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.color import TTkColor, _TTkColor class TTkString(): __slots__ = ('_text','_colors','_baseColor') @@ -49,7 +49,7 @@ class TTkString(): elif isinstance(other, str): ret._text = self._text + other ret._colors = self._colors + [self._baseColor]*len(other) - elif isinstance(other, TTkColor): + elif isinstance(other, _TTkColor): ret._text = self._text ret._colors = self._colors ret._baseColor = other @@ -72,6 +72,9 @@ class TTkString(): def __getitem__(self, index): raise NotImplementedError() + def toAscii(self): + return self._text + def toAansi(self): out = "" color = None diff --git a/TermTk/TTkGui/fileicon_ascii.py b/TermTk/TTkGui/fileicon_ascii.py index 35af2e18..bf6d1f1a 100644 --- a/TermTk/TTkGui/fileicon_ascii.py +++ b/TermTk/TTkGui/fileicon_ascii.py @@ -24,5 +24,6 @@ class FileIcon(): folder_close = '-' folder_open = '+' + @staticmethod def getIcon(fileName): return ' ' \ No newline at end of file diff --git a/TermTk/TTkGui/fileicon_nerd.py b/TermTk/TTkGui/fileicon_nerd.py index 5cf24d7a..46ef7cb5 100644 --- a/TermTk/TTkGui/fileicon_nerd.py +++ b/TermTk/TTkGui/fileicon_nerd.py @@ -30,6 +30,7 @@ import re import os +from TermTk.TTkCore.color import TTkColor class FileIcon(): folder_close = '' @@ -194,6 +195,7 @@ class FileIcon(): ('.rproj' , '鉶'), ('.rs' , ''), ('.rss' , ''), + ('.rst' , ''), ('.sass' , ''), ('.scala' , ''), ('.scss' , ''), @@ -204,6 +206,7 @@ class FileIcon(): ('.sql' , ''), ('.styl' , ''), ('.suo' , ''), + ('.svg' , 'ﰟ'), ('.swift' , ''), ('.t' , ''), ('.tex' , 'ﭨ'), @@ -222,9 +225,11 @@ class FileIcon(): ('.yml' , ''), ('.zsh' , '')) + @staticmethod def getIcon(fileName): fileName = os.path.basename(fileName) + fileName = fileName.lower() # Check the exact match for m, i in FileIcon.file_node_exact_matches: if m == fileName: @@ -236,9 +241,8 @@ class FileIcon(): return i # Check the file extension - fileName = fileName.lower() for m, i in FileIcon.file_node_extensions: - if re.match(m,fileName.endswith()): + if fileName.endswith(m): return i return '' \ No newline at end of file diff --git a/TermTk/TTkGui/fileicon_utf8.py b/TermTk/TTkGui/fileicon_utf8.py index 4449b996..7abeeb3e 100644 --- a/TermTk/TTkGui/fileicon_utf8.py +++ b/TermTk/TTkGui/fileicon_utf8.py @@ -24,5 +24,6 @@ class FileIcon(): folder_close = '-' folder_open = '+' + @staticmethod def getIcon(fileName): return '▫' \ No newline at end of file diff --git a/TermTk/TTkGui/theme.py b/TermTk/TTkGui/theme.py index 6bd0e25b..c567a197 100644 --- a/TermTk/TTkGui/theme.py +++ b/TermTk/TTkGui/theme.py @@ -24,6 +24,7 @@ from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.string import TTkString import TermTk.TTkGui.fileicon_nerd as fi_nerd import TermTk.TTkGui.fileicon_utf8 as fi_utf8 import TermTk.TTkGui.fileicon_ascii as fi_ascii @@ -48,10 +49,21 @@ class TTkTheme(): tab = draw_utf8.TTkTheme.tab braille = draw_utf8.TTkTheme.braille - getFileIcon = fi_utf8.FileIcon.getIcon - folderIconClose = fi_utf8.FileIcon.folder_close - folderIconOpen = fi_utf8.FileIcon.folder_open + fileNameColor = TTkColor.RST # Simil NerdTree purple + folderNameColor = TTkColor.fg("#AAFFFF") # Yellowish + fileIconColor = TTkColor.fg("#FFAAFF") # Simil NerdTree purple + folderIconColor = TTkColor.fg("#FFFFAA") # Yellowish + getIcon = fi_utf8.FileIcon.getIcon + folderIconClose = TTkString() + folderIconColor + fi_utf8.FileIcon.folder_close + TTkColor.RST + folderIconOpen = TTkString() + folderIconColor + fi_utf8.FileIcon.folder_open + TTkColor.RST + + + @staticmethod + def getFileIcon(file): + return TTkString() + TTkTheme.fileIconColor + TTkTheme.getIcon(file) + TTkColor.RST + + @staticmethod def loadTheme(theme): TTkTheme.hline = theme['draw'].TTkTheme.hline TTkTheme.vline = theme['draw'].TTkTheme.vline @@ -65,9 +77,9 @@ class TTkTheme(): TTkTheme.tab = theme['draw'].TTkTheme.tab TTkTheme.braille = theme['draw'].TTkTheme.braille - TTkTheme.getFileIcon = theme['file'].FileIcon.getIcon - TTkTheme.folderIconClose = theme['file'].FileIcon.folder_close - TTkTheme.folderIconOpen = theme['file'].FileIcon.folder_open + TTkTheme.getIcon = theme['file'].FileIcon.getIcon + TTkTheme.folderIconClose = TTkString() + TTkTheme.folderIconColor + theme['file'].FileIcon.folder_close + TTkColor.RST + TTkTheme.folderIconOpen = TTkString() + TTkTheme.folderIconColor + theme['file'].FileIcon.folder_open + TTkColor.RST TTkHelper.updateAll() diff --git a/TermTk/TTkWidgets/TTkPickers/filepicker.py b/TermTk/TTkWidgets/TTkPickers/filepicker.py index 58abdf18..556d6591 100644 --- a/TermTk/TTkWidgets/TTkPickers/filepicker.py +++ b/TermTk/TTkWidgets/TTkPickers/filepicker.py @@ -28,6 +28,7 @@ import datetime from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.window import TTkWindow from TermTk.TTkWidgets.tree import TTkTree from TermTk.TTkWidgets.treewidgetitem import TTkTreeWidgetItem @@ -35,14 +36,21 @@ from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal class _FileTreeWidgetItem(TTkTreeWidgetItem): - __slots__ = ('_path') + FILE = 0x00 + DIR = 0x01 + + __slots__ = ('_path', '_type') def __init__(self, *args, **kwargs): TTkTreeWidgetItem.__init__(self, *args, **kwargs) self._path = kwargs.get('path', '.') + self._type = kwargs.get('type', _FileTreeWidgetItem.FILE) def getPath(self): return self._path + def getType(self): + return self._type + class TTkFileDialogPicker(TTkWindow): __slots__ = ('_path', '_filter', '_caption', #Signals @@ -65,6 +73,8 @@ class TTkFileDialogPicker(TTkWindow): fileTree = TTkTree() fileTree.setHeaderLabels(["Name", "Size", "Type", "Date Modified"]) fileTree.itemExpanded.connect(TTkFileDialogPicker._updateChildren) + fileTree.itemExpanded.connect(TTkFileDialogPicker._folderExpanded) + fileTree.itemCollapsed.connect(TTkFileDialogPicker._folderCollapsed) for i in TTkFileDialogPicker._getFileItems(self._path): fileTree.addTopLevelItem(i) @@ -90,15 +100,34 @@ class TTkFileDialogPicker(TTkWindow): description = [ n, info.st_size, ] if os.path.isdir(nodePath): - ret.append(_FileTreeWidgetItem([ n, "", "Dir", time],path=nodePath, childIndicatorPolicy=TTkK.ShowIndicator)) + ret.append(_FileTreeWidgetItem( + [ TTkString()+TTkCfg.theme.folderNameColor+n+'/', "", "Dir", time], + path=nodePath, + type=_FileTreeWidgetItem.DIR, + icon=TTkCfg.theme.folderIconClose, + childIndicatorPolicy=TTkK.ShowIndicator)) elif os.path.isfile(nodePath): - ret.append(_FileTreeWidgetItem([ n, size, "File", time],path=nodePath, childIndicatorPolicy=TTkK.DontShowIndicator)) + ret.append(_FileTreeWidgetItem( + [ TTkString()+TTkCfg.theme.fileNameColor+n, size, "File", time], + path=nodePath, + type=_FileTreeWidgetItem.FILE, + icon=TTkCfg.theme.getFileIcon(n), + childIndicatorPolicy=TTkK.DontShowIndicator)) elif os.path.islink(nodePath): pass elif os.path.ismount(nodePath): pass return ret + @staticmethod + def _folderExpanded(item): + item.setIcon(0, TTkCfg.theme.folderIconOpen) + + @staticmethod + def _folderCollapsed(item): + item.setIcon(0, TTkCfg.theme.folderIconClose) + + @staticmethod def _updateChildren(item): if item.children(): return for i in TTkFileDialogPicker._getFileItems(item.getPath()): diff --git a/TermTk/TTkWidgets/tree.py b/TermTk/TTkWidgets/tree.py index 60910759..e3aa6449 100644 --- a/TermTk/TTkWidgets/tree.py +++ b/TermTk/TTkWidgets/tree.py @@ -31,7 +31,7 @@ class TTkTree(TTkAbstractScrollArea): __slots__ = ( '_treeView', # Forwarded Signals - 'itemActivated', 'itemChanged', 'itemClicked', 'itemExpanded', 'itemDoubleClicked', + 'itemActivated', 'itemChanged', 'itemClicked', 'itemExpanded', 'itemCollapsed', 'itemDoubleClicked', # Forwarded Methods 'setAlignment', 'setHeader', 'setHeaderLabels', 'setColumnSize', 'setColumnColors', 'appendItem', 'addTopLevelItem' ) @@ -45,6 +45,7 @@ class TTkTree(TTkAbstractScrollArea): self.itemChanged = self._treeView.itemChanged self.itemClicked = self._treeView.itemClicked self.itemExpanded = self._treeView.itemExpanded + self.itemCollapsed = self._treeView.itemCollapsed self.itemDoubleClicked = self._treeView.itemDoubleClicked self.setFocusPolicy(TTkK.ClickFocus) diff --git a/TermTk/TTkWidgets/treewidget.py b/TermTk/TTkWidgets/treewidget.py index 4b217b48..31f5124d 100644 --- a/TermTk/TTkWidgets/treewidget.py +++ b/TermTk/TTkWidgets/treewidget.py @@ -37,7 +37,7 @@ class TTkTreeWidget(TTkAbstractScrollView): '_selectedId', '_selected', '_separatorSelected', '_mouseDelta', '_headerColor', '_selectedColor', # Signals - 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemActivated' + 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' ) @dataclass(frozen=True) class _Cache: @@ -52,6 +52,7 @@ class TTkTreeWidget(TTkAbstractScrollView): self.itemClicked = pyTTkSignal(TTkTreeWidgetItem, int) self.itemDoubleClicked = pyTTkSignal(TTkTreeWidgetItem, int) self.itemExpanded = pyTTkSignal(TTkTreeWidgetItem) + self.itemCollapsed = pyTTkSignal(TTkTreeWidgetItem) super().__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkTreeView' ) @@ -69,7 +70,7 @@ class TTkTreeWidget(TTkAbstractScrollView): # Overridden function def viewFullAreaSize(self) -> (int, int): - w = self._columnsPos[-1] if self._columnsPos else 0 + w = self._columnsPos[-1]+1 if self._columnsPos else 0 h = 1+sum([c.size() for c in self._items]) # TTkLog.debug(f"{w=} {h=}") return w,h @@ -105,6 +106,8 @@ class TTkTreeWidget(TTkAbstractScrollView): item.setExpanded(not item.isExpanded()) if item.isExpanded(): self.itemExpanded.emit(item) + else: + self.itemCollapsed.emit(item) if self._selected: self._selected.setSelected(False) self._selectedId = y @@ -151,6 +154,8 @@ class TTkTreeWidget(TTkAbstractScrollView): item.setExpanded(not item.isExpanded()) if item.isExpanded(): self.itemExpanded.emit(item) + else: + self.itemCollapsed.emit(item) else: if self._selected: self._selected.setSelected(False) @@ -209,18 +214,16 @@ class TTkTreeWidget(TTkAbstractScrollView): ''' self._cache = [] def _addToCache(_child, _level): - tt = TTkCfg.theme.tree _data = [] for _il in range(len(self._header)): - _data.append(_child.data(_il)) - if _child.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and _child.children() or \ - _child.childIndicatorPolicy() == TTkK.ShowIndicator: - if _child.isExpanded(): - _data[0] = f"{' '*_level} {tt[2]} {_data[0]}" + _icon = _child.icon(_il) + if _icon: + _icon = ' '+_icon+' ' + if _il==0: + _data.append(' '*_level+_icon+_child.data(_il)) else: - _data[0] = f"{' '*_level} {tt[1]} {_data[0]}" - else: - _data[0] = f"{' '*_level} {tt[0]} {_data[0]}" + _data.append(_icon+_child.data(_il)) + self._cache.append(TTkTreeWidget._Cache( item = _child, level = _level, @@ -254,8 +257,10 @@ class TTkTreeWidget(TTkAbstractScrollView): if i-y<0 : continue item = c.item level = c.level - color = self._selectedColor if item.isSelected() else TTkColor.RST for il in range(len(self._header)): lx = 0 if il==0 else self._columnsPos[il-1]+1 lx1 = self._columnsPos[il] - self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx, color=color) + if item.isSelected(): + self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx, color=self._selectedColor, forceColor=True) + else: + self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx) diff --git a/TermTk/TTkWidgets/treewidgetitem.py b/TermTk/TTkWidgets/treewidgetitem.py index 8e526be0..a3e1d399 100644 --- a/TermTk/TTkWidgets/treewidgetitem.py +++ b/TermTk/TTkWidgets/treewidgetitem.py @@ -31,7 +31,7 @@ from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel class TTkTreeWidgetItem(TTkAbstractItemModel): __slots__ = ('_parent', '_data', '_children', '_expanded', '_selected', - '_childIndicatorPolicy', + '_childIndicatorPolicy', '_icon', '_defaultIcon' # Signals # 'refreshData' ) @@ -39,25 +39,46 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def __init__(self, *args, **kwargs): # Signals # self.refreshData = pyTTkSignal(TTkTreeWidgetItem) + tt = TTkCfg.theme.tree super().__init__(self, *args, **kwargs) self._children = [] self._data = args[0] if len(args)>0 and type(args[0])==list else None self._parent = kwargs.get('parent', None) self._childIndicatorPolicy = kwargs.get('childIndicatorPolicy', TTkK.DontShowIndicatorWhenChildless) + + self._defaultIcon = True self._expanded = False self._selected = False self._parent = kwargs.get("parent", None) + self._icon = ['']*len(self._data) + self._setDefaultIcon() + if 'icon' in kwargs: + self._icon[0] = kwargs['icon'] + self._defaultIcon = False + + def _setDefaultIcon(self): + if not self._defaultIcon: return + self._icon[0] = TTkCfg.theme.tree[0] + if self._childIndicatorPolicy == TTkK.DontShowIndicatorWhenChildless and self._children or \ + self._childIndicatorPolicy == TTkK.ShowIndicator: + if self._expanded: + self._icon[0] = TTkCfg.theme.tree[2] + else: + self._icon[0] = TTkCfg.theme.tree[1] + def childIndicatorPolicy(self): return self._childIndicatorPolicy def setChildIndicatorPolicy(self, policy): self._childIndicatorPolicy = policy + self._setDefaultIcon() def addChild(self, child): self._children.append(child) child._parent = self child.dataChanged.connect(self.emitDataChanged) + self._setDefaultIcon() self.dataChanged.emit() def addChildren(self, children): @@ -72,6 +93,17 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def children(self): return self._children + def icon(self, col): + if col >= len(self._icon): + return '' + return self._icon[col] + + def setIcon(self, col, icon): + if col==0: + self._defaultIcon = False + self._icon[col] = icon + self.dataChanged.emit() + def data(self, column, role=None): if column >= len(self._data): return '' @@ -86,6 +118,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def setExpanded(self, expand): self._expanded = expand + self._setDefaultIcon() self.emitDataChanged() def setSelected(self, select): diff --git a/demo/demo.py b/demo/demo.py index 02d0a0e5..156f856f 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -40,6 +40,7 @@ from showcase.formwidgets import demoFormWidgets from showcase.scrollarea import demoScrollArea from showcase.list import demoList from showcase.menubar import demoMenuBar +from showcase.filepicker import demoFilePicker from showcase.colorpicker import demoColorPicker from showcase.tree import demoTree from showcase.fancytable import demoFancyTable @@ -106,8 +107,10 @@ def demoShowcase(root=None, border=True): listMenu.addItem(f"Pickers") tabPickers = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) + tabPickers.addTab(demoFilePicker(), " File Picker ") tabPickers.addTab(demoColorPicker(), " Color Picker ") + listMenu.addItem(f"Graphs") tabGraphs = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) tabGraphs.addTab(demoGraph(), " Graph Test ") diff --git a/demo/showcase/filepicker.py b/demo/showcase/filepicker.py index 98355ba6..978481d2 100755 --- a/demo/showcase/filepicker.py +++ b/demo/showcase/filepicker.py @@ -31,8 +31,8 @@ import TermTk as ttk def demoFilePicker(root=None): frame = ttk.TTkFrame(parent=root, border=False) - winFP = ttk.TTkWindow(parent=frame,pos = (0,0), size=(30,16), title="Test File Pickers", border=True) - btn = ttk.TTkButton(parent=winFP, pos=( 0,0), size=(8,3), border=True, text='File' ) + # winFP = ttk.TTkWindow(parent=frame,pos = (0,0), size=(20,10), title="Test File Pickers", border=True) + btn = ttk.TTkButton(parent=frame, pos=( 0,0), size=(8,3), border=True, text='File' ) def _showDialog(): filePicker = ttk.TTkFileDialogPicker(pos = (3,3), size=(75,24), caption="Test File Picker", path=".", filter="All Files (*);;Python Files (*.py)") @@ -49,12 +49,14 @@ def main(): ttk.TTkLog.use_default_file_logging() + ttk.TTkTheme.loadTheme(ttk.TTkTheme.NERD) + root = ttk.TTk() if args.f: root.setLayout(ttk.TTkGridLayout()) winColor1 = root else: - winColor1 = ttk.TTkWindow(parent=root,pos = (0,0), size=(120,50), title="Test File Picker", border=True, layout=ttk.TTkGridLayout()) + winColor1 = ttk.TTkWindow(parent=root,pos = (0,0), size=(50,20), title="Test File Picker", border=True, layout=ttk.TTkGridLayout()) demoFilePicker(winColor1)