diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 57a2edc32..2c082b8a6 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -34,14 +34,13 @@ public class Mail.ConversationList : Granite.Bin { private Gee.HashMap threads; private Gee.HashMap conversations; private ConversationListStore list_store; + private Gtk.ListView list_view; private Gtk.SearchEntry search_entry; + private Gtk.SingleSelection selection_model; private Granite.SwitchModelButton hide_read_switch; private Granite.SwitchModelButton hide_unstarred_switch; private Gtk.MenuButton filter_button; - private Gtk.PopoverMenu context_menu; private Gtk.Stack refresh_stack; - private Gtk.SingleSelection selection_model; - private Gtk.ListView list_view; private uint mark_read_timeout_id = 0; @@ -52,13 +51,43 @@ public class Mail.ConversationList : Granite.Bin { folders = new Gee.HashMap (); folder_info_flags = new Gee.HashMap (); threads = new Gee.HashMap (); + list_store = new ConversationListStore (); + list_store.set_sort_func (thread_sort_function); + list_store.set_filter_func (filter_function); + + selection_model = new Gtk.SingleSelection (list_store); + + var factory = new Gtk.SignalListItemFactory (); + factory.setup.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + var conversation_list_item = new ConversationListItem (); + // conversation_list_item.select.connect (() => { + // if (list_view.selected_row_widget != row) { + // selection_model.select_item (uint position, bool unselect_rest) + // list_view.select_row (row); + // } + // }); + + list_item.set_child (conversation_list_item); + }); + + factory.bind.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + var conversation_list_item = (ConversationListItem) list_item.child; + conversation_list_item.assign ((ConversationItemModel) list_item.get_item ()); + }); + + list_view = new Gtk.ListView (selection_model, factory) { + single_click_activate = true, + vexpand = true + }; var application_instance = (Gtk.Application) GLib.Application.get_default (); search_entry = new Gtk.SearchEntry () { hexpand = true, placeholder_text = _("Search Mail"), - valign = Gtk.Align.CENTER + valign = CENTER }; hide_read_switch = new Granite.SwitchModelButton (_("Hide read conversations")); @@ -79,48 +108,16 @@ public class Mail.ConversationList : Granite.Bin { icon_name = "mail-filter-symbolic", popover = filter_popover, tooltip_text = _("Filter Conversations"), - valign = Gtk.Align.CENTER + valign = CENTER }; - search_header = new Granite.Box (HORIZONTAL, HALF) { - margin_start = 9, - margin_end = 9 - }; + search_header = new Granite.Box (HORIZONTAL); search_header.append (search_entry); search_header.append (filter_button); - list_store = new ConversationListStore (); - list_store.set_sort_func (thread_sort_function); - list_store.set_filter_func (filter_function); - - var deleted_filter = new Gtk.CustomFilter (deleted_filter_func); - - var filter_model = new Gtk.FilterListModel (list_store, deleted_filter); - - selection_model = new Gtk.SingleSelection (filter_model) { - autoselect = false - }; - - var factory = new Gtk.SignalListItemFactory (); - - var event_controller_focus = new Gtk.EventControllerFocus (); - - list_view = new Gtk.ListView (selection_model, factory) { - show_separators = false - }; - list_view.add_controller (event_controller_focus); - - context_menu = new Gtk.PopoverMenu.from_model (null) { - position = RIGHT, - has_arrow = false - }; - context_menu.set_parent (list_view); - var scrolled_window = new Gtk.ScrolledWindow () { - hscrollbar_policy = NEVER, + hscrollbar_policy = Gtk.PolicyType.NEVER, width_request = 158, - hexpand = true, - vexpand = true, child = list_view }; @@ -135,8 +132,8 @@ public class Mail.ConversationList : Granite.Bin { var refresh_spinner = new Gtk.Spinner () { spinning = true, - halign = Gtk.Align.CENTER, - valign = Gtk.Align.CENTER, + halign = CENTER, + valign = CENTER, tooltip_text = _("Fetching new messages…") }; @@ -149,8 +146,8 @@ public class Mail.ConversationList : Granite.Bin { var move_spinner = new Gtk.Spinner () { spinning = true, - halign = Gtk.Align.CENTER, - valign = Gtk.Align.CENTER, + halign =CENTER, + valign = CENTER, visible = false }; MoveOperation.bind_spinner (move_spinner); @@ -160,7 +157,6 @@ public class Mail.ConversationList : Granite.Bin { conversation_action_bar.pack_end (move_spinner); var toolbar_view = new Adw.ToolbarView () { - bottom_bar_style = RAISED, content = scrolled_window }; toolbar_view.add_top_bar (search_header); @@ -170,76 +166,36 @@ public class Mail.ConversationList : Granite.Bin { search_entry.search_changed.connect (() => load_folder.begin (folder_info_per_account)); - factory.setup.connect ((obj) => { - var list_item = (Gtk.ListItem) obj; - var conversation_list_item = new ConversationListItem (); - conversation_list_item.secondary_click.connect ((x, y) => { - if (!selection_model.is_selected (list_item.get_position ())) { - selection_model.select_item (list_item.get_position (), true); - } - double dest_x; - double dest_y; - conversation_list_item.translate_coordinates (list_view, x, y, out dest_x, out dest_y); - create_context_menu (dest_x, dest_y); - }); - list_item.set_child (conversation_list_item); - }); - - factory.bind.connect ((obj) => { - var list_item = (Gtk.ListItem) obj; - var conversation_list_item = (ConversationListItem) list_item.child; - conversation_list_item.assign ((ConversationItemModel) list_item.get_item ()); - }); - - selection_model.selection_changed.connect (() => { + list_view.activate.connect ((pos) => { if (mark_read_timeout_id != 0) { GLib.Source.remove (mark_read_timeout_id); mark_read_timeout_id = 0; } - var selected_items = selection_model.get_selection (); - uint current_item_position; - Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first (selected_items, out current_item_position); - - if (!bitset_iter.is_valid ()) { - conversation_selected (null); - } else { - var conversation_item = (ConversationItemModel) selection_model.get_item (current_item_position); - - if (conversation_item.unread) { - mark_read_timeout_id = GLib.Timeout.add_seconds (MARK_READ_TIMEOUT_SECONDS, () => { - set_thread_flag (conversation_item.node, Camel.MessageFlags.SEEN); + var row = (ConversationItemModel) selection_model.get_item (pos); - mark_read_timeout_id = 0; - return false; - }); - } + if (row != null && row.unread) { + mark_read_timeout_id = GLib.Timeout.add_seconds (MARK_READ_TIMEOUT_SECONDS, () => { + set_thread_flag (row.node, Camel.MessageFlags.SEEN); - var window = (MainWindow) get_root (); - window.get_action (MainWindow.ACTION_MARK_READ).set_enabled (conversation_item.unread); - window.get_action (MainWindow.ACTION_MARK_UNREAD).set_enabled (!conversation_item.unread); - window.get_action (MainWindow.ACTION_MARK_STAR).set_enabled (!conversation_item.flagged); - window.get_action (MainWindow.ACTION_MARK_UNSTAR).set_enabled (conversation_item.flagged); - - conversation_selected (conversation_item.node); + mark_read_timeout_id = 0; + return Source.REMOVE; + }); } }); - // Disable delete accelerators when the conversation list box loses keyboard focus, - // restore them when it returns - event_controller_focus.enter.connect (() => { - application_instance.set_accels_for_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, - MainWindow.action_accelerators[MainWindow.ACTION_MOVE_TO_TRASH].to_array () - ); - }); + selection_model.selection_changed.connect ((pos, n_items) => { + if (pos == -1) { + conversation_selected (null); + } else { + var row = (ConversationItemModel) selection_model.get_item (pos); + action_set_enabled (MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ, row.unread); + action_set_enabled (MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD, !row.unread); + action_set_enabled (MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR, !row.flagged); + action_set_enabled (MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR, row.flagged); - event_controller_focus.leave.connect (() => { - application_instance.set_accels_for_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, - {} - ); + conversation_selected (row.node); + } }); var settings = new GLib.Settings ("io.elementary.mail"); @@ -284,6 +240,7 @@ public class Mail.ConversationList : Granite.Bin { threads.clear (); list_store.remove_all (); + list_store.items_changed (0, previous_items, 0); cancellable = new GLib.Cancellable (); @@ -334,7 +291,7 @@ public class Mail.ConversationList : Granite.Bin { } } - list_store.items_changed (0, previous_items, list_store.get_n_items ()); + list_store.items_changed (0, 0, list_store.get_n_items ()); } public async void refresh_folder (GLib.Cancellable? cancellable = null) { @@ -368,12 +325,13 @@ public class Mail.ConversationList : Granite.Bin { threads[service_uid] = new Camel.FolderThread (folders[service_uid], search_result_uids, false); - var previous_items = list_store.get_n_items (); + var removed = 0; change_info.get_removed_uids ().foreach ((uid) => { var item = conversations[uid]; if (item != null) { conversations.unset (uid); list_store.remove (item); + removed++; } }); @@ -390,6 +348,7 @@ public class Mail.ConversationList : Granite.Bin { if (item.is_older_than (child)) { conversations.unset (child.message.uid); list_store.remove (item); + removed++; add_conversation_item (folder_info_flags[service_uid], child, threads[service_uid], service_uid); }; } @@ -397,7 +356,7 @@ public class Mail.ConversationList : Granite.Bin { child = child.next; } - list_store.items_changed (0, previous_items, list_store.get_n_items ()); + list_store.items_changed (0, removed, list_store.get_n_items ()); } } } @@ -473,51 +432,39 @@ public class Mail.ConversationList : Granite.Bin { return (int)(item2.timestamp - item1.timestamp); } - private static bool deleted_filter_func (Object item) { - return !((ConversationItemModel)item).deleted; - } - public void mark_read_selected_messages () { - var selected_items = selection_model.get_selection (); uint current_item_position; - Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first (selected_items, out current_item_position); + var bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first (selection_model.get_selection (), out current_item_position); while (bitset_iter.is_valid ()) { - ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, ~0); - bitset_iter.next (out current_item_position); + ((ConversationItemModel) selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, ~0); } } public void mark_star_selected_messages () { - var selected_items = selection_model.get_selection (); uint current_item_position; - Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first (selected_items, out current_item_position); + var bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first (selection_model.get_selection (), out current_item_position); while (bitset_iter.is_valid ()) { - ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, ~0); - bitset_iter.next (out current_item_position); + ((ConversationItemModel) selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, ~0); } } public void mark_unread_selected_messages () { - var selected_items = selection_model.get_selection (); uint current_item_position; - Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first (selected_items, out current_item_position); + var bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first (selection_model.get_selection (), out current_item_position); while (bitset_iter.is_valid ()) { - ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, 0); - bitset_iter.next (out current_item_position); + ((ConversationItemModel) selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, 0); } } public void mark_unstar_selected_messages () { - var selected_items = selection_model.get_selection (); uint current_item_position; - Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first (selected_items, out current_item_position); + var bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first (selection_model.get_selection (), out current_item_position); while (bitset_iter.is_valid ()) { - ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, 0); - bitset_iter.next (out current_item_position); + ((ConversationItemModel) selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, 0); } } @@ -526,7 +473,7 @@ public class Mail.ConversationList : Granite.Bin { ConversationItemModel[] moved_conversation_items = {}; var move_threads = new Gee.HashMap> (); - // var selected_rows = list_box.get_selected_rows (); + // var selected_rows = list_view.get_selected_rows (); // int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); // foreach (unowned var selected_row in selected_rows) { @@ -541,8 +488,8 @@ public class Mail.ConversationList : Granite.Bin { // move_threads[selected_item_model.service_uid].add (selected_item_model.node); // } - // list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); - // list_box.select_row_at_index (selected_rows_start_index + 1); + list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); + // selection_model.select_item (selected_rows_start_index + 1, true); uint moved = 0; foreach (var service_uid in move_threads.keys) { @@ -572,33 +519,4 @@ public class Mail.ConversationList : Granite.Bin { list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); } - - private void create_context_menu (double x, double y) { - var menu = new Menu (); - - var conversation_item_model = (ConversationItemModel) selection_model.get_selected_item (); - - menu.append (_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH); - - if (!conversation_item_model.unread) { - menu.append (_("Mark As Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); - } else { - menu.append (_("Mark As Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); - } - - if (!conversation_item_model.flagged) { - menu.append (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); - } else { - menu.append (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); - } - - context_menu.set_menu_model (menu); - - Gdk.Rectangle pos = Gdk.Rectangle () { - x = (int) x, - y = (int) y - }; - context_menu.set_pointing_to (pos); - context_menu.popup (); - } } diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index bda6aec88..775dddbbd 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -19,8 +19,8 @@ * Authored by: Corentin Noël */ -public class Mail.ConversationListItem : Granite.Bin { - public signal void secondary_click (double x, double y); +public class Mail.ConversationListItem : Granite.ListItem { + public signal void select (); private Gtk.Image status_icon; private Gtk.Label date; @@ -46,7 +46,7 @@ public class Mail.ConversationListItem : Granite.Bin { source = new Gtk.Label (null) { hexpand = true, - ellipsize = Pango.EllipsizeMode.END, + ellipsize = END, use_markup = true, xalign = 0 }; @@ -60,7 +60,7 @@ public class Mail.ConversationListItem : Granite.Bin { topic = new Gtk.Label (null) { hexpand = true, - ellipsize = Pango.EllipsizeMode.END, + ellipsize = END, xalign = 0 }; @@ -106,13 +106,15 @@ public class Mail.ConversationListItem : Granite.Bin { add_css_class ("conversation-list-item"); child = carousel; - var gesture_controller = new Gtk.GestureClick () { + var click_controller = new Gtk.GestureClick () { button = Gdk.BUTTON_SECONDARY, propagation_phase = BUBBLE }; - gesture_controller.released.connect ((n_press, x, y) => { - secondary_click (x, y); + click_controller.released.connect ((n_press, x, y) => { + select (); + var menu = create_context_menu (); + menu_popup_at_pointer (menu, x, y); }); var key_controller = new Gtk.EventControllerKey (); @@ -122,10 +124,11 @@ public class Mail.ConversationListItem : Granite.Bin { return; } - secondary_click (-1, -1); + var menu = create_context_menu (); + menu_popup_on_keypress (menu); }); - add_controller (gesture_controller); + add_controller (click_controller); add_controller (key_controller); carousel.page_changed.connect ((index) => { @@ -133,7 +136,9 @@ public class Mail.ConversationListItem : Granite.Bin { return; } - var main_window = (MainWindow)get_root (); + select (); + + var main_window = (MainWindow) get_root (); if (index == 2) { main_window.activate_action (MainWindow.ACTION_MOVE_TO_TRASH, null); } else if (index == 0) { @@ -147,26 +152,26 @@ public class Mail.ConversationListItem : Granite.Bin { }); } - public void assign (ConversationItemModel item_model) { + public void assign (ConversationItemModel data) { carousel.scroll_to (grid, false); - date.label = item_model.formatted_date; - topic.label = item_model.subject; + date.label = data.formatted_date; + topic.label = data.subject; var source_label_text = ""; - if (Camel.FolderInfoFlags.TYPE_SENT == (item_model.folder_info_flags & Camel.FOLDER_TYPE_MASK)) { - source_label_text = item_model.to; + if (Camel.FolderInfoFlags.TYPE_SENT == (data.folder_info_flags & Camel.FOLDER_TYPE_MASK)) { + source_label_text = data.to; } else { - source_label_text = item_model.from; + source_label_text = data.from; } source.label = GLib.Markup.escape_text (source_label_text); - tooltip_markup = GLib.Markup.printf_escaped ("%s\n%s", source_label_text, item_model.subject); + tooltip_markup = GLib.Markup.printf_escaped ("%s\n%s", source_label_text, data.subject); - uint num_messages = item_model.num_messages; + uint num_messages = data.num_messages; messages.label = num_messages > 1 ? "%u".printf (num_messages) : null; messages.visible = num_messages > 1; - if (item_model.unread) { + if (data.unread) { grid.add_css_class ("unread-message"); status_icon.icon_name = "mail-unread-symbolic"; @@ -181,11 +186,11 @@ public class Mail.ConversationListItem : Granite.Bin { status_icon.remove_css_class (Granite.CssClass.ACCENT); source.remove_css_class (Granite.CssClass.ACCENT); - if (item_model.replied_all || item_model.replied) { + if (data.replied_all || data.replied) { status_icon.icon_name = "mail-replied-symbolic"; status_icon.tooltip_text = _("Replied"); status_revealer.reveal_child = true; - } else if (item_model.forwarded) { + } else if (data.forwarded) { status_icon.icon_name = "mail-forwarded-symbolic"; status_icon.tooltip_text = _("Forwarded"); status_revealer.reveal_child = true; @@ -194,10 +199,54 @@ public class Mail.ConversationListItem : Granite.Bin { } } - flagged_icon_revealer.reveal_child = item_model.flagged; + flagged_icon_revealer.reveal_child = data.flagged; + } + + private Gtk.PopoverMenu create_context_menu () { + var menu_model = new Menu (); + menu_model.append (_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH); + + // var item = (ConversationItemModel) model_item; + // if (!item.unread) { + // menu_model.append (_("Mark As Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); + // } else { + // menu_model.append (_("Mark as Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); + // } + + // if (!item.flagged) { + // menu_model.append (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); + // } else { + // menu_model.append (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); + // } + + var menu = new Gtk.PopoverMenu.from_model (menu_model) { + has_arrow = false, + position = BOTTOM + }; + menu.set_parent (this); + + return menu; + } + + private void menu_popup_at_pointer (Gtk.PopoverMenu popover, double x, double y) { + var rect = Gdk.Rectangle () { + x = (int) x, + y = (int) y + }; + popover.pointing_to = rect; + popover.popup (); + } + + private void menu_popup_on_keypress (Gtk.PopoverMenu popover) { + popover.halign = END; + popover.set_pointing_to (Gdk.Rectangle () { + x = (int) get_width (), + y = (int) get_height () / 2 + }); + popover.popup (); } - private class SwipeAffordance : Gtk.Box { + private class SwipeAffordance : Granite.Bin { public Gtk.Align alignment { get; construct; } public string icon_name { get; construct; } public string label { get; construct; } @@ -218,7 +267,7 @@ public class Mail.ConversationListItem : Granite.Bin { var image = new Gtk.Image.from_icon_name (icon_name); var label = new Gtk.Label (label); - label.add_css_class (Granite.CssClass.SMALL); + label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); var box = new Gtk.Box (VERTICAL, 3) { halign = alignment, @@ -229,7 +278,7 @@ public class Mail.ConversationListItem : Granite.Bin { box.append (image); box.append (label); - append (box); + child = box; if (alignment == Gtk.Align.START) { add_css_class ("start"); diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 1546f62de..d536cf136 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -79,7 +79,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { construct { loading_cancellable = new GLib.Cancellable (); - add_css_class (Granite.STYLE_CLASS_CARD); + add_css_class (Granite.CssClass.CARD); unowned string? parsed_address; unowned string? parsed_name;