diff --git a/TermTk/TTkGui/fileicon_ascii.py b/TermTk/TTkGui/fileicon_ascii.py index bf6d1f1a..c559ff93 100644 --- a/TermTk/TTkGui/fileicon_ascii.py +++ b/TermTk/TTkGui/fileicon_ascii.py @@ -21,8 +21,8 @@ # SOFTWARE. class FileIcon(): - folder_close = '-' - folder_open = '+' + folder_close = '+' + folder_open = '-' @staticmethod def getIcon(fileName): diff --git a/TermTk/TTkGui/fileicon_utf8.py b/TermTk/TTkGui/fileicon_utf8.py index 7abeeb3e..867df6e0 100644 --- a/TermTk/TTkGui/fileicon_utf8.py +++ b/TermTk/TTkGui/fileicon_utf8.py @@ -21,9 +21,9 @@ # SOFTWARE. class FileIcon(): - folder_close = '-' - folder_open = '+' + folder_close = '+' + folder_open = '-' @staticmethod def getIcon(fileName): - return '▫' \ No newline at end of file + return '∙' \ No newline at end of file diff --git a/TermTk/TTkGui/theme.py b/TermTk/TTkGui/theme.py index c567a197..6b9be83e 100644 --- a/TermTk/TTkGui/theme.py +++ b/TermTk/TTkGui/theme.py @@ -139,4 +139,5 @@ class TTkTheme(): tabSelectColorFocus = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD treeHeaderColor = TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+TTkColor.BOLD - treeSelectedColor = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD \ No newline at end of file + treeSelectedColor = TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD + treeLineColor = TTkColor.fg("#444444") \ No newline at end of file diff --git a/TermTk/TTkWidgets/TTkPickers/filepicker.py b/TermTk/TTkWidgets/TTkPickers/filepicker.py index 556d6591..3971f4ab 100644 --- a/TermTk/TTkWidgets/TTkPickers/filepicker.py +++ b/TermTk/TTkWidgets/TTkPickers/filepicker.py @@ -32,9 +32,30 @@ from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.window import TTkWindow from TermTk.TTkWidgets.tree import TTkTree from TermTk.TTkWidgets.treewidgetitem import TTkTreeWidgetItem +from TermTk.TTkWidgets.splitter import TTkSplitter +from TermTk.TTkWidgets.frame import TTkFrame +from TermTk.TTkWidgets.combobox import TTkComboBox +from TermTk.TTkWidgets.button import TTkButton +from TermTk.TTkWidgets.label import TTkLabel +from TermTk.TTkWidgets.list_ import TTkList from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal + +''' +:: + + +----------------------------------------+ + | Look in: [--FULL-PATH-|v] [<] [>] [^] | + | +-----------+------------------------+ | + | | Bookmarks ║ File Tree | | + | | ║ | | + | +-----------+------------------------+ | + | File name: [-----------] [Open ] | + | Files of Type [-----------] [Cancel] | + +--------------+-------------------------+ +''' + class _FileTreeWidgetItem(TTkTreeWidgetItem): FILE = 0x00 DIR = 0x01 @@ -44,6 +65,7 @@ class _FileTreeWidgetItem(TTkTreeWidgetItem): TTkTreeWidgetItem.__init__(self, *args, **kwargs) self._path = kwargs.get('path', '.') self._type = kwargs.get('type', _FileTreeWidgetItem.FILE) + self.setTextAlignment(1, TTkK.RIGHT_ALIGN) def getPath(self): return self._path @@ -66,11 +88,37 @@ class TTkFileDialogPicker(TTkWindow): self._path = kwargs.get('path','.') self._filter = kwargs.get('filter','All Files (*)') self._caption = kwargs.get('caption','File Dialog') - self.setTitle(self._caption) + self.setTitle(self._caption) self.setLayout(TTkGridLayout()) - fileTree = TTkTree() + # Top (absPath) + topLayout = TTkGridLayout() + self.layout().addItem(topLayout,0,0) + + topLayout.addWidget(TTkLabel(text="Look in:",maxWidth=14), 0,0) + topLayout.addWidget(lookPath := TTkComboBox(list=TTkFileDialogPicker._getListLook(self._path)), 0,1) + topLayout.addWidget(btnPrev := TTkButton(text="<",maxWidth=3), 0,2) + topLayout.addWidget(btnNext := TTkButton(text=">",maxWidth=3), 0,3) + topLayout.addWidget(btnUp := TTkButton(text="^",maxWidth=3), 0,4) + + # Bottom (File Name, Controls) + bottomLayout = TTkGridLayout() + self.layout().addItem(bottomLayout,2,0) + bottomLayout.addWidget(TTkLabel(text="File name:" ,maxWidth=14), 0,0) + bottomLayout.addWidget(TTkLabel(text="Files of type:" ,maxWidth=14), 1,0) + bottomLayout.addWidget(lookPath := TTkComboBox(), 0,1) + bottomLayout.addWidget(lookPath := TTkComboBox(), 1,1) + bottomLayout.addWidget(btnOpen := TTkButton(text="Open", maxWidth=8), 0,2) + bottomLayout.addWidget(btnCancel := TTkButton(text="Cancel",maxWidth=8), 1,2) + + # Center (FileTree, Bookmarks) + splitter = TTkSplitter(border=True) + self.layout().addWidget(splitter,1,0) + + bookmarks = TTkList(parent=splitter) + + fileTree = TTkTree(parent=splitter) fileTree.setHeaderLabels(["Name", "Size", "Type", "Date Modified"]) fileTree.itemExpanded.connect(TTkFileDialogPicker._updateChildren) fileTree.itemExpanded.connect(TTkFileDialogPicker._folderExpanded) @@ -79,8 +127,19 @@ class TTkFileDialogPicker(TTkWindow): for i in TTkFileDialogPicker._getFileItems(self._path): fileTree.addTopLevelItem(i) - self.layout().addWidget(fileTree,0,0) + @staticmethod + def _getListLook(path): + path = os.path.abspath(path) + ret = [path] + while True: + path, e = os.path.split(path) + if e: + ret.append(path) + if not path or path=='/': + break + return ret + @staticmethod def _getFileItems(path): path = os.path.abspath(path) dir_list = os.listdir(path) @@ -101,14 +160,16 @@ class TTkFileDialogPicker(TTkWindow): description = [ n, info.st_size, ] if os.path.isdir(nodePath): ret.append(_FileTreeWidgetItem( - [ TTkString()+TTkCfg.theme.folderNameColor+n+'/', "", "Dir", time], + [ TTkString()+TTkCfg.theme.folderNameColor+n+'/', "", "Folder", time], path=nodePath, type=_FileTreeWidgetItem.DIR, icon=TTkCfg.theme.folderIconClose, childIndicatorPolicy=TTkK.ShowIndicator)) elif os.path.isfile(nodePath): + _, ext = os.path.splitext(n) + if ext: ext = f"{ext[1:]} " ret.append(_FileTreeWidgetItem( - [ TTkString()+TTkCfg.theme.fileNameColor+n, size, "File", time], + [ TTkString()+TTkCfg.theme.fileNameColor+n, size, f"{ext}File", time], path=nodePath, type=_FileTreeWidgetItem.FILE, icon=TTkCfg.theme.getFileIcon(n), diff --git a/TermTk/TTkWidgets/splitter.py b/TermTk/TTkWidgets/splitter.py index 6c20f0cc..7ab419ea 100644 --- a/TermTk/TTkWidgets/splitter.py +++ b/TermTk/TTkWidgets/splitter.py @@ -45,13 +45,16 @@ class TTkSplitter(TTkFrame): TTkFrame.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkSpacer') self._orientation = kwargs.get('orientation', TTkK.HORIZONTAL) - self.setBorder(False) + self.setBorder(kwargs.get('border' , False)) self.setFocusPolicy(TTkK.ClickFocus) self._splitterInitialized = True def addWidget(self, widget, size=None): TTkFrame.addWidget(self, widget) _,_,w,h = self.geometry() + if self.border(): + w-=2 + h-=2 numW = self.layout().count() if self._orientation == TTkK.HORIZONTAL: @@ -64,6 +67,8 @@ class TTkSplitter(TTkFrame): self._updateGeometries() self._separatorsRef = self._separators self._sizeRef = fullSize + if self.parentWidget(): + self.parentWidget().update(repaint=True, updateLayout=True) def _minMaxSizeBefore(self, index): if self._separatorSelected is None: @@ -92,7 +97,9 @@ class TTkSplitter(TTkFrame): if not self.isVisible(): return _,_,w,h = self.geometry() sep = self._separators - x,y=0,0 + if self.border(): + w-=2 + h-=2 def _processGeometry(index, forward): item = self.layout().itemAt(i) @@ -184,17 +191,21 @@ class TTkSplitter(TTkFrame): self._updateGeometries(resized=True) def paintEvent(self): + off = 1 if self.border() else 0 + TTkFrame.paintEvent(self) w,h = self.size() if self._orientation == TTkK.HORIZONTAL: - for i in self._separators: - self._canvas.drawVLine(pos=(i,0), size=h) + for i in self._separators[:-1]: + self._canvas.drawVLine(pos=(i+off,0), size=h) else: - for i in self._separators: - self._canvas.drawHLine(pos=(0,i), size=w) + for i in self._separators[:-1]: + self._canvas.drawHLine(pos=(0,i+off), size=w) def mousePressEvent(self, evt): self._separatorSelected = None x,y = evt.x, evt.y + if self.border(): + x-=1 ; y-=1 # TTkLog.debug(f"{self._separators} {evt}") for i, val in enumerate(self._separators): if self._orientation == TTkK.HORIZONTAL: @@ -211,10 +222,13 @@ class TTkSplitter(TTkFrame): def mouseDragEvent(self, evt): if self._separatorSelected is not None: + x,y = evt.x, evt.y + if self.border(): + x-=1 ; y-=1 if self._orientation == TTkK.HORIZONTAL: - self._separators[self._separatorSelected] = evt.x + self._separators[self._separatorSelected] = x else: - self._separators[self._separatorSelected] = evt.y + self._separators[self._separatorSelected] = y self._updateGeometries() self.update() return True @@ -224,8 +238,8 @@ class TTkSplitter(TTkFrame): self._separatorSelected = None def minimumHeight(self) -> int: - if not self._splitterInitialized: return 0 - ret = 0 + ret = 2 if self.border() else 0 + if not self._splitterInitialized: return ret if self._orientation == TTkK.VERTICAL: for item in self.layout().children(): ret+=item.minimumHeight()+1 @@ -237,8 +251,8 @@ class TTkSplitter(TTkFrame): return ret def minimumWidth(self) -> int: - if not self._splitterInitialized: return 0 - ret = 0 + ret = 2 if self.border() else 0 + if not self._splitterInitialized: return ret if self._orientation == TTkK.HORIZONTAL: for item in self.layout().children(): ret+=item.minimumWidth()+1 @@ -250,12 +264,13 @@ class TTkSplitter(TTkFrame): return ret def maximumHeight(self) -> int: + b = 2 if self.border() else 0 if not self._splitterInitialized: return 0x10000 if self._orientation == TTkK.VERTICAL: - ret = 0 + ret = b for item in self.layout().children(): ret+=item.maximumHeight()+1 - ret = max(0,ret-1) + ret = max(b,ret-1) else: ret = 0x10000 for item in self.layout().children(): @@ -264,12 +279,13 @@ class TTkSplitter(TTkFrame): return ret def maximumWidth(self) -> int: + b = 2 if self.border() else 0 if not self._splitterInitialized: return 0x10000 if self._orientation == TTkK.HORIZONTAL: - ret = 0 + ret = b for item in self.layout().children(): ret+=item.maximumHeight()+1 - ret = max(0,ret-1) + ret = max(b,ret-1) else: ret = 0x10000 for item in self.layout().children(): diff --git a/TermTk/TTkWidgets/treewidget.py b/TermTk/TTkWidgets/treewidget.py index 31f5124d..dc02104f 100644 --- a/TermTk/TTkWidgets/treewidget.py +++ b/TermTk/TTkWidgets/treewidget.py @@ -35,7 +35,7 @@ from dataclasses import dataclass class TTkTreeWidget(TTkAbstractScrollView): __slots__ = ( '_items', '_header', '_columnsPos', '_cache', '_selectedId', '_selected', '_separatorSelected', '_mouseDelta', - '_headerColor', '_selectedColor', + '_headerColor', '_selectedColor', '_lineColor', # Signals 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' ) @@ -63,8 +63,9 @@ class TTkTreeWidget(TTkAbstractScrollView): self._header = kwargs.get('header',[]) self._columnsPos = [] self._cache = [] - self._headerColor = kwargs.get('headerColor',TTkCfg.theme.treeHeaderColor) - self._selectedColor = kwargs.get('selectedColor',TTkCfg.theme.treeSelectedColor) + self._headerColor = kwargs.get('headerColor', TTkCfg.theme.treeHeaderColor) + self._selectedColor = kwargs.get('selectedColor', TTkCfg.theme.treeSelectedColor) + self._lineColor = kwargs.get('lineColor', TTkCfg.theme.treeLineColor) self.setMinimumHeight(1) self.setFocusPolicy(TTkK.ClickFocus) @@ -191,13 +192,13 @@ class TTkTreeWidget(TTkAbstractScrollView): x += ox ss = self._separatorSelected pos = max((ss+1)*4, x) - self._columnsPos[ss] = pos + diff = pos - self._columnsPos[ss] # Align the previous Separators if pushed for i in range(ss): self._columnsPos[i] = min(self._columnsPos[i], pos-(ss-i)*4) - # Align the next Separators if pushed + # Align all the other Separators relative to the selection for i in range(ss, len(self._columnsPos)): - self._columnsPos[i] = max(self._columnsPos[i], pos+(i-ss)*4) + self._columnsPos[i] += diff self.update() self.viewChanged.emit() return True @@ -250,7 +251,7 @@ class TTkTreeWidget(TTkAbstractScrollView): for sx in self._columnsPos: self._canvas.drawChar(pos=(sx-x,0), char=tt[5], color=self._headerColor) for sy in range(1,h): - self._canvas.drawChar(pos=(sx-x,sy), char=tt[4]) + self._canvas.drawChar(pos=(sx-x,sy), char=tt[4], color=self._lineColor) # Draw cache for i, c in enumerate(self._cache): @@ -261,6 +262,6 @@ class TTkTreeWidget(TTkAbstractScrollView): lx = 0 if il==0 else self._columnsPos[il-1]+1 lx1 = self._columnsPos[il] if item.isSelected(): - self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx, color=self._selectedColor, forceColor=True) + self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx, alignment=item.textAlignment(il), color=self._selectedColor, forceColor=True) else: - self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx) + self._canvas.drawText(pos=(lx-x,i-y+1), text=c.data[il], width=lx1-lx, alignment=item.textAlignment(il)) diff --git a/TermTk/TTkWidgets/treewidgetitem.py b/TermTk/TTkWidgets/treewidgetitem.py index a3e1d399..8b43c5c1 100644 --- a/TermTk/TTkWidgets/treewidgetitem.py +++ b/TermTk/TTkWidgets/treewidgetitem.py @@ -30,7 +30,7 @@ from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel class TTkTreeWidgetItem(TTkAbstractItemModel): - __slots__ = ('_parent', '_data', '_children', '_expanded', '_selected', + __slots__ = ('_parent', '_data', '_alignment', '_children', '_expanded', '_selected', '_childIndicatorPolicy', '_icon', '_defaultIcon' # Signals # 'refreshData' @@ -43,6 +43,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): super().__init__(self, *args, **kwargs) self._children = [] self._data = args[0] if len(args)>0 and type(args[0])==list else None + self._alignment = [TTkK.LEFT_ALIGN]*len(self._data) self._parent = kwargs.get('parent', None) self._childIndicatorPolicy = kwargs.get('childIndicatorPolicy', TTkK.DontShowIndicatorWhenChildless) @@ -104,10 +105,19 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._icon[col] = icon self.dataChanged.emit() - def data(self, column, role=None): - if column >= len(self._data): + def textAlignment(self, col): + if col >= len(self._alignment): + return TTkK.LEFT_ALIGN + return self._alignment[col] + + def setTextAlignment(self, col, alignment): + self._alignment[col] = alignment + self.dataChanged.emit() + + def data(self, col, role=None): + if col >= len(self._data): return '' - return self._data[column] + return self._data[col] @pyTTkSlot() def emitDataChanged(self): diff --git a/demo/showcase/filepicker.py b/demo/showcase/filepicker.py index 978481d2..d2369d8d 100755 --- a/demo/showcase/filepicker.py +++ b/demo/showcase/filepicker.py @@ -35,7 +35,7 @@ def demoFilePicker(root=None): 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)") + filePicker = ttk.TTkFileDialogPicker(pos = (3,3), size=(75,24), caption="Pick a File", path=".", filter="All Files (*);;Python Files (*.py)") ttk.TTkHelper.overlay(frame, filePicker, 2, 1) btn.clicked.connect(_showDialog) diff --git a/demo/showcase/splitter2.py b/demo/showcase/splitter2.py new file mode 100755 index 00000000..c5636bfc --- /dev/null +++ b/demo/showcase/splitter2.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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],'../..')) +import TermTk as ttk + + +def demoSplitter(root=None): + vsplitter = ttk.TTkSplitter(parent=root, border=True, orientation=ttk.TTkK.VERTICAL) + ttk.TTkFrame(parent=vsplitter ,border=True, title="Frame1.1") + hsplitter1 = ttk.TTkSplitter(parent=vsplitter, border=True) + ttk.TTkFrame(parent=vsplitter ,border=True, title="Frame1.2") + hsplitter2 = ttk.TTkSplitter(parent=vsplitter, border=True) + ttk.TTkFrame(parent=vsplitter ,border=True, title="Frame1.3") + ttk.TTkFrame(parent=hsplitter1 ,border=True, title="Frame3") + ttk.TTkTestWidgetSizes(parent=hsplitter1 ,border=True, title="Frame2", minSize=(33,7), maxSize=(33,7)) + ttk.TTkFrame(parent=hsplitter1 ,border=True, title="Frame4") + + ttk.TTkFrame(parent=hsplitter2 ,border=True, title="Frame5") + ttk.TTkTestWidgetSizes(parent=hsplitter2 ,border=True, title="Frame6", minSize=(33,7), maxSize=(33,7)) + ttk.TTkFrame(parent=hsplitter2 ,border=True, title="Frame7") + ttk.TTkTestWidgetSizes(parent=hsplitter2 ,border=True, title="Frame8", minSize=(33,7), maxSize=(33,7)) + ttk.TTkFrame(parent=hsplitter2 ,border=True, title="Frame9") + return vsplitter + + + +def main(): + ttk.TTkLog.use_default_file_logging() + + root = ttk.TTk() + winSplitter = ttk.TTkWindow(parent=root,pos = (10,5), size=(100,40), title="Test Splitter", border=True, layout=ttk.TTkGridLayout()) + demoSplitter(winSplitter) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file