Merge branch 'small-MR-history' into 'master'
Message widget refactor and adds uitype for messages See merge request World/fractal!220
This commit is contained in:
commit
76d9a604c7
6 changed files with 368 additions and 236 deletions
|
@ -83,6 +83,7 @@ fractal-gtk/src/passwd.rs
|
|||
fractal-gtk/src/static_resources.rs
|
||||
fractal-gtk/src/uibuilder.rs
|
||||
fractal-gtk/src/util.rs
|
||||
fractal-gtk/src/uitypes.rs
|
||||
fractal-gtk/src/widgets/address.rs
|
||||
fractal-gtk/src/widgets/autocomplete.rs
|
||||
fractal-gtk/src/widgets/avatar.rs
|
||||
|
|
|
@ -14,12 +14,15 @@ use self::comrak::{markdown_to_html, ComrakOptions};
|
|||
|
||||
use app::InternalCommand;
|
||||
use appop::AppOp;
|
||||
use app::App;
|
||||
use appop::RoomPanel;
|
||||
use appop::room::Force;
|
||||
|
||||
use glib;
|
||||
use globals;
|
||||
use widgets;
|
||||
use uitypes::MessageContent;
|
||||
use uitypes::RowType;
|
||||
use backend::BKCommand;
|
||||
|
||||
use types::Message;
|
||||
|
@ -150,100 +153,137 @@ impl AppOp {
|
|||
prev: Option<Message>,
|
||||
force_full: bool,
|
||||
first_new: bool) {
|
||||
let msg_entry: sourceview::View = self.ui.builder
|
||||
.get_object("msg_entry")
|
||||
.expect("Couldn't find msg_entry in ui file.");
|
||||
let messages = self.ui.builder
|
||||
.get_object::<gtk::ListBox>("message_list")
|
||||
.expect("Can't find message_list in ui file.");
|
||||
if let Some(ui_msg) = self.create_new_room_message(&msg) {
|
||||
let msg_entry: sourceview::View = self.ui.builder
|
||||
.get_object("msg_entry")
|
||||
.expect("Couldn't find msg_entry in ui file.");
|
||||
let messages = self.ui.builder
|
||||
.get_object::<gtk::ListBox>("message_list")
|
||||
.expect("Can't find message_list in ui file.");
|
||||
|
||||
let mut calc_prev = prev;
|
||||
if !force_full && calc_prev.is_none() {
|
||||
if let Some(r) = self.rooms.get(&msg.room) {
|
||||
calc_prev = match r.messages.iter().position(|ref m| m.id == msg.id) {
|
||||
Some(pos) if pos > 0 => r.messages.get(pos - 1).cloned(),
|
||||
_ => None
|
||||
};
|
||||
let mut calc_prev = prev;
|
||||
if !force_full && calc_prev.is_none() {
|
||||
if let Some(r) = self.rooms.get(&msg.room) {
|
||||
calc_prev = match r.messages.iter().position(|ref m| m.id == msg.id) {
|
||||
Some(pos) if pos > 0 => r.messages.get(pos - 1).cloned(),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if msg.room == self.active_room.clone().unwrap_or_default() && !msg.redacted {
|
||||
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
|
||||
let m;
|
||||
{
|
||||
let mb = widgets::MessageBox::new(r, &msg, &self);
|
||||
let entry = msg_entry.clone();
|
||||
mb.username_event_box.set_focus_on_click(false);
|
||||
mb.username_event_box.connect_button_press_event(move |eb, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
if let Some(label) = eb.get_children().iter().next() {
|
||||
if let Ok(l) = label.clone().downcast::<gtk::Label>() {
|
||||
if let Some(t) = l.get_text() {
|
||||
if let Some(buffer) = entry.get_buffer() {
|
||||
buffer.insert_at_cursor(&t[..]);
|
||||
if msg.room == self.active_room.clone().unwrap_or_default() && !msg.redacted {
|
||||
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
|
||||
let m;
|
||||
{
|
||||
let backend = self.backend.clone();
|
||||
let ui = self.ui.clone();
|
||||
let mut mb = widgets::MessageBox::new(&ui_msg, backend, &ui);
|
||||
let entry = msg_entry.clone();
|
||||
mb.username_event_box.set_focus_on_click(false);
|
||||
mb.username_event_box.connect_button_press_event(move |eb, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
if let Some(label) = eb.get_children().iter().next() {
|
||||
if let Ok(l) = label.clone().downcast::<gtk::Label>() {
|
||||
if let Some(t) = l.get_text() {
|
||||
if let Some(buffer) = entry.get_buffer() {
|
||||
buffer.insert_at_cursor(&t[..]);
|
||||
}
|
||||
}
|
||||
entry.grab_focus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
glib::signal::Inhibit(false)
|
||||
});
|
||||
m = match calc_prev {
|
||||
Some(ref p) if self.should_group(&msg, p) => mb.small_widget(),
|
||||
Some(_) if self.has_small_mtype(&msg) => mb.small_widget(),
|
||||
_ => mb.widget(),
|
||||
};
|
||||
if let Some(ref image) = mb.image {
|
||||
let msg = msg.clone();
|
||||
let room = r.clone();
|
||||
image.connect_button_press_event(move |_, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
let msg = msg.clone();
|
||||
let room = room.clone();
|
||||
APPOP!(create_media_viewer, (msg, room));
|
||||
|
||||
glib::signal::Inhibit(false)
|
||||
});
|
||||
m = match calc_prev {
|
||||
Some(ref p) if self.should_group(&msg, p) => mb.small_widget(),
|
||||
Some(_) if self.has_small_mtype(&msg) => mb.small_widget(),
|
||||
_ => mb.widget(),
|
||||
Inhibit(true)
|
||||
} else {
|
||||
Inhibit(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
m.set_focus_on_click(false);
|
||||
|
||||
match msgpos {
|
||||
MsgPos::Bottom => {
|
||||
messages.insert(&m, -1);
|
||||
|
||||
if first_new {
|
||||
let divider = widgets::divider::new(i18n("New Messages").as_str());
|
||||
messages.insert(÷r, -1);
|
||||
}
|
||||
},
|
||||
MsgPos::Top => {
|
||||
messages.insert(&m, 1);
|
||||
|
||||
if first_new {
|
||||
let divider = widgets::divider::new(i18n("New Messages").as_str());
|
||||
messages.insert(÷r, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.shown_messages += 1;
|
||||
}
|
||||
|
||||
m.set_focus_on_click(false);
|
||||
|
||||
match msgpos {
|
||||
MsgPos::Bottom => {
|
||||
messages.insert(&m, -1);
|
||||
|
||||
if first_new {
|
||||
let divider = widgets::divider::new(i18n("New Messages").as_str());
|
||||
messages.insert(÷r, -1);
|
||||
}
|
||||
},
|
||||
MsgPos::Top => {
|
||||
messages.insert(&m, 1);
|
||||
|
||||
if first_new {
|
||||
let divider = widgets::divider::new(i18n("New Messages").as_str());
|
||||
messages.insert(÷r, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.shown_messages += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tmp_room_message(&mut self, msg: Message) {
|
||||
let messages = self.ui.builder
|
||||
.get_object::<gtk::ListBox>("message_list")
|
||||
.expect("Can't find message_list in ui file.");
|
||||
if let Some(ui_msg) = self.create_new_room_message(&msg) {
|
||||
let messages = self.ui.builder
|
||||
.get_object::<gtk::ListBox>("message_list")
|
||||
.expect("Can't find message_list in ui file.");
|
||||
|
||||
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
|
||||
let m;
|
||||
{
|
||||
let mb = widgets::MessageBox::new(r, &msg, &self);
|
||||
m = mb.tmpwidget();
|
||||
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
|
||||
let m;
|
||||
{
|
||||
let backend = self.backend.clone();
|
||||
let ui = self.ui.clone();
|
||||
let mut mb = widgets::MessageBox::new(&ui_msg, backend, &ui);
|
||||
m = mb.tmpwidget();
|
||||
if let Some(ref image) = mb.image {
|
||||
let msg = msg.clone();
|
||||
let room = r.clone();
|
||||
image.connect_button_press_event(move |_, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
let msg = msg.clone();
|
||||
let room = room.clone();
|
||||
APPOP!(create_media_viewer, (msg, room));
|
||||
|
||||
Inhibit(true)
|
||||
} else {
|
||||
Inhibit(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
messages.add(&m);
|
||||
}
|
||||
|
||||
messages.add(&m);
|
||||
}
|
||||
|
||||
if let Some(w) = messages.get_children().iter().last() {
|
||||
self.msg_queue.insert(0, TmpMsg {
|
||||
if let Some(w) = messages.get_children().iter().last() {
|
||||
self.msg_queue.insert(0, TmpMsg {
|
||||
msg: msg.clone(),
|
||||
widget: Some(w.clone()),
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_tmp_msgs(&mut self) {
|
||||
|
@ -263,15 +303,35 @@ impl AppOp {
|
|||
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
|
||||
let mut widgets = vec![];
|
||||
for t in self.msg_queue.iter().rev().filter(|m| m.msg.room == r.id) {
|
||||
let m;
|
||||
{
|
||||
let mb = widgets::MessageBox::new(r, &t.msg, &self);
|
||||
m = mb.tmpwidget();
|
||||
}
|
||||
if let Some(ui_msg) = self.create_new_room_message(&t.msg) {
|
||||
let m;
|
||||
{
|
||||
let backend = self.backend.clone();
|
||||
let ui = self.ui.clone();
|
||||
let mut mb = widgets::MessageBox::new(&ui_msg, backend, &ui);
|
||||
m = mb.tmpwidget();
|
||||
if let Some(ref image) = mb.image {
|
||||
println!("i have a image");
|
||||
let msg = t.msg.clone();
|
||||
let room = r.clone();
|
||||
image.connect_button_press_event(move |_, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
let msg = msg.clone();
|
||||
let room = room.clone();
|
||||
APPOP!(create_media_viewer, (msg, room));
|
||||
|
||||
messages.add(&m);
|
||||
if let Some(w) = messages.get_children().iter().last() {
|
||||
widgets.push(w.clone());
|
||||
Inhibit(true)
|
||||
} else {
|
||||
Inhibit(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
messages.add(&m);
|
||||
if let Some(w) = messages.get_children().iter().last() {
|
||||
widgets.push(w.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,4 +672,66 @@ impl AppOp {
|
|||
}
|
||||
self.internal.send(InternalCommand::LoadMoreNormal).unwrap();
|
||||
}
|
||||
/* parese a backend Message into a Message for the UI */
|
||||
pub fn create_new_room_message(&self, msg: &Message) -> Option<MessageContent> {
|
||||
let mut highlights = vec![];
|
||||
let t = match msg.mtype.as_ref() {
|
||||
"m.emote" => RowType::Emote,
|
||||
"m.image" => RowType::Image,
|
||||
"m.sticker" => RowType::Sticker,
|
||||
"m.audio" => RowType::Audio,
|
||||
"m.video" => RowType::Video,
|
||||
"m.file" => RowType::File,
|
||||
_ => {
|
||||
/* set message type to mention if the body contains the username, we should
|
||||
* also match for MXID */
|
||||
let is_mention = if let Some(user) = self.username.clone() {
|
||||
msg.sender != self.uid.clone()? && msg.body.contains(&user)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_mention {
|
||||
if let Some(user) = self.username.clone() {
|
||||
highlights.push(user);
|
||||
}
|
||||
if let Some(mxid) = self.uid.clone() {
|
||||
highlights.push(mxid);
|
||||
}
|
||||
highlights.push(String::from("message_menu"));
|
||||
|
||||
RowType::Mention
|
||||
} else {
|
||||
RowType::Message
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let room = self.rooms.get(&msg.room)?;
|
||||
let name = if let Some(member) = room.members.get(&msg.sender) {
|
||||
member.alias.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Some(create_ui_message(msg.clone(), name, t, highlights))
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: don't convert msg to ui messages here, we should later get a ui message from storage */
|
||||
fn create_ui_message (msg: Message, name: Option<String>, t: RowType, highlights: Vec<String>) -> MessageContent {
|
||||
MessageContent {
|
||||
msg: msg.clone(),
|
||||
id: msg.id.unwrap_or(String::from("")),
|
||||
sender: msg.sender,
|
||||
sender_name: name,
|
||||
mtype: t,
|
||||
body: msg.body,
|
||||
date: msg.date,
|
||||
thumb: msg.thumb,
|
||||
url: msg.url,
|
||||
formatted_body: msg.formatted_body,
|
||||
format: msg.format,
|
||||
highlights: highlights,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::fs::File;
|
|||
use std::fs::remove_dir_all;
|
||||
use std::io::prelude::*;
|
||||
use gtk;
|
||||
use gtk::LabelExt;
|
||||
|
||||
use types::RoomList;
|
||||
use error::Error;
|
||||
|
@ -14,6 +15,7 @@ use globals;
|
|||
/* includes for avatar download */
|
||||
use backend::BKCommand;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
|
@ -93,3 +95,50 @@ pub fn download_to_cache(backend: Sender<BKCommand>,
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Get username based on the MXID, we should cache the username */
|
||||
pub fn download_to_cache_username(backend: Sender<BKCommand>,
|
||||
uid: &str,
|
||||
label: gtk::Label,
|
||||
avatar: Option<Rc<RefCell<AvatarData>>>) {
|
||||
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
|
||||
backend.send(BKCommand::GetUserNameAsync(uid.to_string(), tx)).unwrap();
|
||||
gtk::timeout_add(50, move || match rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => gtk::Continue(true),
|
||||
Err(TryRecvError::Disconnected) => gtk::Continue(false),
|
||||
Ok(username) => {
|
||||
label.set_text(&username);
|
||||
if let Some(ref rc_data) = avatar {
|
||||
let mut data = rc_data.borrow_mut();
|
||||
data.redraw_fallback(Some(username));
|
||||
}
|
||||
|
||||
gtk::Continue(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Download username for a given MXID and update a emote message
|
||||
* FIXME: We should cache this request and do it before we need to display the username in an emote*/
|
||||
pub fn download_to_cache_username_emote(backend: Sender<BKCommand>,
|
||||
uid: &str,
|
||||
text: &str,
|
||||
label: gtk::Label,
|
||||
avatar: Option<Rc<RefCell<AvatarData>>>) {
|
||||
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
|
||||
backend.send(BKCommand::GetUserNameAsync(uid.to_string(), tx)).unwrap();
|
||||
let text = text.to_string();
|
||||
gtk::timeout_add(50, move || match rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => gtk::Continue(true),
|
||||
Err(TryRecvError::Disconnected) => gtk::Continue(false),
|
||||
Ok(username) => {
|
||||
label.set_markup(&format!("<b>{}</b> {}", &username, text));
|
||||
if let Some(ref rc_data) = avatar {
|
||||
let mut data = rc_data.borrow_mut();
|
||||
data.redraw_fallback(Some(username));
|
||||
}
|
||||
|
||||
gtk::Continue(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ mod globals;
|
|||
#[macro_use]
|
||||
mod util;
|
||||
mod cache;
|
||||
mod uitypes;
|
||||
mod uibuilder;
|
||||
mod static_resources;
|
||||
mod passwd;
|
||||
|
|
36
fractal-gtk/src/uitypes.rs
Normal file
36
fractal-gtk/src/uitypes.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use chrono::prelude::DateTime;
|
||||
use chrono::prelude::Local;
|
||||
use types::Message;
|
||||
/* MessageContent contains all data needed to display one row
|
||||
* therefore it should contain only one Message body with one format
|
||||
* To-Do: this should be moved to a file collecting all structs used in the UI */
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MessageContent {
|
||||
pub id: String,
|
||||
pub sender: String,
|
||||
pub sender_name: Option<String>,
|
||||
pub mtype: RowType,
|
||||
pub body: String,
|
||||
pub date: DateTime<Local>,
|
||||
pub thumb: Option<String>,
|
||||
pub url: Option<String>,
|
||||
pub formatted_body: Option<String>,
|
||||
pub format: Option<String>,
|
||||
/* in some places we still need the backend message type (e.g. media viewer) */
|
||||
pub msg: Message,
|
||||
pub highlights: Vec<String>,
|
||||
}
|
||||
|
||||
/* To-Do: this should be moved to a file collecting all structs used in the UI */
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RowType {
|
||||
Divider,
|
||||
Mention,
|
||||
Emote,
|
||||
Message,
|
||||
Sticker,
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
File,
|
||||
}
|
|
@ -10,11 +10,6 @@ use app::App;
|
|||
use i18n::i18n;
|
||||
|
||||
use self::gtk::prelude::*;
|
||||
|
||||
use types::Message;
|
||||
use types::Member;
|
||||
use types::Room;
|
||||
|
||||
use self::chrono::prelude::*;
|
||||
|
||||
use backend::BKCommand;
|
||||
|
@ -26,36 +21,40 @@ use std::sync::mpsc::{Sender, Receiver};
|
|||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
use cache::download_to_cache;
|
||||
use cache::download_to_cache_username;
|
||||
use cache::download_to_cache_username_emote;
|
||||
|
||||
use appop::AppOp;
|
||||
use globals;
|
||||
use widgets;
|
||||
use widgets::AvatarExt;
|
||||
use widgets::message_menu::MessageMenu;
|
||||
use uitypes::RowType;
|
||||
use uitypes::MessageContent as Message;
|
||||
use uibuilder::UI;
|
||||
|
||||
// Room Message item
|
||||
pub struct MessageBox<'a> {
|
||||
room: &'a Room,
|
||||
msg: &'a Message,
|
||||
op: &'a AppOp,
|
||||
backend: Sender<BKCommand>,
|
||||
ui: &'a UI,
|
||||
username: gtk::Label,
|
||||
pub username_event_box: gtk::EventBox,
|
||||
pub row_event_box: gtk::EventBox,
|
||||
pub image: Option<gtk::DrawingArea>,
|
||||
}
|
||||
|
||||
impl<'a> MessageBox<'a> {
|
||||
pub fn new(room: &'a Room, msg: &'a Message, op: &'a AppOp) -> MessageBox<'a> {
|
||||
pub fn new(msg: &'a Message, backend: Sender<BKCommand>, ui: &'a UI) -> MessageBox<'a> {
|
||||
let username = gtk::Label::new("");
|
||||
let eb = gtk::EventBox::new();
|
||||
|
||||
let row_eb = gtk::EventBox::new();
|
||||
let backend = op.backend.clone();
|
||||
let ui = op.ui.clone();
|
||||
let source_msg = msg.msg.clone();
|
||||
|
||||
row_eb.connect_button_press_event(clone!(msg => move |eb, btn| {
|
||||
row_eb.connect_button_press_event(clone!(source_msg, backend, ui => move |eb, btn| {
|
||||
if btn.get_button() == 3 {
|
||||
let menu = MessageMenu::new_message_menu(ui.clone(), backend.clone(),
|
||||
msg.clone(), None);
|
||||
source_msg.clone(), None);
|
||||
menu.show_menu_popover(eb.clone().upcast::<gtk::Widget>());
|
||||
}
|
||||
|
||||
|
@ -64,15 +63,16 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
MessageBox {
|
||||
msg: msg,
|
||||
room: room,
|
||||
op: op,
|
||||
backend: backend,
|
||||
ui: ui,
|
||||
username: username,
|
||||
username_event_box: eb,
|
||||
row_event_box: row_eb,
|
||||
image: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tmpwidget(&self) -> gtk::ListBoxRow {
|
||||
pub fn tmpwidget(&mut self) -> gtk::ListBoxRow {
|
||||
let w = self.widget();
|
||||
if let Some(style) = w.get_style_context() {
|
||||
style.add_class("msg-tmp");
|
||||
|
@ -80,7 +80,7 @@ impl<'a> MessageBox<'a> {
|
|||
w
|
||||
}
|
||||
|
||||
pub fn widget(&self) -> gtk::ListBoxRow {
|
||||
pub fn widget(&mut self) -> gtk::ListBoxRow {
|
||||
// msg
|
||||
// +--------+---------+
|
||||
// | avatar | content |
|
||||
|
@ -105,7 +105,7 @@ impl<'a> MessageBox<'a> {
|
|||
row
|
||||
}
|
||||
|
||||
pub fn small_widget(&self) -> gtk::ListBoxRow {
|
||||
pub fn small_widget(&mut self) -> gtk::ListBoxRow {
|
||||
// msg
|
||||
// +--------+---------+
|
||||
// | | content |
|
||||
|
@ -127,7 +127,7 @@ impl<'a> MessageBox<'a> {
|
|||
row
|
||||
}
|
||||
|
||||
fn build_room_msg_content(&self, small: bool) -> gtk::Box {
|
||||
fn build_room_msg_content(&mut self, small: bool) -> gtk::Box {
|
||||
// content
|
||||
// +------+
|
||||
// | info |
|
||||
|
@ -138,18 +138,18 @@ impl<'a> MessageBox<'a> {
|
|||
let msg = self.msg;
|
||||
|
||||
if !small {
|
||||
let info = self.build_room_msg_info(self.msg, small);
|
||||
let info = self.build_room_msg_info(self.msg);
|
||||
info.set_margin_top(2);
|
||||
info.set_margin_bottom(3);
|
||||
content.pack_start(&info, false, false, 0);
|
||||
}
|
||||
|
||||
let body = match msg.mtype.as_ref() {
|
||||
"m.sticker" => self.build_room_msg_sticker(),
|
||||
"m.image" => self.build_room_msg_image(),
|
||||
"m.emote" => self.build_room_msg_emote(&msg),
|
||||
"m.audio" => self.build_room_audio_player(),
|
||||
"m.video" | "m.file" => self.build_room_msg_file(),
|
||||
let body = match msg.mtype {
|
||||
RowType::Sticker => self.build_room_msg_sticker(),
|
||||
RowType::Image => self.build_room_msg_image(),
|
||||
RowType::Emote => self.build_room_msg_emote(&msg),
|
||||
RowType::Audio => self.build_room_audio_player(),
|
||||
RowType::Video | RowType::File => self.build_room_msg_file(),
|
||||
_ => self.build_room_msg_body(&msg.body),
|
||||
};
|
||||
|
||||
|
@ -160,52 +160,24 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
fn build_room_msg_avatar(&self) -> widgets::Avatar {
|
||||
let uid = self.msg.sender.clone();
|
||||
let alias = self.msg.sender_name.clone();
|
||||
let avatar = widgets::Avatar::avatar_new(Some(globals::MSG_ICON_SIZE));
|
||||
|
||||
let m = self.room.members.get(&uid);
|
||||
let data = avatar.circle(uid.clone(), alias.clone(), globals::MSG_ICON_SIZE);
|
||||
if let Some(name) = alias {
|
||||
self.username.set_text(&name);
|
||||
} else {
|
||||
self.username.set_text(&uid);
|
||||
}
|
||||
|
||||
let data = match m {
|
||||
Some(member) => {
|
||||
self.username.set_text(&member.get_alias());
|
||||
let username = Some(member.get_alias());
|
||||
avatar.circle(uid.clone(), username, globals::MSG_ICON_SIZE)
|
||||
}
|
||||
None => {
|
||||
let backend = self.op.backend.clone();
|
||||
let data = avatar.circle(uid.clone(), None, globals::MSG_ICON_SIZE);
|
||||
let l = self.username.clone();
|
||||
let d = data.clone();
|
||||
set_username_async(backend, &uid, move |uname| {
|
||||
l.set_text(&uname);
|
||||
let mut data = d.borrow_mut();
|
||||
data.redraw_fallback(Some(uname));
|
||||
});
|
||||
data
|
||||
}
|
||||
};
|
||||
|
||||
download_to_cache(self.op.backend.clone(), uid.clone(),
|
||||
data.clone());
|
||||
download_to_cache(self.backend.clone(), uid.clone(), data.clone());
|
||||
download_to_cache_username(self.backend.clone(), &uid, self.username.clone(), Some(data.clone()));
|
||||
|
||||
avatar
|
||||
}
|
||||
|
||||
fn build_room_msg_username(&self, sender: &str, member: Option<&Member>, small: bool) -> gtk::Label {
|
||||
let uname = match member {
|
||||
Some(m) => m.get_alias(),
|
||||
None => {
|
||||
// in small widget, the avatar doesn't download the username
|
||||
// so we need to download here
|
||||
if small {
|
||||
let backend = self.op.backend.clone();
|
||||
let l = self.username.clone();
|
||||
set_username_async(backend, sender, move |uname| {
|
||||
l.set_text(&uname);
|
||||
});
|
||||
}
|
||||
String::from(sender)
|
||||
}
|
||||
};
|
||||
fn build_room_msg_username(&self, sender: &str) -> gtk::Label {
|
||||
let uname = String::from(sender);
|
||||
|
||||
self.username.set_text(&uname);
|
||||
self.username.set_justify(gtk::Justification::Left);
|
||||
|
@ -219,23 +191,14 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
/// Add classes to the widget depending on the properties:
|
||||
///
|
||||
/// * msg-mention: if the message contains the username in the body and
|
||||
/// sender is not app user
|
||||
/// * msg-mention: if the message contains a keyword, e.g. the username
|
||||
/// * msg-emote: if the message is an emote
|
||||
fn set_msg_styles(&self, w: >k::ListBoxRow) {
|
||||
let uname = &self.op.username.clone().unwrap_or_default();
|
||||
let uid = self.op.uid.clone().unwrap_or_default();
|
||||
let msg = self.msg;
|
||||
let body: &str = &msg.body;
|
||||
|
||||
if let Some(style) = w.get_style_context() {
|
||||
// mentions
|
||||
if String::from(body).contains(uname) && msg.sender != uid {
|
||||
style.add_class("msg-mention");
|
||||
}
|
||||
// emotes
|
||||
if msg.mtype == "m.emote" {
|
||||
style.add_class("msg-emote");
|
||||
match self.msg.mtype {
|
||||
RowType::Mention => style.add_class("msg-mention"),
|
||||
RowType::Emote => style.add_class("msg-emote"),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,35 +215,39 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
fn build_room_msg_body(&self, body: &str) -> gtk::Box {
|
||||
let bx = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
let uname = self.op.username.clone().unwrap_or_default();
|
||||
|
||||
let msg_parts = self.calculate_msg_parts(body);
|
||||
|
||||
if self.msg.sender != self.op.uid.clone().unwrap_or_default()
|
||||
&& String::from(body).contains(&uname) {
|
||||
if self.msg.mtype == RowType::Mention {
|
||||
for msg in msg_parts.iter() {
|
||||
let name = uname.clone();
|
||||
let highlights = self.msg.highlights.clone();
|
||||
msg.connect_property_cursor_position_notify(move |w| {
|
||||
if let Some(text) = w.get_text() {
|
||||
if let Some(attr) = highlight_username(w.clone(), &name, text) {
|
||||
w.set_attributes(&attr);
|
||||
}
|
||||
if let Some(text) = w.get_text() {
|
||||
let attr = pango::AttrList::new();
|
||||
for light in highlights.clone() {
|
||||
highlight_username(w.clone(), &attr, &light, text.clone());
|
||||
}
|
||||
w.set_attributes(&attr);
|
||||
}
|
||||
});
|
||||
|
||||
let name = uname.clone();
|
||||
let highlights = self.msg.highlights.clone();
|
||||
msg.connect_property_selection_bound_notify(move |w| {
|
||||
if let Some(text) = w.get_text() {
|
||||
if let Some(attr) = highlight_username(w.clone(), &name, text) {
|
||||
w.set_attributes(&attr);
|
||||
}
|
||||
if let Some(text) = w.get_text() {
|
||||
let attr = pango::AttrList::new();
|
||||
for light in highlights.clone() {
|
||||
highlight_username(w.clone(), &attr, &light, text.clone());
|
||||
}
|
||||
w.set_attributes(&attr);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(text) = msg.get_text() {
|
||||
if let Some(attr) = highlight_username(msg.clone(), &uname, text) {
|
||||
msg.set_attributes(&attr);
|
||||
let attr = pango::AttrList::new();
|
||||
for light in self.msg.highlights.clone() {
|
||||
highlight_username(msg.clone(), &attr, &light, text.clone());
|
||||
}
|
||||
msg.set_attributes(&attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,45 +280,31 @@ impl<'a> MessageBox<'a> {
|
|||
parts_labels
|
||||
}
|
||||
|
||||
fn build_room_msg_image(&self) -> gtk::Box {
|
||||
fn build_room_msg_image(&mut self) -> gtk::Box {
|
||||
let msg = self.msg;
|
||||
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
|
||||
let backend = self.op.backend.clone();
|
||||
let img_path = match msg.thumb {
|
||||
Some(ref m) => m.clone(),
|
||||
None => msg.url.clone().unwrap_or_default(),
|
||||
};
|
||||
let image = widgets::image::Image::new(&backend, &img_path)
|
||||
let image = widgets::image::Image::new(&self.backend, &img_path)
|
||||
.size(Some(globals::MAX_IMAGE_SIZE)).build();
|
||||
|
||||
let msg = msg.clone();
|
||||
let room = self.room.clone();
|
||||
image.widget.connect_button_press_event(move |_, btn| {
|
||||
if btn.get_button() != 3 {
|
||||
let msg = msg.clone();
|
||||
let room = room.clone();
|
||||
APPOP!(create_media_viewer, (msg, room));
|
||||
|
||||
Inhibit(true)
|
||||
} else {
|
||||
Inhibit(false)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(style) = image.widget.get_style_context() {
|
||||
style.add_class("image-widget");
|
||||
}
|
||||
|
||||
bx.pack_start(&image.widget, true, true, 0);
|
||||
bx.show_all();
|
||||
self.image = Some(image.widget);
|
||||
bx
|
||||
}
|
||||
|
||||
fn build_room_msg_sticker(&self) -> gtk::Box {
|
||||
let msg = self.msg;
|
||||
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
let backend = self.op.backend.clone();
|
||||
let backend = self.backend.clone();
|
||||
let image = widgets::image::Image::new(&backend,
|
||||
&msg.url.clone().unwrap_or_default())
|
||||
.size(Some(globals::MAX_STICKER_SIZE)).build();
|
||||
|
@ -370,7 +323,7 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
let name = msg.body.clone();
|
||||
let url = msg.url.clone().unwrap_or_default();
|
||||
let backend = self.op.backend.clone();
|
||||
let backend = self.backend.clone();
|
||||
|
||||
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
|
||||
backend.send(BKCommand::GetMediaUrl(url.clone(), tx)).unwrap();
|
||||
|
@ -431,7 +384,7 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
let name = msg.body.clone();
|
||||
let url = msg.url.clone().unwrap_or_default();
|
||||
let backend = self.op.backend.clone();
|
||||
let backend = self.backend.clone();
|
||||
let name_lbl = gtk::Label::new(name.as_str());
|
||||
name_lbl.set_tooltip_text(name.as_str());
|
||||
name_lbl.set_ellipsize(pango::EllipsizeMode::End);
|
||||
|
@ -514,15 +467,14 @@ impl<'a> MessageBox<'a> {
|
|||
date
|
||||
}
|
||||
|
||||
fn build_room_msg_info(&self, msg: &Message, small: bool) -> gtk::Box {
|
||||
fn build_room_msg_info(&self, msg: &Message) -> gtk::Box {
|
||||
// info
|
||||
// +----------+------+
|
||||
// | username | date |
|
||||
// +----------+------+
|
||||
let info = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
|
||||
let member = self.room.members.get(&msg.sender);
|
||||
let username = self.build_room_msg_username(&msg.sender, member, small);
|
||||
let username = self.build_room_msg_username(&msg.sender);
|
||||
let date = self.build_room_msg_date(&msg.date);
|
||||
|
||||
self.username_event_box.add(&username);
|
||||
|
@ -535,25 +487,13 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
fn build_room_msg_emote(&self, msg: &Message) -> gtk::Box {
|
||||
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
let member = self.room.members.get(&msg.sender);
|
||||
let sender: &str = &msg.sender;
|
||||
|
||||
/* Use MXID till we have a alias */
|
||||
let sname = msg.sender_name.clone().unwrap_or(String::from(msg.sender.clone()));
|
||||
let msg_label = gtk::Label::new("");
|
||||
let body: &str = &msg.body;
|
||||
let markup = markup_text(body);
|
||||
|
||||
let m = markup.clone();
|
||||
let sname = match member {
|
||||
Some(m) => m.get_alias(),
|
||||
None => {
|
||||
let backend = self.op.backend.clone();
|
||||
let label = msg_label.clone();
|
||||
set_username_async(backend, sender, move |n| {
|
||||
label.set_markup(&format!("<b>{}</b> {}", n, m));
|
||||
});
|
||||
String::from(sender)
|
||||
}
|
||||
};
|
||||
download_to_cache_username_emote(self.backend.clone(), &sname, &markup, msg_label.clone(), None);
|
||||
|
||||
self.connect_right_click_menu(msg_label.clone().upcast::<gtk::Widget>());
|
||||
msg_label.set_markup(&format!("<b>{}</b> {}", sname, markup));
|
||||
|
@ -565,9 +505,9 @@ impl<'a> MessageBox<'a> {
|
|||
|
||||
fn connect_right_click_menu(&self, w: gtk::Widget) {
|
||||
let eb = self.row_event_box.clone();
|
||||
let msg = self.msg.clone();
|
||||
let backend = self.op.backend.clone();
|
||||
let ui = self.op.ui.clone();
|
||||
let backend = self.backend.clone();
|
||||
let ui = self.ui.clone();
|
||||
let msg = self.msg.msg.clone();
|
||||
|
||||
w.connect_button_press_event(move |w, btn| {
|
||||
if btn.get_button() == 3 {
|
||||
|
@ -582,7 +522,7 @@ impl<'a> MessageBox<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn highlight_username(label: gtk::Label, alias: &String, input: String) -> Option<pango::AttrList> {
|
||||
fn highlight_username(label: gtk::Label, attr: &pango::AttrList, alias: &String, input: String) -> Option<()> {
|
||||
fn contains((start, end): (i32, i32), item: i32) -> bool {
|
||||
match start <= end {
|
||||
true => start <= item && end > item,
|
||||
|
@ -599,7 +539,6 @@ fn highlight_username(label: gtk::Label, alias: &String, input: String) -> Optio
|
|||
let blue = fg.blue * 65535. + 0.5;
|
||||
let color = pango::Attribute::new_foreground(red as u16, green as u16, blue as u16)?;
|
||||
|
||||
let attr = pango::AttrList::new();
|
||||
let mut input = input.clone();
|
||||
let alias = &alias.to_lowercase();
|
||||
let mut removed_char = 0;
|
||||
|
@ -647,23 +586,7 @@ fn highlight_username(label: gtk::Label, alias: &String, input: String) -> Optio
|
|||
removed_char = removed_char + pos.1 as u32;
|
||||
}
|
||||
|
||||
Some(attr)
|
||||
}
|
||||
|
||||
fn set_username_async<F>(backend: Sender<BKCommand>,
|
||||
uid: &str,
|
||||
cb: F)
|
||||
where F: Fn(String) + 'static {
|
||||
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
|
||||
backend.send(BKCommand::GetUserNameAsync(uid.to_string(), tx)).unwrap();
|
||||
gtk::timeout_add(50, move || match rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => gtk::Continue(true),
|
||||
Err(TryRecvError::Disconnected) => gtk::Continue(false),
|
||||
Ok(username) => {
|
||||
cb(username);
|
||||
gtk::Continue(false)
|
||||
}
|
||||
});
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
|
Loading…
Reference in a new issue