Ejemplo n.º 1
0
 def on_leave(slot: dragdrop.Slot[Signage]) -> None:
     """Reset the visible sign when left."""
     nonlocal hover_toggle_id, hover_sign
     name_label['text'] = ''
     hover_sign = None
     if hover_toggle_id is not None:
         TK_ROOT.after_cancel(hover_toggle_id)
         hover_toggle_id = None
     img.apply(preview_left, IMG_BLANK)
     img.apply(preview_right, IMG_BLANK)
Ejemplo n.º 2
0
    def __init__(
        self,
        man: Manager,
        parent: tkinter.Misc,
        is_source: bool,
        label: str,
    ) -> None:
        """Internal only, use Manager.slot()."""

        self.man = man
        self.is_source = is_source
        self._contents = None
        self._pos_type = None
        self._lbl = tkinter.Label(parent)
        img.apply(self._lbl, man._img_blank)
        tk_tools.bind_leftclick(self._lbl, self._evt_start)
        self._lbl.bind(tk_tools.EVENTS['LEFT_SHIFT'], self._evt_fastdrag)
        self._lbl.bind('<Enter>', self._evt_hover_enter)
        self._lbl.bind('<Leave>', self._evt_hover_exit)

        config_event = self._evt_configure
        tk_tools.bind_rightclick(self._lbl, config_event)

        if label:
            self._text_lbl = tkinter.Label(
                self._lbl,
                text=label,
                font=('Helvetica', -12),
                relief='ridge',
                bg=img.PETI_ITEM_BG_HEX,
            )
        else:
            self._text_lbl = None

        if man.config_icon:
            self._info_btn = tkinter.Label(
                self._lbl,
                relief='ridge',
            )
            img.apply(self._info_btn, img.Handle.builtin('icons/gear', 10, 10))

            @tk_tools.bind_leftclick(self._info_btn)
            def info_button_click(e):
                """Trigger the callback whenever the gear button was pressed."""
                config_event(e)
                # Cancel the event sequence, so it doesn't travel up to the main
                # window and hide the window again.
                return 'break'

            # Rightclick does the same as the main icon.
            tk_tools.bind_rightclick(self._info_btn, config_event)
        else:
            self._info_btn = None
Ejemplo n.º 3
0
 def _display_item(
     self,
     lbl: Union[tkinter.Label, ttk.Label],
     item: Optional[ItemT],
     group: bool=False,
 ) -> None:
     """Display the specified item on the given label."""
     if item is None:
         image = self._img_blank
     elif group:
         try:
             image = item.dnd_group_icon
         except AttributeError:
             image = item.dnd_icon
     else:
         image = item.dnd_icon
     img.apply(lbl, image)
Ejemplo n.º 4
0
    def __init__(
        self,
        master: tkinter.Toplevel,
        *,
        size: Tuple[int, int]=(64, 64),
        config_icon: bool=False
    ):
        """Create a group of drag-drop slots.

        size is the size of each moved image.
        If config_icon is set, gear icons will be added to each slot to
        configure items. This indicates the right-click option is available,
        and makes it easier to press that.
        """
        self.width, self.height = size

        self._targets = []  # type: List[Slot[ItemT]]
        self._sources = []  # type: List[Slot[ItemT]]

        self._img_blank = img.Handle.color(img.PETI_ITEM_BG, *size)

        self.config_icon = config_icon

        # If dragging, the item we are dragging.
        self._cur_drag = None  # type: Optional[ItemT]
        # While dragging, the place we started at.
        self._cur_prev_slot = None  # type: Optional[Slot[ItemT]]

        self._callbacks = {
            event: []
            for event in Event
        }  # type: Dict[Event, List[Callable[[Slot], None]]]

        self._drag_win = drag_win = tkinter.Toplevel(master)
        drag_win.withdraw()
        drag_win.transient(master=master)
        drag_win.wm_overrideredirect(True)

        self._drag_lbl = drag_lbl = tkinter.Label(drag_win)
        img.apply(drag_lbl, self._img_blank)
        drag_lbl.grid(row=0, column=0)
        drag_win.bind(tk_tools.EVENTS['LEFT_RELEASE'], self._evt_stop)
