mirror of
https://github.com/TakeV-Lambda/dino.git
synced 2024-11-21 22:44:23 +00:00
Fix UI for libadwaita
This commit is contained in:
parent
cc7db3b85f
commit
e35df88d4a
15 changed files with 422 additions and 281 deletions
|
@ -154,6 +154,7 @@ public interface ConversationItemWidgetInterface: Object {
|
||||||
public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget);
|
public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget);
|
||||||
public class MessageAction : Object {
|
public class MessageAction : Object {
|
||||||
public string icon_name;
|
public string icon_name;
|
||||||
|
public string? tooltip;
|
||||||
public Object? popover;
|
public Object? popover;
|
||||||
public MessageActionEvoked? callback;
|
public MessageActionEvoked? callback;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,9 @@ set(MAIN_DEFINITIONS)
|
||||||
if(GTK4_VERSION VERSION_GREATER_EQUAL "4.6")
|
if(GTK4_VERSION VERSION_GREATER_EQUAL "4.6")
|
||||||
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_6)
|
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_6)
|
||||||
endif()
|
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")
|
if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.2")
|
||||||
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_2)
|
set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_2)
|
||||||
endif()
|
endif()
|
||||||
|
@ -204,7 +207,9 @@ SOURCES
|
||||||
src/ui/util/label_hybrid.vala
|
src/ui/util/label_hybrid.vala
|
||||||
src/ui/util/sizing_bin.vala
|
src/ui/util/sizing_bin.vala
|
||||||
src/ui/util/size_request_box.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
|
CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
|
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
|
||||||
${CMAKE_BINARY_DIR}/exports/qlite.vapi
|
${CMAKE_BINARY_DIR}/exports/qlite.vapi
|
||||||
|
|
|
@ -54,44 +54,60 @@
|
||||||
<object class="GtkBox" id="right_box">
|
<object class="GtkBox" id="right_box">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkOverlay">
|
<object class="AdwFlap" id="search_flap">
|
||||||
<property name="child">
|
<property name="flap-position">end</property>
|
||||||
<object class="GtkStack" id="right_stack">
|
<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>
|
<child>
|
||||||
<object class="GtkStackPage">
|
<object class="GtkStack" id="right_stack">
|
||||||
<property name="name">content</property>
|
<property name="hexpand">false</property>
|
||||||
<property name="child">
|
<child>
|
||||||
<object class="DinoUiConversationView" id="conversation_view">
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">content</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="DinoUiConversationView" id="conversation_view">
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
</object>
|
<child>
|
||||||
</child>
|
<object class="GtkStackPage">
|
||||||
<child>
|
<property name="name">placeholder</property>
|
||||||
<object class="GtkStackPage">
|
<property name="child">
|
||||||
<property name="name">placeholder</property>
|
<object class="AdwStatusPage">
|
||||||
<property name="child">
|
<property name="icon-name">im.dino.Dino-symbolic</property>
|
||||||
<object class="AdwStatusPage">
|
<property name="hexpand">True</property>
|
||||||
<property name="icon-name">im.dino.Dino-symbolic</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="hexpand">True</property>
|
</object>
|
||||||
<property name="vexpand">True</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
<child type="overlay">
|
<child type="separator">
|
||||||
<object class="GtkRevealer" id="search_revealer">
|
<object class="GtkSeparator" />
|
||||||
<property name="halign">end</property>
|
</child>
|
||||||
<property name="transition-type">slide-left</property>
|
<child type="flap">
|
||||||
<style>
|
<object class="AdwClamp">
|
||||||
<class name="dino-sidebar"/>
|
<property name="hexpand">false</property>
|
||||||
</style>
|
<property name="maximum-size">400</property>
|
||||||
<property name="child">
|
<property name="tightening-threshold">400</property>
|
||||||
<object class="GtkFrame" id="search_frame">
|
<child>
|
||||||
<property name="width-request">400</property>
|
<object class="AdwBin" id="search_frame">
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<style>
|
||||||
|
<class name="background"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
private uint time_update_timeout = 0;
|
private uint time_update_timeout = 0;
|
||||||
private ulong updated_roster_handler_id = 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.stream_interactor = stream_interactor;
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
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.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, ConversationItemSkeleton> item_item_skeletons = new Gee.HashMap<Plugins.MetaConversationItem, ConversationItemSkeleton>();
|
||||||
private Gee.HashMap<Plugins.MetaConversationItem, Widget> widgets = new Gee.HashMap<Plugins.MetaConversationItem, Widget>();
|
private Gee.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 double? was_page_size;
|
||||||
|
|
||||||
private Mutex reloading_mutex = Mutex();
|
private Mutex reloading_mutex = Mutex();
|
||||||
private bool animate = false;
|
|
||||||
private bool firstLoad = true;
|
private bool firstLoad = true;
|
||||||
private bool at_current_content = true;
|
private bool at_current_content = true;
|
||||||
private bool reload_messages = true;
|
private bool reload_messages = true;
|
||||||
|
@ -82,6 +81,15 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
main.add_controller(main_motion_events);
|
main.add_controller(main_motion_events);
|
||||||
main_motion_events.motion.connect(update_highlight);
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +208,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
MenuButton button = new MenuButton();
|
MenuButton button = new MenuButton();
|
||||||
button.icon_name = message_actions[i].icon_name;
|
button.icon_name = message_actions[i].icon_name;
|
||||||
button.set_popover(message_actions[i].popover as Popover);
|
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);
|
action_buttons.add(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +219,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
button.clicked.connect(() => {
|
button.clicked.connect(() => {
|
||||||
message_action.callback(button, current_meta_item, currently_highlighted);
|
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);
|
action_buttons.add(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,12 +242,71 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
});
|
});
|
||||||
firstLoad = false;
|
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();
|
clear();
|
||||||
initialize_for_conversation_(conversation);
|
initialize_for_conversation_(conversation);
|
||||||
display_latest();
|
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) {
|
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();
|
clear();
|
||||||
initialize_for_conversation_(conversation);
|
initialize_for_conversation_(conversation);
|
||||||
Gee.List<ContentMetaItem> before_items = content_populator.populate_before(conversation, content_item, 40);
|
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);
|
do_insert_item(item);
|
||||||
}
|
}
|
||||||
ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item);
|
ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item);
|
||||||
meta_item.can_merge = false;
|
|
||||||
Widget w = insert_new(meta_item);
|
Widget w = insert_new(meta_item);
|
||||||
content_items.add(meta_item);
|
content_items.add(meta_item);
|
||||||
meta_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.
|
// Compute where to jump to for centered message, jump, highlight.
|
||||||
reload_messages = false;
|
reload_messages = false;
|
||||||
Timeout.add(700, () => {
|
Timeout.add(700, () => {
|
||||||
int h = 0, i = 0;
|
scroll_and_highlight_item(meta_item, 300);
|
||||||
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");
|
|
||||||
reload_messages = true;
|
reload_messages = true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize_for_conversation_(Conversation? conversation) {
|
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
|
// Deinitialize old conversation
|
||||||
Dino.Application app = Dino.Application.get_default();
|
Dino.Application app = Dino.Application.get_default();
|
||||||
if (this.conversation != null) {
|
if (this.conversation != null) {
|
||||||
|
@ -299,9 +360,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
}
|
}
|
||||||
content_populator.init(this, conversation, Plugins.WidgetType.GTK4);
|
content_populator.init(this, conversation, Plugins.WidgetType.GTK4);
|
||||||
subscription_notification.init(conversation, this);
|
subscription_notification.init(conversation, this);
|
||||||
|
|
||||||
animate = false;
|
|
||||||
Timeout.add(20, () => { animate = true; return false; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void display_latest() {
|
private void display_latest() {
|
||||||
|
@ -331,8 +389,8 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
public void do_insert_item(Plugins.MetaConversationItem item) {
|
public void do_insert_item(Plugins.MetaConversationItem item) {
|
||||||
lock (meta_items) {
|
lock (meta_items) {
|
||||||
insert_new(item);
|
insert_new(item);
|
||||||
if (item as ContentMetaItem != null) {
|
if (item is ContentMetaItem) {
|
||||||
content_items.add(item);
|
content_items.add((ContentMetaItem)item);
|
||||||
}
|
}
|
||||||
meta_items.add(item);
|
meta_items.add(item);
|
||||||
}
|
}
|
||||||
|
@ -348,7 +406,9 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
widget_order.remove(skeleton.get_widget());
|
widget_order.remove(skeleton.get_widget());
|
||||||
item_item_skeletons.unset(item);
|
item_item_skeletons.unset(item);
|
||||||
|
|
||||||
content_items.remove(item);
|
if (item is ContentMetaItem) {
|
||||||
|
content_items.remove((ContentMetaItem)item);
|
||||||
|
}
|
||||||
meta_items.remove(item);
|
meta_items.remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +447,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
|
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
|
||||||
|
|
||||||
// Fill datastructure
|
// 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;
|
item_item_skeletons[item] = item_skeleton;
|
||||||
int index = lower_item != null ? widget_order.index_of(item_item_skeletons[lower_item].get_widget()) + 1 : 0;
|
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());
|
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) {
|
private static int compare_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) {
|
||||||
int cmp1 = a.time.compare(b.time);
|
int cmp1 = a.time.compare(b.time);
|
||||||
if (cmp1 != 0) return cmp1;
|
if (cmp1 != 0) return cmp1;
|
||||||
|
|
|
@ -10,9 +10,6 @@ namespace Dino.Ui {
|
||||||
public class FileDefaultWidget : Box {
|
public class FileDefaultWidget : Box {
|
||||||
|
|
||||||
public signal void clicked();
|
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 Stack image_stack;
|
||||||
[GtkChild] public unowned Label name_label;
|
[GtkChild] public unowned Label name_label;
|
||||||
|
@ -23,12 +20,6 @@ public class FileDefaultWidget : Box {
|
||||||
|
|
||||||
private FileTransfer.State state;
|
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() {
|
public FileDefaultWidget() {
|
||||||
EventControllerMotion this_motion_events = new EventControllerMotion();
|
EventControllerMotion this_motion_events = new EventControllerMotion();
|
||||||
this.add_controller(this_motion_events);
|
this.add_controller(this_motion_events);
|
||||||
|
|
|
@ -8,41 +8,70 @@ namespace Dino.Ui {
|
||||||
|
|
||||||
public class FileImageWidget : Box {
|
public class FileImageWidget : Box {
|
||||||
|
|
||||||
FileDefaultWidget file_default_widget;
|
|
||||||
FileDefaultWidgetController file_default_widget_controller;
|
|
||||||
|
|
||||||
public FileImageWidget() {
|
public FileImageWidget() {
|
||||||
this.halign = Align.START;
|
this.halign = Align.START;
|
||||||
|
|
||||||
this.add_css_class("file-image-widget");
|
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 {
|
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);
|
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
|
||||||
string? mime_type = file_info.get_content_type();
|
string? mime_type = file_info.get_content_type();
|
||||||
|
|
||||||
file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false, visible=false };
|
MenuButton button = new MenuButton();
|
||||||
file_default_widget.image_stack.visible = false;
|
button.icon_name = "open-menu";
|
||||||
file_default_widget_controller = new FileDefaultWidgetController(file_default_widget);
|
Menu menu_model = new Menu();
|
||||||
file_default_widget_controller.set_file(file, file_name, mime_type);
|
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 overlay = new Overlay();
|
||||||
overlay.set_child(image);
|
overlay.set_child(image);
|
||||||
overlay.add_overlay(file_default_widget);
|
overlay.add_overlay(image_overlay_toolbar);
|
||||||
overlay.set_measure_overlay(image, true);
|
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();
|
EventControllerMotion this_motion_events = new EventControllerMotion();
|
||||||
this.add_controller(this_motion_events);
|
this.add_controller(this_motion_events);
|
||||||
this_motion_events.enter.connect(() => {
|
this_motion_events.enter.connect(() => {
|
||||||
file_default_widget.visible = true;
|
image_overlay_toolbar.visible = true;
|
||||||
});
|
});
|
||||||
this_motion_events.leave.connect(() => {
|
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);
|
this.append(overlay);
|
||||||
|
|
|
@ -21,7 +21,9 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
|
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) {
|
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
|
||||||
|
@ -57,7 +59,6 @@ public class FileWidget : SizeRequestBox {
|
||||||
DEFAULT
|
DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
|
||||||
private FileTransfer file_transfer;
|
private FileTransfer file_transfer;
|
||||||
public FileTransfer.State file_transfer_state { get; set; }
|
public FileTransfer.State file_transfer_state { get; set; }
|
||||||
public string file_transfer_mime_type { get; set; }
|
public string file_transfer_mime_type { get; set; }
|
||||||
|
@ -66,13 +67,24 @@ public class FileWidget : SizeRequestBox {
|
||||||
private FileDefaultWidgetController default_widget_controller;
|
private FileDefaultWidgetController default_widget_controller;
|
||||||
private Widget? content = null;
|
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 {
|
construct {
|
||||||
margin_top = 4;
|
margin_top = 4;
|
||||||
size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
|
size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileWidget(StreamInteractor stream_interactor, FileTransfer file_transfer) {
|
public FileWidget(FileTransfer file_transfer) {
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
this.file_transfer = file_transfer;
|
this.file_transfer = file_transfer;
|
||||||
|
|
||||||
update_widget.begin();
|
update_widget.begin();
|
||||||
|
@ -113,7 +125,7 @@ public class FileWidget : SizeRequestBox {
|
||||||
if (content != null) this.remove(content);
|
if (content != null) this.remove(content);
|
||||||
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
||||||
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
|
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;
|
content = default_file_widget;
|
||||||
this.state = State.DEFAULT;
|
this.state = State.DEFAULT;
|
||||||
this.append(content);
|
this.append(content);
|
||||||
|
@ -138,94 +150,104 @@ public class FileWidget : SizeRequestBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileDefaultWidgetController : Object {
|
public class FileWidgetController : 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; }
|
|
||||||
|
|
||||||
|
private weak Widget widget;
|
||||||
|
private FileTransfer file_transfer;
|
||||||
private StreamInteractor? stream_interactor;
|
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.widget = widget;
|
||||||
|
this.ref();
|
||||||
widget.clicked.connect(on_clicked);
|
this.widget.weak_ref(() => {
|
||||||
widget.open_file.connect(open_file);
|
this.widget = null;
|
||||||
widget.save_file_as.connect(save_file);
|
this.unref();
|
||||||
widget.cancel_download.connect(cancel_download);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public void set_file_transfer(FileTransfer file_transfer, StreamInteractor stream_interactor) {
|
|
||||||
this.file_transfer = file_transfer;
|
this.file_transfer = file_transfer;
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
widget.name_label.label = file_name = file_transfer.file_name;
|
widget.open_file.connect(open_file);
|
||||||
|
widget.save_file_as.connect(save_file);
|
||||||
file_transfer.bind_property("path", this, "file-transfer-path");
|
widget.start_download.connect(start_download);
|
||||||
file_transfer.bind_property("state", this, "file-transfer-state");
|
widget.cancel_download.connect(cancel_download);
|
||||||
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() {
|
private void open_file() {
|
||||||
try{
|
try{
|
||||||
AppInfo.launch_default_for_uri(file_uri, null);
|
AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null);
|
||||||
} catch (Error err) {
|
} 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() {
|
private void save_file() {
|
||||||
var save_dialog = new FileChooserNative(_("Save as…"), widget.get_root() as Gtk.Window, FileChooserAction.SAVE, null, null);
|
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_modal(true);
|
||||||
save_dialog.set_current_name(file_name);
|
save_dialog.set_current_name(file_transfer.file_name);
|
||||||
|
|
||||||
save_dialog.response.connect(() => {
|
save_dialog.response.connect(() => {
|
||||||
try{
|
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) {
|
} 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();
|
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() {
|
private void cancel_download() {
|
||||||
file_transfer.cancellable.cancel();
|
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() {
|
private void on_clicked() {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case FileTransfer.State.COMPLETE:
|
case FileTransfer.State.COMPLETE:
|
||||||
open_file();
|
widget.activate_action("file.open", null);
|
||||||
break;
|
break;
|
||||||
case FileTransfer.State.NOT_STARTED:
|
case FileTransfer.State.NOT_STARTED:
|
||||||
assert(stream_interactor != null && file_transfer != null);
|
widget.activate_action("file.download", null);
|
||||||
stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
|
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
|
||||||
|
|
|
@ -217,6 +217,7 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
if (correction_allowed) {
|
if (correction_allowed) {
|
||||||
Plugins.MessageAction action1 = new Plugins.MessageAction();
|
Plugins.MessageAction action1 = new Plugins.MessageAction();
|
||||||
action1.icon_name = "document-edit-symbolic";
|
action1.icon_name = "document-edit-symbolic";
|
||||||
|
action1.tooltip = _("Edit message");
|
||||||
action1.callback = (button, content_meta_item_activated, widget) => {
|
action1.callback = (button, content_meta_item_activated, widget) => {
|
||||||
this.in_edit_mode = true;
|
this.in_edit_mode = true;
|
||||||
};
|
};
|
||||||
|
@ -225,6 +226,7 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
|
|
||||||
Plugins.MessageAction reply_action = new Plugins.MessageAction();
|
Plugins.MessageAction reply_action = new Plugins.MessageAction();
|
||||||
reply_action.icon_name = "mail-reply-sender-symbolic";
|
reply_action.icon_name = "mail-reply-sender-symbolic";
|
||||||
|
reply_action.tooltip = _("Reply");
|
||||||
reply_action.callback = (button, content_meta_item_activated, widget) => {
|
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) }));
|
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) {
|
if (supports_reaction) {
|
||||||
Plugins.MessageAction action2 = new Plugins.MessageAction();
|
Plugins.MessageAction action2 = new Plugins.MessageAction();
|
||||||
action2.icon_name = "dino-emoticon-add-symbolic";
|
action2.icon_name = "dino-emoticon-add-symbolic";
|
||||||
|
action2.tooltip = _("Add reaction");
|
||||||
EmojiChooser chooser = new EmojiChooser();
|
EmojiChooser chooser = new EmojiChooser();
|
||||||
chooser.emoji_picked.connect((emoji) => {
|
chooser.emoji_picked.connect((emoji) => {
|
||||||
stream_interactor.get_module(Reactions.IDENTITY).add_reaction(message_item.conversation, message_item, emoji);
|
stream_interactor.get_module(Reactions.IDENTITY).add_reaction(message_item.conversation, message_item, emoji);
|
||||||
|
|
|
@ -151,6 +151,8 @@ public class GlobalSearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear_search() {
|
private void clear_search() {
|
||||||
|
// Scroll to top
|
||||||
|
results_scrolled.vadjustment.value = 0;
|
||||||
foreach (Widget widget in results_box_children) {
|
foreach (Widget widget in results_box_children) {
|
||||||
results_box.remove(widget);
|
results_box.remove(widget);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class MainWindow : Adw.Window {
|
||||||
public Adw.Leaflet leaflet;
|
public Adw.Leaflet leaflet;
|
||||||
public Box left_box;
|
public Box left_box;
|
||||||
public Box right_box;
|
public Box right_box;
|
||||||
public Revealer search_revealer;
|
public Adw.Flap search_flap;
|
||||||
public GlobalSearch global_search;
|
public GlobalSearch global_search;
|
||||||
private Stack stack = new Stack();
|
private Stack stack = new Stack();
|
||||||
private Stack left_stack;
|
private Stack left_stack;
|
||||||
|
@ -35,7 +35,7 @@ public class MainWindow : Adw.Window {
|
||||||
|
|
||||||
class construct {
|
class construct {
|
||||||
var shortcut = new Shortcut(new KeyvalTrigger(Key.F, ModifierType.CONTROL_MASK), new CallbackAction((widget, args) => {
|
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;
|
return false;
|
||||||
}));
|
}));
|
||||||
add_shortcut(shortcut);
|
add_shortcut(shortcut);
|
||||||
|
@ -67,11 +67,11 @@ public class MainWindow : Adw.Window {
|
||||||
left_stack = (Stack) builder.get_object("left_stack");
|
left_stack = (Stack) builder.get_object("left_stack");
|
||||||
right_stack = (Stack) builder.get_object("right_stack");
|
right_stack = (Stack) builder.get_object("right_stack");
|
||||||
conversation_view = (ConversationView) builder.get_object("conversation_view");
|
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 = ((ConversationSelector) builder.get_object("conversation_list")).init(stream_interactor);
|
||||||
conversation_selector.conversation_selected.connect_after(() => leaflet.navigate(Adw.NavigationDirection.FORWARD));
|
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);
|
global_search = new GlobalSearch(stream_interactor);
|
||||||
search_frame.set_child(global_search.get_widget());
|
search_frame.set_child(global_search.get_widget());
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,10 @@ public class MainWindowController : Object {
|
||||||
|
|
||||||
this.conversation_view_controller = new ConversationViewController(window.conversation_view, window.conversation_titlebar, stream_interactor);
|
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(() => {
|
window.search_flap.notify["reveal-flap"].connect(() => {
|
||||||
if (window.search_revealer.child_revealed) {
|
if (window.search_flap.reveal_flap) {
|
||||||
if (window.conversation_view.conversation_frame.conversation != null && window.global_search.search_entry.text == "") {
|
if (window.conversation_view.conversation_frame.conversation != null && window.global_search.search_entry.text == "") {
|
||||||
reset_search_entry();
|
reset_search_entry();
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,9 @@ public class MainWindowController : Object {
|
||||||
window.global_search.selected_item.connect((item) => {
|
window.global_search.selected_item.connect((item) => {
|
||||||
select_conversation(item.conversation, false, false);
|
select_conversation(item.conversation, false, false);
|
||||||
window.conversation_view.conversation_frame.initialize_around_message(item.conversation, item);
|
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(() => {
|
window.welcome_placeholder.primary_button.clicked.connect(() => {
|
||||||
|
@ -91,16 +93,6 @@ public class MainWindowController : Object {
|
||||||
|
|
||||||
Widget window_widget = ((Widget) window);
|
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();
|
EventControllerKey key_event_controller = new EventControllerKey();
|
||||||
window_widget.add_controller(key_event_controller);
|
window_widget.add_controller(key_event_controller);
|
||||||
// TODO GTK4: Why doesn't this work with key_pressed signal
|
// TODO GTK4: Why doesn't this work with key_pressed signal
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
88
main/src/ui/widgets/fixed_ratio_picture.vala
Normal file
88
main/src/ui/widgets/fixed_ratio_picture.vala
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
59
main/src/ui/widgets/natural_size_increase.vala
Normal file
59
main/src/ui/widgets/natural_size_increase.vala
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue