aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/conversation_content_view
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/ui/conversation_content_view')
-rw-r--r--main/src/ui/conversation_content_view/call_widget.vala37
-rw-r--r--main/src/ui/conversation_content_view/chat_state_populator.vala6
-rw-r--r--main/src/ui/conversation_content_view/conversation_item_skeleton.vala265
-rw-r--r--main/src/ui/conversation_content_view/conversation_view.vala133
-rw-r--r--main/src/ui/conversation_content_view/date_separator_populator.vala8
-rw-r--r--main/src/ui/conversation_content_view/file_default_widget.vala78
-rw-r--r--main/src/ui/conversation_content_view/file_image_widget.vala25
-rw-r--r--main/src/ui/conversation_content_view/file_widget.vala45
-rw-r--r--main/src/ui/conversation_content_view/message_item_widget.vala229
-rw-r--r--main/src/ui/conversation_content_view/message_widget.vala217
-rw-r--r--main/src/ui/conversation_content_view/subscription_notification.vala10
11 files changed, 589 insertions, 464 deletions
diff --git a/main/src/ui/conversation_content_view/call_widget.vala b/main/src/ui/conversation_content_view/call_widget.vala
index e45792e2..645c31c1 100644
--- a/main/src/ui/conversation_content_view/call_widget.vala
+++ b/main/src/ui/conversation_content_view/call_widget.vala
@@ -17,7 +17,7 @@ namespace Dino.Ui {
this.stream_interactor = stream_interactor;
}
- public override Object? get_widget(Plugins.WidgetType type) {
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
CallItem call_item = content_item as CallItem;
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call_item.call];
return new CallWidget(stream_interactor, call_item.call, call_state, call_item.conversation) { visible=true };
@@ -45,6 +45,7 @@ namespace Dino.Ui {
private Conversation conversation;
public Call.State call_state { get; set; } // needs to be public for binding
private uint time_update_handler_id = 0;
+ private ArrayList<Widget> multiparty_peer_widgets = new ArrayList<Widget>();
construct {
margin_top = 4;
@@ -58,11 +59,11 @@ namespace Dino.Ui {
this.call = call;
this.conversation = conversation;
- size_allocate.connect((allocation) => {
- if (allocation.height > parent.get_allocated_height()) {
- Idle.add(() => { parent.queue_resize(); return false; });
- }
- });
+// size_allocate.connect((allocation) => {
+// if (allocation.height > parent.get_allocated_height()) {
+// Idle.add(() => { parent.queue_resize(); return false; });
+// }
+// });
call.bind_property("state", this, "call-state");
this.notify["call-state"].connect(update_call_state);
@@ -88,16 +89,20 @@ namespace Dino.Ui {
if (call.state != Call.State.IN_PROGRESS && call.state != Call.State.ENDED) return;
if (call.counterparts.size <= 1 && conversation.type_ == Conversation.Type.CHAT) return;
- multiparty_peer_box.foreach((widget) => { multiparty_peer_box.remove(widget); });
+ foreach (Widget peer_widget in multiparty_peer_widgets) {
+ multiparty_peer_box.remove(peer_widget);
+ }
foreach (Jid counterpart in call.counterparts) {
AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid);
- multiparty_peer_box.add(image);
+ multiparty_peer_box.append(image);
+ multiparty_peer_widgets.add(image);
}
AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid);
- multiparty_peer_box.add(image2);
+ multiparty_peer_box.append(image2);
+ multiparty_peer_widgets.add(image2);
outer_additional_box.get_style_context().add_class("multiparty-participants");
@@ -121,7 +126,7 @@ namespace Dino.Ui {
switch (relevant_state) {
case Call.State.RINGING:
- image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-ring-symbolic");
if (call.direction == Call.DIRECTION_INCOMING) {
bool video = call_manager.should_we_send_video();
@@ -146,7 +151,7 @@ namespace Dino.Ui {
break;
case Call.State.ESTABLISHING:
case Call.State.IN_PROGRESS:
- image.set_from_icon_name("dino-phone-in-talk-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-in-talk-symbolic");
title_label.label = _("Call started");
string duration = get_duration_string((new DateTime.now_utc()).difference(call.local_time));
subtitle_label.label = _("Started %s ago").printf(duration);
@@ -162,13 +167,13 @@ namespace Dino.Ui {
break;
case Call.State.OTHER_DEVICE:
- image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-hangup-symbolic");
title_label.label = call.direction == Call.DIRECTION_INCOMING ? _("Incoming call") : _("Outgoing call");
subtitle_label.label = _("You handled this call on another device");
break;
case Call.State.ENDED:
- image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-hangup-symbolic");
title_label.label = _("Call ended");
string formated_end = Util.format_time(call.end_time, _("%H∶%M"), _("%l∶%M %p"));
string duration = get_duration_string(call.end_time.difference(call.local_time));
@@ -177,7 +182,7 @@ namespace Dino.Ui {
_("Lasted %s").printf(duration);
break;
case Call.State.MISSED:
- image.set_from_icon_name("dino-phone-missed-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-missed-symbolic");
title_label.label = _("Call missed");
if (call.direction == Call.DIRECTION_INCOMING) {
subtitle_label.label = _("You missed this call");
@@ -187,7 +192,7 @@ namespace Dino.Ui {
}
break;
case Call.State.DECLINED:
- image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-hangup-symbolic");
title_label.label = _("Call declined");
if (call.direction == Call.DIRECTION_INCOMING) {
subtitle_label.label = _("You declined this call");
@@ -197,7 +202,7 @@ namespace Dino.Ui {
}
break;
case Call.State.FAILED:
- image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR);
+ image.set_from_icon_name("dino-phone-hangup-symbolic");
title_label.label = _("Call failed");
subtitle_label.label = "Call failed to establish";
break;
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 247c83fe..0665caac 100644
--- a/main/src/ui/conversation_content_view/chat_state_populator.vala
+++ b/main/src/ui/conversation_content_view/chat_state_populator.vala
@@ -76,14 +76,14 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
this.jids = jids;
}
- public override Object? get_widget(Plugins.WidgetType widget_type) {
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
label = new Label("") { xalign=0, vexpand=true, visible=true };
label.get_style_context().add_class("dim-label");
image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true };
Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true };
- image_content_box.add(image);
- image_content_box.add(label);
+ image_content_box.append(image);
+ image_content_box.append(label);
update();
return image_content_box;
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 077440d6..3e4ce88b 100644
--- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
+++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
@@ -7,7 +7,16 @@ using Dino.Entities;
namespace Dino.Ui.ConversationSummary {
-public class ConversationItemSkeleton : EventBox {
+public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface, Object {
+
+ public Grid main_grid { get; set; }
+ public Label name_label { get; set; }
+ public Label time_label { get; set; }
+ public AvatarImage avatar_image { get; set; }
+ public Image encryption_image { get; set; }
+ public Image received_image { get; set; }
+
+ public Widget? content_widget = null;
public bool show_skeleton { get; set; default=false; }
public bool last_group_item { get; set; default=true; }
@@ -20,188 +29,130 @@ public class ConversationItemSkeleton : EventBox {
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 };
- private ItemMetaDataHeader? metadata_header = null;
- private AvatarImage? image = null;
+ private uint time_update_timeout = 0;
+ private ulong updated_roster_handler_id = 0;
public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item, bool initial_item) {
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");
-
- item.bind_property("in-edit-mode", this, "item-in-edit-mode");
- this.notify["item-in-edit-mode"].connect(update_edit_mode);
- item.bind_property("mark", this, "item-mark", BindingFlags.SYNC_CREATE);
- this.notify["item-mark"].connect(update_error_mode);
- update_error_mode();
+ Builder builder = new Builder.from_resource("/im/dino/Dino/conversation_item_widget.ui");
+ main_grid = (Grid) builder.get_object("main_grid");
+ main_grid.get_style_context().add_class("message-box");
+ name_label = (Label) builder.get_object("name_label");
+ time_label = (Label) builder.get_object("time_label");
+ avatar_image = (AvatarImage) builder.get_object("avatar_image");
+ encryption_image = (Image) builder.get_object("encrypted_image");
+ received_image = (Image) builder.get_object("marked_image");
- widget = item.get_widget(Plugins.WidgetType.GTK) as Widget;
+ widget = item.get_widget(this, Plugins.WidgetType.GTK4) as Widget;
if (widget != null) {
widget.valign = Align.END;
- header_content_box.add(widget);
+ set_widget(widget, Plugins.WidgetType.GTK4);
}
- image_content_box.add(header_content_box);
-
- if (initial_item) {
- this.add(image_content_box);
- } else {
- Revealer revealer = new Revealer() { transition_duration=200, transition_type=RevealerTransitionType.SLIDE_UP, reveal_child=false, visible=true };
- revealer.add_with_properties(image_content_box);
- this.add(revealer);
- revealer.reveal_child = true;
+ if (item.requires_header) {
+ avatar_image.set_conversation_participant(stream_interactor, conversation, item.jid);
}
-
this.notify["show-skeleton"].connect(update_margin);
this.notify["last-group-item"].connect(update_margin);
+ this.notify["show-skeleton"].connect(set_header);
update_margin();
}
- 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);
- header_content_box.reorder_child(metadata_header, 0);
- }
- if (item.requires_avatar && show_skeleton && image == null) {
- image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true, allow_gray = false };
- image.set_conversation_participant(stream_interactor, conversation, item.jid);
- image_content_box.add(image);
- image_content_box.reorder_child(image, 0);
- }
+ private void set_header() {
+ if (!show_skeleton || !item.requires_header) return;
+
+ update_name_label();
+// name_label.style_updated.connect(update_name_label);
+ updated_roster_handler_id = stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => {
+ if (this.conversation.account.equals(account) && this.conversation.counterpart.equals(jid)) {
+ update_name_label();
+ }
+ });
+
+ item.notify["encryption"].connect(update_encryption_icon);
+ update_encryption_icon();
- if (image != null) {
- image.visible = this.show_skeleton;
+ if (item.time != null) {
+ update_time();
}
- if (metadata_header != null) {
- metadata_header.visible = this.show_skeleton;
+
+ item.bind_property("mark", this, "item-mark", BindingFlags.SYNC_CREATE);
+ this.notify["item-mark"].connect_after(update_received_mark);
+ update_received_mark();
+ }
+
+ public void set_widget(Object object, Plugins.WidgetType type) {
+ if (content_widget != null) content_widget.unparent();
+
+ Widget widget = (Widget) object;
+ content_widget = widget;
+ main_grid.attach(widget, 1, 1, 4, 1);
+ }
+
+ private void update_margin() {
+ avatar_image.visible = show_skeleton;
+ name_label.visible = show_skeleton;
+ time_label.visible = show_skeleton;
+ encryption_image.visible = show_skeleton;
+ received_image.visible = show_skeleton;
+
+ if (show_skeleton) {
+ main_grid.get_style_context().add_class("has-skeleton");
}
- image_content_box.margin_start = this.show_skeleton ? 15 : 58;
- image_content_box.margin_end = 15;
- if (this.show_skeleton && this.last_group_item) {
- image_content_box.margin_top = 8;
- image_content_box.margin_bottom = 8;
- } else {
- image_content_box.margin_top = 4;
- image_content_box.margin_bottom = 4;
+ if (last_group_item) {
+ main_grid.get_style_context().add_class("last-group-item");
}
}
private void update_edit_mode() {
if (item.in_edit_mode) {
- this.get_style_context().add_class("edit-mode");
+ main_grid.get_style_context().add_class("edit-mode");
} else {
- this.get_style_context().remove_class("edit-mode");
+ main_grid.get_style_context().remove_class("edit-mode");
}
}
private void update_error_mode() {
if (item_mark == Message.Marked.ERROR) {
- this.get_style_context().add_class("error");
+ main_grid.get_style_context().add_class("error");
} else {
- this.get_style_context().remove_class("error");
- }
- }
-}
-
-[GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/item_metadata_header.ui")]
-public class ItemMetaDataHeader : Box {
- [GtkChild] public unowned Label name_label;
- [GtkChild] public unowned Label time_label;
- public Image received_image = new Image() { opacity=0.4 };
- public Widget? encryption_image = null;
-
- public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON", 17, 12);
-
- private StreamInteractor stream_interactor;
- private Conversation conversation;
- private Plugins.MetaConversationItem item;
- public Entities.Message.Marked item_mark { get; set; }
- private ArrayList<Plugins.MetaConversationItem> items = new ArrayList<Plugins.MetaConversationItem>();
- private uint time_update_timeout = 0;
- private ulong updated_roster_handler_id = 0;
-
- public ItemMetaDataHeader(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item) {
- this.stream_interactor = stream_interactor;
- this.conversation = conversation;
- this.item = item;
- items.add(item);
-
- update_name_label();
- name_label.style_updated.connect(update_name_label);
- updated_roster_handler_id = stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => {
- if (this.conversation.account.equals(account) && this.conversation.counterpart.equals(jid)) {
- update_name_label();
- }
- });
-
- conversation.notify["encryption"].connect(update_unencrypted_icon);
- item.notify["encryption"].connect(update_encryption_icon);
- update_encryption_icon();
-
- this.add(received_image);
-
- if (item.time != null) {
- update_time();
+ main_grid.get_style_context().remove_class("error");
}
-
- item.bind_property("mark", this, "item-mark");
- this.notify["item-mark"].connect_after(update_received_mark);
- update_received_mark();
}
private void update_encryption_icon() {
+ encryption_image.visible = true;
+
Application app = GLib.Application.get_default() as Application;
ContentMetaItem ci = item as ContentMetaItem;
if (item.encryption != Encryption.NONE && item.encryption != Encryption.UNKNOWN && ci != null) {
- Widget? widget = null;
+ string? icon_name = null;
foreach(var e in app.plugin_registry.encryption_list_entries) {
if (e.encryption == item.encryption) {
- widget = e.get_encryption_icon(conversation, ci.content_item) as Widget;
+ icon_name = e.get_encryption_icon_name(conversation, ci.content_item);
break;
}
}
- if (widget == null) {
- widget = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true };
- }
- update_encryption_image(widget);
- }
- if (item.encryption == Encryption.NONE) {
- update_unencrypted_icon();
+ encryption_image.icon_name = icon_name ?? "dino-changes-prevent-symbolic";
}
- }
-
- private void update_unencrypted_icon() {
- if (item.encryption != Encryption.NONE) return;
-
- if (conversation.encryption != Encryption.NONE && encryption_image == null) {
- Image image = new Image() { opacity=0.4, visible = true };
- image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER);
- image.tooltip_text = _("Unencrypted");
- update_encryption_image(image);
- Util.force_error_color(image);
- } else if (conversation.encryption == Encryption.NONE && encryption_image != null) {
- update_encryption_image(null);
- }
- }
- private void update_encryption_image(Widget? widget) {
- if (encryption_image != null) {
- this.remove(encryption_image);
- encryption_image = null;
- }
- if (widget != null) {
- this.add(widget);
- this.reorder_child(widget, 3);
- encryption_image = widget;
+ if (item.encryption == Encryption.NONE) {
+ if (conversation.encryption != Encryption.NONE) {
+ encryption_image.icon_name = "dino-changes-allowed-symbolic";
+ encryption_image.tooltip_text = _("Unencrypted");
+ Util.force_error_color(encryption_image);
+ } else if (conversation.encryption == Encryption.NONE) {
+ encryption_image.icon_name = null;
+ encryption_image.visible = false;
+ }
}
}
@@ -209,7 +160,7 @@ public class ItemMetaDataHeader : Box {
time_label.label = get_relative_time(item.time.to_local()).to_string();
time_update_timeout = Timeout.add_seconds((int) get_next_time_change(), () => {
- if (this.parent == null) return false;
+ if (this.main_grid.parent == null) return false;
update_time();
return false;
});
@@ -220,41 +171,15 @@ public class ItemMetaDataHeader : Box {
}
private void update_received_mark() {
- bool all_received = true;
- bool all_read = true;
- bool all_sent = 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", ICON_SIZE_HEADER);
- Util.force_error_color(received_image);
- Util.force_error_color(time_label);
- string error_text = _("Unable to send message");
- received_image.tooltip_text = error_text;
- time_label.tooltip_text = error_text;
- return;
- } else if (item.mark != Message.Marked.READ) {
- all_read = false;
- if (item.mark != Message.Marked.RECEIVED) {
- all_received = false;
- if (item.mark == Message.Marked.UNSENT) {
- all_sent = false;
- }
- }
- }
- }
- if (all_read) {
- received_image.visible = true;
- received_image.set_from_icon_name("dino-double-tick-symbolic", ICON_SIZE_HEADER);
- } else if (all_received) {
- received_image.visible = true;
- received_image.set_from_icon_name("dino-tick-symbolic", ICON_SIZE_HEADER);
- } else if (!all_sent) {
- received_image.visible = true;
- received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER);
- } else if (received_image.visible) {
- received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER);
-
+ switch (content_meta_item.mark) {
+ case Message.Marked.RECEIVED: received_image.icon_name = "dino-tick-symbolic"; break;
+ case Message.Marked.READ: received_image.icon_name = "dino-double-tick-symbolic"; break;
+ case Message.Marked.WONTSEND:
+ received_image.icon_name = "dialog-warning-symbolic";
+ received_image.icon_name = _("Unable to send message");
+ // TODO error color on marked icon and time
+ break;
+ default: received_image.icon_name = null; break;
}
}
@@ -311,6 +236,10 @@ public class ItemMetaDataHeader : Box {
}
}
+ public Widget get_widget() {
+ return main_grid;
+ }
+
public override void dispose() {
if (time_update_timeout != 0) {
Source.remove(time_update_timeout);
diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala
index d6cbed62..8d46281f 100644
--- a/main/src/ui/conversation_content_view/conversation_view.vala
+++ b/main/src/ui/conversation_content_view/conversation_view.vala
@@ -8,7 +8,7 @@ using Dino.Entities;
namespace Dino.Ui.ConversationSummary {
[GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/view.ui")]
-public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins.NotificationCollection {
+public class ConversationView : Widget, Plugins.ConversationItemCollection, Plugins.NotificationCollection {
public Conversation? conversation { get; private set; }
@@ -19,8 +19,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
[GtkChild] private unowned Image button1_icon;
[GtkChild] private unowned Box notifications;
[GtkChild] private unowned Box main;
- [GtkChild] private unowned EventBox main_event_box;
- [GtkChild] private unowned EventBox main_wrap_event_box;
+ [GtkChild] private unowned Box main_wrap_box;
[GtkChild] private unowned Stack stack;
private StreamInteractor stream_interactor;
@@ -41,9 +40,13 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
private bool firstLoad = true;
private bool at_current_content = true;
private bool reload_messages = true;
- ConversationItemSkeleton currently_highlighted = null;
+ Widget currently_highlighted = null;
ContentMetaItem? current_meta_item = null;
- int last_y_root = -1;
+ double last_y = -1;
+
+ construct {
+ this.layout_manager = new BinLayout();
+ }
public ConversationView init(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
@@ -64,20 +67,21 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
// we connect to the parent event box that also wraps the overlaying message_menu_box.
// This eliminates the unwanted leave events emitted on the main_event_box when hovering
// the overlaying menu buttons.
- main_wrap_event_box.events = EventMask.ENTER_NOTIFY_MASK;
- main_wrap_event_box.events = EventMask.LEAVE_NOTIFY_MASK;
- main_wrap_event_box.leave_notify_event.connect(on_leave_notify_event);
- main_wrap_event_box.enter_notify_event.connect(on_enter_notify_event);
+ EventControllerMotion main_wrap_motion_events = new EventControllerMotion();
+ main_wrap_box.add_controller(main_wrap_motion_events);
+ main_wrap_motion_events.leave.connect(on_leave_notify_event);
+ main_wrap_motion_events.enter.connect(update_highlight);
// The buttons of the overlaying message_menu_box may partially overlap the adjacent
// conversation items. We connect to the main_event_box directly to avoid emitting
// the pointer motion events as long as the pointer is above the message menu.
// This ensures that the currently highlighted item remains unchanged when the pointer
// reaches the overlapping part of a button.
- main_event_box.events = EventMask.POINTER_MOTION_MASK;
- main_event_box.motion_notify_event.connect(on_motion_notify_event);
+ EventControllerMotion main_motion_events = new EventControllerMotion();
+ main.add_controller(main_motion_events);
+ main_motion_events.motion.connect(update_highlight);
button1.clicked.connect(() => {
- current_meta_item.get_item_actions(Plugins.WidgetType.GTK)[0].callback(button1, current_meta_item, currently_highlighted.widget);
+ current_meta_item.get_item_actions(Plugins.WidgetType.GTK4)[0].callback(button1, current_meta_item, currently_highlighted);
update_message_menu();
});
@@ -102,66 +106,51 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
}
}
- private bool on_enter_notify_event(Gdk.EventCrossing event) {
- update_highlight((int)event.x_root, (int)event.y_root);
- return false;
- }
-
- private bool on_leave_notify_event(Gdk.EventCrossing event) {
+ private void on_leave_notify_event() {
if (currently_highlighted != null) {
- currently_highlighted.unset_state_flags(StateFlags.PRELIGHT);
+ currently_highlighted.get_style_context().remove_class("highlight");
currently_highlighted = null;
}
message_menu_box.visible = false;
- return false;
}
- private bool on_motion_notify_event(Gdk.EventMotion event) {
- update_highlight((int)event.x_root, (int)event.y_root);
- return false;
- }
-
- private void update_highlight(int x_root, int y_root) {
- if (currently_highlighted != null && (last_y_root - y_root).abs() <= 2) {
+ private void update_highlight(double x, double y) {
+ if (currently_highlighted != null && (last_y - y).abs() <= 2) {
return;
}
- last_y_root = y_root;
-
- int toplevel_window_pos_x, toplevel_window_pos_y, dest_x, dest_y;
- Widget toplevel_widget = this.get_toplevel();
- // Obtain the position of the main application window relative to the root window
- toplevel_widget.get_window().get_origin(out toplevel_window_pos_x, out toplevel_window_pos_y);
- // Get the pointer location relative to the `main` box
- toplevel_widget.translate_coordinates(main, x_root - toplevel_window_pos_x, y_root - toplevel_window_pos_y, out dest_x, out dest_y);
+ last_y = y;
// Get widget under pointer
int h = 0;
- ConversationItemSkeleton? w = null;
- foreach (Widget widget in main.get_children()) {
- h += widget.get_allocated_height();
- if (h >= dest_y) {
- w = widget as ConversationItemSkeleton;
+ Widget? w = null;
+ Plugins.MetaConversationItem? meta_item = null;
+ foreach (Plugins.MetaConversationItem item in meta_items) {
+ Widget widget = widgets[item];
+ h += widget.get_allocated_height() + widget.margin_top + widget.margin_bottom;
+ if (h >= y) {
+ w = widget;
break;
}
};
- if (currently_highlighted != null) currently_highlighted.unset_state_flags(StateFlags.PRELIGHT);
+ if (currently_highlighted != null) currently_highlighted.get_style_context().remove_class("highlight");
+
+ currently_highlighted = null;
+ current_meta_item = null;
if (w == null) {
- currently_highlighted = null;
- current_meta_item = null;
update_message_menu();
return;
}
// Get widget coordinates in main
- int widget_x, widget_y;
+ double widget_x, widget_y;
w.translate_coordinates(main, 0, 0, out widget_x, out widget_y);
// Get MessageItem
foreach (Plugins.MetaConversationItem item in item_item_skeletons.keys) {
- if (item_item_skeletons[item] == w) {
+ if (item_item_skeletons[item].get_widget() == w) {
current_meta_item = item as ContentMetaItem;
}
}
@@ -170,11 +159,11 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
if (current_meta_item != null) {
// Highlight widget
- w.set_state_flags(StateFlags.PRELIGHT, true);
currently_highlighted = w;
+ currently_highlighted.get_style_context().add_class("highlight");
// Move message menu
- message_menu_box.margin_top = widget_y - 10;
+ message_menu_box.margin_top = (int)(widget_y - 10);
}
}
@@ -184,11 +173,11 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
return;
}
- var actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK);
+ var actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
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);
+ button1_icon.set_from_icon_name(actions[0].icon_name);
}
}
@@ -235,15 +224,14 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
reload_messages = false;
Timeout.add(700, () => {
int h = 0, i = 0;
- bool @break = false;
- main.@foreach((widget) => {
- if (widget == w || @break) {
- @break = true;
- return;
+ foreach (Plugins.MetaConversationItem item in meta_items) {
+ Widget widget = widgets[item];
+ if (widget == w) {
+ break;
}
h += widget.get_allocated_height();
i++;
- });
+ }
scrolled.vadjustment.value = h - scrolled.vadjustment.page_size * 1/3;
w.get_style_context().add_class("highlight-once");
reload_messages = true;
@@ -270,9 +258,9 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
// Init for new conversation
foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) {
- populator.init(conversation, this, Plugins.WidgetType.GTK);
+ populator.init(conversation, this, Plugins.WidgetType.GTK4);
}
- content_populator.init(this, conversation, Plugins.WidgetType.GTK);
+ content_populator.init(this, conversation, Plugins.WidgetType.GTK4);
subscription_notification.init(conversation, this);
animate = false;
@@ -286,7 +274,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
}
Application app = GLib.Application.get_default() as Application;
foreach (Plugins.NotificationPopulator populator in app.plugin_registry.notification_populators) {
- populator.init(conversation, this, Plugins.WidgetType.GTK);
+ populator.init(conversation, this, Plugins.WidgetType.GTK4);
}
Idle.add(() => { on_value_notify(); return false; });
}
@@ -318,7 +306,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
private void remove_item(Plugins.MetaConversationItem item) {
ConversationItemSkeleton? skeleton = item_item_skeletons[item];
if (skeleton != null) {
- main.remove(skeleton);
+ main.remove(skeleton.get_widget());
widgets.unset(item);
item_skeletons.remove(skeleton);
item_item_skeletons.unset(item);
@@ -331,21 +319,21 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
}
public void on_add_meta_notification(Plugins.MetaConversationNotification notification) {
- Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK);
+ Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK4);
if (widget != null) {
add_notification(widget);
}
}
public void on_remove_meta_notification(Plugins.MetaConversationNotification notification){
- Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK);
+ Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK4);
if (widget != null) {
remove_notification(widget);
}
}
public void add_notification(Widget widget) {
- notifications.add(widget);
+ notifications.append(widget);
Timeout.add(20, () => {
notification_revealer.transition_duration = 200;
notification_revealer.reveal_child = true;
@@ -362,15 +350,14 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
// Fill datastructure
- ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item, !animate) { visible=true };
+ ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item, !animate);
item_item_skeletons[item] = item_skeleton;
int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0;
item_skeletons.insert(index, item_skeleton);
// Insert widget
- widgets[item] = item_skeleton;
- main.add(item_skeleton);
- main.reorder_child(item_skeleton, index);
+ widgets[item] = item_skeleton.get_widget();
+ widgets[item].insert_after(main, item_item_skeletons.has_key(lower_item) ? item_item_skeletons[lower_item].get_widget() : null);
if (lower_item != null) {
if (can_merge(item, lower_item)) {
@@ -405,7 +392,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
}
}
}
- return item_skeleton;
+ return item_skeleton.get_widget();
}
private bool can_merge(Plugins.MetaConversationItem upper_item /*more recent, displayed below*/, Plugins.MetaConversationItem lower_item /*less recent, displayed above*/) {
@@ -420,7 +407,11 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
private void on_upper_notify() {
if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size
if (at_current_content) {
- scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down
+ Idle.add(() => {
+ // If we do this directly without Idle.add, scrolling down doesn't work properly
+ scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down
+ return false;
+ });
}
} 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
@@ -482,12 +473,14 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
meta_items.clear();
item_skeletons.clear();
item_item_skeletons.clear();
+ foreach (Widget widget in widgets.values) {
+ main.remove(widget);
+ }
widgets.clear();
- main.@foreach((widget) => { main.remove(widget); });
}
private void clear_notifications() {
- notifications.@foreach((widget) => { notifications.remove(widget); });
+// notifications.@foreach((widget) => { notifications.remove(widget); });
notification_revealer.transition_duration = 0;
notification_revealer.set_reveal_child(false);
}
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 5f6838e5..40bf0693 100644
--- a/main/src/ui/conversation_content_view/date_separator_populator.vala
+++ b/main/src/ui/conversation_content_view/date_separator_populator.vala
@@ -61,7 +61,7 @@ public class MetaDateItem : Plugins.MetaConversationItem {
this.time = date;
}
- public override Object? get_widget(Plugins.WidgetType widget_type) {
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
return new DateSeparatorWidget(date);
}
@@ -86,9 +86,9 @@ public class DateSeparatorWidget : Box {
label = new Label("") { use_markup=true, halign=Align.CENTER, hexpand=false, visible=true };
label.get_style_context().add_class("dim-label");
- this.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true });
- this.add(label);
- this.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true });
+ this.append(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true });
+ this.append(label);
+ this.append(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true });
update_time();
}
diff --git a/main/src/ui/conversation_content_view/file_default_widget.vala b/main/src/ui/conversation_content_view/file_default_widget.vala
index 638dab15..3bd5842f 100644
--- a/main/src/ui/conversation_content_view/file_default_widget.vala
+++ b/main/src/ui/conversation_content_view/file_default_widget.vala
@@ -7,34 +7,49 @@ using Dino.Entities;
namespace Dino.Ui {
[GtkTemplate (ui = "/im/dino/Dino/file_default_widget.ui")]
-public class FileDefaultWidget : EventBox {
+public class FileDefaultWidget : Box {
+
+ public signal void open_file();
+ public signal void save_file_as();
+ public signal void cancel_download();
[GtkChild] public unowned Stack image_stack;
[GtkChild] public unowned Label name_label;
[GtkChild] public unowned Label mime_label;
[GtkChild] public unowned Image content_type_image;
[GtkChild] public unowned Spinner spinner;
- [GtkChild] public unowned EventBox stack_event_box;
[GtkChild] public unowned MenuButton file_menu;
- public ModelButton file_open_button;
- public ModelButton file_save_button;
- public ModelButton cancel_button;
-
private FileTransfer.State state;
+ class construct {
+ install_action("file.open", null, (widget, action_name) => { ((FileDefaultWidget) widget).open_file(); });
+ install_action("file.save_as", null, (widget, action_name) => { ((FileDefaultWidget) widget).save_file_as(); });
+ install_action("file.cancel", null, (widget, action_name) => { ((FileDefaultWidget) widget).cancel_download(); });
+ }
+
public FileDefaultWidget() {
- this.enter_notify_event.connect(on_pointer_entered_event);
- this.leave_notify_event.connect(on_pointer_left_event);
- file_open_button = new ModelButton() { text=_("Open"), visible=true };
- file_save_button = new ModelButton() { text=_("Save as…"), visible=true };
- cancel_button = new ModelButton() { text=_("Cancel"), visible=true };
+ EventControllerMotion this_motion_events = new EventControllerMotion();
+ this.add_controller(this_motion_events);
+ this_motion_events.enter.connect(on_pointer_entered_event);
+ this_motion_events.leave.connect(on_pointer_left_event);
+
+ GestureClick gesture_click_controller = new GestureClick();
+ this.add_controller(gesture_click_controller);
+ gesture_click_controller.pressed.connect((n_press, x, y) => {
+ // Check whether the click was inside the file menu. Otherwise, open the file.
+ double x_button, y_button;
+ this.translate_coordinates(file_menu, x, y, out x_button, out y_button);
+ if (file_menu.contains(x_button, y_button)) return;
+
+ this.open_file();
+ });
}
public void update_file_info(string? mime_type, FileTransfer.State state, long size) {
this.state = state;
- spinner.active = false; // A hidden spinning spinner still uses CPU. Deactivate asap
+ spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap
content_type_image.icon_name = get_file_icon_name(mime_type);
string? mime_description = mime_type != null ? ContentType.get_description(mime_type) : null;
@@ -45,33 +60,23 @@ public class FileDefaultWidget : EventBox {
image_stack.set_visible_child_name("content_type_image");
// Create a menu
- Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu();
- Box file_menu_box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true };
- file_menu_box.add(file_open_button);
- file_menu_box.add(file_save_button);
- popover_menu.add(file_menu_box);
+ Menu menu_model = new Menu();
+ menu_model.append(_("Open"), "file.open");
+ menu_model.append(_("Save as…"), "file.save_as");
+ Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model);
file_menu.popover = popover_menu;
- file_menu.button_release_event.connect(() => {
- popover_menu.visible = true;
- return true;
- });
popover_menu.closed.connect(on_pointer_left);
break;
case FileTransfer.State.IN_PROGRESS:
mime_label.label = _("Downloading %s…").printf(get_size_string(size));
- spinner.active = true;
+ spinner.start();
image_stack.set_visible_child_name("spinner");
// Create a menu
- Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu();
- Box file_menu_box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true };
- file_menu_box.add(cancel_button);
- popover_menu.add(file_menu_box);
+ Menu menu_model = new Menu();
+ menu_model.append(_("Cancel"), "file.cancel_download");
+ Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model);
file_menu.popover = popover_menu;
- file_menu.button_release_event.connect(() => {
- popover_menu.visible = true;
- return true;
- });
popover_menu.closed.connect(on_pointer_left);
break;
case FileTransfer.State.NOT_STARTED:
@@ -92,8 +97,8 @@ public class FileDefaultWidget : EventBox {
}
}
- private bool on_pointer_entered_event(Gdk.EventCrossing event) {
- event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.HAND2));
+ private void on_pointer_entered_event() {
+ this.set_cursor_from_name("pointer");
content_type_image.opacity = 0.7;
if (state == FileTransfer.State.NOT_STARTED) {
image_stack.set_visible_child_name("download_image");
@@ -101,16 +106,13 @@ public class FileDefaultWidget : EventBox {
if (state == FileTransfer.State.COMPLETE || state == FileTransfer.State.IN_PROGRESS) {
file_menu.opacity = 1;
}
- return false;
}
- private bool on_pointer_left_event(Gdk.EventCrossing event) {
- if (event.detail == Gdk.NotifyType.INFERIOR) return false;
- if (file_menu.popover != null && file_menu.popover.visible) return false;
+ private void on_pointer_left_event() {
+ if (file_menu.popover != null && file_menu.popover.visible) return;
- event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.XTERM));
+ this.set_cursor(null);
on_pointer_left();
- return false;
}
private void on_pointer_left() {
diff --git a/main/src/ui/conversation_content_view/file_image_widget.vala b/main/src/ui/conversation_content_view/file_image_widget.vala
index 91eddd93..aad220e8 100644
--- a/main/src/ui/conversation_content_view/file_image_widget.vala
+++ b/main/src/ui/conversation_content_view/file_image_widget.vala
@@ -6,7 +6,7 @@ using Dino.Entities;
namespace Dino.Ui {
-public class FileImageWidget : EventBox {
+public class FileImageWidget : Box {
private ScalingImage image;
FileDefaultWidget file_default_widget;
@@ -14,7 +14,6 @@ public class FileImageWidget : EventBox {
public FileImageWidget() {
this.halign = Align.START;
- this.events = EventMask.POINTER_MOTION_MASK;
this.get_style_context().add_class("file-image-widget");
}
@@ -36,6 +35,7 @@ public class FileImageWidget : EventBox {
pixbuf = pixbuf.apply_embedded_orientation();
image.load(pixbuf);
+ Picture picture = new Picture.for_pixbuf(pixbuf) { can_shrink=true, keep_aspect_ratio=true, halign=Align.START };
Idle.add(load_from_file.callback);
return image;
@@ -47,28 +47,29 @@ public class FileImageWidget : EventBox {
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
string? mime_type = file_info.get_content_type();
- file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false };
- file_default_widget.stack_event_box.visible = false;
+ file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false, visible=false };
+ file_default_widget.image_stack.visible = false;
file_default_widget_controller = new FileDefaultWidgetController(file_default_widget);
file_default_widget_controller.set_file(file, file_name, mime_type);
Overlay overlay = new Overlay() { visible=true };
- overlay.add(image);
+ overlay.set_child(image);
overlay.add_overlay(file_default_widget);
+ overlay.set_measure_overlay(image, true);
+ overlay.set_clip_overlay(file_default_widget, true);
- this.enter_notify_event.connect((event) => {
+ EventControllerMotion this_motion_events = new EventControllerMotion();
+ this.add_controller(this_motion_events);
+ this_motion_events.enter.connect(() => {
file_default_widget.visible = true;
- return false;
});
- this.leave_notify_event.connect((event) => {
- if (event.detail == Gdk.NotifyType.INFERIOR) return false;
- if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return false;
+ this_motion_events.leave.connect(() => {
+ if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return;
file_default_widget.visible = false;
- return false;
});
- this.add(overlay);
+ this.append(overlay);
}
}
diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala
index b63195dc..6378c298 100644
--- a/main/src/ui/conversation_content_view/file_widget.vala
+++ b/main/src/ui/conversation_content_view/file_widget.vala
@@ -16,7 +16,7 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem {
this.stream_interactor = stream_interactor;
}
- public override Object? get_widget(Plugins.WidgetType type) {
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
FileItem file_item = content_item as FileItem;
FileTransfer transfer = file_item.file_transfer;
return new FileWidget(stream_interactor, transfer) { visible=true };
@@ -51,11 +51,11 @@ public class FileWidget : SizeRequestBox {
this.file_transfer = file_transfer;
update_widget.begin();
- size_allocate.connect((allocation) => {
- if (allocation.height > parent.get_allocated_height()) {
- Idle.add(() => { parent.queue_resize(); return false; });
- }
- });
+// size_allocate.connect((allocation) => {
+// if (allocation.height > parent.get_allocated_height()) {
+// Idle.add(() => { parent.queue_resize(); return false; });
+// }
+// });
file_transfer.bind_property("state", this, "file-transfer-state");
file_transfer.bind_property("mime-type", this, "file-transfer-mime-type");
@@ -79,7 +79,7 @@ public class FileWidget : SizeRequestBox {
if (content != null) this.remove(content);
content = file_image_widget;
state = State.IMAGE;
- this.add(content);
+ this.append(content);
return;
} catch (Error e) { }
}
@@ -91,7 +91,7 @@ public class FileWidget : SizeRequestBox {
default_widget_controller.set_file_transfer(file_transfer, stream_interactor);
content = default_file_widget;
this.state = State.DEFAULT;
- this.add(content);
+ this.append(content);
}
}
@@ -128,10 +128,15 @@ public class FileDefaultWidgetController : Object {
public FileDefaultWidgetController(FileDefaultWidget widget) {
this.widget = widget;
- widget.button_release_event.connect(on_clicked);
- widget.file_open_button.clicked.connect(open_file);
- widget.file_save_button.clicked.connect(save_file);
- widget.cancel_button.clicked.connect(cancel_download);
+
+ widget.open_file.connect(open_file);
+ widget.save_file_as.connect(save_file);
+ widget.cancel_download.connect(cancel_download);
+
+ var gesture_controller = new GestureClick();
+ gesture_controller.set_button(1); // listen for left clicks
+ gesture_controller.released.connect(on_clicked);
+ widget.add_controller(gesture_controller);
}
public void set_file_transfer(FileTransfer file_transfer, StreamInteractor stream_interactor) {
@@ -173,30 +178,29 @@ public class FileDefaultWidgetController : Object {
}
private void save_file() {
- var save_dialog = new FileChooserNative(_("Save as…"), widget.get_toplevel() as Gtk.Window, FileChooserAction.SAVE, null, null);
- save_dialog.set_do_overwrite_confirmation(true);
+ var save_dialog = new FileChooserNative(_("Save as…"), widget.get_root() as Gtk.Window, FileChooserAction.SAVE, null, null);
save_dialog.set_modal(true);
save_dialog.set_current_name(file_name);
- if (save_dialog.run() == Gtk.ResponseType.ACCEPT) {
+ save_dialog.response.connect(() => {
try{
GLib.File.new_for_uri(file_uri).copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null);
} catch (Error err) {
warning("Failed copy file %s - %s", file_uri, err.message);
}
- }
+ });
+
+ save_dialog.show();
}
private void cancel_download() {
file_transfer.cancellable.cancel();
}
- private bool on_clicked(EventButton event_button) {
+ private void on_clicked() {
switch (state) {
case FileTransfer.State.COMPLETE:
- if (event_button.button == 1) {
- open_file();
- }
+ open_file();
break;
case FileTransfer.State.NOT_STARTED:
assert(stream_interactor != null && file_transfer != null);
@@ -206,7 +210,6 @@ public class FileDefaultWidgetController : Object {
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
break;
}
- return false;
}
}
diff --git a/main/src/ui/conversation_content_view/message_item_widget.vala b/main/src/ui/conversation_content_view/message_item_widget.vala
new file mode 100644
index 00000000..23a499d9
--- /dev/null
+++ b/main/src/ui/conversation_content_view/message_item_widget.vala
@@ -0,0 +1,229 @@
+using Dino.Entities;
+using Gtk;
+
+namespace Dino.Ui {
+ public class MessageItemWidget : SizeRequestBin {
+
+ public signal void edit_cancelled();
+ public signal void edit_sent(string text);
+
+ enum AdditionalInfo {
+ NONE,
+ PENDING,
+ DELIVERY_FAILED
+ }
+
+ StreamInteractor stream_interactor;
+ public ContentItem content_item;
+ public Message.Marked marked { get; set; }
+
+ 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;
+ AdditionalInfo additional_info = AdditionalInfo.NONE;
+
+ ulong realize_id = -1;
+ ulong style_updated_id = -1;
+ ulong marked_notify_handler_id = -1;
+
+ construct {
+ this.append(label);
+ label.activate_link.connect(on_label_activate_link);
+ 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;
+
+ Message message = ((MessageItem) content_item).message;
+ if (message.direction == Message.DIRECTION_SENT && !(message.marked in Message.MARKED_RECEIVED)) {
+ var binding = message.bind_property("marked", this, "marked");
+ marked_notify_handler_id = this.notify["marked"].connect(() => {
+ // Currently "pending", but not anymore
+ if (additional_info == AdditionalInfo.PENDING &&
+ message.marked != Message.Marked.SENDING && message.marked != Message.Marked.UNSENT) {
+ update_label();
+ }
+
+ // Currently "error", but not anymore
+ if (additional_info == AdditionalInfo.DELIVERY_FAILED && message.marked != Message.Marked.ERROR) {
+ update_label();
+ }
+
+ // Currently not error, but should be
+ if (additional_info != AdditionalInfo.DELIVERY_FAILED && message.marked == Message.Marked.ERROR) {
+ update_label();
+ }
+
+ // Nothing bad can happen anymore
+ if (message.marked in Message.MARKED_RECEIVED) {
+ binding.unbind();
+ this.disconnect(marked_notify_handler_id);
+ }
+ });
+ }
+
+ 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(() => {
+ if (((MessageItem) content_item).message.body != edit_mode.chat_text_view.text_view.buffer.text) {
+ edit_sent(edit_mode.chat_text_view.text_view.buffer.text);
+ } else {
+ edit_cancelled();
+ }
+ unset_edit_mode();
+ });
+ }
+
+ edit_mode.chat_text_view.text_view.buffer.text = message.body;
+
+ this.remove(label);
+ this.append(edit_mode);
+
+ edit_mode.chat_text_view.text_view.grab_focus();
+ }
+
+ public void unset_edit_mode() {
+ this.remove(edit_mode);
+ this.append(label);
+ label.grab_focus();
+ label.selectable = false;
+ label.selectable = true;
+ }
+
+ 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(4);
+ }
+
+ if (conversation.type_ == Conversation.Type.GROUPCHAT) {
+ markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
+ } else {
+ markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
+ }
+
+ if (message.body.has_prefix("/me ")) {
+ string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from);
+ markup_text = @"<i><b>$(Markup.escape_text(display_name))</b> " + markup_text + "</i>";
+ }
+
+ 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>";
+ }
+
+ string dim_color = Util.is_dark_theme(this) ? "#BDBDBD" : "#707070";
+
+ if (message.edit_to != null) {
+ markup_text += @" <span size='small' color='$dim_color'>(%s)</span>".printf(_("edited"));
+ theme_dependent = true;
+ }
+
+ // Append message status info
+ additional_info = AdditionalInfo.NONE;
+ if (message.direction == Message.DIRECTION_SENT && (message.marked == Message.Marked.SENDING || message.marked == Message.Marked.UNSENT)) {
+ // Append "pending..." iff message has not been sent yet
+ if (message.time.compare(new DateTime.now_utc().add_seconds(-10)) < 0) {
+ markup_text += @" <span size='small' color='$dim_color'>%s</span>".printf(_("pending…"));
+ theme_dependent = true;
+ additional_info = AdditionalInfo.PENDING;
+ } else {
+ int time_diff = (- (int) message.time.difference(new DateTime.now_utc()) / 1000);
+ Timeout.add(10000 - time_diff, () => {
+ update_label();
+ return false;
+ });
+ }
+ } else if (message.direction == Message.DIRECTION_SENT && message.marked == Message.Marked.ERROR) {
+ // Append "delivery failed" if there was a server error
+ string error_color = Util.rgba_to_hex(Util.get_label_pango_color(label, "@error_color"));
+ markup_text += " <span size='small' color='%s'>%s</span>".printf(error_color, _("delivery failed"));
+ theme_dependent = true;
+ additional_info = AdditionalInfo.DELIVERY_FAILED;
+ }
+
+ 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;
+ }
+
+ public static bool on_label_activate_link(string uri) {
+ // Always handle xmpp URIs with Dino
+ if (!uri.has_prefix("xmpp:")) return false;
+ File file = File.new_for_uri(uri);
+ Dino.Application.get_default().open(new File[]{file}, "");
+ return true;
+ }
+ }
+
+ [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 unowned MenuButton emoji_button;
+ [GtkChild] public unowned ChatTextView chat_text_view;
+ [GtkChild] public unowned Button cancel_button;
+ [GtkChild] public unowned Button send_button;
+ [GtkChild] public unowned 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);
+
+ chat_text_view.text_view.buffer.changed.connect_after(on_text_view_changed);
+
+ cancel_button.clicked.connect(() => cancelled());
+ send_button.clicked.connect(() => send());
+ chat_text_view.cancel_input.connect(() => cancelled());
+ chat_text_view.send_text.connect(() => send());
+ }
+
+ private void on_text_view_changed() {
+ send_button.sensitive = chat_text_view.text_view.buffer.text != "";
+ }
+ }
+} \ No newline at end of file
diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala
index e7bd1282..3ebce0ee 100644
--- a/main/src/ui/conversation_content_view/message_widget.vala
+++ b/main/src/ui/conversation_content_view/message_widget.vala
@@ -10,84 +10,16 @@ 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);
-
- this.notify["in-edit-mode"].connect(() => {
- if (in_edit_mode == false) return;
- bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
- if (allowed) {
- message_item_widget.set_edit_mode();
- } else {
- this.in_edit_mode = false;
- }
- });
-
- 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) => {
- 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);
-
enum AdditionalInfo {
NONE,
PENDING,
DELIVERY_FAILED
}
- StreamInteractor stream_interactor;
- public ContentItem content_item;
+ private StreamInteractor stream_interactor;
+ private MessageItem message_item;
public Message.Marked marked { get; set; }
- 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;
AdditionalInfo additional_info = AdditionalInfo.NONE;
@@ -96,15 +28,14 @@ public class MessageItemWidget : SizeRequestBin {
ulong style_updated_id = -1;
ulong marked_notify_handler_id = -1;
- construct {
- this.add(label);
- label.activate_link.connect(on_label_activate_link);
- this.size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
- }
+ public Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, vexpand=true, can_focus=false };
- public MessageItemWidget(StreamInteractor stream_interactor, ContentItem content_item) {
+ public MessageMetaItem(ContentItem content_item, StreamInteractor stream_interactor) {
+ base(content_item);
+ message_item = content_item as MessageItem;
this.stream_interactor = stream_interactor;
- this.content_item = content_item;
+
+ label.activate_link.connect(on_label_activate_link);
Message message = ((MessageItem) content_item).message;
if (message.direction == Message.DIRECTION_SENT && !(message.marked in Message.MARKED_RECEIVED)) {
@@ -137,51 +68,6 @@ public class MessageItemWidget : SizeRequestBin {
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(() => {
- if (((MessageItem) content_item).message.body != edit_mode.chat_text_view.text_view.buffer.text) {
- edit_sent(edit_mode.chat_text_view.text_view.buffer.text);
- } else {
- edit_cancelled();
- }
- 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);
- label.grab_focus();
- label.selectable = false;
- label.selectable = true;
- }
-
- 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;
@@ -198,9 +84,9 @@ public class MessageItemWidget : SizeRequestBin {
}
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
- markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
+ markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this.label), ref theme_dependent);
} else {
- markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
+ markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this.label), ref theme_dependent);
}
if (message.body.has_prefix("/me ")) {
@@ -214,7 +100,7 @@ public class MessageItemWidget : SizeRequestBin {
markup_text = @"<span size=\'$size_str\'>" + markup_text + "</span>";
}
- string dim_color = Util.is_dark_theme(this) ? "#BDBDBD" : "#707070";
+ string dim_color = Util.is_dark_theme(this.label) ? "#BDBDBD" : "#707070";
if (message.edit_to != null) {
markup_text += @" <span size='small' color='$dim_color'>(%s)</span>".printf(_("edited"));
@@ -246,7 +132,7 @@ public class MessageItemWidget : SizeRequestBin {
if (theme_dependent && realize_id == -1) {
realize_id = label.realize.connect(update_label);
- style_updated_id = label.style_updated.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);
@@ -254,6 +140,83 @@ public class MessageItemWidget : SizeRequestBin {
return markup_text;
}
+ public void update_label() {
+ label.label = generate_markup_text(content_item);
+ }
+
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
+
+ stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect(on_received_correction);
+
+ this.notify["in-edit-mode"].connect(() => {
+ if (in_edit_mode == false) return;
+ bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
+ if (allowed) {
+ MessageItem message_item = content_item as MessageItem;
+ Message message = message_item.message;
+
+ 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(() => {
+ in_edit_mode = false;
+ outer.set_widget(label, Plugins.WidgetType.GTK4);
+ label.grab_focus();
+ });
+ edit_mode.send.connect(() => {
+ if (((MessageItem) content_item).message.body != edit_mode.chat_text_view.text_view.buffer.text) {
+ on_edit_send(edit_mode.chat_text_view.text_view.buffer.text);
+ } else {
+// edit_cancelled();
+ }
+ in_edit_mode = false;
+ outer.set_widget(label, Plugins.WidgetType.GTK4);
+ label.grab_focus();
+ });
+
+ edit_mode.chat_text_view.text_view.buffer.text = message.body;
+
+ outer.set_widget(edit_mode, Plugins.WidgetType.GTK4);
+ edit_mode.chat_text_view.text_view.grab_focus();
+ } else {
+ this.in_edit_mode = false;
+ }
+ });
+
+ return label;
+ }
+
+ 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) => {
+ 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;
+ update_label();
+ }
+ }
+
public static bool on_label_activate_link(string uri) {
// Always handle xmpp URIs with Dino
if (!uri.has_prefix("xmpp:")) return false;
@@ -276,7 +239,7 @@ public class MessageItemEditMode : Box {
[GtkChild] public unowned Frame frame;
construct {
- Util.force_css(frame, "* { border-radius: 3px; }");
+ Util.force_css(frame, "* { border-radius: 3px; padding: 5px 7px; }");
EmojiChooser chooser = new EmojiChooser();
chooser.emoji_picked.connect((emoji) => {
diff --git a/main/src/ui/conversation_content_view/subscription_notification.vala b/main/src/ui/conversation_content_view/subscription_notification.vala
index d493ff78..1f0f39d8 100644
--- a/main/src/ui/conversation_content_view/subscription_notification.vala
+++ b/main/src/ui/conversation_content_view/subscription_notification.vala
@@ -34,8 +34,8 @@ public class SubscriptionNotitication : Object {
private void show_notification() {
Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true };
- Button accept_button = new Button() { label=_("Accept"), visible=true };
- Button deny_button = new Button() { label=_("Deny"), visible=true };
+ Button accept_button = new Button.with_label(_("Accept")) { visible=true };
+ Button deny_button = new Button.with_label(_("Deny")) { visible=true };
GLib.Application app = GLib.Application.get_default();
accept_button.clicked.connect(() => {
app.activate_action("accept-subscription", conversation.id);
@@ -45,9 +45,9 @@ public class SubscriptionNotitication : Object {
app.activate_action("deny-subscription", conversation.id);
conversation_view.remove_notification(box);
});
- box.add(new Label(_("This contact would like to add you to their contact list")) { margin_end=10, visible=true });
- box.add(accept_button);
- box.add(deny_button);
+ box.append(new Label(_("This contact would like to add you to their contact list")) { margin_end=10, visible=true });
+ box.append(accept_button);
+ box.append(deny_button);
conversation_view.add_notification(box);
}
}