[fractal] Add keyboard shortcuts for easier navigation



commit 5cd2c51b14a09ab24711a424888cf18d9fc921ba
Author: Baptiste Gelez <baptiste gelez xyz>
Date:   Sat May 11 14:44:53 2019 +0100

    Add keyboard shortcuts for easier navigation
    
    - Ctrl+PageUp and Ctrl+PageDown to go to the next/previous room of the list
    - Ctrl+Shift+PageUp and Ctrl+Shift+PageUp to go to the next/previous room with unread messages
    - Ctrl+Home and Ctrl+End to go to the first/last room of the list
    - PageUp and PageDown to go up/down in the history
    
    Fixes #49

 fractal-gtk/src/actions/global.rs        |  79 ++++++++++++++++++-
 fractal-gtk/src/widgets/room_history.rs  |   8 ++
 fractal-gtk/src/widgets/roomlist.rs      | 127 +++++++++++++++++++++++++++++++
 fractal-gtk/src/widgets/scroll_widget.rs |  16 +++-
 4 files changed, 228 insertions(+), 2 deletions(-)
---
diff --git a/fractal-gtk/src/actions/global.rs b/fractal-gtk/src/actions/global.rs
index edd93395..f3c27afb 100644
--- a/fractal-gtk/src/actions/global.rs
+++ b/fractal-gtk/src/actions/global.rs
@@ -78,7 +78,7 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
 
     let shortcuts = SimpleAction::new("shortcuts", None);
     let about = SimpleAction::new("about", None);
-    let quit = gio::SimpleAction::new("quit", None);
+    let quit = SimpleAction::new("quit", None);
 
     let open_room = SimpleAction::new("open-room", glib::VariantTy::new("s").ok());
     let back = SimpleAction::new("back", None);
@@ -90,6 +90,15 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
     // TODO: send file should be a room_history action
     let send_file = SimpleAction::new("send-file", None);
 
+    let previous_room = SimpleAction::new("previous-room", None);
+    let next_room = SimpleAction::new("next-room", None);
+    let prev_unread_room = SimpleAction::new("prev-unread-room", None);
+    let next_unread_room = SimpleAction::new("next-unread-room", None);
+    let first_room = SimpleAction::new("first-room", None);
+    let last_room = SimpleAction::new("last-room", None);
+    let older_messages = SimpleAction::new("older-messages", None);
+    let newer_messages = SimpleAction::new("newer-messages", None);
+
     app.add_action(&settings);
     app.add_action(&account);
     app.add_action(&chat);
@@ -113,6 +122,15 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
 
     app.add_action(&send_file);
 
+    app.add_action(&previous_room);
+    app.add_action(&next_room);
+    app.add_action(&prev_unread_room);
+    app.add_action(&next_unread_room);
+    app.add_action(&first_room);
+    app.add_action(&last_room);
+    app.add_action(&older_messages);
+    app.add_action(&newer_messages);
+
     // When activated, shuts down the application
     let app_weak = app.downgrade();
     quit.connect_activate(move |_action, _parameter| {
@@ -134,6 +152,57 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
     newr.connect_activate(clone!(op => move |_, _| op.lock().unwrap().new_room_dialog() ));
     joinr.connect_activate(clone!(op => move |_, _| op.lock().unwrap().join_to_room_dialog() ));
 
+    previous_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.prev_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    next_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.next_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    prev_unread_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.prev_unread_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    next_unread_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.next_unread_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    first_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.first_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    last_room.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(id) = op.roomlist.last_id() {
+            op.set_active_room_by_id(id);
+        }
+    }));
+    older_messages.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(ref mut hist) = op.history {
+            // println!("page up");
+            hist.page_up();
+        }
+    }));
+    newer_messages.connect_activate(clone!(op => move |_, _| {
+        let mut op = op.lock().unwrap();
+        if let Some(ref mut hist) = op.history {
+            // println!("page down");
+            hist.page_down();
+        }
+    }));
+
     /* Store the history of views so we can go back to it, this will be kept alive by the back
      * callback */
     let back_history: Rc<RefCell<Vec<AppState>>> = Rc::new(RefCell::new(vec![]));
