room_history.rs 29.4 KB
Newer Older
Zeeshan Ali's avatar
Zeeshan Ali committed
1
2
3
use chrono::DateTime;
use chrono::Datelike;
use chrono::Local;
4
use chrono::Timelike;
Sonja Heinze's avatar
Sonja Heinze committed
5
6
use fragile::Fragile;
use log::warn;
7
use std::cell::RefCell;
Zeeshan Ali's avatar
Zeeshan Ali committed
8
use std::collections::VecDeque;
9
10
use std::rc::Rc;

11
use crate::appop::UserInfoCache;
12
13
14
use crate::ui::MessageContent;
use crate::ui::RowType;
use crate::ui::UI;
15
use crate::util::i18n::i18n;
16

Alejandro Domínguez's avatar
Alejandro Domínguez committed
17
18
use crate::globals;
use crate::widgets;
Sonja Heinze's avatar
Sonja Heinze committed
19
use crate::widgets::{PlayerExt, VideoPlayerWidget};
20
use gio::ActionMapExt;
Julian Sparber's avatar
Julian Sparber committed
21
use gio::SimpleActionGroup;
22
use glib::clone;
23
use glib::source;
24
use glib::source::Continue;
Sonja Heinze's avatar
Sonja Heinze committed
25
26
use glib::SignalHandlerId;
use glib::Source;
Zeeshan Ali's avatar
Zeeshan Ali committed
27
use gtk::prelude::*;
Alejandro Domínguez's avatar
Alejandro Domínguez committed
28
29
use matrix_sdk::identifiers::RoomId;
use matrix_sdk::Client as MatrixClient;
30

31
struct List {
32
    /* With the exception of temporary widgets, only modify the fields list and listbox
33
34
    through the methods add_top(), add_bottom(), remove_item() and replace_item() to
    maintain the 1-1 correspondence between them. */
35
    list: VecDeque<Element>,
36
    new_divider_index: Option<usize>,
Sonja Heinze's avatar
Sonja Heinze committed
37
38
    playing_videos: Vec<(Rc<VideoPlayerWidget>, SignalHandlerId)>,
    video_scroll_debounce: Option<source::SourceId>,
39
    view: widgets::ScrollWidget,
40
41
42
}

impl List {
Kai Hiller's avatar
Kai Hiller committed
43
    pub fn new(view: widgets::ScrollWidget) -> List {
44
45
        List {
            list: VecDeque::new(),
46
            new_divider_index: None,
Sonja Heinze's avatar
Sonja Heinze committed
47
48
            playing_videos: Vec::new(),
            video_scroll_debounce: None,
49
            view,
50
51
52
        }
    }

53
54
55
56
57
58
59
    /// Adds the element at the given position to the history.
    ///
    /// An index of 0 points to the uppermost element.
    ///
    /// ### Panics
    /// Panics if `index > len`.
    pub fn add_item(&mut self, index: usize, element: Element) {
Kai Hiller's avatar
Kai Hiller committed
60
        self.view.insert(index, element.get_listbox_row());
61
62
63
        self.list.insert(self.list.len() - index, element);
    }

Kai Hiller's avatar
Kai Hiller committed
64
    /// Adds the element to the top of the history.
Sonja Heinze's avatar
Sonja Heinze committed
65
    pub fn add_top(&mut self, element: Element) {
66
        self.add_item(0, element);
67
68
69
70
        /* TODO: update the previous message:
         * we need to update the previous row because it could be that we have to remove the header */
    }

Kai Hiller's avatar
Kai Hiller committed
71
    /// Adds the element to the bottom of the history.
Sonja Heinze's avatar
Sonja Heinze committed
72
    pub fn add_bottom(&mut self, element: Element) {
73
74
75
        if let Some(index) = self.new_divider_index {
            self.new_divider_index = Some(index + 1);
        }
76
        self.add_item(self.list.len(), element);
77
    }
78

Kai Hiller's avatar
Kai Hiller committed
79
80
81
82
    /// Removes the element at the given position from the history.
    ///
    /// ### Panics
    /// Panics if `index >= len`.
Kai Hiller's avatar
Kai Hiller committed
83
    fn remove_item(&mut self, index: usize) {
84
85
        self.list.remove(index);
        self.view.remove(self.list.len() - index - 1);
86
87
    }

Kai Hiller's avatar
Kai Hiller committed
88
89
90
91
    /// Replaces the element at the given position in the history.
    ///
    /// ### Panics
    /// Panics if `index >= len`.
Kai Hiller's avatar
Kai Hiller committed
92
    fn replace_item(&mut self, index: usize, element: Element) {
93
94
95
96
        self.view.remove(self.list.len() - index - 1);
        self.view
            .insert(self.list.len() - index - 1, element.get_listbox_row());
        self.list[index] = element;
97
98
    }

99
    fn create_new_message_divider(rows: Rc<RefCell<Self>>) -> widgets::NewMessageDivider {
100
101
102
103
104
105
106
107
        let remove_divider = clone!(@weak rows => move || {
            let new_divider_index = rows
                .borrow_mut()
                .new_divider_index
                .take()
                .expect("The new divider index must exist, since there is a new divider");
            rows.borrow_mut().list.remove(new_divider_index);
        });
108
109
        widgets::NewMessageDivider::new(i18n("New Messages").as_str(), remove_divider)
    }
Sonja Heinze's avatar
Sonja Heinze committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
    fn update_videos(&mut self) {
        let visible = self.find_visible_videos();
        let mut new_looped: Vec<(Rc<VideoPlayerWidget>, SignalHandlerId)> =
            Vec::with_capacity(visible.len());

        /* Once drain_filter is not nightly-only anymore, we can use drain_filter. */
        for (player, handler_id) in self.playing_videos.drain(..) {
            if visible.contains(&player) {
                new_looped.push((player, handler_id));
            } else {
                player.stop_loop(handler_id);
            }
        }
        for player in visible {
            if !new_looped.iter().any(|(widget, _)| widget == &player) {
                let handler_id = player.play_in_loop();
                new_looped.push((player, handler_id));
            }
        }
        self.playing_videos = new_looped;
    }

