aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2020-04-03 22:49:59 +0200
committerfiaxh <git@lightrise.org>2020-04-03 22:49:59 +0200
commit871ff33ac79f3d17b0260b8bfcd27780038edd6d (patch)
treef8079e29db5d0f9294fbfbfb47b93b0403249cc9 /main/src/ui
parent1c8e15c408f57d93461e6adb33c4c4415ac49267 (diff)
downloaddino-871ff33ac79f3d17b0260b8bfcd27780038edd6d.tar.gz
dino-871ff33ac79f3d17b0260b8bfcd27780038edd6d.zip
Add support for last message correction
Diffstat (limited to 'main/src/ui')
-rw-r--r--main/src/ui/chat_input/chat_input_controller.vala18
-rw-r--r--main/src/ui/chat_input/chat_text_view.vala91
-rw-r--r--main/src/ui/chat_input/edit_history.vala4
-rw-r--r--main/src/ui/chat_input/occupants_tab_completer.vala2
-rw-r--r--main/src/ui/chat_input/smiley_converter.vala2
-rw-r--r--main/src/ui/chat_input/view.vala52
-rw-r--r--main/src/ui/conversation_content_view/chat_state_populator.vala6
-rw-r--r--main/src/ui/conversation_content_view/content_item_widget_factory.vala114
-rw-r--r--main/src/ui/conversation_content_view/content_populator.vala39
-rw-r--r--main/src/ui/conversation_content_view/conversation_item_skeleton.vala20
-rw-r--r--main/src/ui/conversation_content_view/conversation_view.vala63
-rw-r--r--main/src/ui/conversation_content_view/date_separator_populator.vala6
-rw-r--r--main/src/ui/conversation_content_view/file_widget.vala18
-rw-r--r--main/src/ui/conversation_content_view/message_item.vala0
-rw-r--r--main/src/ui/conversation_content_view/message_widget.vala211
-rw-r--r--main/src/ui/conversation_selector/conversation_selector_row.vala6
-rw-r--r--main/src/ui/conversation_view_controller.vala13
-rw-r--r--main/src/ui/util/size_request_box.vala12
18 files changed, 445 insertions, 232 deletions
diff --git a/main/src/ui/chat_input/chat_input_controller.vala b/main/src/ui/chat_input/chat_input_controller.vala
index f65da1e8..c0878c36 100644
--- a/main/src/ui/chat_input/chat_input_controller.vala
+++ b/main/src/ui/chat_input/chat_input_controller.vala
@@ -17,18 +17,20 @@ public class ChatInputController : Object {
private StreamInteractor stream_interactor;
private Plugins.InputFieldStatus input_field_status;
+ private ChatTextViewController chat_text_view_controller;
public ChatInputController(ChatInput.View chat_input, StreamInteractor stream_interactor) {
this.chat_input = chat_input;
this.status_description_label = chat_input.chat_input_status;
this.stream_interactor = stream_interactor;
+ this.chat_text_view_controller = new ChatTextViewController(chat_input.chat_text_view, stream_interactor);
chat_input.init(stream_interactor);
reset_input_field_status();
- chat_input.text_input.buffer.changed.connect(on_text_input_changed);
- chat_input.send_text.connect(send_text);
+ chat_input.chat_text_view.text_view.buffer.changed.connect(on_text_input_changed);
+ chat_text_view_controller.send_text.connect(send_text);
chat_input.encryption_widget.encryption_changed.connect(on_encryption_changed);
@@ -40,10 +42,10 @@ public class ChatInputController : Object {
reset_input_field_status();
- chat_input.initialize_for_conversation(conversation);
- chat_input.occupants_tab_completor.initialize_for_conversation(conversation);
- chat_input.edit_history.initialize_for_conversation(conversation);
chat_input.encryption_widget.set_conversation(conversation);
+
+ chat_input.initialize_for_conversation(conversation);
+ chat_text_view_controller.initialize_for_conversation(conversation);
}
private void on_encryption_changed(Plugins.EncryptionListEntry? encryption_entry) {
@@ -81,8 +83,8 @@ public class ChatInputController : Object {
return;
}
- string text = chat_input.text_input.buffer.text;
- chat_input.text_input.buffer.text = "";
+ string text = chat_input.chat_text_view.text_view.buffer.text;
+ chat_input.chat_text_view.text_view.buffer.text = "";
if (text.has_prefix("/")) {
string[] token = text.split(" ", 2);
switch(token[0]) {
@@ -137,7 +139,7 @@ public class ChatInputController : Object {
}
private void on_text_input_changed() {
- if (chat_input.text_input.buffer.text != "") {
+ if (chat_input.chat_text_view.text_view.buffer.text != "") {
stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_entered(conversation);
} else {
stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation);
diff --git a/main/src/ui/chat_input/chat_text_view.vala b/main/src/ui/chat_input/chat_text_view.vala
new file mode 100644
index 00000000..1155d21d
--- /dev/null
+++ b/main/src/ui/chat_input/chat_text_view.vala
@@ -0,0 +1,91 @@
+using Gdk;
+using Gee;
+using Gtk;
+
+using Dino.Entities;
+using Xmpp;
+
+namespace Dino.Ui {
+
+public class ChatTextViewController : Object {
+
+ public signal void send_text();
+
+ public OccupantsTabCompletor occupants_tab_completor;
+
+ private ChatTextView widget;
+
+ public ChatTextViewController(ChatTextView widget, StreamInteractor stream_interactor) {
+ this.widget = widget;
+ occupants_tab_completor = new OccupantsTabCompletor(stream_interactor, widget.text_view);
+
+ widget.send_text.connect(() => {
+ send_text();
+ });
+ }
+
+ public void initialize_for_conversation(Conversation conversation) {
+ occupants_tab_completor.initialize_for_conversation(conversation);
+ widget.initialize_for_conversation(conversation);
+ }
+}
+
+public class ChatTextView : ScrolledWindow {
+
+ public signal void send_text();
+ public signal void cancel_input();
+
+ public TextView text_view = new TextView() { can_focus=true, hexpand=true, margin=8, wrap_mode=Gtk.WrapMode.WORD_CHAR, valign=Align.CENTER, visible=true };
+ private int vscrollbar_min_height;
+ private SmileyConverter smiley_converter;
+ public EditHistory edit_history;
+
+ construct {
+ max_content_height = 300;
+ propagate_natural_height = true;
+ this.add(text_view);
+
+ smiley_converter = new SmileyConverter(text_view);
+ edit_history = new EditHistory(text_view);
+
+ this.get_vscrollbar().get_preferred_height(out vscrollbar_min_height, null);
+ this.vadjustment.notify["upper"].connect_after(on_upper_notify);
+ text_view.key_press_event.connect(on_text_input_key_press);
+
+ Gtk.drag_dest_unset(text_view);
+ }
+
+ public void initialize_for_conversation(Conversation conversation) {
+ edit_history.initialize_for_conversation(conversation);
+ }
+
+ public override void get_preferred_height(out int min_height, out int nat_height) {
+ base.get_preferred_height(out min_height, out nat_height);
+ min_height = nat_height;
+ }
+
+ private void on_upper_notify() {
+ this.vadjustment.value = this.vadjustment.upper - this.vadjustment.page_size;
+
+ // hack for vscrollbar not requiring space and making textview higher //TODO doesn't resize immediately
+ this.get_vscrollbar().visible = (this.vadjustment.upper > this.max_content_height - 2 * this.vscrollbar_min_height);
+ }
+
+ private bool on_text_input_key_press(EventKey event) {
+ if (event.keyval in new uint[]{Key.Return, Key.KP_Enter}) {
+ if ((event.state & ModifierType.SHIFT_MASK) > 0) {
+ text_view.buffer.insert_at_cursor("\n", 1);
+ } else if (text_view.buffer.text != "") {
+ send_text();
+ edit_history.reset_history();
+ }
+ return true;
+ }
+ if (event.keyval == Key.Escape) {
+ cancel_input();
+ }
+ return false;
+ }
+}
+
+}
diff --git a/main/src/ui/chat_input/edit_history.vala b/main/src/ui/chat_input/edit_history.vala
index 1d179bb7..70f6d400 100644
--- a/main/src/ui/chat_input/edit_history.vala
+++ b/main/src/ui/chat_input/edit_history.vala
@@ -4,7 +4,7 @@ using Gtk;
using Dino.Entities;
-namespace Dino.Ui.ChatInput {
+namespace Dino.Ui {
public class EditHistory {
@@ -14,7 +14,7 @@ public class EditHistory {
private HashMap<Conversation, Gee.List<string>> histories = new HashMap<Conversation, Gee.List<string>>(Conversation.hash_func, Conversation.equals_func);
private HashMap<Conversation, int> indices = new HashMap<Conversation, int>(Conversation.hash_func, Conversation.equals_func);
- public EditHistory(TextView text_input, GLib.Application application) {
+ public EditHistory(TextView text_input) {
this.text_input = text_input;
text_input.key_press_event.connect(on_text_input_key_press);
diff --git a/main/src/ui/chat_input/occupants_tab_completer.vala b/main/src/ui/chat_input/occupants_tab_completer.vala
index 87db8986..ab1b75e0 100644
--- a/main/src/ui/chat_input/occupants_tab_completer.vala
+++ b/main/src/ui/chat_input/occupants_tab_completer.vala
@@ -5,7 +5,7 @@ using Gtk;
using Dino.Entities;
using Xmpp;
-namespace Dino.Ui.ChatInput {
+namespace Dino.Ui {
/**
* - With given prefix: Complete from occupant list (sorted lexicographically)
diff --git a/main/src/ui/chat_input/smiley_converter.vala b/main/src/ui/chat_input/smiley_converter.vala
index a25076ba..8f4dee9a 100644
--- a/main/src/ui/chat_input/smiley_converter.vala
+++ b/main/src/ui/chat_input/smiley_converter.vala
@@ -4,7 +4,7 @@ using Gtk;
using Dino.Entities;
-namespace Dino.Ui.ChatInput {
+namespace Dino.Ui {
class SmileyConverter {
diff --git a/main/src/ui/chat_input/view.vala b/main/src/ui/chat_input/view.vala
index 960e4e14..166ead2e 100644
--- a/main/src/ui/chat_input/view.vala
+++ b/main/src/ui/chat_input/view.vala
@@ -10,25 +10,17 @@ namespace Dino.Ui.ChatInput {
[GtkTemplate (ui = "/im/dino/Dino/chat_input.ui")]
public class View : Box {
- public signal void send_text();
-
public string text {
- owned get { return text_input.buffer.text; }
- set { text_input.buffer.text = value; }
+ owned get { return chat_text_view.text_view.buffer.text; }
+ set { chat_text_view.text_view.buffer.text = value; }
}
private StreamInteractor stream_interactor;
private Conversation? conversation;
private HashMap<Conversation, string> entry_cache = new HashMap<Conversation, string>(Conversation.hash_func, Conversation.equals_func);
- private int vscrollbar_min_height;
-
- public OccupantsTabCompletor occupants_tab_completor;
- private SmileyConverter smiley_converter;
- public EditHistory edit_history;
[GtkChild] public Frame frame;
- [GtkChild] public ScrolledWindow scrolled;
- [GtkChild] public TextView text_input;
+ [GtkChild] public ChatTextView chat_text_view;
[GtkChild] public Box outer_box;
[GtkChild] public Button file_button;
[GtkChild] public Separator file_separator;
@@ -39,9 +31,6 @@ public class View : Box {
public View init(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
- occupants_tab_completor = new OccupantsTabCompletor(stream_interactor, text_input);
- smiley_converter = new SmileyConverter(text_input);
- edit_history = new EditHistory(text_input, GLib.Application.get_default());
encryption_widget = new EncryptionButton(stream_interactor) { relief=ReliefStyle.NONE, margin_top=3, valign=Align.START, visible=true };
file_button.clicked.connect(() => {
@@ -53,9 +42,6 @@ public class View : Box {
});
file_button.get_style_context().add_class("dino-attach-button");
- scrolled.get_vscrollbar().get_preferred_height(out vscrollbar_min_height, null);
- scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify);
-
encryption_widget.get_style_context().add_class("dino-chatinput-button");
encryption_widget.encryption_changed.connect(update_file_transfer_availability);
@@ -68,7 +54,7 @@ public class View : Box {
EmojiChooser chooser = new EmojiChooser();
chooser.emoji_picked.connect((emoji) => {
- text_input.buffer.insert_at_cursor(emoji, emoji.data.length);
+ chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length);
});
emoji_button.set_popover(chooser);
@@ -77,8 +63,6 @@ public class View : Box {
outer_box.add(encryption_widget);
- text_input.key_press_event.connect(on_text_input_key_press);
-
Util.force_css(frame, "* { border-radius: 3px; }");
return this;
@@ -91,17 +75,17 @@ public class View : Box {
}
public void initialize_for_conversation(Conversation conversation) {
- if (this.conversation != null) entry_cache[this.conversation] = text_input.buffer.text;
+ if (this.conversation != null) entry_cache[this.conversation] = chat_text_view.text_view.buffer.text;
this.conversation = conversation;
update_file_transfer_availability();
- text_input.buffer.text = "";
+ chat_text_view.text_view.buffer.text = "";
if (entry_cache.has_key(conversation)) {
- text_input.buffer.text = entry_cache[conversation];
+ chat_text_view.text_view.buffer.text = entry_cache[conversation];
}
- text_input.grab_focus();
+ chat_text_view.text_view.grab_focus();
}
public void set_input_state(Plugins.InputFieldStatus.MessageType message_type) {
@@ -132,26 +116,6 @@ public class View : Box {
return false;
});
}
-
- private bool on_text_input_key_press(EventKey event) {
- if (event.keyval in new uint[]{Key.Return, Key.KP_Enter}) {
- if ((event.state & ModifierType.SHIFT_MASK) > 0) {
- text_input.buffer.insert_at_cursor("\n", 1);
- } else if (this.text != "") {
- send_text();
- edit_history.reset_history();
- }
- return true;
- }
- return false;
- }
-
- private void on_upper_notify() {
- scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size;
-
- // hack for vscrollbar not requiring space and making textview higher //TODO doesn't resize immediately
- scrolled.get_vscrollbar().visible = (scrolled.vadjustment.upper > scrolled.max_content_height - 2 * vscrollbar_min_height);
- }
}
}
diff --git a/main/src/ui/conversation_content_view/chat_state_populator.vala b/main/src/ui/conversation_content_view/chat_state_populator.vala
index 0438e014..545d2c6d 100644
--- a/main/src/ui/conversation_content_view/chat_state_populator.vala
+++ b/main/src/ui/conversation_content_view/chat_state_populator.vala
@@ -64,10 +64,6 @@ class ChatStatePopulator : Plugins.ConversationItemPopulator, Plugins.Conversati
private class MetaChatStateItem : Plugins.MetaConversationItem {
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=false; }
- public override bool requires_header { get; set; default=false; }
-
private StreamInteractor stream_interactor;
private Conversation conversation;
private Gee.List<Jid> jids = new ArrayList<Jid>();
@@ -93,6 +89,8 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
return image_content_box;
}
+ public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; }
+
public void set_new(Gee.List<Jid> jids) {
this.jids = jids;
update();
diff --git a/main/src/ui/conversation_content_view/content_item_widget_factory.vala b/main/src/ui/conversation_content_view/content_item_widget_factory.vala
deleted file mode 100644
index da092e34..00000000
--- a/main/src/ui/conversation_content_view/content_item_widget_factory.vala
+++ /dev/null
@@ -1,114 +0,0 @@
-using Gee;
-using Gdk;
-using Gtk;
-using Pango;
-using Xmpp;
-
-using Dino.Entities;
-
-namespace Dino.Ui.ConversationSummary {
-
-public class ContentItemWidgetFactory : Object {
-
- private StreamInteractor stream_interactor;
- private HashMap<string, WidgetGenerator> generators = new HashMap<string, WidgetGenerator>();
-
- public ContentItemWidgetFactory(StreamInteractor stream_interactor) {
- this.stream_interactor = stream_interactor;
-
- generators[MessageItem.TYPE] = new MessageItemWidgetGenerator(stream_interactor);
- generators[FileItem.TYPE] = new FileItemWidgetGenerator(stream_interactor);
- }
-
- public Widget? get_widget(ContentItem item) {
- WidgetGenerator? generator = generators[item.type_];
- if (generator != null) {
- return (Widget?) generator.get_widget(item);
- }
- return null;
- }
-
- public void register_widget_generator(WidgetGenerator generator) {
- generators[generator.handles_type] = generator;
- }
-}
-
-public interface WidgetGenerator : Object {
- public abstract string handles_type { get; set; }
- public abstract Object get_widget(ContentItem item);
-}
-
-public class MessageItemWidgetGenerator : WidgetGenerator, Object {
-
- public string handles_type { get; set; default=MessageItem.TYPE; }
-
- private StreamInteractor stream_interactor;
-
- public MessageItemWidgetGenerator(StreamInteractor stream_interactor) {
- this.stream_interactor = stream_interactor;
- }
-
- public Object get_widget(ContentItem item) {
- MessageItem message_item = item as MessageItem;
- Conversation conversation = message_item.conversation;
- Message message = message_item.message;
-
- Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true };
- string markup_text = message.body;
- if (markup_text.length > 10000) {
- markup_text = markup_text.substring(0, 10000) + " [" + _("Message too long") + "]";
- }
- if (message_item.message.body.has_prefix("/me")) {
- markup_text = markup_text.substring(3);
- }
-
- if (conversation.type_ == Conversation.Type.GROUPCHAT) {
- markup_text = Util.parse_add_markup(markup_text, conversation.nickname, true, true);
- } else {
- markup_text = Util.parse_add_markup(markup_text, null, true, true);
- }
-
- if (message_item.message.body.has_prefix("/me")) {
- string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from);
- update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text);
- label.realize.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text));
- label.style_updated.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text));
- }
-
- int only_emoji_count = Util.get_only_emoji_count(markup_text);
- if (only_emoji_count != -1) {
- string size_str = only_emoji_count < 5 ? "xx-large" : "large";
- markup_text = @"<span size=\'$size_str\'>" + markup_text + "</span>";
- }
-
- label.label = markup_text;
- return label;
- }
-
- public static void update_me_style(StreamInteractor stream_interactor, Jid jid, string display_name, Account account, Label label, string action_text) {
- string color = Util.get_name_hex_color(stream_interactor, account, jid, Util.is_dark_theme(label));
- label.label = @"<span color=\"#$(color)\">$(Markup.escape_text(display_name))</span>" + action_text;
- }
-}
-
-public class FileItemWidgetGenerator : WidgetGenerator, Object {
-
- public StreamInteractor stream_interactor;
- public string handles_type { get; set; default=FileItem.TYPE; }
-
- private const int MAX_HEIGHT = 300;
- private const int MAX_WIDTH = 600;
-
- public FileItemWidgetGenerator(StreamInteractor stream_interactor) {
- this.stream_interactor = stream_interactor;
- }
-
- public Object get_widget(ContentItem item) {
- FileItem file_item = item as FileItem;
- FileTransfer transfer = file_item.file_transfer;
-
- return new FileWidget(stream_interactor, transfer) { visible=true };
- }
-}
-
-}
diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala
index e8eee06c..2a0f8ac1 100644
--- a/main/src/ui/conversation_content_view/content_populator.vala
+++ b/main/src/ui/conversation_content_view/content_populator.vala
@@ -9,13 +9,11 @@ namespace Dino.Ui.ConversationSummary {
public class ContentProvider : ContentItemCollection, Object {
private StreamInteractor stream_interactor;
- private ContentItemWidgetFactory widget_factory;
private Conversation? current_conversation;
private Plugins.ConversationItemCollection? item_collection;
public ContentProvider(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
- this.widget_factory = new ContentItemWidgetFactory(stream_interactor);
}
public void init(Plugins.ConversationItemCollection item_collection, Conversation conversation, Plugins.WidgetType type) {
@@ -28,7 +26,7 @@ public class ContentProvider : ContentItemCollection, Object {
}
public void insert_item(ContentItem item) {
- item_collection.insert_item(new ContentMetaItem(item, widget_factory));
+ item_collection.insert_item(create_content_meta_item(item));
}
public void remove_item(ContentItem item) { }
@@ -38,7 +36,7 @@ public class ContentProvider : ContentItemCollection, Object {
Gee.List<ContentItem> items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_n_latest(conversation, n);
Gee.List<ContentMetaItem> ret = new ArrayList<ContentMetaItem>();
foreach (ContentItem item in items) {
- ret.add(new ContentMetaItem(item, widget_factory));
+ ret.add(create_content_meta_item(item));
}
return ret;
}
@@ -47,7 +45,7 @@ public class ContentProvider : ContentItemCollection, Object {
Gee.List<ContentMetaItem> ret = new ArrayList<ContentMetaItem>();
Gee.List<ContentItem> items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_before(conversation, before_item, n);
foreach (ContentItem item in items) {
- ret.add(new ContentMetaItem(item, widget_factory));
+ ret.add(create_content_meta_item(item));
}
return ret;
}
@@ -56,26 +54,30 @@ public class ContentProvider : ContentItemCollection, Object {
Gee.List<ContentMetaItem> ret = new ArrayList<ContentMetaItem>();
Gee.List<ContentItem> items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_after(conversation, after_item, n);
foreach (ContentItem item in items) {
- ret.add(new ContentMetaItem(item, widget_factory));
+ ret.add(create_content_meta_item(item));
}
return ret;
}
public ContentMetaItem get_content_meta_item(ContentItem content_item) {
- return new ContentMetaItem(content_item, widget_factory);
+ return create_content_meta_item(content_item);
+ }
+
+ private ContentMetaItem create_content_meta_item(ContentItem content_item) {
+ if (content_item.type_ == MessageItem.TYPE) {
+ return new MessageMetaItem(content_item, stream_interactor);
+ } else if (content_item.type_ == FileItem.TYPE) {
+ return new FileMetaItem(content_item, stream_interactor);
+ }
+ return null;
}
}
-public class ContentMetaItem : 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; }
+public abstract class ContentMetaItem : Plugins.MetaConversationItem {
public ContentItem content_item;
- private ContentItemWidgetFactory widget_factory;
- public ContentMetaItem(ContentItem content_item, ContentItemWidgetFactory widget_factory) {
+ protected ContentMetaItem(ContentItem content_item) {
this.jid = content_item.jid;
this.sort_time = content_item.sort_time;
this.seccondary_sort_indicator = (long) content_item.display_time.to_unix();
@@ -96,15 +98,6 @@ public class ContentMetaItem : Plugins.MetaConversationItem {
this.requires_header = true;
this.content_item = content_item;
- this.widget_factory = widget_factory;
- }
-
- 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 type) {
- return widget_factory.get_widget(content_item);
}
}
diff --git a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
index 8c59dde7..fe6c2dee 100644
--- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
+++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
@@ -15,6 +15,8 @@ public class ConversationItemSkeleton : EventBox {
public StreamInteractor stream_interactor;
public Conversation conversation { get; set; }
public Plugins.MetaConversationItem item;
+ public ContentMetaItem? content_meta_item = null;
+ public Widget? widget = null;
private Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true };
private Box header_content_box = new Box(Orientation.VERTICAL, 0) { visible=true };
@@ -25,9 +27,18 @@ public class ConversationItemSkeleton : EventBox {
this.stream_interactor = stream_interactor;
this.conversation = conversation;
this.item = item;
+ this.content_meta_item = item as ContentMetaItem;
this.get_style_context().add_class("message-box");
- Widget? widget = item.get_widget(Plugins.WidgetType.GTK) as Widget;
+ item.notify["in-edit-mode"].connect(() => {
+ if (item.in_edit_mode) {
+ this.get_style_context().add_class("edit-mode");
+ } else {
+ this.get_style_context().remove_class("edit-mode");
+ }
+ });
+
+ widget = item.get_widget(Plugins.WidgetType.GTK) as Widget;
if (widget != null) {
widget.valign = Align.END;
header_content_box.add(widget);
@@ -51,7 +62,12 @@ public class ConversationItemSkeleton : EventBox {
update_margin();
}
- public void update_margin() {
+ public void set_edit_mode() {
+ if (content_meta_item == null) return;
+
+ }
+
+ private void update_margin() {
if (item.requires_header && show_skeleton && metadata_header == null) {
metadata_header = new ItemMetaDataHeader(stream_interactor, conversation, item) { visible=true };
header_content_box.add(metadata_header);
diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala
index ac6df1fc..808c6cad 100644
--- a/main/src/ui/conversation_content_view/conversation_view.vala
+++ b/main/src/ui/conversation_content_view/conversation_view.vala
@@ -15,6 +15,8 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
[GtkChild] public ScrolledWindow scrolled;
[GtkChild] private Revealer notification_revealer;
[GtkChild] private Box message_menu_box;
+ [GtkChild] private Button button1;
+ [GtkChild] private Image button1_icon;
[GtkChild] private Box notifications;
[GtkChild] private Box main;
[GtkChild] private EventBox main_event_box;
@@ -29,7 +31,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
private Gee.List<ConversationItemSkeleton> item_skeletons = new Gee.ArrayList<ConversationItemSkeleton>();
private ContentProvider content_populator;
private SubscriptionNotitication subscription_notification;
- private bool enable_menu_box = false;
private double? was_value;
private double? was_upper;
@@ -40,8 +41,8 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
private bool firstLoad = true;
private bool at_current_content = true;
private bool reload_messages = true;
- Widget currently_highlighted = null;
- ContentItem current_highlighted_item = null;
+ ConversationItemSkeleton currently_highlighted = null;
+ ContentMetaItem? current_meta_item = null;
bool mouse_inside = false;
int last_y_root = -1;
@@ -67,6 +68,11 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
main_event_box.events = EventMask.POINTER_MOTION_MASK;
main_event_box.motion_notify_event.connect(on_motion_notify_event);
+ button1.clicked.connect(() => {
+ current_meta_item.get_item_actions(Plugins.WidgetType.GTK)[0].callback(button1, current_meta_item, currently_highlighted.widget);
+ update_message_menu();
+ });
+
return this;
}
@@ -95,7 +101,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
}
last_y_root = y_root;
- message_menu_box.visible = enable_menu_box;
// Get pointer location in main
int geometry_x, geometry_y, geometry_width, geometry_height, dest_x, dest_y;
@@ -106,21 +111,24 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
// Get widget under pointer
int h = 0;
bool @break = false;
- Widget? w = null;
+ ConversationItemSkeleton? w = null;
main.@foreach((widget) => {
if (break) return;
h += widget.get_allocated_height();
- w = widget;
+ w = widget as ConversationItemSkeleton;
if (h >= dest_y) {
@break = true;
return;
}
});
+ if (currently_highlighted != null) currently_highlighted.unset_state_flags(StateFlags.PRELIGHT);
+
if (w == null) {
- if (currently_highlighted != null) currently_highlighted.unset_state_flags(StateFlags.PRELIGHT);
currently_highlighted = null;
+ current_meta_item = null;
+ update_message_menu();
return;
}
@@ -129,23 +137,36 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
w.translate_coordinates(main, 0, 0, out widget_x, out widget_y);
// Get MessageItem
- var iter = widgets.map_iterator();
- while (iter.next()) {
- if (iter.get_value() == w) {
- Plugins.MetaConversationItem meta_item = iter.get_key();
- var meta_content_item = meta_item as ContentMetaItem;
- if (meta_content_item == null) return;
- current_highlighted_item = meta_content_item.content_item;
+ foreach (Plugins.MetaConversationItem item in item_item_skeletons.keys) {
+ if (item_item_skeletons[item] == w) {
+ current_meta_item = item as ContentMetaItem;
}
}
- // Highlight widget
- if (currently_highlighted != null) currently_highlighted.unset_state_flags(StateFlags.PRELIGHT);
- w.set_state_flags(StateFlags.PRELIGHT, true);
- currently_highlighted = w;
+ update_message_menu();
+
+ if (current_meta_item != null) {
+ // Highlight widget
+ w.set_state_flags(StateFlags.PRELIGHT, true);
+ currently_highlighted = w;
+
+ // Move message menu
+ message_menu_box.margin_top = widget_y - 10;
+ }
+ }
- // Move message menu
- message_menu_box.margin_top = widget_y - 10;
+ private void update_message_menu() {
+ if (current_meta_item == null) {
+ message_menu_box.visible = false;
+ return;
+ }
+
+ var actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK);
+ message_menu_box.visible = actions != null && actions.size > 0;
+ if (actions != null && actions.size == 1) {
+ button1.visible = true;
+ button1_icon.set_from_icon_name(actions[0].icon_name, IconSize.SMALL_TOOLBAR);
+ }
}
public void initialize_for_conversation(Conversation? conversation) {
@@ -163,8 +184,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
initialize_for_conversation_(conversation);
display_latest();
stack.set_visible_child_name("main");
-
- enable_menu_box = false;
}
public void initialize_around_message(Conversation conversation, ContentItem content_item) {
diff --git a/main/src/ui/conversation_content_view/date_separator_populator.vala b/main/src/ui/conversation_content_view/date_separator_populator.vala
index 3ddb0d9a..91485f25 100644
--- a/main/src/ui/conversation_content_view/date_separator_populator.vala
+++ b/main/src/ui/conversation_content_view/date_separator_populator.vala
@@ -54,10 +54,6 @@ class DateSeparatorPopulator : Plugins.ConversationItemPopulator, Plugins.Conver
public class MetaDateItem : Plugins.MetaConversationItem {
public override DateTime sort_time { get; set; }
- public override bool can_merge { get; set; default=false; }
- public override bool requires_avatar { get; set; default=false; }
- public override bool requires_header { get; set; default=false; }
-
private DateTime date;
public MetaDateItem(DateTime date) {
@@ -76,6 +72,8 @@ public class MetaDateItem : Plugins.MetaConversationItem {
return box;
}
+ public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; }
+
private static string get_relative_time(DateTime time) {
DateTime time_local = time.to_local();
DateTime now_local = new DateTime.now_local();
diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala
index f5ba08e3..ee14af7a 100644
--- a/main/src/ui/conversation_content_view/file_widget.vala
+++ b/main/src/ui/conversation_content_view/file_widget.vala
@@ -7,6 +7,24 @@ using Dino.Entities;
namespace Dino.Ui.ConversationSummary {
+public class FileMetaItem : ContentMetaItem {
+
+ private StreamInteractor stream_interactor;
+
+ public FileMetaItem(ContentItem content_item, StreamInteractor stream_interactor) {
+ base(content_item);
+ this.stream_interactor = stream_interactor;
+ }
+
+ public override Object? get_widget(Plugins.WidgetType type) {
+ FileItem file_item = content_item as FileItem;
+ FileTransfer transfer = file_item.file_transfer;
+ return new FileWidget(stream_interactor, transfer) { visible=true };
+ }
+
+ public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; }
+}
+
public class FileWidget : Box {
enum State {
diff --git a/main/src/ui/conversation_content_view/message_item.vala b/main/src/ui/conversation_content_view/message_item.vala
deleted file mode 100644
index e69de29b..00000000
--- a/main/src/ui/conversation_content_view/message_item.vala
+++ /dev/null
diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala
new file mode 100644
index 00000000..71094c71
--- /dev/null
+++ b/main/src/ui/conversation_content_view/message_widget.vala
@@ -0,0 +1,211 @@
+using Gee;
+using Gdk;
+using Gtk;
+using Pango;
+using Xmpp;
+
+using Dino.Entities;
+
+namespace Dino.Ui.ConversationSummary {
+
+public class MessageMetaItem : ContentMetaItem {
+
+ private StreamInteractor stream_interactor;
+ private MessageItemWidget message_item_widget;
+ private MessageItem message_item;
+
+ public MessageMetaItem(ContentItem content_item, StreamInteractor stream_interactor) {
+ base(content_item);
+ message_item = content_item as MessageItem;
+ this.stream_interactor = stream_interactor;
+ }
+
+ public override Object? get_widget(Plugins.WidgetType type) {
+ message_item_widget = new MessageItemWidget(stream_interactor, content_item) { visible=true };
+
+ message_item_widget.edit_cancelled.connect(() => { this.in_edit_mode = false; });
+ message_item_widget.edit_sent.connect(on_edit_send);
+
+ stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect(on_received_correction);
+
+ return message_item_widget;
+ }
+
+ public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
+ if (content_item as FileItem != null) return null;
+
+ bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
+ Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>();
+ if (allowed && !in_edit_mode) {
+ Plugins.MessageAction action1 = new Plugins.MessageAction();
+ action1.icon_name = "document-edit-symbolic";
+ action1.callback = (button, content_meta_item_activated, widget) => {
+ message_item_widget.set_edit_mode();
+ this.in_edit_mode = true;
+ };
+ actions.add(action1);
+ }
+ return actions;
+ }
+
+ private void on_edit_send(string text) {
+ stream_interactor.get_module(MessageCorrection.IDENTITY).send_correction(message_item.conversation, message_item.message, text);
+ this.in_edit_mode = false;
+ }
+
+ private void on_received_correction(ContentItem content_item) {
+ if (this.content_item.id == content_item.id) {
+ this.content_item = content_item;
+ message_item = content_item as MessageItem;
+ message_item_widget.content_item = content_item;
+ message_item_widget.update_label();
+ }
+ }
+}
+
+public class MessageItemWidget : SizeRequestBin {
+
+ public signal void edit_cancelled();
+ public signal void edit_sent(string text);
+
+ StreamInteractor stream_interactor;
+ public ContentItem content_item;
+
+ Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true };
+ MessageItemEditMode? edit_mode = null;
+ ChatTextViewController? controller = null;
+
+ ulong realize_id = -1;
+ ulong style_updated_id = -1;
+
+ construct {
+ this.add(label);
+ this.size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
+ }
+
+ public MessageItemWidget(StreamInteractor stream_interactor, ContentItem content_item) {
+ this.stream_interactor = stream_interactor;
+ this.content_item = content_item;
+
+ update_label();
+ }
+
+ public void set_edit_mode() {
+
+ MessageItem message_item = content_item as MessageItem;
+ Message message = message_item.message;
+
+ if (edit_mode == null) {
+ edit_mode = new MessageItemEditMode();
+ controller = new ChatTextViewController(edit_mode.chat_text_view, stream_interactor);
+ Conversation conversation = message_item.conversation;
+ controller.initialize_for_conversation(conversation);
+
+ edit_mode.cancelled.connect(() => {
+ edit_cancelled();
+ unset_edit_mode();
+ });
+ edit_mode.send.connect(() => {
+ edit_sent(edit_mode.chat_text_view.text_view.buffer.text);
+ unset_edit_mode();
+ });
+ }
+
+ edit_mode.chat_text_view.text_view.buffer.text = message.body;
+
+ this.remove(label);
+ this.add(edit_mode);
+
+ edit_mode.chat_text_view.text_view.grab_focus();
+ }
+
+ public void unset_edit_mode() {
+ this.remove(edit_mode);
+ this.add(label);
+ }
+
+ public void update_label() {
+ label.label = generate_markup_text(content_item);
+ }
+
+ private string generate_markup_text(ContentItem item) {
+ MessageItem message_item = item as MessageItem;
+ Conversation conversation = message_item.conversation;
+ Message message = message_item.message;
+
+ bool theme_dependent = false;
+
+ string markup_text = message.body;
+ if (markup_text.length > 10000) {
+ markup_text = markup_text.substring(0, 10000) + " [" + _("Message too long") + "]";
+ }
+ if (message.body.has_prefix("/me")) {
+ markup_text = markup_text.substring(3);
+ }
+
+ if (conversation.type_ == Conversation.Type.GROUPCHAT) {
+ markup_text = Util.parse_add_markup(markup_text, conversation.nickname, true, true);
+ } else {
+ markup_text = Util.parse_add_markup(markup_text, null, true, true);
+ }
+
+ if (message.body.has_prefix("/me")) {
+ string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from);
+ string color = Util.get_name_hex_color(stream_interactor, conversation.account, message.real_jid ?? message.from, Util.is_dark_theme(label));
+ markup_text = @"<span color=\"#$(color)\">$(Markup.escape_text(display_name))</span>" + markup_text;
+ theme_dependent = true;
+ }
+
+ int only_emoji_count = Util.get_only_emoji_count(markup_text);
+ if (only_emoji_count != -1) {
+ string size_str = only_emoji_count < 5 ? "xx-large" : "large";
+ markup_text = @"<span size=\'$size_str\'>" + markup_text + "</span>";
+ }
+
+ if (message.edit_to != null) {
+ string color = Util.is_dark_theme(label) ? "#808080" : "#909090";
+ markup_text += " <span size='small' color='%s'>(%s)</span>".printf(color, _("edited"));
+ theme_dependent = true;
+ }
+
+ if (theme_dependent && realize_id == -1) {
+ realize_id = label.realize.connect(update_label);
+ style_updated_id = label.style_updated.connect(update_label);
+ } else if (!theme_dependent && realize_id != -1) {
+ label.disconnect(realize_id);
+ label.disconnect(style_updated_id);
+ }
+ return markup_text;
+ }
+}
+
+[GtkTemplate (ui = "/im/dino/Dino/message_item_widget_edit_mode.ui")]
+public class MessageItemEditMode : Box {
+
+ public signal void cancelled();
+ public signal void send();
+
+ [GtkChild] public MenuButton emoji_button;
+ [GtkChild] public ChatTextView chat_text_view;
+ [GtkChild] public Button cancel_button;
+ [GtkChild] public Button send_button;
+ [GtkChild] public Frame frame;
+
+ construct {
+ Util.force_css(frame, "* { border-radius: 3px; }");
+
+ EmojiChooser chooser = new EmojiChooser();
+ chooser.emoji_picked.connect((emoji) => {
+ chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length);
+ });
+ emoji_button.set_popover(chooser);
+
+ cancel_button.clicked.connect(() => cancelled());
+ send_button.clicked.connect(() => send());
+ chat_text_view.cancel_input.connect(() => cancelled());
+ chat_text_view.send_text.connect(() => send());
+
+ }
+}
+
+}
diff --git a/main/src/ui/conversation_selector/conversation_selector_row.vala b/main/src/ui/conversation_selector/conversation_selector_row.vala
index 6234e9c8..6dc953df 100644
--- a/main/src/ui/conversation_selector/conversation_selector_row.vala
+++ b/main/src/ui/conversation_selector/conversation_selector_row.vala
@@ -86,6 +86,12 @@ public class ConversationSelectorRow : ListBoxRow {
content_item_received(item);
}
});
+ stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect((item) => {
+ if (last_content_item != null && last_content_item.id == item.id) {
+ content_item_received(item);
+ }
+ });
+
last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);
x_button.clicked.connect(() => {
diff --git a/main/src/ui/conversation_view_controller.vala b/main/src/ui/conversation_view_controller.vala
index abb8ab57..2e03de8f 100644
--- a/main/src/ui/conversation_view_controller.vala
+++ b/main/src/ui/conversation_view_controller.vala
@@ -40,7 +40,6 @@ public class ConversationViewController : Object {
view.conversation_frame.init(stream_interactor);
// drag 'n drop file upload
- Gtk.drag_dest_unset(view.chat_input.text_input);
Gtk.drag_dest_set(view, DestDefaults.ALL, target_list, Gdk.DragAction.COPY);
view.drag_data_received.connect(this.on_drag_data_received);
@@ -52,7 +51,9 @@ public class ConversationViewController : Object {
// goto-end floating button
var vadjustment = view.conversation_frame.scrolled.vadjustment;
vadjustment.notify["value"].connect(() => {
- view.goto_end_revealer.reveal_child = vadjustment.value < vadjustment.upper - vadjustment.page_size;
+ bool button_active = vadjustment.value < vadjustment.upper - vadjustment.page_size;
+ view.goto_end_revealer.reveal_child = button_active;
+ view.goto_end_revealer.visible = button_active;
});
view.goto_end_button.clicked.connect(() => {
view.conversation_frame.initialize_for_conversation(conversation);
@@ -158,9 +159,11 @@ public class ConversationViewController : Object {
if ((event.state & ModifierType.CONTROL_MASK) > 0) {
return false;
}
- view.chat_input.text_input.key_press_event(event);
- view.chat_input.text_input.grab_focus();
- return true;
+ if (view.chat_input.chat_text_view.text_view.key_press_event(event)) {
+ view.chat_input.chat_text_view.text_view.grab_focus();
+ return true;
+ }
+ return false;
}
}
}
diff --git a/main/src/ui/util/size_request_box.vala b/main/src/ui/util/size_request_box.vala
index c9adcb70..a2828262 100644
--- a/main/src/ui/util/size_request_box.vala
+++ b/main/src/ui/util/size_request_box.vala
@@ -1,11 +1,19 @@
using Gtk;
namespace Dino.Ui {
-class SizeRequestBox : Box {
+public class SizeRequestBox : Box {
public SizeRequestMode size_request_mode { get; set; default = SizeRequestMode.CONSTANT_SIZE; }
public override Gtk.SizeRequestMode get_request_mode() {
return size_request_mode;
}
}
-} \ No newline at end of file
+
+public class SizeRequestBin : Bin {
+ public SizeRequestMode size_request_mode { get; set; default = SizeRequestMode.CONSTANT_SIZE; }
+
+ public override Gtk.SizeRequestMode get_request_mode() {
+ return size_request_mode;
+ }
+}
+}