1 /* Copyright (C) 2016-2017 Shengyu Zhang <i@silverrainz.me>
2 *
3 * This file is part of Srain.
4 *
5 * Srain is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * @file sui_user_list.c
21 * @brief Widget for listing all participants of a SrnChatbuffer
22 * @author Shengyu Zhang <i@silverrainz.me>
23 * @version 0.06.2
24 * @date 2016-04-03
25 *
26 * NOTE: It is allowed to add duplicate user, deduplication should be
27 * do in the upper layer.
28 */
29
30 #include <gtk/gtk.h>
31 #include <cairo-gobject.h>
32
33 #include "core/core.h"
34
35 #include "sui_user_list.h"
36 #include "nick_menu.h"
37
38 #include "log.h"
39 #include "i18n.h"
40
41 struct _SuiUserList {
42 GtkBox parent;
43
44 GtkLabel *stat_label; // users statistics
45 GtkTreeView *user_tree_view;
46 GtkTreeViewColumn *user_tree_view_column;
47 GtkCellRendererText *user_name_cell_renderer;
48 GtkCellRendererPixbuf *user_icon_cell_renderer;
49
50 /* Data model */
51 SuiUserStat user_stat;
52 GtkListStore *user_list_store;
53 GtkTreeModel *user_tree_model_filter; // FilterTreeModel of user_list_store
54 // TODO: user search
55 };
56
57 struct _SuiUserListClass {
58 GtkBoxClass parent_class;
59 };
60
61 static void user_tree_view_set_model(SuiUserList *self);
62 static void stat_label_update_stat(SuiUserList *self);
63 static int user_list_store_sort_func(GtkTreeModel *model,
64 GtkTreeIter *iter1, GtkTreeIter *iter2, gpointer user_data);
65
66 static gboolean user_tree_view_on_popup(GtkWidget *widget,
67 GdkEventButton *event, gpointer user_data);
68 static void user_list_store_on_row_changed(GtkTreeModel *tree_model,
69 GtkTreePath *path, GtkTreeIter *iter, gpointer user_data);
70 static void on_style_updated(SuiUserList *self, gpointer user_data);
71
72 /*****************************************************************************
73 * GObject functions
74 *****************************************************************************/
75
76 G_DEFINE_TYPE(SuiUserList, sui_user_list, GTK_TYPE_BOX);
77
sui_user_list_init(SuiUserList * self)78 static void sui_user_list_init(SuiUserList *self){
79 gtk_widget_init_template(GTK_WIDGET(self));
80
81 user_tree_view_set_model(self);
82 stat_label_update_stat(self);
83
84 g_signal_connect(self->user_tree_view, "button-press-event",
85 G_CALLBACK(user_tree_view_on_popup), NULL);
86 g_signal_connect(self->user_list_store, "row-changed",
87 G_CALLBACK(user_list_store_on_row_changed), self);
88 g_signal_connect(self, "style-updated",
89 G_CALLBACK(on_style_updated), NULL);
90 }
91
sui_user_list_class_init(SuiUserListClass * class)92 static void sui_user_list_class_init(SuiUserListClass *class){
93 GtkWidgetClass *widget_class;
94
95 widget_class = GTK_WIDGET_CLASS(class);
96
97 gtk_widget_class_set_template_from_resource(widget_class,
98 "/im/srain/Srain/user_list.glade");
99
100 gtk_widget_class_bind_template_child(widget_class, SuiUserList, stat_label);
101 gtk_widget_class_bind_template_child(widget_class, SuiUserList, user_tree_view);
102 gtk_widget_class_bind_template_child(widget_class, SuiUserList, user_tree_view_column);
103 gtk_widget_class_bind_template_child(widget_class, SuiUserList, user_name_cell_renderer);
104 gtk_widget_class_bind_template_child(widget_class, SuiUserList, user_icon_cell_renderer);
105 }
106
107 /*****************************************************************************
108 * Expored functions
109 *****************************************************************************/
110
sui_user_list_new(void)111 SuiUserList* sui_user_list_new(void){
112 return g_object_new(SUI_TYPE_USER_LIST, NULL);
113 }
114
sui_user_list_add_user(SuiUserList * self,SuiUser * user)115 void sui_user_list_add_user(SuiUserList *self, SuiUser *user){
116 gtk_list_store_append(self->user_list_store, (GtkTreeIter *)user);
117 sui_user_set_list(user, self->user_list_store);
118 sui_user_set_stat(user, &self->user_stat);
119 self->user_stat.total++;
120 sui_user_list_update_user(self, user);
121 }
122
sui_user_list_rm_user(SuiUserList * self,SuiUser * user)123 void sui_user_list_rm_user(SuiUserList *self, SuiUser *user){
124 // FIXME: A hack for correcting user statistic
125 SrnChatUser *chat_user = sui_user_get_ctx(user);
126 chat_user->type = SRN_CHAT_USER_TYPE_CHIGUA;
127
128 self->user_stat.total--;
129 sui_user_list_update_user(self, user);
130 gtk_list_store_remove(self->user_list_store, (GtkTreeIter *)user);
131 sui_user_set_list(user, NULL);
132 sui_user_set_stat(user, NULL);
133 }
134
sui_user_list_update_user(SuiUserList * self,SuiUser * user)135 void sui_user_list_update_user(SuiUserList *self, SuiUser *user){
136 sui_user_update(user,
137 gtk_widget_get_style_context(GTK_WIDGET(self)),
138 gtk_widget_get_window(GTK_WIDGET(self)));
139 }
140
sui_user_list_clear(SuiUserList * self)141 void sui_user_list_clear(SuiUserList *self){
142 gtk_list_store_clear(self->user_list_store);
143 memset(&self->user_stat, 0, sizeof(self->user_stat));
144 }
145
sui_user_list_get_users_by_prefix(SuiUserList * self,const char * prefix)146 GList* sui_user_list_get_users_by_prefix(SuiUserList *self, const char *prefix){
147 GList *users;
148 GtkTreeModel *model;
149 GtkTreeIter iter;
150
151 model = GTK_TREE_MODEL(self->user_list_store);
152 if (!gtk_tree_model_get_iter_first(model, &iter)){
153 return NULL;
154 }
155
156 users = NULL;
157 do {
158 SuiUser *user;
159
160 user = sui_user_new_from_iter(GTK_LIST_STORE(model), &iter);
161 if (g_str_has_prefix(sui_user_get_nickname(user), prefix)){
162 users = g_list_append(users, user);
163 } else {
164 sui_user_free(user);
165 }
166 } while (gtk_tree_model_iter_next(model, &iter));
167
168 return users;
169 }
170
171 /*****************************************************************************
172 * Static functions
173 *****************************************************************************/
174
user_tree_view_set_model(SuiUserList * self)175 static void user_tree_view_set_model(SuiUserList *self){
176 GtkListStore *store;
177 GtkTreeModel *filter;
178 GtkTreeView *view;
179
180 /* 4 columns: user, icon, model, type */
181 self->user_list_store = gtk_list_store_new(4,
182 G_TYPE_STRING,
183 CAIRO_GOBJECT_TYPE_SURFACE,
184 G_TYPE_POINTER,
185 G_TYPE_INT);
186 gtk_tree_view_column_add_attribute(self->user_tree_view_column,
187 GTK_CELL_RENDERER(self->user_name_cell_renderer), "text", 0);
188 gtk_tree_view_column_add_attribute(self->user_tree_view_column,
189 GTK_CELL_RENDERER(self->user_icon_cell_renderer), "surface", 1);
190
191 store = self->user_list_store;
192 view = self->user_tree_view;
193
194 self->user_tree_model_filter = gtk_tree_model_filter_new(
195 GTK_TREE_MODEL(store), NULL);
196 filter = self->user_tree_model_filter;
197
198 gtk_tree_sortable_set_default_sort_func(
199 GTK_TREE_SORTABLE(store),
200 user_list_store_sort_func, NULL, NULL);
201 gtk_tree_sortable_set_sort_column_id(
202 GTK_TREE_SORTABLE(store),
203 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
204 GTK_SORT_ASCENDING);
205 gtk_tree_view_set_model(view, filter);
206 }
207
stat_label_update_stat(SuiUserList * self)208 static void stat_label_update_stat(SuiUserList *self){
209 char *stat;
210
211 stat = g_strdup_printf(
212 _("Users: %d, <span color=\"#157915\">%d@</span>,"
213 "<span color=\"#856117\">%d%%</span>,"
214 "<span color=\"#451984\">%d+</span>"),
215 self->user_stat.total,
216 self->user_stat.full_op,
217 self->user_stat.half_op,
218 self->user_stat.voiced);
219 gtk_label_set_markup(self->stat_label, stat);
220 g_free(stat);
221 }
222
user_list_store_sort_func(GtkTreeModel * model,GtkTreeIter * iter1,GtkTreeIter * iter2,gpointer user_data)223 static int user_list_store_sort_func(GtkTreeModel *model,
224 GtkTreeIter *iter1, GtkTreeIter *iter2, gpointer user_data){
225 int ret;
226 SuiUser *user1;
227 SuiUser *user2;
228
229 user1 = sui_user_new_from_iter(GTK_LIST_STORE(model), iter1);
230 user2 = sui_user_new_from_iter(GTK_LIST_STORE(model), iter2);
231
232 ret = sui_user_compare(user1, user2);
233
234 sui_user_free(user1);
235 sui_user_free(user2);
236
237 return ret;
238 }
239
user_tree_view_on_popup(GtkWidget * widget,GdkEventButton * event,gpointer user_data)240 static gboolean user_tree_view_on_popup(GtkWidget *widget,
241 GdkEventButton *event, gpointer user_data){
242 GtkTreeView *view;
243 GtkTreeModel *model;
244 GtkTreeModel *child_model;
245 GtkTreeIter iter;
246 GtkTreeIter child_iter;
247 GtkTreeSelection *selection;
248 SuiUser *user;
249 SrnChatUser *chat_user;
250
251 if (event->button != 3){
252 return FALSE;
253 }
254
255 view = GTK_TREE_VIEW(widget);
256 model = gtk_tree_view_get_model(view);
257 selection = gtk_tree_view_get_selection(view);
258 if (!gtk_tree_selection_get_selected(selection, &model, &iter)){
259 /* If not row is selected, just return */
260 return FALSE;
261 }
262 child_model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
263 gtk_tree_model_filter_convert_iter_to_child_iter(
264 GTK_TREE_MODEL_FILTER(model), &child_iter, &iter);
265
266 user = sui_user_new_from_iter(GTK_LIST_STORE(child_model), &child_iter);
267 chat_user = sui_user_get_ctx(user);
268 g_return_val_if_fail(chat_user, FALSE);
269
270 // TODO: impl SuiUserPanel
271 nick_menu_popup(widget, event, chat_user->srv_user->nick);
272
273 sui_user_free(user);
274
275 return TRUE;
276 }
277
user_list_store_on_row_changed(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)278 static void user_list_store_on_row_changed(GtkTreeModel *tree_model,
279 GtkTreePath *path, GtkTreeIter *iter, gpointer user_data){
280 SuiUserList *self;
281
282 self = SUI_USER_LIST(user_data);
283 stat_label_update_stat(self);
284 }
285
on_style_updated(SuiUserList * self,gpointer user_data)286 static void on_style_updated(SuiUserList *self, gpointer user_data) {
287 GtkTreeModel *model;
288 GtkTreeIter iter;
289
290 model = GTK_TREE_MODEL(self->user_list_store);
291 if (!gtk_tree_model_get_iter_first(model, &iter)){
292 return;
293 }
294
295 do {
296 SuiUser *user;
297 user = sui_user_new_from_iter(GTK_LIST_STORE(model), &iter);
298 sui_user_set_stat(user, &self->user_stat);
299 sui_user_list_update_user(self, user);
300 sui_user_free(user);
301 } while (gtk_tree_model_iter_next(model, &iter));
302 }
303