    fn find_visible_videos(&self) -> Vec<Rc<VideoPlayerWidget>> {
        self.find_all_visible_indices()
            .iter()
            .filter_map(|&index| match self.list.get(index)? {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
136
137
138
139
                Element::Message(content) if content.mtype == RowType::Video => {
                    Some(content
                        .widget
                        .as_ref()?
140
                        .get_video_player()
Alejandro Domínguez's avatar
Alejandro Domínguez committed
141
                        .expect("The widget of every MessageContent, whose mtype is RowType::Video, must have a video_player.")
142
                        .clone()
Alejandro Domínguez's avatar
Alejandro Domínguez committed
143
                    )
Sonja Heinze's avatar
Sonja Heinze committed
144
145
146
147
148
149
150
151
152
153
154
155
                },
                _ => None,
            })
            .collect()
    }

    fn find_all_visible_indices(&self) -> Vec<usize> {
        let len = self.list.len();
        let mut indices = Vec::new();
        if len == 0 {
            return indices;
        }
156
157
158

        let sw = self.view.get_scrolled_window();
        let visible_index = match get_rel_position(&sw, &self.list[0]) {
159
            RelativePosition::In => Some(0),
160
            _ => self.find_visible_index((0, len - 1)),
161
162
163
164
        };
        if let Some(visible) = visible_index {
            indices.push(visible);
            let upper = self.list.iter().enumerate().skip(visible + 1);
Sonja Heinze's avatar
Sonja Heinze committed
165
166
167
168
169
170
            self.add_while_visible(&mut indices, upper);
            let lower = self
                .list
                .iter()
                .enumerate()
                .rev()
171
                .skip(self.list.len() - visible);
Sonja Heinze's avatar
Sonja Heinze committed
172
173
174
175
176
177
            self.add_while_visible(&mut indices, lower);
        }
        indices
    }

    fn find_visible_index(&self, range: (usize, usize)) -> Option<usize> {
178
179
180
181
182
        /* Looks for a message widget in sight among all elements in rows.list.list of RoomHistory
        whose corresponding index lies in the closed interval [range.0, range.1]. */
        if range.0 > range.1 {
            return None;
        }
Sonja Heinze's avatar
Sonja Heinze committed
183
184
185
        let middle_index = (range.0 + range.1) / 2;
        let element = &self.list[middle_index];
        let scrolled_window = self.view.get_scrolled_window();
Günther Wagner's avatar
Günther Wagner committed
186
        match get_rel_position(&scrolled_window, element) {
187
            RelativePosition::Above => {
Sonja Heinze's avatar
Sonja Heinze committed
188
                if range.0 == range.1 {
Günther Wagner's avatar
Günther Wagner committed
189
                    None
Sonja Heinze's avatar
Sonja Heinze committed
190
191
192
193
                } else {
                    self.find_visible_index((range.0, middle_index))
                }
            }
194
195
            RelativePosition::In => Some(middle_index),
            RelativePosition::Below => {
Sonja Heinze's avatar
Sonja Heinze committed
196
                if range.0 == range.1 {
Günther Wagner's avatar
Günther Wagner committed
197
                    None
Sonja Heinze's avatar
Sonja Heinze committed
198
199
200
201
                } else {
                    self.find_visible_index((middle_index + 1, range.1))
                }
            }
Günther Wagner's avatar
Günther Wagner committed
202
        }
Sonja Heinze's avatar
Sonja Heinze committed
203
204
205
206
207
208
209
210
211
    }

