Changeset a14c6b0 in flexograder


Ignore:
Timestamp:
12/15/25 14:10:35 (4 months ago)
Author:
Enrico Schwass <ennoausberlin@…>
Branches:
fake-data, main, master
Children:
9b1abd2
Parents:
34e976b
Message:

image preview added

Files:
7 edited

Legend:

Unmodified
Added
Removed
  • builder/media_items.py

    r34e976b ra14c6b0  
    7878
    7979        return obj
    80    
     80
    8181    def to_html(self):
    8282        raise NotImplementedError
     
    112112@dataclass
    113113class 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 
    118114    label: str = "Datei herunterladen"
    119115
     
    151147
    152148def media_factory(m: dict) -> MediaItem:
    153 
    154     mtype = m.get("type", "").lower()
     149    print("Dict:", m)
     150    mtype = m.get("mtype", "").lower()
    155151
    156152    cls = {
  • gui/answer_options_dialog.py

    r34e976b ra14c6b0  
    3838
    3939        # --- 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")
    4351
    4452        # --- OK / Cancel ---
     
    100108
    101109            option_id = chr(ord("A") + index)
    102            
    103110            all_items.append({
    104111                "id": option_id,
  • gui/attach_media_dialog.py

    r34e976b ra14c6b0  
    11import tkinter as tk
    22from tkinter import ttk, filedialog
     3from pathlib import Path
    34from builder.media_items import MediaItem
    4 from flexoentity import FlexOID  # if you use entity-based IDs
    55
    66class AttachMediaDialog(tk.Toplevel):
    7     def __init__(self, parent, question):
     7    def __init__(self, parent, question_dict):
    88        super().__init__(parent)
    99        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", [])
    1114        self.resizable(False, False)
    1215
     
    1518        self.listbox.pack(padx=10, pady=5, fill="both")
    1619
    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}")
    2024
    2125        btn_frame = ttk.Frame(self)
     
    2428        ttk.Button(btn_frame, text="Add...", command=self.add_media).pack(side=tk.LEFT, padx=5)
    2529        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)
    2731
    2832    def add_media(self):
    29         print("add")
    3033        path = filedialog.askopenfilename(
    3134            title="Select media file",
     
    3437        if not path:
    3538            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}")
    3956
    4057    def remove_media(self):
     
    4461        idx = selection[0]
    4562        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 python3
    2 
    31import tkinter as tk
    42from tkinter import ttk
    5 
     3import tkinter.font as tkfont
     4from PIL import Image, ImageTk
    65
    76class DetailPanel(ttk.Frame):
    87    """
    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.
    1110    """
     11
     12    MAX_IMG_WIDTH = 360
     13    MAX_IMG_HEIGHT = 500
    1214
    1315    def __init__(self, parent):
     
    1517        self.grid(row=0, column=1, sticky="nsew")
    1618        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
    1822
    1923        # ─────────────────────────── Question text ───────────────────────────
     
    2125        self.txt_question = tk.Text(self, wrap="word", height=3)
    2226        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)
    2429        # ─────────────────────────── Metadata ────────────────────────────────
    2530        meta = ttk.LabelFrame(self, text="Metadata", padding=(6, 4))
     
    4348        self.lbl_media.grid(row=3, column=1, sticky="w")
    4449
     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
    4559        # ─────────────────────────── 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")
    4761        self.tree_answers = ttk.Treeview(
    4862            self, columns=("text", "points"), show="headings", height=5
     
    5266        self.tree_answers.column("text", width=320, anchor="w")
    5367        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)
    5570
    5671        # ─────────────────────────── Context ────────────────────────────────
    5772        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))
    5974        ctx.columnconfigure(1, weight=1)
    6075
     
    6883
    6984    # ───────────────────────────────────────────────────────────────────────
     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    # ───────────────────────────────────────────────────────────────────────
    7096    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
    77100        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
    78110        if question:
     111            # Question text
    79112            self.txt_question.insert("1.0", question.text)
     113
     114            # Metadata
    80115            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)))
    85117            self.lbl_id.config(text=str(question.flexo_id))
    86118            self.lbl_media.config(
    87119                text=", ".join([m.src for m in question.media if m]) or "—"
    88120            )
     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
    89135        else:
    90136            self.lbl_type.config(text="—")
     
    93139            self.lbl_media.config(text="—")
    94140
    95         # Update answers
    96         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        )
    101147
    102         # Context: catalog & clipboard
    103         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  
    3333        button_frame.grid(row=2, column=0, columnspan=2, sticky="e")
    3434
    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)
    3742
    3843        # Enter/ESC bindings
  • gui/gui.py

    r34e976b ra14c6b0  
    471471        dlg = self.get_question_editor_for(self, q, editing=True)
    472472        self.wait_window(dlg)
     473        print("Factory:", dlg.result)
    473474        dlg.result["meta"]["originator_id"] = str(uuid4())
    474475        dlg.result["meta"]["owner_id"] = str(uuid4())
  • gui/option_question_editor.py

    r34e976b ra14c6b0  
    1919        self.question_dict = question_dict
    2020        self.result = None
    21         print(question_dict)
    2221        self.meta = question_dict.get("meta", {})
    2322        self.content = question_dict.get("content", {})
     
    116115        self.wait_window(dlg)
    117116
     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
    118130    def on_ok(self):
    119131        text = self.txt_question.get("1.0", tk.END).strip()
     
    122134            return
    123135
    124         print(self.meta)
    125136        content = {"text": text,
    126                    "options": self.content.get("options", {})
     137                   "options": self.content.get("options", {}),
     138                   "media": self.content.get("media", [])
    127139                   }
    128140        meta = {
     
    138150                       "content": content
    139151                       }
    140         print(self.result)
     152        print("Final:", self.result)
    141153        self.destroy()
    142154
Note: See TracChangeset for help on using the changeset viewer.