app.rs 119 KB
Newer Older
1
extern crate gtk;
2
extern crate gdk_pixbuf;
3
extern crate chrono;
4
extern crate gdk;
5
extern crate notify_rust;
6
extern crate rand;
7
extern crate comrak;
8

9 10
use std::env;

11
use self::notify_rust::Notification;
12

13
use util::get_pixbuf_data;
14
use util::markup_text;
15

16
use self::chrono::prelude::*;
17

18 19
use self::rand::{thread_rng, Rng};

20 21
use self::comrak::{markdown_to_html,ComrakOptions};

22
use std::sync::{Arc, Mutex};
23 24
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
25
use std::sync::mpsc::TryRecvError;
26
use std::sync::mpsc::RecvError;
27
use std::collections::HashMap;
28
use std::process::Command;
29
use std::thread;
30

31
use gio::ApplicationExt;
32 33
use gio::SimpleActionExt;
use gio::ActionMapExt;
34 35
use glib;
use gio;
36
use self::gdk_pixbuf::Pixbuf;
37
use self::gdk_pixbuf::PixbufExt;
38
use self::gio::prelude::*;
39
use self::gtk::prelude::*;
40
use self::gdk::FrameClockExt;
41

42 43
use globals;

44
use backend::Backend;
45 46
use backend::BKCommand;
use backend::BKResponse;
47
use backend;
48

49 50
use types::Member;
use types::Message;
51 52
use types::Protocol;
use types::Room;
53
use types::RoomList;
54
use types::Event;
55

56 57
use passwd::PasswordStorage;

58
use widgets;
59
use widgets::AvatarExt;
60
use cache;
61
use uibuilder;
62

63

64
const APP_ID: &'static str = "org.gnome.Fractal";
65

66 67
pub struct Force(pub bool);

68

69 70 71 72 73
struct TmpMsg {
    pub msg: Message,
    pub widget: gtk::Widget,
}

74 75 76 77 78 79
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LastViewed {
    Inline,
    Last,
    No,
}
80

81
pub struct AppOp {
82
    pub ui: uibuilder::UI,
83
    pub gtk_app: gtk::Application,
84
    pub backend: Sender<backend::BKCommand>,
85
    pub internal: Sender<InternalCommand>,
86 87 88

    pub syncing: bool,
    tmp_msgs: Vec<TmpMsg>,
89
    shown_messages: usize,
90
    pub last_viewed_messages: HashMap<String, Message>,
91

92 93
    pub username: Option<String>,
    pub uid: Option<String>,
94
    pub avatar: Option<String>,
95
    pub server_url: String,
96

97
    pub autoscroll: bool,
98
    pub active_room: Option<String>,
99
    pub rooms: RoomList,
100
    pub roomlist: widgets::RoomList,
101
    pub load_more_spn: gtk::Spinner,
102
    pub more_members_btn: gtk::Button,
103
    pub unsent_messages: HashMap<String, (String, i32)>,
104

105 106 107 108 109
    pub highlighted_entry: Vec<String>,
    pub popover_position: Option<i32>,
    pub popover_search: Option<String>,
    pub popover_closing: bool,

110
    pub state: AppState,
111
    pub since: Option<String>,
112
    pub member_limit: usize,
113 114

    pub logged_in: bool,
115
    pub loading_more: bool,
116 117

    pub invitation_roomid: Option<String>,
Christopher Davis's avatar
Christopher Davis committed
118
    pub md_enabled: bool,
119 120 121 122
    invite_list: Vec<Member>,
    search_type: SearchType,
}

123 124
impl PasswordStorage for AppOp {}

125 126 127 128
#[derive(Debug, Clone)]
pub enum SearchType {
    Invite,
    DirectChat,
129 130
}

131
#[derive(Debug, Clone)]
132
pub enum MsgPos {
133 134 135 136
    Top,
    Bottom,
}

137
#[derive(Debug, Clone)]
138
pub enum RoomPanel {
139 140 141 142 143
    Room,
    NoRoom,
    Loading,
}

144

145
#[derive(Debug, Clone)]
146 147 148 149 150 151 152
pub enum AppState {
    Login,
    Chat,
    Directory,
    Loading,
}

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
static mut OP: Option<Arc<Mutex<AppOp>>> = None;

macro_rules! APPOP {
    ($fn: ident, ($($x:ident),*) ) => {{
        if let Some(ctx) = glib::MainContext::default() {
            ctx.invoke(move || {
                $( let $x = $x.clone(); )*
                if let Some(op) = AppOp::def() {
                    op.lock().unwrap().$fn($($x),*);
                }
            });
        }
    }};
    ($fn: ident) => {{
        APPOP!($fn, ( ) );
    }}
}

