[fractal/fractal-next] content: Move room history to it's own widget



commit df5408cc8cf2b81993ab3c52b6c6680c176ed516
Author: Julian Sparber <julian sparber net>
Date:   Wed May 19 09:57:06 2021 +0200

    content: Move room history to it's own widget
    
    This will allow us to add different content widgets, e.g. for invites

 data/resources/resources.gresource.xml    |   1 +
 data/resources/ui/content-room-history.ui | 151 +++++++++++++++++++
 data/resources/ui/content.ui              | 145 +------------------
 po/POTFILES.in                            |   2 +
 src/meson.build                           |   1 +
 src/session/content/content.rs            | 118 +--------------
 src/session/content/mod.rs                |   2 +
 src/session/content/room_history.rs       | 233 ++++++++++++++++++++++++++++++
 8 files changed, 399 insertions(+), 254 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index de778fa9..5acbb7e1 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -3,6 +3,7 @@
   <gresource prefix="/org/gnome/FractalNext/">
     <file compressed="true" preprocess="xml-stripblanks" alias="shortcuts.ui">ui/shortcuts.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="content.ui">ui/content.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="content-room-history.ui">ui/content-room-history.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="content-item.ui">ui/content-item.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-message-row.ui">ui/content-message-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="content-divider-row.ui">ui/content-divider-row.ui</file>
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
new file mode 100644
index 00000000..97509615
--- /dev/null
+++ b/data/resources/ui/content-room-history.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ContentRoomHistory" parent="AdwBin">
+    <property name="vexpand">True</property>
+    <property name="hexpand">True</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="AdwHeaderBar" id="headerbar">
+            <property name="show-start-title-buttons" bind-source="ContentRoomHistory" 
bind-property="compact" bind-flags="sync-create"/>
+            <child type="start">
+              <object class="GtkButton" id="back">
+                <property name="visible" bind-source="ContentRoomHistory" bind-property="compact" 
bind-flags="sync-create"/>
+                <property name="icon-name">go-previous-symbolic</property>
+                <property name="action-name">content.go-back</property>
+              </object>
+            </child>
+            <child type="title">
+              <object class="AdwWindowTitle">
+                <binding name="title">
+                  <lookup name="display-name">
+                    <lookup name="room">ContentRoomHistory</lookup>
+                  </lookup>
+                </binding>
+                <binding name="subtitle">
+                  <lookup name="topic">
+                    <lookup name="room">ContentRoomHistory</lookup>
+                  </lookup>
+                </binding>
+              </object>
+            </child>
+            <child type="end">
+              <object class="GtkMenuButton" id="room_menu">
+                <property name="icon-name">view-more-symbolic</property>
+              </object>
+            </child>
+            <child type="end">
+              <object class="GtkToggleButton" id="search_content_button">
+                <property name="icon-name">system-search-symbolic</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSearchBar" id="room_search">
+            <property name="search-mode-enabled" bind-source="search_content_button" bind-property="active"/>
+            <property name="child">
+              <object class="AdwClamp">
+                <property name="hexpand">True</property>
+                <child>
+                  <object class="GtkSearchEntry"/>
+                </child>
+              </object>
+            </property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolled_window">
+            <property name="vexpand">True</property>
+            <property name="hscrollbar-policy">never</property>
+            <style>
+              <class name="content"/>
+            </style>
+            <property name="child">
+              <object class="AdwClampScrollable">
+                <property name="vexpand">True</property>
+                <property name="hexpand">True</property>
+                <property name="child">
+                  <object class="GtkListView" id="listview">
+                    <style>
+                      <class name="navigation-sidebar"/>
+                    </style>
+                    <property name="factory">
+                      <object class="GtkBuilderListItemFactory">
+                        <property name="resource">/org/gnome/FractalNext/content-item.ui</property>
+                      </object>
+                    </property>
+                    <accessibility>
+                      <property name="label" translatable="yes">Room History</property>
+                    </accessibility>
+                  </object>
+                </property>
+              </object>
+            </property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSeparator"/>
+        </child>
+        <child>
+          <object class="AdwClamp">
+            <child>
+              <object class="GtkBox">
+                <property name="spacing">6</property>
+                <style>
+                  <class name="send-message-area"/>
+                </style>
+                <child>
+                  <object class="GtkButton">
+                    <property name="icon-name">mail-attachment-symbolic</property>
+                    <property name="action-name">room-history.select-file</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="markdown_button">
+                    <property name="direction">up</property>
+                    <property name="icon-name">format-justify-left-symbolic</property>
+                    <property name="popover">
+                      <object class="MarkdownPopover">
+                        <property name="markdown-enabled" bind-source="ContentRoomHistory" 
bind-property="markdown-enabled" bind-flags="sync-create | bidirectional"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolled_window">
+                    <property name="vexpand">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="vscrollbar-policy">external</property>
+                    <property name="max-content-height">200</property>
+                    <property name="propagate-natural-height">True</property>
+                    <property name="child">
+                      <object class="GtkSourceView" id="message_entry">
+                        <property name="hexpand">True</property>
+                      </object>
+                    </property>
+                    <style>
+                      <class name="message-entry"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="icon-name">send-symbolic</property>
+                    <property name="focus-on-click">False</property>
+                    <property name="action-name">room-history.send-text-message</property>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index 25395e48..332a6054 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -3,149 +3,16 @@
   <template class="Content" parent="AdwBin">
     <property name="vexpand">True</property>
     <property name="hexpand">True</property>