@@ -225,6 +294,14 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
 
     /* Add Keybindings to actions */
     app.set_accels_for_action("app.quit", &["<Ctrl>Q"]);
+    app.set_accels_for_action("app.previous-room", &["<Ctrl>Page_Up"]);
+    app.set_accels_for_action("app.next-room", &["<Ctrl>Page_Down"]);
+    app.set_accels_for_action("app.prev-unread-room", &["<Ctrl><Shift>Page_Up"]);
+    app.set_accels_for_action("app.next-unread-room", &["<Ctrl><Shift>Page_Down"]);
+    app.set_accels_for_action("app.first-room", &["<Ctrl>Home"]);
+    app.set_accels_for_action("app.last-room", &["<Ctrl>End"]);
+    app.set_accels_for_action("app.older-messages", &["Page_Up"]);
+    app.set_accels_for_action("app.newer-messages", &["Page_Down"]);
     app.set_accels_for_action("app.back", &["Escape"]);
 
     // connect mouse back button to app.back action
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index 6e4223a4..897bd26f 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -276,6 +276,14 @@ impl RoomHistory {
     pub fn typing_notification(&mut self, typing_str: &str) {
         self.rows.borrow().view.typing_notification(typing_str);
     }
+
+    pub fn page_up(&mut self) {
+        self.rows.borrow_mut().view.page_up();
+    }
+
+    pub fn page_down(&mut self) {
+        self.rows.borrow_mut().view.page_down();
+    }
 }
 
 /* This function creates the content for a Row based on the conntent of msg */
diff --git a/fractal-gtk/src/widgets/roomlist.rs b/fractal-gtk/src/widgets/roomlist.rs
index c1158cbc..c9132444 100644
--- a/fractal-gtk/src/widgets/roomlist.rs
+++ b/fractal-gtk/src/widgets/roomlist.rs
@@ -318,6 +318,71 @@ impl RoomListGroup {
         }
     }
 
