pythonQEPest 2.0.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonQEPest/.env.example +4 -0
- pythonQEPest/__init__.py +11 -0
- pythonQEPest/cli/__init__.py +7 -0
- pythonQEPest/cli/cli.py +101 -0
- pythonQEPest/config/__init__.py +11 -0
- pythonQEPest/config/config_provider.py +9 -0
- pythonQEPest/config/normalise.py +12 -0
- pythonQEPest/config/qepest_config.py +31 -0
- pythonQEPest/config/qepest_default.py +26 -0
- pythonQEPest/core/__init__.py +7 -0
- pythonQEPest/core/qepest.py +98 -0
- pythonQEPest/core/qepest_meta.py +28 -0
- pythonQEPest/data.txt +2 -0
- pythonQEPest/data.txt.out +2 -0
- pythonQEPest/dto/QEPestData.py +21 -0
- pythonQEPest/dto/QEPestFile.py +34 -0
- pythonQEPest/dto/QEPestInput.py +60 -0
- pythonQEPest/dto/QEPestOutput.py +13 -0
- pythonQEPest/dto/__init__.py +27 -0
- pythonQEPest/dto/coefficients/QEPestCoefficient.py +57 -0
- pythonQEPest/dto/coefficients/QEPestCoefficientList.py +29 -0
- pythonQEPest/dto/coefficients/QEPestCoefficientNumerics.py +44 -0
- pythonQEPest/dto/coefficients/__init__.py +7 -0
- pythonQEPest/dto/normalisation/Normaliser.py +16 -0
- pythonQEPest/dto/normalisation/__init__.py +3 -0
- pythonQEPest/dto/pest_type/PestTypeCoefficient.py +30 -0
- pythonQEPest/dto/pest_type/PestTypeConfig.py +9 -0
- pythonQEPest/dto/pest_type/__init__.py +4 -0
- pythonQEPest/gui/__init__.py +7 -0
- pythonQEPest/gui/actions/__init__.py +9 -0
- pythonQEPest/gui/actions/action_clicks.py +42 -0
- pythonQEPest/gui/actions/actions_crud.py +131 -0
- pythonQEPest/gui/actions/actions_other.py +126 -0
- pythonQEPest/gui/elements/ButtonsFrame.py +50 -0
- pythonQEPest/gui/elements/DataTree.py +23 -0
- pythonQEPest/gui/elements/EditWindow.py +63 -0
- pythonQEPest/gui/elements/Menu.py +12 -0
- pythonQEPest/gui/elements/ResultTree.py +22 -0
- pythonQEPest/gui/elements/SaveButton.py +10 -0
- pythonQEPest/gui/elements/__init__.py +17 -0
- pythonQEPest/gui/gui.py +87 -0
- pythonQEPest/gui/gui_meta.py +17 -0
- pythonQEPest/gui/utility/DataManager.py +76 -0
- pythonQEPest/gui/utility/DataManagerMeta.py +8 -0
- pythonQEPest/gui/utility/__init__.py +6 -0
- pythonQEPest/helpers/__init__.py +17 -0
- pythonQEPest/helpers/check_nan.py +8 -0
- pythonQEPest/helpers/compute_df.py +5 -0
- pythonQEPest/helpers/get_num_of_cols.py +2 -0
- pythonQEPest/helpers/get_values_from_line.py +5 -0
- pythonQEPest/helpers/norm.py +33 -0
- pythonQEPest/helpers/round_to_4digs.py +2 -0
- pythonQEPest/logger.py +58 -0
- pythonQEPest/main.py +4 -0
- pythonQEPest/providers/__init__.py +6 -0
- pythonQEPest/providers/default_provider.py +23 -0
- pythonQEPest/providers/json_provider.py +30 -0
- pythonQEPest/providers/txt_provider.py +46 -0
- pythonQEPest/services/QEPestFileService.py +125 -0
- pythonQEPest/services/__init__.py +3 -0
- pythonqepest-2.0.0a2.dist-info/METADATA +217 -0
- pythonqepest-2.0.0a2.dist-info/RECORD +64 -0
- pythonqepest-2.0.0a2.dist-info/WHEEL +4 -0
- pythonqepest-2.0.0a2.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Normaliser:
|
|
5
|
+
def __init__(self, arr: Union[tuple, list]):
|
|
6
|
+
# if len(arr) != 6:
|
|
7
|
+
# raise ValueError(f"Array must be exact length of 6. Given: {len(arr)}")
|
|
8
|
+
|
|
9
|
+
self.arr = arr
|
|
10
|
+
|
|
11
|
+
def norm(self, d: Union[int, float], descr: int) -> float:
|
|
12
|
+
if descr > len(self.arr):
|
|
13
|
+
raise KeyError(f"Out of array. {descr} > {len(self.arr)}")
|
|
14
|
+
|
|
15
|
+
max_val = self.arr[int(descr)]
|
|
16
|
+
return d / max_val if max_val != 0 else 0.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, model_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PestTypeCoefficient(BaseModel):
|
|
7
|
+
mwH: tuple[float, float, float, float]
|
|
8
|
+
logpH: tuple[float, float, float, float]
|
|
9
|
+
hbaH: tuple[float, float, float, float]
|
|
10
|
+
hbdH: tuple[float, float, float, float]
|
|
11
|
+
rbH: tuple[float, float, float, float]
|
|
12
|
+
arRCH: tuple[float, float, float, float]
|
|
13
|
+
|
|
14
|
+
@model_validator(mode="before")
|
|
15
|
+
@classmethod
|
|
16
|
+
def accept_positional(cls, data: Any):
|
|
17
|
+
if isinstance(data, dict):
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
if isinstance(data, (list, tuple)):
|
|
21
|
+
if len(data) != 6:
|
|
22
|
+
raise ValueError("Expected 6 items: mwH, logpH, hbaH, hbdH, rbH, arRCH")
|
|
23
|
+
|
|
24
|
+
keys = ("mwH", "logpH", "hbaH", "hbdH", "rbH", "arRCH")
|
|
25
|
+
return dict(zip(keys, data))
|
|
26
|
+
|
|
27
|
+
raise TypeError("Expected a dict or a 6-item tuple/list")
|
|
28
|
+
|
|
29
|
+
def as_set(self):
|
|
30
|
+
return [self.mwH, self.logpH, self.hbaH, self.hbdH, self.rbH, self.arRCH]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pythonQEPest.gui.actions.action_clicks import GUIActionsClicks
|
|
2
|
+
from pythonQEPest.gui.actions.actions_crud import GUIActionsCRUD
|
|
3
|
+
from pythonQEPest.gui.actions.actions_other import GUIActionsOther
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"GUIActionsClicks",
|
|
7
|
+
"GUIActionsCRUD",
|
|
8
|
+
"GUIActionsOther",
|
|
9
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class GUIActionsClicks:
|
|
2
|
+
def __init__(self, menu):
|
|
3
|
+
self.menu = menu
|
|
4
|
+
|
|
5
|
+
def on_treeview_click_left(self, event):
|
|
6
|
+
tree = event.widget
|
|
7
|
+
item_id = tree.identify_row(event.y)
|
|
8
|
+
if not item_id:
|
|
9
|
+
return
|
|
10
|
+
selected = tree.selection()
|
|
11
|
+
if item_id in selected:
|
|
12
|
+
tree.selection_remove(item_id)
|
|
13
|
+
else:
|
|
14
|
+
tree.selection_set(item_id) # [*selected, item_id]
|
|
15
|
+
|
|
16
|
+
def on_treeview_click_right(self, event):
|
|
17
|
+
tree = event.widget
|
|
18
|
+
item_id = tree.identify_row(event.y)
|
|
19
|
+
if item_id:
|
|
20
|
+
tree.selection_set(item_id)
|
|
21
|
+
self.menu.tk_popup(event.x_root, event.y_root)
|
|
22
|
+
|
|
23
|
+
def resize_columns(self, event):
|
|
24
|
+
widget = event.widget
|
|
25
|
+
region = widget.identify("region", event.x, event.y)
|
|
26
|
+
if region != "separator":
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
col = widget.identify_column(event.x)
|
|
30
|
+
if not col:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
col_index = int(col.replace("#", "")) - 1
|
|
34
|
+
col_id = widget["columns"][col_index]
|
|
35
|
+
|
|
36
|
+
max_width = max(
|
|
37
|
+
[len(str(widget.set(k, col_id))) for k in widget.get_children("")]
|
|
38
|
+
+ [len(col_id)]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
pixel_width = max_width * 8
|
|
42
|
+
widget.column(col_id, width=pixel_width)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from tkinter import messagebox, ttk
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import pyperclip
|
|
5
|
+
except ImportError:
|
|
6
|
+
pyperclip = None
|
|
7
|
+
|
|
8
|
+
from pythonQEPest.gui.elements.EditWindow import EditWindow
|
|
9
|
+
from pythonQEPest.gui.utility.DataManager import DataManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GUIActionsCRUD:
|
|
13
|
+
def __init__(self, data_tree, result_tree, save_button, index):
|
|
14
|
+
self.index = index
|
|
15
|
+
self.data_manager = DataManager()
|
|
16
|
+
self.data_tree: ttk.Treeview = data_tree
|
|
17
|
+
self.result_tree: ttk.Treeview = result_tree
|
|
18
|
+
self.save_button = save_button
|
|
19
|
+
|
|
20
|
+
def edit_selected(self, *args, **kwargs):
|
|
21
|
+
selected = self.data_tree.selection()
|
|
22
|
+
if not selected:
|
|
23
|
+
messagebox.showwarning("Select Entry", "Select Entry for editing")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
item_id = selected[0]
|
|
27
|
+
EditWindow(item_id=item_id, tree=self.data_tree, child_tree=self.result_tree)
|
|
28
|
+
|
|
29
|
+
def copy_selected(self, *args, **kwargs):
|
|
30
|
+
selected_data_tree = self.data_tree.selection()
|
|
31
|
+
selected_result_tree = self.result_tree.selection()
|
|
32
|
+
|
|
33
|
+
if not (selected_data_tree or selected_result_tree):
|
|
34
|
+
messagebox.showwarning("Select Entry", "Select Entry for coping")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
rows_text = []
|
|
38
|
+
if selected_data_tree:
|
|
39
|
+
rows_text.append(
|
|
40
|
+
"\t".join(
|
|
41
|
+
map(str, ("ID", "Name", "MW", "LogP", "HBA", "HBD", "RB", "arR"))
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
for item in selected_data_tree:
|
|
45
|
+
values = self.data_tree.item(item, "values")[1:]
|
|
46
|
+
rows_text.append("\t".join(map(str, values)))
|
|
47
|
+
|
|
48
|
+
if selected_result_tree:
|
|
49
|
+
rows_text.append("\t".join(map(str, ("ID", "Name", "QEH", "QEI", "QEF"))))
|
|
50
|
+
for item in selected_result_tree:
|
|
51
|
+
values = self.result_tree.item(item, "values")[1:]
|
|
52
|
+
rows_text.append("\t".join(map(str, values)))
|
|
53
|
+
|
|
54
|
+
data_str = "\n".join(rows_text)
|
|
55
|
+
|
|
56
|
+
if not pyperclip:
|
|
57
|
+
messagebox.showerror(
|
|
58
|
+
"Error",
|
|
59
|
+
"pyperclip module is not installed. Install it with command"
|
|
60
|
+
+ "'pip install .[gui]' or 'poetry install --with ui'"
|
|
61
|
+
+ "to enable copy functionality.",
|
|
62
|
+
)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
pyperclip.copy(data_str)
|
|
66
|
+
messagebox.showinfo("Copied", "Data copied to clipboard.")
|
|
67
|
+
|
|
68
|
+
def paste_entries(self, *args, **kwargs):
|
|
69
|
+
if not pyperclip:
|
|
70
|
+
messagebox.showerror(
|
|
71
|
+
"Error",
|
|
72
|
+
"pyperclip module is not installed. Install it with command"
|
|
73
|
+
+ "'pip install .[gui]' or 'poetry install --with ui'"
|
|
74
|
+
+ "to enable copy functionality.",
|
|
75
|
+
)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
clipboard_text = pyperclip.paste()
|
|
79
|
+
if not clipboard_text.strip():
|
|
80
|
+
messagebox.showwarning("Clipboard is empty", "Copy the text first.")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
lines = clipboard_text.strip().splitlines()
|
|
84
|
+
count_added = 0
|
|
85
|
+
|
|
86
|
+
for line in lines:
|
|
87
|
+
parts = line.strip().split("\t")
|
|
88
|
+
if len(parts) == 7:
|
|
89
|
+
idx = self.index
|
|
90
|
+
self.data_manager.add_file((idx, *parts))
|
|
91
|
+
self.data_tree.insert("", "end", values=(idx, *parts))
|
|
92
|
+
count_added += 1
|
|
93
|
+
self.index += 1
|
|
94
|
+
|
|
95
|
+
if count_added:
|
|
96
|
+
messagebox.showinfo(
|
|
97
|
+
"Inserted",
|
|
98
|
+
f"Inserted {count_added} entries. Don't forget to process the data.",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def delete_selected(self, *args, **kwargs):
|
|
102
|
+
selected = self.data_tree.selection()
|
|
103
|
+
if not selected:
|
|
104
|
+
messagebox.showwarning("Select Entry", "Select the entry to delete.")
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
idxs_to_remove = []
|
|
108
|
+
for item in selected:
|
|
109
|
+
values = self.data_tree.item(item, "values")
|
|
110
|
+
idx_to_remove = int(values[0])
|
|
111
|
+
idxs_to_remove.append(idx_to_remove)
|
|
112
|
+
|
|
113
|
+
self.data_tree.delete(item)
|
|
114
|
+
|
|
115
|
+
if self.result_tree.exists(item):
|
|
116
|
+
self.result_tree.delete(item)
|
|
117
|
+
|
|
118
|
+
self.data_manager.remove_result_by_ids(idxs_to_remove)
|
|
119
|
+
self.data_manager.remove_file_by_ids(idxs_to_remove)
|
|
120
|
+
|
|
121
|
+
messagebox.showinfo("Deleted", "The selected records have been deleted.")
|
|
122
|
+
|
|
123
|
+
if not self.data_manager:
|
|
124
|
+
self.save_button.config(state="disabled")
|
|
125
|
+
|
|
126
|
+
def clear_everything(self, *args, **kwargs):
|
|
127
|
+
self.data_manager.clear_file()
|
|
128
|
+
self.data_manager.clear_result()
|
|
129
|
+
|
|
130
|
+
self.data_tree.delete(*self.data_tree.get_children())
|
|
131
|
+
self.result_tree.delete(*self.result_tree.get_children())
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import filedialog, messagebox
|
|
3
|
+
|
|
4
|
+
from pythonQEPest.dto.QEPestInput import QEPestInput
|
|
5
|
+
from pythonQEPest.gui.utility.DataManager import DataManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GUIActionsOther:
|
|
9
|
+
def __init__(self, root, data_tree, result_tree, save_button, qepest):
|
|
10
|
+
self.data_manager = DataManager()
|
|
11
|
+
self.root = root
|
|
12
|
+
self.data_tree = data_tree
|
|
13
|
+
self.result_tree = result_tree
|
|
14
|
+
self.save_button = save_button
|
|
15
|
+
self.qepest = qepest
|
|
16
|
+
|
|
17
|
+
def load_file(self):
|
|
18
|
+
file_path = filedialog.askopenfilename(
|
|
19
|
+
filetypes=[("Text files", "*.txt"), ("CSV files", "*.csv")]
|
|
20
|
+
)
|
|
21
|
+
if not file_path:
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
|
26
|
+
self.data_manager.clear_file()
|
|
27
|
+
self.data_tree.delete(*self.data_tree.get_children())
|
|
28
|
+
|
|
29
|
+
for idx, line in enumerate(file):
|
|
30
|
+
parts = line.strip().split("\t")
|
|
31
|
+
if len(parts) == 7:
|
|
32
|
+
self.data_manager.add_file((idx, *parts))
|
|
33
|
+
self.data_tree.insert("", "end", values=(idx, *parts))
|
|
34
|
+
|
|
35
|
+
messagebox.showinfo(
|
|
36
|
+
"File uploaded", f"Loaded {len(self.data_manager.file_data)} rows."
|
|
37
|
+
)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
messagebox.showerror("Loading error", str(e))
|
|
40
|
+
|
|
41
|
+
def add_entry(self):
|
|
42
|
+
form = tk.Toplevel(self.root)
|
|
43
|
+
form.title("Add entry")
|
|
44
|
+
form.transient(self.root)
|
|
45
|
+
form.grab_set()
|
|
46
|
+
|
|
47
|
+
entries = {}
|
|
48
|
+
fields = ["Name", "MW", "LogP", "HBA", "HBD", "RB", "arR"]
|
|
49
|
+
|
|
50
|
+
for idx, field in enumerate(fields):
|
|
51
|
+
tk.Label(form, text=field).grid(
|
|
52
|
+
row=idx, column=0, padx=5, pady=5, sticky="e"
|
|
53
|
+
)
|
|
54
|
+
entry = tk.Entry(form)
|
|
55
|
+
entry.grid(row=idx, column=1, padx=5, pady=5)
|
|
56
|
+
entries[field] = entry
|
|
57
|
+
|
|
58
|
+
def submit():
|
|
59
|
+
values = []
|
|
60
|
+
for field in fields:
|
|
61
|
+
val = entries[field].get().strip()
|
|
62
|
+
if not val:
|
|
63
|
+
messagebox.showwarning("Error", f"The field {field} not filled.")
|
|
64
|
+
return
|
|
65
|
+
values.append(val)
|
|
66
|
+
|
|
67
|
+
idx = len(self.data_manager.file_data)
|
|
68
|
+
self.data_manager.add_file((idx, *values))
|
|
69
|
+
self.data_tree.insert("", "end", values=(idx, *values))
|
|
70
|
+
form.destroy()
|
|
71
|
+
messagebox.showinfo(
|
|
72
|
+
"Added",
|
|
73
|
+
"The entry has been added. Process the data to update the result.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
tk.Button(form, text="Add", command=submit).grid(
|
|
77
|
+
row=len(fields), column=0, columnspan=2, pady=10
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
form.wait_window()
|
|
81
|
+
|
|
82
|
+
def process_data(self):
|
|
83
|
+
if not self.data_manager.file_data:
|
|
84
|
+
messagebox.showwarning("No data", "Please add or upload data first.")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# self.result_data = []
|
|
88
|
+
# self.result_tree.delete(*self.result_tree.get_children())
|
|
89
|
+
|
|
90
|
+
for row in self.data_manager.file_data:
|
|
91
|
+
try:
|
|
92
|
+
result_row = [
|
|
93
|
+
row[0],
|
|
94
|
+
*self.qepest.compute_params(
|
|
95
|
+
QEPestInput.from_array(row[1:])
|
|
96
|
+
).to_array(),
|
|
97
|
+
]
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
messagebox.showwarning("Warning!", f"Line number: {row[0]}\n{str(e)}")
|
|
100
|
+
continue
|
|
101
|
+
self.data_manager.add_result(result_row)
|
|
102
|
+
self.result_tree.insert("", "end", values=result_row)
|
|
103
|
+
|
|
104
|
+
self.save_button.config(state="normal")
|
|
105
|
+
|
|
106
|
+
def save_result(self):
|
|
107
|
+
if not self.data_manager.result_data:
|
|
108
|
+
messagebox.showwarning("No result", "Process the data first.")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
save_path = filedialog.asksaveasfilename(
|
|
112
|
+
defaultextension=".txt", filetypes=[("Text files", "*.txt")]
|
|
113
|
+
)
|
|
114
|
+
if not save_path:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
with open(save_path, "w", encoding="utf-8") as file:
|
|
119
|
+
file.write("Name\tQEH\tQEI\tQEF\n")
|
|
120
|
+
for row in self.data_manager.result_data:
|
|
121
|
+
file.write("\t".join(map(str, row)) + "\n")
|
|
122
|
+
|
|
123
|
+
messagebox.showinfo("Saved", f"The result is saved in: {save_path}")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
messagebox.showerror("Saving error", str(e))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from tkinter import Frame, Button
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import pyperclip
|
|
5
|
+
except ImportError:
|
|
6
|
+
pyperclip = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ButtonsFrame(Frame):
|
|
10
|
+
def __init__(self, root, *args, **kwargs):
|
|
11
|
+
super().__init__(root, *args, **kwargs)
|
|
12
|
+
|
|
13
|
+
self.pack(anchor="w", fill="x", pady=5)
|
|
14
|
+
|
|
15
|
+
# TODO: select_file must have an option for CSV
|
|
16
|
+
self.select_file_button = Button(self, text="Select File")
|
|
17
|
+
self.add_entry_button = Button(self, text="Add entry")
|
|
18
|
+
self.edit_entry_button = Button(self, text="Edit entry")
|
|
19
|
+
self.delete_selected_button = Button(self, text="Delete entry")
|
|
20
|
+
self.copy_button = Button(self, text="Copy Data")
|
|
21
|
+
self.paste_button = Button(self, text="Paste Data")
|
|
22
|
+
|
|
23
|
+
self.process_data_button = Button(self, text="Process Data")
|
|
24
|
+
self.clear_button = Button(self, text="Clear Data")
|
|
25
|
+
|
|
26
|
+
def set_actions(self, actions_crud, actions_other):
|
|
27
|
+
self.select_file_button.pack(side="left", padx=(10, 4))
|
|
28
|
+
self.add_entry_button.pack(side="left", padx=4)
|
|
29
|
+
self.edit_entry_button.pack(side="left", padx=4)
|
|
30
|
+
self.delete_selected_button.pack(side="left", padx=4)
|
|
31
|
+
|
|
32
|
+
if not pyperclip:
|
|
33
|
+
self.copy_button.config(state="disabled")
|
|
34
|
+
self.paste_button.config(state="disabled")
|
|
35
|
+
|
|
36
|
+
self.copy_button.pack(side="left", padx=4)
|
|
37
|
+
self.paste_button.pack(side="left", padx=4)
|
|
38
|
+
|
|
39
|
+
self.process_data_button.pack(side="right", padx=(4, 10))
|
|
40
|
+
self.clear_button.pack(side="right", padx=4)
|
|
41
|
+
|
|
42
|
+
self.select_file_button.config(command=actions_other.load_file)
|
|
43
|
+
self.add_entry_button.config(command=actions_other.add_entry)
|
|
44
|
+
self.edit_entry_button.config(command=actions_crud.edit_selected)
|
|
45
|
+
self.delete_selected_button.config(command=actions_crud.delete_selected)
|
|
46
|
+
self.copy_button.config(command=actions_crud.copy_selected)
|
|
47
|
+
self.paste_button.config(command=actions_crud.paste_entries)
|
|
48
|
+
|
|
49
|
+
self.process_data_button.config(command=actions_other.process_data)
|
|
50
|
+
self.clear_button.config(command=actions_crud.clear_everything)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from tkinter.ttk import Treeview
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataTree(Treeview):
|
|
5
|
+
def __init__(self, root, *args, **kwargs):
|
|
6
|
+
columns = ("ID", "Name", "MW", "LogP", "HBA", "HBD", "RB", "arR")
|
|
7
|
+
super().__init__(root, *args, columns=columns, show="headings", **kwargs)
|
|
8
|
+
|
|
9
|
+
for col in columns:
|
|
10
|
+
self.heading(col, text=col)
|
|
11
|
+
|
|
12
|
+
self.pack(padx=10, pady=10, fill="both", expand=True)
|
|
13
|
+
|
|
14
|
+
def set_actions(self, sort_column, actions_clicks):
|
|
15
|
+
for col in self["columns"]:
|
|
16
|
+
self.heading(
|
|
17
|
+
col, text=col, command=lambda _col=col: sort_column(self, _col, False)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
self.bind("<Button-1>", actions_clicks.on_treeview_click_left)
|
|
21
|
+
self.bind("<Button-3>", actions_clicks.on_treeview_click_right)
|
|
22
|
+
|
|
23
|
+
self.bind("<Double-1>", actions_clicks.resize_columns)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter.ttk import Treeview
|
|
3
|
+
|
|
4
|
+
from pythonQEPest.gui.utility.DataManager import DataManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EditWindow(tk.Toplevel):
|
|
8
|
+
def __init__(self, tree: Treeview, child_tree: Treeview, item_id, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, master=tree, **kwargs)
|
|
10
|
+
|
|
11
|
+
self.title("Edit Entry")
|
|
12
|
+
# self.geometry("200x300")
|
|
13
|
+
|
|
14
|
+
self.data_manager = DataManager()
|
|
15
|
+
|
|
16
|
+
values = tree.item(item_id, "values")
|
|
17
|
+
old_id = values[0]
|
|
18
|
+
|
|
19
|
+
entries = []
|
|
20
|
+
columns = tree["columns"]
|
|
21
|
+
|
|
22
|
+
self.grid_columnconfigure(0, weight=0)
|
|
23
|
+
self.grid_columnconfigure(1, weight=3)
|
|
24
|
+
self.resizable(True, False)
|
|
25
|
+
|
|
26
|
+
for idx, col in enumerate(columns):
|
|
27
|
+
tk.Label(self, text=col).grid(
|
|
28
|
+
row=idx, column=0, padx=5, pady=5, sticky="ew"
|
|
29
|
+
)
|
|
30
|
+
entry = tk.Entry(self)
|
|
31
|
+
entry.insert(0, values[idx])
|
|
32
|
+
entry.grid(row=idx, column=1, padx=(5, 5), pady=5, sticky="ew")
|
|
33
|
+
entries.append(entry)
|
|
34
|
+
|
|
35
|
+
def save_changes() -> None:
|
|
36
|
+
new_values = [entry.get() for entry in entries]
|
|
37
|
+
|
|
38
|
+
if child_tree.exists(item_id):
|
|
39
|
+
new_child_values = list(child_tree.item(item_id, "values"))
|
|
40
|
+
if new_child_values[0] != new_values[0]:
|
|
41
|
+
new_child_values[0] = new_values[0]
|
|
42
|
+
child_tree.item(item_id, values=new_child_values)
|
|
43
|
+
|
|
44
|
+
tree.item(item_id, values=new_values)
|
|
45
|
+
|
|
46
|
+
# Fix it someday plz
|
|
47
|
+
new_values[0] = int(new_values[0])
|
|
48
|
+
update_file_data(new_values)
|
|
49
|
+
|
|
50
|
+
def update_file_data(values: list) -> None:
|
|
51
|
+
element = list(
|
|
52
|
+
filter(lambda x: str(x[0]) == str(old_id), self.data_manager.file_data)
|
|
53
|
+
)[0]
|
|
54
|
+
if element:
|
|
55
|
+
self.data_manager.update_file(index=element[0], new_entry=values)
|
|
56
|
+
self.destroy()
|
|
57
|
+
|
|
58
|
+
tk.Button(self, text="Save", command=save_changes).grid(
|
|
59
|
+
row=len(columns), column=0, columnspan=2, pady=10
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.grab_set()
|
|
63
|
+
self.wait_window()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from tkinter import Menu as tkMenu
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Menu(tkMenu):
|
|
5
|
+
def __init__(self, root, *args, **kwargs):
|
|
6
|
+
super().__init__(root, *args, **kwargs, tearoff=0)
|
|
7
|
+
|
|
8
|
+
def set_actions(self, actions_crud):
|
|
9
|
+
self.add_command(label="Copy", command=actions_crud.copy_selected)
|
|
10
|
+
self.add_command(label="Edit", command=actions_crud.edit_selected)
|
|
11
|
+
self.add_command(label="Paste", command=actions_crud.paste_entries)
|
|
12
|
+
self.add_command(label="Delete", command=actions_crud.delete_selected)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from tkinter.ttk import Treeview
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResultTree(Treeview):
|
|
5
|
+
def __init__(self, root, *args, **kwargs):
|
|
6
|
+
columns = ("ID", "Name", "QEH", "QEI", "QEF")
|
|
7
|
+
super().__init__(root, *args, columns=columns, show="headings", **kwargs)
|
|
8
|
+
|
|
9
|
+
for col in columns:
|
|
10
|
+
self.heading(col, text=col)
|
|
11
|
+
self.pack(padx=10, pady=10, fill="both", expand=True)
|
|
12
|
+
|
|
13
|
+
def set_actions(self, sort_column, actions_clicks):
|
|
14
|
+
for col in self["columns"]:
|
|
15
|
+
self.heading(
|
|
16
|
+
col, text=col, command=lambda _col=col: sort_column(self, _col, False)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
self.bind("<Button-1>", actions_clicks.on_treeview_click_left)
|
|
20
|
+
self.bind("<Button-3>", actions_clicks.on_treeview_click_right)
|
|
21
|
+
|
|
22
|
+
self.bind("<Double-1>", actions_clicks.resize_columns)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from tkinter import Button
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SaveButton(Button):
|
|
5
|
+
def __init__(self, root, *args, **kwargs):
|
|
6
|
+
super().__init__(root, *args, text="Save Results", state="disabled", **kwargs)
|
|
7
|
+
self.pack(padx=10, pady=10, side="right")
|
|
8
|
+
|
|
9
|
+
def set_actions(self, actions_other):
|
|
10
|
+
self.config(command=actions_other.save_result)
|
pythonQEPest/gui/gui.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
from pythonQEPest.logger import init_logger
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import pyperclip
|
|
9
|
+
except ImportError:
|
|
10
|
+
pyperclip = None
|
|
11
|
+
|
|
12
|
+
from pythonQEPest.gui.actions import GUIActionsCRUD
|
|
13
|
+
from pythonQEPest.gui.actions import GUIActionsClicks
|
|
14
|
+
from pythonQEPest.gui.actions import GUIActionsOther
|
|
15
|
+
from pythonQEPest.gui.elements.ButtonsFrame import ButtonsFrame
|
|
16
|
+
from pythonQEPest.gui.elements.DataTree import DataTree
|
|
17
|
+
from pythonQEPest.gui.elements.Menu import Menu
|
|
18
|
+
from pythonQEPest.gui.elements.ResultTree import ResultTree
|
|
19
|
+
from pythonQEPest.gui.elements.SaveButton import SaveButton
|
|
20
|
+
from pythonQEPest.gui.gui_meta import QEPestMeta
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# TODO: Outdated. Must be dynamic
|
|
24
|
+
class GUI(QEPestMeta):
|
|
25
|
+
def treeview_sort_column(self, treeview, col, reverse):
|
|
26
|
+
treeview_lst = [(treeview.set(k, col), k) for k in treeview.get_children("")]
|
|
27
|
+
try:
|
|
28
|
+
treeview_lst.sort(key=lambda t: float(t[0]), reverse=reverse)
|
|
29
|
+
except ValueError:
|
|
30
|
+
treeview_lst.sort(key=lambda t: t[0], reverse=reverse)
|
|
31
|
+
|
|
32
|
+
for index, (_, k) in enumerate(treeview_lst):
|
|
33
|
+
treeview.move(k, "", index)
|
|
34
|
+
|
|
35
|
+
treeview.heading(
|
|
36
|
+
col, command=lambda: self.treeview_sort_column(treeview, col, not reverse)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def build_gui(self):
|
|
40
|
+
self.file_data = []
|
|
41
|
+
self.index = 0
|
|
42
|
+
|
|
43
|
+
self.buttons_frame = ButtonsFrame(root=self.root)
|
|
44
|
+
self.data_tree = DataTree(root=self.root)
|
|
45
|
+
self.result_tree = ResultTree(root=self.root)
|
|
46
|
+
self.save_button = SaveButton(root=self.root)
|
|
47
|
+
self.menu = Menu(root=self.root)
|
|
48
|
+
|
|
49
|
+
self.actions_clicks = GUIActionsClicks(menu=self.menu)
|
|
50
|
+
self.actions_crud = GUIActionsCRUD(
|
|
51
|
+
data_tree=self.data_tree,
|
|
52
|
+
result_tree=self.result_tree,
|
|
53
|
+
save_button=self.save_button,
|
|
54
|
+
index=self.index,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.actions_other = GUIActionsOther(
|
|
58
|
+
data_tree=self.data_tree,
|
|
59
|
+
result_tree=self.result_tree,
|
|
60
|
+
qepest=self.qepest,
|
|
61
|
+
root=self.root,
|
|
62
|
+
save_button=self.save_button,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.buttons_frame.set_actions(self.actions_crud, self.actions_other)
|
|
66
|
+
self.data_tree.set_actions(self.treeview_sort_column, self.actions_clicks)
|
|
67
|
+
self.result_tree.set_actions(self.treeview_sort_column, self.actions_clicks)
|
|
68
|
+
self.save_button.set_actions(self.actions_other)
|
|
69
|
+
self.menu.set_actions(self.actions_crud)
|
|
70
|
+
|
|
71
|
+
if pyperclip:
|
|
72
|
+
self.root.bind("<Control-v>", self.actions_crud.paste_entries)
|
|
73
|
+
self.root.bind("<Control-c>", self.actions_crud.copy_selected)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> int:
|
|
77
|
+
load_dotenv()
|
|
78
|
+
init_logger()
|
|
79
|
+
|
|
80
|
+
root = tk.Tk()
|
|
81
|
+
GUI(root)
|
|
82
|
+
root.mainloop()
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
raise SystemExit(main())
|