    fn add_while_visible<'a, T>(&self, indices: &mut Vec<usize>, iterator: T)
    where
        T: Iterator<Item = (usize, &'a Element)>,
    {
        let scrolled_window = self.view.get_scrolled_window();
        for (index, element) in iterator {
            match get_rel_position(&scrolled_window, element) {
212
                RelativePosition::In => {
Sonja Heinze's avatar
Sonja Heinze committed
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
                    indices.push(index);
                }
                _ => {
                    break;
                }
            }
        }
    }
}

fn get_rel_position(scrolled_window: &gtk::ScrolledWindow, element: &Element) -> RelativePosition {
    let widget = element.get_listbox_row();
    let height_visible_area = gtk::WidgetExt::get_allocated_height(scrolled_window);
    let height_widget = gtk::WidgetExt::get_allocated_height(widget);
    let rel_y = gtk::WidgetExt::translate_coordinates(widget, scrolled_window, 0, 0)
        .expect("Both scrolled_window and widget should be realized and share a common toplevel.")
        .1;
    if rel_y <= -height_widget {
231
        RelativePosition::Above
Sonja Heinze's avatar
Sonja Heinze committed
232
    } else if rel_y < height_visible_area {
233
        RelativePosition::In
Sonja Heinze's avatar
Sonja Heinze committed
234
    } else {
235
        RelativePosition::Below
Sonja Heinze's avatar
Sonja Heinze committed
236
237
238
239
240
    }
}

#[derive(Clone, Debug)]
enum RelativePosition {
241
242
243
    In,
    Above,
    Below,
244
245
}

246
247
/* These Enum contains all differnet types of rows the room history can have, e.g room message, new
 * message divider, day divider */
248
#[derive(Clone)]
249
250
enum Element {
    Message(MessageContent),
251
    NewDivider(widgets::NewMessageDivider),
252
    DayDivider(gtk::ListBoxRow),
253
254
}

Sonja Heinze's avatar
Sonja Heinze committed
255
256
257
impl Element {
    fn get_listbox_row(&self) -> &gtk::ListBoxRow {
        match self {
258
259
260
261
262
            Element::Message(content) => content
                .widget
                .as_ref()
                .expect("The content of every message element must have widget.")
                .get_widget(),
Sonja Heinze's avatar
Sonja Heinze committed
263
264
265
266
267
268
            Element::NewDivider(widgets) => widgets.get_widget(),
            Element::DayDivider(widget) => widget,
        }
    }
}

269
270
pub struct RoomHistory {
    /* Contains a list of msg ids to keep track of the displayed messages */
271
    rows: Rc<RefCell<List>>,
272
    source_id: Rc<RefCell<Option<source::SourceId>>>,
273
    queue: Rc<RefCell<VecDeque<MessageContent>>>,
274
    edit_buffer: Rc<RefCell<VecDeque<MessageContent>>>,
275
276
277
}

impl RoomHistory {
278
279
    pub fn new(actions: SimpleActionGroup, room_id: RoomId, ui: &UI) -> Option<RoomHistory> {
        let history_container = ui
Zeeshan Ali's avatar
Zeeshan Ali committed
280
            .builder
281
282
            .get_object::<gtk::Box>("history_container")
            .expect("Can't find history_container in ui file.");
283
284
        let action = actions.lookup_action("request_older_messages");
        let scroll = widgets::ScrollWidget::new(action, room_id);
285
286
287
        /* remove previous room history widget */
        for ch in history_container.get_children().iter() {
            history_container.remove(ch);
288
        }
289
290
291
        /* add room history widget */
        history_container.add(&scroll.get_container());
        let listbox = scroll.get_listbox();
292

293
        /* Add the action groupe to the room_history */
Sonja Heinze's avatar
Sonja Heinze committed
294
        listbox.insert_action_group("message", Some(&actions));
Sonja Heinze's avatar
Sonja Heinze committed
295
        let mut rh = RoomHistory {
Kai Hiller's avatar
Kai Hiller committed
296
            rows: Rc::new(RefCell::new(List::new(scroll))),
297
            source_id: Rc::new(RefCell::new(None)),
298
            queue: Rc::new(RefCell::new(VecDeque::new())),
299
            edit_buffer: Rc::new(RefCell::new(VecDeque::new())),
Sonja Heinze's avatar
Sonja Heinze committed
300
301
302
303
304
305
        };

        rh.connect_video_auto_play();
        rh.connect_video_focus();

        Some(rh)
306
307
    }

308
309
    pub fn create(
        &mut self,
310
        session_client: MatrixClient,
311
        user_info_cache: UserInfoCache,
312
313
        mut messages: Vec<MessageContent>,
    ) -> Option<()> {
314
315
316
317
318
319
        let mut position = messages.len();
        /* Find position of last viewed message */
        for (i, item) in messages.iter().enumerate() {
            if item.last_viewed {
                position = i + 1;
            }
Zeeshan Ali's avatar
Zeeshan Ali committed
320
        }
321
        let bottom = messages.split_off(position);
322
        messages.reverse();
323
        self.add_old_messages_in_batch(session_client.clone(), user_info_cache.clone(), messages);
324
        /* Add the rest of the messages after the new message divider */
325
        self.add_new_messages_in_batch(session_client, user_info_cache, bottom);
326

327
        let rows = &self.rows;
328
        let id = glib::timeout_add_local(
329
330
331
332
            250,
            clone!(
            @weak rows
            => @default-return Continue(false), move || {
Sonja Heinze's avatar
Sonja Heinze committed
333
                rows.borrow_mut().update_videos();
334
335
336
                Continue(false)
            }),
        );
Sonja Heinze's avatar
Sonja Heinze committed
337
338
        self.rows.borrow_mut().video_scroll_debounce = Some(id);

339
340
341
        None
    }

Sonja Heinze's avatar
Sonja Heinze committed
342
343
344
345
346
347
348
349
350
351
    fn connect_video_auto_play(&self) {
        let scrollbar = self
            .rows
            .borrow()
            .view
            .get_scrolled_window()
            .get_vscrollbar()
            .expect("The scrolled window must have a vertical scrollbar.")
            .downcast::<gtk::Scrollbar>()
            .unwrap();
352
353
        let rows = &self.rows;
        scrollbar.connect_value_changed(clone!(@weak rows => move |sb| {
354
355
            if !sb.get_state_flags().contains(gtk::StateFlags::BACKDROP) {
                /* Fractal is focused */
356
                let new_id = glib::timeout_add_local(250, clone!(
357
358
359
360
                    @weak rows
                    => @default-return Continue(false), move || {
                        rows.borrow_mut().update_videos();
                        rows.borrow_mut().video_scroll_debounce = None;
361
                        Continue(false)
362
363
364
                    }));
                if let Some(old_id) = rows.borrow_mut().video_scroll_debounce.replace(new_id) {
                    let _ = Source::remove(old_id);
365
                }
366
            }
367
        }));
Sonja Heinze's avatar
Sonja Heinze committed
368
369
370
    }