-    <child>
-      <object class="GtkBox">
-        <property name="orientation">vertical</property>
+    <property name="child">
+      <object class="GtkStack">
         <child>
-          <object class="AdwHeaderBar" id="headerbar">
-            <property name="show-start-title-buttons" bind-source="Content" bind-property="compact" 
bind-flags="sync-create"/>
-            <child type="start">
-              <object class="GtkButton" id="back">
-                <property name="visible" bind-source="Content" bind-property="compact" 
bind-flags="sync-create"/>
-                <property name="icon-name">go-previous-symbolic</property>
-                <property name="action-name">content.go-back</property>
-              </object>
-            </child>
-            <child type="title">
-              <object class="AdwWindowTitle">
-                <binding name="title">
-                  <lookup name="display-name">
-                    <lookup name="room">Content</lookup>
-                  </lookup>
-                </binding>
-                <binding name="subtitle">
-                  <lookup name="topic">
-                    <lookup name="room">Content</lookup>
-                  </lookup>
-                </binding>
-              </object>
-            </child>
-            <child type="end">
-              <object class="GtkMenuButton" id="room_menu">
-                <property name="icon-name">view-more-symbolic</property>
-              </object>
-            </child>
-            <child type="end">
-              <object class="GtkToggleButton" id="search_content_button">
-                <property name="icon-name">system-search-symbolic</property>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSearchBar" id="room_search">
-            <property name="search-mode-enabled" bind-source="search_content_button" bind-property="active"/>
-            <property name="child">
-              <object class="AdwClamp">
-                <property name="hexpand">True</property>
-                <child>
-                  <object class="GtkSearchEntry"/>
-                </child>
-              </object>
-            </property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkScrolledWindow" id="scrolled_window">
-            <property name="vexpand">True</property>
-            <property name="hscrollbar-policy">never</property>
-            <style>
-              <class name="content"/>
-            </style>
-            <property name="child">
-              <object class="AdwClampScrollable">
-                <property name="vexpand">True</property>
-                <property name="hexpand">True</property>
-                <property name="child">
-                  <object class="GtkListView" id="listview">
-                    <style>
-                      <class name="navigation-sidebar"/>
-                    </style>
-                    <property name="factory">
-                      <object class="GtkBuilderListItemFactory">
-                        <property name="resource">/org/gnome/FractalNext/content-item.ui</property>
-                      </object>
-                    </property>
-                    <accessibility>
-                      <property name="label" translatable="yes">Room History</property>
-                    </accessibility>
-                  </object>
-                </property>
-              </object>
-            </property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkSeparator"/>
-        </child>
-        <child>
-          <object class="AdwClamp">
-            <child>
-              <object class="GtkBox">
-                <property name="spacing">6</property>
-                <style>
-                  <class name="send-message-area"/>
-                </style>
-                <child>
-                  <object class="GtkButton">
-                    <property name="icon-name">mail-attachment-symbolic</property>
-                    <property name="action-name">content.select-file</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkMenuButton" id="markdown_button">
-                    <property name="direction">up</property>
-                    <property name="icon-name">format-justify-left-symbolic</property>
-                    <property name="popover">
-                      <object class="MarkdownPopover">
-                        <property name="markdown-enabled" bind-source="Content" 
bind-property="markdown-enabled" bind-flags="sync-create | bidirectional"/>
-                      </object>
-                    </property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkScrolledWindow" id="scrolled_window">
-                    <property name="vexpand">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="vscrollbar-policy">external</property>
-                    <property name="max-content-height">200</property>
-                    <property name="propagate-natural-height">True</property>
-                    <property name="child">
-                      <object class="GtkSourceView" id="message_entry">
-                        <property name="hexpand">True</property>
-                      </object>
-                    </property>
-                    <style>
-                      <class name="message-entry"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkButton">
-                    <property name="icon-name">send-symbolic</property>
-                    <property name="focus-on-click">False</property>
-                    <property name="action-name">content.send-text-message</property>
-                    <style>
-                      <class name="suggested-action"/>
-                    </style>
-                  </object>
-                </child>
-              </object>
-            </child>
+          <object class="ContentRoomHistory">
+            <property name="compact" bind-source="Content" bind-property="compact" bind-flags="sync-create"/>
+            <property name="room" bind-source="Content" bind-property="room" bind-flags="sync-create"/>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e9636663..1ef516a9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,6 +10,7 @@ data/resources/ui/content-item-row-menu.ui
 data/resources/ui/content-item.ui
 data/resources/ui/content-markdown-popover.ui
 data/resources/ui/content-message-row.ui