Ejemplo n.º 5
0
 def hover_toggle() -> None:
     """Toggle between arrows and dual icons."""
     nonlocal hover_arrow, hover_toggle_id
     hover_arrow = not hover_arrow
     if hover_sign is None:
         return
     if hover_arrow and sign_arrow:
         left = hover_sign.dnd_icon
         right = sign_arrow.dnd_icon
     else:
         try:
             left = Signage.by_id(hover_sign.prim_id or '').dnd_icon
         except KeyError:
             left = hover_sign.dnd_icon
         try:
             right = Signage.by_id(hover_sign.sec_id or '').dnd_icon
         except KeyError:
             right = IMG_BLANK
     img.apply(preview_left, left)
     img.apply(preview_right, right)
     hover_toggle_id = TK_ROOT.after(1000, hover_toggle)
Ejemplo n.º 6
0
 def unload_icons(self) -> None:
     """Reset all icons to blank. This way they can be destroyed."""
     for slot in self._sources:
         img.apply(slot._lbl, self._img_blank)
     for slot in self._targets:
         img.apply(slot._lbl, self._img_blank)
     img.apply(self._drag_lbl, self._img_blank)
Ejemplo n.º 7
0
 def update_image(var_name: str, var_index: str, operation: str):
     img.apply(swatch, img.Handle.color(parse_color(var.get()), size, size))
Ejemplo n.º 8
0
 def hide_window() -> None:
     """Hide the window."""
     window.withdraw()
     drag_man.unload_icons()
     img.apply(preview_left, IMG_BLANK)
     img.apply(preview_right, IMG_BLANK)
Ejemplo n.º 9
0
def init_widgets(master: ttk.Frame) -> Optional[tk.Widget]:
    """Construct the widgets, returning the configuration button.

    If no signages are defined, this returns None.
    """

    if not any(Signage.all()):
        return ttk.Label(master)

    window.resizable(True, True)
    window.title(gettext('Configure Signage'))

    frame_selected = ttk.Labelframe(
        window,
        text=gettext('Selected'),
        relief='raised',
        labelanchor='n',
    )

    canv_all = tk.Canvas(window)

    scroll = tk_tools.HidingScroll(window, orient='vertical', command=canv_all.yview)
    canv_all['yscrollcommand'] = scroll.set

    name_label = ttk.Label(window, text='', justify='center')
    frame_preview = ttk.Frame(window, relief='raised', borderwidth=4)

    frame_selected.grid(row=0, column=0, sticky='nsew')
    ttk.Separator(orient='horizontal').grid(row=1, column=0, sticky='ew')
    name_label.grid(row=2, column=0)
    frame_preview.grid(row=3, column=0, pady=4)
    canv_all.grid(row=0, column=1, rowspan=4, sticky='nsew')
    scroll.grid(row=0, column=2, rowspan=4, sticky='ns')
    window.columnconfigure(1, weight=1)
    window.rowconfigure(3, weight=1)

    tk_tools.add_mousewheel(canv_all, canv_all, window)

    preview_left = ttk.Label(frame_preview, anchor='e')
    preview_right = ttk.Label(frame_preview, anchor='w')
    img.apply(preview_left, IMG_BLANK)
    img.apply(preview_right, IMG_BLANK)
    preview_left.grid(row=0, column=0)
    preview_right.grid(row=0, column=1)

    try:
        sign_arrow = Signage.by_id('SIGN_ARROW')
    except KeyError:
        LOGGER.warning('No arrow signage defined!')
        sign_arrow = None

    hover_arrow = False
    hover_toggle_id = None
    hover_sign: Optional[Signage] = None

    def hover_toggle() -> None:
        """Toggle between arrows and dual icons."""
        nonlocal hover_arrow, hover_toggle_id
        hover_arrow = not hover_arrow
        if hover_sign is None:
            return
        if hover_arrow and sign_arrow:
            left = hover_sign.dnd_icon
            right = sign_arrow.dnd_icon
        else:
            try:
                left = Signage.by_id(hover_sign.prim_id or '').dnd_icon
            except KeyError:
                left = hover_sign.dnd_icon
            try:
                right = Signage.by_id(hover_sign.sec_id or '').dnd_icon
            except KeyError:
                right = IMG_BLANK
        img.apply(preview_left, left)
        img.apply(preview_right, right)
        hover_toggle_id = TK_ROOT.after(1000, hover_toggle)

    def on_hover(slot: dragdrop.Slot[Signage]) -> None:
        """Show the signage when hovered."""
        nonlocal hover_arrow, hover_sign
        if slot.contents is not None:
            name_label['text'] = gettext('Signage: {}').format(slot.contents.name)
            hover_sign = slot.contents
            hover_arrow = True
            hover_toggle()
        else:
            on_leave(slot)

    def on_leave(slot: dragdrop.Slot[Signage]) -> None:
        """Reset the visible sign when left."""
        nonlocal hover_toggle_id, hover_sign
        name_label['text'] = ''
        hover_sign = None
        if hover_toggle_id is not None:
            TK_ROOT.after_cancel(hover_toggle_id)
            hover_toggle_id = None
        img.apply(preview_left, IMG_BLANK)
        img.apply(preview_right, IMG_BLANK)

    drag_man.reg_callback(dragdrop.Event.HOVER_ENTER, on_hover)
    drag_man.reg_callback(dragdrop.Event.HOVER_EXIT, on_leave)

    for i in range(3, 31):
        SLOTS_SELECTED[i] = slot = drag_man.slot(
            frame_selected,
            source=False,
            label=f'00:{i:02g}'
        )
        row, col = divmod(i-3, 4)
        slot.grid(row=row, column=col, padx=1, pady=1)

        prev_id = DEFAULT_IDS.get(i, '')
        if prev_id:
            try:
                slot.contents = Signage.by_id(prev_id)
            except KeyError:
                LOGGER.warning('Missing sign id: {}', prev_id)

    for sign in sorted(Signage.all(), key=lambda s: s.name):
        if not sign.hidden:
            slot = drag_man.slot(canv_all, source=True)
            slot.contents = sign

    drag_man.flow_slots(canv_all, drag_man.sources())

    canv_all.bind(
        '<Configure>',
        lambda e: drag_man.flow_slots(canv_all, drag_man.sources()),
    )

    def hide_window() -> None:
        """Hide the window."""
        window.withdraw()
        drag_man.unload_icons()
        img.apply(preview_left, IMG_BLANK)
        img.apply(preview_right, IMG_BLANK)

    def show_window() -> None:
        """Show the window."""
        drag_man.load_icons()
        window.deiconify()
        utils.center_win(window, TK_ROOT)

    window.protocol("WM_DELETE_WINDOW", hide_window)
    return ttk.Button(
        master,
        text=gettext('Configure Signage'),
        command=show_window,
    )
