4
* Copyright 2010 PCMan <pcman.tw@gmail.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26
#include "fm-places-model.h"
27
#include <glib/gi18n-lib.h>
29
static void create_trash_item(FmPlacesModel* model);
31
static void update_sep_tp(FmPlacesModel* model)
33
gtk_tree_path_free(model->sep_tp);
34
model->sep_tp = gtk_tree_model_get_path(model, &model->sep_it);
37
static void place_item_free(FmPlaceItem* item)
41
case FM_PLACES_ITEM_VOL:
42
g_object_unref(item->vol);
45
fm_file_info_unref(item->fi);
46
g_slice_free(FmPlaceItem, item);
49
static void on_file_info_job_finished(FmFileInfoJob* job, gpointer user_data)
51
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
57
/* g_debug("file info job finished"); */
58
model->jobs = g_slist_remove(model->jobs, job);
60
if(!gtk_tree_model_get_iter_first(model, &it))
63
if(fm_list_is_empty(job->file_infos))
66
/* optimize for one file case */
67
if(fm_list_get_length(job->file_infos) == 1)
69
fi = FM_FILE_INFO(fm_list_peek_head(job->file_infos));
72
gtk_tree_model_get(model, &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
73
if( item && item->fi && item->fi->path && fm_path_equal(item->fi->path, fi->path) )
75
fm_file_info_unref(item->fi);
76
item->fi = fm_file_info_ref(fi);
79
}while(gtk_tree_model_iter_next(model, &it));
85
gtk_tree_model_get(model, &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
86
if( item && item->fi && item->fi->path )
88
for(l = fm_list_peek_head_link(job->file_infos); l; l = l->next )
90
fi = FM_FILE_INFO(l->data);
91
if(fm_path_equal(item->fi->path, fi->path))
93
fm_file_info_unref(item->fi);
94
item->fi = fm_file_info_ref(fi);
95
/* remove the file from list to speed up further loading.
96
* This won't cause problem since nobody else if using the list. */
97
fm_list_delete_link(job->file_infos, l);
102
}while(gtk_tree_model_iter_next(model, &it));
106
static void update_vol(FmPlacesModel* model, FmPlaceItem* item, GtkTreeIter* it, FmFileInfoJob* job)
115
name = g_volume_get_name(item->vol);
117
fm_icon_unref(item->fi->icon);
118
gicon = g_volume_get_icon(item->vol);
119
icon = fm_icon_from_gicon(gicon);
120
item->fi->icon = icon;
121
g_object_unref(gicon);
123
mount = g_volume_get_mount(item->vol);
126
GFile* gf = g_mount_get_root(mount);
127
path = fm_path_new_for_gfile(gf);
129
g_object_unref(mount);
134
if(!fm_path_equal(item->fi->path, path))
137
fm_path_unref(item->fi->path);
138
item->fi->path = path ? fm_path_ref(path) : NULL;
142
fm_file_info_job_add(job, path);
145
job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_FOLLOW_SYMLINK);
146
model->jobs = g_slist_prepend(model->jobs, job);
147
g_signal_connect(job, "finished", G_CALLBACK(on_file_info_job_finished), model);
148
fm_job_run_async(job);
153
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
154
gtk_list_store_set(model, it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, name, -1);
159
static void add_vol(FmPlacesModel* model, GVolume* vol, FmFileInfoJob* job)
163
item = g_slice_new0(FmPlaceItem);
164
item->fi = fm_file_info_new();
165
item->type = FM_PLACES_ITEM_VOL;
166
item->vol = (GVolume*)g_object_ref(vol);
167
gtk_list_store_insert_before(model, &it, &model->sep_it);
168
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_INFO, item, -1);
169
update_vol(model, item, &it, job);
172
static FmPlaceItem* find_vol(FmPlacesModel* model, GVolume* vol, GtkTreeIter* _it)
176
/* FIXME: don't need to find from the first iter */
177
if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it))
182
gtk_tree_model_get(GTK_TREE_MODEL(model), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
184
if(item && item->type == FM_PLACES_ITEM_VOL && item->vol == vol)
189
}while(it.user_data != model->sep_it.user_data && gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it));
194
void on_vol_added(GVolumeMonitor* vm, GVolume* vol, gpointer user_data)
196
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
197
g_debug("add vol: %p, uuid: %s, udi: %s", vol, g_volume_get_identifier(vol, "uuid"), g_volume_get_identifier(vol, "hal-udi"));
198
add_vol(model, vol, NULL);
199
update_sep_tp(model);
202
void on_vol_removed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data)
204
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
207
item = find_vol(model, vol, &it);
208
/* g_debug("remove vol: %p, uuid: %s, udi: %s", vol, g_volume_get_identifier(vol, "uuid"), g_volume_get_identifier(vol, "hal-udi")); */
211
gtk_list_store_remove(model, &it);
212
place_item_free(item);
213
update_sep_tp(model);
217
void on_vol_changed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data)
219
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
222
item = find_vol(model, vol, &it);
224
update_vol(model, item, &it, NULL);
227
void on_mount_added(GVolumeMonitor* vm, GMount* mount, gpointer user_data)
229
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
230
GVolume* vol = g_mount_get_volume(mount);
235
item = find_vol(model, vol, &it);
236
if(item && item->type == FM_PLACES_ITEM_VOL && !item->fi->path)
238
GFile* gf = g_mount_get_root(mount);
239
FmPath* path = fm_path_new_for_gfile(gf);
240
g_debug("mount path: %s", path->name);
242
item->fi->path = path;
248
static void add_bookmarks(FmPlacesModel* model, FmFileInfoJob* job)
252
FmIcon* icon = fm_icon_from_name("folder");
253
FmIcon* remote_icon = NULL;
254
GdkPixbuf* folder_pix = fm_icon_get_pixbuf(icon, fm_config->pane_icon_size);
255
GdkPixbuf* remote_pix = NULL;
256
bms = fm_bookmarks_list_all(model->bookmarks);
257
for(l=bms;l;l=l->next)
259
FmBookmarkItem* bm = (FmBookmarkItem*)l->data;
262
item = g_slice_new0(FmPlaceItem);
263
item->type = FM_PLACES_ITEM_PATH;
264
item->fi = fm_file_info_new();
265
item->fi->path = fm_path_ref(bm->path);
266
fm_file_info_job_add(job, item->fi->path);
268
if(fm_path_is_native(item->fi->path))
270
item->fi->icon = fm_icon_ref(icon);
275
if(G_UNLIKELY(!remote_icon))
277
remote_icon = fm_icon_from_name("folder-remote");
278
remote_pix = fm_icon_get_pixbuf(remote_icon, fm_config->pane_icon_size);
280
item->fi->icon = fm_icon_ref(remote_icon);
284
gtk_list_store_append(model, &it);
285
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, bm->name, FM_PLACES_MODEL_COL_INFO, item, -1);
287
g_object_unref(folder_pix);
291
fm_icon_unref(remote_icon);
293
g_object_unref(remote_pix);
297
static void on_bookmarks_changed(FmBookmarks* bm, gpointer user_data)
299
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
300
FmFileInfoJob* job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_FOLLOW_SYMLINK);
301
GtkTreeIter it = model->sep_it;
302
/* remove all old bookmarks */
303
if(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it))
305
while(gtk_list_store_remove(model, &it))
308
add_bookmarks(model, job);
310
g_signal_connect(job, "finished", G_CALLBACK(on_file_info_job_finished), model);
311
model->jobs = g_slist_prepend(model->jobs, job);
312
fm_job_run_async(job);
315
static gboolean update_trash_item(gpointer user_data)
317
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
318
if(fm_config->use_trash)
320
GFile* gf = g_file_new_for_uri("trash:///");
321
GFileInfo* inf = g_file_query_info(gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, 0, NULL, NULL);
326
const char* icon_name;
329
guint32 n = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
331
icon_name = n > 0 ? "user-trash-full" : "user-trash";
332
icon = fm_icon_from_name(icon_name);
333
gtk_tree_model_get(GTK_TREE_MODEL(model), &model->trash_it, FM_PLACES_MODEL_COL_INFO, &item, -1);
335
fm_icon_unref(item->fi->icon);
336
item->fi->icon = icon;
337
/* update the icon */
338
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
339
gtk_list_store_set(model, &model->trash_it, FM_PLACES_MODEL_COL_ICON, pix, -1);
347
static void on_trash_changed(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, gpointer user_data)
349
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
350
if(model->trash_idle)
351
g_source_remove(model->trash_idle);
352
model->trash_idle = g_idle_add((GSourceFunc)update_trash_item, model);
355
static void update_icons(FmPlacesModel* model)
358
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it);
360
if(it.user_data != model->sep_it.user_data)
363
gtk_tree_model_get(GTK_TREE_MODEL(model), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
364
/* FIXME: get icon size from FmConfig */
365
GdkPixbuf* pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
366
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, -1);
369
}while( gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it) );
372
static void on_use_trash_changed(FmConfig* cfg, gpointer user_data)
374
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
375
if(cfg->use_trash && model->trash_it.user_data == NULL)
376
create_trash_item(model);
380
gtk_tree_model_get(GTK_TREE_MODEL(model), &model->trash_it, FM_PLACES_MODEL_COL_INFO, &item, -1);
381
gtk_list_store_remove(GTK_LIST_STORE(model), &model->trash_it);
382
model->trash_it.user_data = NULL;
383
place_item_free(item);
385
if(model->trash_monitor)
387
g_signal_handlers_disconnect_by_func(model->trash_monitor, on_trash_changed, model);
388
g_object_unref(model->trash_monitor);
389
model->trash_monitor = NULL;
391
if(model->trash_idle)
393
g_source_remove(model->trash_idle);
394
model->trash_idle = 0;
397
update_sep_tp(model);
400
static void on_pane_icon_size_changed(FmConfig* cfg, gpointer user_data)
402
FmPlacesModel* model = FM_PLACES_MODEL(user_data);
407
static void create_trash_item(FmPlacesModel* model)
414
gf = g_file_new_for_uri("trash:///");
415
model->trash_monitor = fm_monitor_directory(gf, NULL);
416
g_signal_connect(model->trash_monitor, "changed", G_CALLBACK(on_trash_changed), model);
419
item = g_slice_new0(FmPlaceItem);
420
item->type = FM_PLACES_ITEM_PATH;
421
item->fi = fm_file_info_new();
422
item->fi->path = fm_path_ref(fm_path_get_trash());
423
item->fi->icon = fm_icon_from_name("user-trash");
424
gtk_list_store_insert(model, &it, 2);
425
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
426
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, _("Trash"), FM_PLACES_MODEL_COL_INFO, item, -1);
428
model->trash_it = it;
430
if(0 == model->trash_idle)
431
model->trash_idle = g_idle_add((GSourceFunc)update_trash_item, model);
434
static void fm_places_model_init(FmPlacesModel *self)
436
GType types[] = {GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER};
445
FmFileInfoJob* job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_FOLLOW_SYMLINK);
446
GtkListStore* model = GTK_LIST_STORE(self);
448
gtk_list_store_set_column_types(GTK_LIST_STORE(self), FM_PLACES_MODEL_N_COLS, types);
450
self->theme_change_handler = g_signal_connect_swapped(gtk_icon_theme_get_default(), "changed",
451
G_CALLBACK(update_icons), self);
453
self->use_trash_change_handler = g_signal_connect(fm_config, "changed::use_trash",
454
G_CALLBACK(on_use_trash_changed), self);
456
self->pane_icon_size_change_handler = g_signal_connect(fm_config, "changed::pane_icon_size",
457
G_CALLBACK(on_pane_icon_size_changed), self);
459
item = g_slice_new0(FmPlaceItem);
460
item->type = FM_PLACES_ITEM_PATH;
461
item->fi = fm_file_info_new();
462
item->fi->path = fm_path_ref(fm_path_get_home());
463
item->fi->icon = fm_icon_from_name("user-home");
464
gtk_list_store_append(model, &it);
465
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
466
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, item->fi->path->name, FM_PLACES_MODEL_COL_INFO, item, -1);
468
fm_file_info_job_add(job, item->fi->path);
470
/* Only show desktop in side pane when the user has a desktop dir. */
471
if(g_file_test(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP), G_FILE_TEST_IS_DIR))
473
item = g_slice_new0(FmPlaceItem);
474
item->type = FM_PLACES_ITEM_PATH;
475
item->fi = fm_file_info_new();
476
item->fi->path = fm_path_ref(fm_path_get_desktop());
477
item->fi->icon = fm_icon_from_name("user-desktop");
478
gtk_list_store_append(model, &it);
479
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
480
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, _("Desktop"), FM_PLACES_MODEL_COL_INFO, item, -1);
482
fm_file_info_job_add(job, item->fi->path);
485
if(fm_config->use_trash)
486
create_trash_item(self); /* FIXME: how to handle trash can? */
488
item = g_slice_new0(FmPlaceItem);
489
item->type = FM_PLACES_ITEM_PATH;
490
item->fi = fm_file_info_new();
491
item->fi->path = fm_path_ref(fm_path_get_apps_menu());
492
item->fi->icon = fm_icon_from_name("system-software-install");
493
gtk_list_store_append(model, &it);
494
pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size);
495
gtk_list_store_set(model, &it, FM_PLACES_MODEL_COL_ICON, pix, FM_PLACES_MODEL_COL_LABEL, _("Applications"), FM_PLACES_MODEL_COL_INFO, item, -1);
497
/* fm_file_info_job_add(job, item->fi->path); */
500
self->vol_mon = g_volume_monitor_get();
501
g_signal_connect(self->vol_mon, "volume-added", G_CALLBACK(on_vol_added), self);
502
g_signal_connect(self->vol_mon, "volume-removed", G_CALLBACK(on_vol_removed), self);
503
g_signal_connect(self->vol_mon, "volume-changed", G_CALLBACK(on_vol_changed), self);
504
g_signal_connect(self->vol_mon, "mount-added", G_CALLBACK(on_mount_added), self);
507
gtk_list_store_append(model, &self->sep_it);
509
/* add volumes to side-pane */
510
vols = g_volume_monitor_get_volumes(self->vol_mon);
511
for(l=vols;l;l=l->next)
513
GVolume* vol = G_VOLUME(l->data);
514
add_vol(self, vol, job);
519
/* get the path of separator */
520
self->sep_tp = gtk_tree_model_get_path(GTK_TREE_MODEL(self), &self->sep_it);
522
self->bookmarks = fm_bookmarks_get(); /* bookmarks */
523
g_signal_connect(self->bookmarks, "changed", G_CALLBACK(on_bookmarks_changed), self);
525
/* add bookmarks to side pane */
526
add_bookmarks(self, job);
528
g_signal_connect(job, "finished", G_CALLBACK(on_file_info_job_finished), self);
529
self->jobs = g_slist_prepend(self->jobs, job);
530
fm_job_run_async(job);
533
const GtkTreePath* fm_places_model_get_separator_path(FmPlacesModel* model)
535
return model->sep_tp;
538
gboolean fm_places_model_iter_is_separator(FmPlacesModel* model, GtkTreeIter* it)
540
return it && it->user_data == model->sep_it.user_data;
543
gboolean fm_places_model_path_is_separator(FmPlacesModel* model, GtkTreePath* tp)
545
return tp && gtk_tree_path_compare(model->sep_tp, tp) == 0;
548
gboolean fm_places_model_path_is_bookmark(FmPlacesModel* model, GtkTreePath* tp)
550
return tp && gtk_tree_path_compare(model->sep_tp, tp) < 0;
553
gboolean fm_places_model_path_is_places(FmPlacesModel* model, GtkTreePath* tp)
555
return tp && gtk_tree_path_compare(model->sep_tp, tp) > 0;
558
static gboolean row_draggable(GtkTreeDragSource* drag_source, GtkTreePath* tp)
560
FmPlacesModel* model = FM_PLACES_MODEL(drag_source);
561
return fm_places_model_path_is_bookmark(model, tp);
564
static void fm_places_model_drag_source_init(GtkTreeDragSourceIface *iface)
566
iface->row_draggable = row_draggable;
569
G_DEFINE_TYPE_WITH_CODE (FmPlacesModel, fm_places_model, GTK_TYPE_LIST_STORE,
570
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
571
fm_places_model_drag_source_init))
574
static void fm_places_model_finalize(GObject *object)
579
g_return_if_fail(object != NULL);
580
g_return_if_fail(FM_IS_PLACES_MODEL(object));
582
self = FM_PLACES_MODEL(object);
587
for(l = self->jobs; l; l=l->next)
589
fm_job_cancel(FM_JOB(l->data));
590
g_object_unref(l->data);
592
g_slist_free(self->jobs);
595
if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(self), &it))
600
gtk_tree_model_get(GTK_TREE_MODEL(self), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
602
place_item_free(item);
603
}while(gtk_tree_model_iter_next(GTK_TREE_MODEL(self), &it));
606
gtk_tree_path_free(self->sep_tp);
608
g_signal_handler_disconnect(gtk_icon_theme_get_default(), self->theme_change_handler);
609
g_signal_handler_disconnect(fm_config, self->use_trash_change_handler);
610
g_signal_handler_disconnect(fm_config, self->pane_icon_size_change_handler);
612
g_signal_handlers_disconnect_by_func(self->vol_mon, on_vol_added, self);
613
g_signal_handlers_disconnect_by_func(self->vol_mon, on_vol_removed, self);
614
g_signal_handlers_disconnect_by_func(self->vol_mon, on_vol_changed, self);
615
g_signal_handlers_disconnect_by_func(self->vol_mon, on_mount_added, self);
616
g_object_unref(self->vol_mon);
618
if(self->trash_monitor)
620
g_signal_handlers_disconnect_by_func(self->trash_monitor, on_trash_changed, self);
621
g_object_unref(self->trash_monitor);
624
g_source_remove(self->trash_idle);
626
G_OBJECT_CLASS(fm_places_model_parent_class)->finalize(object);
629
static void fm_places_model_class_init(FmPlacesModelClass *klass)
631
GObjectClass *g_object_class;
633
g_object_class = G_OBJECT_CLASS(klass);
634
g_object_class->finalize = fm_places_model_finalize;
638
GtkListStore *fm_places_model_new(void)
640
return g_object_new(FM_TYPE_PLACES_MODEL, NULL);