init
This commit is contained in:
commit
7f99df67cb
582
main.py
Normal file
582
main.py
Normal file
@ -0,0 +1,582 @@
|
||||
import ezodf
|
||||
import sys
|
||||
import re
|
||||
from ttkthemes import ThemedTk
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, simpledialog, ttk, font, scrolledtext
|
||||
import json
|
||||
import os
|
||||
|
||||
SKIP_ROWS = 2
|
||||
TASK_NAME_ROW = 0 + SKIP_ROWS
|
||||
MAX_PTS_ROW = 1 + SKIP_ROWS
|
||||
FIRST_DATA_LINE = 3 + SKIP_ROWS
|
||||
|
||||
class AMCTable:
|
||||
def load_table(self, fn) -> None:
|
||||
# read ODS file
|
||||
self.__tab = ezodf.opendoc(fn)
|
||||
sh = self.__tab.sheets[0]
|
||||
ncols = sh.ncols()
|
||||
|
||||
# find the first, task-associated column
|
||||
first_task_col = -1
|
||||
for i in range(0, ncols):
|
||||
taskid_cell = sh[TASK_NAME_ROW, i]
|
||||
val = taskid_cell.value
|
||||
if (val == "max"):
|
||||
first_task_col = i + 1
|
||||
|
||||
#print("Első, pontszámot tartalmazó oszlop: ", first_task_col)
|
||||
|
||||
# dissect task identifiers
|
||||
task_tree = {}
|
||||
taskre = re.compile("F([0-9])[-_]?([0-9])([a-f])?[-_]?([a-zA-Z0-9_-]+)")
|
||||
for i in range(first_task_col, ncols):
|
||||
taskid_cell = sh[TASK_NAME_ROW, i]
|
||||
maxpts_cell = sh[MAX_PTS_ROW, i]
|
||||
taskid = taskid_cell.value
|
||||
|
||||
m = taskre.match(taskid)
|
||||
if m is None:
|
||||
break
|
||||
|
||||
maxpts = int(maxpts_cell.value)
|
||||
|
||||
tasknum = m.group(1)
|
||||
subtasknum = m.group(2)
|
||||
subsubtaskletter = m.group(3)
|
||||
name = m.group(4)
|
||||
|
||||
# if subsubtaskletter is None:
|
||||
# printsubsubtaskletter = ""
|
||||
# else:
|
||||
# printsubsubtaskletter = " " + subsubtaskletter + ")"
|
||||
# if name is None:
|
||||
# printname = ""
|
||||
# else:
|
||||
# printname = ": " + name
|
||||
#print("F ", tasknum, "/", subtasknum, printsubsubtaskletter, printname, " (", taskid, ")", sep='')
|
||||
|
||||
tasknum = int(tasknum)
|
||||
subtasknum = int(subtasknum)
|
||||
if tasknum not in task_tree:
|
||||
task_tree[tasknum] = { "subtasks": {} }
|
||||
|
||||
if subtasknum not in task_tree[tasknum]["subtasks"]:
|
||||
task_tree[tasknum]["subtasks"][subtasknum] = { "subsubtasks": {} }
|
||||
|
||||
if subsubtaskletter is None:
|
||||
subsubtaskletter = "-"
|
||||
|
||||
if subsubtaskletter not in task_tree[tasknum]["subtasks"][subtasknum]["subsubtasks"]:
|
||||
task_tree[tasknum]["subtasks"][subtasknum]["subsubtasks"][subsubtaskletter] = []
|
||||
|
||||
task_tree[tasknum]["subtasks"][subtasknum]["subsubtasks"][subsubtaskletter].append({"name": name, "fullid": taskid, "column": i, "maxpts": maxpts})
|
||||
|
||||
self.__task_tree = task_tree
|
||||
|
||||
def export_hierarchy_json(self, fn) -> None:
|
||||
json_dump = json.dumps(self.__task_tree)
|
||||
|
||||
with open(fn, "w") as jsonf:
|
||||
jsonf.write(json_dump)
|
||||
|
||||
def export_hierarchy_md(self, fn) -> None:
|
||||
md = ""
|
||||
|
||||
for ti in self.__task_tree:
|
||||
task = self.__task_tree[ti]
|
||||
md += "- F" + str(ti) + ":\n"
|
||||
for sti in task["subtasks"]:
|
||||
md += " - /" + str(sti) + ":\n"
|
||||
subtask = task["subtasks"][sti]
|
||||
for ssti in subtask["subsubtasks"]:
|
||||
md += " - " + str(ssti) + ")\n"
|
||||
subsubtask = subtask["subsubtasks"][ssti]
|
||||
for amcquestion in subsubtask:
|
||||
md += " - " + amcquestion["name"] + "\n"
|
||||
|
||||
with open(fn, "w") as jsonf:
|
||||
jsonf.write(md)
|
||||
|
||||
|
||||
def get_task_tree(self) -> dict:
|
||||
return self.__task_tree
|
||||
|
||||
def create_scoring_sheet(self, fn, scoring) -> None:
|
||||
scs = ezodf.Sheet("Scoring")
|
||||
self.__tab.sheets += scs
|
||||
|
||||
scs.append_columns(len(scoring) + 1)
|
||||
scs.append_rows(2)
|
||||
|
||||
comment = "of:=\"\""
|
||||
|
||||
i = 0
|
||||
for ssti in scoring:
|
||||
expr = scoring[ssti]
|
||||
scs[0,i].set_value(ssti)
|
||||
scs[1,i].formula = "of:" + expr
|
||||
comment += " & \"" + ssti + ": \" & " + "[.$" + index_to_address(i) + "2]"
|
||||
i += 1
|
||||
if i < len(scoring):
|
||||
comment += " & \", \""
|
||||
|
||||
scs[0,i].set_value("Komment")
|
||||
scs[1,i].formula = comment
|
||||
|
||||
self.__tab.saveas(fn)
|
||||
|
||||
def get_first_sheet_name(self) -> str:
|
||||
return self.__tab.sheets[0].name
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__task_tree = {}
|
||||
|
||||
def index_to_address(idx) -> str:
|
||||
BASE = 26
|
||||
out = ""
|
||||
quot = idx
|
||||
place = 0
|
||||
while (quot > 0) or (place == 0):
|
||||
(quot, rem) = divmod(quot, BASE)
|
||||
if (quot == 0) and (place != 0):
|
||||
val = rem - 1
|
||||
else:
|
||||
val = rem
|
||||
out += chr(ord('A') + val)
|
||||
place += 1
|
||||
return out[::-1]
|
||||
|
||||
class SpreadSheetPosition:
|
||||
def __init__(self, idx) -> None:
|
||||
self.__index = idx
|
||||
self.__address = index_to_address(idx)
|
||||
|
||||
def index(self) -> int:
|
||||
return self.__index
|
||||
|
||||
def address(self) -> str:
|
||||
return self.__address
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__address + " (" + str(self.__index) + ")"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class MainWin:
|
||||
def populate_task_treeview(self, task_tree: dict) -> None:
|
||||
self.__task_tree = dict(task_tree)
|
||||
self.__itemdump = {}
|
||||
self.__subsubtaskdump = {}
|
||||
self.__summing = {}
|
||||
self.__columns = {}
|
||||
self.__sel_subsubtask = {}
|
||||
self.__expr_editor.config(state="disabled")
|
||||
|
||||
tree = self.__tree
|
||||
items = tree.get_children()
|
||||
if items != ():
|
||||
tree.delete(*items)
|
||||
|
||||
for ti in self.__task_tree:
|
||||
task_level = tree.insert("", tk.END, text="F" + str(ti), tags=("task"), open=True)
|
||||
subtasks = self.__task_tree[ti]["subtasks"]
|
||||
t_maxpts = 0
|
||||
|
||||
task_id = str(ti)
|
||||
self.__itemdump[task_id] = { "item": task_level, "task": ti }
|
||||
|
||||
for sti in subtasks:
|
||||
subtask_level = tree.insert(task_level, tk.END, text="/" + str(sti), tags=("subtask"), open=True)
|
||||
subsubtasks = subtasks[sti]["subsubtasks"]
|
||||
st_maxpts = 0
|
||||
|
||||
subtask_id = task_id + "/" + str(sti)
|
||||
self.__itemdump[subtask_id] = { "item": subtask_level, "task": ti, "subtask": sti }
|
||||
|
||||
for ssti in subsubtasks:
|
||||
sst_maxpts = 0
|
||||
subsubtask_level = tree.insert(subtask_level, tk.END, text=str(ssti) + ")", tags=("subsubtask", "selectable"), open=True)
|
||||
|
||||
subsubtask_id = subtask_id + "/" + str(ssti)
|
||||
self.__itemdump[subsubtask_id] = { "item": subsubtask_level, "task": ti, "subtask": sti, "subsubtask": ssti, "id": subsubtask_id }
|
||||
self.__subsubtaskdump[subsubtask_level] = subsubtask_id
|
||||
self.__summing[subsubtask_id] = { "expression": "" }
|
||||
|
||||
for amcq in subsubtasks[ssti]:
|
||||
maxpts = amcq["maxpts"]
|
||||
sst_maxpts += maxpts
|
||||
|
||||
column = SpreadSheetPosition(amcq["column"])
|
||||
fullid = amcq["fullid"]
|
||||
amcq_level = tree.insert(subsubtask_level, tk.END, text=amcq["name"], values=(maxpts, column, fullid), tags=("all_missing"))
|
||||
|
||||
amcq_id = subsubtask_id + "/" + str(amcq["name"])
|
||||
self.__columns[amcq_id] = { "column": column, "item": amcq_level }
|
||||
|
||||
self.__tree.item(subsubtask_level, values=(sst_maxpts, "", ""))
|
||||
st_maxpts += sst_maxpts
|
||||
|
||||
self.__tree.item(subtask_level, values=(st_maxpts, "", ""))
|
||||
t_maxpts += st_maxpts
|
||||
|
||||
self.__tree.item(task_level, values=(t_maxpts, "", ""))
|
||||
|
||||
|
||||
def colorize_entries(self) -> None:
|
||||
summed_amcq_missing = 0
|
||||
|
||||
for ti in self.__task_tree:
|
||||
task_id = str(ti)
|
||||
|
||||
subtasks = self.__task_tree[ti]["subtasks"]
|
||||
|
||||
missing_tasks = 0
|
||||
|
||||
for sti in subtasks:
|
||||
subtask_id = task_id + "/" + str(sti)
|
||||
|
||||
subsubtasks = subtasks[sti]["subsubtasks"]
|
||||
|
||||
missing_subsubtasks = 0
|
||||
|
||||
for ssti in subsubtasks:
|
||||
subsubtask_id = subtask_id + "/" + str(ssti)
|
||||
|
||||
missing_amcq = 0
|
||||
included_amcq = 0
|
||||
|
||||
if subsubtask_id in self.__summing:
|
||||
expr = self.__summing[subsubtask_id]["expression"]
|
||||
else:
|
||||
expr = ""
|
||||
|
||||
for amcq in subsubtasks[ssti]:
|
||||
regex = r"#\b" + amcq["name"] + r"\b#"
|
||||
|
||||
if re.search(regex, expr):
|
||||
tags = ["nothing_missing"]
|
||||
included_amcq += 1
|
||||
else:
|
||||
tags = ["all_missing"]
|
||||
missing_amcq += 1
|
||||
|
||||
amcq_id = subsubtask_id + "/" + str(amcq["name"])
|
||||
amcq_level = self.__columns[amcq_id]["item"]
|
||||
self.__tree.item(amcq_level, tags=tags)
|
||||
|
||||
summed_amcq_missing += missing_amcq
|
||||
|
||||
if missing_amcq == 0:
|
||||
tags = [ "nothing_missing" ]
|
||||
elif missing_amcq > 0 and included_amcq > 0:
|
||||
tags = [ "some_missing" ]
|
||||
else:
|
||||
tags = [ "all_missing" ]
|
||||
|
||||
if missing_amcq > 0:
|
||||
missing_subsubtasks += 1
|
||||
|
||||
|
||||
tags.append("selectable")
|
||||
tags.append("subsubtask")
|
||||
|
||||
self.__tree.item(self.__itemdump[subsubtask_id]["item"], tags=tags)
|
||||
|
||||
if missing_subsubtasks == 0:
|
||||
tags = [ "nothing_missing" ]
|
||||
else:
|
||||
tags = [ "all_missing" ]
|
||||
missing_tasks += 1
|
||||
|
||||
tags.append("subtask")
|
||||
|
||||
self.__tree.item(self.__itemdump[subtask_id]["item"], tags=tags)
|
||||
|
||||
if missing_tasks == 0:
|
||||
tags = [ "nothing_missing" ]
|
||||
else:
|
||||
tags = [ "all_missing" ]
|
||||
|
||||
tags.append("task")
|
||||
|
||||
self.__tree.item(self.__itemdump[task_id]["item"], tags=tags)
|
||||
|
||||
self.__status.config(text=str(summed_amcq_missing) + " mező nem szerepel a pontszámításban")
|
||||
|
||||
def init_ods_tab(self) -> None:
|
||||
menuFrame = ttk.Frame(self.__loadOdsTab)
|
||||
menuFrame.pack(side="top", fill="x")
|
||||
|
||||
treeFrame = ttk.Frame(self.__loadOdsTab)
|
||||
treeFrame.pack(side="top", expand=True, fill="both")
|
||||
|
||||
tree = ttk.Treeview(treeFrame, columns=("maxpts", "odscolumn", "fullid"), selectmode="none")
|
||||
tree.pack(side="left", expand=True, fill="both")
|
||||
tree.heading("#0", text="Feladat")
|
||||
tree.heading("maxpts", text="Max. pont")
|
||||
tree.heading("odscolumn", text="ODS oszlop")
|
||||
tree.heading("fullid", text="Azonosító")
|
||||
|
||||
tree.tag_configure("all_missing", background="Salmon")
|
||||
tree.tag_configure("some_missing", background="Gold")
|
||||
tree.tag_configure("nothing_missing", background="LawnGreen")
|
||||
tree.tag_configure("selectable")
|
||||
|
||||
tfont = font.Font(family="Ubuntu Mono", size=11, weight="bold", underline=True)
|
||||
tree.tag_configure("task", font=tfont)
|
||||
|
||||
stfont = font.Font(family="Ubuntu Mono", size=11, weight="bold")
|
||||
tree.tag_configure("subtask", font=stfont)
|
||||
|
||||
sstfont = font.Font(family="Ubuntu Mono", size=11, slant="italic")
|
||||
tree.tag_configure("subsubtask", font=sstfont)
|
||||
|
||||
self.__tree = tree
|
||||
|
||||
tree_scroll = ttk.Scrollbar(treeFrame, orient="vertical", command=tree.yview)
|
||||
tree_scroll.pack(side="right", fill="y")
|
||||
tree.configure(yscrollcommand=tree_scroll.set)
|
||||
|
||||
def tree_click(event) -> None:
|
||||
item = self.__tree.identify_row(event.y)
|
||||
if item:
|
||||
tags = self.__tree.item(item, "tags")
|
||||
if "selectable" in tags:
|
||||
self.__tree.selection_set(item)
|
||||
|
||||
self.__sel_subsubtask = self.__itemdump[self.__subsubtaskdump[item]]
|
||||
id = self.__sel_subsubtask["id"]
|
||||
expr = self.__summing[id]["expression"]
|
||||
self.__expr_editor.config(state="normal")
|
||||
self.__expr_editor.delete(1.0, tk.END)
|
||||
self.__expr_editor.insert(tk.END, expr)
|
||||
pass
|
||||
|
||||
tree.bind("<Button-1>", tree_click)
|
||||
|
||||
expr_editor = scrolledtext.ScrolledText(treeFrame, wrap=tk.WORD, width=30, font=("Ubuntu Mono", 10))
|
||||
expr_editor.config(background="white", state="disabled")
|
||||
expr_editor.pack(side="right", expand=True, fill="both")
|
||||
|
||||
def expr_edit(event) -> None:
|
||||
if self.__expr_editor.cget("state") == "disabled":
|
||||
return
|
||||
|
||||
expr = self.__expr_editor.get(1.0, tk.END).strip()
|
||||
self.__summing[self.__sel_subsubtask["id"]]["expression"] = expr
|
||||
|
||||
self.colorize_entries()
|
||||
|
||||
# amcqs = self.__tree.get_children(self.__sel_subsubtask["item"])
|
||||
# for amcq in amcqs:
|
||||
# item = self.__tree.item(amcq)
|
||||
# pattern = r"#\b" + item["text"] + r"\b#"
|
||||
|
||||
# if re.search(pattern, expr):
|
||||
# tags = ["nothing_missing"]
|
||||
# else:
|
||||
# tags = ["all_missing"]
|
||||
|
||||
# self.__tree.item(amcq, tags=tags)
|
||||
# pass
|
||||
|
||||
expr_editor.bind("<KeyRelease>", expr_edit)
|
||||
|
||||
self.__expr_editor = expr_editor
|
||||
|
||||
def loadOds() -> None:
|
||||
filetypes = [
|
||||
("ODS táblázatok", "*.ods")
|
||||
]
|
||||
fn = filedialog.askopenfilename(
|
||||
title="Táblázat megnyitása",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=filetypes)
|
||||
|
||||
if fn == ():
|
||||
return
|
||||
|
||||
self.__table.load_table(fn)
|
||||
|
||||
task_tree = self.__table.get_task_tree()
|
||||
self.populate_task_treeview(task_tree)
|
||||
|
||||
self.colorize_entries()
|
||||
|
||||
loadOdsBtn = tk.Button(menuFrame, text="Táblázat betöltése", command=loadOds)
|
||||
loadOdsBtn.pack(side="left")
|
||||
|
||||
def save_hierarchy() -> None:
|
||||
filetypes = [
|
||||
("JSON", "*.json"),
|
||||
("Markdown", "*.md")
|
||||
]
|
||||
|
||||
fn = filedialog.asksaveasfilename(
|
||||
title="Táblázathierarchia mentése",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=filetypes
|
||||
)
|
||||
|
||||
if fn == ():
|
||||
return
|
||||
|
||||
if fn.lower().endswith(".md"):
|
||||
self.__table.export_hierarchy_md(fn)
|
||||
else:
|
||||
if not fn.endswith(".json"):
|
||||
fn += ".json"
|
||||
self.__table.export_hierarchy_json(fn)
|
||||
|
||||
storeHierarchyBtn = tk.Button(menuFrame, text="Hierarchia mentése", command=save_hierarchy)
|
||||
storeHierarchyBtn.pack(side="left")
|
||||
|
||||
def save_summing() -> None:
|
||||
filetypes = [
|
||||
("JSON", "*.json"),
|
||||
]
|
||||
|
||||
fn = filedialog.asksaveasfilename(
|
||||
title="Pontozás mentése",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=filetypes
|
||||
)
|
||||
|
||||
if fn == ():
|
||||
return
|
||||
|
||||
if not fn.endswith(".json"):
|
||||
fn += ".json"
|
||||
|
||||
json_dump = json.dumps(self.__summing)
|
||||
|
||||
with open(fn, "w") as jsonf:
|
||||
jsonf.write(json_dump)
|
||||
|
||||
storeSummingBtn = tk.Button(menuFrame, text="Pontozás mentése", command=save_summing)
|
||||
storeSummingBtn.pack(side="left")
|
||||
|
||||
def load_summing() -> None:
|
||||
filetypes = [
|
||||
("JSON", "*.json")
|
||||
]
|
||||
fn = filedialog.askopenfilename(
|
||||
title="Pontozás betöltése",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=filetypes)
|
||||
|
||||
if fn == ():
|
||||
return
|
||||
|
||||
with open(fn, "r") as jsonf:
|
||||
self.__summing = json.load(jsonf)
|
||||
|
||||
self.colorize_entries()
|
||||
|
||||
loadSummingBtn = tk.Button(menuFrame, text="Pontozás betöltése", command=load_summing)
|
||||
loadSummingBtn.pack(side="left")
|
||||
|
||||
def write_summing_into_table() -> None:
|
||||
filetypes = [
|
||||
("ODS", "*.ods"),
|
||||
]
|
||||
|
||||
fn = filedialog.asksaveasfilename(
|
||||
title="Pontozás táblázatba mentése",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=filetypes
|
||||
)
|
||||
|
||||
if fn == ():
|
||||
return
|
||||
|
||||
if not fn.endswith(".ods"):
|
||||
fn += ".ods"
|
||||
|
||||
fsname = self.__table.get_first_sheet_name()
|
||||
compiled_summing = {}
|
||||
|
||||
task_summing = {}
|
||||
task_first_col = 0
|
||||
prev_task = "1"
|
||||
i = 0
|
||||
for ssti in self.__summing:
|
||||
if ssti[0] != prev_task:
|
||||
task_summing["F" + prev_task] = "=SUM([.$" + index_to_address(task_first_col) + "2]:[.$" + index_to_address(i - 1) + "2])"
|
||||
prev_task = ssti[0]
|
||||
task_first_col = i
|
||||
|
||||
expr = self.__summing[ssti]["expression"]
|
||||
fields = re.findall(r"#\b(.+?)\b#", expr)
|
||||
|
||||
fdl = str(FIRST_DATA_LINE + 1)
|
||||
|
||||
for field in fields:
|
||||
amcq_id = ssti + "/" + field
|
||||
if amcq_id in self.__columns:
|
||||
address = "[.$" + fsname + ".$" + self.__columns[amcq_id]["column"].address() + fdl + "]"
|
||||
expr = expr.replace("#" + field + "#", address)
|
||||
|
||||
ssti = ssti.strip("/-")
|
||||
expr = expr.replace("\n", "")
|
||||
compiled_summing["F" + ssti] = expr
|
||||
|
||||
i += 1
|
||||
|
||||
task_summing["F" + prev_task] = "=SUM([.$" + index_to_address(task_first_col) + "2]:[.$" + index_to_address(i - 1) + "2])"
|
||||
|
||||
self.__table.create_scoring_sheet(fn, compiled_summing | task_summing)
|
||||
|
||||
|
||||
writeSummingBtn = tk.Button(menuFrame, text="Pontozás táblázatba írása", command=write_summing_into_table)
|
||||
writeSummingBtn.pack(side="left")
|
||||
|
||||
statusBar = tk.Label(self.__loadOdsTab, anchor="w")
|
||||
statusBar.pack(side="bottom", expand=False, fill="x")
|
||||
statusBar.config(text="Nincs táblázat betöltve")
|
||||
|
||||
self.__status = statusBar
|
||||
|
||||
def init_gui(self) -> None:
|
||||
win = ThemedTk()
|
||||
win.title("AMC táblázatösszegző")
|
||||
|
||||
self.__win = win
|
||||
|
||||
tabs = ttk.Notebook(win)
|
||||
|
||||
loadOdsTab = ttk.Frame(tabs)
|
||||
programScoringTab = ttk.Frame(tabs)
|
||||
|
||||
tabs.add(loadOdsTab, text="Táblázat")
|
||||
#tabs.add(programScoringTab, text="Pontozás")
|
||||
|
||||
self.__loadOdsTab = loadOdsTab
|
||||
#self.__programScoringTab = programScoringTab
|
||||
|
||||
self.init_ods_tab()
|
||||
|
||||
tabs.pack(expand=True, fill="both")
|
||||
|
||||
def init_table(self) -> None:
|
||||
self.__table = AMCTable()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.init_table()
|
||||
self.init_gui()
|
||||
|
||||
|
||||
def mainloop(self) -> None:
|
||||
self.__win.mainloop()
|
||||
|
||||
# -----
|
||||
|
||||
win = MainWin()
|
||||
win.mainloop()
|
||||
|
||||
exit(0)
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
ezodf
|
||||
lxml
|
||||
pillow
|
||||
pip
|
||||
tk
|
||||
ttkthemes
|
||||
Loading…
x
Reference in New Issue
Block a user