171
impl AppOp {
172 173 174 175 176 177 178 179 180
    pub fn def() -> Option<Arc<Mutex<AppOp>>> {
        unsafe {
            match OP {
                Some(ref m) => Some(m.clone()),
                None => None,
            }
        }
    }

181
    pub fn new(app: gtk::Application,
182
               ui: uibuilder::UI,
183 184
               tx: Sender<BKCommand>,
               itx: Sender<InternalCommand>) -> AppOp {
185
        AppOp {
186
            ui: ui,
187
            gtk_app: app,
188
            load_more_spn: gtk::Spinner::new(),
189
            more_members_btn: gtk::Button::new_with_label("Load more members"),
190
            backend: tx,
191
            internal: itx,
192
            autoscroll: true,
193
            active_room: None,
194
            rooms: HashMap::new(),
195 196
            username: None,
            uid: None,
197
            avatar: None,
198
            server_url: String::from("https://matrix.org"),
199 200
            syncing: false,
            tmp_msgs: vec![],
201
            shown_messages: 0,
202
            last_viewed_messages: HashMap::new(),
203
            state: AppState::Login,
204
            roomlist: widgets::RoomList::new(None),
205
            since: None,
206
            member_limit: 50,
207
            unsent_messages: HashMap::new(),
208

209 210 211 212 213
            highlighted_entry: vec![],
            popover_position: None,
            popover_search: None,
            popover_closing: false,

214
            logged_in: false,
215
            loading_more: false,
216

Christopher Davis's avatar
Christopher Davis committed
217
            md_enabled: false,
218
            invitation_roomid: None,
219
            invite_list: vec![],
220
            search_type: SearchType::Invite,
221 222 223
        }
    }

224 225
    pub fn initial_sync(&self, show: bool) {
        if show {
226
            self.inapp_notify("Initial sync, this can take some time");
227
        } else {
228
            self.hide_inapp_notify();
229 230 231
        }
    }

232
    pub fn bk_login(&mut self, uid: String, token: String) {
233
        self.logged_in = true;
234
        self.clean_login();
235 236 237
        if let Err(_) = self.store_token(uid.clone(), token) {
            println!("Error: Can't store the token using libsecret");
        }
238 239 240

        self.set_state(AppState::Chat);
        self.set_uid(Some(uid.clone()));
241 242
        /* Do we need to set the username to uid
        self.set_username(Some(uid));*/
243
        self.get_username();
244 245 246 247

        // initial sync, we're shoing some feedback to the user
        self.initial_sync(true);

248 249 250 251 252 253
        self.sync();

        self.init_protocols();
    }

    pub fn bk_logout(&mut self) {
254 255 256 257 258
        self.set_rooms(&vec![], None);
        if let Err(_) = cache::destroy() {
            println!("Error removing cache file");
        }

259
        self.logged_in = false;
260
        self.syncing = false;
261 262 263 264

        self.set_state(AppState::Login);
        self.set_uid(None);
        self.set_username(None);
265 266 267 268 269 270 271 272 273 274
        self.set_avatar(None);

        // stoping the backend and starting again, we don't want to receive more messages from
        // backend
        self.backend.send(BKCommand::ShutDown).unwrap();

        let (tx, rx): (Sender<BKResponse>, Receiver<BKResponse>) = channel();
        let bk = Backend::new(tx);
        self.backend = bk.run();
        backend_loop(rx);
275 276 277
    }

