aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/ui')
-rw-r--r--main/src/ui/application.vala11
-rw-r--r--main/src/ui/chat_input/chat_input_controller.vala8
-rw-r--r--main/src/ui/conversation_content_view/content_populator.vala2
-rw-r--r--main/src/ui/conversation_content_view/file_default_widget.vala40
-rw-r--r--main/src/ui/conversation_content_view/file_group_widget.vala120
-rw-r--r--main/src/ui/conversation_content_view/file_image_widget.vala105
-rw-r--r--main/src/ui/conversation_content_view/file_transmission_progress.vala19
-rw-r--r--main/src/ui/conversation_content_view/file_widget.vala71
-rw-r--r--main/src/ui/conversation_selector/conversation_selector.vala10
-rw-r--r--main/src/ui/conversation_selector/conversation_selector_row.vala19
-rw-r--r--main/src/ui/conversation_view.vala21
-rw-r--r--main/src/ui/conversation_view_controller.vala4
-rw-r--r--main/src/ui/main_window.vala29
-rw-r--r--main/src/ui/util/data_forms.vala2
-rw-r--r--main/src/ui/util/helper.vala11
-rw-r--r--main/src/ui/widgets/avatar_picture.vala111
-rw-r--r--main/src/ui/widgets/fixed_ratio_picture.vala2
17 files changed, 390 insertions, 195 deletions
diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala
index 03128964..cb3d52e4 100644
--- a/main/src/ui/application.vala
+++ b/main/src/ui/application.vala
@@ -126,6 +126,7 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
case "join":
show_join_muc_dialog(null, jid);
break;
+ case "roster":
case "message":
Gee.List<Account> accounts = stream_interactor.get_accounts();
Jid parsed_jid = null;
@@ -291,6 +292,16 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
call_state.reject();
});
add_action(deny_call_action);
+
+ SimpleAction save_file_action = new SimpleAction("file_open_save_dialog", VariantType.INT32);
+ save_file_action.activate.connect((variant) => {
+ print("action open save dialog\n");
+ int file_transfer_id = variant.get_int32();
+ FileTransfer? file_transfer = stream_interactor.get_module(FileTransferStorage.IDENTITY).get_file_by_id(file_transfer_id, null);
+
+ window.conversation_view.open_save_file_dialog(file_transfer);
+ });
+ add_action(save_file_action);
}
private void show_preferences_window() {
diff --git a/main/src/ui/chat_input/chat_input_controller.vala b/main/src/ui/chat_input/chat_input_controller.vala
index c93a9e22..afbef637 100644
--- a/main/src/ui/chat_input/chat_input_controller.vala
+++ b/main/src/ui/chat_input/chat_input_controller.vala
@@ -245,11 +245,11 @@ public class ChatInputController : Object {
send_text();
}
- foreach (File file in files_to_send) {
- stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation);
+ if (!files_to_send.is_empty) {
+ stream_interactor.get_module(FileManager2.IDENTITY).send_files.begin(files_to_send, conversation);
+ files_to_send = new ArrayList<File>();
+ chat_input.clear_files();
}
- files_to_send = new ArrayList<File>();
- chat_input.clear_files();
}
private void on_text_input_changed() {
diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala
index ef859bde..4d1b8c90 100644
--- a/main/src/ui/conversation_content_view/content_populator.vala
+++ b/main/src/ui/conversation_content_view/content_populator.vala
@@ -70,6 +70,8 @@ public class ContentProvider : ContentItemCollection, Object {
return new FileMetaItem(content_item, stream_interactor);
} else if (content_item.type_ == CallItem.TYPE) {
return new CallMetaItem(content_item, stream_interactor);
+ } else if (content_item.type_ == FileGroupItem.TYPE) {
+ return new FileGroupMetaItem(content_item, stream_interactor);
}
critical("Got unknown content item type %s", content_item.type_);
return null;
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 a21e2afb..1ad3d6c2 100644
--- a/main/src/ui/conversation_content_view/file_default_widget.vala
+++ b/main/src/ui/conversation_content_view/file_default_widget.vala
@@ -51,13 +51,13 @@ public class FileDefaultWidget : Box {
mime_label.label = content_type != null ? content_type.get_description() : null;
}
- public void update_file_info(Xmpp.FileContentType? content_type, FileTransfer.State state, bool direction, int64 size, int64 transferred_bytes) {
- this.state = state;
+ public void update_file_info(FileTransfer file_transfer) {
+ this.state = file_transfer.state;
spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap
- content_type_image.icon_name = get_file_icon_name(content_type);
- string? mime_description = content_type != null ? content_type.get_description() : null;
+ content_type_image.icon_name = get_file_icon_name(file_transfer.content_type);
+ string? mime_description = file_transfer.content_type != null ? file_transfer.content_type.get_description() : null;
switch (state) {
case FileTransfer.State.COMPLETE:
@@ -66,23 +66,31 @@ public class FileDefaultWidget : Box {
// Create a menu
Menu menu_model = new Menu();
- menu_model.append(_("Open"), "file.open");
- menu_model.append(_("Save as…"), "file.save_as");
+
+ MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally");
+ open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id));
+
+ MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as");
+ save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id));
+
+ menu_model.append_item(open_file_item);
+ menu_model.append_item(save_as_item);
+
Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model);
file_menu.popover = popover_menu;
popover_menu.closed.connect(on_pointer_left);
break;
case FileTransfer.State.IN_PROGRESS:
- if (direction == FileTransfer.DIRECTION_RECEIVED) {
- if (size > 0) {
- int64 progress = transferred_bytes * 100 / size;
- mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(size), progress);
+ if (file_transfer.direction == FileTransfer.DIRECTION_RECEIVED) {
+ if (file_transfer.size > 0) {
+ int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size;
+ mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress);
} else {
- mime_label.label = _("Downloading %s…").printf(get_size_string(size));
+ mime_label.label = _("Downloading %s…").printf(get_size_string(file_transfer.size));
}
} else {
- int64 progress = transferred_bytes * 100 / size;
- mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(size), progress);
+ int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size;
+ mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress);
}
spinner.start();
image_stack.set_visible_child_name("spinner");
@@ -96,9 +104,9 @@ public class FileDefaultWidget : Box {
break;
case FileTransfer.State.NOT_STARTED:
if (mime_description != null) {
- mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(size));
- } else if (size != -1) {
- mime_label.label = _("File offered: %s").printf(get_size_string(size));
+ mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(file_transfer.size));
+ } else if (file_transfer.size != -1) {
+ mime_label.label = _("File offered: %s").printf(get_size_string(file_transfer.size));
} else {
mime_label.label = _("File offered");
}
diff --git a/main/src/ui/conversation_content_view/file_group_widget.vala b/main/src/ui/conversation_content_view/file_group_widget.vala
new file mode 100644
index 00000000..80c41a7a
--- /dev/null
+++ b/main/src/ui/conversation_content_view/file_group_widget.vala
@@ -0,0 +1,120 @@
+using Gee;
+using Gdk;
+using Gtk;
+using Pango;
+using Xmpp;
+
+using Dino.Entities;
+
+namespace Dino.Ui {
+
+ public class FileGroupMetaItem : ConversationSummary.ContentMetaItem {
+
+ private StreamInteractor stream_interactor;
+ private FileGroupItem file_group_item;
+
+ public FileGroupMetaItem(ContentItem content_item, StreamInteractor stream_interactor) {
+ base(content_item);
+ this.stream_interactor = stream_interactor;
+ this.file_group_item = content_item as FileGroupItem;
+ }
+
+ public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
+ var file_transfers = file_group_item.file_group.file_transfers;
+
+ if (file_transfers.size > 1 && all_files_images(file_transfers)) {
+ return new ImageGroupWidget(file_transfers);
+ } else {
+ Box box = new Box(Orientation.VERTICAL, 6);
+ foreach (FileTransfer file_transfer in file_transfers) {
+ FileWidget widget = new FileWidget(file_transfer) { halign = Align.START };
+ widget.add_css_class("file-image-widget");
+ FileWidgetController widget_controller = new FileWidgetController(widget, file_transfer, stream_interactor);
+ box.append(widget);
+ }
+ return box;
+ }
+ }
+
+ public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
+ Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>();
+ return actions;
+ }
+ }
+
+ public class ImageGroupWidget : Box {
+
+ private Gee.ArrayList<FileTransfer> file_transfers;
+
+ public ImageGroupWidget(Gee.ArrayList<FileTransfer> file_transfers) {
+ this.file_transfers = file_transfers;
+
+ if (file_transfers.size == 3) {
+ Box box = new Box(Orientation.HORIZONTAL, 4) {
+ overflow = Overflow.HIDDEN,
+ halign = Align.START
+ };
+ box.add_css_class("image-group");
+
+ var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 200);
+ box.append(picture1);
+
+ Box box1 = new Box(Orientation.VERTICAL, 4);
+
+ var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 100);
+ box1.append(picture2);
+ var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], 100);
+ box1.append(picture3);
+ box.append(box1);
+
+ this.append(box);
+ } else {
+ Box box = new Box(Orientation.VERTICAL, 4) {
+ overflow = Overflow.HIDDEN,
+ halign = Align.START
+ };
+ box.add_css_class("image-group");
+
+ Box row_upper_row = new Box(Orientation.HORIZONTAL, 4);
+
+ var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 150);
+ row_upper_row.append(picture1);
+
+ var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 150);
+ row_upper_row.append(picture2);
+ box.append(row_upper_row);
+
+ if (file_transfers.size >= 4) {
+
+ int size = 150;
+ if (file_transfers.size == 5) size = 99;
+
+ Box row_lower_row = new Box(Orientation.HORIZONTAL, 4);
+
+ var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], size);
+ row_lower_row.append(picture3);
+
+ var picture4 = new FileImageWidget.from_file_transfer(file_transfers[3], size);
+ row_lower_row.append(picture4);
+
+ if (file_transfers.size == 5) {
+ var picture5 = new FileImageWidget.from_file_transfer(file_transfers[4], size);
+ row_lower_row.append(picture5);
+ }
+
+ box.append(row_lower_row);
+ }
+ this.append(box);
+ }
+ }
+ }
+
+ public static bool all_files_images(Gee.ArrayList<FileTransfer> file_transfers) {
+ foreach (FileTransfer file_transfer in file_transfers) {
+ if (!Dino.Util.is_pixbuf_supported_content_type(file_transfer.content_type)) {
+ return false;
+ }
+ }
+ return true;
+ }
+} \ No newline at end of file
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 e936ca25..9f12f892 100644
--- a/main/src/ui/conversation_content_view/file_image_widget.vala
+++ b/main/src/ui/conversation_content_view/file_image_widget.vala
@@ -22,6 +22,8 @@ public class FileImageWidget : Widget {
private Gtk.Box image_overlay_toolbar = new Gtk.Box(Orientation.VERTICAL, 0) { halign=Align.END, valign=Align.START, margin_top=10, margin_start=10, margin_end=10, margin_bottom=10, vexpand=false, visible=false };
private Label file_size_label = new Label(null) { halign=Align.START, valign=Align.END, margin_bottom=4, margin_start=4, visible=false };
+ private int size = -1;
+
private FileTransfer file_transfer;
private FileTransmissionProgress transmission_progress = new FileTransmissionProgress() { halign=Align.CENTER, valign=Align.CENTER, visible=false };
@@ -34,22 +36,6 @@ public class FileImageWidget : Widget {
public FileImageWidget(int MAX_WIDTH=600, int MAX_HEIGHT=300) {
this.halign = Align.START;
- this.add_css_class("file-image-widget");
-
- // Setup menu button overlay
- MenuButton button = new MenuButton();
- button.icon_name = "dino-view-more";
- 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);
- image_overlay_toolbar.add_css_class("card");
- image_overlay_toolbar.add_css_class("toolbar");
- image_overlay_toolbar.add_css_class("compact-card-toolbar");
- image_overlay_toolbar.set_cursor_from_name("default");
-
file_size_label.add_css_class("file-details");
overlay.set_child(stack);
@@ -65,12 +51,65 @@ public class FileImageWidget : Widget {
gesture_click_controller.button = 1; // listen for left clicks
gesture_click_controller.released.connect(on_image_clicked);
stack.add_controller(gesture_click_controller);
+ }
+
+ public FileImageWidget.from_file_transfer(FileTransfer file_transfer, int size = -1) {
+ this();
+
+ this.file_transfer = file_transfer;
+ this.size = size;
+
+ this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform);
+ this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE);
+ this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size");
+
+ setup_signals(transmission_progress, file_transfer);
+ setup_menu_button();
+
+ file_transfer.notify["state"].connect(refresh_state);
+ file_transfer.sources_changed.connect(refresh_state);
+ refresh_state();
+ }
+
+ private static void setup_signals(FileTransmissionProgress transmission_progress, FileTransfer file_transfer) {
+ var file_transfer_id = file_transfer.id;
+
+ transmission_progress.start_download.connect(() => {
+ Dino.Application.get_default().activate_action("file_start_download", new Variant.int32(file_transfer_id));
+ });
+ transmission_progress.cancel_transfer.connect(() => {
+ Dino.Application.get_default().activate_action("file_cancel_transfer", new Variant.int32(file_transfer_id));
+ });
+ }
+
+ private void setup_menu_button() {
+ MenuButton button = new MenuButton();
+ button.icon_name = "dino-view-more";
+ Menu menu_model = new Menu();
+
+ MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally");
+ open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id));
+
+ MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as");
+ save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id));
+
+ menu_model.append_item(open_file_item);
+ menu_model.append_item(save_as_item);
+
+ Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model);
+ button.popover = popover_menu;
+ image_overlay_toolbar.append(button);
+ image_overlay_toolbar.add_css_class("card");
+ image_overlay_toolbar.add_css_class("toolbar");
+ image_overlay_toolbar.add_css_class("compact-card-toolbar");
+ image_overlay_toolbar.set_cursor_from_name("default");
EventControllerMotion this_motion_events = new EventControllerMotion();
this.add_controller(this_motion_events);
this_motion_events.enter.connect((controller, x, y) => {
(controller.widget as FileImageWidget).on_motion_event_enter();
});
+
attach_on_motion_event_leave(this_motion_events, button);
}
@@ -88,18 +127,6 @@ public class FileImageWidget : Widget {
file_size_label.visible = file_transfer != null && file_transfer.direction == FileTransfer.DIRECTION_RECEIVED && file_transfer.state == FileTransfer.State.NOT_STARTED && !file_transfer.sfs_sources.is_empty;
}
- public async void set_file_transfer(FileTransfer file_transfer) {
- this.file_transfer = file_transfer;
-
- this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform);
- this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE);
- this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size");
-
- file_transfer.notify["state"].connect(refresh_state);
- file_transfer.sources_changed.connect(refresh_state);
- refresh_state();
- }
-
private static bool file_size_label_transform(Binding binding, Value from_value, ref Value to_value) {
to_value = FileDefaultWidget.get_size_string((int64) from_value);
return true;
@@ -153,7 +180,7 @@ public class FileImageWidget : Widget {
}
public async void load_from_file(File file, string file_name) throws GLib.Error {
- FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 };
+ FixedRatioPicture image = create_picture();
FileInputStream file_stream = null;
try {
@@ -201,7 +228,7 @@ public class FileImageWidget : Widget {
throw new Error(-1, 0, "Error scaling preview image");
}
- FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 };
+ FixedRatioPicture image = create_picture();
image.paintable = Texture.for_pixbuf(pixbuf);
stack.add_child(image);
stack.set_visible_child(image);
@@ -216,12 +243,12 @@ public class FileImageWidget : Widget {
if (n_press == 1) {
image_overlay_toolbar.visible = !image_overlay_toolbar.visible;
} else if (n_press == 2) {
- this.activate_action("file.open", null);
+ Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id));
image_overlay_toolbar.visible = false;
}
break;
default:
- this.activate_action("file.open", null);
+ Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id));
image_overlay_toolbar.visible = false;
break;
}
@@ -237,6 +264,20 @@ public class FileImageWidget : Widget {
(file_transfer.state == FileTransfer.State.COMPLETE || file_transfer.thumbnails.size > 0);
}
+ private FixedRatioPicture create_picture() {
+ if (size == -1) {
+ return new FixedRatioPicture() {
+ min_width=100, max_width=600,
+ min_height=100, max_height=300
+ };
+ } else {
+ return new FixedRatioPicture() {
+ height_request = size, width_request = size,
+ content_fit = ContentFit.COVER, halign = Align.START
+ };
+ }
+ }
+
public override void dispose() {
if (overlay != null && overlay.parent != null) overlay.unparent();
base.dispose();
diff --git a/main/src/ui/conversation_content_view/file_transmission_progress.vala b/main/src/ui/conversation_content_view/file_transmission_progress.vala
index e3f33448..3d893f52 100644
--- a/main/src/ui/conversation_content_view/file_transmission_progress.vala
+++ b/main/src/ui/conversation_content_view/file_transmission_progress.vala
@@ -9,6 +9,9 @@ namespace Dino.Ui {
public class FileTransmissionProgress : Adw.Bin {
+ public signal void start_download();
+ public signal void cancel_transfer();
+
public enum State {
UNKNOWN_SOURCE,
DOWNLOAD_NOT_STARTED,
@@ -81,9 +84,7 @@ namespace Dino.Ui {
}
}
-#if GTK_4_14
private static extern Gsk.Path create_progress_arc(Gsk.Path circle, float percentage);
-#endif
public override void snapshot(Gtk.Snapshot snapshot) {
base.snapshot(snapshot);
@@ -94,7 +95,6 @@ namespace Dino.Ui {
float line_width = (float) LINE_WIDTH;
snapshot.translate({get_width() / 2, get_height() / 2});
-#if GTK_4_14
var fg_color = get_color();
get_style_context().lookup_color("accent_color", out fg_color);
var builder = new Gsk.PathBuilder();
@@ -104,16 +104,6 @@ namespace Dino.Ui {
stroke.set_line_cap(Gsk.LineCap.ROUND);
var arc_path = create_progress_arc(circle_path, (float) progress_animation.value);
snapshot.append_stroke(arc_path, stroke, fg_color);
-#else
- var fg_color = get_style_context().get_color();
- get_style_context().lookup_color("accent_color", out fg_color);
- var context = snapshot.append_cairo({{-radius, -radius}, {radius*2, radius*2}});
- context.set_line_cap(Cairo.LineCap.ROUND);
- context.set_line_width(line_width);
- context.set_source_rgba(fg_color.red, fg_color.green, fg_color.blue, fg_color.alpha);
- context.arc(0, 0, radius - line_width / 2, -0.5 * Math.PI, (progress_animation.value - 0.25) * 2 * Math.PI);
- context.stroke();
-#endif
}
}
@@ -124,11 +114,12 @@ namespace Dino.Ui {
break;
case DOWNLOAD_NOT_STARTED_FAILED_BEFORE:
case DOWNLOAD_NOT_STARTED:
+ start_download();
this.activate_action("file.download", null);
break;
case DOWNLOADING:
case UPLOADING:
- this.activate_action("file.cancel", null);
+ cancel_transfer();
break;
}
}
diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala
index 3c21412a..d6d2415a 100644
--- a/main/src/ui/conversation_content_view/file_widget.vala
+++ b/main/src/ui/conversation_content_view/file_widget.vala
@@ -58,18 +58,6 @@ public class FileWidget : SizeRequestBin {
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;
@@ -80,11 +68,6 @@ public class FileWidget : SizeRequestBin {
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; });
-// }
-// });
file_transfer.bind_property("state", this, "file-transfer-state");
file_transfer.bind_property("content-type", this, "file-transfer-content-type");
@@ -102,8 +85,8 @@ public class FileWidget : SizeRequestBin {
FileImageWidget file_image_widget = null;
try {
- file_image_widget = new FileImageWidget();
- yield file_image_widget.set_file_transfer(file_transfer);
+ file_image_widget = new FileImageWidget.from_file_transfer(file_transfer);
+ file_image_widget.add_css_class("file-image-widget");
// If the widget changed in the meanwhile, stop
if (content != content_bak) return;
@@ -166,50 +149,6 @@ public class FileWidgetController : Object {
});
this.file_transfer = file_transfer;
this.stream_interactor = stream_interactor;
-
- 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);
- }
-
- private void open_file() {
- try{
- AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null);
- } catch (Error err) {
- 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_transfer.file_name);
-
- save_dialog.response.connect(() => {
- File? target_file = save_dialog.get_file();
- if (target_file == null) {
- warning("No file returned from save dialog.");
- return;
- }
- try {
- file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null);
- } catch (Error err) {
- 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();
}
}
@@ -248,16 +187,16 @@ public class FileDefaultWidgetController : Object {
private void update_file_info() {
state = file_transfer.state;
- widget.update_file_info(file_transfer.content_type, file_transfer.state, file_transfer.direction, file_transfer.size, file_transfer.transferred_bytes);
+ widget.update_file_info(file_transfer);
}
private void on_clicked() {
switch (state) {
case FileTransfer.State.COMPLETE:
- widget.activate_action("file.open", null);
+ Dino.Application.get_default().activate_action("file_open_externally", new GLib.Variant.int32(file_transfer.id));
break;
case FileTransfer.State.NOT_STARTED:
- widget.activate_action("file.download", null);
+ Dino.Application.get_default().activate_action("file_start_download", new GLib.Variant.int32(file_transfer.id));
break;
default:
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
diff --git a/main/src/ui/conversation_selector/conversation_selector.vala b/main/src/ui/conversation_selector/conversation_selector.vala
index c11eea82..25ea1b1e 100644
--- a/main/src/ui/conversation_selector/conversation_selector.vala
+++ b/main/src/ui/conversation_selector/conversation_selector.vala
@@ -23,7 +23,7 @@ public class ConversationSelector : Widget {
list_box.add_css_class("navigation-sidebar");
this.layout_manager = new BinLayout();
- stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(add_conversation);
+ stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(on_conversation_activated);
stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(remove_conversation);
stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received);
Timeout.add_seconds(60, () => {
@@ -34,6 +34,7 @@ public class ConversationSelector : Widget {
foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations()) {
add_conversation(conversation);
}
+ list_box.invalidate_sort();
return this;
}
@@ -74,6 +75,7 @@ public class ConversationSelector : Widget {
public void on_conversation_selected(Conversation conversation) {
if (!rows.has_key(conversation)) {
add_conversation(conversation);
+ list_box.invalidate_sort();
}
list_box.select_row(rows[conversation]);
}
@@ -84,6 +86,11 @@ public class ConversationSelector : Widget {
}
}
+ private void on_conversation_activated(Conversation conversation) {
+ add_conversation(conversation);
+ list_box.invalidate_sort();
+ }
+
private void add_conversation(Conversation conversation) {
ConversationSelectorRow row;
if (!rows.has_key(conversation)) {
@@ -113,7 +120,6 @@ public class ConversationSelector : Widget {
});
row.add_controller(drop_motion_controller);
}
- list_box.invalidate_sort();
}
private void select_fallback_conversation(Conversation conversation) {
diff --git a/main/src/ui/conversation_selector/conversation_selector_row.vala b/main/src/ui/conversation_selector/conversation_selector_row.vala
index 82c96fdc..95384a5e 100644
--- a/main/src/ui/conversation_selector/conversation_selector_row.vala
+++ b/main/src/ui/conversation_selector/conversation_selector_row.vala
@@ -257,6 +257,25 @@ public class ConversationSelectorRow : ListBoxRow {
message_label.label = (file_is_image ? _("Image received") : _("File received") );
}
break;
+ case FileGroupItem.TYPE:
+ FileGroupItem file_group_item = last_content_item as FileGroupItem;
+ FileTransfer first_transfer = file_group_item.file_group.file_transfers.get(0);
+
+ if (conversation.type_ == Conversation.Type.GROUPCHAT) {
+ // TODO properly display nick for oneself
+ nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, first_transfer.from, true) + ": ";
+ } else {
+ nick_label.label = first_transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : "";
+ }
+
+ bool all_files_images = Ui.all_files_images(file_group_item.file_group.file_transfers);
+ change_label_attribute(message_label, attr_style_new(Pango.Style.ITALIC));
+ if (first_transfer.direction == Message.DIRECTION_SENT) {
+ message_label.label = (all_files_images ? _("Images sent") : _("Files sent") );
+ } else {
+ message_label.label = (all_files_images ? _("Images received") : _("Files received") );
+ }
+ break;
case CallItem.TYPE:
CallItem call_item = (CallItem) last_content_item;
Call call = call_item.call;
diff --git a/main/src/ui/conversation_view.vala b/main/src/ui/conversation_view.vala
index 26043666..4bb1f537 100644
--- a/main/src/ui/conversation_view.vala
+++ b/main/src/ui/conversation_view.vala
@@ -17,6 +17,27 @@ public class ConversationView : Widget {
construct {
this.layout_manager = new BinLayout();
}
+
+ public void open_save_file_dialog(FileTransfer file_transfer) {
+ var save_dialog = new FileChooserNative(_("Save as…"), (Gtk.Window) this.get_root(), FileChooserAction.SAVE, null, null);
+ save_dialog.set_modal(true);
+ save_dialog.set_current_name(file_transfer.file_name);
+
+ save_dialog.response.connect(() => {
+ File? target_file = save_dialog.get_file();
+ if (target_file == null) {
+ warning("No file returned from save dialog.");
+ return;
+ }
+ try {
+ file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null);
+ } catch (Error err) {
+ warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message);
+ }
+ });
+
+ save_dialog.show();
+ }
}
}
diff --git a/main/src/ui/conversation_view_controller.vala b/main/src/ui/conversation_view_controller.vala
index 0acaae59..bff8a70b 100644
--- a/main/src/ui/conversation_view_controller.vala
+++ b/main/src/ui/conversation_view_controller.vala
@@ -228,10 +228,6 @@ public class ConversationViewController : Object {
chooser.show();
}
- private void send_file(File file) {
- stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation);
- }
-
private bool forward_key_press_to_chat_input(EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) {
if (view.get_root().get_focus() is TextView) {
return false;
diff --git a/main/src/ui/main_window.vala b/main/src/ui/main_window.vala
index 98d68e64..fc18d2bd 100644
--- a/main/src/ui/main_window.vala
+++ b/main/src/ui/main_window.vala
@@ -11,23 +11,23 @@ public class MainWindow : Gtk.ApplicationWindow {
public signal void conversation_selected(Conversation conversation);
- [GtkChild] public Stack stack;
- [GtkChild] public Adw.NavigationSplitView navigation_split_view;
+ [GtkChild] public unowned Stack stack;
+ [GtkChild] public unowned Adw.NavigationSplitView navigation_split_view;
- [GtkChild] public SearchEntry conversation_search_entry;
- [GtkChild] public MenuButton menu_button;
+ [GtkChild] public unowned SearchEntry conversation_search_entry;
+ [GtkChild] public unowned MenuButton menu_button;
[GtkChild] private unowned Box conversation_list_toolbar;
- [GtkChild] public Adw.HeaderBar conversation_headerbar;
- [GtkChild] public Button conversation_title_button;
- [GtkChild] public Adw.WindowTitle conversation_window_title;
+ [GtkChild] public unowned Adw.HeaderBar conversation_headerbar;
+ [GtkChild] public unowned Button conversation_title_button;
+ [GtkChild] public unowned Adw.WindowTitle conversation_window_title;
- [GtkChild] public ConversationView conversation_view;
- [GtkChild] public ConversationSelector conversation_selector;
- [GtkChild] public Adw.Flap search_flap;
- [GtkChild] private Stack left_stack;
- [GtkChild] private Stack right_stack;
- [GtkChild] public Adw.Bin search_frame;
+ [GtkChild] public unowned ConversationView conversation_view;
+ [GtkChild] public unowned ConversationSelector conversation_selector;
+ [GtkChild] public unowned Adw.Flap search_flap;
+ [GtkChild] private unowned Stack left_stack;
+ [GtkChild] private unowned Stack right_stack;
+ [GtkChild] public unowned Adw.Bin search_frame;
public GlobalSearch global_search;
@@ -54,6 +54,9 @@ public class MainWindow : Gtk.ApplicationWindow {
this.title = "Dino";
this.add_css_class("dino-main");
+ if (Dino.get_version().contains("~git")) {
+ this.add_css_class("devel");
+ }
var header_size_group = new SizeGroup(SizeGroupMode.VERTICAL);
header_size_group.add_widget(conversation_list_toolbar);
diff --git a/main/src/ui/util/data_forms.vala b/main/src/ui/util/data_forms.vala
index 7fb9b87d..0b91da15 100644
--- a/main/src/ui/util/data_forms.vala
+++ b/main/src/ui/util/data_forms.vala
@@ -18,7 +18,7 @@ namespace Dino.Ui.Util {
}
public static ViewModel.PreferencesRow.Any? get_data_form_field_view_model(DataForms.DataForm.Field field) {
- if (field.type_ == null) return null;
+ if (field.type_ == DataForms.DataForm.Type.UNKNOWN) return null;
ViewModel.PreferencesRow.Any? view_model = null;
diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala
index e9035be0..a126a196 100644
--- a/main/src/ui/util/helper.vala
+++ b/main/src/ui/util/helper.vala
@@ -415,18 +415,7 @@ public bool use_tooltips() {
}
public static void menu_button_set_icon_with_size(MenuButton menu_button, string icon_name, int pixel_size) {
-#if GTK_4_6 && VALA_0_52
menu_button.set_child(new Image.from_icon_name(icon_name) { pixel_size=pixel_size });
-#else
- menu_button.set_icon_name(icon_name);
- var button = menu_button.get_first_child() as Button;
- if (button == null) return;
- var box = button.child as Box;
- if (box == null) return;
- var image = box.get_first_child() as Image;
- if (image == null) return;
- image.pixel_size = pixel_size;
-#endif
}
}
diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala
index 18e4d02f..b4636f49 100644
--- a/main/src/ui/widgets/avatar_picture.vala
+++ b/main/src/ui/widgets/avatar_picture.vala
@@ -5,7 +5,20 @@ using Xmpp;
public class Dino.Ui.ViewModel.AvatarPictureTileModel : Object {
public string display_text { get; set; }
public Gdk.RGBA background_color { get; set; }
- public File? image_file { get; set; }
+
+ private File? _image_file;
+ [CCode (notify = false)]
+ public File? image_file {
+ get {
+ return _image_file;
+ }
+ set {
+ if (_image_file == value) return;
+ if (_image_file != null && value != null && _image_file.equal(value)) return;
+ _image_file = value;
+ notify_property("image-file");
+ }
+ }
}
public class Dino.Ui.ViewModel.AvatarPictureModel : Object {
@@ -23,8 +36,18 @@ public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : A
public ConversationParticipantAvatarPictureTileModel(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
this.stream_interactor = stream_interactor;
+
+ set_conversation_participant(conversation, jid);
+
+ stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
+ avatar_manager.received_avatar.connect(on_received_avatar);
+ avatar_manager.fetched_avatar.connect(on_received_avatar);
+ }
+
+ public void set_conversation_participant(Conversation conversation, Jid jid) {
this.conversation = conversation;
this.primary_avatar_jid = jid;
+ this.secondary_avatar_jid = null;
this.display_name_jid = jid;
string color_id = jid.to_string();
@@ -43,14 +66,11 @@ public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : A
}
string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
display_text = display.get_char(0).toupper().to_string();
- stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f};
update_image_file();
- avatar_manager.received_avatar.connect(on_received_avatar);
- avatar_manager.fetched_avatar.connect(on_received_avatar);
}
private void update_image_file() {
@@ -140,55 +160,76 @@ public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel {
private void on_room_updated(Account account, Jid room) {
if (conversation != null && account.equals(conversation.account) && conversation.counterpart.equals_bare(room)) {
- reset();
- set_conversation(conversation);
+ update_all();
}
}
private void on_received_avatar(Jid jid, Account account) {
+ // Only update group chat avatar when the respective jid avatar is actually used in it
+ if (conversation == null || conversation.type_ == Conversation.Type.GROUPCHAT && !jid.is_bare()) return;
on_room_updated(account, jid);
}
- public void reset() {
- (tiles as GLib.ListStore).remove_all();
- }
-
public CompatAvatarPictureModel set_conversation(Conversation conversation) {
if (stream_interactor == null) {
critical("set_conversation() used on CompatAvatarPictureModel without stream_interactor");
return this;
}
this.conversation = conversation;
+ update_all();
+ return this;
+ }
+
+ private void update_all() {
+ uint idx = 0;
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
if (avatar_manager.has_avatar(conversation.account, conversation.counterpart)) {
- add_internal("#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart));
+ set_internal(idx++, "#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart));
} else {
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
if (occupants != null && !occupants.is_empty && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
int count = occupants.size > 4 ? 3 : occupants.size;
for (int i = 0; i < count; i++) {
- add_participant(conversation, occupants[i]);
+ set_participant(idx++, conversation, occupants[i]);
}
if (occupants.size > 4) {
- add_internal("+");
+ set_internal(idx++, "+");
}
} else {
- add_internal("#", conversation.counterpart.to_string());
+ set_internal(idx++, "#", conversation.counterpart.to_string());
}
}
} else {
- add_participant(conversation, conversation.counterpart);
+ set_participant(idx++, conversation, conversation.counterpart);
+ }
+
+ GLib.ListStore store = tiles as GLib.ListStore;
+ if (store.get_n_items() >= idx) {
+ store.splice(idx, store.get_n_items() - idx, {});
}
- return this;
}
public CompatAvatarPictureModel add_participant(Conversation conversation, Jid jid) {
+ set_participant(tiles.get_n_items(), conversation, jid);
+ return this;
+ }
+
+ public AvatarPictureTileModel set_participant(uint idx, Conversation conversation, Jid jid) {
if (stream_interactor == null) {
- critical("add_participant() used on CompatAvatarPictureModel without stream_interactor");
- return this;
+ error("set_participant() used on CompatAvatarPictureModel without stream_interactor");
}
- (tiles as GLib.ListStore).append(new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid));
- return this;
+ GLib.ListStore store = tiles as GLib.ListStore;
+ while (idx >= store.get_n_items()) {
+ store.append(new ViewModel.AvatarPictureTileModel());
+ }
+ var model = store.get_item(idx) as ConversationParticipantAvatarPictureTileModel;
+ if (model == null) {
+ model = new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid);
+ store.splice(idx, 1, {model});
+ } else {
+ model.set_conversation_participant(conversation, jid);
+ }
+ return model;
}
public CompatAvatarPictureModel add(string display, string? color_id = null, File? image_file = null) {
@@ -197,14 +238,28 @@ public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel {
}
private AvatarPictureTileModel add_internal(string display, string? color_id = null, File? image_file = null) {
+ return set_internal(tiles.get_n_items(), display, color_id, image_file);
+ }
+
+ private AvatarPictureTileModel set_internal(uint idx, string display, string? color_id = null, File? image_file = null) {
GLib.ListStore store = tiles as GLib.ListStore;
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
- var model = new ViewModel.AvatarPictureTileModel() {
- display_text = display.get_char(0).toupper().to_string(),
- background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f},
- image_file = image_file
- };
- store.append(model);
+ while (idx >= store.get_n_items()) {
+ store.append(new ViewModel.AvatarPictureTileModel());
+ }
+ var model = store.get_item(idx) as ViewModel.AvatarPictureTileModel;
+ if (model == null) {
+ model = new ViewModel.AvatarPictureTileModel() {
+ display_text = display.get_char(0).toupper().to_string(),
+ background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f},
+ image_file = image_file
+ };
+ store.splice(idx, 1, {model});
+ } else {
+ model.display_text = display.get_char(0).toupper().to_string();
+ model.background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f};
+ model.image_file = image_file;
+ }
return model;
}
}
@@ -476,11 +531,7 @@ public class Dino.Ui.AvatarPicture : Gtk.Widget {
label.insert_after(this, null);
label.attributes = new Pango.AttrList();
label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
-#if GTK_4_8 && (VALA_0_56_GREATER_5 || VALA_0_58)
picture.content_fit = Gtk.ContentFit.COVER;
-#elif GTK_4_8
- picture.@set("content-fit", 2);
-#endif
picture.insert_after(this, label);
this.notify["model"].connect(on_model_changed);
check_widget_leak(this);
diff --git a/main/src/ui/widgets/fixed_ratio_picture.vala b/main/src/ui/widgets/fixed_ratio_picture.vala
index 6b04ea61..1898efa7 100644
--- a/main/src/ui/widgets/fixed_ratio_picture.vala
+++ b/main/src/ui/widgets/fixed_ratio_picture.vala
@@ -8,9 +8,7 @@ class Dino.Ui.FixedRatioPicture : Gtk.Widget {
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_56_GREATER_5 || 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 {