update the kaomoji / emote selector
This commit is contained in:
106
src/IRCPanel.py
106
src/IRCPanel.py
@@ -1,4 +1,4 @@
|
|||||||
import wx
|
import wx , wx.adv
|
||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
from SearchDialog import SearchDialog
|
from SearchDialog import SearchDialog
|
||||||
@@ -396,34 +396,67 @@ class IRCPanel(wx.Panel):
|
|||||||
logger.error(f"Error inserting text at caret: {e}")
|
logger.error(f"Error inserting text at caret: {e}")
|
||||||
|
|
||||||
def on_pick_kaomoji(self, event):
|
def on_pick_kaomoji(self, event):
|
||||||
"""Show a kaomoji popup next to the button and insert the chosen one.
|
"""
|
||||||
|
Show a grouped kaomoji popup next to the button and insert the chosen one.
|
||||||
All entries are ASCII-only so they are safe for any IRC server.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
choices = [
|
kaomoji_groups = {
|
||||||
|
"Happy": [
|
||||||
":-)", ":)", ":-D", ":D", "^_^", "^o^", "(*^_^*)", "( ^_^)/", "(:3)", "=)", "=]", "^.^",
|
":-)", ":)", ":-D", ":D", "^_^", "^o^", "(*^_^*)", "( ^_^)/", "(:3)", "=)", "=]", "^.^",
|
||||||
|
"UwU", "OwO", "^w^", "^u^", "x3", ":3", ":3c", "nya~", "n_n", "(>ω<)", ":33", "^3^"
|
||||||
|
],
|
||||||
|
"Sad": [
|
||||||
":-(", ":'(", "T_T", ";_;", ">_<", "(-_-)", "(_ _)",
|
":-(", ":'(", "T_T", ";_;", ">_<", "(-_-)", "(_ _)",
|
||||||
">:(", ">:-(", ">:-O", ">.<", "(-_-)#",
|
],
|
||||||
"<3", "(*^3^)", "(^^)v", "(X_X)", "(^_^)", "*^_^*",
|
"Angry": [
|
||||||
":-O", ":O", ":-0", "O_O", "o_O", "O_o",
|
">:(", ">:-(", ">:-O", ">.<", "(-_-)#"
|
||||||
"-_-", "(-.-) zzz", "(~_~)", "zzz",
|
],
|
||||||
r"¯\_(._.)_/¯", "(¬_¬)", "(*_*)", "(>_>)", "(<_<)",
|
"Love": [
|
||||||
"OwO", "UwU", ">w<", "^w^", "^u^", "rawr x3", ":3", ":3c", "x3", "nya~", "n_n", "(>ω<)", ":33", "^3^",
|
"<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": [
|
||||||
"^///^", "(//▽//)", "(*^///^*)", ">///<", "^_^;", "^///^;",
|
"^///^", "(//▽//)", "(*^///^*)", ">///<", "^_^;", "^///^;",
|
||||||
"(*^▽^*)", "(*´▽`*)", "UwU~", "OwO~",
|
"(*^▽^*)", "(*´▽`*)"
|
||||||
":33", "x3", ":3~", ":3c", "owo", "uwu", "rawr", ":33p",
|
],
|
||||||
"xD", ";-)", ";)", ":-P", ":P", ":-|", ":|", "(o_O)", "(O_o)", "('_')",
|
"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)]
|
display_items = []
|
||||||
if not ascii_choices:
|
item_lookup = [] # tuple: (group, index in group) or None for separators/labels
|
||||||
ascii_choices = [":)", ":D", ";)", ":P"]
|
|
||||||
|
# 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)
|
popup = wx.PopupTransientWindow(self, wx.BORDER_SIMPLE)
|
||||||
panel = wx.Panel(popup)
|
panel = wx.Panel(popup)
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
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)
|
sizer.Add(listbox, 1, wx.EXPAND | wx.ALL, 4)
|
||||||
panel.SetSizerAndFit(sizer)
|
panel.SetSizerAndFit(sizer)
|
||||||
popup.SetClientSize(panel.GetBestSize())
|
popup.SetClientSize(panel.GetBestSize())
|
||||||
@@ -431,6 +464,23 @@ class IRCPanel(wx.Panel):
|
|||||||
# Keep a reference so the popup isn't GC'd
|
# Keep a reference so the popup isn't GC'd
|
||||||
self._kaomoji_popup = popup
|
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):
|
def on_select(evt):
|
||||||
"""Handle selection from the kaomoji list (keyboard or programmatic)."""
|
"""Handle selection from the kaomoji list (keyboard or programmatic)."""
|
||||||
# Ignore synthetic selection changes triggered only for hover visualization
|
# 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
|
idx = evt.GetInt() if hasattr(evt, "GetInt") else -1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if 0 <= idx < len(ascii_choices):
|
if idx is not None and is_selectable(idx):
|
||||||
choice = ascii_choices[idx]
|
choice = get_kaomoji_by_index(idx)
|
||||||
if choice:
|
if choice:
|
||||||
# Insert at current caret position, but DO NOT auto-send.
|
|
||||||
current = self.input_ctrl.GetValue()
|
current = self.input_ctrl.GetValue()
|
||||||
pos = self.input_ctrl.GetInsertionPoint()
|
pos = self.input_ctrl.GetInsertionPoint()
|
||||||
needs_space = pos > 0 and not current[pos - 1].isspace()
|
needs_space = pos > 0 and not current[pos - 1].isspace()
|
||||||
@@ -463,7 +512,7 @@ class IRCPanel(wx.Panel):
|
|||||||
try:
|
try:
|
||||||
pos = evt.GetPosition()
|
pos = evt.GetPosition()
|
||||||
idx = listbox.HitTest(pos)
|
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
|
# Ensure the item is selected, then reuse on_select logic
|
||||||
listbox.SetSelection(idx)
|
listbox.SetSelection(idx)
|
||||||
cmd_evt = wx.CommandEvent(wx.wxEVT_LISTBOX, listbox.GetId())
|
cmd_evt = wx.CommandEvent(wx.wxEVT_LISTBOX, listbox.GetId())
|
||||||
@@ -481,19 +530,19 @@ class IRCPanel(wx.Panel):
|
|||||||
pos = evt.GetPosition()
|
pos = evt.GetPosition()
|
||||||
idx = listbox.HitTest(pos)
|
idx = listbox.HitTest(pos)
|
||||||
current_sel = listbox.GetSelection()
|
current_sel = listbox.GetSelection()
|
||||||
|
if idx != wx.NOT_FOUND and is_selectable(idx) and idx != current_sel:
|
||||||
if idx != wx.NOT_FOUND and idx != current_sel:
|
|
||||||
# Temporarily suppress on_select so hover highlight doesn't send
|
# Temporarily suppress on_select so hover highlight doesn't send
|
||||||
self._suppress_kaomoji_select = True
|
self._suppress_kaomoji_select = True
|
||||||
try:
|
try:
|
||||||
listbox.SetSelection(idx)
|
listbox.SetSelection(idx)
|
||||||
finally:
|
finally:
|
||||||
self._suppress_kaomoji_select = False
|
self._suppress_kaomoji_select = False
|
||||||
elif idx == wx.NOT_FOUND:
|
elif idx == wx.NOT_FOUND or not is_selectable(idx):
|
||||||
# Optionally clear selection when hovering outside items
|
# Optionally clear selection when hovering outside items or on label row
|
||||||
self._suppress_kaomoji_select = True
|
self._suppress_kaomoji_select = True
|
||||||
try:
|
try:
|
||||||
listbox.DeselectAll()
|
# wx.ListBox does not have DeselectAll. Use SetSelection(-1) to clear selection.
|
||||||
|
listbox.SetSelection(wx.NOT_FOUND)
|
||||||
finally:
|
finally:
|
||||||
self._suppress_kaomoji_select = False
|
self._suppress_kaomoji_select = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -506,6 +555,9 @@ class IRCPanel(wx.Panel):
|
|||||||
listbox.Bind(wx.EVT_LEFT_DOWN, on_left_click)
|
listbox.Bind(wx.EVT_LEFT_DOWN, on_left_click)
|
||||||
listbox.Bind(wx.EVT_MOTION, on_motion)
|
listbox.Bind(wx.EVT_MOTION, on_motion)
|
||||||
|
|
||||||
|
def on_draw_item(event):
|
||||||
|
pass
|
||||||
|
|
||||||
# Position popup under the kaomoji button
|
# Position popup under the kaomoji button
|
||||||
btn = self.kaomoji_btn
|
btn = self.kaomoji_btn
|
||||||
btn_pos = btn.ClientToScreen((0, btn.GetSize().height))
|
btn_pos = btn.ClientToScreen((0, btn.GetSize().height))
|
||||||
|
|||||||
Reference in New Issue
Block a user