    pub fn update_rooms(&mut self, rooms: Vec<Room>, default: Option<Room>) {
278 279 280
        let rs: Vec<Room> = rooms.iter().filter(|x| !x.left).cloned().collect();
        self.set_rooms(&rs, default);

281 282 283 284
        // uploading each room avatar
        for r in rooms.iter() {
            self.backend.send(BKCommand::GetRoomAvatar(r.id.clone())).unwrap();
        }
285
    }
286

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    pub fn new_rooms(&mut self, rooms: Vec<Room>) {
        // ignoring existing rooms
        let rs: Vec<&Room> = rooms.iter().filter(|x| !self.rooms.contains_key(&x.id) && !x.left).collect();

        for r in rs {
            self.rooms.insert(r.id.clone(), r.clone());
            self.roomlist.add_room(r.clone());
            self.roomlist.moveup(r.id.clone());
        }

        // removing left rooms
        let rs: Vec<&Room> = rooms.iter().filter(|x| x.left).collect();
        for r in rs {
            if r.id == self.active_room.clone().unwrap_or_default() {
                self.really_leave_active_room();
            } else {
303
                self.remove_room(r.id.clone());
304 305
            }
        }
306 307
    }

308 309 310
    pub fn remove_room(&mut self, id: String) {
        self.rooms.remove(&id);
        self.roomlist.remove_room(id.clone());
311
        self.unsent_messages.remove(&id);
312 313
    }

314
    pub fn clear_room_notifications(&mut self, r: String) {
315 316
        self.set_room_notifications(r.clone(), 0, 0);
        self.roomlist.set_bold(r, false);
317 318
    }

319 320 321 322 323 324 325 326 327 328
    pub fn set_state(&mut self, state: AppState) {
        self.state = state;

        let widget_name = match self.state {
            AppState::Login => "login",
            AppState::Chat => "chat",
            AppState::Directory => "directory",
            AppState::Loading => "loading",
        };

329
        self.ui.builder
330 331 332 333
            .get_object::<gtk::Stack>("main_content_stack")
            .expect("Can't find main_content_stack in ui file.")
            .set_visible_child_name(widget_name);

334 335 336 337 338 339 340 341
        //setting headerbar
        let bar_name = match self.state {
            AppState::Login => "login",
            AppState::Directory => "back",
            AppState::Loading => "login",
            _ => "normal",
        };

342
        self.ui.builder
343 344 345
            .get_object::<gtk::Stack>("headerbar_stack")
            .expect("Can't find headerbar_stack in ui file.")
            .set_visible_child_name(bar_name);
346 347 348 349 350 351 352 353 354

        //set focus for views
        let widget_focus = match self.state {
            AppState::Login => "login_username",
            AppState::Directory => "directory_search_entry",
            _ => "",
        };

        if widget_focus != "" {
355
            self.ui.builder
356 357 358 359
                .get_object::<gtk::Widget>(widget_focus)
                .expect("Can't find widget to set focus in ui file.")
                .grab_focus();
        }
360 361
    }

362 363 364
    pub fn escape(&mut self) {
        if let AppState::Chat = self.state {
            self.room_panel(RoomPanel::NoRoom);
365
            self.active_room = None;
366
            self.clear_tmp_msgs();
367 368 369
        }
    }

370
    pub fn clean_login(&self) {
371
        let user_entry: gtk::Entry = self.ui.builder
372 373
            .get_object("login_username")
            .expect("Can't find login_username in ui file.");
374
        let pass_entry: gtk::Entry = self.ui.builder
375 376
            .get_object("login_password")
            .expect("Can't find login_password in ui file.");
377
        let server_entry: gtk::Entry = self.ui.builder
378 379
            .get_object("login_server")
            .expect("Can't find login_server in ui file.");
380
        let idp_entry: gtk::Entry = self.ui.builder
381 382 383 384 385 386 387 388 389
            .get_object("login_idp")
            .expect("Can't find login_idp in ui file.");

        user_entry.set_text("");
        pass_entry.set_text("");
        server_entry.set_text("https://matrix.org");
        idp_entry.set_text("https://vector.im");
    }

390
    pub fn login(&mut self) {
391
        let user_entry: gtk::Entry = self.ui.builder
392
            .get_object("login_username")
393
            .expect("Can't find login_username in ui file.");
394
        let pass_entry: gtk::Entry = self.ui.builder
395
            .get_object("login_password")
396
            .expect("Can't find login_password in ui file.");
397
        let server_entry: gtk::Entry = self.ui.builder
398
            .get_object("login_server")
399
            .expect("Can't find login_server in ui file.");
400
        let login_error: gtk::Label = self.ui.builder
401 402
            .get_object("login_error_msg")
            .expect("Can't find login_error_msg in ui file.");
403

404
        let username = user_entry.get_text();
405
        let password = pass_entry.get_text();
406

407 408 409 410 411 412 413 414 415 416 417
        if username.clone().unwrap_or_default().is_empty() ||
           password.clone().unwrap_or_default().is_empty() {
            login_error.set_text("Invalid username or password");
            login_error.show();
            return;
        } else {
            login_error.set_text("Unknown Error");
            login_error.hide();
        }

        self.set_state(AppState::Loading);
418
        self.since = None;
419 420 421
        self.connect(username, password, server_entry.get_text());
    }

422
    pub fn set_login_pass(&self, username: &str, password: &str, server: &str) {
423
        let user_entry: gtk::Entry = self.ui.builder
424 425
            .get_object("login_username")
            .expect("Can't find login_username in ui file.");
426
        let pass_entry: gtk::Entry = self.ui.builder
427 428
            .get_object("login_password")
            .expect("Can't find login_password in ui file.");
429
        let server_entry: gtk::Entry = self.ui.builder
430 431 432 433 434 435 436 437
            .get_object("login_server")
            .expect("Can't find login_server in ui file.");

        user_entry.set_text(username);
        pass_entry.set_text(password);
        server_entry.set_text(server);
    }

438
    #[allow(dead_code)]
439
    pub fn register(&mut self) {
440
        let user_entry: gtk::Entry = self.ui.builder
441
            .get_object("register_username")
442
            .expect("Can't find register_username in ui file.");
443
        let pass_entry: gtk::Entry = self.ui.builder
444
            .get_object("register_password")
445
            .expect("Can't find register_password in ui file.");
446
        let pass_conf: gtk::Entry = self.ui.builder
447
            .get_object("register_password_confirm")
448
            .expect("Can't find register_password_confirm in ui file.");
449
        let server_entry: gtk::Entry = self.ui.builder
450
            .get_object("register_server")
451 452
            .expect("Can't find register_server in ui file.");

453 454 455 456 457 458 459 460 461 462 463 464
        let username = match user_entry.get_text() {
            Some(s) => s,
            None => String::from(""),
        };
        let password = match pass_entry.get_text() {
            Some(s) => s,
            None => String::from(""),
        };
        let passconf = match pass_conf.get_text() {
            Some(s) => s,
            None => String::from(""),
        };
465 466

        if password != passconf {
467
            self.show_error("Passwords didn't match, try again".to_string());
468 469 470
            return;
        }

471
        self.server_url = match server_entry.get_text() {
472
            Some(s) => s,
473
            None => String::from("https://matrix.org"),
474 475 476 477 478 479 480 481 482 483
        };

        //self.store_pass(username.clone(), password.clone(), server_url.clone())
        //    .unwrap_or_else(|_| {
        //        // TODO: show an error
        //        println!("Error: Can't store the password using libsecret");
        //    });

        let uname = username.clone();
        let pass = password.clone();
484
        let ser = self.server_url.clone();
485 486 487
        self.backend.send(BKCommand::Register(uname, pass, ser)).unwrap();
    }

488 489
    pub fn connect(&mut self, username: Option<String>, password: Option<String>, server: Option<String>) -> Option<()> {
        self.server_url = match server {
490
            Some(s) => s,
491
            None => String::from("https://matrix.org"),
492 493
        };

494
        self.store_pass(username.clone()?, password.clone()?, self.server_url.clone())
495 496 497 498 499
            .unwrap_or_else(|_| {
                // TODO: show an error
                println!("Error: Can't store the password using libsecret");
            });

500 501
        let uname = username?;
        let pass = password?;
502
        let ser = self.server_url.clone();
503
        self.backend.send(BKCommand::Login(uname, pass, ser)).unwrap();
504
        Some(())
505
    }
506

507 508 509 510 511 512 513 514 515 516 517
    pub fn set_token(&mut self, token: Option<String>, uid: Option<String>, server: Option<String>) -> Option<()> {
        self.server_url = match server {
            Some(s) => s,
            None => String::from("https://matrix.org"),
        };

        let ser = self.server_url.clone();
        self.backend.send(BKCommand::SetToken(token?, uid?, ser)).unwrap();
        Some(())
    }

518
    #[allow(dead_code)]
519 520
    pub fn connect_guest(&mut self, server: Option<String>) {
        self.server_url = match server {
521
            Some(s) => s,
522
            None => String::from("https://matrix.org"),
523 524
        };

525
        self.backend.send(BKCommand::Guest(self.server_url.clone())).unwrap();
526 527 528
    }

