Commit 8ad6ecd4 authored by Florian Müllner's avatar Florian Müllner
Browse files

serverRoomManager: Use a treeview instead of listbox

For potentially large lists, a treeview is significantly more performant
than a listbox with many composite row widgets. As the ServerRoomList
doesn't require anything that cannot be achieved with a treeview, drop
the listbox for a nice performance gain.

https://bugzilla.gnome.org/show_bug.cgi?id=763200
parent 9c1d4c3f
......@@ -92,6 +92,10 @@
background-color: mix(@theme_unfocused_selected_bg_color, @theme_unfocused_selected_fg_color, 0.1);
}
treeview.polari-server-room-list {
padding: 6px 12px;
}
.irc-feedback {
color: @theme_fg_color;
background-color: @theme_bg_color;
......
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
const Tp = imports.gi.TelepathyGLib;
const AccountsMonitor = imports.accountsMonitor;
......@@ -97,6 +98,15 @@ const _ServerRoomManager = new Lang.Class({
});
Signals.addSignalMethods(_ServerRoomManager.prototype);
const RoomListColumn = {
CHECKED: 0,
NAME: 1,
COUNT: 2,
SENSITIVE: 3,
};
const ServerRoomList = new Lang.Class({
Name: 'ServerRoomList',
Extends: Gtk.ScrolledWindow,
......@@ -122,19 +132,62 @@ const ServerRoomList = new Lang.Class({
this.setAccount(null);
});
this._list = new Gtk.ListBox({ visible: true });
this._list.set_header_func(Lang.bind(this, this._updateHeader));
this._list.connect('row-activated',
Lang.bind(this, this._onRowActivated));
let store = new Gtk.ListStore();
store.set_column_types([GObject.TYPE_BOOLEAN,
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_BOOLEAN]);
this._list = new Gtk.TreeView({ model: store,
activate_on_single_click: true,
enable_grid_lines: Gtk.TreeViewGridLines.HORIZONTAL,
headers_visible: false,
visible: true });
this._list.get_style_context().add_class('polari-server-room-list');
this._list.connect('row-activated', (view, path, column) => {
this._toggleChecked(path);
});
this.add(this._list);
let renderer;
let column = new Gtk.TreeViewColumn();
this._list.append_column(column);
renderer = new Gtk.CellRendererToggle();
renderer.connect('toggled', (cell, pathStr) => {
this._toggleChecked(Gtk.TreePath.new_from_string(pathString));
});
column.pack_start(renderer, false);
column.add_attribute(renderer, 'active', RoomListColumn.CHECKED);
column.add_attribute(renderer, 'sensitive', RoomListColumn.SENSITIVE);
renderer = new Gtk.CellRendererText({ ellipsize: Pango.EllipsizeMode.END });
column.pack_start(renderer, true);
column.add_attribute(renderer, 'text', RoomListColumn.NAME);
column.add_attribute(renderer, 'sensitive', RoomListColumn.SENSITIVE);
renderer = new Gtk.CellRendererText();
column.pack_start(renderer, false);
column.add_attribute(renderer, 'text', RoomListColumn.COUNT);
column.add_attribute(renderer, 'sensitive', RoomListColumn.SENSITIVE);
this._manager = getDefault();
this._manager.connect('loading-changed',
Lang.bind(this, this._onLoadingChanged));
},
get can_join() {
return this._list.get_children().some(r => r.sensitive && r.checked);
let canJoin = false;
this._list.model.foreach((model, path, iter) => {
canJoin = model.get_value(iter, RoomListColumn.SENSITIVE) &&
model.get_value(iter, RoomListColumn.CHECKED);
return canJoin;
});
return canJoin;
},
get loading() {
......@@ -142,13 +195,16 @@ const ServerRoomList = new Lang.Class({
(this._account && this._manager.isLoading(this._account));
},
_onRowActivated: function(list, row) {
row.activate();
},
get selectedRooms() {
let selectedRows = this._list.get_children().filter(r => r.checked);
return selectedRows.map(r => r.info.get_name());
let rooms = [];
let [valid, iter] = this._list.model.get_iter_first();
for (; valid; valid = this._list.model.iter_next(iter)) {
if (!this._list.model.get_value(iter, RoomListColumn.SENSITIVE) ||
!this._list.model.get_value(iter, RoomListColumn.CHECKED))
continue;
rooms.push(this._list.model.get_value(iter, RoomListColumn.NAME));
}
return rooms;
},
setAccount: function(account) {
......@@ -157,17 +213,10 @@ const ServerRoomList = new Lang.Class({
this._account = account;
this._pendingInfos = [];
this._list.foreach(function(w) { w.destroy(); });
this._list.model.clear();
this._onLoadingChanged(this._manager, account);
},
_updateHeader: function(row, before) {
if (!before)
row.set_header(null);
else if (!row.get_header())
row.set_header(new Gtk.Separator());
},
_onLoadingChanged: function(mgr, account) {
if (account != this._account)
return;
......@@ -177,7 +226,7 @@ const ServerRoomList = new Lang.Class({
if (this.loading)
return;
this._list.foreach(function(w) { w.destroy(); });
this._list.model.clear();
if (this._idleId)
Mainloop.source_remove(this._idleId);
......@@ -197,11 +246,27 @@ const ServerRoomList = new Lang.Class({
this.notify('loading');
let roomManager = RoomManager.getDefault();
this._idleId = Mainloop.idle_add(() => {
this._pendingInfos.splice(0, LIST_CHUNK_SIZE).forEach(roomInfo => {
let row = new ServerRoomRow({ info: roomInfo });
row.connect('notify::checked', () => { this.notify('can-join'); });
this._list.add(row);
let store = this._list.model;
let name = roomInfo.get_name();
if (name[0] == '#')
name = name.substr(1, name.length);
let room = roomManager.lookupRoomByName(roomInfo.get_name());
let sensitive = room == null;
let checked = !sensitive;
let count = '%d'.format(roomInfo.get_members_count(null));
store.insert_with_valuesv(-1,
[RoomListColumn.CHECKED,
RoomListColumn.NAME,
RoomListColumn.COUNT,
RoomListColumn.SENSITIVE],
[checked, name, count, sensitive]);
});
if (this._pendingInfos.length)
return GLib.SOURCE_CONTINUE;
......@@ -210,67 +275,15 @@ const ServerRoomList = new Lang.Class({
this.notify('loading');
return GLib.SOURCE_REMOVE;
});
}
});
const ServerRoomRow = new Lang.Class({
Name: 'ServerRoomRow',
Extends: Gtk.ListBoxRow,
Properties: { 'checked': GObject.ParamSpec.boolean('checked',
'checked',
'checked',
GObject.ParamFlags.READABLE,
false),
},
_init: function(params) {
if (!params || !params.info)
throw new Error('No info in parameters');
this._info = params.info;
delete params.info;
this.parent(params);
let room = RoomManager.getDefault().lookupRoomByName(this._info.get_name());
this.sensitive = !room;
let name = this._info.get_name();
if (name[0] == '#')
name = name.substr(1, name.length);
let box = new Gtk.Box({ spacing: 12, margin: 12 });
this.add(box);
this._checkbox = new Gtk.CheckButton({ active: !this.sensitive });
this._checkbox.connect('toggled', Lang.bind(this,
function() {
this.notify('checked');
}));
box.add(this._checkbox);
box.add(new Gtk.Label({ label: name,
hexpand: true,
halign: Gtk.Align.START }));
let count = this._info.get_members_count(null);
let label = new Gtk.Label({ label: "%d".format(count) });
label.get_style_context().add_class('dim-label');
box.add(label);
this.show_all();
},
get info() {
return this._info;
},
get checked() {
return this._checkbox.active;
},
_toggleChecked: function(path) {
let [valid, iter] = this._list.model.get_iter(path);
if (!this._list.model.get_value(iter, RoomListColumn.SENSITIVE))
return;
let checked = this._list.model.get_value(iter, RoomListColumn.CHECKED);
this._list.model.set_value(iter, RoomListColumn.CHECKED, !checked);
vfunc_activate: function() {
this._checkbox.activate();
this.notify('can-join');
}
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment