update to be better ; still have to fix the scrolling on windows
This commit is contained in:
153
src/IRCPanel.py
153
src/IRCPanel.py
@@ -1,4 +1,6 @@
|
|||||||
import wx , wx.adv
|
import wx
|
||||||
|
import wx.adv
|
||||||
|
import random
|
||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
from SearchDialog import SearchDialog
|
from SearchDialog import SearchDialog
|
||||||
@@ -19,8 +21,7 @@ class IRCPanel(wx.Panel):
|
|||||||
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
# Use a better font for chat with white theme
|
self.text_ctrl = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_AUTO_URL)
|
||||||
self.text_ctrl = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_AUTO_URL)
|
|
||||||
|
|
||||||
if self.theme:
|
if self.theme:
|
||||||
self.text_ctrl.SetBackgroundColour(self.theme["content_bg"])
|
self.text_ctrl.SetBackgroundColour(self.theme["content_bg"])
|
||||||
@@ -39,9 +40,44 @@ class IRCPanel(wx.Panel):
|
|||||||
self.input_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_send)
|
self.input_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_send)
|
||||||
self.input_ctrl.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
|
self.input_ctrl.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
|
||||||
|
|
||||||
# Kaomoji picker button - inserts plain ASCII kaomojis into the input box
|
self.kaomoji_groups = {
|
||||||
self.kaomoji_btn = wx.Button(self, label="Emotes")
|
"Happy": [
|
||||||
self.kaomoji_btn.SetToolTip("Emotes :3")
|
":-)", ":)", ":-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", ":-|", ":|"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
emote_random = random.choice(list(self.kaomoji_groups.values())) # Get a random value ( returns a list and looks like a value of the dict in the code ; )
|
||||||
|
emote_random = str(emote_random).strip('[]').split(', ')[random.randint(0, len(emote_random)-1)].strip("'") # Do some formatting for the strings, since they are stored in lists
|
||||||
|
|
||||||
|
self.kaomoji_btn = wx.Button(self, label=f"{emote_random}")
|
||||||
|
self.kaomoji_btn.SetToolTip(f"Kaomojis {emote_random} ")
|
||||||
self.kaomoji_btn.Bind(wx.EVT_BUTTON, self.on_pick_kaomoji)
|
self.kaomoji_btn.Bind(wx.EVT_BUTTON, self.on_pick_kaomoji)
|
||||||
|
|
||||||
send_btn = wx.Button(self, label="Send")
|
send_btn = wx.Button(self, label="Send")
|
||||||
@@ -116,7 +152,7 @@ class IRCPanel(wx.Panel):
|
|||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
def add_message(self, message, username=None, username_color=None, message_color=None, bold=False, italic=False, underline=False):
|
def add_message(self, message, username=None, username_color=None, message_color=None, bold=False, italic=False, underline=False):
|
||||||
"""Thread-safe message addition with username coloring"""
|
"""Thread-safe message addition - plain text only for pixel-perfect scrolling"""
|
||||||
try:
|
try:
|
||||||
# Use CallAfter for thread safety
|
# Use CallAfter for thread safety
|
||||||
if wx.IsMainThread():
|
if wx.IsMainThread():
|
||||||
@@ -127,44 +163,13 @@ class IRCPanel(wx.Panel):
|
|||||||
logger.error(f"Error in add_message: {e}")
|
logger.error(f"Error in add_message: {e}")
|
||||||
|
|
||||||
def _add_message_safe(self, message, username=None, username_color=None, message_color=None, bold=False, italic=False, underline=False):
|
def _add_message_safe(self, message, username=None, username_color=None, message_color=None, bold=False, italic=False, underline=False):
|
||||||
"""Actually add message - must be called from main thread"""
|
"""Actually add message - must be called from main thread
|
||||||
|
Note: Without TE_RICH2, color/formatting is not supported, but scrolling is pixel-perfect"""
|
||||||
try:
|
try:
|
||||||
self.messages.append(message)
|
self.messages.append(message)
|
||||||
|
|
||||||
# Save current position for formatting
|
# Simple append without formatting (pixel-perfect scrolling)
|
||||||
start_pos = self.text_ctrl.GetLastPosition()
|
self.text_ctrl.AppendText(message + "\n")
|
||||||
|
|
||||||
if username and username_color:
|
|
||||||
# Add username with its color
|
|
||||||
attr = wx.TextAttr()
|
|
||||||
attr.SetTextColour(username_color)
|
|
||||||
if bold:
|
|
||||||
attr.SetFontWeight(wx.FONTWEIGHT_BOLD)
|
|
||||||
if italic:
|
|
||||||
attr.SetFontStyle(wx.FONTSTYLE_ITALIC)
|
|
||||||
if underline:
|
|
||||||
attr.SetFontUnderlined(True)
|
|
||||||
attr.SetFont(self.font)
|
|
||||||
self.text_ctrl.SetDefaultStyle(attr)
|
|
||||||
self.text_ctrl.AppendText(username)
|
|
||||||
|
|
||||||
# Add the rest of the message with message color
|
|
||||||
attr = wx.TextAttr()
|
|
||||||
if message_color:
|
|
||||||
attr.SetTextColour(message_color)
|
|
||||||
else:
|
|
||||||
attr.SetTextColour(self.default_text_colour)
|
|
||||||
|
|
||||||
attr.SetFont(self.font)
|
|
||||||
self.text_ctrl.SetDefaultStyle(attr)
|
|
||||||
|
|
||||||
# Append the message (without username if we already added it)
|
|
||||||
if username and username_color:
|
|
||||||
# Find the message part after username
|
|
||||||
message_text = message[message.find(username) + len(username):]
|
|
||||||
self.text_ctrl.AppendText(message_text + "\n")
|
|
||||||
else:
|
|
||||||
self.text_ctrl.AppendText(message + "\n")
|
|
||||||
|
|
||||||
# Auto-scroll to bottom
|
# Auto-scroll to bottom
|
||||||
self.text_ctrl.ShowPosition(self.text_ctrl.GetLastPosition())
|
self.text_ctrl.ShowPosition(self.text_ctrl.GetLastPosition())
|
||||||
@@ -172,23 +177,20 @@ class IRCPanel(wx.Panel):
|
|||||||
logger.error(f"Error adding message safely: {e}")
|
logger.error(f"Error adding message safely: {e}")
|
||||||
|
|
||||||
def add_formatted_message(self, timestamp, username, content, username_color=None, is_action=False):
|
def add_formatted_message(self, timestamp, username, content, username_color=None, is_action=False):
|
||||||
"""Add a formatted message with colored username"""
|
"""Add a formatted message (plain text only for pixel-perfect scrolling)"""
|
||||||
try:
|
try:
|
||||||
if is_action:
|
if is_action:
|
||||||
message = f"{timestamp}* {username} {content}"
|
message = f"{timestamp}* {username} {content}"
|
||||||
self.add_message(message, f"* {username}", username_color, wx.Colour(128, 0, 128), italic=True) # Dark purple for actions
|
|
||||||
else:
|
else:
|
||||||
message = f"{timestamp}<{username}> {content}"
|
message = f"{timestamp}<{username}> {content}"
|
||||||
self.add_message(message, f"<{username}>", username_color, self.default_text_colour)
|
self.add_message(message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding formatted message: {e}")
|
logger.error(f"Error adding formatted message: {e}")
|
||||||
|
|
||||||
def add_system_message(self, message, color=None, bold=False):
|
def add_system_message(self, message, color=None, bold=False):
|
||||||
"""Add system message without username coloring"""
|
"""Add system message (plain text only for pixel-perfect scrolling)"""
|
||||||
try:
|
try:
|
||||||
if color is None:
|
self.add_message(message)
|
||||||
color = wx.Colour(0, 0, 128) # Dark blue for system messages
|
|
||||||
self.add_message(message, None, None, color, bold, False, False)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding system message: {e}")
|
logger.error(f"Error adding system message: {e}")
|
||||||
|
|
||||||
@@ -222,20 +224,16 @@ class IRCPanel(wx.Panel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Prepare search parameters
|
# Prepare search parameters
|
||||||
search_flags = 0
|
|
||||||
if not case_sensitive:
|
if not case_sensitive:
|
||||||
# For manual search, we'll handle case sensitivity ourselves
|
|
||||||
search_text_lower = search_text.lower()
|
search_text_lower = search_text.lower()
|
||||||
full_text_lower = full_text.lower()
|
full_text_lower = full_text.lower()
|
||||||
|
|
||||||
# Manual search implementation since wx.TextCtrl doesn't have FindText
|
# Manual search implementation
|
||||||
pos = 0
|
pos = 0
|
||||||
while pos < len(full_text):
|
while pos < len(full_text):
|
||||||
if case_sensitive:
|
if case_sensitive:
|
||||||
# Case sensitive search
|
|
||||||
found_pos = full_text.find(search_text, pos)
|
found_pos = full_text.find(search_text, pos)
|
||||||
else:
|
else:
|
||||||
# Case insensitive search
|
|
||||||
found_pos = full_text_lower.find(search_text_lower, pos)
|
found_pos = full_text_lower.find(search_text_lower, pos)
|
||||||
|
|
||||||
if found_pos == -1:
|
if found_pos == -1:
|
||||||
@@ -243,14 +241,13 @@ class IRCPanel(wx.Panel):
|
|||||||
|
|
||||||
# For whole word search, verify boundaries
|
# For whole word search, verify boundaries
|
||||||
if whole_word:
|
if whole_word:
|
||||||
# Check if it's a whole word
|
|
||||||
is_word_start = (found_pos == 0 or not full_text[found_pos-1].isalnum())
|
is_word_start = (found_pos == 0 or not full_text[found_pos-1].isalnum())
|
||||||
is_word_end = (found_pos + len(search_text) >= len(full_text) or
|
is_word_end = (found_pos + len(search_text) >= len(full_text) or
|
||||||
not full_text[found_pos + len(search_text)].isalnum())
|
not full_text[found_pos + len(search_text)].isalnum())
|
||||||
|
|
||||||
if is_word_start and is_word_end:
|
if is_word_start and is_word_end:
|
||||||
self.search_positions.append(found_pos)
|
self.search_positions.append(found_pos)
|
||||||
pos = found_pos + 1 # Move forward to avoid infinite loop
|
pos = found_pos + 1
|
||||||
else:
|
else:
|
||||||
self.search_positions.append(found_pos)
|
self.search_positions.append(found_pos)
|
||||||
pos = found_pos + len(search_text)
|
pos = found_pos + len(search_text)
|
||||||
@@ -400,38 +397,7 @@ class IRCPanel(wx.Panel):
|
|||||||
Show a grouped 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.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
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", ":-|", ":|"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
display_items = []
|
display_items = []
|
||||||
item_lookup = [] # tuple: (group, index in group) or None for separators/labels
|
item_lookup = [] # tuple: (group, index in group) or None for separators/labels
|
||||||
@@ -440,7 +406,7 @@ class IRCPanel(wx.Panel):
|
|||||||
def is_ascii(s):
|
def is_ascii(s):
|
||||||
return all(ord(ch) < 128 for ch in s)
|
return all(ord(ch) < 128 for ch in s)
|
||||||
|
|
||||||
for group, choices in kaomoji_groups.items():
|
for group, choices in self.kaomoji_groups.items():
|
||||||
group_ascii_choices = [c for c in choices if is_ascii(c)]
|
group_ascii_choices = [c for c in choices if is_ascii(c)]
|
||||||
if not group_ascii_choices:
|
if not group_ascii_choices:
|
||||||
continue
|
continue
|
||||||
@@ -451,11 +417,12 @@ class IRCPanel(wx.Panel):
|
|||||||
display_items.append(f" {choice}")
|
display_items.append(f" {choice}")
|
||||||
item_lookup.append((group, idx))
|
item_lookup.append((group, idx))
|
||||||
|
|
||||||
popup = wx.PopupTransientWindow(self, wx.BORDER_SIMPLE)
|
popup = wx.PopupTransientWindow(self, wx.BORDER_SUNKEN)
|
||||||
|
|
||||||
panel = wx.Panel(popup)
|
panel = wx.Panel(popup)
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
listbox = wx.ListBox(
|
listbox = wx.ListBox(
|
||||||
panel, choices=display_items, style=wx.LB_SINGLE
|
panel, choices=display_items, style=wx.LB_SINGLE | wx.BORDER_SUNKEN
|
||||||
)
|
)
|
||||||
sizer.Add(listbox, 1, wx.EXPAND | wx.ALL, 4)
|
sizer.Add(listbox, 1, wx.EXPAND | wx.ALL, 4)
|
||||||
panel.SetSizerAndFit(sizer)
|
panel.SetSizerAndFit(sizer)
|
||||||
@@ -471,7 +438,7 @@ class IRCPanel(wx.Panel):
|
|||||||
return None
|
return None
|
||||||
group, group_idx = lookup
|
group, group_idx = lookup
|
||||||
if group:
|
if group:
|
||||||
choices = [c for c in kaomoji_groups[group] if is_ascii(c)]
|
choices = [c for c in self.kaomoji_groups[group] if is_ascii(c)]
|
||||||
return choices[group_idx]
|
return choices[group_idx]
|
||||||
else:
|
else:
|
||||||
# Fallback: old style flat
|
# Fallback: old style flat
|
||||||
@@ -505,7 +472,7 @@ class IRCPanel(wx.Panel):
|
|||||||
self.input_ctrl.SetValue(new_value)
|
self.input_ctrl.SetValue(new_value)
|
||||||
self.input_ctrl.SetInsertionPoint(pos + len(insert_text))
|
self.input_ctrl.SetInsertionPoint(pos + len(insert_text))
|
||||||
finally:
|
finally:
|
||||||
popup.Dismiss()
|
popup.Destroy() # Linux did not like dismiss, so we just destroy it. it works better
|
||||||
|
|
||||||
def on_left_click(evt):
|
def on_left_click(evt):
|
||||||
"""Single left-click handler for the kaomoji menu."""
|
"""Single left-click handler for the kaomoji menu."""
|
||||||
|
|||||||
Reference in New Issue
Block a user