    pub fn get_username(&self) {
529 530
        self.backend.send(BKCommand::GetUsername).unwrap();
        self.backend.send(BKCommand::GetAvatar).unwrap();
531 532
    }

533
    pub fn show_user_info (&self) {
534
        let stack = self.ui.builder
535 536
            .get_object::<gtk::Stack>("user_info")
            .expect("Can't find user_info_avatar in ui file.");
537

538 539
        /* Show user infos inside the popover but wait for all data to arrive */
        if self.avatar.is_some() && self.username.is_some() && self.uid.is_some() {
540
            let avatar = self.ui.builder
541 542 543
                .get_object::<gtk::Container>("user_info_avatar")
                .expect("Can't find user_info_avatar in ui file.");

544
            let name = self.ui.builder
545 546 547
                .get_object::<gtk::Label>("user_info_username")
                .expect("Can't find user_info_avatar in ui file.");

548
            let uid = self.ui.builder
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
                .get_object::<gtk::Label>("user_info_uid")
                .expect("Can't find user_info_avatar in ui file.");

            uid.set_text(&self.uid.clone().unwrap_or_default());
            name.set_text(&self.username.clone().unwrap_or_default());

            /* remove all old avatar from the popover */
            for w in avatar.get_children().iter() {
                avatar.remove(w);
            }

            let w = widgets::Avatar::circle_avatar(self.avatar.clone().unwrap_or_default(), Some(40));
            avatar.add(&w);
            stack.set_visible_child_name("info");
        }
        else {
            stack.set_visible_child_name("spinner");
        }
567

568
        /* update user menu button avatar */
569
        let button = self.ui.builder
570 571
            .get_object::<gtk::MenuButton>("user_menu_button")
            .expect("Can't find user_menu_button in ui file.");
572

573
        let eb = gtk::EventBox::new();
574 575
            match self.avatar.clone() {
                Some(s) => {
576
                    let w = widgets::Avatar::circle_avatar(s.clone(), Some(24));
577 578
                    eb.add(&w);
                }
579 580 581 582 583 584 585 586
            None => {
                let w = gtk::Spinner::new();
                w.show();
                w.start();
                eb.add(&w);
            }
        };

587 588
        eb.connect_button_press_event(move |_, _| { Inhibit(false) });
        button.set_image(&eb);
589
    }
590

591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
    pub fn set_username(&mut self, username: Option<String>) {
        self.username = username;
        self.show_user_info();
    }

    pub fn set_uid(&mut self, uid: Option<String>) {
        self.uid = uid;
        self.show_user_info();
    }

