Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
67f44b6
build(deps): bump actions/cache from 4 to 5
dependabot[bot] May 24, 2026
2843bda
build(deps): bump ruff from 0.15.13 to 0.15.14 in the linters group
dependabot[bot] May 24, 2026
e7285ec
Merge pull request #47 from PonyLianna/dependabot/github_actions/acti…
PonyLianna May 25, 2026
5aa246c
Merge pull request #48 from PonyLianna/dependabot/pip/linters-c9834cf0b8
PonyLianna May 25, 2026
3736139
Update README.md
PonyLianna May 25, 2026
9cd3f84
Update README.md
PonyLianna May 25, 2026
666145e
fix[GUI]: Bugs
PonyLianna May 31, 2026
01a57bd
build(deps): bump ruff from 0.15.14 to 0.15.15 in the linters group
dependabot[bot] May 31, 2026
410115a
Merge pull request #49 from PonyLianna/dependabot/pip/linters-a9862e5187
PonyLianna Jun 14, 2026
96de666
build(deps-dev): bump pyinstaller from 6.20.0 to 6.21.0
dependabot[bot] Jun 14, 2026
dd7c502
Merge pull request #51 from PonyLianna/dependabot/pip/pyinstaller-6.21.0
PonyLianna Jun 14, 2026
4170017
build(deps-dev): bump pytest in the testing group across 1 directory
dependabot[bot] Jun 14, 2026
28d0b3e
Merge pull request #50 from PonyLianna/dependabot/pip/testing-19f1f3e67a
PonyLianna Jun 14, 2026
b595c79
build(deps): bump actions/checkout from 6 to 7
dependabot[bot] Jun 21, 2026
5d1db47
build(deps-dev): bump pytest from 9.1.0 to 9.1.1 in the testing group
dependabot[bot] Jun 21, 2026
6991bfc
Merge pull request #52 from PonyLianna/dependabot/github_actions/acti…
PonyLianna Jun 21, 2026
c3bc65f
Merge pull request #53 from PonyLianna/dependabot/pip/testing-89e4b3a62f
PonyLianna Jun 21, 2026
9181943
build(deps): bump ruff in the linters group across 1 directory
dependabot[bot] Jun 21, 2026
bff0111
Merge pull request #54 from PonyLianna/dependabot/pip/linters-289bffc70a
PonyLianna Jun 21, 2026
b72b8c0
feat[poetry]: dependencies update
PonyLianna Jun 27, 2026
4b7efa9
feat[GUI]: ttkbootstrap
PonyLianna Jun 27, 2026
e799745
fix[GUI]: ttkbootstrap
PonyLianna Jun 28, 2026
d6bb406
build(deps): bump actions/cache from 5 to 6
dependabot[bot] Jun 28, 2026
61f1d4c
build(deps-dev): bump poethepoet from 0.46.0 to 0.47.0
dependabot[bot] Jun 28, 2026
6dfbd6d
feat[GUI]: tests
PonyLianna Jun 28, 2026
8919057
feat[GUI]: tests #2
PonyLianna Jun 28, 2026
5a1ae5f
Merge pull request #55 from PonyLianna/dependabot/github_actions/acti…
PonyLianna Jun 28, 2026
cf91746
Merge pull request #57 from PonyLianna/dependabot/pip/poethepoet-0.47.0
PonyLianna Jun 28, 2026
d09168d
fix[cd]: whatever
PonyLianna May 19, 2026
55f5faa
fix[cd]: whatever #2
PonyLianna May 19, 2026
2d17ef5
fix[cd]: whatever #3
PonyLianna May 19, 2026
4ee33be
fix[cd]: whatever #4
PonyLianna May 19, 2026
bd3382f
fix[cd]: whatever #5
PonyLianna May 19, 2026
0487e99
fix[GUI]: Bugs
PonyLianna May 31, 2026
d16b733
feat[poetry]: dependencies update
PonyLianna Jun 27, 2026
3fdaf28
feat[GUI]: ttkbootstrap
PonyLianna Jun 27, 2026
8597f96
fix[GUI]: ttkbootstrap
PonyLianna Jun 28, 2026
1fac09f
feat[GUI]: tests
PonyLianna Jun 28, 2026
12936d8
feat[GUI]: tests #2
PonyLianna Jun 28, 2026
13c8314
Merge remote-tracking branch 'codeberg/experimental' into experimental
PonyLianna Jun 28, 2026
5644cd8
Merge branch 'refs/heads/development' into experimental
PonyLianna Jun 28, 2026
e3a1d34
feat[dependencies]: poetry
PonyLianna Jun 28, 2026
f9866e0
fix[GUI]: __init__.py
PonyLianna Jun 28, 2026
bdd79ec
fix[GUI]: test_gui_smoke.py
PonyLianna Jun 28, 2026
f729abd
Add missing EntryPanel module for GUI elements
PonyLianna Jun 28, 2026
7ae7e82
Merge pull request #58 from PonyLianna/experimental
PonyLianna Jun 28, 2026
38f7db4
feat[dependencies]: poetry.lock
PonyLianna Jun 28, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
python-version: [ "3.12", "3.13", "3.14" ]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
release-build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.13"
Expand All @@ -23,7 +23,7 @@ jobs:
virtualenvs-in-project: true
add-to-path: true
- name: Cache Poetry virtual environment
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: .venv
key: venv-release-build-${{ runner.os }}-3.13-${{ hashFiles('poetry.lock') }}
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
os: [ windows-2022 ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.13"
Expand All @@ -82,7 +82,7 @@ jobs:
shell: pwsh
run: echo "$env:USERPROFILE\.local\bin" >> $env:GITHUB_PATH
- name: Cache Poetry virtual environment
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: .venv
key: venv-pyinstaller-${{ runner.os }}-3.13-${{ hashFiles('poetry.lock') }}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Download ready-to-use executables from [Releases](https://github.com/PonyLianna/

| Platform | Download |
|----------|----------|
| Windows CLI | [main.exe](https://github.com/PonyLianna/pythonQEPest/releases/download/v2.0.0-alpha/main.exe) |
| Windows GUI | [gui.exe](https://github.com/PonyLianna/pythonQEPest/releases/download/v2.0.0-alpha/gui.exe) |
| Windows CLI | [main.exe](https://github.com/PonyLianna/pythonQEPest/releases/download/v2.0.0-alpha.5/pythonQEPest-2.0.0a5-win64.exe) |
| Windows GUI | [gui.exe](https://github.com/PonyLianna/pythonQEPest/releases/download/v2.0.0-alpha.5/pythonQEPest-GUI-2.0.0a5-win64.exe) |


## Installation
Expand Down
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,061 changes: 516 additions & 545 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion 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.13,<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 Down Expand Up @@ -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
Loading