    fn connect_video_focus(&mut self) {
371
372
        let rows = &self.rows;

Sonja Heinze's avatar
Sonja Heinze committed
373
        let scrolled_window = self.rows.borrow().view.get_scrolled_window();
374
        scrolled_window.connect_map(clone!(@weak rows => move |_| {
Sonja Heinze's avatar
Sonja Heinze committed
375
            /* The user has navigated back into the room history */
376
377
378
379
380
381
382
383
            let len = rows.borrow().playing_videos.len();
            if len != 0 {
                warn!(
                    "{:?} videos were playing while the room history was not displayed.",
                    len
                );
                for (player, hander_id) in rows.borrow_mut().playing_videos.drain(..) {
                    player.stop_loop(hander_id);
Sonja Heinze's avatar
Sonja Heinze committed
384
                }
385
            }
386
387
388
389
390
391
392
393
            let visible_videos = rows.borrow().find_visible_videos();
            let mut videos = Vec::with_capacity(visible_videos.len());
            for player in visible_videos {
                let handler_id = player.play_in_loop();
                videos.push((player, handler_id));
            }
            rows.borrow_mut().playing_videos = videos;
        }));
Sonja Heinze's avatar
Sonja Heinze committed
394

395
        scrolled_window.connect_unmap(clone!(@weak rows => move |_| {
Sonja Heinze's avatar
Sonja Heinze committed
396
            /* The user has navigated out of the room history */
397
398
            if let Some(id) = rows.borrow_mut().video_scroll_debounce.take() {
                let _ = Source::remove(id);
399
            }
400
401
402
403
            for (player, handler_id) in rows.borrow_mut().playing_videos.drain(..) {
                player.stop_loop(handler_id);
            }
        }));
Sonja Heinze's avatar
Sonja Heinze committed
404

405
        scrolled_window.connect_state_flags_changed(clone!(@weak rows => move |window, flag| {
Sonja Heinze's avatar
Sonja Heinze committed
406
407
            if window.get_mapped() {
                /* The room history is being displayed */
408
409
410
411
412
413
414
415
416
                let focused = gtk::StateFlags::BACKDROP;
                if flag.contains(focused) {
                    /* Fractal has been focused */
                    let len = rows.borrow().playing_videos.len();
                    if len != 0 {
                        warn!(
                            "{:?} videos were playing while Fractal was focused out.",
                            len
                        );
Sonja Heinze's avatar
Sonja Heinze committed
417
418
419
420
                        for (player, handler_id) in rows.borrow_mut().playing_videos.drain(..) {
                            player.stop_loop(handler_id);
                        }
                    }
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
                    let visible_videos = rows.borrow().find_visible_videos();
                    let mut videos = Vec::with_capacity(visible_videos.len());
                    for player in visible_videos {
                        let handler_id = player.play_in_loop();
                        videos.push((player, handler_id));
                    }
                    rows.borrow_mut().playing_videos = videos;
                } else {
                    /* Fractal has been unfocused */
                    if let Some(id) = rows.borrow_mut().video_scroll_debounce.take() {
                        let _ = Source::remove(id);
                    }
                    for (player, handler_id) in rows.borrow_mut().playing_videos.drain(..) {
                        player.stop_loop(handler_id);
                    }
436
                }
Sonja Heinze's avatar
Sonja Heinze committed
437
            }
438
        }));
Sonja Heinze's avatar
Sonja Heinze committed
439
440
    }

441
442
443
444
445
    fn run_queue(
        &mut self,
        session_client: MatrixClient,
        user_info_cache: UserInfoCache,
    ) -> Option<()> {
446
        let queue = self.queue.clone();
447
        let edit_buffer = self.edit_buffer.clone();
448
449
        let rows = self.rows.clone();

Yuri Chornoivan's avatar
Yuri Chornoivan committed
450
        /* TO-DO: we could set the listbox height the 52 * length of messages, to decrease jumps of the
451
452
453
         * scrollbar. 52 is the normal height of a message with one line
         * self.listbox.set_size_request(-1, 52 * messages.len() as i32); */

454
        if self.source_id.borrow().is_some() {
Zeeshan Ali's avatar
Zeeshan Ali committed
455
            /* We don't need a new loop, just keeping the old one */
456
        } else {
457
            /* Lazy load initial messages */
Zeeshan Ali's avatar
Zeeshan Ali committed
458
            let source_id = self.source_id.clone();
459
            *self.source_id.borrow_mut() = Some(glib::idle_add_local(move || {
Zeeshan Ali's avatar
Zeeshan Ali committed
460
                let mut data = queue.borrow_mut();
461
                let mut edits = edit_buffer.borrow_mut();
Zeeshan Ali's avatar
Zeeshan Ali committed
462
                if let Some(mut item) = data.pop_front() {
463
464
465
466
467
468
469
470
471
472
473
                    /* Since we are reading bottom-to-top, we will encounter edit events sooner than
                     * the original messages. */
                    if item.msg.replace.is_some() {
                        if !edits
                            .iter()
                            .any(|edit| item.msg.replace == edit.msg.replace)
                        {
                            edits.push_back(item);
                        }
                        return Continue(true);
                    }
Alejandro Domínguez's avatar
Alejandro Domínguez committed
474
475
476
477
478
                    if let Some(pos) = edits
                        .iter()
                        .position(|edit| item.msg.id == edit.msg.replace)
                    {
                        edits[pos].msg.date = item.msg.date;
479
480
481
                        item = edits.remove(pos).unwrap();
                    }

Zeeshan Ali's avatar
Zeeshan Ali committed
482
                    let last = data.front();
483
                    let mut prev_day_divider = None;
Zeeshan Ali's avatar
Zeeshan Ali committed
484
                    let mut day_divider = None;
485
486

                    if let Some(first) = rows.borrow().list.back() {
Günther Wagner's avatar
Günther Wagner committed
487
                        if let Element::Message(ref message) = first {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
488
                            if item.msg.date.day() != message.msg.date.day() {
Günther Wagner's avatar
Günther Wagner committed
489
                                prev_day_divider =
Alejandro Domínguez's avatar
Alejandro Domínguez committed
490
                                    Some(Element::DayDivider(create_day_divider(message.msg.date)));
491
492
493
                            }
                        }
                    };
Zeeshan Ali's avatar
Zeeshan Ali committed
494
495
                    let has_header = {
                        if let Some(last) = last {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
496
                            if item.msg.date.day() != last.msg.date.day() {
Zeeshan Ali's avatar
Zeeshan Ali committed
497
                                day_divider =
Alejandro Domínguez's avatar
Alejandro Domínguez committed
498
                                    Some(Element::DayDivider(create_day_divider(item.msg.date)));
Zeeshan Ali's avatar
Zeeshan Ali committed
499
500
501
502
                            }
                            last.mtype == RowType::Emote || !should_group_message(&item, &last)
                        } else {
                            true
503
                        }
Zeeshan Ali's avatar
Zeeshan Ali committed
504
                    };
505

506
507
508
                    if let Some(prev_day_divider) = prev_day_divider {
                        rows.borrow_mut().add_top(prev_day_divider);
                    }
Zeeshan Ali's avatar
Zeeshan Ali committed
509
                    if item.last_viewed && !rows.borrow().list.is_empty() {
510
511
                        let divider =
                            Element::NewDivider(List::create_new_message_divider(rows.clone()));
Zeeshan Ali's avatar
Zeeshan Ali committed
512
                        rows.borrow_mut().add_top(divider);
513
514
                        let new_divider_index = rows.borrow().list.len() - 1;
                        rows.borrow_mut().new_divider_index = Some(new_divider_index);
Zeeshan Ali's avatar
Zeeshan Ali committed
515
                    }
516
                    item.widget = Some(create_row(
517
                        session_client.clone(),
518
                        user_info_cache.clone(),
519
520
                        item.clone(),
                        has_header,
Sonja Heinze's avatar
Sonja Heinze committed
521
                        &rows,
522
                    ));
Zeeshan Ali's avatar
Zeeshan Ali committed
523
524
525
526
527
                    rows.borrow_mut().add_top(Element::Message(item));
                    if let Some(day_divider) = day_divider {
                        rows.borrow_mut().add_top(day_divider);
                    }
                } else {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
528
                    /* Remove the source id, since the closure is destroyed */
Zeeshan Ali's avatar
Zeeshan Ali committed
529
                    source_id.borrow_mut().take();
530
                    return Continue(false);
531
                }
Günther Wagner's avatar
Günther Wagner committed
532
                Continue(true)
Zeeshan Ali's avatar
Zeeshan Ali committed
533
            }));
534
        }
535
536
537
        None
    }

538
539
540
541
542
543
    pub fn destroy(self) {
        if let Some(id) = self.source_id.borrow_mut().take() {
            source::source_remove(id);
        }
    }

Yuri Chornoivan's avatar
Yuri Chornoivan committed
544
    /* This is a temporary function to make the listbox accessible from outside the history, it is
545
     * currently needed for temp messages (which should also be moved to the room history) */
546
    pub fn get_listbox(&self) -> gtk::ListBox {
Kai Hiller's avatar
Kai Hiller committed
547
        self.rows.borrow().view.get_listbox()
548
549
    }

550
    /* This adds new incomming messages at then end of the list */
551
552
    pub fn add_new_message(
        &mut self,
553
        session_client: MatrixClient,
554
        user_info_cache: UserInfoCache,
555
556
        mut item: MessageContent,
    ) -> Option<()> {
557
        if item.msg.replace.is_some() {
558
            self.replace_message(session_client, user_info_cache, item);
559
560
            return None;
        }
561
        let mut rows = self.rows.borrow_mut();
562
        let mut day_divider = None;
563
        let has_header = {
564
            let last = rows.list.front();
565
            if let Some(last) = last {
566
567
                match last {
                    Element::Message(ref message) => {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
568
569
570
                        if item.msg.date.day() != message.msg.date.day() {
                            day_divider =
                                Some(Element::DayDivider(create_day_divider(item.msg.date)));
571
                        }
572
573
                        message.mtype == RowType::Emote || !should_group_message(&item, &message)
                    }
Zeeshan Ali's avatar
Zeeshan Ali committed
574
                    _ => false,
575
                }
576
577
578
579
580
            } else {
                true
            }
        };

581
        if item.last_viewed {
582
            let divider = Element::NewDivider(List::create_new_message_divider(self.rows.clone()));
583
            rows.add_bottom(divider);
584
585
            let new_divider_index = rows.list.len() - 1;
            rows.new_divider_index = Some(new_divider_index);
586
        }
587
        if let Some(day_divider) = day_divider {
588
            rows.add_bottom(day_divider);
589
590
        }

591
        let b = create_row(
592
            session_client,
593
            user_info_cache,
594
595
            item.clone(),
            has_header,
Sonja Heinze's avatar
Sonja Heinze committed
596
            &self.rows,
597
        );
598
        item.widget = Some(b);
599
        rows.add_bottom(Element::Message(item));
600
601
602
        None
    }

603
604
    pub fn replace_message(
        &mut self,
605
        session_client: MatrixClient,
606
        user_info_cache: UserInfoCache,
607
608
609
610
611
612
613
614
615
616
        mut item: MessageContent,
    ) -> Option<()> {
        let mut rows = self.rows.borrow_mut();

        let (i, ref mut msg) = rows
            .list
            .iter_mut()
            .enumerate()
            .find_map(|(i, e)| match e {
                Element::Message(ref mut itermessage)
Alejandro Domínguez's avatar
Alejandro Domínguez committed
617
                    if itermessage.msg.id == item.msg.replace
618
619
620
621
622
623
                        || itermessage.msg.replace == item.msg.replace =>
                {
                    Some((i, itermessage))
                }
                _ => None,
            })?;
Alejandro Domínguez's avatar
Alejandro Domínguez committed
624
        item.msg.date = msg.msg.date;
625
626
627
        let msg_widget = msg.widget.clone()?;

        item.widget = Some(create_row(
628
            session_client,
629
630
            user_info_cache,
            item.clone(),
631
            msg_widget.has_header(),
632
633
            &self.rows,
        ));
Kai Hiller's avatar
Kai Hiller committed
634
        rows.replace_item(i, Element::Message(item));
635
636
637
        None
    }

638
639
    pub fn remove_message(
        &mut self,
640
        session_client: MatrixClient,
641
        user_info_cache: UserInfoCache,
642
643
        item: MessageContent,
    ) -> Option<()> {
644
        let mut rows = self.rows.borrow_mut();
645
646
647
648
649
        let (i, ref mut msg) = rows
            .list
            .iter_mut()
            .enumerate()
            .find_map(|(i, e)| match e {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
650
                Element::Message(ref mut itermessage) if itermessage.msg.id == item.msg.id => {
651
652
653
654
                    Some((i, itermessage))
                }
                _ => None,
            })?;
655
656

        let msg_widget = msg.widget.clone()?;
Alejandro Domínguez's avatar
Alejandro Domínguez committed
657
        let msg_sender = msg.msg.sender.clone();
658
        msg.msg.redacted = true;
Kai Hiller's avatar
Kai Hiller committed
659
        rows.remove_item(i);
660

661
662
        // If the redacted message was a header message let's set
        // the header on the next non-redacted message instead.
663
        if msg_widget.has_header() {
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
            let rows_list_len = rows.list.len();
            if let Some((msg_next_cloned, msg_widget)) = rows
                .list
                .iter_mut()
                .rev()
                .skip(rows_list_len - i)
                .filter_map(|message_next| match message_next {
                    Element::Message(ref mut msg_next) => {
                        let msg_next_cloned = msg_next.clone();
                        msg_next
                            .widget
                            .as_mut()
                            .filter(|_| !msg_next_cloned.msg.redacted)
                            .map(|msg_widet| (msg_next_cloned, msg_widet))
                    }
                    _ => None,
                })
                .next()
                .filter(|(msg_next_cloned, _)| {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
683
                    msg_next_cloned.redactable && msg_next_cloned.msg.sender == msg_sender
684
685
                })
            {
686
                msg_widget.update_header(session_client, user_info_cache, msg_next_cloned, true);
687
688
            }
        }
689
690
691
        None
    }

692
693
    pub fn add_new_messages_in_batch(
        &mut self,
694
        session_client: MatrixClient,
695
        user_info_cache: UserInfoCache,
696
697
        messages: Vec<MessageContent>,
    ) -> Option<()> {
698
699
        /* TODO: use lazy loading */
        for item in messages {
700
            self.add_new_message(session_client.clone(), user_info_cache.clone(), item);
701
702
703
704
        }
        None
    }

705
706
    pub fn add_old_messages_in_batch(
        &mut self,
707
        session_client: MatrixClient,
708
        user_info_cache: UserInfoCache,
709
710
        messages: Vec<MessageContent>,
    ) -> Option<()> {
711
        self.rows.borrow().view.reset_request_sent();
712
        /* TODO: Try if extend would be faster then append */
Zeeshan Ali's avatar
Zeeshan Ali committed
713
714
715
        self.queue
            .borrow_mut()
            .append(&mut VecDeque::from(messages));
716
        self.run_queue(session_client, user_info_cache);
717
718
719

        None
    }
Rasmus Rendal's avatar
Rasmus Rendal committed
720
721
722
723