Ejemplo n.º 10
0
def _show(widget: tk.Misc, mouse_x, mouse_y) -> None:
    """Show the context window."""
    # noinspection PyUnresolvedReferences, PyProtectedMember
    context_label['text'] = widget._bee2_tooltip_text
    # noinspection PyUnresolvedReferences, PyProtectedMember
    img.apply(context_label, widget._bee2_tooltip_img)

    window.deiconify()
    window.update_idletasks()
    window.lift()

    # We're going to position tooltips towards the center of the main window.
    # That way they don't tend to stick out, even in multi-window setups.

    # To decide where to put the tooltip, we first want the center of the
    # main window.
    x = cent_x = TK_ROOT.winfo_rootx() + TK_ROOT.winfo_width() / 2
    y = cent_y = TK_ROOT.winfo_rooty() + TK_ROOT.winfo_height() / 2

    x_centered = y_centered = True

    # If the widget is smaller than the context window, always center.
    if widget.winfo_width() > window.winfo_width():
        if cent_x > mouse_x + CENT_DIST:
            # Left of center, so place right of the target
            x = widget.winfo_rootx() + widget.winfo_width() + PADDING
            x_centered = False
        elif cent_x < mouse_x - CENT_DIST:
            # Right of center, so place left of the target
            x = widget.winfo_rootx() - window.winfo_width() - PADDING
            x_centered = False

    if widget.winfo_height() > window.winfo_height():
        if cent_y > mouse_y + CENT_DIST:
            # Above center, so place below target
            y = widget.winfo_rooty() + widget.winfo_height() + PADDING
            y_centered = False
        elif cent_y < mouse_y - CENT_DIST:
            # Below center, so place above target
            y = widget.winfo_rooty() - window.winfo_height() - PADDING
            y_centered = False

    if x_centered:  # Center horizontally
        x = (
            widget.winfo_rootx() +
            (widget.winfo_width() - window.winfo_width()) // 2
        )

    if y_centered:
        y = (
            widget.winfo_rooty() +
            (widget.winfo_height() - window.winfo_height()) // 2
        )

        # If both X and Y are centered, the tooltip will appear on top of
        # the mouse and immediately hide. Offset it to fix that.
        if x_centered:
            if mouse_y < cent_y:
                y = widget.winfo_rooty() + widget.winfo_height() + PADDING
            else:
                y = widget.winfo_rooty() - window.winfo_height() - PADDING

    window.geometry('+{}+{}'.format(int(x), int(y)))