+data/resources/ui/content-room-history.ui
 data/resources/ui/content-state-row.ui
 data/resources/ui/content.ui
 data/resources/ui/context-menu-bin.ui
@@ -39,6 +40,7 @@ src/session/content/item_row.rs
 src/session/content/markdown_popover.rs
 src/session/content/message_row.rs
 src/session/content/mod.rs
+src/session/content/room_history.rs
 src/session/content/state_row.rs
 src/session/mod.rs
 src/session/room/event.rs
diff --git a/src/meson.build b/src/meson.build
index d40c17af..1606799e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -38,6 +38,7 @@ sources = files(
   'session/content/markdown_popover.rs',
   'session/content/message_row.rs',
   'session/content/mod.rs',
+  'session/content/room_history.rs',
   'session/content/state_row.rs',
   'session/room/event.rs',
   'session/room/highlight_flags.rs',
diff --git a/src/session/content/content.rs b/src/session/content/content.rs
index 5e1ec760..901dce20 100644
--- a/src/session/content/content.rs
+++ b/src/session/content/content.rs
@@ -1,14 +1,9 @@
-use crate::session::{content::ItemRow, content::MarkdownPopover, room::Room};
+use crate::session::{content::RoomHistory, room::Room};
 use adw::subclass::prelude::*;
-use gtk::{
-    gdk, glib, glib::clone, glib::signal::Inhibit, prelude::*, subclass::prelude::*,
-    CompositeTemplate,
-};
-use sourceview::prelude::*;
+use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
 
 mod imp {
     use super::*;
-    use crate::Application;
     use glib::subclass::InitializingObject;
     use std::cell::{Cell, RefCell};
 
@@ -17,17 +12,6 @@ mod imp {
     pub struct Content {
         pub compact: Cell<bool>,
         pub room: RefCell<Option<Room>>,
-        pub md_enabled: Cell<bool>,
-        #[template_child]
-        pub headerbar: TemplateChild<adw::HeaderBar>,
-        #[template_child]
-        pub listview: TemplateChild<gtk::ListView>,
-        #[template_child]
-        pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
-        #[template_child]
-        pub message_entry: TemplateChild<sourceview::View>,
-        #[template_child]
-        pub markdown_button: TemplateChild<gtk::MenuButton>,
     }
 
     #[glib::object_subclass]
@@ -37,17 +21,13 @@ mod imp {
         type ParentType = adw::Bin;
 
         fn class_init(klass: &mut Self::Class) {
-            ItemRow::static_type();
-            MarkdownPopover::static_type();
+            RoomHistory::static_type();
             Self::bind_template(klass);
             klass.set_accessible_role(gtk::AccessibleRole::Group);
 
             klass.install_action("content.go-back", None, move |widget, _, _| {
                 widget.set_room(None);
             });
-            klass.install_action("content.send-text-message", None, move |widget, _, _| {
-                widget.send_text_message();
-            });
         }
 
         fn instance_init(obj: &InitializingObject<Self>) {
@@ -74,13 +54,6 @@ mod imp {
                         Room::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
-                    glib::ParamSpec::new_boolean(
-                        "markdown-enabled",
-                        "Markdown enabled",
-                        "Whether or not to do markdown formatting when sending messages",
-                        false,
-                        glib::ParamFlags::READWRITE,
-                    ),
                 ]
             });
 
@@ -103,15 +76,6 @@ mod imp {
                     let room = value.get().unwrap();
                     obj.set_room(room);
                 }
-                "markdown-enabled" => {
-                    let md_enabled = value.get().unwrap();
-                    self.md_enabled.set(md_enabled);
-                    self.markdown_button.set_icon_name(if md_enabled {
-                        "format-indent-more-symbolic"
-                    } else {
-                        "format-justify-left-symbolic"
-                    });
-                }
                 _ => unimplemented!(),
             }
         }
@@ -120,66 +84,9 @@ mod imp {
             match pspec.name() {
                 "compact" => self.compact.get().to_value(),
                 "room" => obj.room().to_value(),
-                "markdown-enabled" => self.md_enabled.get().to_value(),
                 _ => unimplemented!(),
             }
         }
-
-        fn constructed(&self, obj: &Self::Type) {
-            let adj = self.scrolled_window.vadjustment().unwrap();
-            // TODO: make sure that we have enough messages to fill at least to scroll pages, if the room 
history is long enough
-
-            adj.connect_value_changed(clone!(@weak obj => move |adj| {
-                // Load more message when the user gets close to the end of the known room history
-                // Use the page size twice to detect if the user gets close the end
-                if adj.value() < adj.page_size() * 2.0 {
-                    if let Some(room) = obj.room() {
-                        room.load_previous_events();
-                        }
-                }
-            }));
-
-            let key_events = gtk::EventControllerKey::new();
-            self.message_entry.add_controller(&key_events);
-
-            key_events
-                .connect_key_pressed(clone!(@weak obj => @default-return Inhibit(false), move |_, key, _, 
modifier| {
-                if !modifier.contains(gdk::ModifierType::SHIFT_MASK) && (key == gdk::keys::constants::Return 
|| key == gdk::keys::constants::KP_Enter) {
-                    obj.activate_action("content.send-text-message", None);
-                    Inhibit(true)
-                } else {
-                    Inhibit(false)
-                }
-            }));
-
-            let buffer = self
-                .message_entry
-                .buffer()
-                .downcast::<sourceview::Buffer>()
-                .unwrap();
-
-            buffer.connect_text_notify(clone!(@weak obj => move |buffer| {
-               let (start_iter, end_iter) = buffer.bounds();
-               obj.action_set_enabled("content.send-text-message", start_iter != end_iter);
-            }));
-
-            let (start_iter, end_iter) = buffer.bounds();
-            obj.action_set_enabled("content.send-text-message", start_iter != end_iter);
-
-            let md_lang =
-                sourceview::LanguageManager::default().and_then(|lm| lm.language("markdown"));
-            buffer.set_language(md_lang.as_ref());
-            obj.bind_property("markdown-enabled", &buffer, "highlight-syntax")
-                .flags(glib::BindingFlags::SYNC_CREATE)
-                .build();
-
-            let settings = Application::default().settings();
-            settings
-                .bind("markdown-enabled", obj, "markdown-enabled")
-                .build();
-
-            self.parent_constructed(obj);
-        }
     }
 
     impl WidgetImpl for Content {}
