aboutsummaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
Diffstat (limited to 'main')
-rw-r--r--main/CMakeLists.txt7
-rw-r--r--main/data/unified_main_content.ui78
-rw-r--r--main/src/ui/conversation_content_view/conversation_item_skeleton.vala2
-rw-r--r--main/src/ui/conversation_content_view/conversation_view.vala106
-rw-r--r--main/src/ui/conversation_content_view/file_default_widget.vala9
-rw-r--r--main/src/ui/conversation_content_view/file_image_widget.vala55
-rw-r--r--main/src/ui/conversation_content_view/file_widget.vala132
-rw-r--r--main/src/ui/conversation_content_view/message_widget.vala3
-rw-r--r--main/src/ui/global_search.vala2
-rw-r--r--main/src/ui/main_window.vala8
-rw-r--r--main/src/ui/main_window_controller.vala20
-rw-r--r--main/src/ui/util/scaling_image.vala131
-rw-r--r--main/src/ui/widgets/fixed_ratio_picture.vala88
-rw-r--r--main/src/ui/widgets/natural_size_increase.vala59
14 files changed, 420 insertions, 280 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 363b4185..b8f9d942 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -112,6 +112,9 @@ set(MAIN_DEFINITIONS)
if(GTK4_VERSION VERSION_GREATER_EQUAL "4.6")
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_6)
endif()
+if(GTK4_VERSION VERSION_GREATER_EQUAL "4.8")
+ set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_8)
+endif()
if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.2")
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_2)
endif()
@@ -204,7 +207,9 @@ SOURCES
src/ui/util/label_hybrid.vala
src/ui/util/sizing_bin.vala
src/ui/util/size_request_box.vala
- src/ui/util/scaling_image.vala
+
+ src/ui/widgets/fixed_ratio_picture.vala
+ src/ui/widgets/natural_size_increase.vala
CUSTOM_VAPIS
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
${CMAKE_BINARY_DIR}/exports/qlite.vapi
diff --git a/main/data/unified_main_content.ui b/main/data/unified_main_content.ui
index a66275de..d8ba93be 100644
--- a/main/data/unified_main_content.ui
+++ b/main/data/unified_main_content.ui
@@ -54,44 +54,60 @@
<object class="GtkBox" id="right_box">
<property name="orientation">vertical</property>
<child>
- <object class="GtkOverlay">
- <property name="child">
- <object class="GtkStack" id="right_stack">
+ <object class="AdwFlap" id="search_flap">
+ <property name="flap-position">end</property>
+ <property name="modal">true</property>
+ <property name="locked">true</property>
+ <property name="swipe-to-open">false</property>
+ <property name="fold-threshold-policy">natural</property>
+ <property name="hexpand">true</property>
+ <child type="content">
+ <object class="DinoUiNaturalSizeIncrease">
+ <property name="min-natural-width">600</property>
<child>
- <object class="GtkStackPage">
- <property name="name">content</property>
- <property name="child">
- <object class="DinoUiConversationView" id="conversation_view">
+ <object class="GtkStack" id="right_stack">
+ <property name="hexpand">false</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">content</property>
+ <property name="child">
+ <object class="DinoUiConversationView" id="conversation_view">
+ </object>
+ </property>
</object>
- </property>
- </object>
- </child>
- <child>
- <object class="GtkStackPage">
- <property name="name">placeholder</property>
- <property name="child">
- <object class="AdwStatusPage">
- <property name="icon-name">im.dino.Dino-symbolic</property>
- <property name="hexpand">True</property>
- <property name="vexpand">True</property>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">placeholder</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">im.dino.Dino-symbolic</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ </property>
</object>
- </property>
+ </child>
</object>
</child>
</object>
- </property>
- <child type="overlay">
- <object class="GtkRevealer" id="search_revealer">
- <property name="halign">end</property>
- <property name="transition-type">slide-left</property>
- <style>
- <class name="dino-sidebar"/>
- </style>
- <property name="child">
- <object class="GtkFrame" id="search_frame">
- <property name="width-request">400</property>
+ </child>
+ <child type="separator">
+ <object class="GtkSeparator" />
+ </child>
+ <child type="flap">
+ <object class="AdwClamp">
+ <property name="hexpand">false</property>
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">400</property>
+ <child>
+ <object class="AdwBin" id="search_frame">
+ <property name="hexpand">true</property>
+ <style>
+ <class name="background"/>
+ </style>
</object>
- </property>
+ </child>
</object>
</child>
</object>
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 00c88db3..3a68c9dc 100644
--- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
+++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala
@@ -37,7 +37,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
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) {
+ public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item) {
this.stream_interactor = stream_interactor;
this.conversation = conversation;
this.item = item;
diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala
index 5481cfc5..4d978132 100644
--- a/main/src/ui/conversation_content_view/conversation_view.vala
+++ b/main/src/ui/conversation_content_view/conversation_view.vala
@@ -24,7 +24,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
private StreamInteractor stream_interactor;
- private Gee.TreeSet<Plugins.MetaConversationItem> content_items = new Gee.TreeSet<Plugins.MetaConversationItem>(compare_meta_items);
+ private Gee.TreeSet<ContentMetaItem> content_items = new Gee.TreeSet<ContentMetaItem>(compare_content_meta_items);
private Gee.TreeSet<Plugins.MetaConversationItem> meta_items = new TreeSet<Plugins.MetaConversationItem>(compare_meta_items);
private Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton> item_item_skeletons = new Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton>();
private Gee.HashMap<Plugins.MetaConversationItem, Widget> widgets = new Gee.HashMap<Plugins.MetaConversationItem, Widget>();
@@ -37,7 +37,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
private double? was_page_size;
private Mutex reloading_mutex = Mutex();
- private bool animate = false;
private bool firstLoad = true;
private bool at_current_content = true;
private bool reload_messages = true;
@@ -82,6 +81,15 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
main.add_controller(main_motion_events);
main_motion_events.motion.connect(update_highlight);
+ // Process touch events and capture phase to allow highlighting a message without cursor
+ GestureClick click_controller = new GestureClick();
+ click_controller.touch_only = true;
+ click_controller.propagation_phase = Gtk.PropagationPhase.CAPTURE;
+ main_wrap_box.add_controller(click_controller);
+ click_controller.pressed.connect_after((n, x, y) => {
+ update_highlight(x, y);
+ });
+
return this;
}
@@ -200,6 +208,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
MenuButton button = new MenuButton();
button.icon_name = message_actions[i].icon_name;
button.set_popover(message_actions[i].popover as Popover);
+ button.tooltip_text = Util.string_if_tooltips_active(message_actions[i].tooltip);
action_buttons.add(button);
}
@@ -210,6 +219,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
button.clicked.connect(() => {
message_action.callback(button, current_meta_item, currently_highlighted);
});
+ button.tooltip_text = Util.string_if_tooltips_active(message_actions[i].tooltip);
action_buttons.add(button);
}
}
@@ -232,12 +242,71 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
});
firstLoad = false;
}
+ if (conversation == this.conversation && at_current_content) {
+ // Just make sure we are scrolled down
+ if (scrolled.vadjustment.value != scrolled.vadjustment.upper) {
+ scroll_animation(scrolled.vadjustment.upper).play();
+ }
+ return;
+ }
clear();
initialize_for_conversation_(conversation);
display_latest();
+ at_current_content = true;
+ // Scroll to end
+ scrolled.vadjustment.value = scrolled.vadjustment.upper;
+ }
+
+ private void scroll_and_highlight_item(Plugins.MetaConversationItem target, uint duration = 500) {
+ Widget widget = null;
+ int h = 0;
+ foreach (Plugins.MetaConversationItem item in meta_items) {
+ widget = widgets[item];
+ if (target == item) {
+ break;
+ }
+ h += widget.get_allocated_height();
+ }
+ if (widget != widgets[target]) {
+ warning("Target item widget not reached");
+ return;
+ }
+ double target_height = h - scrolled.vadjustment.page_size * 1/3;
+ Adw.Animation animation = scroll_animation(target_height);
+ animation.done.connect(() => {
+ widget.remove_css_class("highlight-once");
+ widget.add_css_class("highlight-once");
+ Timeout.add(5000, () => {
+ widget.remove_css_class("highlight-once");
+ return false;
+ });
+ });
+ animation.play();
+ }
+
+ private Adw.Animation scroll_animation(double target) {
+#if ADW_1_2
+ return new Adw.TimedAnimation(scrolled, scrolled.vadjustment.value, target, 500,
+ new Adw.PropertyAnimationTarget(scrolled.vadjustment, "value")
+ );
+#else
+ return new Adw.TimedAnimation(scrolled, scrolled.vadjustment.value, target, 500,
+ new Adw.CallbackAnimationTarget(value => {
+ scrolled.vadjustment.value = value;
+ })
+ );
+#endif
+
}
public void initialize_around_message(Conversation conversation, ContentItem content_item) {
+ if (conversation == this.conversation) {
+ ContentMetaItem? matching_item = content_items.first_match(it => it.content_item.id == content_item.id);
+ if (matching_item != null) {
+ scroll_and_highlight_item(matching_item);
+ return;
+ }
+ }
clear();
initialize_for_conversation_(conversation);
Gee.List<ContentMetaItem> before_items = content_populator.populate_before(conversation, content_item, 40);
@@ -245,7 +314,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
do_insert_item(item);
}
ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item);
- meta_item.can_merge = false;
Widget w = insert_new(meta_item);
content_items.add(meta_item);
meta_items.add(meta_item);
@@ -261,23 +329,16 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
// Compute where to jump to for centered message, jump, highlight.
reload_messages = false;
Timeout.add(700, () => {
- int h = 0, i = 0;
- 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.add_css_class("highlight-once");
+ scroll_and_highlight_item(meta_item, 300);
reload_messages = true;
return false;
});
}
private void initialize_for_conversation_(Conversation? conversation) {
+ if (this.conversation == conversation) {
+ print("Re-initialized for %s\n", conversation.counterpart.bare_jid.to_string());
+ }
// Deinitialize old conversation
Dino.Application app = Dino.Application.get_default();
if (this.conversation != null) {
@@ -299,9 +360,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
}
content_populator.init(this, conversation, Plugins.WidgetType.GTK4);
subscription_notification.init(conversation, this);
-
- animate = false;
- Timeout.add(20, () => { animate = true; return false; });
}
private void display_latest() {
@@ -331,8 +389,8 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
public void do_insert_item(Plugins.MetaConversationItem item) {
lock (meta_items) {
insert_new(item);
- if (item as ContentMetaItem != null) {
- content_items.add(item);
+ if (item is ContentMetaItem) {
+ content_items.add((ContentMetaItem)item);
}
meta_items.add(item);
}
@@ -348,7 +406,9 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
widget_order.remove(skeleton.get_widget());
item_item_skeletons.unset(item);
- content_items.remove(item);
+ if (item is ContentMetaItem) {
+ content_items.remove((ContentMetaItem)item);
+ }
meta_items.remove(item);
}
@@ -387,7 +447,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
// Fill datastructure
- ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item, !animate);
+ ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item);
item_item_skeletons[item] = item_skeleton;
int index = lower_item != null ? widget_order.index_of(item_item_skeletons[lower_item].get_widget()) + 1 : 0;
widget_order.insert(index, item_skeleton.get_widget());
@@ -503,6 +563,10 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
}
}
+ private static int compare_content_meta_items(ContentMetaItem a, ContentMetaItem b) {
+ return compare_meta_items(a, b);
+ }
+
private static int compare_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) {
int cmp1 = a.time.compare(b.time);
if (cmp1 != 0) return cmp1;
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 9efc130f..352c8d7a 100644
--- a/main/src/ui/conversation_content_view/file_default_widget.vala
+++ b/main/src/ui/conversation_content_view/file_default_widget.vala
@@ -10,9 +10,6 @@ namespace Dino.Ui {
public class FileDefaultWidget : Box {
public signal void clicked();
- 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;
@@ -23,12 +20,6 @@ public class FileDefaultWidget : Box {
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() {
EventControllerMotion this_motion_events = new EventControllerMotion();
this.add_controller(this_motion_events);
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 ec8481b7..505c46a0 100644
--- a/main/src/ui/conversation_content_view/file_image_widget.vala
+++ b/main/src/ui/conversation_content_view/file_image_widget.vala
@@ -8,41 +8,70 @@ namespace Dino.Ui {
public class FileImageWidget : Box {
- FileDefaultWidget file_default_widget;
- FileDefaultWidgetController file_default_widget_controller;
-
public FileImageWidget() {
this.halign = Align.START;
this.add_css_class("file-image-widget");
+ this.set_cursor_from_name("zoom-in");
}
public async void load_from_file(File file, string file_name, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error {
- FixedRatioPicture image = new FixedRatioPicture() { min_width = 100, min_height = 100, max_width = MAX_WIDTH, max_height = MAX_HEIGHT, file = file };
+ Gtk.Box image_overlay_toolbar = new Gtk.Box(Orientation.HORIZONTAL, 0) { halign=Gtk.Align.END, valign=Gtk.Align.START, margin_top=10, margin_start=10, margin_end=10, margin_bottom=10, vexpand=false, visible=false };
+ image_overlay_toolbar.add_css_class("card");
+ image_overlay_toolbar.add_css_class("toolbar");
+ image_overlay_toolbar.add_css_class("overlay-toolbar");
+ image_overlay_toolbar.set_cursor_from_name("default");
+
+ FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=MAX_WIDTH, max_height=MAX_HEIGHT, file=file };
+ GestureClick gesture_click_controller = new GestureClick();
+ gesture_click_controller.button = 1; // listen for left clicks
+ gesture_click_controller.released.connect((n_press, x, y) => {
+ switch (gesture_click_controller.get_device().source) {
+ case Gdk.InputSource.TOUCHSCREEN:
+ case Gdk.InputSource.PEN:
+ if (n_press == 1) {
+ image_overlay_toolbar.visible = !image_overlay_toolbar.visible;
+ } else if (n_press == 2) {
+ this.activate_action("file.open", null);
+ image_overlay_toolbar.visible = false;
+ }
+ break;
+ default:
+ this.activate_action("file.open", null);
+ image_overlay_toolbar.visible = false;
+ break;
+ }
+ });
+ image.add_controller(gesture_click_controller);
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, 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);
+ MenuButton button = new MenuButton();
+ button.icon_name = "open-menu";
+ 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);
+ button.popover = popover_menu;
+
+ image_overlay_toolbar.append(button);
Overlay overlay = new Overlay();
overlay.set_child(image);
- overlay.add_overlay(file_default_widget);
+ overlay.add_overlay(image_overlay_toolbar);
overlay.set_measure_overlay(image, true);
- overlay.set_clip_overlay(file_default_widget, true);
+ overlay.set_clip_overlay(image_overlay_toolbar, true);
EventControllerMotion this_motion_events = new EventControllerMotion();
this.add_controller(this_motion_events);
this_motion_events.enter.connect(() => {
- file_default_widget.visible = true;
+ image_overlay_toolbar.visible = true;
});
this_motion_events.leave.connect(() => {
- if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return;
+ if (button.popover != null && button.popover.visible) return;
- file_default_widget.visible = false;
+ image_overlay_toolbar.visible = false;
});
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 52a26f33..8c36475a 100644
--- a/main/src/ui/conversation_content_view/file_widget.vala
+++ b/main/src/ui/conversation_content_view/file_widget.vala
@@ -21,7 +21,9 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem {
}
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
- return new FileWidget(stream_interactor, file_transfer);
+ FileWidget widget = new FileWidget(file_transfer);
+ FileWidgetController widget_controller = new FileWidgetController(widget, file_transfer, stream_interactor);
+ return widget;
}
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
@@ -57,7 +59,6 @@ public class FileWidget : SizeRequestBox {
DEFAULT
}
- private StreamInteractor stream_interactor;
private FileTransfer file_transfer;
public FileTransfer.State file_transfer_state { get; set; }
public string file_transfer_mime_type { get; set; }
@@ -66,13 +67,24 @@ public class FileWidget : SizeRequestBox {
private FileDefaultWidgetController default_widget_controller;
private Widget? content = null;
+ public signal void open_file();
+ public signal void save_file_as();
+ public signal void start_download();
+ public signal void cancel_download();
+
+ class construct {
+ install_action("file.open", null, (widget, action_name) => { ((FileWidget) widget).open_file(); });
+ install_action("file.save_as", null, (widget, action_name) => { ((FileWidget) widget).save_file_as(); });
+ install_action("file.download", null, (widget, action_name) => { ((FileWidget) widget).start_download(); });
+ install_action("file.cancel", null, (widget, action_name) => { ((FileWidget) widget).cancel_download(); });
+ }
+
construct {
margin_top = 4;
size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
}
- public FileWidget(StreamInteractor stream_interactor, FileTransfer file_transfer) {
- this.stream_interactor = stream_interactor;
+ public FileWidget(FileTransfer file_transfer) {
this.file_transfer = file_transfer;
update_widget.begin();
@@ -113,7 +125,7 @@ public class FileWidget : SizeRequestBox {
if (content != null) this.remove(content);
FileDefaultWidget default_file_widget = new FileDefaultWidget();
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
- default_widget_controller.set_file_transfer(file_transfer, stream_interactor);
+ default_widget_controller.set_file_transfer(file_transfer);
content = default_file_widget;
this.state = State.DEFAULT;
this.append(content);
@@ -138,94 +150,104 @@ public class FileWidget : SizeRequestBox {
}
}
-public class FileDefaultWidgetController : Object {
-
- private FileDefaultWidget widget;
- private FileTransfer? file_transfer;
- public string file_transfer_path { get; set; }
- public string file_transfer_state { get; set; }
- public string file_transfer_mime_type { get; set; }
+public class FileWidgetController : Object {
+ private weak Widget widget;
+ private FileTransfer file_transfer;
private StreamInteractor? stream_interactor;
- private string file_uri;
- private string file_name;
- private FileTransfer.State state;
- public FileDefaultWidgetController(FileDefaultWidget widget) {
+ public FileWidgetController(FileWidget widget, FileTransfer file_transfer, StreamInteractor? stream_interactor = null) {
this.widget = widget;
+ this.ref();
+ this.widget.weak_ref(() => {
+ this.widget = null;
+ this.unref();
+ });
+ this.file_transfer = file_transfer;
+ this.stream_interactor = stream_interactor;
- widget.clicked.connect(on_clicked);
widget.open_file.connect(open_file);
widget.save_file_as.connect(save_file);
+ widget.start_download.connect(start_download);
widget.cancel_download.connect(cancel_download);
}
- public void set_file_transfer(FileTransfer file_transfer, StreamInteractor stream_interactor) {
- this.file_transfer = file_transfer;
- this.stream_interactor = stream_interactor;
-
- widget.name_label.label = file_name = file_transfer.file_name;
-
- file_transfer.bind_property("path", this, "file-transfer-path");
- file_transfer.bind_property("state", this, "file-transfer-state");
- file_transfer.bind_property("mime-type", this, "file-transfer-mime-type");
-
- this.notify["file-transfer-path"].connect(update_file_info);
- this.notify["file-transfer-state"].connect(update_file_info);
- this.notify["file-transfer-mime-type"].connect(update_file_info);
-
- update_file_info();
- }
-
- public void set_file(File file, string file_name, string? mime_type) {
- file_uri = file.get_uri();
- state = FileTransfer.State.COMPLETE;
- widget.name_label.label = this.file_name = file_name;
- widget.update_file_info(mime_type, state, -1);
- }
-
- private void update_file_info() {
- file_uri = file_transfer.get_file().get_uri();
- state = file_transfer.state;
- widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size);
- }
-
private void open_file() {
try{
- AppInfo.launch_default_for_uri(file_uri, null);
+ AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null);
} catch (Error err) {
- warning("Failed to open %s - %s", file_uri, err.message);
+ warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message);
}
}
private void save_file() {
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);
+ save_dialog.set_current_name(file_transfer.file_name);
save_dialog.response.connect(() => {
try{
- GLib.File.new_for_uri(file_uri).copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null);
+ GLib.File.new_for_uri(file_transfer.get_file().get_uri()).copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null);
} catch (Error err) {
- warning("Failed copy file %s - %s", file_uri, err.message);
+ warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message);
}
});
save_dialog.show();
}
+ private void start_download() {
+ if (stream_interactor != null) {
+ stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer);
+ }
+ }
+
private void cancel_download() {
file_transfer.cancellable.cancel();
}
+}
+
+public class FileDefaultWidgetController : Object {
+
+ private FileDefaultWidget widget;
+ private FileTransfer? file_transfer;
+ public string file_transfer_state { get; set; }
+ public string file_transfer_mime_type { get; set; }
+
+ private FileTransfer.State state;
+
+ public FileDefaultWidgetController(FileDefaultWidget widget) {
+ this.widget = widget;
+
+ widget.clicked.connect(on_clicked);
+
+ this.notify["file-transfer-state"].connect(update_file_info);
+ this.notify["file-transfer-mime-type"].connect(update_file_info);
+ }
+
+ public void set_file_transfer(FileTransfer file_transfer) {
+ this.file_transfer = file_transfer;
+
+ widget.name_label.label = file_transfer.file_name;
+
+ file_transfer.bind_property("state", this, "file-transfer-state");
+ file_transfer.bind_property("mime-type", this, "file-transfer-mime-type");
+
+ update_file_info();
+ }
+
+ private void update_file_info() {
+ state = file_transfer.state;
+ widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size);
+ }
private void on_clicked() {
switch (state) {
case FileTransfer.State.COMPLETE:
- open_file();
+ widget.activate_action("file.open", null);
break;
case FileTransfer.State.NOT_STARTED:
- assert(stream_interactor != null && file_transfer != null);
- stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer);
+ widget.activate_action("file.download", null);
break;
default:
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala
index fb4ba162..900525fe 100644
--- a/main/src/ui/conversation_content_view/message_widget.vala
+++ b/main/src/ui/conversation_content_view/message_widget.vala
@@ -217,6 +217,7 @@ public class MessageMetaItem : ContentMetaItem {
if (correction_allowed) {
Plugins.MessageAction action1 = new Plugins.MessageAction();
action1.icon_name = "document-edit-symbolic";
+ action1.tooltip = _("Edit message");
action1.callback = (button, content_meta_item_activated, widget) => {
this.in_edit_mode = true;
};
@@ -225,6 +226,7 @@ public class MessageMetaItem : ContentMetaItem {
Plugins.MessageAction reply_action = new Plugins.MessageAction();
reply_action.icon_name = "mail-reply-sender-symbolic";
+ reply_action.tooltip = _("Reply");
reply_action.callback = (button, content_meta_item_activated, widget) => {
GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(content_item.id) }));
};
@@ -233,6 +235,7 @@ public class MessageMetaItem : ContentMetaItem {
if (supports_reaction) {
Plugins.MessageAction action2 = new Plugins.MessageAction();
action2.icon_name = "dino-emoticon-add-symbolic";
+ action2.tooltip = _("Add reaction");
EmojiChooser chooser = new EmojiChooser();
chooser.emoji_picked.connect((emoji) => {
stream_interactor.get_module(Reactions.IDENTITY).add_reaction(message_item.conversation, message_item, emoji);
diff --git a/main/src/ui/global_search.vala b/main/src/ui/global_search.vala
index aaf41b08..d206220d 100644
--- a/main/src/ui/global_search.vala
+++ b/main/src/ui/global_search.vala
@@ -151,6 +151,8 @@ public class GlobalSearch {
}
private void clear_search() {
+ // Scroll to top
+ results_scrolled.vadjustment.value = 0;
foreach (Widget widget in results_box_children) {
results_box.remove(widget);
}
diff --git a/main/src/ui/main_window.vala b/main/src/ui/main_window.vala
index d892d9ab..9121b91e 100644
--- a/main/src/ui/main_window.vala
+++ b/main/src/ui/main_window.vala
@@ -23,7 +23,7 @@ public class MainWindow : Adw.Window {
public Adw.Leaflet leaflet;
public Box left_box;
public Box right_box;
- public Revealer search_revealer;
+ public Adw.Flap search_flap;
public GlobalSearch global_search;
private Stack stack = new Stack();
private Stack left_stack;
@@ -35,7 +35,7 @@ public class MainWindow : Adw.Window {
class construct {
var shortcut = new Shortcut(new KeyvalTrigger(Key.F, ModifierType.CONTROL_MASK), new CallbackAction((widget, args) => {
- ((MainWindow) widget).search_revealer.reveal_child = true;
+ ((MainWindow) widget).search_flap.reveal_flap = true;
return false;
}));
add_shortcut(shortcut);
@@ -67,11 +67,11 @@ public class MainWindow : Adw.Window {
left_stack = (Stack) builder.get_object("left_stack");
right_stack = (Stack) builder.get_object("right_stack");
conversation_view = (ConversationView) builder.get_object("conversation_view");
- search_revealer = (Revealer) builder.get_object("search_revealer");
+ search_flap = (Adw.Flap) builder.get_object("search_flap");
conversation_selector = ((ConversationSelector) builder.get_object("conversation_list")).init(stream_interactor);
conversation_selector.conversation_selected.connect_after(() => leaflet.navigate(Adw.NavigationDirection.FORWARD));
- Frame search_frame = (Frame) builder.get_object("search_frame");
+ Adw.Bin search_frame = (Adw.Bin) builder.get_object("search_frame");
global_search = new GlobalSearch(stream_interactor);
search_frame.set_child(global_search.get_widget());
}
diff --git a/main/src/ui/main_window_controller.vala b/main/src/ui/main_window_controller.vala
index 9e7e8ce7..7a3ebcb2 100644
--- a/main/src/ui/main_window_controller.vala
+++ b/main/src/ui/main_window_controller.vala
@@ -45,10 +45,10 @@ public class MainWindowController : Object {
this.conversation_view_controller = new ConversationViewController(window.conversation_view, window.conversation_titlebar, stream_interactor);
- conversation_view_controller.search_menu_entry.button.bind_property("active", window.search_revealer, "reveal_child", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+ conversation_view_controller.search_menu_entry.button.bind_property("active", window.search_flap, "reveal-flap", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
- window.search_revealer.notify["child-revealed"].connect(() => {
- if (window.search_revealer.child_revealed) {
+ window.search_flap.notify["reveal-flap"].connect(() => {
+ if (window.search_flap.reveal_flap) {
if (window.conversation_view.conversation_frame.conversation != null && window.global_search.search_entry.text == "") {
reset_search_entry();
}
@@ -59,7 +59,9 @@ public class MainWindowController : Object {
window.global_search.selected_item.connect((item) => {
select_conversation(item.conversation, false, false);
window.conversation_view.conversation_frame.initialize_around_message(item.conversation, item);
- close_search();
+ if (window.search_flap.folded) {
+ close_search();
+ }
});
window.welcome_placeholder.primary_button.clicked.connect(() => {
@@ -91,16 +93,6 @@ public class MainWindowController : Object {
Widget window_widget = ((Widget) window);
- GestureClick gesture_click_controller = new GestureClick();
- window_widget.add_controller(gesture_click_controller);
- gesture_click_controller.pressed.connect((n_press, click_x, click_y) => {
- double search_x, search_y;
- bool ret = window.search_revealer.translate_coordinates(window, 0, 0, out search_x, out search_y);
- if (ret && click_x < search_x) {
- close_search();
- }
- });
-
EventControllerKey key_event_controller = new EventControllerKey();
window_widget.add_controller(key_event_controller);
// TODO GTK4: Why doesn't this work with key_pressed signal
diff --git a/main/src/ui/util/scaling_image.vala b/main/src/ui/util/scaling_image.vala
deleted file mode 100644
index d6ca31fd..00000000
--- a/main/src/ui/util/scaling_image.vala
+++ /dev/null
@@ -1,131 +0,0 @@
-using Gdk;
-using Gtk;
-
-namespace Dino.Ui {
-
-class FixedRatioLayout : Gtk.LayoutManager {
- public int min_width { get; set; default = 0; }
- public int target_width { get; set; default = -1; }
- public int max_width { get; set; default = int.MAX; }
- public int min_height { get; set; default = 0; }
- public int target_height { get; set; default = -1; }
- public int max_height { get; set; default = int.MAX; }
-
- public FixedRatioLayout() {
- this.notify.connect(layout_changed);
- }
-
- private void measure_target_size(Gtk.Widget widget, out int width, out int height) {
- if (target_width != -1 && target_height != -1) {
- width = target_width;
- height = target_height;
- return;
- }
- Widget child;
- width = min_width;
- height = min_height;
-
- child = widget.get_first_child();
- while (child != null) {
- if (child.should_layout()) {
- int child_min = 0;
- int child_nat = 0;
- int child_min_baseline = -1;
- int child_nat_baseline = -1;
- child.measure(Orientation.HORIZONTAL, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
- width = int.max(child_nat, width);
- }
- child = child.get_next_sibling();
- }
- width = int.min(width, max_width);
-
- child = widget.get_first_child();
- while (child != null) {
- if (child.should_layout()) {
- int child_min = 0;
- int child_nat = 0;
- int child_min_baseline = -1;
- int child_nat_baseline = -1;
- child.measure(Orientation.VERTICAL, width, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
- height = int.max(child_nat, height);
- }
- child = child.get_next_sibling();
- }
-
- if (height > max_height) {
- height = max_height;
- width = min_width;
-
- child = widget.get_first_child();
- while (child != null) {
- if (child.should_layout()) {
- int child_min = 0;
- int child_nat = 0;
- int child_min_baseline = -1;
- int child_nat_baseline = -1;
- child.measure(Orientation.HORIZONTAL, max_height, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
- width = int.max(child_nat, width);
- }
- child = child.get_next_sibling();
- }
- width = int.min(width, max_width);
- }
- }
-
- public override void measure(Gtk.Widget widget, Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
- minimum_baseline = -1;
- natural_baseline = -1;
- int width, height;
- measure_target_size(widget, out width, out height);
- if (orientation == Orientation.HORIZONTAL) {
- minimum = min_width;
- natural = width;
- } else if (for_size == -1) {
- minimum = min_height;
- natural = height;
- } else {
- minimum = natural = height * for_size / width;
- }
- }
-
- public override void allocate(Gtk.Widget widget, int width, int height, int baseline) {
- Widget child = widget.get_first_child();
- while (child != null) {
- if (child.should_layout()) {
- child.allocate(width, height, baseline, null);
- }
- child = child.get_next_sibling();
- }
- }
-
- public override SizeRequestMode get_request_mode(Gtk.Widget widget) {
- return SizeRequestMode.HEIGHT_FOR_WIDTH;
- }
-}
-
-class FixedRatioPicture : Gtk.Widget {
- public int min_width { get { return layout.min_width; } set { layout.min_width = value; } }
- public int target_width { get { return layout.target_width; } set { layout.target_width = value; } }
- public int max_width { get { return layout.max_width; } set { layout.max_width = value; } }
- public int min_height { get { return layout.min_height; } set { layout.min_height = value; } }
- public int target_height { get { return layout.target_height; } set { layout.target_height = value; } }
- public int max_height { get { return layout.max_height; } set { layout.max_height = value; } }
- public File file { get { return inner.file; } set { inner.file = value; } }
- public Gdk.Paintable paintable { get { return inner.paintable; } set { inner.paintable = value; } }
-#if GTK_4_8 && VALA_0_58
- public Gtk.ContentFit content_fit { get { return inner.content_fit; } set { inner.content_fit = value; } }
-#endif
- private Gtk.Picture inner = new Gtk.Picture();
- private FixedRatioLayout layout = new FixedRatioLayout();
-
- public FixedRatioPicture() {
- layout_manager = layout;
- inner.insert_after(this, null);
- }
-
- public override void dispose() {
- inner.unparent();
- base.dispose();
- }
-}
-} \ No newline at end of file
diff --git a/main/src/ui/widgets/fixed_ratio_picture.vala b/main/src/ui/widgets/fixed_ratio_picture.vala
new file mode 100644
index 00000000..79c60141
--- /dev/null
+++ b/main/src/ui/widgets/fixed_ratio_picture.vala
@@ -0,0 +1,88 @@
+using Gdk;
+using Gtk;
+
+class Dino.Ui.FixedRatioPicture : Gtk.Widget {
+ public int min_width { get; set; default = -1; }
+ public int max_width { get; set; default = int.MAX; }
+ public int min_height { get; set; default = -1; }
+ public int max_height { get; set; default = int.MAX; }
+ public File file { get { return inner.file; } set { inner.file = value; } }
+ public Gdk.Paintable paintable { get { return inner.paintable; } set { inner.paintable = value; } }
+#if GTK_4_8 && VALA_0_58
+ public Gtk.ContentFit content_fit { get { return inner.content_fit; } set { inner.content_fit = value; } }
+#endif
+ private Gtk.Picture inner = new Gtk.Picture();
+
+ construct {
+ set_css_name("picture");
+ add_css_class("fixed-ratio");
+ inner.insert_after(this, null);
+ this.notify.connect(queue_resize);
+ }
+
+ private void measure_target_size(out int width, out int height) {
+ if (width_request != -1 && height_request != -1) {
+ width = width_request;
+ height = height_request;
+ return;
+ }
+ width = min_width;
+ height = min_height;
+
+ if (inner.should_layout()) {
+ int child_min = 0, child_nat = 0, child_min_baseline = -1, child_nat_baseline = -1;
+ inner.measure(Orientation.HORIZONTAL, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
+ width = int.max(child_nat, width);
+ }
+ width = int.min(width, max_width);
+
+ if (inner.should_layout()) {
+ int child_min = 0, child_nat = 0, child_min_baseline = -1, child_nat_baseline = -1;
+ inner.measure(Orientation.VERTICAL, width, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
+ height = int.max(child_nat, height);
+ }
+
+ if (height > max_height) {
+ height = max_height;
+ width = min_width;
+
+ if (inner.should_layout()) {
+ int child_min = 0, child_nat = 0, child_min_baseline = -1, child_nat_baseline = -1;
+ inner.measure(Orientation.HORIZONTAL, max_height, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
+ width = int.max(child_nat, width);
+ }
+ width = int.min(width, max_width);
+ }
+ }
+
+ public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
+ minimum_baseline = -1;
+ natural_baseline = -1;
+ int width, height;
+ measure_target_size(out width, out height);
+ if (orientation == Orientation.HORIZONTAL) {
+ minimum = min_width;
+ natural = width;
+ } else if (for_size == -1) {
+ minimum = min_height;
+ natural = height;
+ } else {
+ minimum = natural = height * for_size / width;
+ }
+ }
+
+ public override void size_allocate(int width, int height, int baseline) {
+ if (inner.should_layout()) {
+ inner.allocate(width, height, baseline, null);
+ }
+ }
+
+ public override SizeRequestMode get_request_mode() {
+ return SizeRequestMode.HEIGHT_FOR_WIDTH;
+ }
+
+ public override void dispose() {
+ inner.unparent();
+ base.dispose();
+ }
+} \ No newline at end of file
diff --git a/main/src/ui/widgets/natural_size_increase.vala b/main/src/ui/widgets/natural_size_increase.vala
new file mode 100644
index 00000000..2b04d748
--- /dev/null
+++ b/main/src/ui/widgets/natural_size_increase.vala
@@ -0,0 +1,59 @@
+using Gtk;
+
+public class Dino.Ui.NaturalSizeIncrease : Gtk.Widget {
+ public int min_natural_height { get; set; default = -1; }
+ public int min_natural_width { get; set; default = -1; }
+
+ construct {
+ this.notify.connect(queue_resize);
+ }
+
+ public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
+ minimum = 0;
+ if (orientation == Orientation.HORIZONTAL) {
+ natural = min_natural_width;
+ } else {
+ natural = min_natural_height;
+ }
+ natural = int.max(0, natural);
+ minimum_baseline = -1;
+ natural_baseline = -1;
+ Widget child = get_first_child();
+ while (child != null) {
+ if (child.should_layout()) {
+ int child_min = 0;
+ int child_nat = 0;
+ int child_min_baseline = -1;
+ int child_nat_baseline = -1;
+ child.measure(orientation, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
+ minimum = int.max(minimum, child_min);
+ natural = int.max(natural, child_nat);
+ if (child_min_baseline > 0) {
+ minimum_baseline = int.max(minimum_baseline, child_min_baseline);
+ }
+ if (child_nat_baseline > 0) {
+ natural_baseline = int.max(natural_baseline, child_nat_baseline);
+ }
+ }
+ child = child.get_next_sibling();
+ }
+ }
+
+ public override void size_allocate(int width, int height, int baseline) {
+ Widget child = get_first_child();
+ while (child != null) {
+ if (child.should_layout()) {
+ child.allocate(width, height, baseline, null);
+ }
+ child = child.get_next_sibling();
+ }
+ }
+
+ public override SizeRequestMode get_request_mode() {
+ Widget child = get_first_child();
+ if (child != null) {
+ return child.get_request_mode();
+ }
+ return SizeRequestMode.CONSTANT_SIZE;
+ }
+} \ No newline at end of file