Ejemplo n.º 11
0
def init() -> None:
    """Initialise all widgets in the given window."""
    for cat, btn_text in [
            ('back_', gettext('Restore:')),
            ('game_', gettext('Backup:')),
            ]:
        UI[cat + 'frame'] = frame = ttk.Frame(
            window,
        )
        UI[cat + 'title_frame'] = title_frame = ttk.Frame(
            frame,
        )
        title_frame.grid(row=0, column=0, sticky='EW')
        UI[cat + 'title'] = ttk.Label(
            title_frame,
            font='TkHeadingFont',
        )
        UI[cat + 'title'].grid(row=0, column=0)
        title_frame.rowconfigure(0, weight=1)
        title_frame.columnconfigure(0, weight=1)

        UI[cat + 'details'] = CheckDetails(
            frame,
            headers=HEADERS,
        )
        UI[cat + 'details'].grid(row=1, column=0, sticky='NSEW')
        frame.rowconfigure(1, weight=1)
        frame.columnconfigure(0, weight=1)

        button_frame = ttk.Frame(
            frame,
        )
        button_frame.grid(column=0, row=2)
        ttk.Label(button_frame, text=btn_text).grid(row=0, column=0)
        UI[cat + 'btn_all'] = ttk.Button(
            button_frame,
            text='All',
            width=3,
        )
        UI[cat + 'btn_sel'] = ttk.Button(
            button_frame,
            text=gettext('Checked'),
            width=8,
        )
        UI[cat + 'btn_all'].grid(row=0, column=1)
        UI[cat + 'btn_sel'].grid(row=0, column=2)

        UI[cat + 'btn_del'] = ttk.Button(
            button_frame,
            text=gettext('Delete Checked'),
            width=14,
        )
        UI[cat + 'btn_del'].grid(row=1, column=0, columnspan=3)

        tk_tools.add_mousewheel(
            UI[cat + 'details'].wid_canvas,
            UI[cat + 'frame'],
        )

    game_refresh = ttk.Button(
        UI['game_title_frame'],
        command=ui_refresh_game,
    )
    game_refresh.grid(row=0, column=1, sticky='E')
    add_tooltip(
        game_refresh,
        "Reload the map list.",
    )
    img.apply(game_refresh, img.Handle.builtin('icons/tool_sub', 16, 16))

    UI['game_title']['textvariable'] = game_name
    UI['back_title']['textvariable'] = backup_name

    UI['game_btn_all']['command'] = ui_backup_all
    UI['game_btn_sel']['command'] = ui_backup_sel
    UI['game_btn_del']['command'] = ui_delete_game

    UI['back_btn_all']['command'] = ui_restore_all
    UI['back_btn_sel']['command'] = ui_restore_sel
    UI['back_btn_del']['command'] = ui_delete_backup


    UI['back_frame'].grid(row=1, column=0, sticky='NSEW')
    ttk.Separator(orient=tk.VERTICAL).grid(
        row=1, column=1, sticky='NS', padx=5,
    )
    UI['game_frame'].grid(row=1, column=2, sticky='NSEW')

    window.rowconfigure(1, weight=1)
    window.columnconfigure(0, weight=1)
    window.columnconfigure(2, weight=1)