+    /// Find the ID of a room after or before the current one in the list
+    ///
+    /// # Parameters
+    ///
+    /// - `unread_only`: true to only look for rooms with unread messages
+    /// - `direction`: `-1` for the previous room, `+1` for the next
+    ///
+    /// # Return value
+    ///
+    /// `(Room id if found, go to previous group, go to next group)`
+    fn sibling_id(&self, unread_only: bool, direction: i32) -> (Option<String>, bool, bool) {
+        match self.list.get_selected_row() {
+            Some(row) => {
+                let rv = self.roomvec.lock().unwrap();
+                let mut idx = row.get_index() + direction;
+                while unread_only
+                    && 0 <= idx
+                    && (idx as usize) < rv.len()
+                    && rv[idx as usize].room.notifications == 0
+                {
+                    idx += direction;
+                }
+
+                if 0 <= idx && (idx as usize) < rv.len() {
+                    (Some(rv[idx as usize].room.id.clone()), false, false)
+                } else {
+                    (None, idx < 0, idx >= 0)
+                }
+            }
+            None => (None, false, false),
+        }
+    }
+
+    fn first_id(&self, unread_only: bool) -> Option<String> {
+        self.roomvec
+            .lock()
+            .unwrap()
+            .iter()
+            .filter(|r| {
+                if unread_only {
+                    r.room.notifications > 0
+                } else {
+                    true
+                }
+            })
+            .next()
+            .map(|r| r.room.id.clone())
+    }
+
+    fn last_id(&self, unread_only: bool) -> Option<String> {
+        self.roomvec
+            .lock()
+            .unwrap()
+            .iter()
+            .filter(|r| {
+                if unread_only {
+                    r.room.notifications > 0
+                } else {
+                    true
+                }
+            })
+            .last()
+            .map(|r| r.room.id.clone())
+    }
+
     pub fn add_rooms(&mut self, mut array: Vec<Room>) {
         array.sort_by_key(|ref x| match x.messages.last() {
             Some(l) => l.date,
@@ -460,6 +525,68 @@ impl RoomList {
         run_in_group!(self, &r.to_string(), set_selected, Some(r.to_string()));
     }
 
+    fn sibling_id(&self, unread_only: bool, direction: i32) -> Option<String> {
+        let (room, _, next) = self.inv.get().sibling_id(unread_only, direction);
+
+        if let Some(room) = room {
+            Some(room)
+        } else if next {
+            self.fav.get().first_id(unread_only)
+        } else {
+            let (room, prev, next) = self.fav.get().sibling_id(unread_only, direction);
+
+            if let Some(room) = room {
+                Some(room)
+            } else if prev {
+                self.inv.get().last_id(unread_only)
+            } else if next {
+                self.rooms.get().first_id(unread_only)
+            } else {
+                let (room, prev, _) = self.rooms.get().sibling_id(unread_only, direction);
+
+                if let Some(room) = room {
+                    Some(room)
+                } else if prev {
+                    self.fav.get().last_id(unread_only)
+                } else {
+                    None
+                }
+            }
+        }
+    }
+
+    pub fn next_id(&self) -> Option<String> {
+        self.sibling_id(false, 1)
+    }
+
+    pub fn prev_id(&self) -> Option<String> {
+        self.sibling_id(false, -1)
+    }
+
+    pub fn next_unread_id(&self) -> Option<String> {
+        self.sibling_id(true, 1)
+    }
+
+    pub fn prev_unread_id(&self) -> Option<String> {
+        self.sibling_id(true, -1)
+    }
+
+    pub fn first_id(&self) -> Option<String> {
+        self.inv
+            .get()
+            .first_id(false)
+            .or_else(|| self.fav.get().first_id(false))
+            .or_else(|| self.rooms.get().first_id(false))
+    }
+
+    pub fn last_id(&self) -> Option<String> {
+        self.rooms
+            .get()
+            .last_id(false)
+            .or_else(|| self.fav.get().last_id(false))
+            .or_else(|| self.inv.get().last_id(false))
+    }
+
     pub fn unselect(&self) {
         self.inv.get().set_selected(None);
         self.fav.get().set_selected(None);
diff --git a/fractal-gtk/src/widgets/scroll_widget.rs b/fractal-gtk/src/widgets/scroll_widget.rs
index f848384b..f3b710f4 100644
--- a/fractal-gtk/src/widgets/scroll_widget.rs
+++ b/fractal-gtk/src/widgets/scroll_widget.rs
@@ -20,7 +20,7 @@ enum Position {
 #[allow(dead_code)]
 pub struct ScrollWidget {
     upper: Rc<Cell<f64>>,
-    value: Rc<Cell<f64>>,
+    value: Rc<Cell<f64>>, // FIXME: is it really used anywhere?
     balance: Rc<Cell<Option<Position>>>,
     autoscroll: Rc<Cell<bool>>,
     /* whether a request for more messages has been send or not */
@@ -287,6 +287,20 @@ impl ScrollWidget {
             self.widgets.typing_label.set_markup(typing_str);
         }
     }
+
+    pub fn page_up(&mut self) {
+        if let Some(adj) = self.widgets.view.get_vadjustment() {
+            adj.set_value(adj.get_value() - adj.get_page_size());
+            self.upper.set(adj.get_upper());
+        }
+    }
+
+    pub fn page_down(&mut self) {
+        if let Some(adj) = self.widgets.view.get_vadjustment() {
+            adj.set_value(adj.get_value() + adj.get_page_size());
+            self.upper.set(adj.get_upper());
+        }
+    }
 }
 
 /* Functions to animate the scroll */


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