roomlist.rs 17.6 KB
Newer Older
1
extern crate chrono;
2 3
extern crate url;
extern crate gtk;
4
extern crate pango;
5
extern crate gdk;
6

7
use glib;
8
use self::gdk::DragContextExtManual;
9

10 11 12 13 14 15
use self::url::Url;
use std::collections::HashMap;
use self::gtk::prelude::*;

use widgets::roomrow::RoomRow;
use types::Room;
16
use types::Message;
17
use std::sync::{Arc, Mutex, MutexGuard};
18

19 20
use self::chrono::prelude::*;

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

fn get_url(url: Option<String>) -> Url {
    let defurl = Url::parse("https://matrix.org").unwrap();

    match url {
        Some(u) => {
            match Url::parse(&u) {
                Ok(url) => url,
                Err(_) => defurl,
            }
        }
        None => defurl,
    }
}


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
pub struct RoomUpdated {
    pub room: Room,
    pub updated: DateTime<Local>,
}

impl RoomUpdated {
    pub fn new(room: Room) -> RoomUpdated {
        let updated = match room.messages.last() {
            Some(l) => l.date,
            None => Message::default().date,
        };

        RoomUpdated {
            room,
            updated,
        }
    }

    pub fn up(&mut self) {
        self.updated = Local::now();
    }
}

60
pub struct RoomListGroup {
61 62
    pub rooms: HashMap<String, RoomRow>,
    pub baseu: Url,
63
    pub list: gtk::ListBox,
64
    rev: gtk::Revealer,
65 66
    arrow: gtk::Image,
    expanded: Arc<Mutex<bool>>,
67
    title: gtk::Label,
68
    empty: gtk::Label,
69
    title_eb: gtk::EventBox,
70

71 72 73 74
    wbox: gtk::Box,
    pub widget: gtk::EventBox,

    roomvec: Arc<Mutex<Vec<RoomUpdated>>>,
75
    filter: Option<String>,
76 77
}

78 79
impl RoomListGroup {
    pub fn new(url: &Url, name: &str, empty_text: &str) -> RoomListGroup {
80
        let list = gtk::ListBox::new();
81
        let baseu = url.clone();
82
        let rooms = HashMap::new();
83
        let roomvec = Arc::new(Mutex::new(vec![]));
84 85 86 87 88 89 90 91 92

        let empty = gtk::Label::new(empty_text);
        empty.set_line_wrap_mode(pango::WrapMode::WordChar);
        empty.set_line_wrap(true);
        empty.set_justify(gtk::Justification::Center);
        if let Some(style) = empty.get_style_context() {
            style.add_class("room-empty-text");
        }

93
        let rev = gtk::Revealer::new();
94 95 96 97 98
        let b = gtk::Box::new(gtk::Orientation::Vertical, 0);
        b.add(&empty);
        b.add(&list);

        rev.add(&b);
99 100
        rev.set_reveal_child(true);

101
        let title = gtk::Label::new(name);
102 103
        title.set_halign(gtk::Align::Start);
        title.set_valign(gtk::Align::Start);
104 105
        let arrow = gtk::Image::new_from_icon_name("pan-down-symbolic", 2);
        let expanded = Arc::new(Mutex::new(true));
106 107
        let title_eb = gtk::EventBox::new();

108 109
        title_eb.connect_button_press_event(clone!(list, arrow, rev, expanded => move |_, _| {
            match *expanded.lock().unwrap() {
110
                true => {
111 112 113 114 115
                    arrow.set_from_icon_name("pan-end-symbolic", 2);
                    rev.set_reveal_child(false);
                    if let Some(style) = list.get_style_context() {
                        style.add_class("collapsed");
                    }
116
                }
117
                false => {
118 119 120 121 122
                    arrow.set_from_icon_name("pan-down-symbolic", 2);
                    rev.set_reveal_child(true);
                    if let Some(style) = list.get_style_context() {
                        style.remove_class("collapsed");
                    }
123 124
                }
            };
125 126
            let exp = !(*expanded.lock().unwrap());
            *expanded.lock().unwrap() = exp;
127
            glib::signal::Inhibit(true)
128
        }));
129

130 131 132
        let widget = gtk::EventBox::new();
        let wbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
        widget.add(&wbox);
133

134 135
        let filter= None;

136
        RoomListGroup {
137 138 139
            list,
            baseu,
            rooms,
140
            roomvec,
141 142 143 144
            rev,
            title,
            arrow,
            title_eb,
145 146
            widget,
            empty,
147
            wbox,
148
            expanded,
149
            filter,
150 151 152 153 154 155 156 157 158 159
        }
    }