Ejemplo n.º 12
0
def make_tab(group, config: ConfigFile, tab_type):
    """Create all the widgets for a tab."""
    if tab_type is TabTypes.MIDCHAMBER:
        # Mid-chamber voice lines have predefined values.
        group_name = gettext('Mid - Chamber')
        group_id = 'MIDCHAMBER'
        group_desc = gettext('Lines played during the actual chamber, '
                             'after specific events have occurred.')
    elif tab_type is TabTypes.RESPONSE:
        # Note: 'Response' tab header, and description
        group_name = gettext('Responses')
        group_id = None
        group_desc = gettext(
            'Lines played in response to certain events in Coop.')
    elif tab_type is TabTypes.NORM:
        group_name = group['name', 'No Name!']
        group_id = group_name.upper()
        group_desc = group['desc', ''] + ':'
    else:
        raise ValueError('Invalid tab type!')

    # This is just to hold the canvas and scrollbar
    outer_frame = ttk.Frame(UI['tabs'])
    outer_frame.columnconfigure(0, weight=1)
    outer_frame.rowconfigure(0, weight=1)

    TABS[group_name] = outer_frame
    # We add this attribute so the refresh() method knows all the
    # tab names
    outer_frame.nb_text = group_name
    outer_frame.nb_type = tab_type

    # We need a canvas to make the list scrollable.
    canv = Canvas(
        outer_frame,
        highlightthickness=0,
    )
    scroll = tk_tools.HidingScroll(
        outer_frame,
        orient=VERTICAL,
        command=canv.yview,
    )
    canv['yscrollcommand'] = scroll.set
    canv.grid(row=0, column=0, sticky='NSEW')
    scroll.grid(row=0, column=1, sticky='NS')

    UI['tabs'].add(outer_frame)

    # This holds the actual elements
    frame = ttk.Frame(canv, )
    frame.columnconfigure(0, weight=1)
    canv.create_window(0, 0, window=frame, anchor="nw")

    ttk.Label(
        frame,
        text=group_name,
        anchor='center',
        font='tkHeadingFont',
    ).grid(
        row=0,
        column=0,
        sticky='EW',
    )

    ttk.Label(
        frame,
        text=group_desc,
    ).grid(
        row=1,
        column=0,
        sticky='EW',
    )

    ttk.Separator(frame, orient=HORIZONTAL).grid(
        row=2,
        column=0,
        sticky='EW',
    )

    if tab_type is TabTypes.RESPONSE:
        sorted_quotes = sorted(group, key=lambda prop: prop.real_name)
    else:
        sorted_quotes = sorted(
            group.find_all('Quote'),
            key=quote_sort_func,
            reverse=True,
        )

    for quote in sorted_quotes:  # type: Property
        if not quote.has_children():
            continue  # Skip over config commands..

        if tab_type is TabTypes.RESPONSE:
            try:
                name = RESPONSE_NAMES[quote.name]
            except KeyError:
                # Convert channels of the form 'death_goo' into 'Death - Goo'.
                channel, ch_arg = quote.name.split('_', 1)
                name = channel.title() + ' - ' + ch_arg.title()
                del channel, ch_arg

            group_id = quote.name
        else:
            # note: default for quote names
            name = quote['name', gettext('No Name!')]

        ttk.Label(
            frame,
            text=name,
            font=QUOTE_FONT,
        ).grid(
            column=0,
            sticky=W,
        )

        if tab_type is TabTypes.RESPONSE:
            line_iter = find_resp_lines(quote)
        else:
            line_iter = find_lines(quote)

        for badges, line, line_id in line_iter:
            line_frame = ttk.Frame(frame, )
            line_frame.grid(
                column=0,
                padx=(10, 0),
                sticky=W,
            )
            for x, (img_handle, ctx) in enumerate(badges):
                label = ttk.Label(line_frame, padding=0)
                img.apply(label, img_handle)
                label.grid(row=0, column=x)
                add_tooltip(label, ctx)

            line_frame.columnconfigure(len(badges), weight=1)

            check = ttk.Checkbutton(
                line_frame,
                # note: default voice line name next to checkbox.
                text=line['name', gettext('No Name?')],
            )

            check.quote_var = IntVar(value=config.get_bool(
                group_id, line_id, True), )

            check['variable'] = check.quote_var

            check['command'] = functools.partial(
                check_toggled,
                var=check.quote_var,
                config_section=config[group_id],
                quote_id=line_id,
            )
            check.transcript = list(get_trans_lines(line))
            check.grid(
                row=0,
                column=len(badges),
            )
            check.bind("<Enter>", show_trans)

    def configure_canv(e):
        """Allow resizing the windows."""
        canv['scrollregion'] = (
            4,
            0,
            canv.winfo_reqwidth(),
            frame.winfo_reqheight(),
        )
        frame['width'] = canv.winfo_reqwidth()

    canv.bind('<Configure>', configure_canv)

    return outer_frame