Plugins providing conversation items for ConversationView
This commit is contained in:
parent
a807ded65c
commit
8bc0d107e7
|
@ -16,8 +16,8 @@ SOURCES
|
||||||
src/dbus/upower.vala
|
src/dbus/upower.vala
|
||||||
|
|
||||||
src/entity/account.vala
|
src/entity/account.vala
|
||||||
src/entity/encryption.vala
|
|
||||||
src/entity/conversation.vala
|
src/entity/conversation.vala
|
||||||
|
src/entity/encryption.vala
|
||||||
src/entity/jid.vala
|
src/entity/jid.vala
|
||||||
src/entity/message.vala
|
src/entity/message.vala
|
||||||
src/entity/settings.vala
|
src/entity/settings.vala
|
||||||
|
|
|
@ -71,4 +71,41 @@ public interface ConversationTitlebarWidget : Object {
|
||||||
public abstract void set_conversation(Conversation conversation);
|
public abstract void set_conversation(Conversation conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract interface ConversationItemPopulator : Object {
|
||||||
|
public abstract string id { get; }
|
||||||
|
public abstract void init(Conversation conversation, ConversationItemCollection summary, WidgetType type);
|
||||||
|
public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { }
|
||||||
|
public virtual void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { }
|
||||||
|
public abstract void close(Conversation conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MetaConversationItem : Object {
|
||||||
|
public virtual Jid? jid { get; set; default=null; }
|
||||||
|
public virtual string color { get; set; default=null; }
|
||||||
|
public virtual string display_name { get; set; default=null; }
|
||||||
|
public virtual bool dim { get; set; default=false; }
|
||||||
|
public virtual DateTime? sort_time { get; set; default=null; }
|
||||||
|
public virtual DateTime? display_time { get; set; default=null; }
|
||||||
|
public virtual Encryption? encryption { get; set; default=null; }
|
||||||
|
public virtual Entities.Message.Marked? mark { get; set; default=null; }
|
||||||
|
|
||||||
|
public abstract bool can_merge { get; set; }
|
||||||
|
public abstract bool requires_avatar { get; set; }
|
||||||
|
public abstract bool requires_header { get; set; }
|
||||||
|
|
||||||
|
public abstract Object get_widget(WidgetType type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ConversationItemCollection : Object {
|
||||||
|
public abstract void insert_item(MetaConversationItem item);
|
||||||
|
public abstract void remove_item(MetaConversationItem item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MessageDisplayProvider : Object {
|
||||||
|
public abstract string id { get; set; }
|
||||||
|
public abstract double priority { get; set; }
|
||||||
|
public abstract bool can_display(Entities.Message? message);
|
||||||
|
public abstract MetaConversationItem? get_item(Entities.Message message, Entities.Conversation conversation);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ public class Registry {
|
||||||
internal ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
|
internal ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
|
||||||
internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
|
internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
|
||||||
internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
|
internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
|
||||||
|
internal Gee.List<MessageDisplayProvider> message_displays = new ArrayList<MessageDisplayProvider>();
|
||||||
|
internal Gee.List<ConversationItemPopulator> conversation_item_populators = new ArrayList<ConversationItemPopulator>();
|
||||||
internal Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
|
internal Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
|
||||||
if (a.order < b.order) {
|
if (a.order < b.order) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -67,6 +69,26 @@ public class Registry {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool register_message_display(MessageDisplayProvider provider) {
|
||||||
|
lock (message_displays) {
|
||||||
|
foreach(MessageDisplayProvider p in message_displays) {
|
||||||
|
if (p.id == provider.id) return false;
|
||||||
|
}
|
||||||
|
message_displays.add(provider);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool register_conversation_item_populator(ConversationItemPopulator populator) {
|
||||||
|
lock (conversation_item_populators) {
|
||||||
|
foreach(ConversationItemPopulator p in conversation_item_populators) {
|
||||||
|
if (p.id == populator.id) return false;
|
||||||
|
}
|
||||||
|
conversation_item_populators.add(populator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,7 +201,7 @@ public class Database : Qlite.Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gee.List<Message> get_messages(Jid jid, Account account, Message.Type? type, int count, Message? before) {
|
public Gee.List<Message> get_messages(Jid jid, Account account, Message.Type? type, int count, DateTime? before) {
|
||||||
QueryBuilder select = message.select()
|
QueryBuilder select = message.select()
|
||||||
.with(message.counterpart_id, "=", get_jid_id(jid))
|
.with(message.counterpart_id, "=", get_jid_id(jid))
|
||||||
.with(message.account_id, "=", account.id)
|
.with(message.account_id, "=", account.id)
|
||||||
|
@ -214,7 +214,7 @@ public class Database : Qlite.Database {
|
||||||
select.with(message.type_, "=", (int) type);
|
select.with(message.type_, "=", (int) type);
|
||||||
}
|
}
|
||||||
if (before != null) {
|
if (before != null) {
|
||||||
select.with(message.time, "<", (long) before.time.to_unix());
|
select.with(message.time, "<", (long) before.to_unix());
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkedList<Message> ret = new LinkedList<Message>();
|
LinkedList<Message> ret = new LinkedList<Message>();
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class MessageStorage : StreamInteractionModule, Object {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gee.List<Message>? get_messages_before(Conversation? conversation, Message before, int count = 20) {
|
public Gee.List<Message>? get_messages_before(Conversation? conversation, DateTime before, int count = 20) {
|
||||||
Gee.List<Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before);
|
Gee.List<Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before);
|
||||||
return db_messages;
|
return db_messages;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,13 +96,13 @@ SOURCES
|
||||||
src/ui/conversation_selector/groupchat_row.vala
|
src/ui/conversation_selector/groupchat_row.vala
|
||||||
src/ui/conversation_selector/list.vala
|
src/ui/conversation_selector/list.vala
|
||||||
src/ui/conversation_selector/view.vala
|
src/ui/conversation_selector/view.vala
|
||||||
src/ui/conversation_summary/conversation_item.vala
|
src/ui/conversation_summary/chat_state_populator.vala
|
||||||
src/ui/conversation_summary/merged_message_item.vala
|
src/ui/conversation_summary/conversation_item_skeleton.vala
|
||||||
src/ui/conversation_summary/message_item.vala
|
src/ui/conversation_summary/conversation_view.vala
|
||||||
|
src/ui/conversation_summary/default_message_display.vala
|
||||||
|
src/ui/conversation_summary/message_populator.vala
|
||||||
src/ui/conversation_summary/message_textview.vala
|
src/ui/conversation_summary/message_textview.vala
|
||||||
src/ui/conversation_summary/slashme_item.vala
|
src/ui/conversation_summary/slashme_message_display.vala
|
||||||
src/ui/conversation_summary/status_item.vala
|
|
||||||
src/ui/conversation_summary/view.vala
|
|
||||||
src/ui/conversation_titlebar/encryption_entry.vala
|
src/ui/conversation_titlebar/encryption_entry.vala
|
||||||
src/ui/conversation_titlebar/menu_entry.vala
|
src/ui/conversation_titlebar/menu_entry.vala
|
||||||
src/ui/conversation_titlebar/occupants_entry.vala
|
src/ui/conversation_titlebar/occupants_entry.vala
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<template class="DinoUiConversationSummaryMessageItem">
|
<template class="DinoUiConversationSummaryConversationItemSkeleton">
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="column-spacing">7</property>
|
<property name="column-spacing">7</property>
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
|
@ -23,8 +23,9 @@
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="time_label">
|
<object class="GtkLabel" id="time_label">
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="xalign">1</property>
|
<property name="xalign">1</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
<object class="GtkImage" id="encryption_image">
|
<object class="GtkImage" id="encryption_image">
|
||||||
<property name="visible">False</property>
|
<property name="visible">False</property>
|
||||||
<property name="xalign">1</property>
|
<property name="xalign">1</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
@ -55,6 +57,7 @@
|
||||||
<object class="GtkImage" id="received_image">
|
<object class="GtkImage" id="received_image">
|
||||||
<property name="visible">False</property>
|
<property name="visible">False</property>
|
||||||
<property name="xalign">1</property>
|
<property name="xalign">1</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<template class="DinoUiConversationSummaryView">
|
<template class="DinoUiConversationSummaryConversationView">
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
<property name="homogeneous">False</property>
|
<property name="homogeneous">False</property>
|
||||||
<property name="spacing">0</property>
|
<property name="spacing">0</property>
|
||||||
|
|
116
main/src/ui/conversation_summary/chat_state_populator.vala
Normal file
116
main/src/ui/conversation_summary/chat_state_populator.vala
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
using Gee;
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
using Dino.Entities;
|
||||||
|
using Xmpp;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
class ChatStatePopulator : Plugins.ConversationItemPopulator, Object {
|
||||||
|
|
||||||
|
public string id { get { return "chat_state"; } }
|
||||||
|
|
||||||
|
private StreamInteractor? stream_interactor;
|
||||||
|
private Conversation? current_conversation;
|
||||||
|
private Plugins.ConversationItemCollection? item_collection;
|
||||||
|
|
||||||
|
private MetaChatStateItem? meta_item;
|
||||||
|
|
||||||
|
public ChatStatePopulator(StreamInteractor stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
|
stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).received_state.connect((account, jid, state) => {
|
||||||
|
if (current_conversation != null && current_conversation.account.equals(account) && current_conversation.counterpart.equals_bare(jid)) {
|
||||||
|
Idle.add(() => { update_chat_state(account, jid, state); return false; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => {
|
||||||
|
if (conversation.equals(current_conversation)) {
|
||||||
|
Idle.add(() => { update_chat_state(conversation.account, conversation.counterpart); return false; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) {
|
||||||
|
current_conversation = conversation;
|
||||||
|
this.item_collection = item_collection;
|
||||||
|
this.meta_item = null;
|
||||||
|
|
||||||
|
update_chat_state(conversation.account, conversation.counterpart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(Conversation conversation) { }
|
||||||
|
|
||||||
|
public void populate_timespan(Conversation conversation, DateTime from, DateTime to) { }
|
||||||
|
|
||||||
|
public void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { }
|
||||||
|
|
||||||
|
private void update_chat_state(Account account, Jid jid, string? state = null) {
|
||||||
|
string? state_ = state;
|
||||||
|
if (state_ == null) {
|
||||||
|
state_ = stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).get_chat_state(current_conversation.account, current_conversation.counterpart);
|
||||||
|
}
|
||||||
|
string? new_text = null;
|
||||||
|
if (state_ != null) {
|
||||||
|
if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING || state_ == Xep.ChatStateNotifications.STATE_PAUSED) {
|
||||||
|
string display_name = Util.get_display_name(stream_interactor, jid, account);
|
||||||
|
if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING) {
|
||||||
|
new_text = _("is typing...");
|
||||||
|
} else if (state_ == Xep.ChatStateNotifications.STATE_PAUSED) {
|
||||||
|
new_text = _("has stopped typing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (meta_item != null && new_text == null) {
|
||||||
|
item_collection.remove_item(meta_item);
|
||||||
|
meta_item = null;
|
||||||
|
} else if (meta_item != null && new_text != null) {
|
||||||
|
meta_item.set_text(new_text);
|
||||||
|
} else if (new_text != null) {
|
||||||
|
meta_item = new MetaChatStateItem(stream_interactor, current_conversation, jid, new_text);
|
||||||
|
item_collection.insert_item(meta_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
|
public override Jid? jid { get; set; }
|
||||||
|
public override bool dim { get; set; default=true; }
|
||||||
|
public override DateTime? sort_time { get; set; default=new DateTime.now_utc().add_years(10); }
|
||||||
|
|
||||||
|
public override bool can_merge { get; set; default=false; }
|
||||||
|
public override bool requires_avatar { get; set; default=true; }
|
||||||
|
public override bool requires_header { get; set; default=false; }
|
||||||
|
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private Conversation conversation;
|
||||||
|
private string text;
|
||||||
|
private Label label;
|
||||||
|
|
||||||
|
public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Jid jid, string text) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.jid = jid;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Object get_widget(Plugins.WidgetType widget_type) {
|
||||||
|
label = new Label("") { xalign=0, vexpand=true, visible=true };
|
||||||
|
label.get_style_context().add_class("dim-label");
|
||||||
|
update_text();
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_text(string text) {
|
||||||
|
this.text = text;
|
||||||
|
update_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_text() {
|
||||||
|
string display_name = Util.get_display_name(stream_interactor, jid, conversation.account);
|
||||||
|
label.label = display_name + " " + text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
using Dino.Entities;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
public enum MessageKind {
|
|
||||||
TEXT,
|
|
||||||
ME_COMMAND
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageKind get_message_kind(Message message) {
|
|
||||||
if (message.body.has_prefix("/me ")) {
|
|
||||||
return MessageKind.ME_COMMAND;
|
|
||||||
} else {
|
|
||||||
return MessageKind.TEXT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ConversationItem : Gtk.Widget {
|
|
||||||
public abstract bool merge(Entities.Message message);
|
|
||||||
|
|
||||||
public static ConversationItem create_for_message(StreamInteractor stream_interactor, Conversation conversation, Message message) {
|
|
||||||
switch (get_message_kind(message)) {
|
|
||||||
case MessageKind.TEXT:
|
|
||||||
return new MergedMessageItem(stream_interactor, conversation, message);
|
|
||||||
case MessageKind.ME_COMMAND:
|
|
||||||
return new SlashMeItem(stream_interactor, conversation, message);
|
|
||||||
}
|
|
||||||
assert_not_reached();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
142
main/src/ui/conversation_summary/conversation_item_skeleton.vala
Normal file
142
main/src/ui/conversation_summary/conversation_item_skeleton.vala
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
using Gee;
|
||||||
|
using Gdk;
|
||||||
|
using Gtk;
|
||||||
|
using Markup;
|
||||||
|
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/im/dino/conversation_summary/message_item.ui")]
|
||||||
|
public class ConversationItemSkeleton : Grid {
|
||||||
|
|
||||||
|
[GtkChild] private Image image;
|
||||||
|
[GtkChild] private Label time_label;
|
||||||
|
[GtkChild] private Image encryption_image;
|
||||||
|
[GtkChild] private Image received_image;
|
||||||
|
|
||||||
|
public StreamInteractor stream_interactor;
|
||||||
|
public Conversation conversation { get; set; }
|
||||||
|
public Gee.List<Plugins.MetaConversationItem> items = new ArrayList<Plugins.MetaConversationItem>();
|
||||||
|
|
||||||
|
private Box box = new Box(Orientation.VERTICAL, 2) { visible=true };
|
||||||
|
|
||||||
|
public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation) {
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
|
set_main_widget(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add_meta_item(Plugins.MetaConversationItem item) {
|
||||||
|
items.add(item);
|
||||||
|
if (items.size == 1) {
|
||||||
|
setup(item);
|
||||||
|
}
|
||||||
|
Widget widget = (Widget) item.get_widget(Plugins.WidgetType.GTK);
|
||||||
|
if (item.requires_header) {
|
||||||
|
box.add(widget);
|
||||||
|
} else {
|
||||||
|
set_title_widget(widget);
|
||||||
|
}
|
||||||
|
item.notify["mark"].connect_after(update_received);
|
||||||
|
update_received();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_title_widget(Widget w) {
|
||||||
|
attach(w, 1, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_main_widget(Widget w) {
|
||||||
|
attach(w, 1, 1, 2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update_time() {
|
||||||
|
if (items.size > 0 && items[0].display_time != null) {
|
||||||
|
time_label.label = get_relative_time(items[0].display_time.to_local());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup(Plugins.MetaConversationItem item) {
|
||||||
|
update_time();
|
||||||
|
Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).set_greyscale(item.dim).draw_jid(stream_interactor, item.jid, conversation.account));
|
||||||
|
if (item.requires_header) {
|
||||||
|
set_default_title_widget(item.jid);
|
||||||
|
}
|
||||||
|
if (item.encryption != null && item.encryption != Encryption.NONE) {
|
||||||
|
encryption_image.visible = true;
|
||||||
|
encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set_default_title_widget(Jid jid) {
|
||||||
|
Label name_label = new Label("") { use_markup=true, xalign=0, hexpand=true, visible=true };
|
||||||
|
string display_name = Util.get_display_name(stream_interactor, jid, conversation.account);
|
||||||
|
string color = Util.get_name_hex_color(stream_interactor, conversation.account, jid, Util.is_dark_theme(name_label));
|
||||||
|
name_label.label = @"<span foreground=\"#$color\">$display_name</span>";
|
||||||
|
name_label.style_updated.connect(() => {
|
||||||
|
string new_color = Util.get_name_hex_color(stream_interactor, conversation.account, jid, Util.is_dark_theme(name_label));
|
||||||
|
name_label.set_markup(@"<span foreground=\"#$new_color\">$display_name</span>");
|
||||||
|
});
|
||||||
|
set_title_widget(name_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_received() {
|
||||||
|
bool all_received = true;
|
||||||
|
bool all_read = true;
|
||||||
|
foreach (Plugins.MetaConversationItem item in items) {
|
||||||
|
if (item.mark == Message.Marked.WONTSEND) {
|
||||||
|
received_image.visible = true;
|
||||||
|
received_image.set_from_icon_name("dialog-warning-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
Util.force_error_color(received_image);
|
||||||
|
Util.force_error_color(encryption_image);
|
||||||
|
Util.force_error_color(time_label);
|
||||||
|
return;
|
||||||
|
} else if (item.mark != Message.Marked.READ) {
|
||||||
|
all_read = false;
|
||||||
|
if (item.mark != Message.Marked.RECEIVED) {
|
||||||
|
all_received = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (all_read) {
|
||||||
|
received_image.visible = true;
|
||||||
|
received_image.set_from_icon_name("dino-double-tick-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
} else if (all_received) {
|
||||||
|
received_image.visible = true;
|
||||||
|
received_image.set_from_icon_name("dino-tick-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
} else if (received_image.visible) {
|
||||||
|
received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string get_relative_time(DateTime datetime) {
|
||||||
|
DateTime now = new DateTime.now_local();
|
||||||
|
TimeSpan timespan = now.difference(datetime);
|
||||||
|
if (timespan > 365 * TimeSpan.DAY) {
|
||||||
|
return datetime.format(Util.is_24h_format() ?
|
||||||
|
/* xgettext:no-c-format */ /* Date + time in 24h format (w/o seconds) */ _("%x, %H\u2236%M") :
|
||||||
|
/* xgettext:no-c-format */ /* Date + time in 12h format (w/o seconds)*/ _("%x, %l\u2236%M %p"));
|
||||||
|
} else if (timespan > 7 * TimeSpan.DAY) {
|
||||||
|
return datetime.format(Util.is_24h_format() ?
|
||||||
|
/* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %H\u2236%M") :
|
||||||
|
/* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %l\u2236%M %p"));
|
||||||
|
} else if (datetime.get_day_of_month() != new DateTime.now_utc().get_day_of_month()) {
|
||||||
|
return datetime.format(Util.is_24h_format() ?
|
||||||
|
/* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */ _("%a, %H\u2236%M") :
|
||||||
|
/* xgettext:no-c-format */ _("%a, %l\u2236%M %p"));
|
||||||
|
} else if (timespan > 9 * TimeSpan.MINUTE) {
|
||||||
|
return datetime.format(Util.is_24h_format() ?
|
||||||
|
/* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%H\u2236%M") :
|
||||||
|
/* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%l\u2236%M %p"));
|
||||||
|
} else if (timespan > TimeSpan.MINUTE) {
|
||||||
|
ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE);
|
||||||
|
/* xgettext:this is the beginning of a sentence. */
|
||||||
|
return n("%i min ago", "%i mins ago", mins).printf(mins);
|
||||||
|
} else {
|
||||||
|
return _("Just now");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
173
main/src/ui/conversation_summary/conversation_view.vala
Normal file
173
main/src/ui/conversation_summary/conversation_view.vala
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
using Gee;
|
||||||
|
using Gtk;
|
||||||
|
using Pango;
|
||||||
|
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/im/dino/conversation_summary/view.ui")]
|
||||||
|
public class ConversationView : Box, Plugins.ConversationItemCollection {
|
||||||
|
|
||||||
|
public Conversation? conversation { get; private set; }
|
||||||
|
|
||||||
|
[GtkChild] private ScrolledWindow scrolled;
|
||||||
|
[GtkChild] private Box main;
|
||||||
|
[GtkChild] private Stack stack;
|
||||||
|
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private Gee.TreeSet<Plugins.MetaConversationItem> meta_items = new TreeSet<Plugins.MetaConversationItem>((a, b) => { return a.sort_time.compare(b.sort_time); });
|
||||||
|
private Gee.Map<Plugins.MetaConversationItem, Gee.List<Plugins.MetaConversationItem>> meta_after_items = new Gee.HashMap<Plugins.MetaConversationItem, Gee.List<Plugins.MetaConversationItem>>();
|
||||||
|
private Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton> item_item_skeletons = new Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton>();
|
||||||
|
private Gee.HashMap<Plugins.MetaConversationItem, Widget> widgets = new Gee.HashMap<Plugins.MetaConversationItem, Widget>();
|
||||||
|
private Gee.List<ConversationItemSkeleton> item_skeletons = new Gee.ArrayList<ConversationItemSkeleton>();
|
||||||
|
private MessagePopulator message_item_populator;
|
||||||
|
|
||||||
|
private double? was_value;
|
||||||
|
private double? was_upper;
|
||||||
|
private double? was_page_size;
|
||||||
|
|
||||||
|
private Mutex reloading_mutex = new Mutex();
|
||||||
|
private bool animate = false;
|
||||||
|
|
||||||
|
public ConversationView(StreamInteractor stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify);
|
||||||
|
scrolled.vadjustment.notify["value"].connect(on_value_notify);
|
||||||
|
|
||||||
|
message_item_populator = new MessagePopulator(stream_interactor);
|
||||||
|
|
||||||
|
Application app = GLib.Application.get_default() as Application;
|
||||||
|
app.plugin_registry.register_conversation_item_populator(new ChatStatePopulator(stream_interactor));
|
||||||
|
|
||||||
|
Timeout.add_seconds(60, () => {
|
||||||
|
foreach (ConversationItemSkeleton item_skeleton in item_skeletons) {
|
||||||
|
item_skeleton.update_time();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Util.force_base_background(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize_for_conversation(Conversation? conversation) {
|
||||||
|
this.conversation = conversation;
|
||||||
|
stack.set_visible_child_name("void");
|
||||||
|
clear();
|
||||||
|
was_upper = null;
|
||||||
|
was_page_size = null;
|
||||||
|
animate = false;
|
||||||
|
Timeout.add(20, () => { animate = true; return false; });
|
||||||
|
|
||||||
|
message_item_populator.init(conversation, this);
|
||||||
|
message_item_populator.populate_number(conversation, new DateTime.now_utc(), 50);
|
||||||
|
|
||||||
|
Dino.Application app = Dino.Application.get_default();
|
||||||
|
foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_item_populators) {
|
||||||
|
populator.init(conversation, this, Plugins.WidgetType.GTK);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.set_visible_child_name("main");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert_item(Plugins.MetaConversationItem item) {
|
||||||
|
meta_items.add(item);
|
||||||
|
if (!item.can_merge || !merge_back(item)) {
|
||||||
|
insert_new(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove_item(Plugins.MetaConversationItem item) {
|
||||||
|
main.remove(widgets[item]);
|
||||||
|
widgets.unset(item);
|
||||||
|
meta_items.remove(item);
|
||||||
|
item_skeletons.remove(item_item_skeletons[item]);
|
||||||
|
item_item_skeletons.unset(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool merge_back(Plugins.MetaConversationItem item) {
|
||||||
|
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
|
||||||
|
if (lower_item != null) {
|
||||||
|
ConversationItemSkeleton lower_skeleton = item_item_skeletons[lower_item];
|
||||||
|
Plugins.MetaConversationItem lower_start_item = lower_skeleton.items[0];
|
||||||
|
if (lower_start_item.can_merge &&
|
||||||
|
item.display_time.difference(lower_start_item.display_time) < TimeSpan.MINUTE &&
|
||||||
|
lower_start_item.jid.equals(item.jid) &&
|
||||||
|
lower_start_item.encryption == item.encryption &&
|
||||||
|
item.mark != Message.Marked.WONTSEND) {
|
||||||
|
lower_skeleton.add_meta_item(item);
|
||||||
|
force_alloc_width(lower_skeleton, main.get_allocated_width());
|
||||||
|
item_item_skeletons[item] = lower_skeleton;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insert_new(Plugins.MetaConversationItem item) {
|
||||||
|
ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation);
|
||||||
|
item_skeleton.add_meta_item(item);
|
||||||
|
item_item_skeletons[item] = item_skeleton;
|
||||||
|
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
|
||||||
|
int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0;
|
||||||
|
item_skeletons.insert(index, item_skeleton);
|
||||||
|
|
||||||
|
Widget insert = item_skeleton;
|
||||||
|
if (animate) {
|
||||||
|
Revealer revealer = new Revealer() {transition_duration = 200, transition_type = RevealerTransitionType.SLIDE_UP, visible = true};
|
||||||
|
revealer.add(item_skeleton);
|
||||||
|
insert = revealer;
|
||||||
|
main.add(insert);
|
||||||
|
revealer.reveal_child = true;
|
||||||
|
} else {
|
||||||
|
main.add(insert);
|
||||||
|
}
|
||||||
|
widgets[item] = insert;
|
||||||
|
force_alloc_width(insert, main.get_allocated_width());
|
||||||
|
main.reorder_child(insert, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_upper_notify() {
|
||||||
|
if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1 ||
|
||||||
|
scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size
|
||||||
|
scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down
|
||||||
|
} else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1) {
|
||||||
|
scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content
|
||||||
|
}
|
||||||
|
was_upper = scrolled.vadjustment.upper;
|
||||||
|
was_page_size = scrolled.vadjustment.page_size;
|
||||||
|
reloading_mutex.trylock();
|
||||||
|
reloading_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_value_notify() {
|
||||||
|
if (scrolled.vadjustment.value < 200) {
|
||||||
|
load_earlier_messages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load_earlier_messages() {
|
||||||
|
was_value = scrolled.vadjustment.value;
|
||||||
|
if (!reloading_mutex.trylock()) return;
|
||||||
|
if (meta_items.size > 0) message_item_populator.populate_number(conversation, meta_items.first().sort_time, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround GTK TextView issues
|
||||||
|
private void force_alloc_width(Widget widget, int width) {
|
||||||
|
Allocation alloc = Allocation();
|
||||||
|
widget.get_preferred_width(out alloc.width, null);
|
||||||
|
widget.get_preferred_height(out alloc.height, null);
|
||||||
|
alloc.width = width;
|
||||||
|
widget.size_allocate(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
meta_items.clear();
|
||||||
|
meta_after_items.clear();
|
||||||
|
item_skeletons.clear();
|
||||||
|
item_item_skeletons.clear();
|
||||||
|
main.@foreach((widget) => { main.remove(widget); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
public class DefaultMessageDisplay : Plugins.MessageDisplayProvider, Object {
|
||||||
|
public string id { get; set; default="default"; }
|
||||||
|
public double priority { get; set; default=0; }
|
||||||
|
|
||||||
|
public StreamInteractor stream_interactor;
|
||||||
|
|
||||||
|
public DefaultMessageDisplay(StreamInteractor stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool can_display(Entities.Message? message) { return true; }
|
||||||
|
|
||||||
|
public Plugins.MetaConversationItem? get_item(Entities.Message message, Conversation conversation) {
|
||||||
|
return new MetaMessageItem(stream_interactor, message, conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetaMessageItem : Plugins.MetaConversationItem {
|
||||||
|
public override Jid? jid { get; set; }
|
||||||
|
public override DateTime? sort_time { get; set; }
|
||||||
|
public override DateTime? display_time { get; set; }
|
||||||
|
public override Encryption? encryption { get; set; }
|
||||||
|
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private Conversation conversation;
|
||||||
|
private Message message;
|
||||||
|
|
||||||
|
public MetaMessageItem(StreamInteractor stream_interactor, Message message, Conversation conversation) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.message = message;
|
||||||
|
this.jid = message.from;
|
||||||
|
this.sort_time = message.local_time;
|
||||||
|
this.display_time = message.time;
|
||||||
|
this.encryption = message.encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool can_merge { get; set; default=true; }
|
||||||
|
public override bool requires_avatar { get; set; default=true; }
|
||||||
|
public override bool requires_header { get; set; default=true; }
|
||||||
|
|
||||||
|
public override Object get_widget(Plugins.WidgetType widget_type) {
|
||||||
|
MessageTextView text_view = new MessageTextView() { visible = true };
|
||||||
|
text_view.add_text(message.body);
|
||||||
|
return text_view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
using Gee;
|
|
||||||
using Gdk;
|
|
||||||
using Gtk;
|
|
||||||
using Markup;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
public class MergedMessageItem : MessageItem {
|
|
||||||
|
|
||||||
private Label name_label = new Label("") { xalign=0, visible=true, hexpand=true };
|
|
||||||
private MessageTextView textview = new MessageTextView() { visible=true };
|
|
||||||
|
|
||||||
public MergedMessageItem(StreamInteractor stream_interactor, Conversation conversation, Message message) {
|
|
||||||
base(stream_interactor, conversation, message);
|
|
||||||
set_main_widget(textview);
|
|
||||||
set_title_widget(name_label);
|
|
||||||
add_message(message);
|
|
||||||
|
|
||||||
string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account);
|
|
||||||
string color = Util.get_name_hex_color(stream_interactor, conversation.account, message.from, false);
|
|
||||||
name_label.set_markup(@"<span foreground=\"#$color\">$display_name</span>");
|
|
||||||
|
|
||||||
textview.style_updated.connect(update_display_style);
|
|
||||||
update_display_style();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void add_message(Message message) {
|
|
||||||
base.add_message(message);
|
|
||||||
if (messages.size > 1) textview.add_text("\n");
|
|
||||||
string text = message.body;
|
|
||||||
if (text.length > 10000) {
|
|
||||||
text = text.slice(0, 10000) + " [" + _("Message too long") + "]";
|
|
||||||
}
|
|
||||||
textview.add_text(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool merge(Message message) {
|
|
||||||
if (get_message_kind(message) == MessageKind.TEXT &&
|
|
||||||
this.from.equals(message.from) &&
|
|
||||||
this.messages[0].encryption == message.encryption &&
|
|
||||||
message.time.difference(initial_time) < TimeSpan.MINUTE &&
|
|
||||||
this.messages[0].marked != Entities.Message.Marked.WONTSEND) {
|
|
||||||
add_message(message);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_display_style() {
|
|
||||||
string display_name = Util.get_message_display_name(stream_interactor, messages[0], conversation.account);
|
|
||||||
string color = Util.get_name_hex_color(stream_interactor, conversation.account, messages[0].real_jid ?? messages[0].from, Util.is_dark_theme(textview));
|
|
||||||
name_label.set_markup(@"<span foreground=\"#$color\">$display_name</span>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
using Gee;
|
|
||||||
using Gdk;
|
|
||||||
using Gtk;
|
|
||||||
using Markup;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
[GtkTemplate (ui = "/im/dino/conversation_summary/message_item.ui")]
|
|
||||||
public class MessageItem : Grid, ConversationItem {
|
|
||||||
|
|
||||||
[GtkChild] private Image image;
|
|
||||||
[GtkChild] private Label time_label;
|
|
||||||
[GtkChild] private Image encryption_image;
|
|
||||||
[GtkChild] private Image received_image;
|
|
||||||
|
|
||||||
public StreamInteractor stream_interactor;
|
|
||||||
public Conversation conversation { get; set; }
|
|
||||||
public Jid from { get; private set; }
|
|
||||||
public DateTime initial_time { get; private set; }
|
|
||||||
public ArrayList<Message> messages = new ArrayList<Message>(Message.equals_func);
|
|
||||||
|
|
||||||
public MessageItem(StreamInteractor stream_interactor, Conversation conversation, Message message) {
|
|
||||||
this.conversation = conversation;
|
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
this.initial_time = message.time;
|
|
||||||
this.from = message.from;
|
|
||||||
|
|
||||||
if (message.encryption != Encryption.NONE) {
|
|
||||||
encryption_image.visible = true;
|
|
||||||
encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
time_label.label = get_relative_time(initial_time.to_local());
|
|
||||||
Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_message(stream_interactor, message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_title_widget(Widget w) {
|
|
||||||
attach(w, 1, 0, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_main_widget(Widget w) {
|
|
||||||
attach(w, 1, 1, 2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() {
|
|
||||||
time_label.label = get_relative_time(initial_time.to_local());
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void add_message(Message message) {
|
|
||||||
messages.add(message);
|
|
||||||
|
|
||||||
message.notify["marked"].connect_after(() => {
|
|
||||||
Idle.add(() => { update_received(); return false; });
|
|
||||||
});
|
|
||||||
update_received();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool merge(Message message) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_received() {
|
|
||||||
bool all_received = true;
|
|
||||||
bool all_read = true;
|
|
||||||
foreach (Message message in messages) {
|
|
||||||
if (message.marked == Message.Marked.WONTSEND) {
|
|
||||||
received_image.visible = true;
|
|
||||||
received_image.set_from_icon_name("dialog-warning-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
Util.force_error_color(received_image);
|
|
||||||
Util.force_error_color(encryption_image);
|
|
||||||
Util.force_error_color(time_label);
|
|
||||||
return;
|
|
||||||
} else if (message.marked != Message.Marked.READ) {
|
|
||||||
all_read = false;
|
|
||||||
if (message.marked != Message.Marked.RECEIVED) {
|
|
||||||
all_received = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (all_read) {
|
|
||||||
received_image.visible = true;
|
|
||||||
received_image.set_from_icon_name("dino-double-tick-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
} else if (all_received) {
|
|
||||||
received_image.visible = true;
|
|
||||||
received_image.set_from_icon_name("dino-tick-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
} else if (received_image.visible) {
|
|
||||||
received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string get_relative_time(DateTime datetime) {
|
|
||||||
DateTime now = new DateTime.now_local();
|
|
||||||
TimeSpan timespan = now.difference(datetime);
|
|
||||||
if (timespan > 365 * TimeSpan.DAY) {
|
|
||||||
return datetime.format(Util.is_24h_format() ?
|
|
||||||
/* xgettext:no-c-format */ /* Date + time in 24h format (w/o seconds) */ _("%x, %H\u2236%M") :
|
|
||||||
/* xgettext:no-c-format */ /* Date + time in 12h format (w/o seconds)*/ _("%x, %l\u2236%M %p"));
|
|
||||||
} else if (timespan > 7 * TimeSpan.DAY) {
|
|
||||||
return datetime.format(Util.is_24h_format() ?
|
|
||||||
/* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %H\u2236%M") :
|
|
||||||
/* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %l\u2236%M %p"));
|
|
||||||
} else if (timespan > 1 * TimeSpan.DAY) {
|
|
||||||
return datetime.format(Util.is_24h_format() ?
|
|
||||||
/* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */ _("%a, %H\u2236%M") :
|
|
||||||
/* xgettext:no-c-format */ _("%a, %l\u2236%M %p"));
|
|
||||||
} else if (timespan > 9 * TimeSpan.MINUTE) {
|
|
||||||
return datetime.format(Util.is_24h_format() ?
|
|
||||||
/* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%H\u2236%M") :
|
|
||||||
/* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%l\u2236%M %p"));
|
|
||||||
} else if (timespan > TimeSpan.MINUTE) {
|
|
||||||
ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE);
|
|
||||||
/* xgettext:this is the beginning of a sentence. */
|
|
||||||
return n("%i min ago", "%i mins ago", mins).printf(mins);
|
|
||||||
} else {
|
|
||||||
return _("Just now");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
66
main/src/ui/conversation_summary/message_populator.vala
Normal file
66
main/src/ui/conversation_summary/message_populator.vala
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using Gee;
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
public class MessagePopulator : Object {
|
||||||
|
|
||||||
|
private StreamInteractor? stream_interactor;
|
||||||
|
private Conversation? current_conversation;
|
||||||
|
private Plugins.ConversationItemCollection? item_collection;
|
||||||
|
|
||||||
|
public MessagePopulator(StreamInteractor stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
|
Application app = GLib.Application.get_default() as Application;
|
||||||
|
app.plugin_registry.register_message_display(new DefaultMessageDisplay(stream_interactor));
|
||||||
|
app.plugin_registry.register_message_display(new SlashmeMessageDisplay(stream_interactor));
|
||||||
|
|
||||||
|
|
||||||
|
stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect((message, conversation) => {
|
||||||
|
Idle.add(() => { handle_message(message, conversation); return false; });
|
||||||
|
});
|
||||||
|
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => {
|
||||||
|
Idle.add(() => { handle_message(message, conversation); return false; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection) {
|
||||||
|
current_conversation = conversation;
|
||||||
|
this.item_collection = item_collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(Conversation conversation) { }
|
||||||
|
|
||||||
|
public void populate_number(Conversation conversation, DateTime from, int n) {
|
||||||
|
Gee.List<Entities.Message>? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before(conversation, from, n);
|
||||||
|
if (messages != null) {
|
||||||
|
foreach (Entities.Message message in messages) {
|
||||||
|
handle_message(message, conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handle_message(Message message, Conversation conversation) {
|
||||||
|
if (!conversation.equals(current_conversation)) return;
|
||||||
|
|
||||||
|
Plugins.MessageDisplayProvider? best_provider = null;
|
||||||
|
int priority = -1;
|
||||||
|
Application app = GLib.Application.get_default() as Application;
|
||||||
|
foreach (Plugins.MessageDisplayProvider provider in app.plugin_registry.message_displays) {
|
||||||
|
if (provider.can_display(message) && provider.priority > priority) {
|
||||||
|
best_provider = provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Plugins.MetaConversationItem meta_item = best_provider.get_item(message, conversation);
|
||||||
|
meta_item.mark = message.marked;
|
||||||
|
message.notify["marked"].connect(() => {
|
||||||
|
meta_item.mark = message.marked;
|
||||||
|
});
|
||||||
|
item_collection.insert_item(meta_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,7 +27,11 @@ public class MessageTextView : TextView {
|
||||||
minimum_width = 0;
|
minimum_width = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add_text(string text) {
|
public void add_text(string text_) {
|
||||||
|
string text = text_;
|
||||||
|
if (text.length > 10000) {
|
||||||
|
text = text.slice(0, 10000) + " [" + _("Message too long") + "]";
|
||||||
|
}
|
||||||
TextIter end;
|
TextIter end;
|
||||||
buffer.get_end_iter(out end);
|
buffer.get_end_iter(out end);
|
||||||
buffer.insert(ref end, text, -1);
|
buffer.insert(ref end, text, -1);
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
using Gdk;
|
|
||||||
using Gtk;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
public class SlashMeItem : MessageItem {
|
|
||||||
|
|
||||||
private Box box = new Box(Orientation.VERTICAL, 0) { visible=true, vexpand=true };
|
|
||||||
private MessageTextView textview = new MessageTextView() { visible=true };
|
|
||||||
private string text;
|
|
||||||
private TextTag nick_tag;
|
|
||||||
|
|
||||||
public SlashMeItem(StreamInteractor stream_interactor, Conversation conversation, Message message) {
|
|
||||||
base(stream_interactor, conversation, message);
|
|
||||||
box.set_center_widget(textview);
|
|
||||||
set_title_widget(box);
|
|
||||||
text = message.body.substring(3);
|
|
||||||
|
|
||||||
string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account);
|
|
||||||
string color = Util.get_name_hex_color(stream_interactor, conversation.account, conversation.counterpart, false);
|
|
||||||
nick_tag = textview.buffer.create_tag("nick", foreground: "#" + color);
|
|
||||||
TextIter iter;
|
|
||||||
textview.buffer.get_start_iter(out iter);
|
|
||||||
textview.buffer.insert_with_tags(ref iter, display_name, display_name.length, nick_tag);
|
|
||||||
textview.add_text(text);
|
|
||||||
add_message(message);
|
|
||||||
|
|
||||||
textview.style_updated.connect(update_display_style);
|
|
||||||
update_display_style();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool merge(Message message) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_display_style() {
|
|
||||||
string color = Util.get_name_hex_color(stream_interactor, conversation.account, messages[0].real_jid ?? messages[0].from, Util.is_dark_theme(textview));
|
|
||||||
nick_tag.foreground = "#" + color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Ui.ConversationSummary {
|
||||||
|
|
||||||
|
public class SlashmeMessageDisplay : Plugins.MessageDisplayProvider, Object {
|
||||||
|
public string id { get; set; default="slashme"; }
|
||||||
|
public double priority { get; set; default=1; }
|
||||||
|
|
||||||
|
public StreamInteractor stream_interactor;
|
||||||
|
|
||||||
|
public SlashmeMessageDisplay(StreamInteractor stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool can_display(Entities.Message? message) {
|
||||||
|
return message.body.has_prefix("/me");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugins.MetaConversationItem? get_item(Entities.Message message, Conversation conversation) {
|
||||||
|
return new MetaSlashmeItem(stream_interactor, message, conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetaSlashmeItem : Plugins.MetaConversationItem {
|
||||||
|
public override Jid? jid { get; set; }
|
||||||
|
public override DateTime? sort_time { get; set; }
|
||||||
|
public override DateTime? display_time { get; set; }
|
||||||
|
public override Encryption? encryption { get; set; }
|
||||||
|
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private Conversation conversation;
|
||||||
|
private Message message;
|
||||||
|
private TextTag nick_tag;
|
||||||
|
private MessageTextView text_view;
|
||||||
|
|
||||||
|
public MetaSlashmeItem(StreamInteractor stream_interactor, Message message, Conversation conversation) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.message = message;
|
||||||
|
this.jid = message.from;
|
||||||
|
this.sort_time = message.local_time;
|
||||||
|
this.display_time = message.time;
|
||||||
|
this.encryption = message.encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool can_merge { get; set; default=false; }
|
||||||
|
public override bool requires_avatar { get; set; default=true; }
|
||||||
|
public override bool requires_header { get; set; default=false; }
|
||||||
|
|
||||||
|
public override Object get_widget(Plugins.WidgetType widget_type) {
|
||||||
|
text_view = new MessageTextView() { valign=Align.CENTER, vexpand=true, visible = true };
|
||||||
|
|
||||||
|
string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account);
|
||||||
|
string color = Util.get_name_hex_color(stream_interactor, conversation.account, conversation.counterpart, Util.is_dark_theme(text_view));
|
||||||
|
nick_tag = text_view.buffer.create_tag("nick", foreground: "#" + color);
|
||||||
|
TextIter iter;
|
||||||
|
text_view.buffer.get_start_iter(out iter);
|
||||||
|
text_view.buffer.insert_with_tags(ref iter, display_name, display_name.length, nick_tag);
|
||||||
|
|
||||||
|
text_view.add_text(message.body.substring(3));
|
||||||
|
text_view.style_updated.connect(update_style);
|
||||||
|
text_view.realize.connect(update_style);
|
||||||
|
return text_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_style() {
|
||||||
|
string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account);
|
||||||
|
string color = Util.get_name_hex_color(stream_interactor, conversation.account, message.real_jid ?? message.from, Util.is_dark_theme(text_view));
|
||||||
|
nick_tag.foreground = "#" + color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
using Markup;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
private class StatusItem : Grid {
|
|
||||||
|
|
||||||
private Image image = new Image();
|
|
||||||
private Label label = new Label("");
|
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
|
||||||
private Conversation conversation;
|
|
||||||
|
|
||||||
public StatusItem(StreamInteractor stream_interactor, Conversation conversation, string? text) {
|
|
||||||
Object(column_spacing : 7);
|
|
||||||
set_hexpand(true);
|
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
this.conversation = conversation;
|
|
||||||
image.set_from_pixbuf((new AvatarGenerator(30, 30)).set_greyscale(true).draw_conversation(stream_interactor, conversation));
|
|
||||||
attach(image, 0, 0, 1, 1);
|
|
||||||
attach(label, 1, 0, 1, 1);
|
|
||||||
string display_name = Util.get_display_name(stream_interactor, conversation.counterpart, conversation.account);
|
|
||||||
label.set_markup(@"<span foreground=\"#B1B1B1\">$(escape_text(display_name)) $text</span>");
|
|
||||||
show_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,216 +0,0 @@
|
||||||
using Gee;
|
|
||||||
using Gtk;
|
|
||||||
using Pango;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
|
||||||
using Xmpp;
|
|
||||||
|
|
||||||
namespace Dino.Ui.ConversationSummary {
|
|
||||||
|
|
||||||
[GtkTemplate (ui = "/im/dino/conversation_summary/view.ui")]
|
|
||||||
public class View : Box {
|
|
||||||
|
|
||||||
public Conversation? conversation { get; private set; }
|
|
||||||
public HashMap<Entities.Message, ConversationItem> conversation_items = new HashMap<Entities.Message, ConversationItem>(Entities.Message.hash_func, Entities.Message.equals_func);
|
|
||||||
|
|
||||||
[GtkChild] private ScrolledWindow scrolled;
|
|
||||||
[GtkChild] private Box main;
|
|
||||||
[GtkChild] private Stack stack;
|
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
|
||||||
private ConversationItem? last_conversation_item;
|
|
||||||
private StatusItem typing_status;
|
|
||||||
private Entities.Message? earliest_message;
|
|
||||||
double? was_value;
|
|
||||||
double? was_upper;
|
|
||||||
double? was_page_size;
|
|
||||||
Object reloading_lock = new Object();
|
|
||||||
bool reloading = false;
|
|
||||||
|
|
||||||
public View(StreamInteractor stream_interactor) {
|
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify);
|
|
||||||
scrolled.vadjustment.notify["value"].connect(on_value_notify);
|
|
||||||
|
|
||||||
stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).received_state.connect((account, jid, state) => {
|
|
||||||
Idle.add(() => { on_received_state(account, jid, state); return false; });
|
|
||||||
});
|
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect((message, conversation) => {
|
|
||||||
Idle.add(() => { show_message(message, conversation, true); return false; });
|
|
||||||
});
|
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => {
|
|
||||||
Idle.add(() => { show_message(message, conversation, true); return false; });
|
|
||||||
});
|
|
||||||
stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect((show, jid, account) => {
|
|
||||||
Idle.add(() => { on_show_received(show, jid, account); return false; });
|
|
||||||
});
|
|
||||||
Timeout.add_seconds(60, () => {
|
|
||||||
foreach (ConversationItem conversation_item in conversation_items.values) {
|
|
||||||
MessageItem message_item = conversation_item as MessageItem;
|
|
||||||
if (message_item != null) message_item.update();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
Util.force_base_background(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initialize_for_conversation(Conversation? conversation) {
|
|
||||||
this.conversation = conversation;
|
|
||||||
stack.set_visible_child_name("void");
|
|
||||||
clear();
|
|
||||||
conversation_items.clear();
|
|
||||||
was_upper = null;
|
|
||||||
was_page_size = null;
|
|
||||||
last_conversation_item = null;
|
|
||||||
|
|
||||||
ArrayList<Object> objects = new ArrayList<Object>();
|
|
||||||
Gee.List<Entities.Message> messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation);
|
|
||||||
if (messages.size > 0) {
|
|
||||||
earliest_message = messages[messages.size -1];
|
|
||||||
objects.add_all(messages);
|
|
||||||
}
|
|
||||||
HashMap<Jid, ArrayList<Show>>? shows = stream_interactor.get_module(PresenceManager.IDENTITY).get_shows(conversation.counterpart, conversation.account);
|
|
||||||
if (shows != null) {
|
|
||||||
foreach (Jid jid in shows.keys) objects.add_all(shows[jid]);
|
|
||||||
}
|
|
||||||
objects.sort((a, b) => {
|
|
||||||
DateTime? dt1 = null;
|
|
||||||
DateTime? dt2 = null;
|
|
||||||
Entities.Message m1 = a as Entities.Message;
|
|
||||||
if (m1 != null) dt1 = m1.time;
|
|
||||||
Show s1 = a as Show;
|
|
||||||
if (s1 != null) dt1 = s1.datetime;
|
|
||||||
Entities.Message m2 = b as Entities.Message;
|
|
||||||
if (m2 != null) dt2 = m2.time;
|
|
||||||
Show s2 = b as Show;
|
|
||||||
if (s2 != null) dt2 = s2.datetime;
|
|
||||||
return dt1.compare(dt2);
|
|
||||||
});
|
|
||||||
foreach (Object o in objects) {
|
|
||||||
Entities.Message message = o as Entities.Message;
|
|
||||||
Show show = o as Show;
|
|
||||||
if (message != null) {
|
|
||||||
show_message(message, conversation);
|
|
||||||
} else if (show != null) {
|
|
||||||
on_show_received(show, conversation.counterpart, conversation.account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update_chat_state();
|
|
||||||
stack.set_visible_child_name("main");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_received_state(Account account, Jid jid, string state) {
|
|
||||||
if (conversation != null && conversation.account.equals(account) && conversation.counterpart.equals_bare(jid)) {
|
|
||||||
update_chat_state(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_chat_state(string? state = null) {
|
|
||||||
string? state_ = state;
|
|
||||||
if (state_ == null) {
|
|
||||||
state_ = stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).get_chat_state(conversation.account, conversation.counterpart);
|
|
||||||
}
|
|
||||||
if (typing_status != null) {
|
|
||||||
main.remove(typing_status);
|
|
||||||
}
|
|
||||||
if (state_ != null) {
|
|
||||||
if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING || state_ == Xep.ChatStateNotifications.STATE_PAUSED) {
|
|
||||||
if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING) {
|
|
||||||
typing_status = new StatusItem(stream_interactor, conversation, _("is typing…"));
|
|
||||||
} else if (state_ == Xep.ChatStateNotifications.STATE_PAUSED) {
|
|
||||||
typing_status = new StatusItem(stream_interactor, conversation, _("has stopped typing"));
|
|
||||||
}
|
|
||||||
main.add(typing_status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_show_received(Show show, Jid jid, Account account) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_upper_notify() {
|
|
||||||
if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1 ||
|
|
||||||
scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size
|
|
||||||
scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down
|
|
||||||
} else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1) {
|
|
||||||
scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content
|
|
||||||
}
|
|
||||||
was_upper = scrolled.vadjustment.upper;
|
|
||||||
was_page_size = scrolled.vadjustment.page_size;
|
|
||||||
lock(reloading_lock) {
|
|
||||||
reloading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_value_notify() {
|
|
||||||
if (scrolled.vadjustment.value < 200) {
|
|
||||||
load_earlier_messages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load_earlier_messages() {
|
|
||||||
if (earliest_message == null) return;
|
|
||||||
|
|
||||||
was_value = scrolled.vadjustment.value;
|
|
||||||
lock(reloading_lock) {
|
|
||||||
if(reloading) return;
|
|
||||||
reloading = true;
|
|
||||||
}
|
|
||||||
Gee.List<Entities.Message>? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before(conversation, earliest_message);
|
|
||||||
if (messages != null && messages.size > 0) {
|
|
||||||
earliest_message = messages[0];
|
|
||||||
MergedMessageItem? current_item = null;
|
|
||||||
int items_added = 0;
|
|
||||||
for (int i = 0; i < messages.size; i++) {
|
|
||||||
if (current_item == null || !current_item.merge(messages[i])) {
|
|
||||||
current_item = new MergedMessageItem(stream_interactor, conversation, messages[i]);
|
|
||||||
force_alloc_width(current_item, main.get_allocated_width());
|
|
||||||
main.add(current_item);
|
|
||||||
conversation_items[messages[i]] = current_item;
|
|
||||||
main.reorder_child(current_item, items_added);
|
|
||||||
items_added++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reloading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void show_message(Entities.Message message, Conversation conversation, bool animate = false) {
|
|
||||||
if (this.conversation != null && this.conversation.equals(conversation)) {
|
|
||||||
if (last_conversation_item == null || !last_conversation_item.merge(message)) {
|
|
||||||
ConversationItem conversation_item = ConversationItem.create_for_message(stream_interactor, conversation, message);
|
|
||||||
if (animate) {
|
|
||||||
Revealer revealer = new Revealer() {transition_duration = 200, transition_type = RevealerTransitionType.SLIDE_UP, visible = true};
|
|
||||||
revealer.add(conversation_item);
|
|
||||||
force_alloc_width(revealer, main.get_allocated_width());
|
|
||||||
main.add(revealer);
|
|
||||||
revealer.set_reveal_child(true);
|
|
||||||
} else {
|
|
||||||
force_alloc_width(conversation_item, main.get_allocated_width());
|
|
||||||
main.add(conversation_item);
|
|
||||||
}
|
|
||||||
last_conversation_item = conversation_item;
|
|
||||||
}
|
|
||||||
conversation_items[message] = last_conversation_item;
|
|
||||||
update_chat_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround GTK TextView issues
|
|
||||||
private void force_alloc_width(Widget widget, int width) {
|
|
||||||
Allocation alloc = Allocation();
|
|
||||||
widget.get_preferred_width(out alloc.width, null);
|
|
||||||
widget.get_preferred_height(out alloc.height, null);
|
|
||||||
alloc.width = width;
|
|
||||||
widget.size_allocate(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear() {
|
|
||||||
main.@foreach((widget) => { main.remove(widget); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ public class UnifiedWindow : Window {
|
||||||
private ChatInput.View chat_input;
|
private ChatInput.View chat_input;
|
||||||
private ConversationListTitlebar conversation_list_titlebar;
|
private ConversationListTitlebar conversation_list_titlebar;
|
||||||
private ConversationSelector.View filterable_conversation_list;
|
private ConversationSelector.View filterable_conversation_list;
|
||||||
private ConversationSummary.View conversation_frame;
|
private ConversationSummary.ConversationView conversation_frame;
|
||||||
private ConversationTitlebar conversation_titlebar;
|
private ConversationTitlebar conversation_titlebar;
|
||||||
private HeaderBar placeholder_headerbar = new HeaderBar() { title="Dino", show_close_button=true, visible=true };
|
private HeaderBar placeholder_headerbar = new HeaderBar() { title="Dino", show_close_button=true, visible=true };
|
||||||
private Paned headerbar_paned = new Paned(Orientation.HORIZONTAL) { visible=true };
|
private Paned headerbar_paned = new Paned(Orientation.HORIZONTAL) { visible=true };
|
||||||
|
@ -69,7 +69,7 @@ public class UnifiedWindow : Window {
|
||||||
|
|
||||||
private void setup_unified() {
|
private void setup_unified() {
|
||||||
chat_input = new ChatInput.View(stream_interactor) { visible=true };
|
chat_input = new ChatInput.View(stream_interactor) { visible=true };
|
||||||
conversation_frame = new ConversationSummary.View(stream_interactor) { visible=true };
|
conversation_frame = new ConversationSummary.ConversationView(stream_interactor) { visible=true };
|
||||||
filterable_conversation_list = new ConversationSelector.View(stream_interactor) { visible=true };
|
filterable_conversation_list = new ConversationSelector.View(stream_interactor) { visible=true };
|
||||||
|
|
||||||
Grid grid = new Grid() { orientation=Orientation.VERTICAL, visible=true };
|
Grid grid = new Grid() { orientation=Orientation.VERTICAL, visible=true };
|
||||||
|
|
|
@ -119,8 +119,8 @@ public static void force_error_color(Gtk.Widget widget, string selector = "*") {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool is_dark_theme(Gtk.Widget widget) {
|
public static bool is_dark_theme(Gtk.Widget widget) {
|
||||||
Gdk.RGBA bg = widget.get_style_context().get_background_color(StateFlags.NORMAL);
|
Gdk.RGBA bg = widget.get_style_context().get_color(StateFlags.NORMAL);
|
||||||
return (bg.red < 0.5 && bg.green < 0.5 && bg.blue < 0.5);
|
return (bg.red > 0.5 && bg.green > 0.5 && bg.blue > 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool is_24h_format() {
|
public static bool is_24h_format() {
|
||||||
|
|
|
@ -5,7 +5,7 @@ private const string NS_URI = "vcard-temp";
|
||||||
private const string NS_URI_UPDATE = NS_URI + ":x:update";
|
private const string NS_URI_UPDATE = NS_URI + ":x:update";
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0027_current_pgp_usage");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0153_vcard_based_avatars");
|
||||||
|
|
||||||
public signal void received_avatar(XmppStream stream, string jid, string id);
|
public signal void received_avatar(XmppStream stream, string jid, string id);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Xmpp.Xep.StreamManagement {
|
||||||
public const string NS_URI = "urn:xmpp:sm:3";
|
public const string NS_URI = "urn:xmpp:sm:3";
|
||||||
|
|
||||||
public class Module : XmppStreamNegotiationModule {
|
public class Module : XmppStreamNegotiationModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0313_message_archive_management");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0198_stream_management");
|
||||||
|
|
||||||
public int h_inbound { get; private set; default=0; }
|
public int h_inbound { get; private set; default=0; }
|
||||||
public string? session_id { get; set; default=null; }
|
public string? session_id { get; set; default=null; }
|
||||||
|
|
Loading…
Reference in a new issue