    pub fn add_room(&mut self, r: Room) {
        if self.rooms.contains_key(&r.id) {
            // room added, we'll pass
            return;
        }

        let rid = r.id.clone();
160
        self.roomvec.lock().unwrap().push(RoomUpdated::new(r.clone()));
161

162 163 164 165
        let row = RoomRow::new(r, &self.baseu);
        self.list.add(&row.widget());

        self.rooms.insert(rid, row);
166
        self.show();
167 168
    }

169 170
    pub fn add_room_up(&mut self, r: RoomUpdated) {
        if self.rooms.contains_key(&r.room.id) {
171 172 173 174
            // room added, we'll pass
            return;
        }

175 176 177 178 179 180 181 182 183
        let rid = r.room.id.clone();
        let mut rv = self.roomvec.lock().unwrap();
        let mut pos = rv.len();
        for (i, ru) in rv.iter().enumerate() {
            if ru.updated < r.updated {
                pos = i;
                break;
            }
        }
184

185 186 187 188
        rv.insert(pos, RoomUpdated::new(r.room.clone()));

        let row = RoomRow::new(r.room, &self.baseu);
        self.list.insert(&row.widget(), pos as i32);
189 190

        self.rooms.insert(rid, row);
191
        self.show();
192 193
    }

194 195 196 197 198 199
    pub fn set_bold(&mut self, room: String, bold: bool) {
        if let Some(ref mut r) = self.rooms.get_mut(&room) {
            r.set_bold(bold);
        }
    }

200
    pub fn set_room_notifications(&mut self, room: String, n: i32, h: i32) {
201
        if let Some(ref mut r) = self.rooms.get_mut(&room) {
202
            r.set_notifications(n, h);
203
        }
204

205 206 207 208
        self.edit_room(&room, move |rv| {
            rv.room.notifications = n;
            rv.room.highlight = h;
        });
209 210
    }

211
    pub fn remove_room(&mut self, room: String) -> Option<RoomUpdated> {
212
        self.rooms.remove(&room);
213
        let mut rv = self.roomvec.lock().unwrap();
214
        if let Some(idx) = rv.iter().position(|x| { x.room.id == room}) {
215 216 217
            if let Some(row) = self.list.get_row_at_index(idx as i32) {
                self.list.remove(&row);
            }
218
            self.show();
219
            return Some(rv.remove(idx));
220
        }
221 222

        None
223 224
    }

225
    pub fn rename_room(&mut self, room: String, newname: Option<String>) {
226
        if let (Some(r), Some(n)) = (self.rooms.get_mut(&room), newname.clone()) {
227 228
            r.set_name(n);
        }
229

230
        self.edit_room(&room, move |rv| { rv.room.name = newname.clone(); });
231 232
    }

233 234
    pub fn set_room_avatar(&mut self, room: String, av: Option<String>) {
        if let Some(r) = self.rooms.get_mut(&room) {
235 236 237
            r.set_avatar(av.clone());
        }

238
        self.edit_room(&room, move |rv| { rv.room.avatar = av.clone(); });
239 240
    }

241 242
    pub fn widget(&self) -> gtk::EventBox {
        let b = self.wbox.clone();
243 244
        if let Some(style) = b.get_style_context() {
            style.add_class("room-list");
245
            style.add_class("sidebar");
246
        }
247

248 249 250 251 252 253 254 255 256 257 258 259 260
        // building the heading
        let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
        if let Some(style) = hbox.get_style_context() {
            style.add_class("room-title");
        }
        hbox.pack_start(&self.title, true, true, 0);
        hbox.pack_start(&self.arrow, false, false, 0);

        for ch in self.title_eb.get_children() {
            self.title_eb.remove(&ch);
        }
        self.title_eb.add(&hbox);

261 262
        self.arrow.set_from_icon_name("pan-down-symbolic", 2);
        *self.expanded.lock().unwrap() = true;
263
        self.rev.set_reveal_child(true);
264 265 266 267
        if let Some(style) = self.list.get_style_context() {
            style.remove_class("collapsed");
        }

268 269
        b.pack_start(&self.title_eb, false, false, 0);
        b.pack_start(&self.rev, true, true, 0);
270 271

        self.show();
272

273
        self.widget.clone()
274
    }
275

276 277 278 279 280 281 282 283 284
    pub fn show(&self) {
        self.widget.show_all();
        if self.rooms.is_empty() {
            self.empty.show();
            self.list.hide();
        } else {
            self.list.show();
            self.empty.hide();
        }
285
        self.render_notifies();
286 287 288 289 290 291
    }