    pub fn typing_notification(&mut self, typing_str: &str) {
        self.rows.borrow().view.typing_notification(typing_str);
    }
724
725

    pub fn page_up(&mut self) {
726
727
        let scrolled_window = self.rows.borrow().view.get_scrolled_window();
        widgets::page_up(scrolled_window);
728
729
    }

730
731
732
    pub fn page_down(&self) {
        let scrolled_window = self.rows.borrow().view.get_scrolled_window();
        widgets::page_down(scrolled_window);
733
    }
734
}
Julian Sparber's avatar
Julian Sparber committed
735

Yuri Chornoivan's avatar
Yuri Chornoivan committed
736
/* This function creates the content for a Row based on the content of msg */
737
fn create_row(
738
    session_client: MatrixClient,
739
    user_info_cache: UserInfoCache,
740
741
    row: MessageContent,
    has_header: bool,
Sonja Heinze's avatar
Sonja Heinze committed
742
    rows: &Rc<RefCell<List>>,
743
) -> widgets::MessageBox {
744
745
    /* 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 */
Alejandro Domínguez's avatar
Alejandro Domínguez committed
746
    let mb = widgets::MessageBox::create(
747
        session_client,
748
749
750
751
752
        user_info_cache,
        &row,
        has_header && row.mtype != RowType::Emote,
        false,
    );
753

Günther Wagner's avatar
Günther Wagner committed
754
755
756
    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));