@@ -203,12 +110,6 @@ impl Content {
             return;
         }
 
-        // TODO: use gtk::MultiSelection to allow selection
-        let model = room
-            .as_ref()
-            .and_then(|room| Some(gtk::NoSelection::new(Some(room.timeline()))));
-
-        priv_.listview.set_model(model.as_ref());
         priv_.room.replace(room);
         self.notify("room");
     }
@@ -217,17 +118,4 @@ impl Content {
         let priv_ = imp::Content::from_instance(self);
         priv_.room.borrow().clone()
     }
-
-    pub fn send_text_message(&self) {
-        let priv_ = imp::Content::from_instance(self);
-        let buffer = priv_.message_entry.buffer();
-        let (start_iter, end_iter) = buffer.bounds();
-        let body = buffer.text(&start_iter, &end_iter, true);
-
-        if let Some(room) = &*priv_.room.borrow() {
-            room.send_text_message(body.as_str(), priv_.md_enabled.get());
-        }
-
-        buffer.set_text("");
-    }
 }
diff --git a/src/session/content/mod.rs b/src/session/content/mod.rs
index 40ec26b2..2aa007f0 100644
--- a/src/session/content/mod.rs
+++ b/src/session/content/mod.rs
@@ -3,6 +3,7 @@ mod divider_row;
 mod item_row;
 mod markdown_popover;
 mod message_row;
