Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions gui.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


a = Analysis(
['pythonQEPest/gui/gui.py'],
['pythonQEPest\\gui\\gui.py'],
pathex=[],
binaries=[],
datas=[],
Expand All @@ -22,11 +22,11 @@ exe = EXE(
a.binaries,
a.datas,
[],
name='pythonQEPest-GUI',
name='gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
Expand Down
2 changes: 1 addition & 1 deletion main.spec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ a = Analysis(
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
excludes=['rdkit'],
noarchive=False,
optimize=0,
)
Expand Down
1,018 changes: 495 additions & 523 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "Java QEPest but written in Python"
readme = "README.md"
requires-python = ">=3.12,<3.15"
authors = [{ name = "Lina", email = "knocker767@gmail.com" }]
dependencies = ["pydantic==2.12.5", "python-dotenv>=1.2.1,<2.0.0", "ruff (>=0.15.18,<0.16.0)"]
dependencies = ["pydantic==2.12.5", "python-dotenv>=1.2.1,<2.0.0", "ruff (>=0.15.13,<0.16.0)", "ttkbootstrap>=1.20.4,<2.0.0"]

[project.optional-dependencies]
gui = ["pyperclip>=1.11.0,<2.0.0"]
Expand All @@ -22,10 +22,10 @@ packages = [{ include = "pythonQEPest" }]

[dependency-groups]
dev = [
"pytest (>=9.1.1)",
"pytest (>=9.0.3)",
"pre-commit (>=4.6.0,<5.0.0)",
"pyinstaller (>=6.21.0,<7.0.0)",
"poethepoet[poetry_plugin] (>=0.47.0)",
"pyinstaller (>=6.18.0,<7.0.0)",
"poethepoet[poetry_plugin] (>=0.46.0)",
"pytest-cov (>=7.0.0,<8.0.0)",
"twine (>=6.2.0,<7.0.0)",
]
Expand All @@ -50,3 +50,5 @@ test = "pytest"
build-simple = "pyinstaller --onefile pythonQEPest/main.py"
build-gui = "pyinstaller --onefile --noconsole pythonQEPest/gui/gui.py"
publish-pypi = "twine upload --repository pythonQEPest dist/*"
ruff_lint = "ruff check --fix"
ruff_format = "ruff format"
75 changes: 57 additions & 18 deletions pythonQEPest/gui/actions/actions_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,50 @@
except ImportError:
pyperclip = None

from pythonQEPest.gui.elements.EditWindow import EditWindow
from pythonQEPest.gui.utility.DataManager import DataManager


class GUIActionsCRUD:
def __init__(self, data_tree, result_tree, save_button, index):
self.index = index
def __init__(self, data_tree, result_tree, save_button, entry_panel):
self.data_manager = DataManager()
self.data_tree: ttk.Treeview = data_tree
self.result_tree: ttk.Treeview = result_tree
self.save_button = save_button
self.entry_panel = entry_panel

def sync_selection_to_panel(self, *args, **kwargs):
selected = self.data_tree.selection()
if selected:
item_id = selected[0]
values = self.data_tree.item(item_id, "values")
self.entry_panel.load_entry(values, item_id)

def edit_selected(self, *args, **kwargs):
selected = self.data_tree.selection()
if not selected:
messagebox.showwarning("Select Entry", "Select Entry for editing")
return

item_id = selected[0]
EditWindow(item_id=item_id, tree=self.data_tree, child_tree=self.result_tree)
self.sync_selection_to_panel()

def undo(self, *args, **kwargs):
if self.data_manager.undo() is None:
return
self._rebuild_from_data_manager()

def _rebuild_from_data_manager(self):
self.data_tree.delete(*self.data_tree.get_children())
self.result_tree.delete(*self.result_tree.get_children())

for row in self.data_manager.file_data:
self.data_tree.insert("", "end", values=tuple(str(v) for v in row))
for row in self.data_manager.result_data:
self.result_tree.insert("", "end", values=tuple(str(v) for v in row))

self.entry_panel.cancel()
self.save_button.config(
state="normal" if self.data_manager.result_data else "disabled"
)

def copy_selected(self, *args, **kwargs):
selected_data_tree = self.data_tree.selection()
Expand Down Expand Up @@ -83,14 +107,18 @@ def paste_entries(self, *args, **kwargs):
lines = clipboard_text.strip().splitlines()
count_added = 0

self.data_manager.push_undo()
for line in lines:
parts = line.strip().split("\t")
if len(parts) == 7:
idx = self.index
try:
float(parts[1])
except ValueError:
continue
idx = self.data_manager.next_file_id
self.data_manager.add_file((idx, *parts))
self.data_tree.insert("", "end", values=(idx, *parts))
count_added += 1
self.index += 1

if count_added:
messagebox.showinfo(
Expand All @@ -99,33 +127,44 @@ def paste_entries(self, *args, **kwargs):
)

def delete_selected(self, *args, **kwargs):
selected = self.data_tree.selection()
if not selected:
selected_data = self.data_tree.selection()
selected_result = self.result_tree.selection()

if not selected_data and not selected_result:
messagebox.showwarning("Select Entry", "Select the entry to delete.")
return

idxs_to_remove = []
for item in selected:
values = self.data_tree.item(item, "values")
idx_to_remove = int(values[0])
idxs_to_remove.append(idx_to_remove)

self.data_manager.push_undo()
for item in selected_data:
values = self.data_tree.item(item, "values")
idxs_to_remove.append(int(values[0]))
self.data_tree.delete(item)

if self.result_tree.exists(item):
self.result_tree.delete(item)
for item in selected_result:
values = self.result_tree.item(item, "values")
idxs_to_remove.append(int(values[0]))
self.result_tree.delete(item)

for child in self.result_tree.get_children(""):
if int(self.result_tree.set(child, "ID")) in idxs_to_remove:
self.result_tree.delete(child)

for child in self.data_tree.get_children(""):
if int(self.data_tree.set(child, "ID")) in idxs_to_remove:
self.data_tree.delete(child)

self.data_manager.remove_result_by_ids(idxs_to_remove)
self.data_manager.remove_file_by_ids(idxs_to_remove)

messagebox.showinfo("Deleted", "The selected records have been deleted.")

if not self.data_manager:
self.save_button.config(state="disabled")

def clear_everything(self, *args, **kwargs):
self.data_manager.push_undo()
self.data_manager.clear_file()
self.data_manager.clear_result()

self.data_tree.delete(*self.data_tree.get_children())
self.result_tree.delete(*self.result_tree.get_children())
self.save_button.config(state="disabled")
91 changes: 51 additions & 40 deletions pythonQEPest/gui/actions/actions_other.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import tkinter as tk
from tkinter import filedialog, messagebox

from pythonQEPest.dto.QEPestInput import QEPestInput
from pythonQEPest.gui.utility.DataManager import DataManager


class GUIActionsOther:
def __init__(self, root, data_tree, result_tree, save_button, qepest):
def __init__(self, root, data_tree, result_tree, save_button, qepest, entry_panel):
self.data_manager = DataManager()
self.root = root
self.data_tree = data_tree
self.result_tree = result_tree
self.save_button = save_button
self.qepest = qepest
self.entry_panel = entry_panel

def load_file(self):
def load_file(self, *args):
file_path = filedialog.askopenfilename(
filetypes=[("Text files", "*.txt"), ("CSV files", "*.csv")]
)
Expand All @@ -23,12 +23,18 @@ def load_file(self):

try:
with open(file_path, encoding="utf-8") as file:
self.data_manager.push_undo()
self.data_manager.clear_file()
self.data_tree.delete(*self.data_tree.get_children())

for idx, line in enumerate(file):
for line in file:
parts = line.strip().split("\t")
if len(parts) == 7:
try:
float(parts[1])
except ValueError:
continue
idx = self.data_manager.next_file_id
self.data_manager.add_file((idx, *parts))
self.data_tree.insert("", "end", values=(idx, *parts))

Expand All @@ -39,53 +45,58 @@ def load_file(self):
messagebox.showerror("Loading error", str(e))

def add_entry(self):
form = tk.Toplevel(self.root)
form.title("Add entry")
form.transient(self.root)
form.grab_set()

entries = {}
fields = ["Name", "MW", "LogP", "HBA", "HBD", "RB", "arR"]

for idx, field in enumerate(fields):
tk.Label(form, text=field).grid(
row=idx, column=0, padx=5, pady=5, sticky="e"
)
entry = tk.Entry(form)
entry.grid(row=idx, column=1, padx=5, pady=5)
entries[field] = entry

def submit():
values = []
for field in fields:
val = entries[field].get().strip()
if not val:
messagebox.showwarning("Error", f"The field {field} not filled.")
return
values.append(val)

idx = len(self.data_manager.file_data)
self.data_manager.add_file((idx, *values))
self.data_tree.insert("", "end", values=(idx, *values))
form.destroy()
next_id = self.data_manager.next_file_id
self.entry_panel.clear_for_add(next_id)

def save_from_panel(self, mode, item_id, values):
self.data_manager.push_undo()
if mode == "add":
self.data_manager.add_file(tuple(values))
self.data_tree.insert("", "end", values=tuple(str(v) for v in values))
next_id = self.data_manager.next_file_id
self.entry_panel.reset_after_save(next_id)
messagebox.showinfo(
"Added",
"The entry has been added. Process the data to update the result.",
)
elif mode == "edit" and item_id:
str_values = [str(v) for v in values]
self.data_tree.item(item_id, values=tuple(str_values))

tk.Button(form, text="Add", command=submit).grid(
row=len(fields), column=0, columnspan=2, pady=10
)
old_id = int(self.data_tree.set(item_id, self.data_tree["columns"][0]))

if self.result_tree.exists(item_id):
child_values = list(self.result_tree.item(item_id, "values"))
child_values[0] = str(values[0])
child_values[1] = str(values[1])
self.result_tree.item(item_id, values=tuple(child_values))

form.wait_window()
res_matches = [
x for x in self.data_manager.result_data if x[0] == old_id
]
if res_matches:
ridx = self.data_manager.result_data.index(res_matches[0])
updated = list(self.data_manager.result_data[ridx])
updated[0] = int(values[0])
updated[1] = str(values[1])
self.data_manager.update_result(
index=ridx, new_entry=tuple(updated)
)

matches = [x for x in self.data_manager.file_data if x[0] == old_id]
if matches:
idx = self.data_manager.file_data.index(matches[0])
self.data_manager.update_file(index=idx, new_entry=values)
messagebox.showinfo("Saved", "The entry has been updated.")

def process_data(self):
if not self.data_manager.file_data:
messagebox.showwarning("No data", "Please add or upload data first.")
return

# self.result_data = []
# self.result_tree.delete(*self.result_tree.get_children())
self.data_manager.push_undo()
self.data_manager.clear_result()
self.result_tree.delete(*self.result_tree.get_children())

for row in self.data_manager.file_data:
try:
Expand All @@ -103,7 +114,7 @@ def process_data(self):

self.save_button.config(state="normal")

def save_result(self):
def save_result(self, *args):
if not self.data_manager.result_data:
messagebox.showwarning("No result", "Process the data first.")
return
Expand Down
Loading