    pub fn set_avatar(&mut self, fname: Option<String>) {
        self.avatar = fname;
        self.show_user_info();
    }

606
    pub fn disconnect(&self) {
607
        self.backend.send(BKCommand::ShutDown).unwrap();
608
    }
609

610
    pub fn logout(&mut self) {
611
        let _ = self.delete_pass("fractal");
612
        self.backend.send(BKCommand::Logout).unwrap();
613
        self.bk_logout();
614 615
    }

616
    pub fn init(&mut self) {
617 618
        self.set_state(AppState::Loading);

619 620
        if let Ok(data) = cache::load() {
            let r: Vec<Room> = data.rooms.values().cloned().collect();
621
            self.set_rooms(&r, None);
622
            self.last_viewed_messages = data.last_viewed_messages;
623 624 625
            self.since = Some(data.since);
            self.username = Some(data.username);
            self.uid = Some(data.uid);
626 627
        }

628
        if let Ok(pass) = self.get_pass() {
629 630 631
            if let Ok((token, uid)) = self.get_token() {
                self.set_token(Some(token), Some(uid), Some(pass.2));
            } else {
632
                self.set_login_pass(&pass.0, &pass.1, &pass.2);
633 634
                self.connect(Some(pass.0), Some(pass.1), Some(pass.2));
            }
635
        } else {
636
            self.set_state(AppState::Login);
637 638
        }
    }
639

640
    pub fn room_panel(&self, t: RoomPanel) {
641
        let s = self.ui.builder
642 643
            .get_object::<gtk::Stack>("room_view_stack")
            .expect("Can't find room_view_stack in ui file.");
644
        let headerbar = self.ui.builder
645 646
            .get_object::<gtk::HeaderBar>("room_header_bar")
            .expect("Can't find room_header_bar in ui file.");
647

648 649 650 651 652 653 654
        let v = match t {
            RoomPanel::Loading => "loading",
            RoomPanel::Room => "room_view",
            RoomPanel::NoRoom => "noroom",
        };

        s.set_visible_child_name(v);
655 656 657

        match v {
            "noroom" => {
658 659 660
                for ch in headerbar.get_children().iter() {
                    ch.hide();
                }
661
                self.roomlist.set_selected(None);
662
            },
663
            "room_view" => {
664 665 666
                for ch in headerbar.get_children().iter() {
                    ch.show();
                }
667

668
                let msg_entry: gtk::Entry = self.ui.builder
669 670 671
                    .get_object("msg_entry")
                    .expect("Couldn't find msg_entry in ui file.");
                msg_entry.grab_focus();
672 673 674 675 676 677 678

                let active_room_id = self.active_room.clone().unwrap_or_default();
                let msg = self.unsent_messages
                    .get(&active_room_id).cloned()
                    .unwrap_or((String::new(), 0));
                msg_entry.set_text(&msg.0);
                msg_entry.set_position(msg.1);
679
            },
680
            _ => {
681 682 683
                for ch in headerbar.get_children().iter() {
                    ch.show();
                }
684 685
            }
        }
686 687
    }

688
    pub fn sync(&mut self) {
689
        if !self.syncing && self.logged_in {
690 691 692
            self.syncing = true;
            self.backend.send(BKCommand::Sync).unwrap();
        }
693
    }
694

695
    pub fn synced(&mut self, since: Option<String>) {
696 697
        self.syncing = false;
        self.since = since;
698
        self.sync();
699
        self.initial_sync(false);
700 701 702 703 704
    }

