Kertas Jawapan

Anonymous
plain_text
02/24/2026 12:50 AM
13.9 KB
28
Indexable
import tkinter as tk
from tkinter import ttk

TOTAL_QUESTIONS = 100
OPTIONS = ["A", "B", "C", "D"]

COLOR_NORMAL          = "#e8e8e8"
COLOR_SELECTED_ANSWER = "#43A047"
COLOR_SELECTED_SCHEME = "#FB8C00"
COLOR_CORRECT         = "#1565C0"
COLOR_WRONG           = "#C62828"

BTN_W   = 5
BTN_PAD = 3


class AnswerSheetApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Kertas Jawapan & Skema")
        self.resizable(True, True)

        self.answers        = {}
        self.scheme         = {}
        self.answer_buttons = {}
        self.scheme_buttons = {}
        self.result_labels  = {}

        self._build_ui()
        self._fit_window()

    # ──────────────────────────────────────── BUILD UI

    def _build_ui(self):
        style = ttk.Style(self)
        style.configure("TNotebook.Tab",
                        font=("Segoe UI", 11, "bold"), padding=[14, 6])

        # Footer packed FIRST (bottom), then notebook fills the rest
        self._build_footer()

        self.nb = ttk.Notebook(self)
        self.nb.pack(fill="both", expand=True, padx=6, pady=(6, 0))

        # ── TAB JAWAPAN
        tab_ans = ttk.Frame(self.nb)
        self.nb.add(tab_ans, text="  📝  Jawapan  ")
        self.ans_canvas, self.ans_inner = self._make_scrollable(tab_ans)

        ttk.Label(self.ans_inner,
                  text="Pilih jawapan anda untuk setiap soalan",
                  font=("Segoe UI", 10, "italic"), foreground="#666"
                  ).pack(pady=(8, 4))

        for q in range(1, TOTAL_QUESTIONS + 1):
            self._make_answer_row(q)

        # ── TAB SKEMA
        tab_sch = ttk.Frame(self.nb)
        self.nb.add(tab_sch, text="  🗝️  Skema Jawapan  ")
        self.sch_canvas, self.sch_inner = self._make_scrollable(tab_sch)

        ttk.Label(self.sch_inner,
                  text="Masukkan skema / kunci jawapan",
                  font=("Segoe UI", 10, "italic"), foreground="#666"
                  ).pack(pady=(8, 4))

        for q in range(1, TOTAL_QUESTIONS + 1):
            self._make_scheme_row(q)

    # ──────────────────────────────────────── FOOTER (packed to bottom FIRST)

    def _build_footer(self):
        ttk.Separator(self, orient="horizontal").pack(side="bottom", fill="x")

        self.footer = tk.Frame(self, bg="#f0f4f0", pady=10)
        self.footer.pack(side="bottom", fill="x")

        # Row 1: Semak button — centered
        row1 = tk.Frame(self.footer, bg="#f0f4f0")
        row1.pack(fill="x")

        self.check_btn = tk.Button(
            row1,
            text="✔  Semak Jawapan",
            command=self._check,
            bg="#43A047", fg="white",
            font=("Segoe UI", 11, "bold"),
            relief="flat", padx=20, pady=7,
            cursor="hand2",
            activebackground="#388E3C", activeforeground="white"
        )
        self.check_btn.pack(anchor="center")

        # Row 2: Betul & Salah — centered
        row2 = tk.Frame(self.footer, bg="#f0f4f0")
        row2.pack(fill="x", pady=(6, 0))

        self.lbl_correct = tk.Label(
            row2, text="✔  Betul: —",
            font=("Segoe UI", 10, "bold"),
            fg=COLOR_CORRECT, bg="#f0f4f0"
        )
        self.lbl_correct.pack(side="left", expand=True)

        self.lbl_wrong = tk.Label(
            row2, text="✘  Salah: —",
            font=("Segoe UI", 10, "bold"),
            fg=COLOR_WRONG, bg="#f0f4f0"
        )
        self.lbl_wrong.pack(side="left", expand=True)

        # Row 3: Markah badge — centered
        row3 = tk.Frame(self.footer, bg="#f0f4f0")
        row3.pack(fill="x", pady=(4, 0))

        tk.Label(row3, text="Markah:",
                 font=("Segoe UI", 10, "bold"),
                 fg="#333", bg="#f0f4f0"
                 ).pack(side="left", expand=True, anchor="e", padx=(0, 6))

        self.lbl_score = tk.Label(
            row3, text="—",
            font=("Segoe UI", 11, "bold"),
            fg="white", bg="#9e9e9e",
            padx=12, pady=4
        )
        self.lbl_score.pack(side="left", expand=True, anchor="w")

    # ──────────────────────────────────────── SCROLLABLE

    def _make_scrollable(self, parent):
        outer = ttk.Frame(parent)
        outer.pack(fill="both", expand=True)

        canvas = tk.Canvas(outer, highlightthickness=0)
        sb     = ttk.Scrollbar(outer, orient="vertical", command=canvas.yview)
        inner  = ttk.Frame(canvas)

        inner.bind("<Configure>",
                   lambda e: canvas.configure(
                       scrollregion=canvas.bbox("all")))
        canvas.create_window((0, 0), window=inner, anchor="nw")
        canvas.configure(yscrollcommand=sb.set)

        canvas.pack(side="left", fill="both", expand=True)
        sb.pack(side="right", fill="y")

        for w in (canvas, inner):
            w.bind("<MouseWheel>",
                   lambda e, c=canvas: c.yview_scroll(
                       int(-1 * (e.delta / 120)), "units"))

        return canvas, inner

    def _bind_scroll(self, widget, canvas):
        widget.bind("<MouseWheel>",
                    lambda e, c=canvas: c.yview_scroll(
                        int(-1 * (e.delta / 120)), "units"))
        for child in widget.winfo_children():
            self._bind_scroll(child, canvas)

    # ──────────────────────────────────────── ROWS

    def _make_answer_row(self, q):
        row = ttk.Frame(self.ans_inner)
        row.pack(fill="x", padx=16, pady=2)

        ttk.Label(row, text=f"{q:>3}.", width=4,
                  font=("Segoe UI", 10)).pack(side="left")

        self.answers[q]        = None
        self.answer_buttons[q] = {}

        for opt in OPTIONS:
            btn = tk.Button(
                row, text=opt, width=BTN_W, bg=COLOR_NORMAL,
                font=("Segoe UI", 9, "bold"), relief="flat", bd=0,
                command=lambda o=opt, n=q: self._sel_ans(n, o)
            )
            btn.pack(side="left", padx=BTN_PAD)
            self.answer_buttons[q][opt] = btn

        lbl = ttk.Label(row, width=9, font=("Segoe UI", 9))
        lbl.pack(side="left", padx=8)
        self.result_labels[q] = lbl

        self._bind_scroll(row, self.ans_canvas)

    def _make_scheme_row(self, q):
        row = ttk.Frame(self.sch_inner)
        row.pack(fill="x", padx=16, pady=2)

        ttk.Label(row, text=f"{q:>3}.", width=4,
                  font=("Segoe UI", 10)).pack(side="left")

        self.scheme[q]         = None
        self.scheme_buttons[q] = {}

        for opt in OPTIONS:
            btn = tk.Button(
                row, text=opt, width=BTN_W, bg=COLOR_NORMAL,
                font=("Segoe UI", 9, "bold"), relief="flat", bd=0,
                command=lambda o=opt, n=q: self._sel_sch(n, o)
            )
            btn.pack(side="left", padx=BTN_PAD)
            self.scheme_buttons[q][opt] = btn

        self._bind_scroll(row, self.sch_canvas)

    # ──────────────────────────────────────── SELECTION

    def _sel_ans(self, q, opt):
        # klik jawapan sama → clear; klik lain → pilih
        if self.answers[q] == opt:
            self.answer_buttons[q][opt].config(bg=COLOR_NORMAL, fg="black")
            self.answers[q] = None
        else:
            for b in self.answer_buttons[q].values():
                b.config(bg=COLOR_NORMAL, fg="black")
            self.answer_buttons[q][opt].config(bg=COLOR_SELECTED_ANSWER, fg="white")
            self.answers[q] = opt

    def _sel_sch(self, q, opt):
        # klik skema sama → clear; klik lain → pilih
        if self.scheme[q] == opt:
            self.scheme_buttons[q][opt].config(bg=COLOR_NORMAL, fg="black")
            self.scheme[q] = None
        else:
            for b in self.scheme_buttons[q].values():
                b.config(bg=COLOR_NORMAL, fg="black")
            self.scheme_buttons[q][opt].config(bg=COLOR_SELECTED_SCHEME, fg="white")
            self.scheme[q] = opt

    # ──────────────────────────────────────── CHECK

    def _check(self):
        correct = wrong = skipped = 0

        for q in range(1, TOTAL_QUESTIONS + 1):
            ua = self.answers.get(q)
            sa = self.scheme.get(q)
            if not ua or not sa:
                self.result_labels[q].config(text="—", foreground="#aaa")
                skipped += 1
            elif ua == sa:
                self.result_labels[q].config(text="✔ Betul", foreground=COLOR_CORRECT)
                correct += 1
            else:
                self.result_labels[q].config(text="✘ Salah", foreground=COLOR_WRONG)
                wrong += 1

        pct = (correct / TOTAL_QUESTIONS) * 100

        self.lbl_correct.config(text=f"✔  Betul: {correct}")
        self.lbl_wrong.config(text=f"✘  Salah: {wrong}")
        self.lbl_score.config(text=f" {pct:.1f}% ")

        if pct >= 80:
            badge_bg = "#1565C0"
        elif pct >= 60:
            badge_bg = "#F57F17"
        else:
            badge_bg = "#C62828"
        self.lbl_score.config(bg=badge_bg)

    # ──────────────────────────────────────── FIT WINDOW

    def _fit_window(self):
        self.update_idletasks()
        self.ans_inner.update_idletasks()

        inner_w   = self.ans_inner.winfo_reqwidth()
        win_w     = inner_w + 18 + 30

        screen_h  = self.winfo_screenheight()
        max_h     = int(screen_h * 0.60)
        content_h = self.ans_inner.winfo_reqheight()
        footer_h  = self.footer.winfo_reqheight() + 10
        tab_bar_h = 48
        canvas_h  = min(content_h, max_h - tab_bar_h - footer_h)

        self.ans_canvas.config(width=inner_w, height=canvas_h)
        self.sch_canvas.config(width=inner_w, height=canvas_h)

        total_h = canvas_h + tab_bar_h + footer_h + 18
        self.geometry(f"{win_w}x{total_h}")


if __name__ == "__main__":
    AnswerSheetApp().mainloop()
Editor is loading...
Leave a Comment