aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui
diff options
context:
space:
mode:
authorbobufa <bobufa@users.noreply.github.com>2018-07-25 20:41:51 +0200
committerbobufa <bobufa@users.noreply.github.com>2018-08-13 22:39:18 +0200
commite376a577b6bfcdd9bdc0cc6ca283d99199a0197a (patch)
tree741cab1ce822fcfc89655e762abfe2ceb3892357 /main/src/ui
parent4901b096708ff5ca54c3e5393de74f2a8be55894 (diff)
downloaddino-e376a577b6bfcdd9bdc0cc6ca283d99199a0197a.tar.gz
dino-e376a577b6bfcdd9bdc0cc6ca283d99199a0197a.zip
improve sidebar UI
- only display messages that are content items - only display messages for active accounts - "fix" textview issue - add empty states (no search, no results)
Diffstat (limited to 'main/src/ui')
-rw-r--r--main/src/ui/application.vala2
-rw-r--r--main/src/ui/conversation_summary/conversation_view.vala93
-rw-r--r--main/src/ui/conversation_summary/message_textview.vala1
-rw-r--r--main/src/ui/conversation_titlebar/search_entry.vala4
-rw-r--r--main/src/ui/global_search.vala129
-rw-r--r--main/src/ui/unified_window.vala23
-rw-r--r--main/src/ui/util/helper.vala13
7 files changed, 171 insertions, 94 deletions
diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala
index 22d6d93d..86a4e288 100644
--- a/main/src/ui/application.vala
+++ b/main/src/ui/application.vala
@@ -32,7 +32,7 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
window = new UnifiedWindow(this, stream_interactor);
notifications = new Notifications(stream_interactor, window);
notifications.start();
- notifications.conversation_selected.connect(window.on_conversation_selected);
+ notifications.conversation_selected.connect((conversation) => window.on_conversation_selected(conversation));
}
window.present();
});
diff --git a/main/src/ui/conversation_summary/conversation_view.vala b/main/src/ui/conversation_summary/conversation_view.vala
index a1863cf4..c74884a4 100644
--- a/main/src/ui/conversation_summary/conversation_view.vala
+++ b/main/src/ui/conversation_summary/conversation_view.vala
@@ -34,6 +34,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
private bool animate = false;
private bool firstLoad = true;
private bool at_current_content = true;
+ private bool reload_messages = true;
public ConversationView init(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
@@ -57,7 +58,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
return true;
});
- Util.force_base_background(this);
return this;
}
@@ -66,14 +66,71 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
if (firstLoad) {
int timeout = firstLoad ? 1000 : 0;
Timeout.add(timeout, () => {
+ stack.set_visible_child_name("void");
initialize_for_conversation_(conversation);
+ display_latest();
+ stack.set_visible_child_name("main");
return false;
});
firstLoad = false;
} else {
+ stack.set_visible_child_name("void");
initialize_for_conversation_(conversation);
+ display_latest();
+ stack.set_visible_child_name("main");
+ }
+ }
+
+ public void initialize_around_message(Conversation conversation, ContentItem content_item) {
+ stack.set_visible_child_name("void");
+ clear();
+ initialize_for_conversation_(conversation);
+ Gee.List<ContentMetaItem> before_items = content_populator.populate_before(conversation, content_item, 40);
+ foreach (ContentMetaItem item in before_items) {
+ do_insert_item(item);
+ }
+ ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item);
+ meta_item.can_merge = false;
+ Widget w = insert_new(meta_item);
+ content_items.add(meta_item);
+ meta_items.add(meta_item);
+
+ Gee.List<ContentMetaItem> after_items = content_populator.populate_after(conversation, content_item, 40);
+ foreach (ContentMetaItem item in after_items) {
+ do_insert_item(item);
+ }
+ if (after_items.size == 40) {
+ at_current_content = false;
+ }
+ {
+ int h = 0, i = 0;
+ main.@foreach((widget) => {
+ if (i >= before_items.size) return;
+ ConversationItemSkeleton? sk = widget as ConversationItemSkeleton;
+ i += sk != null ? sk.items.size : 1;
+ int minimum_height, natural_height;
+ widget.get_preferred_height_for_width(main.get_allocated_width() - 2 * main.margin, out minimum_height, out natural_height);
+ h += minimum_height + 15;
+ });
+ print(@"height_for_w: $(h)\n");
}
+ reload_messages = false;
+ Timeout.add(700, () => {
+ int h = 0, i = 0;
+ main.@foreach((widget) => {
+ if (i >= before_items.size) return;
+ ConversationItemSkeleton? sk = widget as ConversationItemSkeleton;
+ i += sk != null ? sk.items.size : 1;
+ h += widget.get_allocated_height() + 15;
+ });
+ print(@"timeout: $(h)\n");
+ scrolled.vadjustment.value = h - scrolled.vadjustment.page_size * 1/3;
+ w.get_style_context().add_class("highlight-once");
+ reload_messages = true;
+ stack.set_visible_child_name("main");
+ return false;
+ });
}
private void initialize_for_conversation_(Conversation? conversation) {
@@ -84,7 +141,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
}
}
this.conversation = conversation;
- stack.set_visible_child_name("void");
foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) {
populator.init(conversation, this, Plugins.WidgetType.GTK);
@@ -92,17 +148,12 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
content_populator.init(this, conversation, Plugins.WidgetType.GTK);
subscription_notification.init(conversation, this);
- display_latest();
-
- stack.set_visible_child_name("main");
+ animate = false;
+ Timeout.add(20, () => { animate = true; return false; });
}
private void display_latest() {
clear();
- was_upper = null;
- was_page_size = null;
- animate = false;
- Timeout.add(20, () => { animate = true; return false; });
Gee.List<ContentMetaItem> items = content_populator.populate_latest(conversation, 40);
foreach (ContentMetaItem item in items) {
@@ -163,7 +214,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
lower_start_item.encryption == item.encryption &&
(item.mark == Message.Marked.WONTSEND) == (lower_start_item.mark == Message.Marked.WONTSEND)) {
lower_skeleton.add_meta_item(item);
- force_alloc_width(lower_skeleton, main.get_allocated_width());
+ Util.force_alloc_width(lower_skeleton, main.get_allocated_width());
widgets[item] = widgets[lower_start_item];
item_item_skeletons[item] = lower_skeleton;
@@ -174,7 +225,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
return false;
}
- private void insert_new(Plugins.MetaConversationItem item) {
+ private Widget insert_new(Plugins.MetaConversationItem item) {
Plugins.MetaConversationItem? lower_item = meta_items.lower(item);
// Does another skeleton need to be split?
@@ -206,7 +257,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
main.add(insert);
}
widgets[item] = insert;
- force_alloc_width(insert, main.get_allocated_width());
+ Util.force_alloc_width(insert, main.get_allocated_width());
main.reorder_child(insert, index);
// If an item from the past was added, add everything between that item and the (post-)first present item
@@ -222,6 +273,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
}
}
}
+ return insert;
}
private void split_at_time(ConversationItemSkeleton split_skeleton, DateTime time) {
@@ -273,10 +325,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
Gee.List<ContentMetaItem> items = content_populator.populate_before(conversation, (content_items.first() as ContentMetaItem).content_item, 20);
foreach (ContentMetaItem item in items) {
do_insert_item(item);
- if (content_items.size > 50) {
- do_remove_item(content_items.last());
- at_current_content = false;
- }
}
} else {
reloading_mutex.unlock();
@@ -310,21 +358,14 @@ public class ConversationView : Box, Plugins.ConversationItemCollection {
int res = a.sort_time.compare(b.sort_time);
if (res == 0) {
if (a.seccondary_sort_indicator < b.seccondary_sort_indicator) res = -1;
- else if (a.seccondary_sort_indicator > b.seccondary_sort_indicator) res = 1;
+ else if (a.seccondary_sort_indicator > b.seccondary_sort_indicator) res = 1;
}
return res;
}
- // Workaround GTK TextView issues
- private void force_alloc_width(Widget widget, int width) {
- Allocation alloc = Allocation();
- widget.get_preferred_width(out alloc.width, null);
- widget.get_preferred_height(out alloc.height, null);
- alloc.width = width;
- widget.size_allocate(alloc);
- }
-
private void clear() {
+ was_upper = null;
+ was_page_size = null;
content_items.clear();
meta_items.clear();
item_skeletons.clear();
diff --git a/main/src/ui/conversation_summary/message_textview.vala b/main/src/ui/conversation_summary/message_textview.vala
index 0c38c269..71ca35f8 100644
--- a/main/src/ui/conversation_summary/message_textview.vala
+++ b/main/src/ui/conversation_summary/message_textview.vala
@@ -24,7 +24,6 @@ public class MessageTextView : TextView {
motion_notify_event.connect(change_cursor_over_url);
update_display_style();
- Util.force_base_background(this, "textview, text:not(:selected)");
style_updated.connect(update_display_style);
populate_popup.connect(populate_context_menu);
}
diff --git a/main/src/ui/conversation_titlebar/search_entry.vala b/main/src/ui/conversation_titlebar/search_entry.vala
index e80e5954..b452bdce 100644
--- a/main/src/ui/conversation_titlebar/search_entry.vala
+++ b/main/src/ui/conversation_titlebar/search_entry.vala
@@ -24,9 +24,7 @@ public class SearchMenuEntry : Plugins.ConversationTitlebarEntry, Object {
}
public class GlobalSearchButton : Plugins.ConversationTitlebarWidget, Gtk.ToggleButton {
- public new void set_conversation(Conversation conversation) {
- active = false;
- }
+ public new void set_conversation(Conversation conversation) { }
}
}
diff --git a/main/src/ui/global_search.vala b/main/src/ui/global_search.vala
index cadee9c1..8bd13e6f 100644
--- a/main/src/ui/global_search.vala
+++ b/main/src/ui/global_search.vala
@@ -1,3 +1,4 @@
+using Gee;
using Gtk;
using Pango;
@@ -7,6 +8,8 @@ namespace Dino.Ui {
[GtkTemplate (ui = "/im/dino/Dino/global_search.ui")]
class GlobalSearch : Box {
+ public signal void selected_item(MessageItem item);
+
private StreamInteractor stream_interactor;
private string search = "";
private int loaded_results = -1;
@@ -16,6 +19,7 @@ class GlobalSearch : Box {
[GtkChild] public Label entry_number_label;
[GtkChild] public ScrolledWindow results_scrolled;
[GtkChild] public Box results_box;
+ [GtkChild] public Stack results_empty_stack;
public GlobalSearch init(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
@@ -27,7 +31,7 @@ class GlobalSearch : Box {
results_scrolled.vadjustment.notify["value"].connect(() => {
if (results_scrolled.vadjustment.upper - (results_scrolled.vadjustment.value + results_scrolled.vadjustment.page_size) < 100) {
if (!reloading_mutex.trylock()) return;
- Gee.List<Message> new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results);
+ Gee.List<MessageItem> new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results);
if (new_messages.size == 0) {
reloading_mutex.unlock();
return;
@@ -51,37 +55,47 @@ class GlobalSearch : Box {
clear_search();
this.search = search;
- int match_count = stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search);
- entry_number_label.label = "<i>" + _("%i search results").printf(match_count) + "</i>";
- Gee.List<Message> messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search);
- loaded_results += messages.size;
- append_messages(messages);
+ if (get_keywords(search).is_empty) {
+ results_empty_stack.set_visible_child_name("empty");
+ return;
+ }
+
+ Gee.List<MessageItem> messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search);
+ if (messages.size == 0) {
+ results_empty_stack.set_visible_child_name("no-result");
+ } else {
+ results_empty_stack.set_visible_child_name("results");
+
+ int match_count = messages.size < 10 ? messages.size : stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search);
+ entry_number_label.label = "<i>" + _("%i search results").printf(match_count) + "</i>";
+ loaded_results += messages.size;
+ append_messages(messages);
+ }
}
- private void append_messages(Gee.List<Message> messages) {
- foreach (Message message in messages) {
- if (message.from == null) {
- print("wtf null\n");
- continue;
- }
- Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message);
- Gee.List<Message> before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, message.local_time, message.id, 1);
- Gee.List<Message> after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(conversation, message.local_time, message.id, 1);
+ private void append_messages(Gee.List<MessageItem> messages) {
+ foreach (MessageItem item in messages) {
+ Gee.List<MessageItem> before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(item.conversation, item.message.local_time, item.message.id, 1);
+ Gee.List<MessageItem> after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(item.conversation, item.message.local_time, item.message.id, 1);
Box context_box = new Box(Orientation.VERTICAL, 5) { visible=true };
if (before_message != null && before_message.size > 0) {
context_box.add(get_context_message_widget(before_message.first()));
}
- context_box.add(get_match_message_widget(message));
+
+ Widget match_widget = get_match_message_widget(item);
+ Util.force_alloc_width(match_widget, results_empty_stack.get_allocated_width() - results_box.margin * 2);
+ context_box.add(match_widget);
+
if (after_message != null && after_message.size > 0) {
context_box.add(get_context_message_widget(after_message.first()));
}
- Label date_label = new Label(ConversationSummary.DefaultSkeletonHeader.get_relative_time(message.time)) { xalign=0, visible=true };
+ Label date_label = new Label(ConversationSummary.DefaultSkeletonHeader.get_relative_time(item.display_time)) { xalign=0, visible=true };
date_label.get_style_context().add_class("dim-label");
- string display_name = Util.get_conversation_display_name(stream_interactor, conversation);
- string title = message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name);
+ string display_name = Util.get_conversation_display_name(stream_interactor, item.conversation);
+ string title = item.message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name);
Box header_box = new Box(Orientation.HORIZONTAL, 10) { margin_left=7, visible=true };
header_box.add(new Label(@"<b>$(Markup.escape_text(title))</b>") { ellipsize=EllipsizeMode.END, xalign=0, use_markup=true, visible=true });
header_box.add(date_label);
@@ -94,21 +108,12 @@ class GlobalSearch : Box {
}
}
- // Workaround GTK TextView issues
- private void force_alloc_width(Widget widget, int width) {
- Allocation alloc = Allocation();
- widget.get_preferred_width(out alloc.width, null);
- widget.get_preferred_height(out alloc.height, null);
- alloc.width = width;
- widget.size_allocate(alloc);
- }
-
- private Widget get_match_message_widget(Message message) {
- Grid grid = get_skeleton(message);
+ private Widget get_match_message_widget(MessageItem item) {
+ Grid grid = get_skeleton(item);
grid.margin_top = 3;
grid.margin_bottom = 3;
- string text = message.body.replace("\n", "").replace("\r", "");
+ string text = item.message.body.replace("\n", "").replace("\r", "");
if (text.length > 200) {
int index = text.index_of(search);
if (index + search.length <= 100) {
@@ -123,52 +128,68 @@ class GlobalSearch : Box {
tv.buffer.text = text;
TextTag link_tag = tv.buffer.create_tag("hit", background: "yellow");
- Regex url_regex = new Regex(search.down());
- MatchInfo match_info;
- url_regex.match(text.down(), 0, out match_info);
- for (; match_info.matches(); match_info.next()) {
- int start;
- int end;
- match_info.fetch_pos(0, out start, out end);
- start = text[0:start].char_count();
- end = text[0:end].char_count();
- TextIter start_iter;
- TextIter end_iter;
- tv.buffer.get_iter_at_offset(out start_iter, start);
- tv.buffer.get_iter_at_offset(out end_iter, end);
- tv.buffer.apply_tag(link_tag, start_iter, end_iter);
+ Gee.List<string> keywords = get_keywords(Regex.escape_string(search.down()));
+ foreach (string keyword in keywords) {
+ Regex url_regex = new Regex(keyword.down());
+ MatchInfo match_info;
+ url_regex.match(text.down(), 0, out match_info);
+ for (; match_info.matches(); match_info.next()) {
+ int start;
+ int end;
+ match_info.fetch_pos(0, out start, out end);
+ start = text[0:start].char_count();
+ end = text[0:end].char_count();
+ TextIter start_iter;
+ TextIter end_iter;
+ tv.buffer.get_iter_at_offset(out start_iter, start);
+ tv.buffer.get_iter_at_offset(out end_iter, end);
+ tv.buffer.apply_tag(link_tag, start_iter, end_iter);
+ }
}
- grid.attach(tv, 1, 1, 1, 1);
- // force_alloc_width(tv, this.width_request);
+ grid.attach(tv, 1, 1, 1, 1);
Button button = new Button() { relief=ReliefStyle.NONE, visible=true };
+ button.clicked.connect(() => {
+ selected_item(item);
+ });
button.add(grid);
return button;
}
- private Grid get_context_message_widget(Message message) {
- Grid grid = get_skeleton(message);
+ private Grid get_context_message_widget(MessageItem item) {
+ Grid grid = get_skeleton(item);
grid.margin_left = 7;
- Label label = new Label(message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true };
+ Label label = new Label(item.message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true };
grid.attach(label, 1, 1, 1, 1);
grid.opacity = 0.55;
return grid;
}
- private Grid get_skeleton(Message message) {
+ private Grid get_skeleton(MessageItem item) {
AvatarImage image = new AvatarImage() { height=32, width=32, margin_right=7, valign=Align.START, visible=true, allow_gray = false };
- image.set_jid(stream_interactor, message.from, message.account);
+ image.set_jid(stream_interactor, item.jid, item.message.account);
Grid grid = new Grid() { row_homogeneous=false, visible=true };
grid.attach(image, 0, 0, 1, 2);
- string display_name = Util.get_display_name(stream_interactor, message.from, message.account);
- string color = Util.get_name_hex_color(stream_interactor, message.account, message.from, false); // TODO Util.is_dark_theme(name_label)
+ string display_name = Util.get_display_name(stream_interactor, item.jid, item.message.account);
+ string color = Util.get_name_hex_color(stream_interactor, item.message.account, item.jid, false); // TODO Util.is_dark_theme(name_label)
Label name_label = new Label("") { use_markup=true, xalign=0, visible=true };
name_label.label = @"<span size='small' foreground=\"#$color\">$display_name</span>";
grid.attach(name_label, 1, 0, 1, 1);
return grid;
}
+
+ private static Gee.List<string> get_keywords(string search_string) {
+ Gee.List<string> ret = new ArrayList<string>();
+ foreach (string search in search_string.split(" ")) {
+ bool is_filter = search.has_prefix("from:") || search.has_prefix("in:") || search.has_prefix("with:");
+ if (!is_filter && search != "") {
+ ret.add(search);
+ }
+ }
+ return ret;
+ }
}
}
diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala
index cfcd2bff..60aeb832 100644
--- a/main/src/ui/unified_window.vala
+++ b/main/src/ui/unified_window.vala
@@ -39,7 +39,10 @@ public class UnifiedWindow : Window {
setup_unified();
setup_stack();
- conversation_titlebar.search_button.bind_property("active", search_revealer, "reveal-child", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+
+ conversation_titlebar.search_button.clicked.connect(() => {
+ search_revealer.reveal_child = conversation_titlebar.search_button.active;
+ });
search_revealer.notify["child-revealed"].connect(() => {
if (search_revealer.child_revealed) {
if (conversation_frame.conversation != null) {
@@ -58,6 +61,10 @@ public class UnifiedWindow : Window {
search_box.search_entry.text = "";
}
});
+ search_box.selected_item.connect((item) => {
+ on_conversation_selected(item.conversation, false, false);
+ conversation_frame.initialize_around_message(item.conversation, item);
+ });
paned.bind_property("position", headerbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
@@ -71,8 +78,8 @@ public class UnifiedWindow : Window {
accounts_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("accounts", null); });
conversations_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("add_chat", null); });
conversations_placeholder.secondary_button.clicked.connect(() => { get_application().activate_action("add_conference", null); });
- filterable_conversation_list.conversation_list.conversation_selected.connect(on_conversation_selected);
- conversation_list_titlebar.conversation_opened.connect(on_conversation_selected);
+ filterable_conversation_list.conversation_list.conversation_selected.connect((conversation) => on_conversation_selected(conversation));
+ conversation_list_titlebar.conversation_opened.connect((conversation) => on_conversation_selected(conversation));
check_stack();
}
@@ -89,15 +96,21 @@ public class UnifiedWindow : Window {
search_revealer.valign = Align.FILL;
}
- public void on_conversation_selected(Conversation conversation) {
+ public void on_conversation_selected(Conversation conversation, bool close_search = true, bool default_initialize_conversation = true) {
if (this.conversation == null || !this.conversation.equals(conversation)) {
this.conversation = conversation;
stream_interactor.get_module(ChatInteraction.IDENTITY).on_conversation_selected(conversation);
conversation.active = true; // only for conversation_selected
filterable_conversation_list.conversation_list.on_conversation_selected(conversation); // only for conversation_opened
+ if (close_search) {
+ conversation_titlebar.search_button.active = false;
+ search_revealer.reveal_child = false;
+ }
chat_input.initialize_for_conversation(conversation);
- conversation_frame.initialize_for_conversation(conversation);
+ if (default_initialize_conversation) {
+ conversation_frame.initialize_for_conversation(conversation);
+ }
conversation_titlebar.initialize_for_conversation(conversation);
}
}
diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala
index 3cadfffb..4e9e942d 100644
--- a/main/src/ui/util/helper.vala
+++ b/main/src/ui/util/helper.vala
@@ -118,10 +118,6 @@ public static void force_background(Gtk.Widget widget, string color, string sele
force_css(widget, force_background_css.printf(selector, color));
}
-public static void force_base_background(Gtk.Widget widget, string selector = "*") {
- force_background(widget, "@theme_base_color", selector);
-}
-
public static void force_color(Gtk.Widget widget, string color, string selector = "*") {
force_css(widget, force_color_css.printf(selector, color));
}
@@ -142,4 +138,13 @@ public static bool is_24h_format() {
return settings_format == "24h" || p_format == " ";
}
+// Workaround GTK TextView issues
+public static void force_alloc_width(Widget widget, int width) {
+ Allocation alloc = Allocation();
+ widget.get_preferred_width(out alloc.width, null);
+ widget.get_preferred_height(out alloc.height, null);
+ alloc.width = width;
+ widget.size_allocate(alloc);
+}
+
}