    pub fn sync_error(&mut self) {
        self.syncing = false;
        self.sync();
705 706
    }

707
    pub fn set_rooms(&mut self, rooms: &Vec<Room>, def: Option<Room>) {
708
        let container: gtk::Box = self.ui.builder
709 710
            .get_object("room_container")
            .expect("Couldn't find room_container in ui file.");
711

712
        let selected_room = self.roomlist.get_selected();
713

714
        self.rooms.clear();
715 716 717
        for ch in container.get_children().iter() {
            container.remove(ch);
        }
718

719
        for r in rooms.iter() {
720
            self.rooms.insert(r.id.clone(), r.clone());
721 722
        }

723
        self.roomlist = widgets::RoomList::new(Some(self.server_url.clone()));
724
        self.roomlist.add_rooms(rooms.iter().cloned().collect());
725
        container.add(&self.roomlist.widget());
726
        self.roomlist.set_selected(selected_room);
727

728
        let bk = self.internal.clone();
729
        self.roomlist.connect(move |room| {
730
            bk.send(InternalCommand::SelectRoom(room)).unwrap();
731
        });
732 733 734 735
        let bk = self.backend.clone();
        self.roomlist.connect_fav(move |room, tofav| {
            bk.send(BKCommand::AddToFav(room.id.clone(), tofav)).unwrap();
        });
736

737
        let mut godef = def;
738 739
        if let Some(aroom) = self.active_room.clone() {
            if let Some(r) = self.rooms.get(&aroom) {
740 741 742 743 744
                godef = Some(r.clone());
            }
        }

        if let Some(d) = godef {
745
            self.set_active_room_by_id(d.id.clone());
746
        } else {
747
            self.set_state(AppState::Chat);
748
            self.room_panel(RoomPanel::NoRoom);
749
            self.active_room = None;
750
            self.clear_tmp_msgs();
751
        }
752 753

        self.cache_rooms();
754 755
    }

756
    pub fn cache_rooms(&self) {
757
        // serializing rooms
758
        if let Err(_) = cache::store(&self.rooms, self.last_viewed_messages.clone(), self.since.clone().unwrap_or_default(), self.username.clone().unwrap_or_default(), self.uid.clone().unwrap_or_default()) {
759 760
            println!("Error caching rooms");
        };
761 762
    }

763
    pub fn reload_rooms(&mut self) {
764
        self.set_state(AppState::Chat);
765 766
    }

767
    pub fn remove_messages(&mut self) {
768
        let messages = self.ui.builder
769 770
            .get_object::<gtk::ListBox>("message_list")
            .expect("Can't find message_list in ui file.");
771 772
        for ch in messages.get_children().iter().skip(1) {
            messages.remove(ch);
773
        }
774 775
    }

776 777 778 779 780 781 782
    pub fn set_active_room_by_id(&mut self, roomid: String) {
        let mut room = None;
        if let Some(r) = self.rooms.get(&roomid) {
            room = Some(r.clone());
        }

        if let Some(r) = room {
783
            if r.inv {
784
                self.show_inv_dialog(&r);
785 786 787
                return;
            }

788 789 790 791
            self.set_active_room(&r);
        }
    }

792
    pub fn show_inv_dialog(&mut self, r: &Room) {
793
        let dialog = self.ui.builder
794 795 796 797
            .get_object::<gtk::MessageDialog>("invite_dialog")
            .expect("Can't find invite_dialog in ui file.");

        let room_name = r.name.clone().unwrap_or_default();
798
        let title = format!("Join {}?", room_name);
799 800
        let secondary;
        if let Some(ref sender) = r.inv_sender {
801
            let sender_name = sender.get_alias();
802
            secondary = format!("You've been invited to join to <b>{}</b> room by <b>{}</b>",
803 804
                                     room_name, sender_name);
        } else {
805
            secondary = format!("You've been invited to join to <b>{}</b>", room_name);
806 807 808
        }

        dialog.set_property_text(Some(&title));
809
        dialog.set_property_secondary_use_markup(true);
810 811
        dialog.set_property_secondary_text(Some(&secondary));

812
        self.invitation_roomid = Some(r.id.clone());
813 814 815
        dialog.present();
    }

816 817 818 819 820 821 822 823 824 825 826
    pub fn accept_inv(&mut self, accept: bool) {
        if let Some(ref rid) = self.invitation_roomid {
            match accept {
                true => self.backend.send(BKCommand::AcceptInv(rid.clone())).unwrap(),
                false => self.backend.send(BKCommand::RejectInv(rid.clone())).unwrap(),
            }
            self.internal.send(InternalCommand::RemoveInv(rid.clone())).unwrap();
        }
        self.invitation_roomid = None;
    }

827 828 829 830 831
    pub fn remove_inv(&mut self, roomid: String) {
        self.rooms.remove(&roomid);
        self.roomlist.remove_room(roomid);
    }

832 833 834 835 836 837
    pub fn search_invite_user(&self, term: Option<String>) {
        if let Some(t) = term {
            self.backend.send(BKCommand::UserSearch(t)).unwrap();
        }
    }

838 839 840
    pub fn user_search_finished(&self, users: Vec<Member>) {
        match self.search_type {
            SearchType::Invite => {
841
                let listbox = self.ui.builder
842 843
                    .get_object::<gtk::ListBox>("user_search_box")
                    .expect("Can't find user_search_box in ui file.");
844
                let scroll = self.ui.builder
845 846 847 848 849
                    .get_object::<gtk::Widget>("user_search_scroll")
                    .expect("Can't find user_search_scroll in ui file.");
                self.search_finished(users, listbox, scroll);
            },
            SearchType::DirectChat => {
850
                let listbox = self.ui.builder
851 852
                    .get_object::<gtk::ListBox>("direct_chat_search_box")
                    .expect("Can't find direct_chat_search_box in ui file.");
853
                let scroll = self.ui.builder
854 855 856 857 858 859
                    .get_object::<gtk::Widget>("direct_chat_search_scroll")
                    .expect("Can't find direct_chat_search_scroll in ui file.");
                self.search_finished(users, listbox, scroll);
            }
        }
    }
860

861 862 863
    pub fn search_finished(&self, users: Vec<Member>,
                           listbox: gtk::ListBox,
                           scroll: gtk::Widget) {
864 865 866
        for ch in listbox.get_children().iter() {
            listbox.remove(ch);
        }
867
        scroll.hide();
868 869 870 871 872 873 874 875

        for (i, u) in users.iter().enumerate() {
            let w;
            {
                let mb = widgets::MemberBox::new(u, &self);
                w = mb.widget(true);
            }

876 877 878
            let tx = self.internal.clone();
            w.connect_button_press_event(clone!(u => move |_, _| {
                tx.send(InternalCommand::ToInvite(u.clone())).unwrap();
879 880 881 882
                glib::signal::Inhibit(true)
            }));

            listbox.insert(&w, i as i32);
883
            scroll.show();
884 885 886
        }
    }

887
    pub fn close_invite_dialog(&mut self) {
888
        let listbox = self.ui.builder
889 890
            .get_object::<gtk::ListBox>("user_search_box")
            .expect("Can't find user_search_box in ui file.");
891
        let scroll = self.ui.builder
892 893
            .get_object::<gtk::Widget>("user_search_scroll")
            .expect("Can't find user_search_scroll in ui file.");
894
        let to_invite = self.ui.builder
895 896
            .get_object::<gtk::ListBox>("to_invite")
            .expect("Can't find to_invite in ui file.");
897
        let entry = self.ui.builder
898 899
            .get_object::<gtk::Entry>("invite_entry")
            .expect("Can't find invite_entry in ui file.");
900
        let dialog = self.ui.builder
901 902 903
            .get_object::<gtk::Dialog>("invite_user_dialog")
            .expect("Can't find invite_user_dialog in ui file.");

904
        self.invite_list = vec![];
905 906 907 908 909 910
        for ch in to_invite.get_children().iter() {
            to_invite.remove(ch);
        }
        for ch in listbox.get_children().iter() {
            listbox.remove(ch);
        }
911
        scroll.hide();
912 913
        entry.set_text("");
        dialog.hide();
914
        dialog.resize(300, 200);
915 916
    }

917 918 919
    pub fn invite(&mut self) {
        if let &Some(ref r) = &self.active_room {
            for user in &self.invite_list {
920
                self.backend.send(BKCommand::Invite(r.clone(), user.uid.clone())).unwrap();
921
            }
922 923 924 925
        }
        self.close_invite_dialog();
    }

926
    pub fn close_direct_chat_dialog(&mut self) {
927
        let listbox = self.ui.builder
928 929
            .get_object::<gtk::ListBox>("direct_chat_search_box")
            .expect("Can't find direct_chat_search_box in ui file.");
930
        let scroll = self.ui.builder
931 932
            .get_object::<gtk::Widget>("direct_chat_search_scroll")
            .expect("Can't find direct_chat_search_scroll in ui file.");
933
        let to_invite = self.ui.builder
934 935
            .get_object::<gtk::ListBox>("to_chat")
            .expect("Can't find to_chat in ui file.");
936
        let entry = self.ui.builder
937 938
            .get_object::<gtk::Entry>("to_chat_entry")
            .expect("Can't find to_chat_entry in ui file.");
939
        let dialog = self.ui.builder
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
            .get_object::<gtk::Dialog>("direct_chat_dialog")
            .expect("Can't find direct_chat_dialog in ui file.");

        self.invite_list = vec![];
        for ch in to_invite.get_children().iter() {
            to_invite.remove(ch);
        }
        for ch in listbox.get_children().iter() {
            listbox.remove(ch);
        }
        scroll.hide();
        entry.set_text("");
        dialog.hide();
        dialog.resize(300, 200);
    }

