Skip to content

Commit

Permalink
enh: display the actual directory tree instead of a table
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmueller committed Nov 30, 2023
1 parent 1a926ca commit 6c86837
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 414 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
0.6.0
- feat: generalize GUI to use all recipes
- enh: display the actual directory tree instead of a table
- enh: compute file hash while copying, avoiding reading data twice
- enh: handle PermissionError when building directory tree
- enh: identify existing target paths based on size quicker
- enh: correct progress display and remove unused code
- enh: display object count and size in tree views
- ref: migrate from pkg_resources to importlib.resources
- ref: unify input and output tree widget with one base class
- ref: remove the path_tree submodule
0.5.2
- build: add mpldc.exe CLI to Windows binary release
- setup: bump dclab to 0.55.6
Expand Down
7 changes: 3 additions & 4 deletions mpl_data_cast/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def on_action_preferences(self):
"main/tree_depth_limit", 8))
self.widget_input.tree_depth_limit = int(self.settings.value(
"main/tree_depth_limit", 8))
self.widget_output.update_tree_dir(self.settings.value(
"main/output_path", pathlib.Path.home()))
self.widget_output.path = self.settings.value("main/output_path",
pathlib.Path.home())

@QtCore.pyqtSlot()
def on_action_quit(self) -> None:
Expand Down Expand Up @@ -154,7 +154,6 @@ def on_task_transfer(self) -> None:
self.progressBar.setValue(0)
QtWidgets.QApplication.processEvents(
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
self.widget_output.update_tree()
else:
msg = "Some problems occured during data transfer:\n"
for path, _ in result["errors"]:
Expand All @@ -171,7 +170,7 @@ def on_task_transfer(self) -> None:
def on_recipe_changed(self):
# Update the recipe description
rec_cls = self.current_recipe
doc = rec_cls.__doc__.split("\n")[0]
doc = rec_cls.__doc__.strip().split("\n")[0]
self.label_recipe_descr.setText(f"*{doc}*")
self.widget_input.recipe = rec_cls
self.widget_output.recipe = rec_cls
Expand Down
4 changes: 4 additions & 0 deletions mpl_data_cast/gui/widget_input.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pathlib

from .widget_tree import TreeWidget


Expand All @@ -6,3 +8,5 @@ class InputWidget(TreeWidget):
Contains a lineEdit, a button, and a treeview widget."""
def __init__(self, *args, **kwargs):
super(InputWidget, self).__init__(which="input", *args, **kwargs)

self.path = pathlib.Path.cwd()
4 changes: 2 additions & 2 deletions mpl_data_cast/gui/widget_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ class OutputWidget(TreeWidget):
def __init__(self, *args, **kwargs):
super(OutputWidget, self).__init__(which="output", *args, **kwargs)

self.update_tree_dir(
str(self.settings.value("main/output_path", pathlib.Path.home())))
self.path = self.settings.value("main/output_path",
pathlib.Path.home())
123 changes: 50 additions & 73 deletions mpl_data_cast/gui/widget_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import threading
from typing import Literal

from PyQt6 import QtWidgets, QtCore, uic
from PyQt6 import QtWidgets, QtCore, QtGui, uic

from ..path_tree import PathTree, list_items_in_tree
from ..recipe import map_recipe_name_to_class
from ..util import is_dir_writable

Expand All @@ -22,6 +21,7 @@ def __init__(self, *args, **kwargs):
self.size_objects = 0
self.must_break = False
self.is_counting = False
self.has_counted = False

def run(self):
recipe = self.recipe
Expand All @@ -41,14 +41,16 @@ def run(self):
# reset
self.num_objects = 0
self.size_objects = 0
self.has_counted = False
recipe = self.recipe
path = self.path
elif self.num_objects:
elif self.num_objects or self.has_counted:
# already counted
pass
else:
# start crawling the directory tree
self.is_counting = True
self.has_counted = False
try:
rcp = recipe(path, path)
except BaseException:
Expand Down Expand Up @@ -76,6 +78,7 @@ def run(self):
except BaseException:
pass
self.is_counting = False
self.has_counted = True
time.sleep(0.5)


Expand Down Expand Up @@ -105,6 +108,11 @@ def __init__(self,
self.tree_counter = TreeObjectCounter()
self.tree_counter.start()

# tree view model
self.model = QtGui.QFileSystemModel()
self.model.setReadOnly(True)
self.treeView.setModel(self.model)

# UI update function
self.tree_label_timer = QtCore.QTimer(self)
self.tree_label_timer.timeout.connect(self.on_update_object_count)
Expand All @@ -115,21 +123,33 @@ def __init__(self,
self.settings = QtCore.QSettings()
self.tree_depth_limit = int(self.settings.value(
"main/tree_depth_limit", 3))
self.update_tree_dir(str(pathlib.Path.home()))

self.pushButton_dir.clicked.connect(
self.on_task_select_tree_dir)
self.on_tree_browse_button)
self.lineEdit_dir.editingFinished.connect(
self.update_tree_dir_from_lineedit)
self.on_tree_edit_line)

@property
def path(self):
return self._path

@path.setter
def path(self, path):
self._path = path
self.tree_counter.path = path
path = pathlib.Path(path)
if not self.readonly and not is_dir_writable(path):
msg_txt = f"The {self.which} directory '{path}' is not " \
f"writable. Please select a different directory."
msg = QtWidgets.QMessageBox(self)
msg.setIcon(QtWidgets.QMessageBox.Icon.Warning)
msg.setText(msg_txt)
msg.setWindowTitle(f"{self.which.capitalize()} directory invalid")
msg.exec()
else:
self._path = path
self.tree_counter.path = path
self.model.setRootPath(str(path))
self.treeView.setRootIndex(self.model.index(str(path)))
self.lineEdit_dir.setText(str(path))

@property
def recipe(self):
Expand All @@ -141,26 +161,6 @@ def recipe(self, recipe):
self._recipe = recipe
self.tree_counter.recipe = recipe

@QtCore.pyqtSlot()
def on_task_select_tree_dir(self) -> None:
p = QtWidgets.QFileDialog.getExistingDirectory(
parent=self,
caption=f"Select {self.which} directory:",
directory=str(self.path) if self.path else None)
if p:
self.update_tree_dir(p)

@QtCore.pyqtSlot()
def on_update_object_count(self):
objects = self.tree_counter.num_objects
size = self.tree_counter.size_objects
size_str = human_size(size)
if self.tree_counter.is_counting:
label = f"counting {objects} objects ({size_str})"
else:
label = f"{objects} objects ({size_str})"
self.label_objects.setText(label)

@QtCore.pyqtSlot(object)
def dragEnterEvent(self, e) -> None:
"""Whether files are accepted"""
Expand All @@ -179,58 +179,35 @@ def dropEvent(self, e) -> None:
path_tree = pp
else:
path_tree = pp.parent
self.update_tree_dir(path_tree)

@QtCore.pyqtSlot()
def update_tree_dir_from_lineedit(self) -> None:
"""Executed when the tree path was manually edited by the user."""
tree_dir = self.lineEdit_dir.text()
if tree_dir:
self.update_tree_dir(tree_dir)
self.path = path_tree

@QtCore.pyqtSlot()
def update_object_count(self) -> None:
"""Update `self.label_objects` with the counted events"""
def on_tree_browse_button(self) -> None:
p = QtWidgets.QFileDialog.getExistingDirectory(
parent=self,
caption=f"Select {self.which} directory:",
directory=str(self.path) if self.path else None)
if p:
self.path = p

@QtCore.pyqtSlot()
def update_tree_dir(self, tree_dir: str | pathlib.Path) -> None:
"""Checks if the tree directory as given by the user exists and
updates the lineEdit widget accordingly.
Parameter
---------
tree_dir: str or pathlib.Path
The directory for the tree.
"""
if not self.readonly and not is_dir_writable(tree_dir):
msg_txt = f"The {self.which} directory '{tree_dir}' is not " \
f"writable. Please select a different directory."
msg = QtWidgets.QMessageBox(self)
msg.setIcon(QtWidgets.QMessageBox.Icon.Warning)
msg.setText(msg_txt)
msg.setWindowTitle(f"{self.which.capitalize()} directory invalid")
msg.exec()
else:
tree_dir = pathlib.Path(tree_dir)
def on_tree_edit_line(self) -> None:
"""User edited the lineEdit manually"""
tree_dir = pathlib.Path(self.lineEdit_dir.text())
if tree_dir.is_dir():
self.path = tree_dir
self.lineEdit_dir.setText(str(tree_dir))
self.update_tree()

@QtCore.pyqtSlot()
def update_tree(self) -> None:
"""Update the `PathTree` object based on the current root path in
`self.path` and update the GUI to show the new tree."""
self.p_tree = PathTree(self.path, self.tree_depth_limit)
self.treeWidget.clear()
self.treeWidget.setColumnCount(self.p_tree.tree_depth + 1)

list_items_in_tree(self.p_tree,
self.treeWidget,
h_level=0,
depth_limit=self.tree_depth_limit)

QtWidgets.QApplication.processEvents(
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
def on_update_object_count(self):
"""`self.tree_label_timer` calls this regularly"""
objects = self.tree_counter.num_objects
size = self.tree_counter.size_objects
size_str = human_size(size)
if self.tree_counter.is_counting:
label = f"counting {objects} objects ({size_str})"
else:
label = f"{objects} objects ({size_str})"
self.label_objects.setText(label)


def human_size(bt, units=None):
Expand Down
26 changes: 6 additions & 20 deletions mpl_data_cast/gui/widget_tree.ui
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</layout>
</item>
<item>
<widget class="QTreeWidget" name="treeWidget">
<widget class="QTreeView" name="treeView">
<property name="acceptDrops">
<bool>true</bool>
</property>
Expand Down Expand Up @@ -94,32 +94,18 @@
<property name="textElideMode">
<enum>Qt::ElideRight</enum>
</property>
<property name="indentation">
<number>5</number>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="columnCount">
<number>1</number>
</property>
<property name="topLevelItemCount" stdset="0">
<number>0</number>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<property name="animated">
<bool>true</bool>
</attribute>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
</property>
<attribute name="headerDefaultSectionSize">
<number>125</number>
<number>200</number>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
Expand Down
2 changes: 1 addition & 1 deletion mpl_data_cast/mod_recipes/rcp_rtdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class RTDCRecipe(Recipe):
__doc__ = f"""
Compress DC data and include .ini files (dclab {dclab.__version__})
Compress raw DC data and include .ini files (dclab {dclab.__version__})
"""

def convert_dataset(self, path_list, temp_path, **kwargs):
Expand Down
Loading

0 comments on commit 6c86837

Please sign in to comment.