diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 35c93786..705cdfe1 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -1,5 +1,7 @@ using Gtk; +using Dino.Entities; + namespace Dino.Plugins { public enum Priority { @@ -20,7 +22,7 @@ public interface EncryptionListEntry : Object { public abstract Entities.Encryption encryption { get; } public abstract string name { get; } - public abstract bool can_encrypt(Entities.Conversation conversation); + public abstract bool can_encrypt(Conversation conversation); } public abstract class AccountSettingsEntry : Object { @@ -33,11 +35,22 @@ public abstract class AccountSettingsEntry : Object { } public interface AccountSettingsWidget : Gtk.Widget { - public abstract void set_account(Entities.Account account); + public abstract void set_account(Account account); public abstract signal void activated(); public abstract void deactivate(); } +public abstract class ContactDetailsProvider : Object { + public abstract string id { get; } + + public abstract void populate(Conversation conversation, ContactDetails contact_details); +} + +public class ContactDetails : Object { + public signal void save(); + public signal void add(string category, string label, string desc, Widget widget); +} + } \ No newline at end of file diff --git a/libdino/src/plugin/registry.vala b/libdino/src/plugin/registry.vala index 27cac6f3..e1ba605d 100644 --- a/libdino/src/plugin/registry.vala +++ b/libdino/src/plugin/registry.vala @@ -5,6 +5,7 @@ namespace Dino.Plugins { public class Registry { internal ArrayList encryption_list_entries = new ArrayList(); internal ArrayList account_settings_entries = new ArrayList(); + internal ArrayList contact_details_entries = new ArrayList(); public bool register_encryption_list_entry(EncryptionListEntry entry) { lock(encryption_list_entries) { @@ -28,6 +29,16 @@ public class Registry { return true; } } + + public bool register_contact_details_entry(ContactDetailsProvider entry) { + lock(contact_details_entries) { + foreach(ContactDetailsProvider e in contact_details_entries) { + if (e.id == entry.id) return false; + } + contact_details_entries.add(entry); + return true; + } + } } } \ No newline at end of file diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index bf9b4cda..71a66bb4 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -45,6 +45,16 @@ public class MucManager : StreamInteractionModule, Object { if (conversation != null) stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); } + [CCode (has_target = false)] public delegate void OnResult(Jid jid, Xep.DataForms.DataForm data_form, Object? store); + public void get_config_form(Account account, Jid jid, OnResult on_result, Object? store) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream == null) return; + stream.get_module(Xep.Muc.Module.IDENTITY).get_config_form(stream, jid.to_string(), (stream, jid, data_form, store) => { + Tuple tuple = store as Tuple; + tuple.a(new Jid(jid), data_form, tuple.b); + }, Tuple.create(on_result, store)); + } + public void change_subject(Account account, Jid jid, string subject) { Core.XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_subject(stream, jid.bare_jid.to_string(), subject); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 52f38c56..1d0364c4 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -27,6 +27,7 @@ set(RESOURCE_LIST add_conversation/list_row.ui add_conversation/select_jid_fragment.ui chat_input.ui + contact_details_dialog.ui conversation_list_titlebar.ui conversation_selector/view.ui conversation_selector/chat_row_tooltip.ui @@ -84,6 +85,9 @@ SOURCES src/ui/chat_input/occupants_tab_completer.vala src/ui/chat_input/smiley_converter.vala src/ui/chat_input/view.vala + src/ui/contact_details/settings_provider.vala + src/ui/contact_details/dialog.vala + src/ui/contact_details/muc_config_form_provider.vala src/ui/conversation_list_titlebar.vala src/ui/conversation_selector/chat_row.vala src/ui/conversation_selector/conversation_row.vala @@ -108,7 +112,8 @@ SOURCES src/ui/occupant_menu/view.vala src/ui/settings_dialog.vala src/ui/unified_window.vala - src/ui/util.vala + src/ui/util/helper.vala + src/ui/util/label_hybrid.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi diff --git a/main/data/contact_details_dialog.ui b/main/data/contact_details_dialog.ui new file mode 100644 index 00000000..5b074650 --- /dev/null +++ b/main/data/contact_details_dialog.ui @@ -0,0 +1,109 @@ + + + + diff --git a/main/data/menu_conversation.ui b/main/data/menu_conversation.ui index 9fe2a2b7..42b580be 100644 --- a/main/data/menu_conversation.ui +++ b/main/data/menu_conversation.ui @@ -2,6 +2,7 @@
+ app.contact_details Contact Details
diff --git a/main/src/ui/add_conversation/chat/dialog.vala b/main/src/ui/add_conversation/chat/dialog.vala index 6873b4ca..a36731a5 100644 --- a/main/src/ui/add_conversation/chat/dialog.vala +++ b/main/src/ui/add_conversation/chat/dialog.vala @@ -53,7 +53,7 @@ public class Dialog : Gtk.Dialog { select_jid_fragment.add_jid.connect((row) => { AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); add_contact_dialog.set_transient_for(this); - add_contact_dialog.show(); + add_contact_dialog.present(); }); select_jid_fragment.edit_jid.connect(() => { diff --git a/main/src/ui/add_conversation/conference/dialog.vala b/main/src/ui/add_conversation/conference/dialog.vala index 2da37c08..412fb07c 100644 --- a/main/src/ui/add_conversation/conference/dialog.vala +++ b/main/src/ui/add_conversation/conference/dialog.vala @@ -92,13 +92,13 @@ public class Dialog : Gtk.Dialog { select_fragment.add_jid.connect((row) => { AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); dialog.set_transient_for(this); - dialog.show(); + dialog.present(); }); select_fragment.edit_jid.connect((row) => { ConferenceListRow conference_row = row as ConferenceListRow; AddGroupchatDialog dialog = new AddGroupchatDialog.for_conference(stream_interactor, conference_row.account, conference_row.bookmark); dialog.set_transient_for(this); - dialog.show(); + dialog.present(); }); select_fragment.remove_jid.connect((row) => { ConferenceListRow conference_row = row as ConferenceListRow; diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 35791b07..72bd4d0d 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -19,7 +19,7 @@ public class Dino.Ui.Application : Dino.Application { notifications = new Notifications(stream_interaction, window); notifications.start(); notifications.conversation_selected.connect(window.on_conversation_selected); - window.show(); + window.present(); }); } @@ -28,13 +28,13 @@ public class Dino.Ui.Application : Dino.Application { dialog.set_transient_for(window); dialog.account_enabled.connect(add_connection); dialog.account_disabled.connect(remove_connection); - dialog.show(); + dialog.present(); } private void show_settings_window() { SettingsDialog dialog = new SettingsDialog(); dialog.set_transient_for(window); - dialog.show(); + dialog.present(); } private void create_set_app_menu() { diff --git a/main/src/ui/contact_details/dialog.vala b/main/src/ui/contact_details/dialog.vala new file mode 100644 index 00000000..7e2f6100 --- /dev/null +++ b/main/src/ui/contact_details/dialog.vala @@ -0,0 +1,120 @@ +using Gee; +using Gtk; +using Markup; + +using Dino.Entities; + +namespace Dino.Ui.ContactDetails { + +[GtkTemplate (ui = "/org/dino-im/contact_details_dialog.ui")] +public class Dialog : Gtk.Dialog { + + [GtkChild] public Image avatar; + [GtkChild] public Label name_label; + [GtkChild] public Label jid_label; + [GtkChild] public Label account_label; + [GtkChild] public Box main_box; + + private StreamInteractor stream_interactor; + private Conversation conversation; + + private Plugins.ContactDetails contact_details = new Plugins.ContactDetails(); + private HashMap categories = new HashMap(); + private Util.LabelHybridGroup hybrid_group = new Util.LabelHybridGroup(); + + public Dialog(StreamInteractor stream_interactor, Conversation conversation) { + Object(use_header_bar : 1); + this.stream_interactor = stream_interactor; + this.conversation = conversation; + + title = conversation.type_ == Conversation.Type.GROUPCHAT ? _("Conference Details") : _("Contact Details"); + (get_header_bar() as HeaderBar).set_subtitle(Util.get_conversation_display_name(stream_interactor, conversation)); + + name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); + jid_label.label = conversation.counterpart.to_string(); + account_label.label = "via " + conversation.account.bare_jid.to_string(); + Util.image_set_from_scaled_pixbuf(avatar, (new AvatarGenerator(50, 50, avatar.scale_factor)).draw_conversation(stream_interactor, conversation)); + + contact_details.add.connect(add_entry); + + Application app = GLib.Application.get_default() as Application; + app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor)); + app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor)); + + foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { + provider.populate(conversation, contact_details); + } + + destroy.connect(() => { + contact_details.save(); + }); + } + + public void add_entry(string category, string label, string? description, Widget w) { + add_category(category); + + ListBoxRow list_row = new ListBoxRow() { activatable=false, visible=true }; + Box row = new Box(Orientation.HORIZONTAL, 20) { margin_left=15, margin_right=15, margin_top=3, margin_bottom=3, visible=true }; + list_row.add(row); + Label label_label = new Label(label) { xalign=0, yalign=0.5f, hexpand=true, visible=true }; + if (description != null && description != "") { + Box box = new Box(Orientation.VERTICAL, 0) { visible=true }; + box.add(label_label); + Label desc_label = new Label("") { xalign=0, yalign=0.5f, hexpand=true, visible=true }; + desc_label.set_markup("%s".printf(Markup.escape_text(description))); + desc_label.get_style_context().add_class("dim-label"); + box.add(desc_label); + row.add(box); + } else { + row.add(label_label); + } + + Widget widget = w; + if (widget.get_type().is_a(typeof(Entry))) { + Util.EntryLabelHybrid hybrid = new Util.EntryLabelHybrid(widget as Entry) { xalign=1, visible=true }; + hybrid_group.add(hybrid); + widget = hybrid; + } else if (widget.get_type().is_a(typeof(ComboBoxText))) { + Util.ComboBoxTextLabelHybrid hybrid = new Util.ComboBoxTextLabelHybrid(widget as ComboBoxText) { xalign=1, visible=true }; + hybrid_group.add(hybrid); + widget = hybrid; + } + widget.margin_bottom = 5; + widget.margin_top = 5; + + + row.add(widget); + categories[category].add(list_row); + + Idle.add(() => { + int pref_height, pref_width; + get_content_area().get_preferred_height(null, out pref_height); + get_preferred_width(out pref_width, null); + resize(pref_width, int.min(500, pref_height)); + return false; + }); + } + + public void add_category(string category) { + if (!categories.has_key(category)) { + ListBox list_box = new ListBox() { selection_mode=SelectionMode.NONE, visible=true }; + categories[category] = list_box; + list_box.set_header_func((row, before_row) => { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + }); + Box box = new Box(Orientation.VERTICAL, 5) { margin_top=12, margin_bottom=12, visible=true }; + Label category_label = new Label("") { xalign=0, visible=true }; + category_label.set_markup(@"$(Markup.escape_text(category))"); + box.add(category_label); + Frame frame = new Frame(null) { visible=true }; + frame.add(list_box); + box.add(frame); + main_box.add(box); + } + } +} + +} + diff --git a/main/src/ui/contact_details/muc_config_form_provider.vala b/main/src/ui/contact_details/muc_config_form_provider.vala new file mode 100644 index 00000000..44659888 --- /dev/null +++ b/main/src/ui/contact_details/muc_config_form_provider.vala @@ -0,0 +1,131 @@ +using Gee; +using Gtk; + +using Dino.Entities; +using Xmpp.Xep; + +namespace Dino.Ui.ContactDetails { + +public class MucConfigFormProvider : Plugins.ContactDetailsProvider { + public override string id { get { return "muc_config_form"; } } + private StreamInteractor stream_interactor; + + public MucConfigFormProvider(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public override void populate(Conversation conversation, Plugins.ContactDetails contact_details) { + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + Xmpp.Core.XmppStream? stream = stream_interactor.get_stream(conversation.account); + if (stream == null) return; + stream_interactor.get_module(MucManager.IDENTITY).get_config_form(conversation.account, conversation.counterpart, (jid, data_form, store) => { + Plugins.ContactDetails contact_details_ = store as Plugins.ContactDetails; + contact_details_.save.connect(() => { + data_form.submit(); + }); + Idle.add(() => { + for (int i = 0; i < data_form.fields.size; i++) { + DataForms.DataForm.Field field = data_form.fields[i]; + add_field(field, contact_details_); + } + return false; + }); + }, contact_details); + } + } + + public static void add_field(DataForms.DataForm.Field field, Plugins.ContactDetails contact_details) { + string label = field.label ?? ""; + string? desc = null; + switch (field.var) { + case "muc#roomconfig_roomname": + label = _("Name"); + desc = _("Name of the room"); + break; + case "muc#roomconfig_roomdesc": + label = _("Description"); + desc = _("Description of the room"); + break; + case "muc#roomconfig_persistentroom": + label = _("Persistent"); + break; + case "muc#roomconfig_publicroom": + label = _("Publicly searchable"); + break; + case "muc#roomconfig_changesubject": + label = _("Occupants may change subject"); + break; + case "muc#roomconfig_whois": + label = _("Discover real jids"); + desc = "Who may discover real jids"; + break; + case "muc#roomconfig_roomsecret": + label = _("Password"); + desc = _("Passwort required to enter the room. Leave empty for none."); + break; + case "muc#roomconfig_moderatedroom": + label = _("Moderated"); + break; + case "muc#roomconfig_membersonly": + label = _("Members only"); + desc = _("Only members may enter the room"); + break; + case "muc#roomconfig_historylength": + label = _("Message History"); + desc = _("Maximum number of history messages returned by the room"); + break; + } + + Widget? widget = get_widget(field); + if (widget != null) contact_details.add(_("Room Configuration"), label, desc, widget); + } + + private static Widget? get_widget(DataForms.DataForm.Field field) { + switch (field.type_) { + case DataForms.DataForm.Type.BOOLEAN: + DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField; + Switch sw = new Switch() { active=boolean_field.value, valign=Align.CENTER, visible=true }; + sw.state_set.connect((state) => { + boolean_field.value = state; + return false; + }); + return sw; + case DataForms.DataForm.Type.JID_MULTI: + return null; + case DataForms.DataForm.Type.LIST_SINGLE: + DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField; + ComboBoxText combobox = new ComboBoxText() { valign=Align.CENTER, visible=true }; + for (int i = 0; i < list_single_field.options.size; i++) { + DataForms.DataForm.Option option = list_single_field.options[i]; + combobox.append(option.value, option.label); + if (option.value == list_single_field.value) combobox.active = i; + } + combobox.changed.connect(() => { + list_single_field.value = combobox.get_active_id(); + }); + return combobox; + case DataForms.DataForm.Type.LIST_MULTI: + return null; + case DataForms.DataForm.Type.TEXT_PRIVATE: + DataForms.DataForm.TextPrivateField text_private_field = field as DataForms.DataForm.TextPrivateField; + Entry entry = new Entry() { text=text_private_field.value ?? "", valign=Align.CENTER, visible=true, visibility=false }; + entry.key_release_event.connect(() => { + text_private_field.value = entry.text; + return false; + }); + return entry; + case DataForms.DataForm.Type.TEXT_SINGLE: + DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField; + Entry entry = new Entry() { text=text_single_field.value ?? "", valign=Align.CENTER, visible=true }; + entry.key_release_event.connect(() => { + text_single_field.value = entry.text; + return false; + }); + return entry; + default: + return null; + } + } +} + +} \ No newline at end of file diff --git a/main/src/ui/contact_details/settings_provider.vala b/main/src/ui/contact_details/settings_provider.vala new file mode 100644 index 00000000..fd49f9a1 --- /dev/null +++ b/main/src/ui/contact_details/settings_provider.vala @@ -0,0 +1,102 @@ +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ContactDetails { + +public class SettingsProvider : Plugins.ContactDetailsProvider { + public override string id { get { return "chat_settings"; } } + + private StreamInteractor stream_interactor; + + public SettingsProvider(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public override void populate(Conversation conversation, Plugins.ContactDetails contact_details) { + if (conversation.type_ == Conversation.Type.CHAT) { + ComboBoxText[] comboboxes = new ComboBoxText[2]; + for (int i = 0; i < 3; i++) { + comboboxes[i] = new ComboBoxText() { visible=true }; + comboboxes[i].append("default", _("Default")); + comboboxes[i].append("on", _("On")); + comboboxes[i].append("off", _("Off")); + } + + contact_details.add(_("Settings"), _("Send typing notifications"), "", comboboxes[0]); + comboboxes[0].active_id = get_setting_id(conversation.get_send_typing_setting()); + comboboxes[0].changed.connect(() => { print("changed!\n"); conversation.send_typing = get_setting(comboboxes[0].active_id); } ); + + contact_details.add(_("Settings"), _("Send message marker"), "", comboboxes[1]); + comboboxes[1].active_id = get_setting_id(conversation.get_send_marker_setting()); + comboboxes[1].changed.connect(() => { conversation.send_marker = get_setting(comboboxes[1].active_id); } ); + + contact_details.add(_("Settings"), _("Notifications"), "", comboboxes[2]); + comboboxes[2].active_id = get_notify_setting_id(conversation.get_notification_setting(stream_interactor)); + comboboxes[2].changed.connect(() => { conversation.notify_setting = get_notify_setting(comboboxes[2].active_id); } ); + } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { + ComboBoxText combobox = new ComboBoxText() { visible=true }; + combobox.append("default", _("Default")); + combobox.append("hightlight", _("Only when mentioned")); + combobox.append("on", _("On")); + combobox.append("off", _("Off")); + contact_details.add(_("Local Settings"), _("Notifications"), "", combobox); + combobox.active_id = get_notify_setting_id(conversation.get_notification_setting(stream_interactor)); + combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } ); + } + } + + public Conversation.Setting get_setting(string id) { + switch (id) { + case "default": + return Conversation.Setting.DEFAULT; + case "on": + return Conversation.Setting.ON; + case "off": + return Conversation.Setting.OFF; + } + assert_not_reached(); + } + + public Conversation.NotifySetting get_notify_setting(string id) { + switch (id) { + case "default": + return Conversation.NotifySetting.DEFAULT; + case "on": + return Conversation.NotifySetting.ON; + case "off": + return Conversation.NotifySetting.OFF; + case "highlight": + return Conversation.NotifySetting.HIGHLIGHT; + } + assert_not_reached(); + } + + public string get_setting_id(Conversation.Setting setting) { + switch (setting) { + case Conversation.Setting.DEFAULT: + return "default"; + case Conversation.Setting.ON: + return "on"; + case Conversation.Setting.OFF: + return "off"; + } + assert_not_reached(); + } + + public string get_notify_setting_id(Conversation.NotifySetting setting) { + switch (setting) { + case Conversation.NotifySetting.DEFAULT: + return "default"; + case Conversation.NotifySetting.ON: + return "on"; + case Conversation.NotifySetting.OFF: + return "off"; + case Conversation.NotifySetting.HIGHLIGHT: + return "hightlight"; + } + assert_not_reached(); + } +} + +} \ No newline at end of file diff --git a/main/src/ui/conversation_list_titlebar.vala b/main/src/ui/conversation_list_titlebar.vala index c49b43c4..327e1f50 100644 --- a/main/src/ui/conversation_list_titlebar.vala +++ b/main/src/ui/conversation_list_titlebar.vala @@ -14,9 +14,9 @@ public class ConversationListTitlebar : Gtk.HeaderBar { private StreamInteractor stream_interactor; - public ConversationListTitlebar(Window application, StreamInteractor stream_interactor) { + public ConversationListTitlebar(StreamInteractor stream_interactor, Window window) { this.stream_interactor = stream_interactor; - create_add_menu(application); + create_add_menu(window); } private void create_add_menu(Window window) { @@ -25,18 +25,18 @@ public class ConversationListTitlebar : Gtk.HeaderBar { AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor); add_chat_dialog.set_transient_for(window); add_chat_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); - add_chat_dialog.show(); + add_chat_dialog.present(); }); - window.get_application().add_action(contacts_action); + GLib.Application.get_default().add_action(contacts_action); SimpleAction conference_action = new SimpleAction("add_conference", null); conference_action.activate.connect(() => { AddConversation.Conference.Dialog add_conference_dialog = new AddConversation.Conference.Dialog(stream_interactor); add_conference_dialog.set_transient_for(window); add_conference_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); - add_conference_dialog.show(); + add_conference_dialog.present(); }); - window.get_application().add_action(conference_action); + GLib.Application.get_default().add_action(conference_action); Builder builder = new Builder.from_resource("/org/dino-im/menu_add.ui"); MenuModel menu = builder.get_object("menu_add") as MenuModel; diff --git a/main/src/ui/conversation_titlebar.vala b/main/src/ui/conversation_titlebar.vala index b1db3604..0537a1b9 100644 --- a/main/src/ui/conversation_titlebar.vala +++ b/main/src/ui/conversation_titlebar.vala @@ -91,6 +91,14 @@ public class ConversationTitlebar : Gtk.HeaderBar { Builder builder = new Builder.from_resource("/org/dino-im/menu_conversation.ui"); MenuModel menu = builder.get_object("menu_conversation") as MenuModel; menu_button.set_menu_model(menu); + + SimpleAction contact_details_action = new SimpleAction("contact_details", null); + contact_details_action.activate.connect(() => { + ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); + contact_details_dialog.set_transient_for((Window) get_toplevel()); + contact_details_dialog.present(); + }); + GLib.Application.get_default().add_action(contact_details_action); } private void encryption_changed() { diff --git a/main/src/ui/manage_accounts/dialog.vala b/main/src/ui/manage_accounts/dialog.vala index 858332e6..6a474e67 100644 --- a/main/src/ui/manage_accounts/dialog.vala +++ b/main/src/ui/manage_accounts/dialog.vala @@ -120,7 +120,7 @@ public class Dialog : Gtk.Dialog { account_list.select_row(account_item); account_list.queue_draw(); }); - add_account_dialog.show(); + add_account_dialog.present(); } private void on_remove_button_clicked() { diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index 7fd4640a..670da9b2 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -84,7 +84,7 @@ public class UnifiedWindow : Window { private void setup_headerbar() { conversation_titlebar = new ConversationTitlebar(stream_interactor) { visible=true }; - conversation_list_titlebar = new ConversationListTitlebar(this, stream_interactor) { visible=true }; + conversation_list_titlebar = new ConversationListTitlebar(stream_interactor, this) { visible=true }; headerbar_paned.add1(conversation_list_titlebar); headerbar_paned.add2(conversation_titlebar); diff --git a/main/src/ui/util.vala b/main/src/ui/util.vala deleted file mode 100644 index 696ce2d1..00000000 --- a/main/src/ui/util.vala +++ /dev/null @@ -1,124 +0,0 @@ -using Gtk; - -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui { - -public class Util : Object { - - private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; - private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; - private const string[] material_colors_800 = {"D32F2F", "C2185B", "7B1FA2", "512DA8", "303F9F", "1976D2", "0288D1", "0097A7", "00796B", "388E3C", "689F38", "AFB42B", "FFA000", "F57C00", "E64A19", "5D4037"}; - private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFC107", "FF9800", "FF5722", "795548"}; - private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; - private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; - - public static string get_avatar_hex_color(string name) { - return material_colors_300[name.hash() % material_colors_300.length]; -// return tango_colors_light[name.hash() % tango_colors_light.length]; - } - - public static string get_name_hex_color(string name, bool dark_theme = false) { - if (dark_theme) { - return material_colors_300[name.hash() % material_colors_300.length]; - } else { - return material_colors_500[name.hash() % material_colors_500.length]; - } -// return tango_colors_medium[name.hash() % tango_colors_medium.length]; - } - - public static string color_for_show(string show) { - switch(show) { - case "online": return "#9CCC65"; - case "away": return "#FFCA28"; - case "chat": return "#66BB6A"; - case "xa": return "#EF5350"; - case "dnd": return "#EF5350"; - default: return "#BDBDBD"; - } - } - - public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { - if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - return conversation.counterpart.resourcepart + " from " + get_display_name(stream_interactor, conversation.counterpart.bare_jid, conversation.account); - } - return get_display_name(stream_interactor, conversation.counterpart, conversation.account); - } - - public static string get_display_name(StreamInteractor stream_interactor, Jid jid, Account account) { - if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) { - return jid.resourcepart; - } else { - if (jid.bare_jid.equals(account.bare_jid.bare_jid)) { - if (account.alias == null || account.alias == "") { - return account.bare_jid.to_string(); - } else { - return account.alias; - } - } - Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); - if (roster_item != null && roster_item.name != null) { - return roster_item.name; - } - return jid.bare_jid.to_string(); - } - } - - public static string get_message_display_name(StreamInteractor stream_interactor, Entities.Message message, Account account) { - Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_message_real_jid(message); - if (real_jid != null) { - return get_display_name(stream_interactor, real_jid, account); - } else { - return get_display_name(stream_interactor, message.from, account); - } - } - - public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0) { - if (scale == 0) scale = image.get_scale_factor(); - image.set_from_surface(Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window())); - } - - private const string force_background_css = "%s { background-color: %s; }"; - private const string force_color_css = "%s { color: %s; }"; - - private static void force_css(Gtk.Widget widget, string css) { - var p = new Gtk.CssProvider(); - try { - p.load_from_data(css); - widget.get_style_context().add_provider(p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - } catch (GLib.Error err) { - // handle err - } - } - - public static void force_background(Gtk.Widget widget, string color, string selector = "*") { - 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)); - } - - public static void force_error_color(Gtk.Widget widget, string selector = "*") { - force_color(widget, "@error_color", selector); - } - - public static bool is_dark_theme(Gtk.Widget widget) { - Gdk.RGBA bg = widget.get_style_context().get_background_color(StateFlags.NORMAL); - return (bg.red < 0.5 && bg.green < 0.5 && bg.blue < 0.5); - } - - public static bool is_24h_format() { - GLib.Settings settings = new GLib.Settings("org.gnome.desktop.interface"); - string settings_format = settings.get_string("clock-format"); - string p_format = (new DateTime.now_local()).format("%p"); - return settings_format == "24h" || p_format == " "; - } -} - -} \ No newline at end of file diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala new file mode 100644 index 00000000..c65acfc9 --- /dev/null +++ b/main/src/ui/util/helper.vala @@ -0,0 +1,121 @@ +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui.Util { + +private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; +private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; +private const string[] material_colors_800 = {"D32F2F", "C2185B", "7B1FA2", "512DA8", "303F9F", "1976D2", "0288D1", "0097A7", "00796B", "388E3C", "689F38", "AFB42B", "FFA000", "F57C00", "E64A19", "5D4037"}; +private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFC107", "FF9800", "FF5722", "795548"}; +private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; +private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; + +public static string get_avatar_hex_color(string name) { + return material_colors_300[name.hash() % material_colors_300.length]; +// return tango_colors_light[name.hash() % tango_colors_light.length]; +} + +public static string get_name_hex_color(string name, bool dark_theme = false) { + if (dark_theme) { + return material_colors_300[name.hash() % material_colors_300.length]; + } else { + return material_colors_500[name.hash() % material_colors_500.length]; + } +// return tango_colors_medium[name.hash() % tango_colors_medium.length]; +} + +public static string color_for_show(string show) { + switch(show) { + case "online": return "#9CCC65"; + case "away": return "#FFCA28"; + case "chat": return "#66BB6A"; + case "xa": return "#EF5350"; + case "dnd": return "#EF5350"; + default: return "#BDBDBD"; + } +} + +public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { + if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + return conversation.counterpart.resourcepart + " from " + get_display_name(stream_interactor, conversation.counterpart.bare_jid, conversation.account); + } + return get_display_name(stream_interactor, conversation.counterpart, conversation.account); +} + +public static string get_display_name(StreamInteractor stream_interactor, Jid jid, Account account) { + if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) { + return jid.resourcepart; + } else { + if (jid.bare_jid.equals(account.bare_jid.bare_jid)) { + if (account.alias == null || account.alias == "") { + return account.bare_jid.to_string(); + } else { + return account.alias; + } + } + Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); + if (roster_item != null && roster_item.name != null) { + return roster_item.name; + } + return jid.bare_jid.to_string(); + } +} + +public static string get_message_display_name(StreamInteractor stream_interactor, Entities.Message message, Account account) { + Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_message_real_jid(message); + if (real_jid != null) { + return get_display_name(stream_interactor, real_jid, account); + } else { + return get_display_name(stream_interactor, message.from, account); + } +} + +public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0) { + if (scale == 0) scale = image.get_scale_factor(); + image.set_from_surface(Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window())); +} + +private const string force_background_css = "%s { background-color: %s; }"; +private const string force_color_css = "%s { color: %s; }"; + +private static void force_css(Gtk.Widget widget, string css) { + var p = new Gtk.CssProvider(); + try { + p.load_from_data(css); + widget.get_style_context().add_provider(p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } catch (GLib.Error err) { + // handle err + } +} + +public static void force_background(Gtk.Widget widget, string color, string selector = "*") { + 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)); +} + +public static void force_error_color(Gtk.Widget widget, string selector = "*") { + force_color(widget, "@error_color", selector); +} + +public static bool is_dark_theme(Gtk.Widget widget) { + Gdk.RGBA bg = widget.get_style_context().get_background_color(StateFlags.NORMAL); + return (bg.red < 0.5 && bg.green < 0.5 && bg.blue < 0.5); +} + +public static bool is_24h_format() { + GLib.Settings settings = new GLib.Settings("org.gnome.desktop.interface"); + string settings_format = settings.get_string("clock-format"); + string p_format = (new DateTime.now_local()).format("%p"); + return settings_format == "24h" || p_format == " "; +} + +} \ No newline at end of file diff --git a/main/src/ui/util/label_hybrid.vala b/main/src/ui/util/label_hybrid.vala new file mode 100644 index 00000000..4486f25b --- /dev/null +++ b/main/src/ui/util/label_hybrid.vala @@ -0,0 +1,129 @@ +using Gee; +using Gtk; + +namespace Dino.Ui.Util { + +public class LabelHybrid : Stack { + + public Label label = new Label("") { visible=true }; + protected Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; + + public void init(Widget widget) { + button.add(label); + add_named(button, "label"); + add_named(widget, "widget"); + + button.clicked.connect(() => { + show_widget(); + }); + } + + public void show_widget() { + visible_child_name = "widget"; + } + + public void show_label() { + visible_child_name = "label"; + } +} + +public class EntryLabelHybrid : LabelHybrid { + + public string text { + get { return entry.text; } + set { + entry.text = value; + label.label = value; + } + } + + public bool visibility { + get { return entry.visibility; } + set { entry.visibility = value; } + } + + public float xalign { + get { return label.xalign; } + set { + label.xalign = value; + entry.set_alignment(value); + } + } + + private Entry entry; + + public EntryLabelHybrid(Entry? e = null) { + entry = e ?? new Entry() { visible=true }; + init(entry); + update_label(); + + entry.key_release_event.connect((event) => { + if (event.keyval == Gdk.Key.Return) { + show_label(); + } else { + label.label = entry.text; + } + return false; + }); + } + + private void update_label() { + text = text; + } +} + +public class ComboBoxTextLabelHybrid : LabelHybrid { + + public int active { + get { return combobox.active; } + set { combobox.active = value; } + } + + public float xalign { + get { return label.xalign; } + set { label.xalign = value; } + } + + private ComboBoxText combobox; + + public ComboBoxTextLabelHybrid(ComboBoxText? cb = null) { + combobox = cb ?? new ComboBoxText() { visible=true }; + init(combobox); + update_label(); + + combobox.changed.connect(() => { + update_label(); + show_label(); + }); + button.clicked.connect(() => { + combobox.popup(); + }); + } + + public void append(string id, string text) { combobox.append(id, text); } + public string get_active_text() { return combobox.get_active_text(); } + + private void update_label() { + label.label = combobox.get_active_text(); + } +} + +public class LabelHybridGroup { + + private Gee.List hybrids = new ArrayList(); + + public void add(LabelHybrid hybrid) { + hybrids.add(hybrid); + + hybrid.notify["visible-child-name"].connect(() => { + if (hybrid.visible_child_name == "label") return; + foreach (LabelHybrid h in hybrids) { + if (h != hybrid) { + h.set_visible_child_name("label"); + } + } + }); + } +} + +} \ No newline at end of file