    pub fn start_chat(&mut self) {
        if self.invite_list.len() != 1 {
            return;
        }

        let user = self.invite_list[0].clone();

        let internal_id: String = thread_rng().gen_ascii_chars().take(10).collect();
        self.backend.send(BKCommand::DirectChat(user.clone(), internal_id.clone())).unwrap();
        self.close_direct_chat_dialog();

        let mut fakeroom = Room::new(internal_id.clone(), user.alias.clone());
        fakeroom.direct = true;

        self.new_room(fakeroom, None);
        self.roomlist.set_selected(Some(internal_id.clone()));
        self.set_active_room_by_id(internal_id);
        self.room_panel(RoomPanel::Loading);
    }

976
    pub fn set_active_room(&mut self, room: &Room) {
977
        self.member_limit = 50;
978
        self.room_panel(RoomPanel::Loading);
979

980
        let msg_entry: gtk::Entry = self.ui.builder
981 982 983 984 985 986 987 988 989 990 991
            .get_object("msg_entry")
            .expect("Couldn't find msg_entry in ui file.");
        if let Some(msg) = msg_entry.get_text() {
            let active_room_id = self.active_room.clone().unwrap_or_default();
            if msg.len() > 0 {
                self.unsent_messages.insert(active_room_id, (msg, msg_entry.get_position()));
            } else {
                self.unsent_messages.remove(&active_room_id);
            }
        }

992
        self.active_room = Some(room.id.clone());
993
        self.clear_tmp_msgs();
994
        self.autoscroll = true;
995 996

        self.remove_messages();
997

998
        let mut getmessages = true;
999
        self.shown_messages = 0;
1000

1001 1002 1003 1004 1005 1006 1007
        let msgs = room.messages.iter().rev()
                                .take(globals::INITIAL_MESSAGES)
                                .collect::<Vec<&Message>>();
        for (i, msg) in msgs.iter().enumerate() {
            let command = InternalCommand::AddRoomMessage((*msg).clone(),
                                                          MsgPos::Top,
                                                          None,
1008 1009
                                                          i == msgs.len() - 1,
                                                          self.is_last_viewed(&msg));
1010
            self.internal.send(command).unwrap();
1011
        }
1012 1013
        self.internal.send(InternalCommand::SetPanel(RoomPanel::Room)).unwrap();

1014 1015 1016
        if !room.messages.is_empty() {
            getmessages = false;
            if let Some(msg) = room.messages.iter().last() {
1017
                self.mark_as_read(msg, Force(false));
1018 1019 1020
            }
        }

1021 1022
        // getting room details
        self.backend.send(BKCommand::SetRoom(room.clone())).unwrap();
1023
        self.reload_members();
1024

1025 1026
        self.set_room_topic_label(room.topic.clone());

1027
        let name_label = self.ui.builder
1028 1029
            .get_object::<gtk::Label>("room_name")
            .expect("Can't find room_name in ui file.");
1030
        let edit = self.ui.builder
1031 1032
            .get_object::<gtk::Entry>("room_name_entry")
            .expect("Can't find room_name_entry in ui file.");
1033

1034 1035
        name_label.set_text(&room.name.clone().unwrap_or_default());
        edit.set_text(&room.name.clone().unwrap_or_default());
1036

1037 1038 1039 1040 1041 1042 1043 1044
        let mut size = 24;
        if let Some(r) = room.topic.clone() {
            if !r.is_empty() {
                size = 16;
            }
        }

        self.set_current_room_avatar(room.avatar.clone(), size);
1045
        let id = self.ui.builder
1046 1047 1048 1049 1050
            .get_object::<gtk::Label>("room_id")
            .expect("Can't find room_id in ui file.");
        id.set_text(&room.id.clone());
        self.set_current_room_detail(String::from("m.room.name"), room.name.clone());
        self.set_current_room_detail(String::from("m.room.topic"), room.topic.clone());
1051

1052
        if getmessages {
1053
            self.backend.send(BKCommand::GetRoomMessages(self.active_room.clone().unwrap_or_default())).unwrap();
1054
        }
1055 1056
    }

1057 1058
    /// This function is used to mark as read the last message of a room when the focus comes in,
    /// so we need to force the mark_as_read because the window isn't active yet
1059
    pub fn mark_active_room_messages(&mut self) {
1060
        let mut msg: Option<Message> = None;
1061

1062 1063 1064 1065
        if let Some(ref active_room_id) = self.active_room {
            if let Some(ref r) = self.rooms.get(active_room_id) {
                if let Some(m) = r.messages.last() {
                    msg = Some(m.clone());
1066 1067 1068
                }
            }
        }
1069 1070 1071 1072 1073 1074

        // this is done here because in the above we've a reference to self and mark as read needs
        // a mutable reference to self so we can't do it inside
        if let Some(m) = msg {
            self.mark_as_read(&m, Force(true));
        }
1075 1076
    }

1077
    pub fn set_room_detail(&mut self, roomid: String, key: String, value: Option<String>) {
1078 1079 1080 1081 1082 1083 1084 1085 1086
        if let Some(r) = self.rooms.get_mut(&roomid) {
            let k: &str = &key;
            match k {
                "m.room.name" => { r.name = value.clone(); }
                "m.room.topic" => { r.topic = value.clone(); }
                _ => {}
            };
        }

1087
        if roomid == self.active_room.clone().unwrap_or_default() {
1088 1089 1090 1091
            self.set_current_room_detail(key, value);
        }
    }

1092
    pub fn set_room_avatar(&mut self, roomid: String, avatar: Option<String>) {
1093 1094
        if let Some(r) = self.rooms.get_mut(&roomid) {
            r.avatar = avatar.clone();
1095
            self.roomlist.set_room_avatar(roomid.clone(), r.avatar.clone());
1096 1097
        }

1098
        if roomid == self.active_room.clone().unwrap_or_default() {
1099 1100
            let mut size = 24;
            if let Some(r) = self.rooms.get_mut(&roomid) {
1101
                if !r.clone().topic.unwrap_or_default().is_empty() {
1102 1103 1104 1105
                    size = 16;
                }
            }
            self.set_current_room_avatar(avatar, size);
1106 1107 1108
        }
    }

1109 1110
    pub fn set_current_room_detail(&self, key: String, value: Option<String>) {
        let value = value.unwrap_or_default();
1111 1112 1113
        let k: &str = &key;
        match k {
            "m.room.name" => {
1114
                let name_label = self.ui.builder
1115 1116
                    .get_object::<gtk::Label>("room_name")
                    .expect("Can't find room_name in ui file.");
1117
                let edit = self.ui.builder
1118 1119 1120
                    .get_object::<gtk::Entry>("room_name_entry")
                    .expect("Can't find room_name_entry in ui file.");

1121
                name_label.set_text(&value);
1122
                edit.set_text(&value);