[fractal/split-appop-ui: 3/5] Improve MessageBox




commit 348d7a42bb13d4a1cc126c4df02366e0d81ba0b6
Author: Alejandro Domínguez <adomu net-c com>
Date:   Mon Jan 4 04:32:05 2021 +0100

    Improve MessageBox

 fractal-gtk/src/appop/message.rs        |   8 +-
 fractal-gtk/src/widgets/message.rs      | 247 ++++++++++++++------------------
 fractal-gtk/src/widgets/room_history.rs |  35 ++---
 3 files changed, 130 insertions(+), 160 deletions(-)
---
diff --git a/fractal-gtk/src/appop/message.rs b/fractal-gtk/src/appop/message.rs
index e63e0d25..887f9661 100644
--- a/fractal-gtk/src/appop/message.rs
+++ b/fractal-gtk/src/appop/message.rs
@@ -69,12 +69,12 @@ impl AppOp {
         let login_data = self.login_data.clone()?;
         let messages = self.ui.history.as_ref()?.get_listbox();
         if let Some(ui_msg) = self.create_new_room_message(&msg) {
-            let mb = widgets::MessageBox::new().tmpwidget(
+            let mb = widgets::MessageBox::tmpwidget(
                 login_data.session_client.clone(),
                 self.user_info_cache.clone(),
                 &ui_msg,
             );
-            let m = mb.get_listbox_row();
+            let m = &mb.root;
             messages.add(m);
 
             if let Some(w) = messages.get_children().iter().last() {
@@ -109,12 +109,12 @@ impl AppOp {
         let mut widgets = vec![];
         for t in self.msg_queue.iter().rev().filter(|m| m.msg.room == r.id) {
             if let Some(ui_msg) = self.create_new_room_message(&t.msg) {
-                let mb = widgets::MessageBox::new().tmpwidget(
+                let mb = widgets::MessageBox::tmpwidget(
                     login_data.session_client.clone(),
                     self.user_info_cache.clone(),
                     &ui_msg,
                 );
-                let m = mb.get_listbox_row();
+                let m = &mb.root;
                 messages.add(m);
 
                 if let Some(w) = messages.get_children().iter().last() {
diff --git a/fractal-gtk/src/widgets/message.rs b/fractal-gtk/src/widgets/message.rs
index e6851620..3f40473d 100644
--- a/fractal-gtk/src/widgets/message.rs
+++ b/fractal-gtk/src/widgets/message.rs
@@ -1,82 +1,78 @@
-use crate::util::i18n::i18n;
-use itertools::Itertools;
-
 use crate::appop::UserInfoCache;
-use chrono::prelude::*;
-use either::Either;
-use glib::clone;
-use gtk::{prelude::*, ButtonExt, ContainerExt, LabelExt, Overlay, WidgetExt};
-use matrix_sdk::Client as MatrixClient;
-use std::cmp::max;
-use std::rc::Rc;
-
-use crate::util::markup_text;
-
 use crate::cache::download_to_cache;
-
 use crate::globals;
 use crate::ui::MessageContent as Message;
 use crate::ui::RowType;
+use crate::util::i18n::i18n;
+use crate::util::markup_text;
 use crate::widgets;
 use crate::widgets::message_menu::MessageMenu;
 use crate::widgets::AvatarExt;
 use crate::widgets::ClipContainer;
 use crate::widgets::{AudioPlayerWidget, PlayerExt, VideoPlayerWidget};
+use chrono::prelude::*;
+use either::Either;
+use glib::clone;
+use gtk::{prelude::*, ButtonExt, ContainerExt, LabelExt, Overlay, WidgetExt};
+use itertools::Itertools;
+use matrix_sdk::Client as MatrixClient;
+use std::cmp::max;
+use std::rc::Rc;
 
-/* A message row in the room history */
+// A message row in the room history
 #[derive(Clone, Debug)]
 pub struct MessageBox {
+    pub root: gtk::ListBoxRow,
     username: gtk::Label,
     pub username_event_box: gtk::EventBox,
     eventbox: gtk::EventBox,
     gesture: gtk::GestureLongPress,
-    row: gtk::ListBoxRow,
     image: Option<gtk::DrawingArea>,
-    video_player: Option<Rc<VideoPlayerWidget>>,
+    pub video_player: Option<Rc<VideoPlayerWidget>>,
     pub header: bool,
 }
 
 impl MessageBox {
-    pub fn new() -> MessageBox {
+    fn new() -> Self {
         let username = gtk::Label::new(None);
-        let eb = gtk::EventBox::new();
+        let username_event_box = gtk::EventBox::new();
         let eventbox = gtk::EventBox::new();
-        let row = gtk::ListBoxRow::new();
+        let root = gtk::ListBoxRow::new();
         let gesture = gtk::GestureLongPress::new(&eventbox);
 
         username.set_ellipsize(pango::EllipsizeMode::End);
         gesture.set_propagation_phase(gtk::PropagationPhase::Capture);
         gesture.set_touch_only(true);
 
-        MessageBox {
+        Self {
+            root,
             username,
-            username_event_box: eb,
+            username_event_box,
             eventbox,
             gesture,
-            row,
             image: None,
             video_player: None,
             header: true,
         }
     }
 
-    /* create the message row with or without a header */
+    // create the message row with or without a header
     pub fn create(
-        &mut self,
         session_client: MatrixClient,
         user_info_cache: UserInfoCache,
         msg: &Message,
         has_header: bool,
         is_temp: bool,
-    ) {
-        self.set_msg_styles(msg, &self.row);
-        self.row.set_selectable(false);
+    ) -> Self {
+        let mut mb = Self::new();
+        mb.set_msg_styles(msg);
+        mb.root.set_selectable(false);
         let upload_attachment_msg = gtk::Box::new(gtk::Orientation::Horizontal, 10);
         let w = match msg.mtype {
             RowType::Emote => {
-                self.row.set_margin_top(12);
-                self.header = false;
-                self.small_widget(session_client, msg)
+                mb.root.set_margin_top(12);
+                mb.header = false;
+                mb.small_widget(session_client, msg)
             }
             RowType::Video if is_temp => {
                 upload_attachment_msg
@@ -98,38 +94,32 @@ impl MessageBox {
                 upload_attachment_msg
             }
             _ if has_header => {
-                self.row.set_margin_top(12);
-                self.header = true;
-                self.widget(session_client, user_info_cache, msg)
+                mb.root.set_margin_top(12);
+                mb.header = true;
+                mb.widget(session_client, user_info_cache, msg)
             }
             _ => {
-                self.header = false;
-                self.small_widget(session_client, msg)
+                mb.header = false;
+                mb.small_widget(session_client, msg)
             }
         };
 
-        self.eventbox.add(&w);
-        self.row.add(&self.eventbox);
-        self.row.show_all();
-        self.connect_right_click_menu(msg, None);
-    }
+        mb.eventbox.add(&w);
+        mb.root.add(&mb.eventbox);
+        mb.root.show_all();
+        mb.connect_right_click_menu(msg, None);
 
-    pub fn get_listbox_row(&self) -> &gtk::ListBoxRow {
-        &self.row
+        mb
     }
 
     pub fn tmpwidget(
-        mut self,
         session_client: MatrixClient,
         user_info_cache: UserInfoCache,
         msg: &Message,
-    ) -> MessageBox {
-        self.create(session_client, user_info_cache, msg, true, true);
-        {
-            let w = self.get_listbox_row();
-            w.get_style_context().add_class("msg-tmp");
-        }
-        self
+    ) -> Self {
+        let mb = Self::create(session_client, user_info_cache, msg, true, true);
+        mb.root.get_style_context().add_class("msg-tmp");
+        mb
     }
 
     pub fn update_header(
@@ -140,12 +130,12 @@ impl MessageBox {
         has_header: bool,
     ) {
         let w = if has_header && msg.mtype != RowType::Emote {
-            self.row.set_margin_top(12);
+            self.root.set_margin_top(12);
             self.header = true;
             self.widget(session_client, user_info_cache, &msg)
         } else {
             if let RowType::Emote = msg.mtype {
-                self.row.set_margin_top(12);
+                self.root.set_margin_top(12);
             }
             self.header = false;
             self.small_widget(session_client, &msg)
@@ -154,7 +144,7 @@ impl MessageBox {
             self.eventbox.remove(&eb);
         }
         self.eventbox.add(&w);
-        self.row.show_all();
+        self.root.show_all();
     }
 
     fn widget(
@@ -169,7 +159,7 @@ impl MessageBox {
         // +--------+---------+
         let msg_widget = gtk::Box::new(gtk::Orientation::Horizontal, 10);
         let content = self.build_room_msg_content(session_client.clone(), msg, false);
-        /* Todo: make build_room_msg_avatar() faster (currently ~1ms) */
+        // TODO: make build_room_msg_avatar() faster (currently ~1ms)
         let avatar = self.build_room_msg_avatar(session_client, user_info_cache, msg);
 
         msg_widget.pack_start(&avatar, false, false, 0);
@@ -270,11 +260,9 @@ impl MessageBox {
             None,
             None,
         );
-        if let Some(name) = alias {
-            self.username.set_text(&name);
-        } else {
-            self.username.set_text(&uid.to_string());
-        }
+
+        self.username
+            .set_text(alias.as_deref().unwrap_or(uid.as_str()));
 
         download_to_cache(
             session_client.clone(),
@@ -286,8 +274,8 @@ impl MessageBox {
         avatar
     }
 
-    fn build_room_msg_username(&self, uname: String) -> gtk::Label {
-        self.username.set_text(&uname);
+    fn build_room_msg_username(&self, uname: &str) -> gtk::Label {
+        self.username.set_text(uname);
         self.username.set_justify(gtk::Justification::Left);
         self.username.set_halign(gtk::Align::Start);
         self.username.get_style_context().add_class("username");
@@ -295,9 +283,9 @@ impl MessageBox {
         self.username.clone()
     }
 
-    /* Add classes to the widget based on message type */
-    fn set_msg_styles(&self, msg: &Message, w: &gtk::ListBoxRow) {
-        let style = w.get_style_context();
+    // Add classes to the widget based on message type
+    fn set_msg_styles(&self, msg: &Message) {
+        let style = self.root.get_style_context();
         match msg.mtype {
             RowType::Mention => style.add_class("msg-mention"),
             RowType::Emote => style.add_class("msg-emote"),
@@ -306,23 +294,38 @@ impl MessageBox {
         }
     }
 
-    fn set_label_styles(&self, w: &gtk::Label) {
-        w.set_line_wrap(true);
-        w.set_line_wrap_mode(pango::WrapMode::WordChar);
-        w.set_justify(gtk::Justification::Left);
-        w.set_xalign(0.0);
-        w.set_valign(gtk::Align::Start);
-        w.set_halign(gtk::Align::Fill);
-        w.set_selectable(true);
-    }
-
     fn build_room_msg_body(&self, msg: &Message) -> gtk::Box {
         let bx = gtk::Box::new(gtk::Orientation::Vertical, 6);
 
-        let msg_parts = self.create_msg_parts(&msg.body);
+        let msgs_by_kind_of_line = msg.body.lines().group_by(|&line| kind_of_line(line));
+        let msg_parts = msgs_by_kind_of_line.into_iter().map(|(k, group)| {
+            let mut v: Vec<&str> = if k == MsgPartType::Quote {
+                group.map(trim_start_quote).collect()
+            } else {
+                group.collect()
+            };
+            // We need to remove the first and last empty line (if any) because quotes use \n\n
+            if v.starts_with(&[""]) {
+                v.drain(..1);
+            }
+            if v.ends_with(&[""]) {
+                v.pop();
+            }
+            let part = v.join("\n");
+
+            let part_widget = gtk::Label::new(None);
+            part_widget.set_markup(&markup_text(&part));
+            set_label_styles(&part_widget);
+
+            if k == MsgPartType::Quote {
+                part_widget.get_style_context().add_class("quote");
+            }
+
+            part_widget
+        });
 
-        if msg.mtype == RowType::Mention {
-            for part in msg_parts.iter() {
+        for part in msg_parts {
+            if msg.mtype == RowType::Mention {
                 let highlights = msg.highlights.clone();
                 part.connect_property_cursor_position_notify(move |w| {
                     let attr = pango::AttrList::new();
@@ -347,48 +350,12 @@ impl MessageBox {
                 }
                 part.set_attributes(Some(&attr));
             }
-        }
 
-        for part in msg_parts {
             self.connect_right_click_menu(msg, Some(&part));
             bx.add(&part);
         }
-        bx
-    }
-
-    fn create_msg_parts(&self, body: &str) -> Vec<gtk::Label> {
-        let mut parts_labels: Vec<gtk::Label> = vec![];
-
-        for (k, group) in body.lines().group_by(kind_of_line).into_iter() {
-            let mut v: Vec<&str> = if k == MsgPartType::Quote {
-                group.map(|l| trim_start_quote(l)).collect()
-            } else {
-                group.collect()
-            };
-            /* We need to remove the first and last empty line (if any) because quotes use /n/n */
-            if v.starts_with(&[""]) {
-                v.drain(..1);
-            }
-            if v.ends_with(&[""]) {
-                v.pop();
-            }
-            let part = v.join("\n");
-
-            parts_labels.push(self.create_msg(part.as_str(), k));
-        }
 
-        parts_labels
-    }
-
-    fn create_msg(&self, body: &str, k: MsgPartType) -> gtk::Label {
-        let msg_part = gtk::Label::new(None);
-        msg_part.set_markup(&markup_text(body));
-        self.set_label_styles(&msg_part);
-
-        if k == MsgPartType::Quote {
-            msg_part.get_style_context().add_class("quote");
-        }
-        msg_part
+        bx
     }
 
     fn build_room_msg_image(&mut self, session_client: MatrixClient, msg: &Message) -> gtk::Box {
@@ -553,10 +520,6 @@ impl MessageBox {
         bx
     }
 
-    pub fn get_video_widget(&self) -> Option<Rc<VideoPlayerWidget>> {
-        self.video_player.clone()
-    }
-
     fn build_room_msg_file(&self, msg: &Message) -> gtk::Box {
         let bx = gtk::Box::new(gtk::Orientation::Horizontal, 12);
         let btn_bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
@@ -600,13 +563,13 @@ impl MessageBox {
     }
 
     fn build_room_msg_date(&self, dt: &DateTime<Local>) -> gtk::Label {
-        /* TODO: get system preference for 12h/24h */
+        // TODO: get system preference for 12h/24h
         let use_ampm = false;
         let format = if use_ampm {
-            /* Use 12h time format (AM/PM) */
+            // Use 12h time format (AM/PM)
             i18n("%l∶%M %p")
         } else {
-            /* Use 24 time format */
+            // Use 24 time format
             i18n("%R")
         };
 
@@ -630,11 +593,8 @@ impl MessageBox {
         // +----------+------+
         let info = gtk::Box::new(gtk::Orientation::Horizontal, 0);
 
-        let username = self.build_room_msg_username(
-            msg.sender_name
-                .clone()
-                .unwrap_or_else(|| msg.sender.to_string()),
-        );
+        let username =
+            self.build_room_msg_username(msg.sender_name.as_deref().unwrap_or(msg.sender.as_str()));
         let date = self.build_room_msg_date(&msg.date);
 
         self.username_event_box.add(&username);
@@ -647,18 +607,17 @@ impl MessageBox {
 
     fn build_room_msg_emote(&self, msg: &Message) -> gtk::Box {
         let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
-        /* Use MXID till we have a alias */
+        // Use MXID till we have a alias
         let sname = msg
             .sender_name
             .clone()
             .unwrap_or_else(|| msg.sender.to_string());
         let msg_label = gtk::Label::new(None);
-        let body: &str = &msg.body;
-        let markup = markup_text(body);
+        let markup = markup_text(&msg.body);
 
         self.connect_right_click_menu(msg, Some(&msg_label));
         msg_label.set_markup(&format!("<b>{}</b> {}", sname, markup));
-        self.set_label_styles(&msg_label);
+        set_label_styles(&msg_label);
 
         bx.add(&msg_label);
         bx
@@ -697,12 +656,22 @@ impl MessageBox {
     fn connect_media_viewer(&self, msg: &Message) -> Option<()> {
         let evid = msg.id.as_ref()?.to_string();
         let data = glib::Variant::from(evid);
-        self.row.set_action_name(Some("app.open-media-viewer"));
-        self.row.set_action_target_value(Some(&data));
+        self.root.set_action_name(Some("app.open-media-viewer"));
+        self.root.set_action_target_value(Some(&data));
         None
     }
 }
 
+fn set_label_styles(w: &gtk::Label) {
+    w.set_line_wrap(true);
+    w.set_line_wrap_mode(pango::WrapMode::WordChar);
+    w.set_justify(gtk::Justification::Left);
+    w.set_xalign(0.0);
+    w.set_valign(gtk::Align::Start);
+    w.set_halign(gtk::Align::Fill);
+    w.set_selectable(true);
+}
+
 fn highlight_username(
     label: gtk::Label,
     attr: &pango::AttrList,
@@ -737,24 +706,24 @@ fn highlight_username(
         let mark_start = removed_char as i32 + pos.0;
         let mark_end = removed_char as i32 + pos.1;
         let mut final_pos = Some((mark_start, mark_end));
-        /* exclude selected text */
+        // exclude selected text
         if let Some((bounds_start, bounds_end)) = bounds {
-            /* If the selection is within the alias */
+            // If the selection is within the alias
             if contains((mark_start, mark_end), bounds_start)
                 && contains((mark_start, mark_end), bounds_end)
             {
                 final_pos = Some((mark_start, bounds_start));
-                /* Add blue color after a selection */
+                // Add blue color after a selection
                 let mut color = color.clone();
                 color.set_start_index(bounds_end as u32);
                 color.set_end_index(mark_end as u32);
                 attr.insert(color);
             } else {
-                /* The alias starts inside a selection */
+                // The alias starts inside a selection
                 if contains(bounds?, mark_start) {
                     final_pos = Some((bounds_end, final_pos?.1));
                 }
-                /* The alias ends inside a selection */
+                // The alias ends inside a selection
                 if contains(bounds?, mark_end - 1) {
                     final_pos = Some((final_pos?.0, bounds_start));
                 }
@@ -782,7 +751,7 @@ enum MsgPartType {
     Quote,
 }
 
-fn kind_of_line(line: &&str) -> MsgPartType {
+fn kind_of_line(line: &str) -> MsgPartType {
     if line.trim_start().starts_with('>') {
         MsgPartType::Quote
     } else {
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index 7964ba4d..5ccb8ff1 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -133,15 +133,14 @@ impl List {
         self.find_all_visible_indices()
             .iter()
             .filter_map(|&index| match self.list.get(index)? {
-                Element::Message(content) => match content.mtype {
-                    RowType::Video => {
-                        Some(content
-                            .widget
-                            .as_ref()?
-                            .get_video_widget()
-                            .expect("The widget of every MessageContent, whose mtype is RowType::Video, must 
have a video_player."))
-                    }
-                    _ => None,
+                Element::Message(content) if content.mtype == RowType::Video => {
+                    Some(content
+                        .widget
+                        .as_ref()?
+                        .video_player
+                        .clone()
+                        .expect("The widget of every MessageContent, whose mtype is RowType::Video, must 
have a video_player.")
+                    )
                 },
                 _ => None,
             })
@@ -256,11 +255,13 @@ enum Element {
 impl Element {
     fn get_listbox_row(&self) -> &gtk::ListBoxRow {
         match self {
-            Element::Message(content) => content
-                .widget
-                .as_ref()
-                .expect("The content of every message element must have widget.")
-                .get_listbox_row(),
+            Element::Message(content) => {
+                &content
+                    .widget
+                    .as_ref()
+                    .expect("The content of every message element must have widget.")
+                    .root
+            }
             Element::NewDivider(widgets) => widgets.get_widget(),
             Element::DayDivider(widget) => widget,
         }
@@ -740,8 +741,7 @@ fn create_row(
 ) -> widgets::MessageBox {
     /* we need to create a message with the username, so that we don't have to pass
      * all information to the widget creating each row */
-    let mut mb = widgets::MessageBox::new();
-    mb.create(
+    let mb = widgets::MessageBox::create(
         session_client,
         user_info_cache,
         &row,
@@ -752,7 +752,8 @@ fn create_row(
     if let RowType::Video = row.mtype {
         /* The followign callback requires `Send` but is handled by the gtk main loop */
         let fragile_rows = Fragile::new(Rc::downgrade(rows));
-        PlayerExt::get_player(&mb.get_video_widget()
+        PlayerExt::get_player(mb.video_player
+                .as_ref()
                 .expect("The widget of every MessageContent, whose mtype is RowType::Video, must have a 
video_player."))
                 .connect_uri_loaded(move |player, _| {
                     if let Some(rows) = fragile_rows.get().upgrade() {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]