757
758
        PlayerExt::get_player(mb
                .get_video_player()
Sonja Heinze's avatar
Sonja Heinze committed
759
760
                .expect("The widget of every MessageContent, whose mtype is RowType::Video, must have a video_player."))
                .connect_uri_loaded(move |player, _| {
761
                    if let Some(rows) = fragile_rows.get().upgrade() {
762
                        let is_player_widget = rows.borrow().playing_videos.iter().any(|(player_widget, _)| {
Sonja Heinze's avatar
Sonja Heinze committed
763
                            &PlayerExt::get_player(&player_widget) == player
764
765
                        });
                        if is_player_widget {
Sonja Heinze's avatar
Sonja Heinze committed
766
767
                            player.play();
                        }
768
                    }
Sonja Heinze's avatar
Sonja Heinze committed
769
770
                });
    }
771
    mb
772
773
774
775
}

/* returns if two messages should have only a single header or not */
fn should_group_message(msg: &MessageContent, prev: &MessageContent) -> bool {
Alejandro Domínguez's avatar
Alejandro Domínguez committed
776
777
    if msg.msg.sender == prev.msg.sender && !prev.msg.redacted {
        let diff = msg.msg.date.signed_duration_since(prev.msg.date);
778
779
780
781
782
783
        let minutes = diff.num_minutes();
        minutes < globals::MINUTES_TO_SPLIT_MSGS
    } else {
        false
    }
}
784

785
/* Create the day divider */
786
fn create_day_divider(date: DateTime<Local>) -> gtk::ListBoxRow {
787
788
789
790
791
792
793
794
    let gdate = glib::DateTime::new_local(
        date.year(),
        date.month() as i32,
        date.day() as i32,
        date.hour() as i32,
        date.minute() as i32,
        date.second() as f64,
    );
795
    /* We show the year only when the message wasn't send in the current year */
796
797
798
    let format = if date.year() == Local::now().year() {
        // Translators: This is a date format in the day divider without the year
        i18n("%B %e")
799
    } else {
800
801
802
        // Translators: This is a date format in the day divider with the year
        i18n("%B %e, %Y")
    };
Christopher Davis's avatar
Christopher Davis committed
803
804
    let stamp = if let Some(gstr) = gdate.format(&format) {
        gstr.to_string()
805
806
807
    } else {
        // Fallback to a non glib time string
        date.format(&format).to_string()
808
    };
809
    let row = gtk::ListBoxRow::new();
Christopher Davis's avatar
Christopher Davis committed
810
    row.get_style_context().add_class("divider");
811
    row.set_margin_top(24);
812
813
    row.set_selectable(false);
    row.set_activatable(false);
Christopher Davis's avatar
Christopher Davis committed
814
    let label = gtk::Label::new(Some(stamp.as_str()));
815
816
817
818
819
    label.set_selectable(false);
    row.add(&label);

    row.show_all();
    row
820
}