Changeset a14c6b0 in flexograder
- Timestamp:
- 12/15/25 14:10:35 (4 months ago)
- Branches:
- fake-data, main, master
- Children:
- 9b1abd2
- Parents:
- 34e976b
- Files:
-
- 7 edited
-
builder/media_items.py (modified) (3 diffs)
-
gui/answer_options_dialog.py (modified) (2 diffs)
-
gui/attach_media_dialog.py (modified) (5 diffs)
-
gui/detail_panel.py (modified) (7 diffs)
-
gui/edit_answer_dialog.py (modified) (1 diff)
-
gui/gui.py (modified) (1 diff)
-
gui/option_question_editor.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
builder/media_items.py
r34e976b ra14c6b0 78 78 79 79 return obj 80 80 81 81 def to_html(self): 82 82 raise NotImplementedError … … 112 112 @dataclass 113 113 class DownloadItem(MediaItem): 114 # def __init__(self, src: str, label: str = None):115 # super().__init__(domain="GEN", entity_type=EntityType.MEDIA, state=EntityState.DRAFT, src=src)116 # self.label = label or "Datei herunterladen"117 118 114 label: str = "Datei herunterladen" 119 115 … … 151 147 152 148 def media_factory(m: dict) -> MediaItem: 153 154 mtype = m.get(" type", "").lower()149 print("Dict:", m) 150 mtype = m.get("mtype", "").lower() 155 151 156 152 cls = { -
gui/answer_options_dialog.py
r34e976b ra14c6b0 38 38 39 39 # --- Buttons below --- 40 ttk.Button(frame, text="Add", command=self.add_option).grid(row=1, column=0, sticky="ew", padx=(0, 5)) 41 ttk.Button(frame, text="Edit", command=self.edit_option).grid(row=1, column=1, sticky="ew", padx=(0, 5)) 42 ttk.Button(frame, text="Delete", command=self.delete_option).grid(row=1, column=2, sticky="ew") 40 ttk.Button(frame, text="Add", command=self.add_option).grid(row=1, 41 column=0, 42 sticky="ew", 43 padx=(0, 5)) 44 ttk.Button(frame, text="Edit", command=self.edit_option).grid(row=1, 45 column=1, 46 sticky="ew", 47 padx=(0, 5)) 48 ttk.Button(frame, text="Delete", command=self.delete_option).grid(row=1, 49 column=2, 50 sticky="ew") 43 51 44 52 # --- OK / Cancel --- … … 100 108 101 109 option_id = chr(ord("A") + index) 102 103 110 all_items.append({ 104 111 "id": option_id, -
gui/attach_media_dialog.py
r34e976b ra14c6b0 1 1 import tkinter as tk 2 2 from tkinter import ttk, filedialog 3 from pathlib import Path 3 4 from builder.media_items import MediaItem 4 from flexoentity import FlexOID # if you use entity-based IDs5 5 6 6 class AttachMediaDialog(tk.Toplevel): 7 def __init__(self, parent, question ):7 def __init__(self, parent, question_dict): 8 8 super().__init__(parent) 9 9 self.title("Media attachments") 10 self.question = question 10 self.question_dict = question_dict 11 self.result = [] 12 # Ensure media list exists 13 self.media_list = question_dict.get("media", []) 11 14 self.resizable(False, False) 12 15 … … 15 18 self.listbox.pack(padx=10, pady=5, fill="both") 16 19 17 print(question.media) 18 for m in question.media: 19 self.listbox.insert(tk.END, f"{m.mtype.upper()}: {m.src}") 20 for m in self.media_list: 21 mtype = str(m.get("mtype", "unknown")).upper() 22 src = m.get("src", "?") 23 self.listbox.insert(tk.END, f"{mtype}: {src}") 20 24 21 25 btn_frame = ttk.Frame(self) … … 24 28 ttk.Button(btn_frame, text="Add...", command=self.add_media).pack(side=tk.LEFT, padx=5) 25 29 ttk.Button(btn_frame, text="Remove", command=self.remove_media).pack(side=tk.LEFT, padx=5) 26 ttk.Button(btn_frame, text="Close", command=self. destroy).pack(side=tk.RIGHT, padx=5)30 ttk.Button(btn_frame, text="Close", command=self.on_ok).pack(side=tk.RIGHT, padx=5) 27 31 28 32 def add_media(self): 29 print("add")30 33 path = filedialog.askopenfilename( 31 34 title="Select media file", … … 34 37 if not path: 35 38 return 36 media_item = MediaItem(src=path) 37 self.question.media.append(media_item) 38 self.listbox.insert(tk.END, f"{media_item.mtype.upper()}: {path}") 39 40 # Determine media type from extension 41 ext = Path(path).suffix.lower() 42 if ext in {".png", ".jpg", ".jpeg"}: 43 mtype = "image" 44 elif ext in {".mp4"}: 45 mtype = "video" 46 elif ext in {".wav"}: 47 mtype = "audio" 48 elif ext in {".pdf"}: 49 mtype = "pdf" 50 else: 51 mtype = "unknown" 52 media_entry = {"mtype": mtype, "src": path} 53 self.media_list.append(media_entry) 54 print("List:", self.media_list) 55 self.listbox.insert(tk.END, f"{mtype.upper()}: {path}") 39 56 40 57 def remove_media(self): … … 44 61 idx = selection[0] 45 62 self.listbox.delete(idx) 46 del self.question.media[idx] 63 try: 64 del self.media_list[idx] 65 except IndexError: 66 pass 67 68 def on_ok(self): 69 """Return the final edited media list.""" 70 print("OK:", self.media_list) 71 self.result = self.media_list 72 self.destroy() 73 74 def on_close(self): 75 """Return the final edited media list.""" 76 self.result = self.media_list 77 self.destroy() -
gui/detail_panel.py
r34e976b ra14c6b0 1 #!/usr/bin/env python32 3 1 import tkinter as tk 4 2 from tkinter import ttk 5 3 import tkinter.font as tkfont 4 from PIL import Image, ImageTk 6 5 7 6 class DetailPanel(ttk.Frame): 8 7 """ 9 Right-hand info panel for Flex-O-Grader main window.10 Displays question text, metadata, answers, and context (catalog + clipboard).8 Right-hand info panel for Flex-O-Grader. 9 With question text, metadata, preview image, answers, and context. 11 10 """ 11 12 MAX_IMG_WIDTH = 360 13 MAX_IMG_HEIGHT = 500 12 14 13 15 def __init__(self, parent): … … 15 17 self.grid(row=0, column=1, sticky="nsew") 16 18 self.columnconfigure(0, weight=1) 17 self.rowconfigure(3, weight=1) # answers section expands 19 20 # Cache for the currently displayed PhotoImage (must hold ref!) 21 self._image_ref = None 18 22 19 23 # ─────────────────────────── Question text ─────────────────────────── … … 21 25 self.txt_question = tk.Text(self, wrap="word", height=3) 22 26 self.txt_question.grid(row=1, column=0, sticky="ew", pady=(0, 4)) 23 27 self.font_question = tkfont.Font(family="TkDefaultFont", size=13) 28 self.txt_question.configure(font=self.font_question) 24 29 # ─────────────────────────── Metadata ──────────────────────────────── 25 30 meta = ttk.LabelFrame(self, text="Metadata", padding=(6, 4)) … … 43 48 self.lbl_media.grid(row=3, column=1, sticky="w") 44 49 50 # ─────────────────────────── Image preview section ─────────────────── 51 self.img_frame = ttk.LabelFrame(self, text="Picture Preview", padding=(6, 6)) 52 self.img_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 4)) 53 self.img_frame.columnconfigure(0, weight=1) 54 self.rowconfigure(3, weight=3) # allow preview area to expand 55 56 self.img_label = ttk.Label(self.img_frame, anchor="center") 57 self.img_label.grid(row=0, column=0, sticky="nsew") 58 45 59 # ─────────────────────────── Answers table ─────────────────────────── 46 ttk.Label(self, text="Answers").grid(row= 3, column=0, sticky="w")60 ttk.Label(self, text="Answers").grid(row=4, column=0, sticky="w") 47 61 self.tree_answers = ttk.Treeview( 48 62 self, columns=("text", "points"), show="headings", height=5 … … 52 66 self.tree_answers.column("text", width=320, anchor="w") 53 67 self.tree_answers.column("points", width=60, anchor="center") 54 self.tree_answers.grid(row=4, column=0, sticky="nsew", pady=(0, 4)) 68 self.tree_answers.grid(row=5, column=0, sticky="nsew", pady=(0, 4)) 69 self.rowconfigure(5, weight=0) 55 70 56 71 # ─────────────────────────── Context ──────────────────────────────── 57 72 ctx = ttk.LabelFrame(self, text="Context", padding=(6, 4)) 58 ctx.grid(row= 5, column=0, sticky="ew", pady=(4, 0))73 ctx.grid(row=6, column=0, sticky="ew", pady=(4, 0)) 59 74 ctx.columnconfigure(1, weight=1) 60 75 … … 68 83 69 84 # ─────────────────────────────────────────────────────────────────────── 85 def _load_and_resize_image(self, img_path): 86 """Load an image from disk and resize to fit preview area.""" 87 try: 88 img = Image.open(img_path) 89 except Exception: 90 return None 91 92 img.thumbnail((self.MAX_IMG_WIDTH, self.MAX_IMG_HEIGHT), Image.Resampling.LANCZOS) 93 return ImageTk.PhotoImage(img) 94 95 # ─────────────────────────────────────────────────────────────────────── 70 96 def refresh_details(self, question=None, catalog_title=None, 71 clipboard_action=None, clipboard_count=0): 72 """ 73 Refresh all visible info according to the selected question and state. 74 Called by main GUI after question selection or clipboard change. 75 """ 76 # Clear / update question text 97 clipboard_action=None, clipboard_count=0): 98 99 # Clear question text 77 100 self.txt_question.delete("1.0", tk.END) 101 102 # Clear image preview 103 self.img_label.configure(image="") 104 self._image_ref = None 105 106 # Clear answers 107 for item in self.tree_answers.get_children(): 108 self.tree_answers.delete(item) 109 78 110 if question: 111 # Question text 79 112 self.txt_question.insert("1.0", question.text) 113 114 # Metadata 80 115 self.lbl_type.config(text=question.__class__.__name__) 81 try: 82 self.lbl_state.config(text=question.state.name) 83 except AttributeError: 84 self.lbl_state.config(text=str(question.state)) 116 self.lbl_state.config(text=getattr(question.state, "name", str(question.state))) 85 117 self.lbl_id.config(text=str(question.flexo_id)) 86 118 self.lbl_media.config( 87 119 text=", ".join([m.src for m in question.media if m]) or "—" 88 120 ) 121 122 # Answers 123 for text, points in question.answer_options_for_display(): 124 self.tree_answers.insert("", "end", values=(text, points)) 125 126 # Image preview (first media entry wins) 127 if question.media: 128 first = question.media[0] 129 if first and getattr(first, "src", None): 130 img = self._load_and_resize_image(first.src) 131 if img: 132 self._image_ref = img # must keep reference 133 self.img_label.configure(image=img) 134 89 135 else: 90 136 self.lbl_type.config(text="—") … … 93 139 self.lbl_media.config(text="—") 94 140 95 # Update answers96 for item in self.tree_answers.get_children():97 self.tree_answers.delete(item)98 if question:99 for text, points in question.answer_options_for_display():100 self.tree_answers.insert("", "end", values=(text, points))141 # Context 142 self.lbl_catalog.config(text=catalog_title or "—") 143 self.lbl_clipboard.config( 144 text="Empty" if clipboard_count == 0 145 else f"{clipboard_action or '?'} ({clipboard_count})" 146 ) 101 147 102 # Context: catalog & clipboard103 self.lbl_catalog.config(text=catalog_title or "—")104 if clipboard_count == 0:105 self.lbl_clipboard.config(text="Empty")106 else:107 self.lbl_clipboard.config(108 text=f"{clipboard_action or '?'} ({clipboard_count})"109 ) -
gui/edit_answer_dialog.py
r34e976b ra14c6b0 33 33 button_frame.grid(row=2, column=0, columnspan=2, sticky="e") 34 34 35 ttk.Button(button_frame, text="OK", command=self.on_ok).grid(row=0, column=0, padx=5) 36 ttk.Button(button_frame, text="Cancel", command=self.on_cancel).grid(row=0, column=1, padx=5) 35 ttk.Button(button_frame, text="OK", command=self.on_ok).grid(row=0, 36 column=0, 37 padx=5) 38 39 ttk.Button(button_frame, text="Cancel", command=self.on_cancel).grid(row=0, 40 column=1, 41 padx=5) 37 42 38 43 # Enter/ESC bindings -
gui/gui.py
r34e976b ra14c6b0 471 471 dlg = self.get_question_editor_for(self, q, editing=True) 472 472 self.wait_window(dlg) 473 print("Factory:", dlg.result) 473 474 dlg.result["meta"]["originator_id"] = str(uuid4()) 474 475 dlg.result["meta"]["owner_id"] = str(uuid4()) -
gui/option_question_editor.py
r34e976b ra14c6b0 19 19 self.question_dict = question_dict 20 20 self.result = None 21 print(question_dict)22 21 self.meta = question_dict.get("meta", {}) 23 22 self.content = question_dict.get("content", {}) … … 116 115 self.wait_window(dlg) 117 116 117 if dlg.result is not None: 118 # ensure target structure exists 119 if "media" not in self.question_dict: 120 self.question_dict["media"] = [] 121 122 # overwrite old list 123 self.question_dict["media"] = dlg.result 124 125 # also update content-side snapshot 126 self.content["media"] = dlg.result 127 128 messagebox.showinfo("Media updated", f"{len(dlg.result)} media items attached.") 129 118 130 def on_ok(self): 119 131 text = self.txt_question.get("1.0", tk.END).strip() … … 122 134 return 123 135 124 print(self.meta)125 136 content = {"text": text, 126 "options": self.content.get("options", {}) 137 "options": self.content.get("options", {}), 138 "media": self.content.get("media", []) 127 139 } 128 140 meta = { … … 138 150 "content": content 139 151 } 140 print( self.result)152 print("Final:", self.result) 141 153 self.destroy() 142 154
Note:
See TracChangeset
for help on using the changeset viewer.
