# chat.py # # Copyright 2024 Lucas Fryzek # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # SPDX-License-Identifier: GPL-3.0-or-later from gi.repository import Adw, Gtk, Gio, GObject, GLib from .message import WeegtkMessage from .color import Color from weegtk import config from weegtk import netfile import json @Gtk.Template(resource_path='/com/fryzekconcepts/weegtk/gtk/chat.ui') class WeegtkChat(Adw.Bin): __gtype_name__ = 'WeegtkChat' __gsignals__ = { "buffer_input" : ( GObject.SignalFlags.RUN_FIRST, None, (str, str) ) } window = Gtk.Template.Child() messages = Gtk.Template.Child() text_entry = Gtk.Template.Child() scroll_button_revealer = Gtk.Template.Child() def __init__(self, data=None, **kwargs): super().__init__(**kwargs) self.data = data or {} self.model = Gtk.StringList() self.select = Gtk.NoSelection(model=self.model) self.factory = Gtk.SignalListItemFactory() self.factory.connect("setup", self.setup_list_item) self.factory.connect("bind", self.bind_list_item) self.messages.set_model(self.select) self.messages.set_factory(self.factory) self.color = Color(config.color_options(), False) self.auto_scroll = True self.set_sticky(True) adj = self.window.get_vadjustment() adj.connect("value-changed", self.scroll_changes) adj.connect("notify::upper", self.upper_notify) # TODO figure out why style is not being taken from ui file self.add_css_class("view") def is_at_bottom(self): adj = self.window.get_vadjustment() return (adj.get_value() + adj.get_page_size()) == adj.get_upper() def set_sticky(self, is_sticky): if not is_sticky: self.scroll_button_revealer.set_visible(True) self.scroll_button_revealer.set_reveal_child(not is_sticky) self.sticky = is_sticky def scroll_changes(self, *args): is_at_bottom = self.is_at_bottom() if self.auto_scroll: if is_at_bottom: self.auto_scroll = False self.set_sticky(True) else: self.scroll_bottom() else: self.set_sticky(is_at_bottom) def upper_notify(self, *args): if self.sticky: self.scroll_bottom() @Gtk.Template.Callback() def scroll_bottom(self, *args): n_items = self.model.get_n_items() self.auto_scroll = True if n_items > 0: self.messages.scroll_to(n_items - 1, Gtk.ListScrollFlags.FOCUS) self.window.emit("scroll_child", Gtk.ScrollType.END, False) def update_prompt(self): # TODO code copied from QWeechat, figure out what I should do with it pass def setup_list_item(self, factory, list_item, *user_data): message = WeegtkMessage() list_item.set_child(message) pass def bind_list_item(self, factory, list_item, *user_data): text = list_item.get_item().get_string() data = json.loads(text) message = list_item.get_child() message.set_contents(data) def is_chat(self): buf_type = self.data['local_variables']['type'] if 'type' in self.data['local_variables'] else None return buf_type is not None and (buf_type == "private" or buf_type == "channel") def pointer(self): """Return pointer on buffer.""" return self.data.get('__path', [''])[0] def parse_out_colors(self, text): clean_text = self.color.convert(text) return clean_text def display(self, time, prefix, text, forcecolor=None): # TODO using JSON here seems a bit ineffcient # See if its possible to make a model that uses # our own custom struct user = self.parse_out_colors(prefix) msg = self.parse_out_colors(text) count = self.model.get_n_items() msg_type = "message" # TODO figure out if there is a way to check if a message is from the # the system instead of from a user if (len(user) == 0 or user[0] == "=" or user[0] == "-" or user[0] == "[" or user[0] == "<"): user = f"{self.data['short_name']} {user}" msg_type = "system" if count != 0: last = self.model.get_string(count - 1) last_data = json.loads(last) if last_data["username"] == user: last_data["text"].append(msg) self.model.splice(count - 1, 1, [json.dumps(last_data)]) return data = { "username": user, "text": [msg], "type": msg_type } self.model.append(json.dumps(data)) @Gtk.Template.Callback() def entry_activate(self, *args): entry_buffer = self.text_entry.get_buffer() text = entry_buffer.get_text() entry_buffer.set_text("", 0) self.emit("buffer_input", self.data['full_name'], text) def open_file_dialog(self, dialog, result, caller): try: file = dialog.open_finish(result) except GLib.GError: # gtk-dialog-error-quark: Dismissed by user pass else: conf = config.read() file_url = netfile.upload(file.get_path(), conf["upload"]["url"]) entry_buffer = self.text_entry.get_buffer() entry_buffer.insert_text(self.text_entry.get_position(), file_url, -1) @Gtk.Template.Callback() def attach_file(self, *args): dialog = Gtk.FileDialog() dialog.set_title("Select file to upload") dialog.open(self.get_root(), None, self.open_file_dialog, self)