diff --git a/src/IRCPanel.py b/src/IRCPanel.py index 197eb43..11dc1a0 100644 --- a/src/IRCPanel.py +++ b/src/IRCPanel.py @@ -1,4 +1,4 @@ -import wx +import wx , wx.adv import threading import logging from SearchDialog import SearchDialog @@ -396,34 +396,67 @@ class IRCPanel(wx.Panel): logger.error(f"Error inserting text at caret: {e}") def on_pick_kaomoji(self, event): - """Show a kaomoji popup next to the button and insert the chosen one. - - All entries are ASCII-only so they are safe for any IRC server. + """ + Show a grouped kaomoji popup next to the button and insert the chosen one. """ try: - choices = [ - ":-)", ":)", ":-D", ":D", "^_^", "^o^", "(*^_^*)", "( ^_^)/", "(:3)", "=)", "=]", "^.^", - ":-(", ":'(", "T_T", ";_;", ">_<", "(-_-)", "(_ _)", - ">:(", ">:-(", ">:-O", ">.<", "(-_-)#", - "<3", "(*^3^)", "(^^)v", "(X_X)", "(^_^)", "*^_^*", - ":-O", ":O", ":-0", "O_O", "o_O", "O_o", - "-_-", "(-.-) zzz", "(~_~)", "zzz", - r"¯\_(._.)_/¯", "(¬_¬)", "(*_*)", "(>_>)", "(<_<)", - "OwO", "UwU", ">w<", "^w^", "^u^", "rawr x3", ":3", ":3c", "x3", "nya~", "n_n", "(>ω<)", ":33", "^3^", - "^///^", "(//▽//)", "(*^///^*)", ">///<", "^_^;", "^///^;", - "(*^▽^*)", "(*´▽`*)", "UwU~", "OwO~", - ":33", "x3", ":3~", ":3c", "owo", "uwu", "rawr", ":33p", - "xD", ";-)", ";)", ":-P", ":P", ":-|", ":|", "(o_O)", "(O_o)", "('_')", - ] + kaomoji_groups = { + "Happy": [ + ":-)", ":)", ":-D", ":D", "^_^", "^o^", "(*^_^*)", "( ^_^)/", "(:3)", "=)", "=]", "^.^", + "UwU", "OwO", "^w^", "^u^", "x3", ":3", ":3c", "nya~", "n_n", "(>ω<)", ":33", "^3^" + ], + "Sad": [ + ":-(", ":'(", "T_T", ";_;", ">_<", "(-_-)", "(_ _)", + ], + "Angry": [ + ">:(", ">:-(", ">:-O", ">.<", "(-_-)#" + ], + "Love": [ + "<3", "(*^3^)", "(^^)v", "(^_^)", "*^_^*" + ], + "Surprised / Misc": [ + ":-O", ":O", ":-0", "O_O", "o_O", "O_o" + ], + "Sleepy | Bored": [ + "-_-", "(-.-) zzz", "(~_~)", "zzz" + ], + "Gay": [ + r"¯\_(._.)_/¯", "(¬_¬)", "(*_*)", "(>_>)", "(<_<)", "(o_O)", "(O_o)", "('_')", + "(//▽//)", "(*^///^*)", ">///<", "^_^;", "^///^;", "owo", "uwu", "rawr", ":33p" + ], + "Blush": [ + "^///^", "(//▽//)", "(*^///^*)", ">///<", "^_^;", "^///^;", + "(*^▽^*)", "(*´▽`*)" + ], + "Others": [ + "UwU~", "OwO~", ":33", "x3", ":3~", ":3c", "rawr x3", "xD", ";-)", ";)", ":-P", ":P", ":-|", ":|" + ] + } - ascii_choices = [c for c in choices if all(ord(ch) < 128 for ch in c)] - if not ascii_choices: - ascii_choices = [":)", ":D", ";)", ":P"] + display_items = [] + item_lookup = [] # tuple: (group, index in group) or None for separators/labels + + # Helper for ASCII filtering + def is_ascii(s): + return all(ord(ch) < 128 for ch in s) + + for group, choices in kaomoji_groups.items(): + group_ascii_choices = [c for c in choices if is_ascii(c)] + if not group_ascii_choices: + continue + + display_items.append(f"──────────── {group} ────────────") + item_lookup.append(None) # None means not selectable + for idx, choice in enumerate(group_ascii_choices): + display_items.append(f" {choice}") + item_lookup.append((group, idx)) popup = wx.PopupTransientWindow(self, wx.BORDER_SIMPLE) panel = wx.Panel(popup) sizer = wx.BoxSizer(wx.VERTICAL) - listbox = wx.ListBox(panel, choices=ascii_choices, style=wx.LB_SINGLE) + listbox = wx.ListBox( + panel, choices=display_items, style=wx.LB_SINGLE + ) sizer.Add(listbox, 1, wx.EXPAND | wx.ALL, 4) panel.SetSizerAndFit(sizer) popup.SetClientSize(panel.GetBestSize()) @@ -431,6 +464,23 @@ class IRCPanel(wx.Panel): # Keep a reference so the popup isn't GC'd self._kaomoji_popup = popup + def get_kaomoji_by_index(list_idx): + """Given listbox index, return the chosen kaomoji str or None.""" + lookup = item_lookup[list_idx] + if not lookup: + return None + group, group_idx = lookup + if group: + choices = [c for c in kaomoji_groups[group] if is_ascii(c)] + return choices[group_idx] + else: + # Fallback: old style flat + return display_items[list_idx].strip() + + def is_selectable(list_idx): + """Whether this list index is a real choice (not a separator/label).""" + return item_lookup[list_idx] is not None + def on_select(evt): """Handle selection from the kaomoji list (keyboard or programmatic).""" # Ignore synthetic selection changes triggered only for hover visualization @@ -444,10 +494,9 @@ class IRCPanel(wx.Panel): idx = evt.GetInt() if hasattr(evt, "GetInt") else -1 try: - if 0 <= idx < len(ascii_choices): - choice = ascii_choices[idx] + if idx is not None and is_selectable(idx): + choice = get_kaomoji_by_index(idx) if choice: - # Insert at current caret position, but DO NOT auto-send. current = self.input_ctrl.GetValue() pos = self.input_ctrl.GetInsertionPoint() needs_space = pos > 0 and not current[pos - 1].isspace() @@ -463,7 +512,7 @@ class IRCPanel(wx.Panel): try: pos = evt.GetPosition() idx = listbox.HitTest(pos) - if idx != wx.NOT_FOUND: + if idx != wx.NOT_FOUND and is_selectable(idx): # Ensure the item is selected, then reuse on_select logic listbox.SetSelection(idx) cmd_evt = wx.CommandEvent(wx.wxEVT_LISTBOX, listbox.GetId()) @@ -481,19 +530,19 @@ class IRCPanel(wx.Panel): pos = evt.GetPosition() idx = listbox.HitTest(pos) current_sel = listbox.GetSelection() - - if idx != wx.NOT_FOUND and idx != current_sel: + if idx != wx.NOT_FOUND and is_selectable(idx) and idx != current_sel: # Temporarily suppress on_select so hover highlight doesn't send self._suppress_kaomoji_select = True try: listbox.SetSelection(idx) finally: self._suppress_kaomoji_select = False - elif idx == wx.NOT_FOUND: - # Optionally clear selection when hovering outside items + elif idx == wx.NOT_FOUND or not is_selectable(idx): + # Optionally clear selection when hovering outside items or on label row self._suppress_kaomoji_select = True try: - listbox.DeselectAll() + # wx.ListBox does not have DeselectAll. Use SetSelection(-1) to clear selection. + listbox.SetSelection(wx.NOT_FOUND) finally: self._suppress_kaomoji_select = False except Exception as e: @@ -506,10 +555,13 @@ class IRCPanel(wx.Panel): listbox.Bind(wx.EVT_LEFT_DOWN, on_left_click) listbox.Bind(wx.EVT_MOTION, on_motion) + def on_draw_item(event): + pass + # Position popup under the kaomoji button btn = self.kaomoji_btn btn_pos = btn.ClientToScreen((0, btn.GetSize().height)) popup.Position(btn_pos, (0, 0)) popup.Popup() except Exception as e: - logger.error(f"Error in kaomoji picker: {e}") \ No newline at end of file + logger.error(f"Error in kaomoji picker: {e}")