1
/* -*- Mode: vala; tab-width: 4; intend-tabs-mode: t -*- */
4
* Copyright (C) 2012 Seif Lotfy <seif@lotfy.com>
5
* Copyright (C) 2012 Manish Sinha <manishsinha@ubuntu.com>
6
* Copyright (C) 2012 Intel Corp.
7
* Authored by: Seif Lotfy <seif.lotfy@collabora.co.uk>
8
* Copyright (C) 2012 Stefano Candori <stefano.candori@gmail.com>
10
* alm is free software: you can redistribute it and/or modify it
11
* under the terms of the GNU Lesser General Public License as published
12
* by the Free Software Foundation, either version 2 of the License, or
13
* (at your option) any later version.
15
* alm is distributed in the hope that it will be useful, but
16
* WITHOUT ANY WARRANTY; without even the implied warranty of
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
* See the GNU Lesser General Public License for more details.
20
* You should have received a copy of the GNU Lesser General Public License
21
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
30
public class FileTypeBlacklist {
31
private Blacklist blacklist_interface;
32
private HashMap<string, CheckButton> checkboxes;
34
public static string interpretation_prefix = "interpretation-";
36
public FileTypeBlacklist (Blacklist blacklist_inter,
37
HashMap<string, CheckButton> all_checkboxes) {
38
blacklist_interface = blacklist_inter;
39
checkboxes = all_checkboxes;
42
private string get_name(string interpretation) {
43
var names = interpretation.split("#");
44
var name = names[names.length-1].down();
45
return "%s%s".printf(interpretation_prefix, name);
48
public void populate_file_types() {
50
foreach(string key in blacklist_interface.all_templates.get_keys()) {
51
if(key.has_prefix(this.interpretation_prefix))
53
var inter = blacklist_interface.all_templates[key].get_subject(0).get_interpretation ();
54
if(checkboxes.has_key(inter))
55
checkboxes.get(inter).active = true;
60
public void block(string interpretation) {
61
Event ev = new Event();
62
Subject sub = new Subject();
63
sub.set_interpretation(interpretation);
66
blacklist_interface.add_template(
67
this.get_name(interpretation), ev);
70
public void unblock(string interpretation) {
71
blacklist_interface.remove_template(
72
this.get_name(interpretation));
76
public class PathBlacklist {
77
private Blacklist blacklist_interface;
79
public static string folder_prefix = "dir-";
80
private static string suffix = "/*";
82
private HashSet<string> all_blocked_folder;
84
public signal void folder_added(string path);
86
public signal void folder_removed(string path);
88
public PathBlacklist (Blacklist blacklist_inter) {
89
blacklist_interface = blacklist_inter;
90
this.blacklist_interface.template_added.connect(on_blacklist_added);
91
this.blacklist_interface.template_removed.connect(on_blacklist_removed);
93
this.get_blocked_folder ();
96
public HashSet<string> all_folders {
98
return all_blocked_folder;
102
public bool is_duplicate (string path) {
103
return all_blocked_folder.contains (path);
106
private void get_blocked_folder () {
107
all_blocked_folder = new HashSet<string>();
109
foreach(string key in blacklist_interface.all_templates.get_keys()) {
110
if(key.has_prefix(folder_prefix))
112
string folder = get_folder(blacklist_interface.all_templates.get(key));
114
all_blocked_folder.add(folder);
119
private void on_blacklist_added (string blacklist_id, Event ev) {
120
if(blacklist_id.has_prefix(folder_prefix))
122
string uri = get_folder(ev);
126
if(!all_blocked_folder.contains(uri))
127
all_blocked_folder.add(uri);
132
private void on_blacklist_removed (string blacklist_id, Event ev) {
133
if(blacklist_id.has_prefix(folder_prefix))
135
string uri = get_folder(ev);
139
if(all_blocked_folder.contains(uri))
140
all_blocked_folder.remove(uri);
145
private string get_folder(Event ev) {
146
Subject sub = ev.get_subject(0);
147
string uri = sub.get_uri ();
148
uri = uri.replace(suffix, "");
149
File blocked_uri = File.new_for_uri(uri);
151
string final_path = blocked_uri.query_exists(null)? blocked_uri.get_path(): null;
156
public void block(string folder) {
157
Event ev = new Event ();
158
Subject sub = new Subject ();
160
File block_path = File.new_for_path(folder);
161
string uri = "%s%s".printf(block_path.get_uri(), suffix);
165
blacklist_interface.add_template(
166
"%s%s".printf(folder_prefix, folder), ev);
168
if(!all_blocked_folder.contains(folder))
169
all_blocked_folder.add(folder);
172
public void unblock(string folder) {
173
blacklist_interface.remove_template(
174
"%s%s".printf(folder_prefix, folder));
176
if(all_blocked_folder.contains(folder))
177
all_blocked_folder.remove(folder);
182
public class FilesWidget : Gtk.Box {
184
private FileTypeBlacklist files_type_blacklist;
185
private PathBlacklist path_blacklist;
186
private TreeView folder_list;
187
private ListStore folder_list_store;
189
private HashMap<string, string> mime_dict;
190
private HashMap<string, CheckButton> checkboxes;
191
private HashSet<CheckButton> button_list;
193
private HashMap<string, UserDirectory> defined_dirs;
195
private Blacklist blacklist;
196
private bool file_type_fire_signal = true;
198
private IconTheme icon_theme;
199
private Gdk.Pixbuf? stock_folder_icon;
209
public FilesWidget (Blacklist blacklist_interface) {
210
Object (orientation: Orientation.VERTICAL);
212
this.blacklist = blacklist_interface;
213
this.checkboxes = new HashMap<string, CheckButton>();
214
this.button_list = new HashSet<CheckButton>();
215
this.icon_theme = new IconTheme();
217
this.files_type_blacklist = new FileTypeBlacklist(blacklist_interface,
219
this.path_blacklist = new PathBlacklist(blacklist_interface);
221
blacklist_interface.template_added.connect(
222
(blacklist_id, blacklist_template) => {
223
if(blacklist_id.has_prefix(FileTypeBlacklist.interpretation_prefix))
225
file_type_fire_signal = false;
226
var inter = blacklist_template.get_subject(0).get_interpretation ();
227
if(checkboxes.has_key(inter))
228
checkboxes.get(inter).active = true;
229
file_type_fire_signal = true;
232
blacklist_interface.template_removed.connect(
233
(blacklist_id, blacklist_template) => {
234
if(blacklist_id.has_prefix(FileTypeBlacklist.interpretation_prefix))
236
file_type_fire_signal = false;
237
var inter = blacklist_template.get_subject(0).get_interpretation ();
238
if(checkboxes.has_key(inter))
239
checkboxes.get(inter).active = false;
240
file_type_fire_signal = true;
244
this.path_blacklist.folder_added.connect((folder) => {
245
if(!this.path_blacklist.is_duplicate(folder))
246
add_folder_to_view(folder);
249
this.path_blacklist.folder_removed.connect((folder) => {
250
remove_folder_from_view(folder);
253
mime_dict = new HashMap<string, string>(str_hash, str_equal);
254
mime_dict.set(_("Audio"), NFO_AUDIO);
255
mime_dict.set(_("Video"), NFO_VIDEO);
256
mime_dict.set(_("Image"), NFO_IMAGE);
257
mime_dict.set(_("Text"), NFO_DOCUMENT);
258
mime_dict.set(_("Presentation"), NFO_PRESENTATION);
259
mime_dict.set(_("Spreadsheet"), NFO_SPREADSHEET);
260
mime_dict.set(_("Instant Messaging"), NMO_IMMESSAGE);
261
mime_dict.set(_("E-mail"), NMO_EMAIL);
262
mime_dict.set(_("Website"), NFO_WEBSITE);
264
defined_dirs = new HashMap<string, UserDirectory>(str_hash, str_equal);
265
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.DESKTOP), UserDirectory.DESKTOP);
266
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.DOCUMENTS), UserDirectory.DOCUMENTS);
267
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.DOWNLOAD), UserDirectory.DOWNLOAD);
268
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.MUSIC), UserDirectory.MUSIC);
269
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.PICTURES), UserDirectory.PICTURES);
270
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.PUBLIC_SHARE), UserDirectory.PUBLIC_SHARE);
271
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.TEMPLATES), UserDirectory.TEMPLATES);
272
defined_dirs.set(Environment.get_user_special_dir(UserDirectory.VIDEOS), UserDirectory.VIDEOS);
274
//FIXME: Not sure if the correct icon is being fetched for stock folder
275
stock_folder_icon = this.render_icon_pixbuf(Stock.DIRECTORY, IconSize.LARGE_TOOLBAR);
280
private void setup_ui() {
282
this.set_border_width(12);
284
var vbox_file_types = new Box(Orientation.VERTICAL, 6);
285
this.pack_start(vbox_file_types, false, false);
287
// Label for Checkbox list
288
var file_type_label = new Label(null);
289
file_type_label.set_markup("<b>%s</b>".printf(_("Don't record activity for following type of files:")));
290
file_type_label.set_alignment(0.0f, 0.5f);
291
vbox_file_types.pack_start(file_type_label, false);
294
var checkbox_table = new Table(3, 3, true);
296
string[] keys = mime_dict.keys.to_array();
297
for(int i=0,row=0,col=0; i < keys.length; i++, col++) {
298
//foreach (var entry in mime_dict.entries) {
299
var check_button = new CheckButton.with_label (keys[i]);
301
this.button_list.add(check_button);
302
this.checkboxes.set(mime_dict[keys[i]], check_button);
309
checkbox_table.attach_defaults(check_button, col, col+1, row, row+1);
312
vbox_file_types.pack_start(checkbox_table, false);
314
this.files_type_blacklist.populate_file_types();
316
foreach(CheckButton check_button in this.button_list)
318
check_button.toggled.connect(() => {
319
if(file_type_fire_signal)
321
file_type_fire_signal = false;
322
if(check_button.active)
323
files_type_blacklist.block(mime_dict[check_button.label]);
325
files_type_blacklist.unblock(mime_dict[check_button.label]);
326
file_type_fire_signal = true;
332
var hoz_line = new HSeparator();
333
this.pack_start(hoz_line, false, false, 12);
336
var vbox_folders = new Box(Orientation.VERTICAL, 6);
337
this.pack_start(vbox_folders, true, true);
339
// Label for Folder list list
340
var folders_label = new Label(null);
341
folders_label.set_markup("<b>%s</b>".printf(_("Don't record activity in the following folders:")));
342
folders_label.set_alignment(0.0f, 0.5f);
343
vbox_folders.pack_start(folders_label, false);
345
var file_chooser_vbox = new Box(Orientation.VERTICAL, 0);
346
vbox_folders.pack_start(file_chooser_vbox, true, true);
348
// Folder list TreeView
349
this.folder_list_store = new ListStore (3, typeof(string), typeof(Gdk.Pixbuf?), typeof(string));
350
this.folder_list = new TreeView.with_model (this.folder_list_store);
351
this.folder_list.set_headers_visible (false);
352
this.folder_list.set_rules_hint (true);
354
var column_pix_name = new TreeViewColumn ();
355
column_pix_name.set_title (_("Name"));
356
this.folder_list.append_column (column_pix_name);
358
var pix_rend = new FilesCellRenderer ();
359
column_pix_name.pack_start (pix_rend, false);
360
column_pix_name.add_attribute (pix_rend, "pixbuf", 1);
361
column_pix_name.add_attribute (pix_rend, "text", 2);
362
column_pix_name.add_attribute (pix_rend, "path", 0);
364
var scroll = new ScrolledWindow(null, null);
365
scroll.add(this.folder_list);
366
scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
367
scroll.set_shadow_type (ShadowType.IN);
368
scroll.set_border_width (1);
369
file_chooser_vbox.pack_start(scroll, true, true);
371
// Add Remove buttons
372
var folder_toolbar = new Toolbar();
373
folder_toolbar.toolbar_style = ToolbarStyle.ICONS;
374
folder_toolbar.icon_size = 1;
375
folder_toolbar.icon_size_set = true;
376
folder_toolbar.visible = true;
378
scroll.get_style_context().set_junction_sides(Gtk.JunctionSides.BOTTOM);
379
folder_toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
380
folder_toolbar.get_style_context().set_junction_sides(Gtk.JunctionSides.TOP);
382
var folder_add = new ToolButton(null, _("Add Folder"));
383
folder_add.set_icon_name("list-add-symbolic");
384
folder_add.clicked.connect(on_add_folder);
386
var folder_remove = new ToolButton(null, _("Remove Folder"));
387
folder_remove.set_icon_name("list-remove-symbolic");
388
folder_remove.clicked.connect(on_remove_folder);
390
folder_toolbar.insert(folder_add, 0);
391
folder_toolbar.insert(folder_remove, 1);
393
file_chooser_vbox.pack_start(folder_toolbar, false, false);
394
this.folders_populate ();
397
private void folders_populate () {
398
foreach(string folder in path_blacklist.all_folders)
400
add_folder_to_view(folder);
404
private void on_add_folder() {
405
var chooser = new FileChooserDialog(
406
_("Select a directory to blacklist"),
407
null, FileChooserAction.SELECT_FOLDER);
408
chooser.add_buttons (Stock.OK, ResponseType.OK, Stock.CANCEL, ResponseType.CANCEL);
409
int res = chooser.run();
411
if(res == ResponseType.OK)
413
string folder = chooser.get_filename ();
414
if(!this.path_blacklist.is_duplicate(folder))
416
add_folder_to_view(folder);
417
this.path_blacklist.block(folder);
422
private void on_remove_folder () {
423
TreeSelection sel = this.folder_list.get_selection ();
428
if(sel.get_selected(out model, out iter))
431
model.get(iter, 0, out folder);
434
folder_list_store.remove(iter);
435
this.path_blacklist.unblock(folder);
441
private void add_folder_to_view (string folder) {
443
Gdk.Pixbuf? icon = stock_folder_icon;
444
ThemedIcon? nautilus_icon = null;
445
if(defined_dirs.has_key(folder))
447
UserDirectory dir = defined_dirs.get(folder);
448
if (dir.to_string() == "G_USER_DIRECTORY_DOCUMENTS")
449
nautilus_icon = new ThemedIcon("folder-documents");
450
else if (dir.to_string() == "G_USER_DIRECTORY_DOWNLOAD")
451
nautilus_icon = new ThemedIcon("folder-download");
452
else if (dir.to_string() == "G_USER_DIRECTORY_MUSIC")
453
nautilus_icon = new ThemedIcon("folder-music");
454
else if (dir.to_string() == "G_USER_DIRECTORY_DESKTOP")
455
nautilus_icon = new ThemedIcon("user-desktop");
456
else if (dir.to_string() == "G_USER_DIRECTORY_PICTURES")
457
nautilus_icon = new ThemedIcon("folder-pictures");
458
else if (dir.to_string() == "G_USER_DIRECTORY_VIDEOS")
459
nautilus_icon = new ThemedIcon("folder-videos");
460
else if (dir.to_string() == "G_USER_DIRECTORY_TEMPLATES")
461
nautilus_icon = new ThemedIcon("folder-templates");
462
else if (dir.to_string() == "G_USER_DIRECTORY_PUBLIC_SHARE")
463
nautilus_icon = new ThemedIcon("folder-publicshare");
464
if(nautilus_icon != null) {
465
var pixbuf = ApplicationsTreeView.get_pixbuf_from_gio_icon(nautilus_icon, 24);
471
string name = Path.get_basename(folder.strip());
473
this.folder_list_store.append(out iter);
474
// First element is full path, second is icon and third is just the path basename
475
this.folder_list_store.set(iter, 0, folder, 1, icon, 2, name, -1);
478
private bool remove_folder_from_view (string folder) {
479
var model = this.folder_list.get_model ();
481
model.get_iter_first (out iter);
485
model.get_value(iter, 0, out can_app_value);
486
string cand_folder = can_app_value.get_string();
487
if(folder == cand_folder)
489
this.folder_list_store.remove(iter);
492
bool more_entires = model.iter_next(ref iter);
501
//Based on gnome-contacts contacts-cell-renderer-shape.vala
502
public class FilesCellRenderer : CellRenderer {
504
private const int PIXBUF_SIZE = 24;
505
private const int xspacing = 3;
507
private const int default_width = 60;
508
private const int renderer_height = 50;
510
private Widget current_widget;
512
private Gdk.Pixbuf pixbuf_;
513
private string text_;
514
private string path_;
516
public Gdk.Pixbuf pixbuf {
543
public FilesCellRenderer () {
547
private Pango.Layout get_text_layout (Widget widget,
548
Gdk.Rectangle? cell_area,
549
CellRendererState flags,
555
var attr_list = new Pango.AttrList ();
557
layout = widget.create_pango_layout (text);
559
var attr = new Pango.AttrSize (size * Pango.SCALE);
561
attr.start_index = 0;
562
attr.end_index = attr.start_index + text.length;
563
attr_list.insert ((owned) attr);
566
var desc = new Pango.FontDescription();
567
desc.set_weight (Pango.Weight.BOLD);
568
var attr_f = new Pango.AttrFontDesc (desc);
569
attr_list.insert ((owned) attr_f);
571
layout.set_attributes (attr_list);
573
get_padding (out xpad, null);
575
layout.set_ellipsize (Pango.EllipsizeMode.END);
577
Pango.Rectangle rect;
578
int width, text_width;
580
layout.get_extents (null, out rect);
581
text_width = rect.width;
583
if (cell_area != null)
584
width = (cell_area.width - xpad) * Pango.SCALE;
586
width = default_width * Pango.SCALE;
588
width = int.min (width, text_width);
589
layout.set_width (width);
591
Pango.Alignment align;
592
if (widget.get_direction () == TextDirection.RTL)
593
align = Pango.Alignment.RIGHT;
595
align = Pango.Alignment.LEFT;
596
layout.set_alignment (align);
601
public override void get_size (Widget widget,
602
Gdk.Rectangle? cell_area,
607
x_offset = y_offset = width = height = 0;
611
private void do_get_size (Widget widget,
612
Gdk.Rectangle? cell_area,
613
Pango.Layout? layout,
616
Pango.Rectangle rect;
619
get_padding (out xpad, out ypad);
621
layout.get_pixel_extents (null, out rect);
623
if (cell_area != null) {
624
rect.width = int.min (rect.width, cell_area.width - 2 * xpad + xspacing);
626
if (widget.get_direction () == TextDirection.RTL)
627
x_offset = cell_area.width - (rect.width + xpad);
631
x_offset = int.max (x_offset, 0);
640
public override void render (Cairo.Context cr,
642
Gdk.Rectangle background_area,
643
Gdk.Rectangle cell_area,
644
CellRendererState flags) {
645
StyleContext context;
646
Pango.Layout text_layout, path_layout;
647
int text_x_offset = 0;
648
int path_x_offset = 0;
649
int text_y_offset = 0;
650
int path_y_offset = 0;
652
Pango.Rectangle text_rect, path_rect;
654
current_widget = widget;
656
context = widget.get_style_context ();
657
var font_size = context.get_font (StateFlags.NORMAL).get_size () / Pango.SCALE;
658
get_padding (out xpad, null);
660
text_layout = get_text_layout (widget, cell_area, flags, text, true, font_size);
661
do_get_size (widget, cell_area, text_layout, out text_x_offset, out text_y_offset);
662
text_layout.get_pixel_extents (null, out text_rect);
663
text_x_offset = text_x_offset - text_rect.x;
665
path_layout = get_text_layout (widget, cell_area, flags, path, false, font_size - 1);
666
do_get_size (widget, cell_area, path_layout, out path_x_offset, out path_y_offset);
667
path_layout.get_pixel_extents (null, out path_rect);
668
path_x_offset = path_x_offset - path_rect.x;
672
Gdk.cairo_rectangle (cr, cell_area);
675
Gdk.cairo_set_source_pixbuf (cr, this.pixbuf, cell_area.x, cell_area.y);
678
context.render_layout (cr,
679
cell_area.x + text_x_offset + this.pixbuf.width+ xspacing ,
680
cell_area.y + text_y_offset + 2,
682
context.render_layout (cr,
683
cell_area.x + path_x_offset,
684
cell_area.y + path_y_offset + renderer_height - 11 - path_layout.get_baseline () / Pango.SCALE,
689
public override void get_preferred_width (Widget widget,
694
get_padding (out xpad, null);
696
nat_width = min_width = xpad + default_width;
699
public override void get_preferred_height_for_width (Widget widget,
701
out int minimum_height,
702
out int natural_height) {
705
get_padding (null, out ypad);
706
minimum_height = renderer_height + ypad;
707
natural_height = renderer_height + ypad;
710
public override void get_preferred_height (Widget widget,
711
out int minimum_size,
712
out int natural_size) {
715
get_preferred_width (widget, out min_width, null);
716
get_preferred_height_for_width (widget, min_width,
717
out minimum_size, out natural_size);