+mod room_history;
 mod state_row;
 
 pub use self::content::Content;
@@ -10,4 +11,5 @@ use self::divider_row::DividerRow;
 use self::item_row::ItemRow;
 use self::markdown_popover::MarkdownPopover;
 use self::message_row::MessageRow;
+use self::room_history::RoomHistory;
 use self::state_row::StateRow;
diff --git a/src/session/content/room_history.rs b/src/session/content/room_history.rs
new file mode 100644
index 00000000..764bea01
--- /dev/null
+++ b/src/session/content/room_history.rs
@@ -0,0 +1,233 @@
+use crate::session::{content::ItemRow, content::MarkdownPopover, room::Room};
+use adw::subclass::prelude::*;
+use gtk::{
+    gdk, glib, glib::clone, glib::signal::Inhibit, prelude::*, subclass::prelude::*,
+    CompositeTemplate,
+};
+use sourceview::prelude::*;
+
+mod imp {
+    use super::*;
+    use crate::Application;
+    use glib::subclass::InitializingObject;
+    use std::cell::{Cell, RefCell};
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/content-room-history.ui")]
+    pub struct RoomHistory {
+        pub compact: Cell<bool>,
+        pub room: RefCell<Option<Room>>,
+        pub md_enabled: Cell<bool>,
+        #[template_child]
+        pub headerbar: TemplateChild<adw::HeaderBar>,
+        #[template_child]
+        pub listview: TemplateChild<gtk::ListView>,
+        #[template_child]
+        pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
+        #[template_child]
+        pub message_entry: TemplateChild<sourceview::View>,
+        #[template_child]
+        pub markdown_button: TemplateChild<gtk::MenuButton>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for RoomHistory {
+        const NAME: &'static str = "ContentRoomHistory";
+        type Type = super::RoomHistory;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            ItemRow::static_type();
+            MarkdownPopover::static_type();
+            Self::bind_template(klass);
+            klass.set_accessible_role(gtk::AccessibleRole::Group);
+            klass.install_action(
+                "room-history.send-text-message",
+                None,
+                move |widget, _, _| {
+                    widget.send_text_message();
+                },
+            );
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for RoomHistory {
+        fn properties() -> &'static [glib::ParamSpec] {
+            use once_cell::sync::Lazy;
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![
+                    glib::ParamSpec::new_boolean(
+                        "compact",
+                        "Compact",
+                        "Wheter a compact view is used or not",
+                        false,
+                        glib::ParamFlags::READWRITE,
+                    ),
+                    glib::ParamSpec::new_object(
+                        "room",
+                        "Room",
+                        "The room currently shown",
+                        Room::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                    glib::ParamSpec::new_boolean(
+                        "markdown-enabled",
+                        "Markdown enabled",
+                        "Whether or not to do markdown formatting when sending messages",
+                        false,
+                        glib::ParamFlags::READWRITE,
+                    ),
+                ]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "compact" => {
+                    let compact = value.get().unwrap();
+                    self.compact.set(compact);
+                }
+                "room" => {
+                    let room = value.get().unwrap();
+                    obj.set_room(room);
+                }
+                "markdown-enabled" => {
+                    let md_enabled = value.get().unwrap();
+                    self.md_enabled.set(md_enabled);
+                    self.markdown_button.set_icon_name(if md_enabled {
+                        "format-indent-more-symbolic"
+                    } else {
+                        "format-justify-left-symbolic"
+                    });
+                }
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "compact" => self.compact.get().to_value(),
+                "room" => obj.room().to_value(),
+                "markdown-enabled" => self.md_enabled.get().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn constructed(&self, obj: &Self::Type) {
+            let adj = self.scrolled_window.vadjustment().unwrap();
+            // TODO: make sure that we have enough messages to fill at least to scroll pages, if the room 
history is long enough
+
+            adj.connect_value_changed(clone!(@weak obj => move |adj| {
+                // Load more message when the user gets close to the end of the known room history
+                // Use the page size twice to detect if the user gets close the end
+                if adj.value() < adj.page_size() * 2.0 {
+                    if let Some(room) = obj.room() {
+                        room.load_previous_events();
+                        }
+                }
+            }));
+
+            let key_events = gtk::EventControllerKey::new();
+            self.message_entry.add_controller(&key_events);
+
+            key_events
+                .connect_key_pressed(clone!(@weak obj => @default-return Inhibit(false), move |_, key, _, 
modifier| {
+                if !modifier.contains(gdk::ModifierType::SHIFT_MASK) && (key == gdk::keys::constants::Return 
|| key == gdk::keys::constants::KP_Enter) {
+                    obj.activate_action("content.send-text-message", None);
+                    Inhibit(true)
+                } else {
+                    Inhibit(false)
+                }
+            }));
+
+            let buffer = self
+                .message_entry
+                .buffer()
+                .downcast::<sourceview::Buffer>()
+                .unwrap();
+
+            buffer.connect_text_notify(clone!(@weak obj => move |buffer| {
+               let (start_iter, end_iter) = buffer.bounds();
+               obj.action_set_enabled("content.send-text-message", start_iter != end_iter);
+            }));
+
+            let (start_iter, end_iter) = buffer.bounds();
+            obj.action_set_enabled("content.send-text-message", start_iter != end_iter);
+
+            let md_lang =
+                sourceview::LanguageManager::default().and_then(|lm| lm.language("markdown"));
+            buffer.set_language(md_lang.as_ref());
+            obj.bind_property("markdown-enabled", &buffer, "highlight-syntax")
+                .flags(glib::BindingFlags::SYNC_CREATE)
+                .build();
+
+            let settings = Application::default().settings();
+            settings
+                .bind("markdown-enabled", obj, "markdown-enabled")
+                .build();
+
+            self.parent_constructed(obj);
+        }
+    }
+
+    impl WidgetImpl for RoomHistory {}
+    impl BinImpl for RoomHistory {}
+}
+
+glib::wrapper! {
+    pub struct RoomHistory(ObjectSubclass<imp::RoomHistory>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl RoomHistory {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create RoomHistory")
+    }
+
+    pub fn set_room(&self, room: Option<Room>) {
+        let priv_ = imp::RoomHistory::from_instance(self);
+
+        if self.room() == room {
+            return;
+        }
+
+        // TODO: use gtk::MultiSelection to allow selection
+        let model = room
+            .as_ref()
+            .and_then(|room| Some(gtk::NoSelection::new(Some(room.timeline()))));
+
+        priv_.listview.set_model(model.as_ref());
+        priv_.room.replace(room);
+        self.notify("room");
+    }
+
+    pub fn room(&self) -> Option<Room> {
+        let priv_ = imp::RoomHistory::from_instance(self);
+        priv_.room.borrow().clone()
+    }
+
+    pub fn send_text_message(&self) {
+        let priv_ = imp::RoomHistory::from_instance(self);
+        let buffer = priv_.message_entry.buffer();
+        let (start_iter, end_iter) = buffer.bounds();
+        let body = buffer.text(&start_iter, &end_iter, true);
+
+        if let Some(room) = &*priv_.room.borrow() {
+            room.send_text_message(body.as_str(), priv_.md_enabled.get());
+        }
+
+        buffer.set_text("");
+    }
+}


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