Commit 4e6b802a authored by Julian Sparber's avatar Julian Sparber

room: add room settings panel

move all functionality of the old room config dialog to the new panel
and start the implementation of the new UI for the room settings panel

https://gitlab.gnome.org/World/fractal/issues/19
parent ce59272e
......@@ -91,6 +91,22 @@
border-bottom: none;
}
.list-with-separator row {
border-bottom: solid 1px rgba(0,0,0, 0.1);
}
.list-with-separator row:last-child {
border-bottom: none
}
.room-settings-name {
font-size: xx-large;
}
.room-settings-type {
color: @theme_selected_bg_color;
}
row:not(:selected) .member-uid {
color: @insensitive_fg_color;
}
......
......@@ -18,10 +18,10 @@
<file preprocess="xml-stripblanks">ui/main_window.ui</file>
<file preprocess="xml-stripblanks">ui/members.ui</file>
<file preprocess="xml-stripblanks">ui/new_room.ui</file>
<file preprocess="xml-stripblanks">ui/room_config.ui</file>
<file preprocess="xml-stripblanks">ui/room_menu.ui</file>
<file preprocess="xml-stripblanks">ui/user_menu.ui</file>
<file preprocess="xml-stripblanks">ui/account_settings.ui</file>
<file preprocess="xml-stripblanks">ui/room_settings.ui</file>
<file preprocess="xml-stripblanks">ui/password_dialog.ui</file>
<file preprocess="xml-stripblanks">ui/markdown_popover.ui</file>
<file preprocess="xml-stripblanks">ui/media_viewer.ui</file>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkDialog" id="room_config_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="gravity">center</property>
<property name="transient_for">main_window</property>
<property name="attached_to">main_window</property>
<child internal-child="vbox">
<object class="GtkBox" id="room_config_form">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="room_config_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="room_avatar_filechooser">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="margin_top">20</property>
<property name="margin_bottom">10</property>
<child>
<object class="GtkImage" id="room_avatar_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">100</property>
<property name="icon_name">camera-photo-symbolic</property>
<property name="icon_size">6</property>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="room_avatar_filechooser-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">Room avatar</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">30</property>
<property name="margin_right">30</property>
<property name="margin_top">6</property>
<property name="margin_bottom">30</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<property name="row_homogeneous">True</property>
<child>
<object class="GtkLabel" id="room_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">ID</property>
<attributes>
<attribute name="foreground" value="#88888a8a8585"/>
</attributes>
<accessibility>
<relation type="label-for" target="room_id"/>
</accessibility>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="room_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Name</property>
<attributes>
<attribute name="foreground" value="#88888a8a8585"/>
</attributes>
<accessibility>
<relation type="label-for" target="room_name_entry"/>
</accessibility>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="room_topic_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Topic</property>
<attributes>
<attribute name="foreground" value="#88888a8a8585"/>
</attributes>
<accessibility>
<relation type="label-for" target="room_topic_entry"/>
</accessibility>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="room_name_entry">
<property name="width_request">400</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<accessibility>
<relation type="labelled-by" target="room_name_label"/>
</accessibility>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="room_topic_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<accessibility>
<relation type="labelled-by" target="room_topic_label"/>
</accessibility>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="room_id">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label">#</property>
<property name="selectable">True</property>
<accessibility>
<relation type="labelled-by" target="room_id_label"/>
</accessibility>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Room details</property>
<child>
<object class="GtkButton" id="room_dialog_close">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="room_dialog_set">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
This diff is collapsed.
......@@ -60,7 +60,7 @@ impl App {
dir.connect_activate(clone!(op => move |_, _| op.lock().unwrap().set_state(AppState::Directory) ));
logout.connect_activate(clone!(op => move |_, _| op.lock().unwrap().logout() ));
room.connect_activate(clone!(op => move |_, _| op.lock().unwrap().show_room_dialog() ));
room.connect_activate(clone!(op => move |_, _| op.lock().unwrap().show_room_settings() ));
inv.connect_activate(clone!(op => move |_, _| op.lock().unwrap().show_invite_user_dialog() ));
chat.connect_activate(clone!(op => move |_, _| op.lock().unwrap().show_direct_chat_dialog() ));
search.connect_activate(clone!(op => move |_, _| op.lock().unwrap().toggle_search() ));
......
......@@ -146,9 +146,15 @@ pub fn backend_loop(rx: Receiver<BKResponse>) {
APPOP!(reload_rooms);
}
Ok(BKResponse::LeaveRoom) => { }
Ok(BKResponse::SetRoomName) => { }
Ok(BKResponse::SetRoomTopic) => { }
Ok(BKResponse::SetRoomAvatar) => { }
Ok(BKResponse::SetRoomName) => {
APPOP!(show_new_room_name);
}
Ok(BKResponse::SetRoomTopic) => {
APPOP!(show_new_room_topic);
}
Ok(BKResponse::SetRoomAvatar) => {
APPOP!(show_new_room_avatar);
}
Ok(BKResponse::MarkedAsRead(r, _)) => {
APPOP!(clear_room_notifications, (r));
}
......
extern crate gtk;
extern crate gdk_pixbuf;
use self::gtk::prelude::*;
use glib;
use self::gdk_pixbuf::Pixbuf;
use app::App;
impl App {
pub fn connect_room_config(&self) {
let dialog = self.ui.builder
.get_object::<gtk::Dialog>("room_config_dialog")
.expect("Can't find room_config_dialog in ui file.");
let btn = self.ui.builder
.get_object::<gtk::Button>("room_dialog_close")
.expect("Can't find room_dialog_close in ui file.");
btn.connect_clicked(clone!(dialog => move |_| {
dialog.hide();
}));
dialog.connect_delete_event(clone!(dialog => move |_, _| {
dialog.hide();
glib::signal::Inhibit(true)
}));
let avatar = self.ui.builder
.get_object::<gtk::Image>("room_avatar_image")
.expect("Can't find room_avatar_image in ui file.");
let avatar_btn = self.ui.builder
.get_object::<gtk::Button>("room_avatar_filechooser")
.expect("Can't find room_avatar_filechooser in ui file.");
let avatar_fs = self.ui.builder
.get_object::<gtk::FileChooserDialog>("file_chooser_dialog")
.expect("Can't find file_chooser_dialog in ui file.");
let op = &self.op;
let back = self.ui.builder
.get_object::<gtk::Button>("room_settings_back_button")
.expect("Can't find room_settings_back_button in ui file.");
let name_btn = self.ui.builder
.get_object::<gtk::Button>("room_settings_room_name_button")
.expect("Can't find room_settings_room_name_button in ui file.");
let name_entry = self.ui.builder
.get_object::<gtk::Entry>("room_settings_room_name_entry")
.expect("Can't find room_settings_room_name_entry in ui file.");
let topic_btn = self.ui.builder
.get_object::<gtk::Button>("room_settings_room_topic_button")
.expect("Can't find room_settings_room_topic_button in ui file.");
let topic_entry = self.ui.builder
.get_object::<gtk::Entry>("room_settings_room_topic_entry")
.expect("Can't find room_settings_room_topic_entry in ui file.");
let fs_set = self.ui.builder
.get_object::<gtk::Button>("file_chooser_set")
.expect("Can't find file_chooser_set in ui file.");
let fs_cancel = self.ui.builder
.get_object::<gtk::Button>("file_chooser_cancel")
.expect("Can't find file_chooser_cancel in ui file.");
let fs_preview = self.ui.builder
.get_object::<gtk::Image>("file_chooser_preview")
.expect("Can't find file_chooser_preview in ui file.");
fs_cancel.connect_clicked(clone!(avatar_fs => move |_| {
avatar_fs.hide();
/* Headerbar */
back.connect_clicked(clone!(op => move |_| {
op.lock().unwrap().close_room_settings();
}));
avatar_fs.connect_delete_event(move |d, _| {
d.hide();
glib::signal::Inhibit(true)
});
fs_set.connect_clicked(clone!(avatar_fs, avatar => move |_| {
avatar_fs.hide();
if let Some(fname) = avatar_fs.get_filename() {
if let Some(name) = fname.to_str() {
if let Ok(pixbuf) = Pixbuf::new_from_file_at_size(name, 100, 100) {
avatar.set_from_pixbuf(&pixbuf);
} else {
avatar.set_from_icon_name("image-missing", 5);
}
let button = name_btn.clone();
name_entry.connect_property_text_notify(clone!(op => move |w| {
let lock = op.try_lock();
if let Ok(guard) = lock {
let result = guard.validate_room_name(w.get_text());
if result.is_some() {
button.show();
return;
}
}
button.hide();
}));
avatar_fs.connect_selection_changed(move |fs| {
if let Some(fname) = fs.get_filename() {
if let Some(name) = fname.to_str() {
if let Ok(pixbuf) = Pixbuf::new_from_file_at_size(name, 100, 100) {
fs_preview.set_from_pixbuf(&pixbuf);
}
let button = topic_btn.clone();
topic_entry.connect_property_text_notify(clone!(op => move |w| {
let lock = op.try_lock();
if let Ok(guard) = lock {
let result = guard.validate_room_topic(w.get_text());
if result.is_some() {
button.show();
return;
}
}
button.hide();
}));
let button = name_btn.clone();
name_entry.connect_activate(move |_w| {
let _ = button.emit("clicked", &[]);
});
name_btn.connect_clicked(clone!(op => move |_| {
op.lock().unwrap().update_room_name();
}));
let button = topic_btn.clone();
topic_entry.connect_activate(move |_w| {
let _ = button.emit("clicked", &[]);
});
avatar_btn.connect_clicked(clone!(avatar_fs => move |_| {
avatar_fs.present();
topic_btn.connect_clicked(clone!(op => move |_| {
op.lock().unwrap().update_room_topic();
}));
let btn = self.ui.builder
.get_object::<gtk::Button>("room_dialog_set")
.expect("Can't find room_dialog_set in ui file.");
let op = self.op.clone();
btn.connect_clicked(clone!(dialog => move |_| {
op.lock().unwrap().change_room_config();
dialog.hide();
/* Connect avatar button */
let avatar_btn = self.ui.builder
.get_object::<gtk::Button>("room_settings_avatar_button")
.expect("Can't find room_settings_avatar_button in ui file.");
let builder = &self.ui.builder;
avatar_btn.connect_clicked(clone!(op, builder => move |_| {
let window = builder
.get_object::<gtk::Window>("main_window")
.expect("Can't find main_window in ui file.");
let file_chooser = gtk::FileChooserNative::new("Pick a new room avatar", Some(&window), gtk::FileChooserAction::Open, Some("Select"), None);
/* http://gtk-rs.org/docs/gtk/struct.FileChooser.html */
let result = gtk::NativeDialog::run(&file_chooser.clone().upcast::<gtk::NativeDialog>());
if gtk::ResponseType::from(result) == gtk::ResponseType::Accept {
if let Some(file) = file_chooser.get_filename() {
if let Some(path) = file.to_str() {
op.lock().unwrap().update_room_avatar(String::from(path));
}
}
}
}));
}
}
......@@ -114,6 +114,16 @@ impl App {
stack.add_named(&child, "account-settings");
stack_header.add_named(&child_header, "account-settings");
/* Add room settings view to the main stack */
let child = ui.builder
.get_object::<gtk::Box>("room_settings_box")
.expect("Can't find room_settings_box in ui file.");
let child_header = ui.builder
.get_object::<gtk::Box>("room_settings_headerbar")
.expect("Can't find room_settings_headerbar in ui file.");
stack.add_named(&child, "room-settings");
stack_header.add_named(&child_header, "room-settings");
/* Add media viewer to the main stack */
let child = ui.builder
.get_object::<gtk::Box>("media_viewer_box")
......@@ -124,7 +134,6 @@ impl App {
stack.add_named(&child, "media-viewer");
stack_header.add_named(&child_header, "media-viewer");
let op = Arc::new(Mutex::new(
AppOp::new(gtk_app.clone(), ui.clone(), apptx, itx)
));
......
......@@ -30,6 +30,7 @@ mod login;
mod sync;
mod user;
mod account;
mod room_settings;
mod notifications;
mod state;
mod room;
......
......@@ -5,6 +5,7 @@ use i18n::{i18n, i18n_k};
use self::gtk::prelude::*;
use cache::download_to_cache;
use appop::AppOp;
use appop::AppState;
use appop::MsgPos;
......@@ -16,6 +17,7 @@ use backend::BKCommand;
use globals;
use cache;
use widgets;
use widgets::avatar::AvatarExt;
use types::Room;
use types::Message;
......@@ -215,12 +217,8 @@ impl AppOp {
let name_label = self.ui.builder
.get_object::<gtk::Label>("room_name")
.expect("Can't find room_name in ui file.");
let edit = self.ui.builder
.get_object::<gtk::Entry>("room_name_entry")
.expect("Can't find room_name_entry in ui file.");
name_label.set_text(&room.name.clone().unwrap_or_default());
edit.set_text(&room.name.clone().unwrap_or_default());
let mut size = 24;
if let Some(r) = room.topic.clone() {
......@@ -230,10 +228,6 @@ impl AppOp {
}
self.set_current_room_avatar(room.avatar.clone(), size);
let id = self.ui.builder
.get_object::<gtk::Label>("room_id")
.expect("Can't find room_id in ui file.");
id.set_text(&room.id.clone());
self.set_current_room_detail(String::from("m.room.name"), room.name.clone());
self.set_current_room_detail(String::from("m.room.topic"), room.topic.clone());
......@@ -386,48 +380,17 @@ impl AppOp {
let name_label = self.ui.builder
.get_object::<gtk::Label>("room_name")
.expect("Can't find room_name in ui file.");
let edit = self.ui.builder
.get_object::<gtk::Entry>("room_name_entry")
.expect("Can't find room_name_entry in ui file.");
let pl = *self.active_room.clone()
.and_then(|ar| self.rooms.get(&ar))
.and_then(|r| r.power_levels.get(&self.uid.clone()?))
.unwrap_or(&0);
if pl >= 50 {
edit.set_editable(true);
} else {
edit.set_editable(false);
}
name_label.set_text(&value);
edit.set_text(&value);
}
"m.room.topic" => {
self.set_room_topic_label(Some(value.clone()));
let edit = self.ui.builder
.get_object::<gtk::Entry>("room_topic_entry")
.expect("Can't find room_topic_entry in ui file.");
let pl = *self.active_room.clone()
.and_then(|ar| self.rooms.get(&ar))
.and_then(|r| r.power_levels.get(&self.uid.clone()?))
.unwrap_or(&0);
if pl >= 50 {
edit.set_editable(true);
} else {
edit.set_editable(false);
}
edit.set_text(&value);
}
_ => println!("no key {}", key),
};
}
pub fn set_current_room_avatar(&self, _avatar: Option<String>, _size: i32) {
pub fn set_current_room_avatar(&self, _avatar: Option<String>, size: i32) {
let image = self.ui.builder
.get_object::<gtk::Box>("room_image")
.expect("Can't find room_image in ui file.");
......@@ -435,24 +398,10 @@ impl AppOp {
image.remove(&ch);
}
/*
* This will be removed soon.
let config = self.ui.builder
.get_object::<gtk::Image>("room_avatar_image")
.expect("Can't find room_avatar_image in ui file.");
if avatar.is_some() && !avatar.clone().unwrap().is_empty() {
image.add(&widgets::Avatar::circle_avatar(avatar.clone().unwrap(), Some(size)));
if let Ok(pixbuf) = Pixbuf::new_from_file_at_size(&avatar.clone().unwrap(), 100, 100) {
config.set_from_pixbuf(&pixbuf);
}
} else {
let w = widgets::Avatar::avatar_new(Some(size));
w.default(String::from("camera-photo-symbolic"), Some(size));
image.add(&w);
config.set_from_icon_name("camera-photo-symbolic", 1);
}
*/
download_to_cache(self.backend.clone(), self.uid.clone().unwrap_or_default());
let w = widgets::Avatar::avatar_new(Some(size));
w.circle(self.uid.clone().unwrap_or_default(), self.username.clone(), size);
image.add(&w);
}
pub fn filter_rooms(&self, term: Option<String>) {
......@@ -537,39 +486,6 @@ impl AppOp {
}
}
pub fn change_room_config(&mut self) {
let name = self.ui.builder
.get_object::<gtk::Entry>("room_name_entry")
.expect("Can't find room_name_entry in ui file.");
let topic = self.ui.builder
.get_object::<gtk::Entry>("room_topic_entry")
.expect("Can't find room_topic_entry in ui file.");
let avatar_fs = self.ui.builder
.get_object::<gtk::FileChooserDialog>("file_chooser_dialog")
.expect("Can't find file_chooser_dialog in ui file.");
if let Some(r) = self.rooms.get(&self.active_room.clone().unwrap_or_default()) {
if let Some(n) = name.get_text() {
if n != r.name.clone().unwrap_or_default() {
let command = BKCommand::SetRoomName(r.id.clone(), n.clone());
self.backend.send(command).unwrap();
}
}
if let Some(t) = topic.get_text() {
if t != r.topic.clone().unwrap_or_default() {
let command = BKCommand::SetRoomTopic(r.id.clone(), t.clone());
self.backend.send(command).unwrap();
}
}
if let Some(f) = avatar_fs.get_filename() {
if let Some(name) = f.to_str() {
let command = BKCommand::SetRoomAvatar(r.id.clone(), String::from(name));
self.backend.send(command).unwrap();
}
}
}
}
/// This method calculate the room name when there's no room name event
/// For this we use the members in the room. If there's only one member we'll return that
/// member name, if there's more than one we'll return the first one and others
......@@ -655,8 +571,8 @@ impl AppOp {
.get_object::<gtk::Label>("room_topic")
.expect("Can't find room_topic in ui file.");
let n = self.ui.builder
.get_object::<gtk::Label>("room_name")
.expect("Can't find room_name in ui file.");
.get_object::<gtk::Label>("room_name")
.expect("Can't find room_name in ui file.");
match topic {
None => {
......@@ -685,12 +601,4 @@ impl AppOp {
self.backend.send(BKCommand::GetRoomAvatar(roomid)).unwrap();
}
pub fn show_room_dialog(&self) {
let dialog = self.ui.builder
.get_object::<gtk::Dialog>("room_config_dialog")
.expect("Can't find room_config_dialog in ui file.");
dialog.present();
}
}
extern crate gtk;
use self::gtk::prelude::*;
use appop::AppOp;
use appop::AppState;
use backend::BKCommand;
use fractal_api::types::Member;
use cache::download_to_cache;
use widgets;
use widgets::AvatarExt;
impl AppOp {
pub fn show_room_settings(&mut self) {
//check for type we have to show
self.init_room_settings();
self.set_state(AppState::RoomSettings);
}
pub fn close_room_settings(&mut self) {
self.set_state(AppState::Chat);
}
fn init_room_settings(&mut self) -> Option<()> {
let room = self.rooms.get(&self.active_room.clone()?)?;
let avatar = room.avatar.clone();
let name = room.name.clone();
let topic = room.topic.clone();
let is_room = true;
let members: Vec<Member> = room.members.values().cloned().collect();
let power = *room.power_levels.get(&self.uid.clone()?).unwrap_or(&0);
let edit = power >= 50 && !room.direct;
let description = if room.direct {
self.get_direct_partner_uid(members)
} else {
/* we don't have private groups yet
let description = Some(format!("Private Group - {} members", members.len()));
*/
Some(format!("Public Room - {} members", members.len()))
};