    pub fn hide(&self) {
        self.widget.hide();
    }

292 293 294 295
    pub fn connect<F: Fn(Room) + 'static>(&self, cb: F) {
        let rs = self.roomvec.clone();
        self.list.connect_row_activated(move |_, row| {
            let idx = row.get_index();
296
            cb(rs.lock().unwrap()[idx as usize].room.clone());
297 298
        });
    }
299 300

    pub fn get_selected(&self) -> Option<String> {
301
        let rv = self.roomvec.lock().unwrap();
302
        match self.list.get_selected_row() {
303
            Some(row) => Some(rv[row.get_index() as usize].room.id.clone()),
304 305 306 307 308 309 310 311 312 313 314 315 316
            None => None,
        }
    }

    pub fn set_selected(&self, room: Option<String>) {
        self.list.unselect_all();

        if room.is_none() {
            return;
        }

        let room = room.unwrap();

317
        let rv = self.roomvec.lock().unwrap();
318
        if let Some(idx) = rv.iter().position(|x| { x.room.id == room}) {
319 320 321 322 323 324 325
            if let Some(ref row) = self.list.get_row_at_index(idx as i32) {
                self.list.select_row(row);
            }
        }
    }

    pub fn add_rooms(&mut self, mut array: Vec<Room>) {
326 327 328 329 330 331
        array.sort_by_key(|ref x| {
            match x.messages.last() {
                Some(l) => l.date,
                None => Message::default().date,
            }
        });
332

333 334
        for r in array.iter().rev() {
            self.add_room(r.clone());
335 336 337
        }
    }

338 339 340
    pub fn moveup(&mut self, room: String) {
        let s = self.get_selected();

341
        self.edit_room(&room, move |rv| { rv.up(); });
342
        if let Some(r) = self.remove_room(room) {
343
            self.add_room_up(r);
344 345 346
        }

        self.set_selected(s);
347 348
        let term = self.filter.clone();
        self.filter_rooms(&term);
349 350
    }

351 352 353 354 355
    fn render_notifies(&self) {
        for (_k, r) in self.rooms.iter() {
            r.render_notifies();
        }
    }
356

357
    fn edit_room<F: Fn(&mut RoomUpdated) + 'static>(&mut self, room: &str, cb: F) {
358
        let mut rv = self.roomvec.lock().unwrap();
359
        if let Some(idx) = rv.iter().position(|x| { x.room.id == room}) {
360 361 362
            if let Some(ref mut m) = rv.get_mut(idx) {
                cb(m);
            }
363 364
        }
    }
365

366 367 368
    pub fn filter_rooms(&mut self, term: &Option<String>) {
        self.filter = term.clone();

369 370 371
        for (i, r) in self.roomvec.lock().unwrap().iter().enumerate() {
            if let Some(row) = self.list.get_row_at_index(i as i32) {
                match term {
372
                    &Some(ref t) if !t.is_empty() => {
373 374 375 376 377 378 379 380 381
                        let rname = r.room.name.clone()
                                     .unwrap_or("".to_string())
                                     .to_lowercase();
                        if rname.contains(&t.to_lowercase()) {
                            row.show();
                        } else {
                            row.hide();
                        }
                    }
382
                    _ => { row.show(); }
383 384 385 386
                };
            }
        }
    }
387
}
388

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
#[derive(Clone)]
struct RGroup {
    g: Arc<Mutex<RoomListGroup>>,
}

impl RGroup {
    pub fn new(url: &Url, name: &str, empty_text: &str) -> RGroup {
        let r = RoomListGroup::new(url, name, empty_text);
        RGroup{ g: Arc::new(Mutex::new(r)) }
    }

    pub fn get(&self) -> MutexGuard<RoomListGroup> {
        self.g.lock().unwrap()
    }
}
404 405 406 407 408

pub struct RoomList {
    pub baseu: Url,
    widget: gtk::Box,

409 410 411
    inv: RGroup,
    fav: RGroup,
    rooms: RGroup,
412 413 414 415
}

macro_rules! run_in_group {
    ($self: expr, $roomid: expr, $fn: ident, $($arg: expr),*) => {{
416 417 418 419
        if $self.inv.get().rooms.contains_key($roomid) {
            $self.inv.get().$fn($($arg),*)
        } else if $self.fav.get().rooms.contains_key($roomid) {
            $self.fav.get().$fn($($arg),*)
420
        } else {
421
            $self.rooms.get().$fn($($arg),*)
422 423 424 425 426 427 428 429 430
        }
    }}
}

impl RoomList {
    pub fn new(url: Option<String>) -> RoomList {
        let widget = gtk::Box::new(gtk::Orientation::Vertical, 6);
        let baseu = get_url(url);

431 432 433 434
        let inv = RGroup::new(&baseu, "Invites", "You don't have any invitations");
        let fav = RGroup::new(&baseu, "Favorites", "Drag and drop rooms here to \
                                                    add them to your favorites");
        let rooms = RGroup::new(&baseu, "Rooms", "You don't have any rooms yet");
435

436
        let rl = RoomList {
437 438 439 440 441
            baseu,
            widget,
            inv,
            fav,
            rooms,
442 443 444
        };

        rl
445 446 447
    }

    pub fn set_selected(&self, room: Option<String>) {
448 449 450
        self.inv.get().set_selected(None);
        self.fav.get().set_selected(None);
        self.rooms.get().set_selected(None);
451 452 453 454 455 456 457 458

        if let Some(r) = room {
            run_in_group!(self, &r, set_selected, Some(r.clone()));
        }
    }

    pub fn get_selected(&self) -> Option<String> {
        for i in [&self.inv, &self.fav, &self.rooms].iter() {
459
            if let Some(s) = i.get().get_selected() {
460 461 462 463 464 465 466
                return Some(s.clone());
            }
        }
        None
    }

    pub fn add_rooms(&mut self, array: Vec<Room>) {
467 468 469
        self.inv.get().add_rooms(array.iter().filter(|r| r.inv).cloned().collect::<Vec<Room>>());
        self.fav.get().add_rooms(array.iter().filter(|r| r.fav).cloned().collect::<Vec<Room>>());
        self.rooms.get().add_rooms(array.iter().filter(|r| !r.fav && !r.inv).cloned().collect::<Vec<Room>>());
470 471 472 473 474 475 476
        self.show_and_hide();
    }

    pub fn connect<F: Fn(Room) + 'static>(&self, cb: F) {
        let acb = Arc::new(cb);

        let cb = acb.clone();
477
        self.inv.get().connect(move |room| cb(room));
478
        let cb = acb.clone();
479
        self.fav.get().connect(move |room| cb(room));
480
        let cb = acb.clone();
481
        self.rooms.get().connect(move |room| cb(room));
482 483
    }

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    pub fn connect_fav<F: Fn(Room, bool) + 'static>(&self, cb: F) {
        let acb = Arc::new(cb);

        let favw = self.fav.get().widget.clone();
        let r = self.rooms.clone();
        let f = self.fav.clone();
        let cb = acb.clone();
        self.connect_drop(favw, move |roomid| {
            if let Some(room) = r.get().remove_room(roomid) {
                cb(room.room.clone(), true);
                f.get().add_room_up(room);
            }
        });

        let rw = self.rooms.get().widget.clone();
        let r = self.rooms.clone();
        let f = self.fav.clone();
        let cb = acb.clone();
        self.connect_drop(rw, move |roomid| {
            if let Some(room) = f.get().remove_room(roomid) {
                cb(room.room.clone(), false);
                r.get().add_room_up(room);
            }
        });
    }

