[fractal/fractal-next] sidebar: Enable changing room category with drag-and-drop
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] sidebar: Enable changing room category with drag-and-drop
- Date: Tue, 18 Jan 2022 17:07:43 +0000 (UTC)
commit ec3f7cfeae43bfc4bbb86b4218579ae7222d46cd
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Tue Jan 18 15:37:09 2022 +0100
sidebar: Enable changing room category with drag-and-drop
Part of #757
Cargo.lock | 22 ++++
Cargo.toml | 23 +++-
data/resources/style.css | 113 +++++++++++++----
data/resources/ui/sidebar-category-row.ui | 6 +-
src/session/room/mod.rs | 202 +++++++++++++++++++++---------
src/session/room/room_type.rs | 62 ++++++++-
src/session/room_list.rs | 3 +
src/session/sidebar/category_row.rs | 100 +++++++++++++--
src/session/sidebar/category_type.rs | 4 +-
src/session/sidebar/entry.rs | 1 +
src/session/sidebar/entry_type.rs | 2 +
src/session/sidebar/item_list.rs | 94 +++++++-------
src/session/sidebar/mod.rs | 184 ++++++++++++++++++++++++++-
src/session/sidebar/room_row.rs | 50 +++++++-
src/session/sidebar/row.rs | 111 +++++++++++++++-
15 files changed, 818 insertions(+), 159 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index fd7abb20..b4bf1918 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -999,6 +999,7 @@ dependencies = [
"matrix-sdk",
"mime",
"mime_guess",
+ "num_enum",
"once_cell",
"qrcode",
"rand 0.8.4",
@@ -2603,6 +2604,27 @@ dependencies = [
"libc",
]
+[[package]]
+name = "num_enum"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21"
+dependencies = [
+ "proc-macro-crate 1.1.0",
+ "proc-macro2 1.0.30",
+ "quote 1.0.10",
+ "syn 1.0.80",
+]
+
[[package]]
name = "objc"
version = "0.2.7"
diff --git a/Cargo.toml b/Cargo.toml
index 5da11da3..bebe547d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,13 +29,18 @@ futures = "0.3"
rand = "0.8"
indexmap = "1.6.2"
qrcode = "0.12.0"
-ashpd = {git = "https://github.com/bilelmoussaoui/ashpd", rev="66d4dc0020181a7174451150ecc711344082b5ce",
features=["feature_gtk4", "feature_pipewire", "log"]}
-gst = {version = "0.17", package = "gstreamer"}
-gst_base = {version = "0.17", package = "gstreamer-base"}
-gst_video = {version = "0.17", package = "gstreamer-video"}
-image = {version = "0.23", default-features = false, features=["png"]}
+ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "66d4dc0020181a7174451150ecc711344082b5ce",
features = [
+ "feature_gtk4",
+ "feature_pipewire",
+ "log",
+] }
+gst = { version = "0.17", package = "gstreamer" }
+gst_base = { version = "0.17", package = "gstreamer-base" }
+gst_video = { version = "0.17", package = "gstreamer-video" }
+image = { version = "0.23", default-features = false, features = ["png"] }
regex = "1.5.4"
mime_guess = "2.0.3"
+num_enum = "0.5.6"
[dependencies.sourceview]
package = "sourceview5"
@@ -52,4 +57,10 @@ version = "0.1.0-alpha-6"
[dependencies.matrix-sdk]
git = "https://github.com/jsparber/matrix-rust-sdk.git"
branch = "messages-api"
-features = ["socks", "encryption", "sled_cryptostore", "sled_state_store", "markdown"]
+features = [
+ "socks",
+ "encryption",
+ "sled_cryptostore",
+ "sled_state_store",
+ "markdown",
+]
diff --git a/data/resources/style.css b/data/resources/style.css
index e0dcec31..812c53e3 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -135,43 +135,80 @@ headerbar.flat {
/* Sidebar List */
.sidebar-list row {
- padding-left: 10px;
- padding-right: 10px;
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
}
-.sidebar-list .category {
- margin-top: 4px;
- font-size: 0.8em;
- font-weight: bold;
+.sidebar-list row:focus-visible:focus-within {
+ outline: 0;
}
-.sidebar-list .entry {
- margin-top: 4px;
- font-weight: bold;
+.sidebar-list row:selected {
+ background: none;
}
-.sidebar-list .category image.arrow {
- transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+.sidebar-list row > * {
+ margin: 0 12px;
+ padding: 12px 6px;
+ border-radius: 6px;
+ transition-property: outline, outline-width, outline-offset, outline-color;
+ transition-duration: 300ms;
+ animation-timing-function: ease-in-out;
+ outline: 0 solid transparent;
+ outline-offset: 2px;
}
-.sidebar-list .category .category-row:not(:checked) image.arrow:dir(ltr) {
- transform: rotate(-0.25turn);
+.sidebar-list row:focus-visible:focus-within > * {
+ outline-color: alpha(@accent_color, 0.5);
+ outline-width: 2px;
+ outline-offset: -2px;
}
-.sidebar-list .category .category-row:not(:checked) image.arrow:dir(rtl) {
- transform: rotate(0.25turn);
+.sidebar-list:not(.drop-mode) row:hover > * {
+ background-color: alpha(currentColor, 0.07);
+}
+
+.sidebar-list row:active > * {
+ background-color: alpha(currentColor, 0.16);
+}
+
+.sidebar-list row:selected > * {
+ background-color: alpha(currentColor, 0.1);
+}
+
+.sidebar-list:not(.drop-mode) row:selected:hover > *,
+.sidebar-list row:selected.has-open-popup > * {
+ background-color: alpha(currentColor, 0.13);
}
-.sidebar-list .room {
- padding-top: 4px;
- padding-bottom: 4px;
+.sidebar-list row:selected:active > * {
+ background-color: alpha(currentColor, 0.19);
}
-.sidebar-list .room .bold {
+.sidebar-list row.entry {
font-weight: bold;
}
-.sidebar-list .room .notification_count {
+.sidebar-list row.category {
+ margin-top: 6px;
+ font-size: 0.8em;
+ font-weight: bold;
+}
+
+.sidebar-list row.category image.arrow {
+ transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.sidebar-list row.category .category-row:not(:checked) image.arrow:dir(ltr) {
+ transform: rotate(-0.25turn);
+}
+
+.sidebar-list row.category .category-row:not(:checked) image.arrow:dir(rtl) {
+ transform: rotate(0.25turn);
+}
+
+.sidebar-list row.room .notification_count {
font-weight: bold;
font-size: 0.8em;
border-radius: 10px;
@@ -179,10 +216,44 @@ headerbar.flat {
padding: 2px 5px;
}
-.sidebar-list .room .highlight {
+.sidebar-list row.room .highlight {
+ color: @accent_fg_color;
background-color: @accent_bg_color;
}
+.sidebar-list sidebar-row.drag {
+ color: @accent_fg_color;
+ background-color: @accent_bg_color;
+ opacity: 0.6;
+}
+
+.sidebar-list sidebar-row.drop-disabled > * {
+ opacity: 0.6;
+}
+
+.sidebar-list sidebar-row.drop-empty {
+ color: @accent_color;
+}
+
+.sidebar-list sidebar-row.forget {
+ color: @error_color;
+ background: none;
+}
+
+.sidebar-list row.drop-active {
+ background-color: alpha(@accent_color, 0.1);
+}
+
+.sidebar-list row.category.drop-active,
+.sidebar-list row.drop-active sidebar-row.forget {
+ color: @accent_color;
+}
+
+.sidebar-list row.drop-active .dim-label,
+.sidebar-list row.drop-active sidebar-row.drop-empty .dim-label {
+ opacity: 1;
+}
+
/* Content */
.room-history {
background: @theme_base_color;
diff --git a/data/resources/ui/sidebar-category-row.ui b/data/resources/ui/sidebar-category-row.ui
index 4b7d6341..bab9f9a6 100644
--- a/data/resources/ui/sidebar-category-row.ui
+++ b/data/resources/ui/sidebar-category-row.ui
@@ -12,11 +12,7 @@
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
- <binding name="label">
- <lookup name="display-name">
- <lookup name="category">SidebarCategoryRow</lookup>
- </lookup>
- </binding>
+ <property name="label" bind-source="SidebarCategoryRow" bind-property="label"
bind-flags="sync-create"/>
<style>
<class name="dim-label"/>
</style>
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index ad902f83..71b6bace 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -36,7 +36,7 @@ use matrix_sdk::{
member::MembershipState, message::RoomMessageEventContent,
name::RoomNameEventContent, topic::RoomTopicEventContent,
},
- tag::TagName,
+ tag::{TagInfo, TagName},
AnyRoomAccountDataEvent, AnyStateEventContent, AnyStrippedStateEvent,
AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, SyncMessageEvent,
Unsigned,
@@ -284,7 +284,10 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
- vec![Signal::builder("order-changed", &[], <()>::static_type().into()).build()]
+ vec![
+ Signal::builder("order-changed", &[], <()>::static_type().into()).build(),
+ Signal::builder("room-forgotten", &[], <()>::static_type().into()).build(),
+ ]
});
SIGNALS.as_ref()
}
@@ -366,6 +369,56 @@ impl Room {
self.load_category();
}
+ /// Forget a room that is left.
+ pub fn forget(&self) {
+ if self.category() != RoomType::Left {
+ warn!("Cannot forget a room that is not left");
+ return;
+ }
+
+ let matrix_room = self.matrix_room();
+
+ let handle = spawn_tokio!(async move {
+ match matrix_room {
+ MatrixRoom::Left(room) => room.forget().await,
+ _ => unimplemented!(),
+ }
+ });
+
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak self as obj => async move {
+ match handle.await.unwrap() {
+ Ok(_) => {
+ obj.emit_by_name("room-forgotten", &[]).unwrap();
+ }
+ Err(error) => {
+ error!("Couldn’t forget the room: {}", error);
+ let error = Error::new(
+ clone!(@weak obj => @default-return None, move |_| {
+ let error_message = gettext(
+ "Failed to forget <widget>."
+ );
+ let room_pill = Pill::new();
+ room_pill.set_room(Some(obj));
+ let label = LabelWithWidgets::new(&error_message,
vec![room_pill]);
+
+ Some(label.upcast())
+ }),
+ );
+
+ if let Some(window) = obj.session().parent_window() {
+ window.append_error(&error);
+ }
+
+ // Load the previous category
+ obj.load_category();
+ },
+ };
+ })
+ );
+ }
+
pub fn category(&self) -> RoomType {
let priv_ = imp::Room::from_instance(self);
priv_.category.get()
@@ -386,7 +439,9 @@ impl Room {
/// Set the category of this room.
///
/// This makes the necessary to propagate the category to the homeserver.
- /// Note: Rooms can't be moved to the invite category and they can't be moved once they are upgraded
+ ///
+ /// Note: Rooms can't be moved to the invite category and they can't be
+ /// moved once they are upgraded.
pub fn set_category(&self, category: RoomType) {
let matrix_room = self.matrix_room();
let previous_category = self.category();
@@ -395,78 +450,93 @@ impl Room {
return;
}
- if category == RoomType::Invited {
- warn!("Rooms can’t be moved to the invite Category");
- return;
- }
-
- if self.category() == RoomType::Outdated {
+ if previous_category == RoomType::Outdated {
warn!("Can't set the category of an upgraded room");
return;
}
- // Outdated rooms don't need to propagate anything to the server
- if category == RoomType::Outdated {
- self.set_category_internal(category);
- return;
+ match category {
+ RoomType::Invited => {
+ warn!("Rooms can’t be moved to the invite Category");
+ return;
+ }
+ RoomType::Outdated => {
+ // Outdated rooms don't need to propagate anything to the server
+ self.set_category_internal(category);
+ return;
+ }
+ _ => {}
}
let handle = spawn_tokio!(async move {
match matrix_room {
- MatrixRoom::Invited(room) => {
- match category {
- RoomType::Invited => Ok(()),
- RoomType::Favorite => {
- room.accept_invitation().await
- // TODO: set favorite tag
- }
- RoomType::Normal => room.accept_invitation().await,
- RoomType::LowPriority => {
- room.accept_invitation().await
- // TODO: set low priority tag
- }
- RoomType::Left => room.reject_invitation().await,
- RoomType::Outdated => unimplemented!(),
+ MatrixRoom::Invited(room) => match category {
+ RoomType::Invited => Ok(()),
+ RoomType::Favorite => {
+ room.accept_invitation().await
+ // TODO: set favorite tag
}
- }
- MatrixRoom::Joined(room) => {
- match category {
- RoomType::Invited => Ok(()),
- RoomType::Favorite => {
- // TODO: set favorite tag
- Ok(())
- }
- RoomType::Normal => {
- // TODO: remove tags
- Ok(())
- }
- RoomType::LowPriority => {
- // TODO: set low priority tag
- Ok(())
- }
- RoomType::Left => room.leave().await,
- RoomType::Outdated => unimplemented!(),
+ RoomType::Normal => {
+ room.accept_invitation().await
+ // TODO: remove tags
}
- }
- MatrixRoom::Left(room) => {
- match category {
- RoomType::Invited => Ok(()),
- RoomType::Favorite => {
- room.join().await
- // TODO: set favorite tag
+ RoomType::LowPriority => {
+ room.accept_invitation().await
+ // TODO: set low priority tag
+ }
+ RoomType::Left => room.reject_invitation().await,
+ RoomType::Outdated => unimplemented!(),
+ },
+ MatrixRoom::Joined(room) => match category {
+ RoomType::Invited => Ok(()),
+ RoomType::Favorite => {
+ room.set_tag(TagName::Favorite.as_ref(), TagInfo::new())
+ .await?;
+ if previous_category == RoomType::LowPriority {
+ room.remove_tag(TagName::LowPriority.as_ref()).await?;
}
- RoomType::Normal => {
- room.join().await
- // TODO: remove tags
+ Ok(())
+ }
+ RoomType::Normal => {
+ match previous_category {
+ RoomType::Favorite => {
+ room.remove_tag(TagName::Favorite.as_ref()).await?;
+ }
+ RoomType::LowPriority => {
+ room.remove_tag(TagName::LowPriority.as_ref()).await?;
+ }
+ _ => {}
}
- RoomType::LowPriority => {
- room.join().await
- // TODO: set low priority tag
+ Ok(())
+ }
+ RoomType::LowPriority => {
+ room.set_tag(TagName::LowPriority.as_ref(), TagInfo::new())
+ .await?;
+ if previous_category == RoomType::Favorite {
+ room.remove_tag(TagName::Favorite.as_ref()).await?;
}
- RoomType::Left => Ok(()),
- RoomType::Outdated => unimplemented!(),
+ Ok(())
}
- }
+ RoomType::Left => room.leave().await,
+ RoomType::Outdated => unimplemented!(),
+ },
+ MatrixRoom::Left(room) => match category {
+ RoomType::Invited => Ok(()),
+ RoomType::Favorite => {
+ room.join().await
+ // TODO: set favorite tag
+ }
+ RoomType::Normal => {
+ room.join().await
+ // TODO: remove tags
+ }
+ RoomType::LowPriority => {
+ room.join().await
+ // TODO: set low priority tag
+ }
+ RoomType::Left => Ok(()),
+ RoomType::Outdated => unimplemented!(),
+ },
}
});
@@ -1033,6 +1103,16 @@ impl Room {
.unwrap()
}
+ /// Connect to the signal sent when a room was forgotten.
+ pub fn connect_room_forgotten<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+ self.connect_local("room-forgotten", true, move |values| {
+ let obj = values[0].get::<Self>().unwrap();
+ f(&obj);
+ None
+ })
+ .unwrap()
+ }
+
pub fn predecessor(&self) -> Option<&RoomId> {
let priv_ = imp::Room::from_instance(self);
priv_.predecessor.get().map(std::ops::Deref::deref)
diff --git a/src/session/room/room_type.rs b/src/session/room/room_type.rs
index 16057d8e..100f89bf 100644
--- a/src/session/room/room_type.rs
+++ b/src/session/room/room_type.rs
@@ -1,8 +1,12 @@
-use crate::session::sidebar::CategoryType;
+use std::convert::TryFrom;
+
use gtk::glib;
+use num_enum::{IntoPrimitive, TryFromPrimitive};
+
+use crate::session::sidebar::CategoryType;
// TODO: do we also want the category `People` and a custom category support?
-#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum, IntoPrimitive, TryFromPrimitive)]
#[repr(u32)]
#[genum(type_name = "RoomType")]
pub enum RoomType {
@@ -14,6 +18,33 @@ pub enum RoomType {
Outdated = 5,
}
+impl RoomType {
+ /// Check whether this `RoomType` can be changed to `category`.
+ pub fn can_change_to(&self, category: &RoomType) -> bool {
+ match self {
+ Self::Invited => {
+ matches!(
+ category,
+ Self::Favorite | Self::Normal | Self::LowPriority | Self::Left
+ )
+ }
+ Self::Favorite => {
+ matches!(category, Self::Normal | Self::LowPriority | Self::Left)
+ }
+ Self::Normal => {
+ matches!(category, Self::Favorite | Self::LowPriority | Self::Left)
+ }
+ Self::LowPriority => {
+ matches!(category, Self::Favorite | Self::Normal | Self::Left)
+ }
+ Self::Left => {
+ matches!(category, Self::Favorite | Self::Normal | Self::LowPriority)
+ }
+ Self::Outdated => false,
+ }
+ }
+}
+
impl Default for RoomType {
fn default() -> Self {
RoomType::Normal
@@ -25,3 +56,30 @@ impl ToString for RoomType {
CategoryType::from(self).to_string()
}
}
+
+impl TryFrom<CategoryType> for RoomType {
+ type Error = &'static str;
+
+ fn try_from(category_type: CategoryType) -> Result<Self, Self::Error> {
+ Self::try_from(&category_type)
+ }
+}
+
+impl TryFrom<&CategoryType> for RoomType {
+ type Error = &'static str;
+
+ fn try_from(category_type: &CategoryType) -> Result<Self, Self::Error> {
+ match category_type {
+ CategoryType::None => Err("CategoryType::None cannot be a RoomType"),
+ CategoryType::Invited => Ok(Self::Invited),
+ CategoryType::Favorite => Ok(Self::Favorite),
+ CategoryType::Normal => Ok(Self::Normal),
+ CategoryType::LowPriority => Ok(Self::LowPriority),
+ CategoryType::Left => Ok(Self::Left),
+ CategoryType::Outdated => Ok(Self::Outdated),
+ CategoryType::VerificationRequest => {
+ Err("CategoryType::VerificationRequest cannot be a RoomType")
+ }
+ }
+ }
+}
diff --git a/src/session/room_list.rs b/src/session/room_list.rs
index 934989d6..9be478ec 100644
--- a/src/session/room_list.rs
+++ b/src/session/room_list.rs
@@ -226,6 +226,9 @@ impl RoomList {
obj.items_changed(position as u32, 1, 1);
}
}));
+ room.connect_room_forgotten(clone!(@weak self as obj => move |room| {
+ obj.remove(room.room_id());
+ }));
}
self.items_changed(position as u32, 0, added as u32);
diff --git a/src/session/sidebar/category_row.rs b/src/session/sidebar/category_row.rs
index fd102c71..f1c04624 100644
--- a/src/session/sidebar/category_row.rs
+++ b/src/session/sidebar/category_row.rs
@@ -1,9 +1,10 @@
use adw::subclass::prelude::BinImpl;
+use gettextrs::gettext;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
use gtk::{glib, CompositeTemplate};
-use crate::session::sidebar::Category;
+use crate::session::sidebar::{Category, CategoryType};
mod imp {
use super::*;
@@ -13,8 +14,13 @@ mod imp {
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/sidebar-category-row.ui")]
pub struct CategoryRow {
+ /// The category of this row.
pub category: RefCell<Option<Category>>,
+ /// The expanded state of this row.
pub expanded: Cell<bool>,
+ /// The `CategoryType` to show a label for during a drag-and-drop
+ /// operation.
+ pub show_label_for_category: Cell<CategoryType>,
}
#[glib::object_subclass]
@@ -51,6 +57,21 @@ mod imp {
true,
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
+ glib::ParamSpec::new_string(
+ "label",
+ "Label",
+ "The label to show for this row",
+ None,
+ glib::ParamFlags::READABLE,
+ ),
+ glib::ParamSpec::new_enum(
+ "show-label-for-category",
+ "Show Label for Category",
+ "The CategoryType to show a label for",
+ CategoryType::static_type(),
+ CategoryType::None as i32,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
]
});
@@ -65,14 +86,9 @@ mod imp {
pspec: &glib::ParamSpec,
) {
match pspec.name() {
- "category" => {
- let category = value.get().unwrap();
- obj.set_category(category);
- }
- "expanded" => {
- let expanded = value.get().unwrap();
- obj.set_expanded(expanded);
- }
+ "category" => obj.set_category(value.get().unwrap()),
+ "expanded" => obj.set_expanded(value.get().unwrap()),
+ "show-label-for-category" => obj.set_show_label_for_category(value.get().unwrap()),
_ => unimplemented!(),
}
}
@@ -81,6 +97,8 @@ mod imp {
match pspec.name() {
"category" => obj.category().to_value(),
"expanded" => obj.expanded().to_value(),
+ "label" => obj.label().to_value(),
+ "show-label-for-category" => obj.show_label_for_category().to_value(),
_ => unimplemented!(),
}
}
@@ -97,7 +115,8 @@ glib::wrapper! {
impl CategoryRow {
pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create CategoryRow")
+ glib::Object::new(&[("show-label-for-category", &CategoryType::None)])
+ .expect("Failed to create CategoryRow")
}
pub fn category(&self) -> Option<Category> {
@@ -114,6 +133,7 @@ impl CategoryRow {
priv_.category.replace(category);
self.notify("category");
+ self.notify("label");
}
fn expanded(&self) -> bool {
@@ -137,6 +157,66 @@ impl CategoryRow {
priv_.expanded.set(expanded);
self.notify("expanded");
}
+
+ pub fn label(&self) -> Option<String> {
+ let to_type = self.category()?.type_();
+ let from_type = self.show_label_for_category();
+
+ let label = match from_type {
+ CategoryType::Invited => match to_type {
+ CategoryType::Favorite => gettext("Join Room as Favorite"),
+ CategoryType::Normal => gettext("Join Room"),
+ CategoryType::LowPriority => gettext("Join Room as Low Priority"),
+ CategoryType::Left => gettext("Reject Invite"),
+ _ => to_type.to_string(),
+ },
+ CategoryType::Favorite => match to_type {
+ CategoryType::Normal => gettext("Unmark as Favorite"),
+ CategoryType::LowPriority => gettext("Mark as Low Priority"),
+ CategoryType::Left => gettext("Leave Room"),
+ _ => to_type.to_string(),
+ },
+ CategoryType::Normal => match to_type {
+ CategoryType::Favorite => gettext("Mark as Favorite"),
+ CategoryType::LowPriority => gettext("Mark as Low Priority"),
+ CategoryType::Left => gettext("Leave Room"),
+ _ => to_type.to_string(),
+ },
+ CategoryType::LowPriority => match to_type {
+ CategoryType::Favorite => gettext("Mark as Favorite"),
+ CategoryType::Normal => gettext("Unmark as Low Priority"),
+ CategoryType::Left => gettext("Leave Room"),
+ _ => to_type.to_string(),
+ },
+ CategoryType::Left => match to_type {
+ CategoryType::Favorite => gettext("Rejoin Room as Favorite"),
+ CategoryType::Normal => gettext("Rejoin Room"),
+ CategoryType::LowPriority => gettext("Rejoin Room as Low Priority"),
+ _ => to_type.to_string(),
+ },
+ _ => to_type.to_string(),
+ };
+
+ Some(label)
+ }
+
+ pub fn show_label_for_category(&self) -> CategoryType {
+ let priv_ = imp::CategoryRow::from_instance(self);
+ priv_.show_label_for_category.get()
+ }
+
+ pub fn set_show_label_for_category(&self, category: CategoryType) {
+ let priv_ = imp::CategoryRow::from_instance(self);
+
+ if category == self.show_label_for_category() {
+ return;
+ }
+
+ priv_.show_label_for_category.set(category);
+
+ self.notify("show-label-for-category");
+ self.notify("label");
+ }
}
impl Default for CategoryRow {
diff --git a/src/session/sidebar/category_type.rs b/src/session/sidebar/category_type.rs
index f64edc20..bd3d20cb 100644
--- a/src/session/sidebar/category_type.rs
+++ b/src/session/sidebar/category_type.rs
@@ -3,9 +3,10 @@ use gettextrs::gettext;
use gtk::glib;
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
-#[repr(u32)]
+#[repr(i32)]
#[genum(type_name = "CategoryType")]
pub enum CategoryType {
+ None = -1,
VerificationRequest = 0,
Invited = 1,
Favorite = 2,
@@ -24,6 +25,7 @@ impl Default for CategoryType {
impl ToString for CategoryType {
fn to_string(&self) -> String {
match self {
+ CategoryType::None => unimplemented!(),
CategoryType::VerificationRequest => gettext("Verifications"),
CategoryType::Invited => gettext("Invited"),
CategoryType::Favorite => gettext("Favorite"),
diff --git a/src/session/sidebar/entry.rs b/src/session/sidebar/entry.rs
index 4a0572f6..6e57fe16 100644
--- a/src/session/sidebar/entry.rs
+++ b/src/session/sidebar/entry.rs
@@ -103,6 +103,7 @@ impl Entry {
pub fn icon_name(&self) -> Option<&str> {
match self.type_() {
EntryType::Explore => Some("explore-symbolic"),
+ EntryType::Forget => Some("user-trash-symbolic"),
}
}
}
diff --git a/src/session/sidebar/entry_type.rs b/src/session/sidebar/entry_type.rs
index fcbdfd17..791b6722 100644
--- a/src/session/sidebar/entry_type.rs
+++ b/src/session/sidebar/entry_type.rs
@@ -6,6 +6,7 @@ use gtk::glib;
#[genum(type_name = "EntryType")]
pub enum EntryType {
Explore = 0,
+ Forget = 1,
}
impl Default for EntryType {
@@ -18,6 +19,7 @@ impl ToString for EntryType {
fn to_string(&self) -> String {
match self {
EntryType::Explore => gettext("Explore"),
+ EntryType::Forget => gettext("Forget Room"),
}
}
}
diff --git a/src/session/sidebar/item_list.rs b/src/session/sidebar/item_list.rs
index 5a583aba..b47df0b5 100644
--- a/src/session/sidebar/item_list.rs
+++ b/src/session/sidebar/item_list.rs
@@ -1,6 +1,9 @@
+use std::convert::TryFrom;
+
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use crate::session::{
+ room::RoomType,
room_list::RoomList,
sidebar::CategoryType,
sidebar::EntryType,
@@ -17,10 +20,13 @@ mod imp {
#[derive(Debug, Default)]
pub struct ItemList {
- pub list: OnceCell<[(glib::Object, Cell<bool>); 7]>,
+ pub list: OnceCell<[(glib::Object, Cell<bool>); 8]>,
pub room_list: OnceCell<RoomList>,
pub verification_list: OnceCell<VerificationList>,
- pub show_all: Cell<bool>,
+ /// The `CategoryType` to show all compatible categories for.
+ ///
+ /// Uses `RoomType::can_change_to` to find compatible categories.
+ pub show_all_for_category: Cell<CategoryType>,
}
#[glib::object_subclass]
@@ -49,11 +55,12 @@ mod imp {
VerificationList::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
),
- glib::ParamSpec::new_boolean(
- "show-all",
- "Show All",
- "Whether all room categories should be shown",
- false,
+ glib::ParamSpec::new_enum(
+ "show-all-for-category",
+ "Show All For Category",
+ "The `CategoryType` to show all compatible categories for",
+ CategoryType::static_type(),
+ CategoryType::None as i32,
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
]
@@ -72,7 +79,7 @@ mod imp {
match pspec.name() {
"room-list" => obj.set_room_list(value.get().unwrap()),
"verification-list" => obj.set_verification_list(value.get().unwrap()),
- "show-all" => obj.set_show_all(value.get().unwrap()),
+ "show-all-for-category" => obj.set_show_all_for_category(value.get().unwrap()),
_ => unimplemented!(),
}
}
@@ -81,7 +88,7 @@ mod imp {
match pspec.name() {
"room-list" => obj.room_list().to_value(),
"verification-list" => obj.verification_list().to_value(),
- "show-all" => obj.show_all().to_value(),
+ "show-all-for-category" => obj.show_all_for_category().to_value(),
_ => unimplemented!(),
}
}
@@ -101,6 +108,7 @@ mod imp {
Category::new(CategoryType::Normal, room_list).upcast::<glib::Object>(),
Category::new(CategoryType::LowPriority, room_list).upcast::<glib::Object>(),
Category::new(CategoryType::Left, room_list).upcast::<glib::Object>(),
+ Entry::new(EntryType::Forget).upcast::<glib::Object>(),
];
for (index, item) in list.iter().enumerate() {
@@ -108,7 +116,7 @@ mod imp {
category.connect_notify_local(
Some("empty"),
clone!(@weak obj => move |_, _| {
- obj.update_category(index);
+ obj.update_item(index);
}),
);
}
@@ -118,7 +126,9 @@ mod imp {
let visible = if let Some(category) = item.downcast_ref::<Category>() {
!category.is_empty()
} else {
- true
+ item.downcast_ref::<Entry>()
+ .filter(|entry| entry.type_() == EntryType::Forget)
+ .is_none()
};
(item, Cell::new(visible))
});
@@ -177,12 +187,30 @@ impl ItemList {
.expect("Failed to create ItemList")
}
- fn update_category(&self, position: usize) {
+ fn update_item(&self, position: usize) {
let priv_ = imp::ItemList::from_instance(self);
let (item, old_visible) = priv_.list.get().unwrap().get(position).unwrap();
- let category = item.downcast_ref::<Category>().unwrap();
- let visible = !category.is_empty() || (self.show_all() && is_show_all_category(category));
+ let visible = if let Some(category) = item.downcast_ref::<Category>() {
+ !category.is_empty()
+ || RoomType::try_from(self.show_all_for_category())
+ .ok()
+ .and_then(|room_type| {
+ RoomType::try_from(category.type_())
+ .ok()
+ .filter(|category| room_type.can_change_to(category))
+ })
+ .is_some()
+ } else if item
+ .downcast_ref::<Entry>()
+ .filter(|entry| entry.type_() == EntryType::Forget)
+ .is_some()
+ {
+ self.show_all_for_category() == CategoryType::Left
+ } else {
+ return;
+ };
+
if visible != old_visible.get() {
old_visible.set(visible);
let hidden_before_position = priv_
@@ -201,32 +229,24 @@ impl ItemList {
}
}
- // Whether all room categories are shown
- // This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
- pub fn show_all(&self) -> bool {
+ pub fn show_all_for_category(&self) -> CategoryType {
let priv_ = imp::ItemList::from_instance(self);
- priv_.show_all.get()
+ priv_.show_all_for_category.get()
}
- // Set whether all room categories should be shown
- // This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
- pub fn set_show_all(&self, show_all: bool) {
+ pub fn set_show_all_for_category(&self, category: CategoryType) {
let priv_ = imp::ItemList::from_instance(self);
- if show_all == self.show_all() {
+
+ if category == self.show_all_for_category() {
return;
}
- priv_.show_all.set(show_all);
-
- for (index, (item, _)) in priv_.list.get().unwrap().iter().enumerate() {
- if let Some(category) = item.downcast_ref::<Category>() {
- if is_show_all_category(category) {
- self.update_category(index);
- }
- }
+ priv_.show_all_for_category.set(category);
+ for i in 0..priv_.list.get().unwrap().len() {
+ self.update_item(i);
}
- self.notify("show-all");
+ self.notify("show-all-for-category");
}
fn set_room_list(&self, room_list: RoomList) {
@@ -249,15 +269,3 @@ impl ItemList {
priv_.verification_list.get().unwrap()
}
}
-
-// Wheter this category should be shown when `show-all` is `true`
-// This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
-fn is_show_all_category(category: &Category) -> bool {
- matches!(
- category.type_(),
- CategoryType::Favorite
- | CategoryType::Normal
- | CategoryType::LowPriority
- | CategoryType::Left
- )
-}
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index fe3bd4b2..45622c6e 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -23,11 +23,11 @@ use self::row::Row;
use self::selection::Selection;
use self::verification_row::VerificationRow;
-use adw::subclass::prelude::BinImpl;
-use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate, SelectionModel};
+use adw::{prelude::*, subclass::prelude::*};
+use gtk::{gio, glib, subclass::prelude::*, CompositeTemplate, SelectionModel};
use crate::components::Avatar;
-use crate::session::room::Room;
+use crate::session::room::{Room, RoomType};
use crate::session::verification::IdentityVerification;
use crate::session::Session;
use crate::session::User;
@@ -37,7 +37,10 @@ mod imp {
use super::*;
use glib::subclass::InitializingObject;
use once_cell::sync::Lazy;
- use std::cell::{Cell, RefCell};
+ use std::{
+ cell::{Cell, RefCell},
+ convert::TryFrom,
+ };
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
@@ -55,6 +58,9 @@ mod imp {
#[template_child]
pub room_search: TemplateChild<gtk::SearchBar>,
pub user: RefCell<Option<User>>,
+ /// The type of the source that activated drop mode.
+ pub drop_source_type: Cell<Option<RoomType>>,
+ pub drop_binding: RefCell<Option<glib::Binding>>,
}
#[glib::object_subclass]
@@ -68,6 +74,35 @@ mod imp {
Row::static_type();
Avatar::static_type();
Self::bind_template(klass);
+ klass.set_css_name("sidebar");
+
+ klass.install_action(
+ "sidebar.set-drop-source-type",
+ Some("u"),
+ move |obj, _, variant| {
+ obj.set_drop_source_type(
+ variant
+ .and_then(|variant| variant.get::<Option<u32>>().flatten())
+ .and_then(|u| RoomType::try_from(u).ok()),
+ );
+ },
+ );
+ klass.install_action("sidebar.update-drop-targets", None, move |obj, _, _| {
+ if obj.drop_source_type().is_some() {
+ obj.update_drop_targets();
+ }
+ });
+ klass.install_action(
+ "sidebar.set-active-drop-category",
+ Some("mu"),
+ move |obj, _, variant| {
+ obj.update_active_drop_targets(
+ variant
+ .and_then(|variant| variant.get::<Option<u32>>().flatten())
+ .and_then(|u| RoomType::try_from(u).ok()),
+ );
+ },
+ );
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -98,7 +133,7 @@ mod imp {
"Item List",
"The list of items in the sidebar",
ItemList::static_type(),
- glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ glib::ParamFlags::WRITABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpec::new_object(
"selected-item",
@@ -107,6 +142,14 @@ mod imp {
glib::Object::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
+ glib::ParamSpec::new_enum(
+ "drop-source-type",
+ "Drop Source Type",
+ "The type of the source that activated drop mode",
+ CategoryType::static_type(),
+ CategoryType::None as i32,
+ glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
]
});
@@ -144,6 +187,11 @@ mod imp {
"compact" => self.compact.get().to_value(),
"user" => obj.user().to_value(),
"selected-item" => obj.selected_item().to_value(),
+ "drop-source-type" => obj
+ .drop_source_type()
+ .map(CategoryType::from)
+ .unwrap_or(CategoryType::None)
+ .to_value(),
_ => unimplemented!(),
}
}
@@ -151,7 +199,7 @@ mod imp {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
- self.listview.get().connect_activate(move |listview, pos| {
+ self.listview.connect_activate(move |listview, pos| {
let model: Option<Selection> = listview.model().and_then(|o| o.downcast().ok());
let row: Option<gtk::TreeListRow> = model
.as_ref()
@@ -200,6 +248,11 @@ impl Sidebar {
pub fn set_item_list(&self, item_list: Option<ItemList>) {
let priv_ = imp::Sidebar::from_instance(self);
+
+ if let Some(binding) = priv_.drop_binding.take() {
+ binding.unbind();
+ }
+
let item_list = match item_list {
Some(item_list) => item_list,
None => {
@@ -208,6 +261,12 @@ impl Sidebar {
}
};
+ priv_.drop_binding.replace(
+ self.bind_property("drop-source-type", &item_list, "show-all-for-category")
+ .flags(glib::BindingFlags::SYNC_CREATE)
+ .build(),
+ );
+
let tree_model = gtk::TreeListModel::new(&item_list, false, true, |item| {
item.clone().downcast::<gio::ListModel>().ok()
});
@@ -280,6 +339,119 @@ impl Sidebar {
.account_switcher
.set_logged_in_users(sessions_stack_pages, session_root);
}
+
+ pub fn drop_source_type(&self) -> Option<RoomType> {
+ let priv_ = imp::Sidebar::from_instance(self);
+ priv_.drop_source_type.get()
+ }
+
+ pub fn set_drop_source_type(&self, source_type: Option<RoomType>) {
+ let priv_ = imp::Sidebar::from_instance(self);
+
+ if self.drop_source_type() == source_type {
+ return;
+ }
+
+ priv_.drop_source_type.set(source_type);
+
+ if source_type.is_some() {
+ priv_.listview.add_css_class("drop-mode");
+ } else {
+ priv_.listview.remove_css_class("drop-mode");
+ }
+
+ self.notify("drop-source-type");
+ self.update_drop_targets();
+ }
+
+ /// Update the disabled or empty state of drop targets.
+ fn update_drop_targets(&self) {
+ let priv_ = imp::Sidebar::from_instance(self);
+ let mut child = priv_.listview.first_child();
+
+ while let Some(widget) = child {
+ if let Some(row) = widget
+ .first_child()
+ .and_then(|widget| widget.downcast::<Row>().ok())
+ {
+ if let Some(source_type) = self.drop_source_type() {
+ if row
+ .room_type()
+ .filter(|row_type| source_type.can_change_to(row_type))
+ .is_some()
+ {
+ row.remove_css_class("drop-disabled");
+
+ if row
+ .item()
+ .and_then(|object| object.downcast::<Category>().ok())
+ .filter(|category| category.is_empty())
+ .is_some()
+ {
+ row.add_css_class("drop-empty");
+ } else {
+ row.remove_css_class("drop-empty");
+ }
+ } else {
+ let is_forget_entry = row
+ .entry_type()
+ .filter(|entry_type| entry_type == &EntryType::Forget)
+ .is_some();
+ if is_forget_entry && source_type == RoomType::Left {
+ row.remove_css_class("drop-disabled");
+ } else {
+ row.add_css_class("drop-disabled");
+ row.remove_css_class("drop-empty");
+ }
+ }
+ } else {
+ // Clear style
+ row.remove_css_class("drop-disabled");
+ row.remove_css_class("drop-empty");
+ row.parent().unwrap().remove_css_class("drop-active");
+ };
+
+ if let Some(category_row) = row
+ .child()
+ .and_then(|child| child.downcast::<CategoryRow>().ok())
+ {
+ category_row.set_show_label_for_category(
+ self.drop_source_type()
+ .map(CategoryType::from)
+ .unwrap_or(CategoryType::None),
+ );
+ }
+ }
+ child = widget.next_sibling();
+ }
+ }
+
+ /// Update the active state of drop targets.
+ fn update_active_drop_targets(&self, target_type: Option<RoomType>) {
+ let priv_ = imp::Sidebar::from_instance(self);
+ let mut child = priv_.listview.first_child();
+
+ while let Some(widget) = child {
+ if let Some((row, row_type)) = widget
+ .first_child()
+ .and_then(|widget| widget.downcast::<Row>().ok())
+ .and_then(|row| {
+ let row_type = row.room_type()?;
+ Some((row, row_type))
+ })
+ {
+ if target_type
+ .filter(|target_type| target_type == &row_type)
+ .is_some()
+ {
+ row.parent().unwrap().add_css_class("drop-active");
+ } else {
+ row.parent().unwrap().remove_css_class("drop-active");
+ }
+ }
+ child = widget.next_sibling();
+ }
+ }
}
impl Default for Sidebar {
diff --git a/src/session/sidebar/room_row.rs b/src/session/sidebar/room_row.rs
index 5b6540bb..f08b8593 100644
--- a/src/session/sidebar/room_row.rs
+++ b/src/session/sidebar/room_row.rs
@@ -1,7 +1,7 @@
use adw::subclass::prelude::BinImpl;
-use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
-use crate::session::room::{HighlightFlags, Room};
+use crate::session::room::{HighlightFlags, Room, RoomType};
mod imp {
use super::*;
@@ -74,6 +74,27 @@ mod imp {
}
}
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ // Allow to drag rooms
+ let drag = gtk::DragSource::builder()
+ .actions(gdk::DragAction::MOVE)
+ .build();
+ drag.connect_prepare(
+ clone!(@weak obj => @default-return None, move |drag, x, y| {
+ obj.drag_prepare(drag, x, y)
+ }),
+ );
+ drag.connect_drag_begin(clone!(@weak obj => move |_, _| {
+ obj.drag_begin();
+ }));
+ drag.connect_drag_end(clone!(@weak obj => move |_, _, _| {
+ obj.drag_end();
+ }));
+ obj.add_controller(&drag);
+ }
+
fn dispose(&self, _obj: &Self::Type) {
if let Some(room) = self.room.take() {
if let Some(id) = self.signal_handler.take() {
@@ -116,6 +137,7 @@ impl RoomRow {
if let Some(binding) = priv_.binding.take() {
binding.unbind();
}
+ priv_.display_name.remove_css_class("dim-label");
}
if let Some(ref room) = room {
@@ -138,6 +160,10 @@ impl RoomRow {
}),
)));
+ if room.category() == RoomType::Left {
+ priv_.display_name.add_css_class("dim-label");
+ }
+
self.set_highlight();
}
priv_.room.replace(room);
@@ -168,6 +194,26 @@ impl RoomRow {
};
}
}
+
+ fn drag_prepare(&self, drag: >k::DragSource, x: f64, y: f64) -> Option<gdk::ContentProvider> {
+ let paintable = gtk::WidgetPaintable::new(Some(&self.parent().unwrap()));
+ // FIXME: The hotspot coordinates don't work.
+ // See https://gitlab.gnome.org/GNOME/gtk/-/issues/2341
+ drag.set_icon(Some(&paintable), x as i32, y as i32);
+ self.room()
+ .map(|room| gdk::ContentProvider::for_value(&room.to_value()))
+ }
+
+ fn drag_begin(&self) {
+ self.parent().unwrap().add_css_class("drag");
+ let category = Some(u32::from(self.room().unwrap().category()));
+ self.activate_action("sidebar.set-drop-source-type", Some(&category.to_variant()));
+ }
+
+ fn drag_end(&self) {
+ self.activate_action("sidebar.set-drop-source-type", None);
+ self.parent().unwrap().remove_css_class("drag");
+ }
}
impl Default for RoomRow {
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
index d993d91e..41f71f05 100644
--- a/src/session/sidebar/row.rs
+++ b/src/session/sidebar/row.rs
@@ -1,12 +1,16 @@
+use std::convert::TryFrom;
+
use adw::{prelude::*, subclass::prelude::*};
-use gtk::{glib, subclass::prelude::*};
+use gtk::{gdk, glib, glib::clone, subclass::prelude::*};
use crate::session::{
- room::Room,
+ room::{Room, RoomType},
sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, VerificationRow},
verification::IdentityVerification,
};
+use super::EntryType;
+
mod imp {
use super::*;
use once_cell::sync::Lazy;
@@ -23,6 +27,10 @@ mod imp {
const NAME: &'static str = "SidebarRow";
type Type = super::Row;
type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ klass.set_css_name("sidebar-row");
+ }
}
impl ObjectImpl for Row {
@@ -72,6 +80,28 @@ mod imp {
_ => unimplemented!(),
}
}
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ // Set up drop controller
+ let drop = gtk::DropTarget::builder()
+ .actions(gdk::DragAction::MOVE)
+ .formats(&gdk::ContentFormats::for_type(Room::static_type()))
+ .build();
+ drop.connect_accept(clone!(@weak obj => @default-return false, move |_, drop| {
+ obj.drop_accept(drop)
+ }));
+ drop.connect_leave(clone!(@weak obj => move |_| {
+ obj.drop_leave();
+ }));
+ drop.connect_drop(
+ clone!(@weak obj => @default-return false, move |_, v, _, _| {
+ obj.drop_end(v)
+ }),
+ );
+ obj.add_controller(&drop);
+ }
}
impl WidgetImpl for Row {}
@@ -162,6 +192,10 @@ impl Row {
child
};
+ if entry.type_() == EntryType::Forget {
+ self.add_css_class("forget");
+ }
+
child.set_entry(Some(entry.clone()));
if let Some(list_item) = self.parent() {
@@ -186,11 +220,84 @@ impl Row {
} else {
panic!("Wrong row item: {:?}", item);
}
+ self.activate_action("sidebar.update-drop-targets", None);
}
self.notify("item");
self.notify("list-row");
}
+
+ /// Get the `RoomType` of this item.
+ ///
+ /// If this is not a `Category` or one of its children, returns `None`.
+ pub fn room_type(&self) -> Option<RoomType> {
+ let item = self.item()?;
+
+ if let Some(room) = item.downcast_ref::<Room>() {
+ Some(room.category())
+ } else {
+ item.downcast_ref::<Category>()
+ .and_then(|category| RoomType::try_from(category.type_()).ok())
+ }
+ }
+
+ /// Get the `EntryType` of this item.
+ ///
+ /// If this is not a `Entry`, returns `None`.
+ pub fn entry_type(&self) -> Option<EntryType> {
+ let item = self.item()?;
+ item.downcast_ref::<Entry>().map(|entry| entry.type_())
+ }
+
+ fn drop_accept(&self, drop: &gdk::Drop) -> bool {
+ let room = drop
+ .drag()
+ .and_then(|drag| drag.content())
+ .and_then(|content| content.value(Room::static_type()).ok())
+ .and_then(|value| value.get::<Room>().ok());
+ if let Some(room) = room {
+ if let Some(target_type) = self.room_type() {
+ if room.category().can_change_to(&target_type) {
+ self.activate_action(
+ "sidebar.set-active-drop-category",
+ Some(&Some(u32::from(target_type)).to_variant()),
+ );
+ return true;
+ }
+ } else if let Some(entry_type) = self.entry_type() {
+ if room.category() == RoomType::Left && entry_type == EntryType::Forget {
+ self.parent().unwrap().add_css_class("drop-active");
+ self.activate_action("sidebar.set-active-drop-category", None);
+ return true;
+ }
+ }
+ }
+ false
+ }
+
+ fn drop_leave(&self) {
+ self.parent().unwrap().remove_css_class("drop-active");
+ self.activate_action("sidebar.set-active-drop-category", None);
+ }
+
+ fn drop_end(&self, value: &glib::Value) -> bool {
+ let mut ret = false;
+ if let Ok(room) = value.get::<Room>() {
+ if let Some(target_type) = self.room_type() {
+ if room.category().can_change_to(&target_type) {
+ room.set_category(target_type);
+ ret = true;
+ }
+ } else if let Some(entry_type) = self.entry_type() {
+ if room.category() == RoomType::Left && entry_type == EntryType::Forget {
+ room.forget();
+ ret = true;
+ }
+ }
+ }
+ self.activate_action("sidebar.set-drop-source-type", None);
+ ret
+ }
}
impl Default for Row {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]