Browse Source

event: Subclass events to separate those that fail to deserialize

merge-requests/1327/merge
Kévin Commaille 4 years ago committed by Julian Sparber
parent
commit
36eee80e4f
  1. 5
      src/prelude.rs
  2. 30
      src/session/content/room_history/item_row.rs
  3. 18
      src/session/content/room_history/message_row/mod.rs
  4. 4
      src/session/content/room_history/mod.rs
  5. 12
      src/session/media_viewer.rs
  6. 4
      src/session/mod.rs
  7. 419
      src/session/room/event/mod.rs
  8. 519
      src/session/room/event/supported_event.rs
  9. 56
      src/session/room/event/unsupported_event.rs
  10. 227
      src/session/room/event_actions.rs
  11. 30
      src/session/room/mod.rs
  12. 12
      src/session/room/reaction_group.rs
  13. 12
      src/session/room/reaction_list.rs
  14. 184
      src/session/room/timeline/mod.rs
  15. 26
      src/session/room/timeline/timeline_item.rs

5
src/prelude.rs

@ -1 +1,4 @@
pub use crate::session::UserExt;
pub use crate::session::{
room::{EventExt, TimelineItemExt},
UserExt,
};

30
src/session/content/room_history/item_row.rs

@ -5,11 +5,12 @@ use matrix_sdk::ruma::events::AnySyncRoomEvent;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
prelude::*,
session::{
content::room_history::{message_row::MessageRow, DividerRow, RoomHistory, StateRow},
room::{
Event, EventActions, TimelineDayDivider, TimelineItem, TimelineNewMessagesDivider,
TimelineSpinner,
Event, EventActions, SupportedEvent, TimelineDayDivider, TimelineItem,
TimelineNewMessagesDivider, TimelineSpinner,
},
},
};
@ -95,7 +96,7 @@ mod imp {
.item
.borrow()
.as_ref()
.and_then(|item| item.downcast_ref::<Event>())
.and_then(|item| item.downcast_ref::<SupportedEvent>())
{
if let Some(handler) = self.notify_handler.borrow_mut().take() {
event.disconnect(handler);
@ -113,7 +114,10 @@ mod imp {
let room_history = obj.room_history();
let popover = room_history.item_context_menu().to_owned();
if event.content().is_some() {
if let Some(event) = event
.downcast_ref::<SupportedEvent>()
.filter(|event| event.content().is_some())
{
let menu_model = Self::Type::event_message_menu_model();
let reaction_chooser = room_history.item_reaction_chooser();
if popover.menu_model().as_ref() != Some(menu_model) {
@ -189,7 +193,7 @@ impl ItemRow {
.item
.borrow()
.as_ref()
.and_then(|item| item.downcast_ref::<Event>())
.and_then(|item| item.downcast_ref::<SupportedEvent>())
{
if let Some(handler) = priv_.notify_handler.borrow_mut().take() {
event.disconnect(handler);
@ -199,15 +203,13 @@ impl ItemRow {
}
if let Some(ref item) = item {
if let Some(event) = item.downcast_ref::<Event>() {
self.set_action_group(self.set_event_actions(Some(event)));
if let Some(event) = item.downcast_ref::<SupportedEvent>() {
self.set_action_group(self.set_event_actions(Some(event.upcast_ref())));
let notify_handler = event.connect_notify_local(
Some("event"),
clone!(@weak self as obj => move |event, _| {
let notify_handler =
event.connect_pure_event_notify(clone!(@weak self as obj => move |event| {
obj.set_event_widget(event);
}),
);
}));
priv_.notify_handler.replace(Some(notify_handler));
self.set_event_widget(event);
@ -265,9 +267,9 @@ impl ItemRow {
priv_.item.replace(item);
}
fn set_event_widget(&self, event: &Event) {
fn set_event_widget(&self, event: &SupportedEvent) {
match event.matrix_event() {
Some(AnySyncRoomEvent::State(state)) => {
AnySyncRoomEvent::State(state) => {
let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::<StateRow>())
{
child

18
src/session/content/room_history/message_row/mod.rs

@ -25,7 +25,7 @@ use self::{
reaction_list::MessageReactionList, reply::MessageReply, text::MessageText,
};
use crate::{
components::Avatar, prelude::*, session::room::Event, spawn, utils::filename_for_mime,
components::Avatar, prelude::*, session::room::SupportedEvent, spawn, utils::filename_for_mime,
};
mod imp {
@ -53,7 +53,7 @@ mod imp {
pub reactions: TemplateChild<MessageReactionList>,
pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
pub bindings: RefCell<Vec<glib::Binding>>,
pub event: RefCell<Option<Event>>,
pub event: RefCell<Option<SupportedEvent>>,
}
#[glib::object_subclass]
@ -144,7 +144,7 @@ impl MessageRow {
self.notify("show-header");
}
pub fn set_event(&self, event: Event) {
pub fn set_event(&self, event: SupportedEvent) {
let priv_ = self.imp();
// Remove signals and bindings from the previous event
if let Some(event) = priv_.event.take() {
@ -195,14 +195,20 @@ impl MessageRow {
priv_.event.replace(Some(event));
}
fn update_content(&self, event: &Event) {
fn update_content(&self, event: &SupportedEvent) {
if event.is_reply() {
spawn!(
glib::PRIORITY_HIGH,
clone!(@weak self as obj, @weak event => async move {
let priv_ = obj.imp();
if let Ok(Some(related_event)) = event.reply_to_event().await {
if let Some(related_event) = event
.reply_to_event()
.await
.ok()
.flatten()
.and_then(|event| event.downcast::<SupportedEvent>().ok())
{
let reply = MessageReply::new();
reply.set_related_content_sender(related_event.sender().upcast());
build_content(reply.related_content(), &related_event, true);
@ -229,7 +235,7 @@ impl Default for MessageRow {
///
/// If `compact` is true, the content should appear in a smaller format without
/// interactions, if possible.
fn build_content(parent: &adw::Bin, event: &Event, compact: bool) {
fn build_content(parent: &adw::Bin, event: &SupportedEvent, compact: bool) {
match event.content() {
Some(AnyMessageLikeEventContent::RoomMessage(message)) => {
let msgtype = if let Some(Relation::Replacement(replacement)) = message.relates_to {

4
src/session/content/room_history/mod.rs

@ -37,7 +37,7 @@ use crate::{
i18n::gettext_f,
session::{
content::{MarkdownPopover, RoomDetails},
room::{Event, Room, RoomType, Timeline, TimelineItem, TimelineState},
room::{Room, RoomType, SupportedEvent, Timeline, TimelineItem, TimelineState},
user::UserExt,
},
spawn, toast,
@ -280,7 +280,7 @@ mod imp {
.model()
.and_then(|model| model.item(pos))
.as_ref()
.and_then(|o| o.downcast_ref::<Event>())
.and_then(|o| o.downcast_ref::<SupportedEvent>())
{
if let Some(room) = obj.room() {
room.session().show_media(event);

12
src/session/media_viewer.rs

@ -6,7 +6,7 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
use super::room::EventActions;
use crate::{
components::{ContentType, MediaContentViewer},
session::room::Event,
session::room::SupportedEvent,
spawn,
utils::cache_dir,
Window,
@ -24,7 +24,7 @@ mod imp {
#[template(resource = "/org/gnome/Fractal/media-viewer.ui")]
pub struct MediaViewer {
pub fullscreened: Cell<bool>,
pub event: RefCell<Option<WeakRef<Event>>>,
pub event: RefCell<Option<WeakRef<SupportedEvent>>>,
pub body: RefCell<Option<String>>,
#[template_child]
pub flap: TemplateChild<adw::Flap>,
@ -91,7 +91,7 @@ mod imp {
"event",
"Event",
"The media event to display",
Event::static_type(),
SupportedEvent::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpecString::new(
@ -164,7 +164,7 @@ impl MediaViewer {
glib::Object::new(&[]).expect("Failed to create MediaViewer")
}
pub fn event(&self) -> Option<Event> {
pub fn event(&self) -> Option<SupportedEvent> {
self.imp()
.event
.borrow()
@ -172,7 +172,7 @@ impl MediaViewer {
.and_then(|event| event.upgrade())
}
pub fn set_event(&self, event: Option<Event>) {
pub fn set_event(&self, event: Option<SupportedEvent>) {
if event == self.event() {
return;
}
@ -226,7 +226,7 @@ impl MediaViewer {
self.imp().media.show_loading();
if let Some(event) = self.event() {
self.set_event_actions(Some(&event));
self.set_event_actions(Some(event.upcast_ref()));
if let Some(AnyMessageLikeEventContent::RoomMessage(content)) = event.content() {
match content.msgtype {
MessageType::Image(image) => {

4
src/session/mod.rs

@ -63,7 +63,7 @@ use self::{
};
pub use self::{
avatar::Avatar,
room::{Event, Room},
room::{Room, SupportedEvent},
room_creation::RoomCreation,
user::{User, UserActions, UserExt},
};
@ -896,7 +896,7 @@ impl Session {
}
/// Show a media event
pub fn show_media(&self, event: &Event) {
pub fn show_media(&self, event: &SupportedEvent) {
let priv_ = self.imp();
priv_.media_viewer.set_event(Some(event.clone()));

419
src/session/room/event/mod.rs

@ -0,0 +1,419 @@
use gtk::{glib, prelude::*, subclass::prelude::*};
use log::warn;
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId},
};
use super::{
timeline::{TimelineItem, TimelineItemImpl},
Member, Room,
};
mod supported_event;
mod unsupported_event;
pub use supported_event::SupportedEvent;
pub use unsupported_event::UnsupportedEvent;
#[derive(Clone, Debug, glib::Boxed)]
#[boxed_type(name = "BoxedSyncRoomEvent")]
pub struct BoxedSyncRoomEvent(SyncRoomEvent);
mod imp {
use std::cell::RefCell;
use glib::{object::WeakRef, Class};
use once_cell::{sync::Lazy, unsync::OnceCell};
use super::*;
#[repr(C)]
pub struct EventClass {
pub parent_class: Class<TimelineItem>,
pub source: fn(&super::Event) -> String,
pub event_id: fn(&super::Event) -> Option<OwnedEventId>,
pub sender_id: fn(&super::Event) -> Option<OwnedUserId>,
pub origin_server_ts: fn(&super::Event) -> Option<MilliSecondsSinceUnixEpoch>,
}
unsafe impl ClassStruct for EventClass {
type Type = Event;
}
pub(super) fn event_source(this: &super::Event) -> String {
let klass = this.class();
(klass.as_ref().source)(this)
}
pub(super) fn event_event_id(this: &super::Event) -> Option<OwnedEventId> {
let klass = this.class();
(klass.as_ref().event_id)(this)
}
pub(super) fn event_sender_id(this: &super::Event) -> Option<OwnedUserId> {
let klass = this.class();
(klass.as_ref().sender_id)(this)
}
pub(super) fn event_origin_server_ts(
this: &super::Event,
) -> Option<MilliSecondsSinceUnixEpoch> {
let klass = this.class();
(klass.as_ref().origin_server_ts)(this)
}
#[derive(Debug, Default)]
pub struct Event {
/// The SDK event containing encryption information and the serialized
/// event as `Raw`.
pub pure_event: RefCell<Option<SyncRoomEvent>>,
/// The room containing this `Event`.
pub room: OnceCell<WeakRef<Room>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Event {
const NAME: &'static str = "RoomEvent";
const ABSTRACT: bool = true;
type Type = super::Event;
type ParentType = TimelineItem;
type Class = EventClass;
}
impl ObjectImpl for Event {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecBoxed::new(
"pure-event",
"Pure Event",
"The pure Matrix event of this Event",
BoxedSyncRoomEvent::static_type(),
glib::ParamFlags::WRITABLE,
),
glib::ParamSpecString::new(
"source",
"Source",
"The JSON source of this Event",
None,
glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpecObject::new(
"room",
"Room",
"The room containing this Event",
Room::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
),
glib::ParamSpecString::new(
"time",
"Time",
"The locally formatted time of this Matrix event",
None,
glib::ParamFlags::READABLE,
),
]
});
PROPERTIES.as_ref()
}
fn set_property(
&self,
obj: &Self::Type,
_id: usize,
value: &glib::Value,
pspec: &glib::ParamSpec,
) {
match pspec.name() {
"pure-event" => {
let event = value.get::<BoxedSyncRoomEvent>().unwrap();
obj.set_pure_event(event.0);
}
"room" => {
self.room
.set(value.get::<Room>().unwrap().downgrade())
.unwrap();
}
_ => unimplemented!(),
}
}
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"source" => obj.source().to_value(),
"room" => obj.room().to_value(),
"time" => obj.time().to_value(),
_ => unimplemented!(),
}
}
}
impl TimelineItemImpl for Event {
fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
Some(obj.room().members().member_by_id(obj.sender_id()?))
}
fn selectable(&self, _obj: &Self::Type) -> bool {
true
}
}
}
glib::wrapper! {
/// GObject representation of a Matrix room event.
pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
}
impl Event {
/// Create an `Event` with the given pure SDK event and room.
///
/// Constructs the proper subtype according to the event.
pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
SupportedEvent::try_from_event(pure_event.clone(), room)
.map(|event| event.upcast())
.unwrap_or_else(|_| {
warn!("Failed to deserialize event: {:?}", pure_event);
UnsupportedEvent::new(pure_event, room).upcast()
})
}
}
/// Public trait containing implemented methods for everything that derives from
/// `Event`.
///
/// To override the behavior of these methods, override the corresponding method
/// of `EventImpl`.
pub trait EventExt: 'static {
/// The `Room` where this `Event` was sent.
fn room(&self) -> Room;
/// The pure SDK event of this `Event`.
fn pure_event(&self) -> SyncRoomEvent;
/// Set the pure SDK event of this `Event`.
fn set_pure_event(&self, pure_event: SyncRoomEvent);
/// The source JSON of this `Event`.
fn original_source(&self) -> String;
/// The source JSON displayed for this `Event`.
///
/// Defaults to the `original_source`.
fn source(&self) -> String;
/// The event ID of this `Event`, if it was found.
fn event_id(&self) -> Option<OwnedEventId>;
/// The user ID of the sender of this `Event`, if it was found.
fn sender_id(&self) -> Option<OwnedUserId>;
/// The timestamp on the origin server when this `Event` was sent as
/// `MilliSecondsSinceUnixEpoch`, if it was found.
fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
/// The timestamp on the origin server when this `Event` was sent as
/// `glib::DateTime`.
///
/// This is computed from the `origin_server_ts`.
fn timestamp(&self) -> Option<glib::DateTime> {
glib::DateTime::from_unix_utc(self.origin_server_ts()?.as_secs().into())
.and_then(|t| t.to_local())
.ok()
}
/// The formatted time when this `Event` was sent.
///
/// This is computed from the `origin_server_ts`.
fn time(&self) -> Option<String> {
let datetime = self.timestamp()?;
// FIXME Is there a cleaner to find out if we should use 24h format?
let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
let time = if local_time.ends_with("am") || local_time.ends_with("pm") {
// Use 12h time format (AM/PM)
datetime.format("%l∶%M %p").unwrap().to_string()
} else {
// Use 24 time format
datetime.format("%R").unwrap().to_string()
};
Some(time)
}
fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId;
}
impl<O: IsA<Event>> EventExt for O {
fn room(&self) -> Room {
self.upcast_ref()
.imp()
.room
.get()
.unwrap()
.upgrade()
.unwrap()
}
fn pure_event(&self) -> SyncRoomEvent {
self.upcast_ref().imp().pure_event.borrow().clone().unwrap()
}
fn set_pure_event(&self, pure_event: SyncRoomEvent) {
let priv_ = self.upcast_ref().imp();
priv_.pure_event.replace(Some(pure_event));
self.notify("pure-event");
self.notify("source");
}
fn original_source(&self) -> String {
let pure_event = self.upcast_ref().imp().pure_event.borrow();
let raw = pure_event.as_ref().unwrap().event.json().get();
// We have to convert it to a Value, because a RawValue cannot be
// pretty-printed.
if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw) {
serde_json::to_string_pretty(&json).unwrap()
} else {
raw.to_owned()
}
}
fn source(&self) -> String {
imp::event_source(self.upcast_ref())
}
fn event_id(&self) -> Option<OwnedEventId> {
imp::event_event_id(self.upcast_ref())
}
fn sender_id(&self) -> Option<OwnedUserId> {
imp::event_sender_id(self.upcast_ref())
}
fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
imp::event_origin_server_ts(self.upcast_ref())
}
fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_notify_local(Some("pure-event"), move |this, _| {
f(this);
})
}
}
/// Public trait that must be implemented for everything that derives from
/// `Event`.
///
/// Overriding a method from this trait overrides also its behavior in
/// `EventExt`.
pub trait EventImpl: ObjectImpl {
fn source(&self, obj: &Self::Type) -> String {
obj.dynamic_cast_ref::<Event>()
.map(|event| event.original_source())
.unwrap_or_default()
}
fn event_id(&self, obj: &Self::Type) -> Option<OwnedEventId> {
obj.dynamic_cast_ref::<Event>().and_then(|event| {
event
.imp()
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<OwnedEventId>("event_id")
.ok()
.flatten()
})
}
fn sender_id(&self, obj: &Self::Type) -> Option<OwnedUserId> {
obj.dynamic_cast_ref::<Event>().and_then(|event| {
event
.imp()
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<OwnedUserId>("sender")
.ok()
.flatten()
})
}
fn origin_server_ts(&self, obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
obj.dynamic_cast_ref::<Event>().and_then(|event| {
event
.imp()
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
.ok()
.flatten()
})
}
}
// Make `Event` subclassable.
unsafe impl<T> IsSubclassable<T> for Event
where
T: TimelineItemImpl + EventImpl,
T::Type: IsA<TimelineItem> + IsA<Event>,
{
fn class_init(class: &mut glib::Class<Self>) {
Self::parent_class_init::<T>(class.upcast_ref_mut());
let klass = class.as_mut();
klass.source = source_trampoline::<T>;
klass.event_id = event_id_trampoline::<T>;
klass.sender_id = sender_id_trampoline::<T>;
klass.origin_server_ts = origin_server_ts_trampoline::<T>;
}
}
// Virtual method implementation trampolines.
fn source_trampoline<T>(this: &Event) -> String
where
T: ObjectSubclass + EventImpl,
T::Type: IsA<Event>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().source(this)
}
fn event_id_trampoline<T>(this: &Event) -> Option<OwnedEventId>
where
T: ObjectSubclass + EventImpl,
T::Type: IsA<Event>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().event_id(this)
}
fn sender_id_trampoline<T>(this: &Event) -> Option<OwnedUserId>
where
T: ObjectSubclass + EventImpl,
T::Type: IsA<Event>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().sender_id(this)
}
fn origin_server_ts_trampoline<T>(this: &Event) -> Option<MilliSecondsSinceUnixEpoch>
where
T: ObjectSubclass + EventImpl,
T::Type: IsA<Event>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().origin_server_ts(this)
}

519
src/session/room/event.rs → src/session/room/event/supported_event.rs

@ -1,9 +1,4 @@
use gtk::{
glib,
glib::{clone, DateTime},
prelude::*,
subclass::prelude::*,
};
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use log::warn;
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
@ -16,89 +11,67 @@ use matrix_sdk::{
redaction::SyncRoomRedactionEvent,
},
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncRoomEvent,
AnySyncStateEvent, MessageLikeUnsigned, SyncMessageLikeEvent, SyncStateEvent,
AnySyncStateEvent, SyncMessageLikeEvent, SyncStateEvent,
},
serde::Raw,
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
},
Error as MatrixError,
};
use serde_json::Error as JsonError;
use super::{
timeline::{TimelineItem, TimelineItemImpl},
Member, ReactionList, Room,
};
use super::{BoxedSyncRoomEvent, Event, EventImpl};
use crate::{
prelude::*,
session::room::{
timeline::{TimelineItem, TimelineItemImpl},
Member, ReactionList, Room,
},
spawn, spawn_tokio,
utils::{filename_for_mime, media_type_uid},
};
#[derive(Clone, Debug, glib::Boxed)]
#[boxed_type(name = "BoxedSyncRoomEvent")]
pub struct BoxedSyncRoomEvent(SyncRoomEvent);
#[boxed_type(name = "BoxedAnySyncRoomEvent")]
pub struct BoxedAnySyncRoomEvent(AnySyncRoomEvent);
mod imp {
use std::cell::RefCell;
use glib::{object::WeakRef, SignalHandlerId};
use once_cell::{sync::Lazy, unsync::OnceCell};
use glib::SignalHandlerId;
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default)]
pub struct Event {
/// The deserialized matrix event
pub event: RefCell<Option<AnySyncRoomEvent>>,
/// The SDK event containing encryption information and the serialized
/// event as `Raw`
pub pure_event: RefCell<Option<SyncRoomEvent>>,
pub struct SupportedEvent {
/// The deserialized Matrix event.
pub matrix_event: RefCell<Option<AnySyncRoomEvent>>,
/// Events that replace this one, in the order they arrive.
pub replacing_events: RefCell<Vec<super::Event>>,
pub replacing_events: RefCell<Vec<super::SupportedEvent>>,
pub reactions: ReactionList,
pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
pub keys_handle: RefCell<Option<SignalHandlerId>>,
pub room: OnceCell<WeakRef<Room>>,
pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Event {
const NAME: &'static str = "RoomEvent";
type Type = super::Event;
type ParentType = TimelineItem;
impl ObjectSubclass for SupportedEvent {
const NAME: &'static str = "RoomSupportedEvent";
type Type = super::SupportedEvent;
type ParentType = Event;
}
impl ObjectImpl for Event {
impl ObjectImpl for SupportedEvent {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecBoxed::new(
"event",
"event",
"The matrix event of this Event",
BoxedSyncRoomEvent::static_type(),
"matrix-event",
"Matrix Event",
"The deserialized Matrix event of this Event",
BoxedAnySyncRoomEvent::static_type(),
glib::ParamFlags::WRITABLE,
),
glib::ParamSpecString::new(
"source",
"Source",
"The source (JSON) of this Event",
None,
glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpecObject::new(
"room",
"Room",
"The room containing this event",
Room::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
),
glib::ParamSpecString::new(
"time",
"Time",
"The locally formatted time of this matrix event",
None,
glib::ParamFlags::READABLE,
),
glib::ParamSpecObject::new(
"reactions",
"Reactions",
@ -120,14 +93,9 @@ mod imp {
pspec: &glib::ParamSpec,
) {
match pspec.name() {
"event" => {
let event = value.get::<BoxedSyncRoomEvent>().unwrap();
obj.set_matrix_pure_event(event.0);
}
"room" => {
self.room
.set(value.get::<Room>().unwrap().downgrade())
.unwrap();
"matrix-event" => {
let matrix_event = value.get::<BoxedAnySyncRoomEvent>().unwrap();
obj.set_matrix_event(matrix_event.0);
}
_ => unimplemented!(),
}
@ -135,20 +103,13 @@ mod imp {
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"source" => obj.source().to_value(),
"room" => obj.room().to_value(),
"time" => obj.time().to_value(),
"reactions" => obj.reactions().to_value(),
_ => unimplemented!(),
}
}
}
impl TimelineItemImpl for Event {
fn selectable(&self, _obj: &Self::Type) -> bool {
true
}
impl TimelineItemImpl for SupportedEvent {
fn activatable(&self, obj: &Self::Type) -> bool {
match obj.original_content() {
// The event can be activated to open the media viewer if it's an image or a video.
@ -181,72 +142,79 @@ mod imp {
}
}
fn sender(&self, obj: &Self::Type) -> Option<Member> {
Some(obj.room().members().member_by_id(obj.matrix_sender()))
fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
Some(obj.sender())
}
}
impl EventImpl for SupportedEvent {
fn source(&self, obj: &Self::Type) -> String {
obj.replacement()
.map(|replacement| replacement.source())
.unwrap_or_else(|| obj.original_source())
}
fn origin_server_ts(&self, _obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
Some(
self.matrix_event
.borrow()
.as_ref()
.unwrap()
.origin_server_ts(),
)
}
}
}
glib::wrapper! {
/// GObject representation of a Matrix room event.
pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
/// GObject representation of a supported Matrix room event.
pub struct SupportedEvent(ObjectSubclass<imp::SupportedEvent>) @extends TimelineItem, Event;
}
// TODO:
// - [ ] implement operations for events: forward, reply, delete...
impl Event {
pub fn new(event: SyncRoomEvent, room: &Room) -> Self {
let event = BoxedSyncRoomEvent(event);
glib::Object::new(&[("event", &event), ("room", room)]).expect("Failed to create Event")
}
pub fn sender(&self) -> Member {
self.room().members().member_by_id(self.matrix_sender())
}
pub fn room(&self) -> Room {
self.imp().room.get().unwrap().upgrade().unwrap()
}
// - [ ] implement operations for events: forward, reply, edit...
/// Get the matrix event
impl SupportedEvent {
/// Try to construct a new `SupportedEvent` with the given pure event and
/// room.
///
/// If the `SyncRoomEvent` couldn't be deserialized this is `None`
pub fn matrix_event(&self) -> Option<AnySyncRoomEvent> {
self.imp().event.borrow().clone()
}
pub fn matrix_pure_event(&self) -> SyncRoomEvent {
self.imp().pure_event.borrow().clone().unwrap()
}
pub fn set_matrix_pure_event(&self, event: SyncRoomEvent) {
let priv_ = self.imp();
if let Ok(deserialized) = event.event.deserialize() {
if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
SyncMessageLikeEvent::Original(_),
)) = deserialized
{
let raw_event = event.event.clone();
spawn!(clone!(@weak self as obj => async move {
obj.try_to_decrypt(raw_event.cast()).await;
}));
}
priv_.event.replace(Some(deserialized));
} else {
warn!("Failed to deserialize event: {:?}", event);
/// Returns an error if the pure event fails to deserialize.
pub fn try_from_event(pure_event: SyncRoomEvent, room: &Room) -> Result<Self, JsonError> {
let matrix_event = BoxedAnySyncRoomEvent(pure_event.event.deserialize()?);
let pure_event = BoxedSyncRoomEvent(pure_event);
Ok(glib::Object::new(&[
("pure-event", &pure_event),
("matrix-event", &matrix_event),
("room", room),
])
.expect("Failed to create SupportedEvent"))
}
/// Set the deserialized Matrix event of this `SupportedEvent`.
fn set_matrix_event(&self, matrix_event: AnySyncRoomEvent) {
if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
SyncMessageLikeEvent::Original(_),
)) = matrix_event
{
spawn!(clone!(@weak self as obj => async move {
obj.try_to_decrypt(obj.pure_event().event.cast()).await;
}));
}
priv_.pure_event.replace(Some(event));
self.notify("event");
self.imp().matrix_event.replace(Some(matrix_event));
self.notify("activatable");
self.notify("source");
}
async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
/// The deserialized Matrix event of this `SupportedEvent`.
pub fn matrix_event(&self) -> AnySyncRoomEvent {
self.imp().matrix_event.borrow().clone().unwrap()
}
/// Try to decrypt this `SupportedEvent` with the current room keys.
///
/// If decryption fails, it will be retried everytime we receive new room
/// keys.
pub async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
let priv_ = self.imp();
let room = self.room().matrix_room();
let handle = spawn_tokio!(async move { room.decrypt_event(&event).await });
@ -256,7 +224,10 @@ impl Event {
if let Some(keys_handle) = priv_.keys_handle.take() {
self.room().disconnect(keys_handle);
}
self.set_matrix_pure_event(decrypted.into());
let pure_event = SyncRoomEvent::from(decrypted);
let matrix_event = pure_event.event.deserialize().unwrap();
self.set_pure_event(pure_event);
self.set_matrix_event(matrix_event);
}
Err(error) => {
warn!("Failed to decrypt event: {}", error);
@ -264,7 +235,7 @@ impl Event {
let handle = self.room().connect_new_encryption_keys(
clone!(@weak self as obj => move |_| {
// Try to decrypt the event again
obj.set_matrix_pure_event(obj.matrix_pure_event());
obj.set_matrix_event(obj.matrix_event());
}),
);
@ -274,142 +245,50 @@ impl Event {
}
}
pub fn matrix_sender(&self) -> OwnedUserId {
let priv_ = self.imp();
if let Some(event) = priv_.event.borrow().as_ref() {
event.sender().into()
} else {
priv_
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<OwnedUserId>("sender")
.unwrap()
.unwrap()
}
}
pub fn matrix_event_id(&self) -> OwnedEventId {
let priv_ = self.imp();
if let Some(event) = priv_.event.borrow().as_ref() {
event.event_id().to_owned()
} else {
priv_
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<OwnedEventId>("event_id")
.unwrap()
.unwrap()
}
}
pub fn matrix_transaction_id(&self) -> Option<OwnedTransactionId> {
/// The event ID of this `SupportedEvent`.
pub fn event_id(&self) -> OwnedEventId {
self.imp()
.pure_event
.matrix_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<MessageLikeUnsigned>("unsigned")
.ok()
.flatten()
.and_then(|unsigned| unsigned.transaction_id)
.event_id()
.to_owned()
}
/// The original timestamp of this event.
pub fn matrix_origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
let priv_ = self.imp();
if let Some(event) = priv_.event.borrow().as_ref() {
event.origin_server_ts().to_owned()
} else {
priv_
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
.unwrap()
.unwrap()
}
/// The user ID of the sender of this `SupportedEvent`.
pub fn sender_id(&self) -> OwnedUserId {
self.imp()
.matrix_event
.borrow()
.as_ref()
.unwrap()
.sender()
.to_owned()
}
/// The pretty-formatted JSON of this matrix event.
pub fn original_source(&self) -> String {
// We have to convert it to a Value, because a RawValue cannot be
// pretty-printed.
let json: serde_json::Value = serde_json::from_str(
self.imp()
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.json()
.get(),
)
.unwrap();
serde_json::to_string_pretty(&json).unwrap()
/// The room member that sent this `SupportedEvent`.
pub fn sender(&self) -> Member {
self.room().members().member_by_id(self.sender_id())
}
/// The pretty-formatted JSON used for this matrix event.
/// The transaction ID of this `SupportedEvent`, if any.
///
/// If this matrix event has been replaced, returns the replacing `Event`'s
/// source.
pub fn source(&self) -> String {
self.replacement()
.map(|replacement| replacement.source())
.unwrap_or_else(|| self.original_source())
}
pub fn timestamp(&self) -> DateTime {
let priv_ = self.imp();
let ts = if let Some(event) = priv_.event.borrow().as_ref() {
event.origin_server_ts().as_secs()
} else {
priv_
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
.unwrap()
.unwrap()
.as_secs()
};
DateTime::from_unix_utc(ts.into())
.and_then(|t| t.to_local())
/// This is the random string sent with the event, if it was sent from this
/// session.
pub fn transaction_id(&self) -> Option<OwnedTransactionId> {
self.imp()
.matrix_event
.borrow()
.as_ref()
.unwrap()
.transaction_id()
.map(|txn_id| txn_id.to_owned())
}
pub fn time(&self) -> String {
let datetime = self.timestamp();
// FIXME Is there a cleaner way to do that?
let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
if local_time.ends_with("am") || local_time.ends_with("pm") {
// Use 12h time format (AM/PM)
datetime.format("%l∶%M %p").unwrap().to_string()
} else {
// Use 24 time format
datetime.format("%R").unwrap().to_string()
}
}
/// Find the related event if any
pub fn related_matrix_event(&self) -> Option<OwnedEventId> {
match self.imp().event.borrow().as_ref()? {
/// The ID of the event this `SupportedEvent` relates to, if any.
pub fn related_event_id(&self) -> Option<OwnedEventId> {
match self.imp().matrix_event.borrow().as_ref()? {
AnySyncRoomEvent::MessageLike(ref message) => match message {
AnySyncMessageLikeEvent::RoomRedaction(SyncRoomRedactionEvent::Original(event)) => {
Some(event.redacts.clone())
@ -420,8 +299,6 @@ impl Event {
AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(event)) => {
match &event.content.relates_to {
Some(relates_to) => match relates_to {
// TODO: Figure out Relation::Annotation(), Relation::Reference() but
// they are pre-specs for now See: https://github.com/uhoreg/matrix-doc/blob/aggregations-reactions/proposals/2677-reactions.md
Relation::Reply { in_reply_to } => Some(in_reply_to.event_id.clone()),
Relation::Replacement(replacement) => {
Some(replacement.event_id.clone())
@ -438,63 +315,26 @@ impl Event {
}
}
/// Whether this event is hidden from the user or displayed in the room
/// history.
pub fn is_hidden_event(&self) -> bool {
let priv_ = self.imp();
if self.related_matrix_event().is_some() {
if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(message),
))) = priv_.event.borrow().as_ref()
{
if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
return false;
}
}
return true;
}
let event = priv_.event.borrow();
// List of all events to be shown.
match event.as_ref() {
Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
message,
AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
| AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
| AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
),
Some(AnySyncRoomEvent::State(state)) => !matches!(
state,
AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
),
_ => true,
}
}
/// Whether this is a replacing `Event`.
/// Whether this `SupportedEvent` replaces another one.
///
/// Replacing matrix events are:
/// Replacing Matrix events are:
///
/// - `RoomRedaction`
/// - `RoomMessage` with `Relation::Replacement`
pub fn is_replacing_event(&self) -> bool {
match self.matrix_event() {
Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
match self.imp().matrix_event.borrow().as_ref().unwrap() {
AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(message),
))) => {
)) => {
matches!(message.content.relates_to, Some(Relation::Replacement(_)))
}
Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))) => true,
AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_)) => true,
_ => false,
}
}
pub fn prepend_replacing_events(&self, events: Vec<Event>) {
/// Prepend the given events to the list of replacing events.
pub fn prepend_replacing_events(&self, events: Vec<SupportedEvent>) {
let priv_ = self.imp();
priv_.replacing_events.borrow_mut().splice(..0, events);
if self.redacted() {
@ -502,7 +342,8 @@ impl Event {
}
}
pub fn append_replacing_events(&self, events: Vec<Event>) {
/// Append the given events to the list of replacing events.
pub fn append_replacing_events(&self, events: Vec<SupportedEvent>) {
let priv_ = self.imp();
let old_replacement = self.replacement();
@ -536,15 +377,14 @@ impl Event {
}
}
pub fn replacing_events(&self) -> Vec<Event> {
/// The replacing events of this `SupportedEvent`, in the order of the
/// timeline.
pub fn replacing_events(&self) -> Vec<SupportedEvent> {
self.imp().replacing_events.borrow().clone()
}
/// The `Event` that replaces this one, if any.
///
/// If this matrix event has been redacted or replaced, returns the
/// corresponding `Event`, otherwise returns `None`.
pub fn replacement(&self) -> Option<Event> {
/// The event that replaces this `SupportedEvent`, if any.
pub fn replacement(&self) -> Option<SupportedEvent> {
self.replacing_events()
.iter()
.rev()
@ -552,21 +392,19 @@ impl Event {
.cloned()
}
/// Whether this matrix event has been redacted.
/// Whether this `SupportedEvent` has been redacted.
pub fn redacted(&self) -> bool {
self.replacement()
.filter(|event| {
matches!(
event.matrix_event(),
Some(AnySyncRoomEvent::MessageLike(
AnySyncMessageLikeEvent::RoomRedaction(_)
))
AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))
)
})
.is_some()
}
/// Whether this is a reaction.
/// Whether this `SupportedEvent` is a reaction.
pub fn is_reaction(&self) -> bool {
matches!(
self.original_content(),
@ -574,41 +412,38 @@ impl Event {
)
}
/// The reactions for this event.
/// The reactions for this `SupportedEvent`.
pub fn reactions(&self) -> &ReactionList {
&self.imp().reactions
}
/// Add reactions to this event.
pub fn add_reactions(&self, reactions: Vec<Event>) {
/// Add reactions to this `SupportedEvent`.
pub fn add_reactions(&self, reactions: Vec<SupportedEvent>) {
if !self.redacted() {
self.imp().reactions.add_reactions(reactions);
}
}
/// The content of this matrix event.
///
/// Returns `None` if this is not a message-like event.
/// The content of this `SupportedEvent`, if this is a message-like event.
pub fn original_content(&self) -> Option<AnyMessageLikeEventContent> {
match self.matrix_event()? {
match self.matrix_event() {
AnySyncRoomEvent::MessageLike(message) => message.original_content(),
_ => None,
}
}
/// The content to display for this `Event`.
///
/// If this matrix event has been replaced, returns the replacing `Event`'s
/// content.
/// The content to display for this `SupportedEvent`, if this is a
/// message-like event.
///
/// Returns `None` if this is not a message-like event.
/// If this event has been replaced, returns the replacing
/// `SupportedEvent`'s content.
pub fn content(&self) -> Option<AnyMessageLikeEventContent> {
self.replacement()
.and_then(|replacement| replacement.content())
.or_else(|| self.original_content())
}
/// The content of a media message.
/// Fetch the content of the media message in this `SupportedEvent`.
///
/// Compatible events:
///
@ -617,9 +452,11 @@ impl Event {
/// - Video message (`MessageType::Video`).
/// - Audio message (`MessageType::Audio`).
///
/// Returns `Ok((uid, filename, binary_content))` on success, `Err` if an
/// error occurred while fetching the content. Panics on an incompatible
/// event. `uid` is a unique identifier for this media.
/// Returns `Ok((uid, filename, binary_content))` on success. `uid` is a
/// unique identifier for this media.
///
/// Returns `Err` if an error occurred while fetching the content. Panics on
/// an incompatible event.
pub async fn get_media_content(&self) -> Result<(String, String, Vec<u8>), matrix_sdk::Error> {
if let AnyMessageLikeEventContent::RoomMessage(content) = self.original_content().unwrap() {
let client = self.room().session().client();
@ -704,7 +541,7 @@ impl Event {
panic!("Trying to get the media content of an event of incompatible type");
}
/// Get the id of the event this `Event` replies to, if any.
/// Get the ID of the event this `SupportedEvent` replies to, if any.
pub fn reply_to_id(&self) -> Option<OwnedEventId> {
match self.original_content()? {
AnyMessageLikeEventContent::RoomMessage(message) => {
@ -718,12 +555,12 @@ impl Event {
}
}
/// Whether this `Event` is a reply to another event.
/// Whether this `SupportedEvent` is a reply to another event.
pub fn is_reply(&self) -> bool {
self.reply_to_id().is_some()
}
/// Get the `Event` this `Event` replies to, if any.
/// Get the `Event` this `SupportedEvent` replies to, if any.
///
/// Returns `Ok(None)` if this event is not a reply.
pub async fn reply_to_event(&self) -> Result<Option<Event>, MatrixError> {
@ -740,4 +577,40 @@ impl Event {
.await?;
Ok(Some(event))
}
/// Whether this `SupportedEvent` is hidden from the user or displayed in
/// the room history.
pub fn is_hidden_event(&self) -> bool {
let priv_ = self.imp();
if self.related_event_id().is_some() {
if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(message),
))) = priv_.matrix_event.borrow().as_ref()
{
if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
return false;
}
}
return true;
}
// List of all events to be shown.
match priv_.matrix_event.borrow().as_ref() {
Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
message,
AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
| AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
| AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
),
Some(AnySyncRoomEvent::State(state)) => !matches!(
state,
AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
| AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
),
_ => true,
}
}
}

56
src/session/room/event/unsupported_event.rs

@ -0,0 +1,56 @@
use gtk::{glib, prelude::*, subclass::prelude::*};
use matrix_sdk::{deserialized_responses::SyncRoomEvent, ruma::events::RoomEventType};
use super::{BoxedSyncRoomEvent, Event, EventImpl};
use crate::session::room::{
timeline::{TimelineItem, TimelineItemImpl},
Room,
};
mod imp {
use super::*;
#[derive(Debug, Default)]
pub struct UnsupportedEvent {}
#[glib::object_subclass]
impl ObjectSubclass for UnsupportedEvent {
const NAME: &'static str = "RoomUnsupportedEvent";
type Type = super::UnsupportedEvent;
type ParentType = Event;
}
impl ObjectImpl for UnsupportedEvent {}
impl TimelineItemImpl for UnsupportedEvent {}
impl EventImpl for UnsupportedEvent {}
}
glib::wrapper! {
/// GObject representation of an unsupported Matrix room event.
pub struct UnsupportedEvent(ObjectSubclass<imp::UnsupportedEvent>) @extends TimelineItem, Event;
}
impl UnsupportedEvent {
/// Construct an `UnsupportedEvent` from the given pure event and room.
pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
let pure_event = BoxedSyncRoomEvent(pure_event);
glib::Object::new(&[("pure-event", &pure_event), ("room", room)])
.expect("Failed to create UnsupportedEvent")
}
/// The type of this `UnsupportedEvent`, if the field is found.
pub fn event_type(&self) -> Option<RoomEventType> {
self.upcast_ref::<Event>()
.imp()
.pure_event
.borrow()
.as_ref()
.unwrap()
.event
.get_field::<RoomEventType>("type")
.ok()
.flatten()
}
}

227
src/session/room/event_actions.rs

@ -5,10 +5,10 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
use once_cell::sync::Lazy;
use crate::{
prelude::*,
session::{
event_source_dialog::EventSourceDialog,
room::{Event, RoomAction},
user::UserExt,
room::{Event, RoomAction, SupportedEvent},
},
spawn, toast,
utils::cache_dir,
@ -90,119 +90,122 @@ where
})
);
if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
let user_id = event
.room()
.session()
.user()
.map(|user| user.user_id())
.unwrap();
let user = event.room().members().member_by_id(user_id);
if event.sender() == user
|| event
if let Some(event) = event.downcast_ref::<SupportedEvent>() {
if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
let user_id = event
.room()
.power_levels()
.min_level_for_room_action(&RoomAction::Redact)
<= user.power_level()
{
// Remove message
gtk_macros::action!(
&action_group,
"remove",
clone!(@weak event, => move |_, _| {
event.room().redact(event.matrix_event_id(), None);
})
);
}
// Send/redact a reaction
gtk_macros::action!(
&action_group,
"toggle-reaction",
Some(&String::static_variant_type()),
clone!(@weak event => move |_, variant| {
let key: String = variant.unwrap().get().unwrap();
let room = event.room();
let reaction_group = event.reactions().reaction_group_by_key(&key);
if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
// The user already sent that reaction, redact it.
room.redact(reaction.matrix_event_id(), None);
} else {
// The user didn't send that redaction, send it.
room.send_reaction(key, event.matrix_event_id());
}
})
);
match message.msgtype {
// Copy Text-Message
MessageType::Text(text_message) => {
.session()
.user()
.map(|user| user.user_id())
.unwrap();
let user = event.room().members().member_by_id(user_id);
if event.sender() == user
|| event
.room()
.power_levels()
.min_level_for_room_action(&RoomAction::Redact)
<= user.power_level()
{
// Remove message
gtk_macros::action!(
&action_group,
"copy-text",
clone!(@weak self as widget => move |_, _| {
widget.clipboard().set_text(&text_message.body);
"remove",
clone!(@weak event, => move |_, _| {
event.room().redact(event.event_id(), None);
})
);
}
MessageType::File(_) => {
// Save message's file
gtk_macros::action!(
&action_group,
"file-save",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
// Send/redact a reaction
gtk_macros::action!(
&action_group,
"toggle-reaction",
Some(&String::static_variant_type()),
clone!(@weak event => move |_, variant| {
let key: String = variant.unwrap().get().unwrap();
let room = event.room();
// Open message's file
gtk_macros::action!(
&action_group,
"file-open",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.open_event_file(event);
})
);
}
MessageType::Emote(message) => {
gtk_macros::action!(
&action_group,
"copy-text",
clone!(@weak self as widget, @weak event => move |_, _| {
let display_name = event.sender().display_name();
let message = display_name + " " + &message.body;
widget.clipboard().set_text(&message);
})
);
}
MessageType::Image(_) => {
gtk_macros::action!(
&action_group,
"save-image",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
}
MessageType::Video(_) => {
gtk_macros::action!(
&action_group,
"save-video",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
}
MessageType::Audio(_) => {
gtk_macros::action!(
&action_group,
"save-audio",
clone!(@weak self as widget, @weak event => move |_, _| {
let reaction_group = event.reactions().reaction_group_by_key(&key);
if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
// The user already sent that reaction, redact it.
room.redact(reaction.event_id(), None);
} else {
// The user didn't send that redaction, send it.
room.send_reaction(key, event.event_id());
}
})
);
match message.msgtype {
// Copy Text-Message
MessageType::Text(text_message) => {
gtk_macros::action!(
&action_group,
"copy-text",
clone!(@weak self as widget => move |_, _| {
widget.clipboard().set_text(&text_message.body);
})
);
}
MessageType::File(_) => {
// Save message's file
gtk_macros::action!(
&action_group,
"file-save",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
})
);
// Open message's file
gtk_macros::action!(
&action_group,
"file-open",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.open_event_file(event);
})
);
}
MessageType::Emote(message) => {
gtk_macros::action!(
&action_group,
"copy-text",
clone!(@weak self as widget, @weak event => move |_, _| {
let display_name = event.sender().display_name();
let message = display_name + " " + &message.body;
widget.clipboard().set_text(&message);
})
);
}
MessageType::Image(_) => {
gtk_macros::action!(
&action_group,
"save-image",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
}
MessageType::Video(_) => {
gtk_macros::action!(
&action_group,
"save-video",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
}
MessageType::Audio(_) => {
gtk_macros::action!(
&action_group,
"save-audio",
clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
})
);
}
_ => {}
}
_ => {}
}
}
self.insert_action_group("event", Some(&action_group));
@ -211,9 +214,9 @@ where
/// Save the file in `event`.
///
/// See `Event::get_media_content` for compatible events. Panics on an
/// incompatible event.
fn save_event_file(&self, event: Event) {
/// See [`SupportedEvent::get_media_content()`] for compatible events.
/// Panics on an incompatible event.
fn save_event_file(&self, event: SupportedEvent) {
let window: Window = self.root().unwrap().downcast().unwrap();
spawn!(
glib::PRIORITY_LOW,
@ -260,9 +263,9 @@ where
/// Open the file in `event`.
///
/// See `Event::get_media_content` for compatible events. Panics on an
/// incompatible event.
fn open_event_file(&self, event: Event) {
/// See [`SupportedEvent::get_media_content()`] for compatible events.
/// Panics on an incompatible event.
fn open_event_file(&self, event: SupportedEvent) {
spawn!(
glib::PRIORITY_LOW,
clone!(@weak self as obj => async move {

30
src/session/room/mod.rs

@ -46,7 +46,7 @@ use matrix_sdk::{
use ruma::events::SyncEphemeralRoomEvent;
pub use self::{
event::Event,
event::*,
event_actions::EventActions,
highlight_flags::HighlightFlags,
member::{Member, Membership},
@ -55,10 +55,7 @@ pub use self::{
reaction_group::ReactionGroup,
reaction_list::ReactionList,
room_type::RoomType,
timeline::{
Timeline, TimelineDayDivider, TimelineItem, TimelineItemExt, TimelineNewMessagesDivider,
TimelineSpinner, TimelineState,
},
timeline::*,
};
use super::verification::IdentityVerification;
use crate::{
@ -103,7 +100,7 @@ mod imp {
/// The event of the user's read receipt for this room.
pub read_receipt: RefCell<Option<Event>>,
/// The latest read event in the room's timeline.
pub latest_read: RefCell<Option<Event>>,
pub latest_read: RefCell<Option<SupportedEvent>>,
/// The highlight state of the room,
pub highlight: Cell<HighlightFlags>,
pub predecessor: OnceCell<OwnedRoomId>,
@ -827,7 +824,7 @@ impl Room {
if Some(event_id)
== self
.read_receipt()
.map(|event| event.matrix_event_id())
.and_then(|event| event.event_id())
.as_deref()
{
return;
@ -872,7 +869,7 @@ impl Room {
timeline
.item(i)
.as_ref()
.and_then(|obj| obj.downcast_ref::<Event>())
.and_then(|obj| obj.downcast_ref::<SupportedEvent>())
.and_then(|event| {
// The user sent the event so it's the latest read event.
// Necessary because we don't get read receipts for the user's own events.
@ -886,9 +883,8 @@ impl Room {
}
// The event is older than the read receipt so it has been read.
if event.matrix_event().filter(count_as_unread).is_some()
&& event.matrix_origin_server_ts()
<= read_receipt.matrix_origin_server_ts()
if count_as_unread(&event.matrix_event())
&& event.origin_server_ts() <= read_receipt.origin_server_ts()
{
return Some(event.to_owned());
}
@ -902,12 +898,12 @@ impl Room {
}
/// The latest read event in the room's timeline.
pub fn latest_read(&self) -> Option<Event> {
pub fn latest_read(&self) -> Option<SupportedEvent> {
self.imp().latest_read.borrow().clone()
}
/// Set the latest read event.
fn set_latest_read(&self, latest_read: Option<Event>) {
fn set_latest_read(&self, latest_read: Option<SupportedEvent>) {
if latest_read == self.latest_read() {
return;
}
@ -974,7 +970,7 @@ impl Room {
if let Some(event) = timeline
.item(i)
.as_ref()
.and_then(|obj| obj.downcast_ref::<Event>())
.and_then(|obj| obj.downcast_ref::<SupportedEvent>())
{
// This is the event corresponding to the read receipt so there's no unread
// messages.
@ -983,7 +979,7 @@ impl Room {
}
// The user hasn't read the latest message.
if event.matrix_event().filter(count_as_unread).is_some() {
if count_as_unread(&event.matrix_event()) {
return false;
}
}
@ -1306,7 +1302,7 @@ impl Room {
};
let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&matrix_event).unwrap().cast();
let event = Event::new(raw_event.into(), self);
let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
self.imp()
.timeline
.get()
@ -1357,7 +1353,7 @@ impl Room {
if let MatrixRoom::Joined(matrix_room) = self.matrix_room() {
let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&event).unwrap().cast();
let event = Event::new(raw_event.into(), self);
let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
self.imp()
.timeline
.get()

12
src/session/room/reaction_group.rs

@ -1,7 +1,7 @@
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use super::Event;
use crate::session::UserExt;
use super::SupportedEvent;
use crate::prelude::*;
mod imp {
use std::cell::RefCell;
@ -16,7 +16,7 @@ mod imp {
/// The key of the group.
pub key: OnceCell<String>,
/// The reactions in the group.
pub reactions: RefCell<IndexSet<Event>>,
pub reactions: RefCell<IndexSet<SupportedEvent>>,
}
#[glib::object_subclass]
@ -108,14 +108,14 @@ impl ReactionGroup {
}
/// The reaction in this group sent by this user, if any.
pub fn user_reaction(&self) -> Option<Event> {
pub fn user_reaction(&self) -> Option<SupportedEvent> {
let reactions = self.imp().reactions.borrow();
if let Some(user) = reactions
.first()
.and_then(|event| event.room().session().user().cloned())
{
for reaction in reactions.iter().filter(|event| !event.redacted()) {
if reaction.matrix_sender() == user.user_id() {
if reaction.sender_id() == user.user_id() {
return Some(reaction.clone());
}
}
@ -129,7 +129,7 @@ impl ReactionGroup {
}
/// Add new reactions to this group.
pub fn add_reactions(&self, new_reactions: Vec<Event>) {
pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
let prev_has_user = self.has_user();
let mut added_reactions = Vec::with_capacity(new_reactions.len());

12
src/session/room/reaction_list.rs

@ -3,7 +3,7 @@ use std::collections::HashMap;
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::events::AnyMessageLikeEventContent;
use super::{Event, ReactionGroup};
use super::{ReactionGroup, SupportedEvent};
mod imp {
use std::cell::RefCell;
@ -45,7 +45,7 @@ mod imp {
}
glib::wrapper! {
/// List of all `ReactionGroup`s for an `Event`. Implements `ListModel`.
/// List of all `ReactionGroup`s for a `SupportedEvent`. Implements `ListModel`.
///
/// `ReactionGroup`s are sorted in "insertion order".
pub struct ReactionList(ObjectSubclass<imp::ReactionList>)
@ -57,15 +57,15 @@ impl ReactionList {
glib::Object::new(&[]).expect("Failed to create ReactionList")
}
/// Add reactions with the given reaction `Event`s.
/// Add reactions with the given reaction `SupportedEvent`s.
///
/// Ignores `Event`s that are not reactions.
pub fn add_reactions(&self, new_reactions: Vec<Event>) {
/// Ignores `SupportedEvent`s that are not reactions.
pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
let mut reactions = self.imp().reactions.borrow_mut();
let prev_len = reactions.len();
// Group reactions by key
let mut grouped_reactions: HashMap<String, Vec<Event>> = HashMap::new();
let mut grouped_reactions: HashMap<String, Vec<SupportedEvent>> = HashMap::new();
for event in new_reactions {
if let Some(AnyMessageLikeEventContent::Reaction(reaction)) = event.content() {
let relation = reaction.relates_to;

184
src/session/room/timeline/mod.rs

@ -23,10 +23,8 @@ pub use timeline_new_messages_divider::TimelineNewMessagesDivider;
pub use timeline_spinner::TimelineSpinner;
use tokio::task::JoinHandle;
use crate::{
session::room::{Event, Room},
spawn_tokio,
};
use super::{Event, Room, SupportedEvent, UnsupportedEvent};
use crate::{prelude::*, spawn_tokio};
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[repr(u32)]
@ -195,7 +193,7 @@ impl Timeline {
let mut previous_timestamp = if position > 0 {
list.get(position - 1)
.and_then(|item| item.downcast_ref::<Event>())
.map(|event| event.timestamp())
.and_then(|event| event.timestamp())
} else {
None
};
@ -204,7 +202,7 @@ impl Timeline {
for current in list.range(position..position + added) {
if let Some(current_timestamp) = current
.downcast_ref::<Event>()
.map(|event| event.timestamp())
.and_then(|event| event.timestamp())
{
if Some(current_timestamp.ymd()) != previous_timestamp.as_ref().map(|t| t.ymd())
{
@ -252,13 +250,13 @@ impl Timeline {
let mut previous_sender = if position > 0 {
list.get(position - 1)
.filter(|item| item.can_hide_header())
.and_then(|item| item.sender())
.and_then(|item| item.event_sender())
} else {
None
};
for current in list.range(position..position + added) {
let current_sender = current.sender();
let current_sender = current.event_sender();
if !current.can_hide_header() {
current.set_show_header(false);
@ -281,7 +279,7 @@ impl Timeline {
// Once the sender changes we can be sure that the visibility for headers will
// be correct
if next.sender() != previous_sender {
if next.event_sender() != previous_sender {
next.set_show_header(true);
break;
}
@ -300,15 +298,16 @@ impl Timeline {
for event in list
.range(position as usize..(position + added) as usize)
.filter_map(|item| item.downcast_ref::<Event>())
.filter_map(|item| item.downcast_ref::<SupportedEvent>())
{
if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
let mut replacing_events: Vec<Event> = vec![];
let mut reactions: Vec<Event> = vec![];
if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
let mut replacing_events = vec![];
let mut reactions = vec![];
for relation_event_id in relates_to {
let relation = self
.event_by_id(&relation_event_id)
.and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared");
if relation.is_replacing_event() {
@ -326,7 +325,7 @@ impl Timeline {
event.add_reactions(reactions);
if event.redacted() {
redacted_events.insert(event.matrix_event_id());
redacted_events.insert(event.event_id());
}
}
}
@ -352,8 +351,8 @@ impl Timeline {
let mut list = list.iter();
while let Some(item) = list.next_back() {
if let Some(event) = item.downcast_ref::<Event>() {
if redacted_events.remove(&event.matrix_event_id()) {
if let Some(event) = item.downcast_ref::<SupportedEvent>() {
if redacted_events.remove(&event.event_id()) {
redacted_events_pos.push(i - 1);
}
if redacted_events.is_empty() {
@ -412,20 +411,21 @@ impl Timeline {
}
}
fn add_hidden_events(&self, events: Vec<Event>, at_front: bool) {
fn add_hidden_events(&self, events: Vec<SupportedEvent>, at_front: bool) {
let priv_ = self.imp();
let mut relates_to_events = priv_.relates_to_events.borrow_mut();
// Group events by related event
let mut new_relations: HashMap<OwnedEventId, Vec<Event>> = HashMap::new();
let mut new_relations: HashMap<_, Vec<_>> = HashMap::new();
for event in events {
if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
let mut replacing_events: Vec<Event> = vec![];
let mut reactions: Vec<Event> = vec![];
if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
let mut replacing_events = vec![];
let mut reactions = vec![];
for relation_event_id in relates_to {
let relation = self
.event_by_id(&relation_event_id)
.and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared");
if relation.is_replacing_event() {
@ -443,7 +443,7 @@ impl Timeline {
event.add_reactions(reactions);
}
if let Some(relates_to_event) = event.related_matrix_event() {
if let Some(relates_to_event) = event.related_event_id() {
let relations = new_relations.entry(relates_to_event).or_default();
relations.push(event);
}
@ -454,53 +454,54 @@ impl Timeline {
for (relates_to_event_id, new_relations) in new_relations {
if let Some(relates_to_event) = self.event_by_id(&relates_to_event_id) {
// Get the relations in relates_to_event otherwise they will be added in
// in items_changed and they might not be added at the right place.
let mut relations: Vec<Event> = relates_to_events
.remove(&relates_to_event.matrix_event_id())
// items_changed and they might not be added at the right place.
let mut relations: Vec<_> = relates_to_events
.remove(&relates_to_event_id)
.unwrap_or_default()
.into_iter()
.map(|event_id| {
self.event_by_id(&event_id)
.and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared")
})
.collect();
if at_front {
relations.splice(..0, new_relations);
} else {
relations.extend(new_relations);
}
if let Some(relates_to_event) = relates_to_event.downcast_ref::<SupportedEvent>() {
if at_front {
relations.splice(..0, new_relations);
} else {
relations.extend(new_relations);
}
let mut replacing_events: Vec<Event> = vec![];
let mut reactions: Vec<Event> = vec![];
let mut replacing_events = vec![];
let mut reactions = vec![];
for relation in relations {
if relation.is_replacing_event() {
replacing_events.push(relation);
} else if relation.is_reaction() {
reactions.push(relation);
for relation in relations {
if relation.is_replacing_event() {
replacing_events.push(relation);
} else if relation.is_reaction() {
reactions.push(relation);
}
}
}
if !at_front || relates_to_event.replacing_events().is_empty() {
relates_to_event.append_replacing_events(replacing_events);
} else {
relates_to_event.prepend_replacing_events(replacing_events);
}
relates_to_event.add_reactions(reactions);
if !at_front || relates_to_event.replacing_events().is_empty() {
relates_to_event.append_replacing_events(replacing_events);
} else {
relates_to_event.prepend_replacing_events(replacing_events);
}
relates_to_event.add_reactions(reactions);
if relates_to_event.redacted() {
redacted_events.insert(relates_to_event.matrix_event_id());
if relates_to_event.redacted() {
redacted_events.insert(relates_to_event.event_id());
}
}
} else {
// Store the new event if the `related_to` event isn't known, we will update the
// `relates_to` once the `related_to` event is added to the list
let relates_to_event = relates_to_events.entry(relates_to_event_id).or_default();
let relations_ids: Vec<OwnedEventId> = new_relations
.iter()
.map(|event| event.matrix_event_id())
.collect();
let relations_ids: Vec<_> =
new_relations.iter().map(|event| event.event_id()).collect();
if at_front {
relates_to_event.splice(..0, relations_ids);
} else {
@ -648,30 +649,43 @@ impl Timeline {
};
let mut pending_events = priv_.pending_events.borrow_mut();
let mut hidden_events: Vec<Event> = vec![];
for event in batch.into_iter() {
let event_id = event.matrix_event_id();
let mut hidden_events = vec![];
if let Some(pending_id) = event
.matrix_transaction_id()
.and_then(|txn_id| pending_events.remove(&txn_id))
for event in batch {
if let Some(event_id) = event
.downcast_ref::<UnsupportedEvent>()
.and_then(|event| event.event_id())
{
let mut event_map = priv_.event_map.borrow_mut();
if let Some(pending_event) = event_map.remove(&pending_id) {
pending_event.set_matrix_pure_event(event.matrix_pure_event());
event_map.insert(event_id, pending_event);
};
priv_.event_map.borrow_mut().insert(event_id, event);
added -= 1;
} else {
priv_.event_map.borrow_mut().insert(event_id, event.clone());
if event.is_hidden_event() {
hidden_events.push(event);
} else if let Ok(event) = event.downcast::<SupportedEvent>() {
let event_id = event.event_id();
if let Some(pending_id) = event
.transaction_id()
.and_then(|txn_id| pending_events.remove(&txn_id))
{
let mut event_map = priv_.event_map.borrow_mut();
if let Some(pending_event) = event_map.remove(&pending_id) {
pending_event.set_pure_event(event.pure_event());
event_map.insert(event_id, pending_event);
};
added -= 1;
} else {
priv_.list.borrow_mut().push_back(event.upcast());
priv_
.event_map
.borrow_mut()
.insert(event_id, event.clone().upcast());
if event.is_hidden_event() {
hidden_events.push(event);
added -= 1;
} else {
priv_.list.borrow_mut().push_back(event.upcast());
}
}
} else {
added -= 1;
}
}
@ -684,18 +698,18 @@ impl Timeline {
}
/// Append an event that wasn't yet fully sent and received via a sync
pub fn append_pending(&self, txn_id: &TransactionId, event: Event) {
pub fn append_pending(&self, txn_id: &TransactionId, event: SupportedEvent) {
let priv_ = self.imp();
priv_
.event_map
.borrow_mut()
.insert(event.matrix_event_id(), event.clone());
.insert(event.event_id(), event.clone().upcast());
priv_
.pending_events
.borrow_mut()
.insert(txn_id.to_owned(), event.matrix_event_id());
.insert(txn_id.to_owned(), event.event_id());
let index = {
let mut list = priv_.list.borrow_mut();
@ -756,22 +770,32 @@ impl Timeline {
let mut added = batch.len();
{
let mut hidden_events: Vec<Event> = vec![];
let mut hidden_events: Vec<_> = vec![];
// Extend the size of the list so that rust doesn't need to reallocate memory
// multiple times
priv_.list.borrow_mut().reserve(added);
for event in batch {
priv_
.event_map
.borrow_mut()
.insert(event.matrix_event_id(), event.clone());
if event.is_hidden_event() {
hidden_events.push(event);
if let Some(event_id) = event
.downcast_ref::<UnsupportedEvent>()
.and_then(|event| event.event_id())
{
priv_.event_map.borrow_mut().insert(event_id, event);
added -= 1;
} else if let Ok(event) = event.downcast::<SupportedEvent>() {
priv_
.event_map
.borrow_mut()
.insert(event.event_id(), event.clone().upcast());
if event.is_hidden_event() {
hidden_events.push(event);
added -= 1;
} else {
priv_.list.borrow_mut().push_front(event.upcast());
}
} else {
priv_.list.borrow_mut().push_front(event.upcast());
added -= 1;
}
}
self.add_hidden_events(hidden_events, true);

26
src/session/room/timeline/timeline_item.rs

@ -15,7 +15,7 @@ mod imp {
pub selectable: fn(&super::TimelineItem) -> bool,
pub activatable: fn(&super::TimelineItem) -> bool,
pub can_hide_header: fn(&super::TimelineItem) -> bool,
pub sender: fn(&super::TimelineItem) -> Option<Member>,
pub event_sender: fn(&super::TimelineItem) -> Option<Member>,
}
unsafe impl ClassStruct for TimelineItemClass {
@ -37,9 +37,9 @@ mod imp {
(klass.as_ref().can_hide_header)(this)
}
pub(super) fn timeline_item_sender(this: &super::TimelineItem) -> Option<Member> {
pub(super) fn timeline_item_event_sender(this: &super::TimelineItem) -> Option<Member> {
let klass = this.class();
(klass.as_ref().sender)(this)
(klass.as_ref().event_sender)(this)
}
#[derive(Debug, Default)]
@ -88,8 +88,8 @@ mod imp {
glib::ParamFlags::READABLE,
),
glib::ParamSpecObject::new(
"sender",
"Sender",
"event-sender",
"Event Sender",
"If this item is a Matrix event, the sender of the event.",
Member::static_type(),
glib::ParamFlags::READABLE,
@ -119,7 +119,7 @@ mod imp {
"activatable" => obj.activatable().to_value(),
"show-header" => obj.show_header().to_value(),
"can-hide-header" => obj.can_hide_header().to_value(),
"sender" => obj.sender().to_value(),
"event-sender" => obj.event_sender().to_value(),
_ => unimplemented!(),
}
}
@ -163,7 +163,7 @@ pub trait TimelineItemExt: 'static {
/// If this is a Matrix event, the sender of the event.
///
/// Defaults to `None`.
fn sender(&self) -> Option<Member>;
fn event_sender(&self) -> Option<Member>;
}
impl<O: IsA<TimelineItem>> TimelineItemExt for O {
@ -194,8 +194,8 @@ impl<O: IsA<TimelineItem>> TimelineItemExt for O {
imp::timeline_item_can_hide_header(self.upcast_ref())
}
fn sender(&self) -> Option<Member> {
imp::timeline_item_sender(self.upcast_ref())
fn event_sender(&self) -> Option<Member> {
imp::timeline_item_event_sender(self.upcast_ref())
}
}
@ -217,7 +217,7 @@ pub trait TimelineItemImpl: ObjectImpl {
false
}
fn sender(&self, _obj: &Self::Type) -> Option<Member> {
fn event_sender(&self, _obj: &Self::Type) -> Option<Member> {
None
}
}
@ -236,7 +236,7 @@ where
klass.selectable = selectable_trampoline::<T>;
klass.activatable = activatable_trampoline::<T>;
klass.can_hide_header = can_hide_header_trampoline::<T>;
klass.sender = sender_trampoline::<T>;
klass.event_sender = event_sender_trampoline::<T>;
}
}
@ -268,11 +268,11 @@ where
this.imp().can_hide_header(this)
}
fn sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
fn event_sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
where
T: ObjectSubclass + TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().sender(this)
this.imp().event_sender(this)
}

Loading…
Cancel
Save