Source code for scwidgets.exercise._widget_multiplechoice_exercise

import random
from typing import Any, Dict, List, Optional, Union

from ipywidgets import (
    HTML,
    HBox,
    HTMLMath,
    Layout,
    Output,
    RadioButtons,
    SelectMultiple,
    VBox,
)

from .._utils import Formatter
from ..css_style import CssStyle
from ..cue import SaveCueBox, SaveResetCueButton
from ._widget_exercise_registry import ExerciseRegistry, ExerciseWidget


[docs] class MultipleChoiceExercise(VBox, ExerciseWidget): """ :param options: Either a dict or a list. If a dict is provided, the widget will display the dictionary’s value but save its key to the registry. :param key: Unique key for the exercise. :param description: A string describing the exercise that will be put into an HTML widget above the exercise. :param title: A title for the exercise. If not provided, the key is used. :param exercise_registry: An exercise registry that is used to register the answers to save them later. If specified the save and load panel will appear. :param allow_multiple: Whether multiple selections are allowed. :param randomize_order: Whether to randomize order of options. """ def __init__( self, options: Union[List[Any], Dict[Any, Any]], key: Optional[str] = None, description: Optional[str] = None, title: Optional[str] = None, exercise_registry: Optional[ExerciseRegistry] = None, allow_multiple: bool = False, randomize_order: bool = False, *args, **kwargs, ): self._description = description if description is not None: self._description_html = HTMLMath(self._description) self._description_html.add_class("exercise-description") else: self._description_html = None self._title: Union[str, None] if title is None: if key is not None: self._title = key self._title_html = HTML(f"<b>{key}</b>") else: self._title = None self._title_html = None else: self._title = title self._title_html = HTML(f"<b>{title}</b>") if self._title_html is not None: self._title_html.add_class("exercise-title") self._options_dict: Union[dict, None] if isinstance(options, dict): self._options_dict = options options_list = [(value, key) for key, value in options.items()] elif isinstance(options, list): self._options_dict = None options_list = options else: raise ValueError("Options must be provided as a dict or a list.") if randomize_order: random.shuffle(options_list) self._options_list = options_list self.allow_multiple = allow_multiple if allow_multiple: self._selection_widget = SelectMultiple( options=options_list, description="", layout=Layout(width="auto"), ) else: self._selection_widget = RadioButtons( options=options_list, description="", layout=Layout(width="auto"), ) if exercise_registry is None: self._cue_selection = self._selection_widget self._save_button = None self._load_button = None self._button_panel = None else: self._cue_selection = SaveCueBox( self._selection_widget, "value", self._selection_widget, cued=True ) self._save_button = SaveResetCueButton( self._cue_selection, self._on_click_save_action, disable_on_successful_action=kwargs.pop( "disable_save_button_on_successful_action", False ), disable_during_action=kwargs.pop( "disable_save_button_during_action", True ), description="Save answer", button_tooltip="Saves answer to the loaded file", ) self._load_button = SaveResetCueButton( self._cue_selection, self._on_click_load_action, disable_on_successful_action=kwargs.pop( "disable_load_button_on_successful_action", False ), disable_during_action=kwargs.pop( "disable_load_button_during_action", True ), description="Load answer", button_tooltip="Loads answer from the loaded file", ) self._save_button.set_cue_widgets([self._cue_selection, self._load_button]) self._load_button.set_cue_widgets([self._cue_selection, self._save_button]) self._button_panel = HBox( [self._save_button, self._load_button], layout=Layout(justify_content="flex-end"), ) self._output = Output() if exercise_registry is not None: ExerciseWidget.__init__(self, exercise_registry, key) else: # otherwise ExerciseWidget constructor will raise an error ExerciseWidget.__init__(self, None, None) widget_children = [CssStyle()] if self._title_html is not None: widget_children.append(self._title_html) if self._description_html is not None: widget_children.append(self._description_html) widget_children.append(self._cue_selection) if self._button_panel is not None: widget_children.append(self._button_panel) widget_children.append(self._output) VBox.__init__(self, widget_children, *args, **kwargs) @property def title(self) -> Union[str, None]: return self._title @property def description(self) -> Union[str, None]: return self._description @property def answer(self) -> dict: return {"selection": self._selection_widget.value} @answer.setter def answer(self, answer) -> None: if hasattr(self._cue_selection, "unobserve_widgets"): self._cue_selection.unobserve_widgets() if self._save_button is not None: self._save_button.unobserve_widgets() if self._load_button is not None: self._load_button.unobserve_widgets() self._selection_widget.value = answer["selection"] if hasattr(self._cue_selection, "observe_widgets"): self._cue_selection.observe_widgets() if self._save_button is not None: self._save_button.observe_widgets() if self._load_button is not None: self._load_button.observe_widgets() def _on_click_save_action(self) -> bool: self._output.clear_output(wait=True) raised_error = False with self._output: try: result = self.save() if isinstance(result, str): print(Formatter.color_success_message(result)) elif isinstance(result, Exception): raise result else: print(result) except Exception as e: print(Formatter.color_error_message("Error raised while saving file:")) raised_error = True raise e return not raised_error def _on_click_load_action(self) -> bool: self._output.clear_output(wait=True) raised_error = False with self._output: try: result = self.load() if isinstance(result, str): print(Formatter.color_success_message(result)) elif isinstance(result, Exception): raise result else: print(result) return True except Exception as e: print(Formatter.color_error_message("Error raised while loading file:")) raised_error = True raise e return not raised_error
[docs] def handle_save_result(self, result: Union[str, Exception]) -> None: self._output.clear_output(wait=True) with self._output: if isinstance(result, Exception): print(Formatter.color_error_message("Error raised while saving file:")) raise result else: if self._load_button is not None: self._load_button.cued = False if self._save_button is not None: self._save_button.cued = False print(Formatter.color_success_message(result))
[docs] def handle_load_result(self, result: Union[str, Exception]) -> None: self._output.clear_output(wait=True) with self._output: if isinstance(result, Exception): print(Formatter.color_error_message("Error raised while loading file:")) raise result else: if self._load_button is not None: self._load_button.cued = False if self._save_button is not None: self._save_button.cued = False print(Formatter.color_success_message(result))