diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index 245f87cd..6117b586 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -99,9 +99,14 @@ class _pyTTkSignal_obj(): # no_receiver_check - suppress the check that the underlying C++ receiver instance still exists and deliver the signal anyway. # Returns: # a Connection object which can be passed to disconnect(). This is the only way to disconnect a connection to a lambda function. - if hasattr(slot, '_TTkslot_attr') and slot._TTkslot_attr != self._types: - error = "Decorated slot has no signature compatible: "+slot.__name__+str(slot._TTkslot_attr)+" != signal"+str(self._types) - raise TypeError(error) + if hasattr(slot, '_TTkslot_attr'): + if len(slot._TTkslot_attr) != len(self._types): + error = "Decorated slot has no signature compatible: "+slot.__name__+str(slot._TTkslot_attr)+" != signal"+str(self._types) + raise TypeError(error) + for a,b in zip(slot._TTkslot_attr, self._types): + if not issubclass(a,b): + error = "Decorated slot has no signature compatible: "+slot.__name__+str(slot._TTkslot_attr)+" != signal"+str(self._types) + raise TypeError(error) if slot not in self._connected_slots: self._connected_slots.append(slot) diff --git a/TermTk/TTkWidgets/TTkModelView/__init__.py b/TermTk/TTkWidgets/TTkModelView/__init__.py index 7c38fe31..b7dafca5 100644 --- a/TermTk/TTkWidgets/TTkModelView/__init__.py +++ b/TermTk/TTkWidgets/TTkModelView/__init__.py @@ -2,4 +2,6 @@ from .tree import TTkTree from .treewidget import TTkTreeWidget from .treewidgetitem import TTkTreeWidgetItem +from .filetree import TTkFileTree +from .filetreewidget import TTkFileTreeWidget from .filetreewidgetitem import TTkFileTreeWidgetItem diff --git a/TermTk/TTkWidgets/TTkModelView/filetree.py b/TermTk/TTkWidgets/TTkModelView/filetree.py new file mode 100644 index 00000000..c3379ba6 --- /dev/null +++ b/TermTk/TTkWidgets/TTkModelView/filetree.py @@ -0,0 +1,52 @@ +#!/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. + +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkWidgets.TTkModelView.tree import TTkTree +from TermTk.TTkWidgets.TTkModelView.filetreewidget import TTkFileTreeWidget + +class TTkFileTree(TTkTree): + __slots__ = ('_fileTreeWidget', + # Forwarded Methods + 'openPath', + # Forwarded Signals + 'fileClicked', 'folderClicked', 'fileDoubleClicked', 'folderDoubleClicked') + + def __init__(self, *args, **kwargs): + wkwargs = kwargs.copy() + if 'parent' in wkwargs: wkwargs.pop('parent') + self._fileTreeWidget = TTkFileTreeWidget(*args, **wkwargs) + + TTkTree.__init__(self, *args, **kwargs, treeWidget=self._fileTreeWidget) + self._name = kwargs.get('name' , 'TTkFileTree' ) + + # Forward Signals + self.fileClicked = self._fileTreeWidget.fileClicked + self.folderClicked = self._fileTreeWidget.folderClicked + self.fileDoubleClicked = self._fileTreeWidget.fileDoubleClicked + self.folderDoubleClicked = self._fileTreeWidget.folderDoubleClicked + + # Forward Methods + self.openPath = self._fileTreeWidget.openPath \ No newline at end of file diff --git a/TermTk/TTkWidgets/TTkModelView/filetreewidget.py b/TermTk/TTkWidgets/TTkModelView/filetreewidget.py new file mode 100644 index 00000000..71a4ad8c --- /dev/null +++ b/TermTk/TTkWidgets/TTkModelView/filetreewidget.py @@ -0,0 +1,140 @@ +#!/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 os +import re +import datetime + +from TermTk.TTkCore.color import TTkColor + +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.TTkModelView.treewidget import TTkTreeWidget +from TermTk.TTkWidgets.TTkModelView.filetreewidgetitem import TTkFileTreeWidgetItem +from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal + +class TTkFileTreeWidget(TTkTreeWidget): + __slots__ = ('_path', '_filter', + # Signals + 'fileClicked', 'folderClicked', 'fileDoubleClicked', 'folderDoubleClicked') + def __init__(self, *args, **kwargs): + # Signals + self.fileClicked = pyTTkSignal(TTkFileTreeWidgetItem) + self.folderClicked = pyTTkSignal(TTkFileTreeWidgetItem) + self.fileDoubleClicked = pyTTkSignal(TTkFileTreeWidgetItem) + self.folderDoubleClicked = pyTTkSignal(TTkFileTreeWidgetItem) + TTkTreeWidget.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'TTkFileTreeWidget' ) + self._path = kwargs.get('path','.') + self._filter = '*' + + def setFilter(self, filter): + self._filter = filter + # TODO: Avoid to refer directly '_rootItem' + TTkFileTreeWidgetItem.setFilter(self._rootItem, filter) + + def openPath(self, path): + self._path = path + + self.clear() + for i in TTkFileTreeWidget._getFileItems(path): + self.addTopLevelItem(i) + self.setFilter(self._filter) + + def _getFileItems(path): + path = os.path.abspath(path) + if not os.path.exists(path): return [] + dir_list = os.listdir(path) + ret = [] + for n in dir_list: + nodePath = os.path.join(path,n) + + def _getStat(_path): + info = os.stat(_path) + time = datetime.datetime.fromtimestamp(info.st_ctime).strftime('%Y-%m-%d %H:%M:%S') + if info.st_size > (1024*1024*1024): + size = f"{info.st_size/(1024*1024*1024):.2f} GB" + if info.st_size > (1024*1024): + size = f"{info.st_size/(1024*1024):.2f} MB" + elif info.st_size > 1024: + size = f"{info.st_size/1024:.2f} KB" + else: + size = f"{info.st_size} bytes" + return time, size, info.st_ctime, info.st_size + + if os.path.isdir(nodePath): + if os.path.exists(nodePath): + time, _, rawTime, _ = _getStat(nodePath) + color = TTkCfg.theme.folderNameColor + else: + time, _, rawTime, _ = "" + color = TTkCfg.theme.failNameColor + + if os.path.islink(nodePath): + name = TTkString()+TTkCfg.theme.linkNameColor+n+'/'+TTkColor.RST+' -> '+TTkCfg.theme.folderNameColor+os.readlink(nodePath) + typef = "Folder Link" + else: + name = TTkString()+color+n+'/' + typef = "Folder" + + ret.append(TTkFileTreeWidgetItem( + [ name, "", typef, time], + raw = [ n , -1 , typef , rawTime ], + path=nodePath, + type=TTkFileTreeWidgetItem.DIR, + icon=TTkString() + TTkCfg.theme.folderIconColor + TTkCfg.theme.fileIcon.folderClose + TTkColor.RST, + childIndicatorPolicy=TTkK.ShowIndicator)) + + elif os.path.isfile(nodePath) or os.path.islink(nodePath): + if os.path.exists(nodePath): + time, size, rawTime, rawSize = _getStat(nodePath) + if os.access(nodePath, os.X_OK): + color = TTkCfg.theme.executableColor + typef="Exec" + else: + color = TTkCfg.theme.fileNameColor + typef="File" + else: + time, size, rawTime, rawSize = "", "", 0, 0 + color = TTkCfg.theme.failNameColor + typef="Broken" + + if os.path.islink(nodePath): + name = TTkString()+TTkCfg.theme.linkNameColor+n+TTkColor.RST+' -> '+color+os.readlink(nodePath) + typef += " Link" + else: + name = TTkString()+color+n + + _, ext = os.path.splitext(n) + if ext: ext = f"{ext[1:]} " + ret.append(TTkFileTreeWidgetItem( + [ name, size, typef, time], + raw = [ n , rawSize , typef , rawTime ], + path=nodePath, + type=TTkFileTreeWidgetItem.FILE, + icon=TTkString() + TTkCfg.theme.fileIconColor + TTkCfg.theme.fileIcon.getIcon(n) + TTkColor.RST, + childIndicatorPolicy=TTkK.DontShowIndicator)) + return ret \ No newline at end of file diff --git a/TermTk/TTkWidgets/TTkModelView/tree.py b/TermTk/TTkWidgets/TTkModelView/tree.py index 3a30b1ba..28f6c1e2 100644 --- a/TermTk/TTkWidgets/TTkModelView/tree.py +++ b/TermTk/TTkWidgets/TTkModelView/tree.py @@ -24,6 +24,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkWidgets.TTkModelView import treewidget from TermTk.TTkWidgets.TTkModelView.treewidget import TTkTreeWidget from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea @@ -39,18 +40,18 @@ class TTkTree(TTkAbstractScrollArea): TTkAbstractScrollArea.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkTree' ) if 'parent' in kwargs: kwargs.pop('parent') - self._treeView = TTkTreeWidget(*args, **kwargs) + self._treeView = kwargs.get('treeWidget',TTkTreeWidget(*args, **kwargs)) + self.setViewport(self._treeView) + self.setFocusPolicy(TTkK.ClickFocus) + # Forward the signal self.itemActivated = self._treeView.itemActivated self.itemChanged = self._treeView.itemChanged self.itemClicked = self._treeView.itemClicked self.itemExpanded = self._treeView.itemExpanded - self.itemCollapsed = self._treeView.itemCollapsed + self.itemCollapsed = self._treeView.itemCollapsed self.itemDoubleClicked = self._treeView.itemDoubleClicked - self.setFocusPolicy(TTkK.ClickFocus) - self.setViewport(self._treeView) - # Forwarded Methods #self.setAlignment = self._treeView.setAlignment #self.setHeader = self._treeView.setHeader @@ -60,6 +61,3 @@ class TTkTree(TTkAbstractScrollArea): #self.appendItem = self._treeView.appendItem self.addTopLevelItem = self._treeView.addTopLevelItem self.clear = self._treeView.clear - - - diff --git a/TermTk/TTkWidgets/TTkPickers/filepicker.py b/TermTk/TTkWidgets/TTkPickers/filepicker.py index a376442f..a375992b 100644 --- a/TermTk/TTkWidgets/TTkPickers/filepicker.py +++ b/TermTk/TTkWidgets/TTkPickers/filepicker.py @@ -36,14 +36,12 @@ from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.lineedit import TTkLineEdit from TermTk.TTkWidgets.window import TTkWindow 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.TTkWidgets.TTkModelView.tree import TTkTree -from TermTk.TTkWidgets.TTkModelView.treewidgetitem import TTkTreeWidgetItem +from TermTk.TTkWidgets.TTkModelView.filetree import TTkFileTree from TermTk.TTkWidgets.TTkModelView.filetreewidgetitem import TTkFileTreeWidgetItem @@ -148,7 +146,7 @@ class TTkFileDialogPicker(TTkWindow): # Home Folder (Win Compatible): # os.path.expanduser("~") - self._fileTree = TTkTree(parent=splitter) + self._fileTree = TTkFileTree(parent=splitter) splitter.setSizes([10,self.width()-13]) self._fileTree.setHeaderLabels(["Name", "Size", "Type", "Date Modified"]) self._fileTree.itemExpanded.connect(self._updateChildren) @@ -163,10 +161,8 @@ class TTkFileDialogPicker(TTkWindow): @pyTTkSlot(str) def _fileTypeChanged(self, type): - # TODO: Fix This Crap, _rootItem should not be addressed directly - # but I am just too tired now to find a proper way self._filter = re.match(".*\((.*)\)",type).group(1) - TTkFileTreeWidgetItem.setFilter(self._fileTree._treeView._rootItem, self._filter) + self._fileTree.setFilter(self._filter) @pyTTkSlot(str) def _checkFileName(self, fileName): @@ -182,12 +178,12 @@ class TTkFileDialogPicker(TTkWindow): self.filePicked.emit(fileName) self.close() - @pyTTkSlot(TTkTreeWidgetItem, int) + @pyTTkSlot(TTkFileTreeWidgetItem, int) def _selectedItem(self, item, _): if item.getType() != item.FILE: return self._fileName.setText(item.path()) - @pyTTkSlot(TTkTreeWidgetItem, int) + @pyTTkSlot(TTkFileTreeWidgetItem, int) def _activatedItem(self, item, _): path = item.path() if os.path.isdir(path): @@ -229,15 +225,12 @@ class TTkFileDialogPicker(TTkWindow): if self._recentPathId: self._btnPrev.setEnabled() self._btnNext.setDisabled() - self._fileTree.clear() - for i in TTkFileDialogPicker._getFileItems(path): - self._fileTree.addTopLevelItem(i) + self._fileTree.openPath(path) self._lookPath.currentTextChanged.disconnect(self._openNewPath) self._lookPath.clear() self._lookPath.addItems(TTkFileDialogPicker._getListLook(self._path)) self._lookPath.setCurrentIndex(0) self._lookPath.currentTextChanged.connect(self._openNewPath) - TTkFileTreeWidgetItem.setFilter(self._fileTree._treeView._rootItem, self._filter) @staticmethod def _getListLook(path):