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