510 511 512 513
    pub fn set_room_avatar(&mut self, room: String, av: Option<String>) {
        run_in_group!(self, &room, set_room_avatar, room, av);
    }

514 515
    pub fn set_room_notifications(&mut self, room: String, n: i32, h: i32) {
        run_in_group!(self, &room, set_room_notifications, room, n, h);
516 517
    }

518
    pub fn remove_room(&mut self, room: String) -> Option<RoomUpdated> {
519
        let ret = run_in_group!(self, &room, remove_room, room);
520
        self.show_and_hide();
521
        ret
522 523
    }

524 525 526 527
    pub fn set_bold(&mut self, room: String, bold: bool) {
        run_in_group!(self, &room, set_bold, room, bold)
    }

528
    pub fn add_room(&mut self, r: Room) {
529 530 531 532 533 534 535
        if r.inv {
            self.inv.get().add_room(r);
        } else if r.fav {
            self.fav.get().add_room(r);
        } else {
            self.rooms.get().add_room(r);
        }
536 537 538 539 540 541 542 543 544 545 546 547
        self.show_and_hide();
    }

    pub fn rename_room(&mut self, room: String, newname: Option<String>) {
        run_in_group!(self, &room, rename_room, room, newname);
    }

    pub fn moveup(&mut self, room: String) {
        run_in_group!(self, &room, moveup, room);
    }

    pub fn widget(&self) -> gtk::Box {
548
        self.connect_select();
549

550 551 552
        for ch in self.widget.get_children() {
            self.widget.remove(&ch);
        }
553 554 555
        self.widget.add(&self.inv.get().widget());
        self.widget.add(&self.fav.get().widget());
        self.widget.add(&self.rooms.get().widget());
556 557 558 559 560 561 562 563 564

        self.show_and_hide();

        self.widget.clone()
    }

    pub fn show_and_hide(&self) {
        self.widget.show_all();

565 566
        if self.inv.get().rooms.is_empty() {
            self.inv.get().hide();
567
        } else {
568
            self.inv.get().show();
569 570
        }

571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
        self.fav.get().show();
        self.rooms.get().show();
    }

    pub fn connect_select(&self) {
        let inv = self.inv.clone();
        let rooms = self.rooms.clone();
        self.fav.get().list.connect_row_activated(move |_, _| {
            inv.get().set_selected(None);
            rooms.get().set_selected(None);
        });

        let inv = self.inv.clone();
        let fav = self.fav.clone();
        self.rooms.get().list.connect_row_activated(move |_, _| {
            inv.get().set_selected(None);
            fav.get().set_selected(None);
        });
589
    }
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611

    pub fn connect_drop<F: Fn(String) + 'static>(&self, widget: gtk::EventBox, cb: F) {
        let flags = gtk::DestDefaults::empty();
        let action = gdk::DragAction::all();
        widget.drag_dest_set(flags, &[], action);
        widget.drag_dest_add_text_targets();
        widget.connect_drag_motion(move |_w, ctx, _x, _y, time| {
            ctx.drag_status(gdk::DragAction::MOVE, time);
            glib::signal::Inhibit(true)
        });
        widget.connect_drag_drop(move |w, ctx, _x, _y, time| {
            if let Some(target) = w.drag_dest_find_target(ctx, None) {
                w.drag_get_data(ctx, &target, time);
            }
            glib::signal::Inhibit(true)
        });
        widget.connect_drag_data_received(move |_w, _ctx, _x, _y, data, _info, _time| {
            if let Some(roomid) = data.get_text() {
                cb(roomid);
            }
        });
    }
612 613 614 615 616 617

    pub fn filter_rooms(&self, term: Option<String>) {
        self.inv.get().filter_rooms(&term);
        self.fav.get().filter_rooms(&term);
        self.rooms.get().filter_rooms(&term);
    }
618
}