1 /*
2 * Copyright © 2004-2010 Jens Oknelid, paskharen@gmail.com
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 * In addition, as a special exception, compiling, linking, and/or
19 * using OpenSSL with this program is allowed.
20 */
21
22 #include "treeview.hh"
23 #include "settingsmanager.hh"
24 #include "WulforUtil.hh"
25 #include <glib/gi18n.h>
26
27 using namespace std;
28
TreeView()29 TreeView::TreeView()
30 {
31 view = NULL;
32 count = 0;
33 padding = false;
34 gtypes = NULL;
35 }
36
~TreeView()37 TreeView::~TreeView()
38 {
39 if (!name.empty())
40 saveSettings();
41 if (gtypes)
42 delete [] gtypes;
43 g_object_unref(menu);
44 }
45
setView(GtkTreeView * view)46 void TreeView::setView(GtkTreeView *view)
47 {
48 this->view = view;
49 gtk_tree_view_set_headers_clickable(view, TRUE);
50 gtk_tree_view_set_rubber_banding(view, TRUE);
51 }
52
setView(GtkTreeView * view,bool padding,const string & name)53 void TreeView::setView(GtkTreeView *view, bool padding, const string &name)
54 {
55 this->view = view;
56 this->padding = padding;
57 this->name = name;
58 gtk_tree_view_set_headers_clickable(view, TRUE);
59 gtk_tree_view_set_rubber_banding(view, TRUE);
60 }
61
get()62 GtkTreeView *TreeView::get()
63 {
64 return view;
65 }
66
67 /*
68 * We can't use getValue() for strings since it would cause a memory leak.
69 */
getString(GtkTreeIter * i,const string & column,GtkTreeModel * m)70 string TreeView::getString(GtkTreeIter *i, const string &column, GtkTreeModel *m)
71 {
72 if(!m)
73 m = gtk_tree_view_get_model(view);
74 string value;
75 gchar* temp;
76 dcassert(gtk_tree_model_get_column_type(m, col(column)) == G_TYPE_STRING);
77 gtk_tree_model_get(m, i, col(column), &temp, -1);
78
79 if (temp)
80 {
81 value = string(temp);
82 g_free(temp);
83 }
84
85 return value;
86 }
87
insertColumn(const string & title,const GType & gtype,const columnType type,const int width,const string & linkedCol)88 void TreeView::insertColumn(const string &title, const GType >ype, const columnType type, const int width, const string &linkedCol)
89 {
90 // All insertColumn's have to be called before any insertHiddenColumn's.
91 dcassert(hiddenColumns.empty());
92
93 // Title must be unique.
94 dcassert(!title.empty() && columns.find(title) == columns.end());
95
96 columns[title] = Column(title, count, gtype, type, width, linkedCol);
97 sortedColumns[count] = title;
98 ++count;
99 }
100
insertColumn(const string & title,const GType & gtype,const columnType type,const int width,const string & linkedCol,const string & linkedTextColor)101 void TreeView::insertColumn(const string &title, const GType >ype, const columnType type, const int width,
102 const string &linkedCol, const string &linkedTextColor)
103 {
104 // All insertColumn's have to be called before any insertHiddenColumn's.
105 dcassert(hiddenColumns.empty());
106
107 // Title must be unique.
108 dcassert(!title.empty() && columns.find(title) == columns.end());
109
110 columns[title] = Column(title, count, gtype, type, width, linkedCol, linkedTextColor);
111 sortedColumns[count] = title;
112 ++count;
113 }
114
insertHiddenColumn(const string & title,const GType & gtype)115 void TreeView::insertHiddenColumn(const string &title, const GType >ype)
116 {
117 // Title must be unique.
118 dcassert(!title.empty());
119 dcassert(hiddenColumns.find(title) == hiddenColumns.end());
120 dcassert(columns.find(title) == columns.end());
121
122 hiddenColumns[title] = Column(title, count, gtype);
123 sortedHiddenColumns[count] = title;
124 ++count;
125 }
126
finalize()127 void TreeView::finalize()
128 {
129 dcassert(count > 0);
130
131 menu = GTK_MENU(gtk_menu_new());
132 g_object_ref_sink(menu);
133 visibleColumns = columns.size();
134
135 if (!name.empty())
136 restoreSettings();
137
138 for (SortedColIter iter = sortedColumns.begin(); iter != sortedColumns.end(); ++iter)
139 {
140 Column& col = columns[iter->second];
141 addColumn_gui(col);
142
143 colMenuItems[col.title] = gtk_check_menu_item_new_with_label(col.title.c_str());
144 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(colMenuItems[col.title]), col.visible);
145 g_signal_connect(colMenuItems[col.title], "activate", G_CALLBACK(toggleColumnVisibility), (gpointer)this);
146 gtk_menu_shell_append(GTK_MENU_SHELL(menu), colMenuItems[col.title]);
147
148 if (!col.visible)
149 visibleColumns--;
150 }
151
152 if (padding)
153 {
154 GtkTreeViewColumn *col = gtk_tree_view_column_new();
155 // Set to fixed so that gtk_tree_view_set_fixed_height() doesn't complain.
156 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
157 gtk_tree_view_insert_column(view, col, count);
158 }
159 }
160
161 /*
162 * This is the total number of columns, including hidden columns.
163 */
getColCount()164 int TreeView::getColCount()
165 {
166 return count;
167 }
168
169 /*
170 * Slow method. Shouldn't be used unless necessary.
171 */
getRowCount()172 int TreeView::getRowCount()
173 {
174 GtkTreeIter iter;
175 GtkTreeModel *m = gtk_tree_view_get_model(view);
176 int numRows = 0;
177 gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
178
179 while (valid)
180 {
181 ++numRows;
182 valid = gtk_tree_model_iter_next(m, &iter);
183 }
184
185 return numRows;
186 }
187
getGTypes()188 GType* TreeView::getGTypes()
189 {
190 int i = 0;
191 if (gtypes)
192 return gtypes;
193 gtypes = new GType[count];
194
195 for (SortedColIter iter = sortedColumns.begin(); iter != sortedColumns.end(); ++iter)
196 gtypes[i++] = columns[iter->second].gtype;
197 for (SortedColIter iter = sortedHiddenColumns.begin(); iter != sortedHiddenColumns.end(); ++iter)
198 gtypes[i++] = hiddenColumns[iter->second].gtype;
199
200 return gtypes;
201 }
202
speedDataFunc(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer column)203 void TreeView::speedDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer column)
204 {
205 string speedString;
206 int64_t speed;
207 gtk_tree_model_get(model, iter, static_cast<Column*>(column)->pos, &speed, -1);
208
209 if (speed >= 0)
210 {
211 speedString = dcpp::Util::formatBytes(speed) + "/" + _("s");
212 }
213
214 g_object_set(renderer, "text", speedString.c_str(), NULL);
215 }
216
sizeDataFunc(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer column)217 void TreeView::sizeDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer column)
218 {
219 string sizeString;
220 int64_t size;
221 gtk_tree_model_get(model, iter, static_cast<Column*>(column)->pos, &size, -1);
222
223 if (size >= 0)
224 {
225 sizeString = dcpp::Util::formatBytes(size);
226 }
227
228 g_object_set(renderer, "text", sizeString.c_str(), NULL);
229 }
230
timeLeftDataFunc(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer column)231 void TreeView::timeLeftDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer column)
232 {
233 string timeLeftString;
234 int64_t seconds;
235 gtk_tree_model_get(model, iter, static_cast<Column*>(column)->pos, &seconds, -1);
236
237 if (seconds >= 0)
238 {
239 timeLeftString = dcpp::Util::formatSeconds(seconds);
240 }
241
242 g_object_set(renderer, "text", timeLeftString.c_str(), NULL);
243 }
244
addColumn_gui(Column & column)245 void TreeView::addColumn_gui(Column& column)
246 {
247 GtkTreeViewColumn *col = NULL;
248 GtkCellRenderer *renderer;
249
250 switch (column.type)
251 {
252 case INT:
253 case STRING:
254 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
255 gtk_cell_renderer_text_new(), "text", column.pos, NULL);
256 break;
257 case SIZE:
258 renderer = gtk_cell_renderer_text_new();
259 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
260 renderer, "text", column.pos, NULL);
261 gtk_tree_view_column_set_cell_data_func(col, renderer, TreeView::sizeDataFunc, &column, NULL);
262 break;
263 case SPEED:
264 renderer = gtk_cell_renderer_text_new();
265 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
266 renderer, "text", column.pos, NULL);
267 gtk_tree_view_column_set_cell_data_func(col, renderer, TreeView::speedDataFunc, &column, NULL);
268 break;
269 case TIME_LEFT:
270 renderer = gtk_cell_renderer_text_new();
271 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
272 renderer, "text", column.pos, NULL);
273 gtk_tree_view_column_set_cell_data_func(col, renderer, TreeView::timeLeftDataFunc, &column, NULL);
274 break;
275 case STRINGR:
276 renderer = gtk_cell_renderer_text_new();
277 g_object_set(renderer, "xalign", 1.0, NULL);
278 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(), renderer, "text", column.pos, NULL);
279 gtk_tree_view_column_set_alignment(col, 1.0);
280 break;
281 case BOOL:
282 renderer = gtk_cell_renderer_toggle_new();
283 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(), renderer, "active", column.pos, NULL);
284 break;
285 case PIXBUF:
286 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
287 gtk_cell_renderer_pixbuf_new(), "pixbuf", column.pos, NULL);
288 break;
289 case PIXBUF_STRING:
290 renderer = gtk_cell_renderer_pixbuf_new();
291 col = gtk_tree_view_column_new();
292 gtk_tree_view_column_set_title(col, column.title.c_str());
293 gtk_tree_view_column_pack_start(col, renderer, false);
294 gtk_tree_view_column_add_attribute(col, renderer, "pixbuf", TreeView::col(column.linkedCol));
295 renderer = gtk_cell_renderer_text_new();
296 gtk_tree_view_column_pack_start(col, renderer, true);
297 gtk_tree_view_column_add_attribute(col, renderer, "text", column.pos);
298 break;
299 case ICON_STRING:
300 renderer = gtk_cell_renderer_pixbuf_new();
301 col = gtk_tree_view_column_new();
302 gtk_tree_view_column_set_title(col, column.title.c_str());
303 gtk_tree_view_column_pack_start(col, renderer, false);
304 gtk_tree_view_column_add_attribute(col, renderer, "stock-id", TreeView::col(column.linkedCol));
305 renderer = gtk_cell_renderer_text_new();
306 gtk_tree_view_column_pack_start(col, renderer, true);
307 gtk_tree_view_column_add_attribute(col, renderer, "text", column.pos);
308 break;
309 case ICON_STRING_TEXT_COLOR:
310 // icon
311 renderer = gtk_cell_renderer_pixbuf_new();
312 col = gtk_tree_view_column_new();
313 gtk_tree_view_column_set_title(col, column.title.c_str());
314 gtk_tree_view_column_pack_start(col, renderer, false);
315 gtk_tree_view_column_add_attribute(col, renderer, "stock-id", TreeView::col(column.linkedCol));
316 // text
317 renderer = gtk_cell_renderer_text_new();
318 gtk_tree_view_column_pack_start(col, renderer, true);
319 gtk_tree_view_column_add_attribute(col, renderer, "text", column.pos);
320 //gtk_tree_view_column_add_attribute(col, renderer, "foreground", TreeView::col(column.linkedTextColor));
321 break;
322 case EDIT_STRING:
323 renderer = gtk_cell_renderer_text_new();
324 g_object_set(renderer, "editable", TRUE, NULL);
325 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(), renderer, "text", column.pos, NULL);
326 break;
327 case PROGRESS:
328 renderer = gtk_cell_renderer_progress_new();
329 g_object_set(renderer, "xalign", 0.0, NULL); // Doesn't work yet. See: http://bugzilla.gnome.org/show_bug.cgi?id=334576
330 col = gtk_tree_view_column_new_with_attributes(column.title.c_str(),
331 renderer, "text", column.pos, "value", TreeView::col(column.linkedCol), NULL);
332 break;
333 }
334
335 if (!col)
336 return;
337
338 // If columns are too small, they can't be manipulated
339 if (column.width >= 20)
340 {
341 gtk_tree_view_column_set_fixed_width(col, column.width);
342 gtk_tree_view_column_set_resizable(col, TRUE);
343 }
344 if (column.width != -1)
345 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
346
347 //make columns sortable
348 if (column.type != BOOL && column.type != PIXBUF && column.type != EDIT_STRING)
349 {
350 gtk_tree_view_column_set_sort_column_id(col, column.pos);
351 gtk_tree_view_column_set_sort_indicator(col, TRUE);
352 }
353
354 gtk_tree_view_column_set_clickable(col, TRUE);
355 gtk_tree_view_column_set_reorderable(col, true);
356 gtk_tree_view_column_set_visible(col, column.visible);
357
358 gtk_tree_view_insert_column(view, col, column.pos);
359
360 /*
361 * Breaks GTK+ API, but is the only way to attach a signal to a gtktreeview column header. See GTK bug #141937.
362 * @todo: Replace when GTK adds a way to add a signal to the entire header (remove visibleColumns var, too).
363 */
364 #if GTK_CHECK_VERSION(3, 0, 0)
365 g_signal_connect(gtk_tree_view_column_get_button(col) , "button-release-event", G_CALLBACK(popupMenu_gui), (gpointer)this);
366 #else
367 g_signal_connect(col->button, "button-release-event", G_CALLBACK(popupMenu_gui), (gpointer)this);
368 #endif
369 }
370
setSortColumn_gui(const string & column,const string & sortColumn)371 void TreeView::setSortColumn_gui(const string &column, const string &sortColumn)
372 {
373 GtkTreeViewColumn *gtkColumn = gtk_tree_view_get_column(view, col(column));
374 gtk_tree_view_column_set_sort_column_id(gtkColumn, col(sortColumn));
375 }
376
col(const string & title)377 int TreeView::col(const string &title)
378 {
379 dcassert(!title.empty());
380 dcassert(columns.find(title) != columns.end() || hiddenColumns.find(title) != hiddenColumns.end());
381 int retval = -1;
382
383 if (columns.find(title) != columns.end())
384 retval = columns[title].pos;
385 else
386 retval = hiddenColumns[title].id;
387
388 dcassert(retval >= 0 && (retval < count));
389 return retval;
390 }
391
popupMenu_gui(GtkWidget * widget,GdkEventButton * event,gpointer data)392 gboolean TreeView::popupMenu_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
393 {
394 TreeView *tv = (TreeView*)data;
395
396 if (event->button == 3)
397 {
398 gtk_menu_popup(tv->menu, NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0, gdk_event_get_time((GdkEvent*)event));
399 gtk_widget_show_all(GTK_WIDGET(tv->menu));
400 return true;
401 }
402 else
403 return false;
404 }
405
toggleColumnVisibility(GtkMenuItem * item,gpointer data)406 void TreeView::toggleColumnVisibility(GtkMenuItem *item, gpointer data)
407 {
408 TreeView *tv = (TreeView*)data;
409 GtkTreeViewColumn *column = NULL;
410 gboolean visible;
411 SortedColIter iter;
412 string title = string(gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(item)))));
413
414 // Function col(title) doesn't work here, so we have to find column manually.
415 for (iter = tv->sortedColumns.begin(); iter != tv->sortedColumns.end(); ++iter)
416 {
417 column = gtk_tree_view_get_column(tv->view, iter->first);
418 if (string(gtk_tree_view_column_get_title(column)) == title)
419 break;
420 }
421
422 if (!column)
423 return;
424
425 visible = !gtk_tree_view_column_get_visible(column);
426
427 // Can't let number of visible columns fall below 1, otherwise there's no way to unhide columns.
428 if (!visible && tv->visibleColumns <= 1)
429 {
430 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(tv->colMenuItems[title]), true);
431 return;
432 }
433
434 gtk_tree_view_column_set_visible(column, visible);
435 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(tv->colMenuItems[title]), visible);
436
437 if (visible)
438 {
439 tv->visibleColumns++;
440 // Seems to be a bug in gtk where sometimes columns are unresizable after being made visible
441 gtk_tree_view_column_set_resizable(column, TRUE);
442 }
443 else
444 tv->visibleColumns--;
445 }
446
restoreSettings()447 void TreeView::restoreSettings()
448 {
449 vector<int> columnOrder, columnWidth, columnVisibility;
450 columnOrder = WulforUtil::splitString(WGETS(name + "-order"), ",");
451 columnWidth = WulforUtil::splitString(WGETS(name + "-width"), ",");
452 columnVisibility = WulforUtil::splitString(WGETS(name + "-visibility"), ",");
453
454 if (columns.size() == columnOrder.size() &&
455 columnOrder.size() == columnWidth.size() &&
456 columnWidth.size() == columnVisibility.size())
457 {
458 for (ColIter iter = columns.begin(); iter != columns.end(); ++iter)
459 {
460 for (size_t i = 0; i < columns.size(); i++)
461 {
462 if (iter->second.id == columnOrder.at(i))
463 {
464 iter->second.pos = i;
465 sortedColumns[i] = iter->second.title;
466 if (columnWidth.at(i) >= 20)
467 iter->second.width = columnWidth.at(i);
468 if (columnVisibility.at(i) == 0 || columnVisibility.at(i) == 1)
469 iter->second.visible = columnVisibility.at(i);
470 break;
471 }
472 }
473 }
474 }
475 }
476
saveSettings()477 void TreeView::saveSettings()
478 {
479 string columnOrder, columnWidth, columnVisibility, title;
480 GtkTreeViewColumn *col;
481 gint width;
482
483 dcassert(!columns.empty());
484
485 for (size_t i = 0; i < columns.size(); i++)
486 {
487 col = gtk_tree_view_get_column(view, i);
488 if (!col)
489 continue;
490
491 title = string(gtk_tree_view_column_get_title(col));
492 width = gtk_tree_view_column_get_width(col);
493
494 // A col was moved to the right of the padding col
495 if (title.empty())
496 return;
497
498 columnOrder += dcpp::Util::toString(columns[title].id) + ",";
499 if (width >= 20)
500 columnWidth += dcpp::Util::toString(width) + ",";
501 else
502 columnWidth += dcpp::Util::toString(columns[title].width) + ",";
503 columnVisibility += dcpp::Util::toString(gtk_tree_view_column_get_visible(col)) + ",";
504 }
505
506 if (!columnOrder.empty())
507 {
508 columnOrder.erase(columnOrder.size() - 1, 1);
509 columnWidth.erase(columnWidth.size() - 1, 1);
510 columnVisibility.erase(columnVisibility.size() - 1, 1);
511
512 WSET(name + "-order", columnOrder);
513 WSET(name + "-width", columnWidth);
514 WSET(name + "-visibility", columnVisibility);
515 }
516 }
517