/* $Id$ Copyright (C) 2004-2010 tooar . This file is part of emelFM2. emelFM2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses. */ /** @file src/e2_filelist.c @brief directory content liststore functions This file contains functions related to creation, filling and emptying a liststore of directory content data, and fns for the associated model. Also filtering of store content, and other related utilties. */ #include "e2_filelist.h" #include #include #include #include #include #include #include "e2_option.h" #include "e2_task.h" #include "e2_dialog.h" #ifdef E2_SELTXT_RECOLOR extern GdkColor selectedtext; #endif #ifdef E2_FAM_DNOTIFY #include extern sigset_t dnotify_signal_set; #endif //disable-refresh counter for both filelists volatile gint refresh_refcount; //disable-refresh counter for single filelists static volatile gint refresh_active_refcount; static volatile gint refresh_inactive_refcount; //refresh deferral counters static gint pane1repeats = 0; static gint pane2repeats = 0; //static gboolean case_sensitive; //local copy of option value, for faster access /** @brief determine whether pane defined by @a pane is the active one @param pane enumerator for pane to be checked @return TRUE if @a pane is the active one */ static gboolean _e2_filelist_check_active_pane (E2_ListChoice pane) { gboolean active; switch (pane) { case PANE1: active = (curr_view == &app.pane1.view); break; case PANE2: active = (curr_view == &app.pane2.view); break; case PANEINACTIVE: active = FALSE; break; default: active = TRUE; break; } return active; } /** @brief disable refreshing of a single pane filelist This is mainly intended to block changes of filesystem CWD when a process is working in a displayed directory @param pane enumerator for pane to be blocked @return */ void e2_filelist_disable_one_refresh (E2_ListChoice pane) { if (_e2_filelist_check_active_pane (pane)) { if (g_atomic_int_exchange_and_add (&refresh_active_refcount, 1) == 0) { LISTS_LOCK curr_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } } else { if (g_atomic_int_exchange_and_add (&refresh_inactive_refcount, 1) == 0) { LISTS_LOCK other_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } } } /** @brief enable refreshing of a single pane filelist @param pane enumerator for pane to be unblocked @return */ void e2_filelist_enable_one_refresh (E2_ListChoice pane) { if (_e2_filelist_check_active_pane (pane)) { if (g_atomic_int_exchange_and_add (&refresh_active_refcount, -1) == 1 && g_atomic_int_get (&refresh_refcount) == 0) { LISTS_LOCK curr_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } if (refresh_active_refcount < 0) g_atomic_int_set (&refresh_active_refcount, 0); } else { if (g_atomic_int_exchange_and_add (&refresh_inactive_refcount, -1) == 1 && g_atomic_int_get (&refresh_refcount) == 0) { LISTS_LOCK other_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } if (refresh_inactive_refcount < 0) g_atomic_int_set (&refresh_inactive_refcount, 0); } } /** @brief disable -refresh action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ gboolean e2_filelist_disable_refresh_action (gpointer from, E2_ActionRuntime *art) { e2_filelist_disable_refresh (); return TRUE; } /** @brief enable -refresh action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ gboolean e2_filelist_enable_refresh_action (gpointer from, E2_ActionRuntime *art) { e2_filelist_enable_refresh (); return TRUE; } /** @brief reset polling of config data and pane filelists @return */ void e2_filelist_reset_refresh (void) { if (g_atomic_int_get (&refresh_refcount) != 0) { g_atomic_int_set (&refresh_refcount, 1); e2_filelist_enable_refresh (); } } /* These functions must encapsulate any instructions that access the FileInfo structs produced when a selection is interrogated, because a refresh in the middle of such an operation would free FileInfo structs as a side-effect. */ /** @brief disable refreshing of pane filelists and config data Increases the ref-count, then if 1, stop the timer process which calls the refresh fns Why is config data check bundled here ? Expects BGL open? return */ void e2_filelist_disable_refresh (void) { gint preinc = g_atomic_int_exchange_and_add (&refresh_refcount, 1); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, refresh ref now = %d", preinc + 1); #endif if (preinc == 0) { if (app.timers[REFRESHBEGIN_T] > 0) { //we're waiting for a pending refresh g_source_remove (app.timers[REFRESHBEGIN_T]); app.timers[REFRESHBEGIN_T] = 0; } if (e2_option_bool_get ("auto-refresh")) { //don't cancel check-dirty timer, to reduce risk that FAM //dirty-reports queue may over-fill LISTS_LOCK curr_view->listcontrols.norefresh = TRUE; other_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } if (e2_option_bool_get ("auto-refresh-config")) { //CHECKME just block this too? //(can't reasonably be many changes to the config file) if (app.timers[CONFIG_T] > 0) { g_source_remove (app.timers[CONFIG_T]); app.timers[CONFIG_T] = 0; } } //#ifndef E2_STATUS_DEMAND ??E2_STATUS_BLOCK // e2_window_disable_status_update (); //#endif } } /** @brief re-enable polling of pane filelists and config data Decreases the ref-count, then if 0, re-enable/restart the processes which detect changes of filelist content and/or config data Config timer is bundled here because the same func refreshes the panes Expects BGL open @return */ void e2_filelist_enable_refresh (void) { gint predec = g_atomic_int_exchange_and_add (&refresh_refcount, -1); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, refresh ref now = %d", predec - 1); #endif if (predec == 1) { if (e2_option_bool_get ("auto-refresh")) { LISTS_LOCK curr_view->listcontrols.norefresh = FALSE; other_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } //this timer is used, even with fam backends if (e2_option_bool_get ("auto-refresh-config")) app.timers[CONFIG_T] = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_CONFIGCHECK_INTERVAL_S, #else g_timeout_add (E2_CONFIGCHECK_INTERVAL, #endif (GSourceFunc) e2_option_check_config_files, NULL); //#ifndef E2_STATUS_DEMAND ??E2_STATUS_BLOCK // e2_window_enable_status_update (-1); //#endif } else if (predec < 1) { printd (WARN, "The auto-refresh refresh count is < 0"); gdk_threads_enter (); e2_window_output_show (NULL, NULL); e2_output_print_error ( _("Something is wrong with the auto-refresh mechanism"), FALSE); gdk_threads_leave (); } } /** @brief idle function to run refresh hooklist @param view pointer to view data struct @return FALSE to abort the source */ static gboolean _e2_fileview_more_refresh (ViewInfo *view) { e2_hook_list_run (&view->hook_refresh, view); return FALSE; } /** @brief timer destroy function after cancelling filelists checking @param data UNUSED data specified when the timer was established @return */ static void _e2_filelist_timer_shutdown (gpointer data) { app.timers[REFRESHBEGIN_T] = 0; } /** @brief timer callback function which refreshes filelist(s) as appropriate, as soon as any block is gone @param data pointerised value of current timer delay @return FALSE when there's nothing left to refresh or more waiting is needed */ static gboolean _e2_filelist_refresh_manage (gpointer data) { pthread_t thisID; gpointer result; gboolean cvdone, ovdone, cvhook, ovhook; LISTS_LOCK ViewInfo *cv = curr_view; //log these, in case active pane is changed during this process ViewInfo *ov = other_view; cvhook = ovhook = FALSE; e2_utf8_set_name_conversion_if_requested (); retest: if (cv->listcontrols.refresh_requested) { cvdone = !( cv->listcontrols.norefresh || //norefresh flags set when refresh is disabled g_atomic_int_get (&cv->listcontrols.refresh_working) || g_atomic_int_get (&cv->listcontrols.cd_working) #ifdef E2_STATUS_BLOCK || g_atomic_int_get (&app.status_working) #endif ); if (cvdone) { LISTS_UNLOCK #ifdef E2_VFSTMP //FIXME dir when not mounted local #else printd (DEBUG, "accepting request to refresh %s", cv->dir); #endif //refresh function expects BGL open if (pthread_create (&thisID, NULL, (gpointer(*)(gpointer))e2_fileview_refresh_list, cv) == 0) { pthread_join (thisID, &result); //only 1 refresh at a time cvhook = TRUE; //send it downstream } else printd (WARN, "Failed to create list refresh thread"); LISTS_LOCK //clear flag, too bad if a request happened late in the process if (result != NULL) cv->listcontrols.refresh_requested = FALSE; } } else cvdone = TRUE; retest2: if (ov->listcontrols.refresh_requested) { ovdone = !( ov->listcontrols.norefresh || g_atomic_int_get (&ov->listcontrols.refresh_working) || g_atomic_int_get (&ov->listcontrols.cd_working) #ifdef E2_STATUS_BLOCK || g_atomic_int_get (&app.status_working) #endif ); if (ovdone) { LISTS_UNLOCK #ifdef E2_VFSTMP //FIXME dir when not mounted local #else printd (DEBUG, "accepting request to refresh %s", ov->dir); #endif if (pthread_create (&thisID, NULL, (gpointer(*)(gpointer))e2_fileview_refresh_list, ov) == 0) { pthread_join (thisID, &result); //only 1 refresh at a time ovhook = TRUE; } else printd (WARN, "Failed to create list refresh thread"); LISTS_LOCK if (result != NULL) ov->listcontrols.refresh_requested = FALSE; } } else ovdone = TRUE; if (cv->listcontrols.refresh_requested || ov->listcontrols.refresh_requested) { //again/now/still want to do one or both panes //but only go back now not blocked before if (cv->listcontrols.refresh_requested && cvdone) { LISTS_UNLOCK goto retest; } if (ov->listcontrols.refresh_requested && ovdone) { LISTS_UNLOCK goto retest2; } printd (DEBUG, "more refresh request(s), deferred because busy"); app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 200, (GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (200), (GDestroyNotify) _e2_filelist_timer_shutdown); } LISTS_UNLOCK //CHECKME do this separately e.g. in idle, to avoid any delay ? if (cvhook) g_idle_add ((GSourceFunc)_e2_fileview_more_refresh, curr_view); // e2_hook_list_run (&curr_view->hook_refresh, curr_view); if (ovhook) // e2_hook_list_run (&other_view->hook_refresh, other_view); g_idle_add ((GSourceFunc)_e2_fileview_more_refresh, other_view); return FALSE; } /** @brief log request, and possibly initiate, filelist(s) refresh if it/they currently show @a dir If a refresh is initiated, either or both flagged lists will be refreshed as soon as any in-progress re-list (either or both panes) is completed @param dir the dir to be refreshed (utf8 string) @param immediate TRUE to intiate a refresh @return TRUE if a refresh was initiated */ gboolean e2_filelist_request_refresh (gchar *dir, gboolean immediate) { gboolean matched = FALSE; #ifdef E2_VFSTMP //CHECKME v-dir ok #endif if (!strcmp (curr_view->dir, dir)) { matched = TRUE; LISTS_LOCK curr_view->listcontrols.refresh_requested = TRUE; LISTS_UNLOCK } #ifdef E2_VFSTMP //CHECKME v-dir ok #endif if (!strcmp (other_view->dir, dir)) { matched = TRUE; LISTS_LOCK other_view->listcontrols.refresh_requested = TRUE; LISTS_UNLOCK } if (matched && immediate) //clear any fs report about dirty list, then do the refresh e2_filelist_check_dirty (NULL); return matched; } /** @brief decide whether to lengthen the interval between filelist refresh requests This is called when a 'dirty' flag has been detected @param pane enumerator for pane 1 or pane 2, which was reported dirty @return TRUE when */ static gboolean _e2_filelist_refresh_ok (E2_ListChoice pane) { gboolean retval; /* Ideally this would check CPU resource-usage for this app and all children, and only proceed if there's enough(?) idle capacity Instead we assume small dirs can be processed without much adverse effect */ gint times, itemcount = (pane == PANE1) ? //these are counts of total items, not filtered items gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane1.view.store), NULL): gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane2.view.store), NULL); if (itemcount < 51) times = 1; else if (itemcount < 251) times = 2; else times = 3; if (pane == PANE1) { if (++pane1repeats >= times) { printd (DEBUG, "Pane 1 refresh interval count = %d", times); pane1repeats = 0; retval = TRUE; } else retval = FALSE; } else { if (++pane2repeats >= times) { printd (DEBUG, "Pane 2 refresh interval count = %d", times); pane2repeats = 0; retval = TRUE; } else retval = FALSE; } return retval; } /** @brief request refresh of filepane(s) contents (and with FAM, config too) if the corresponding changed is detected Among other uses, this is the timer callback function for filelists auto refresh When FAM/gamin in use, it also checks for and flags (not processes) config file updates, as that data comes from the same data stream. In certain circumstances any dirty flag might be ignored, or deferred to lengthen the effective interval between refreshes This expects gtk's BGL to be off/open @param userdata data specified when the timer was initialised (NULL), or when called directly, non-NULL @return TRUE so the timer keeps working */ gboolean e2_filelist_check_dirty (gpointer userdata) { static gboolean busy = FALSE; if (busy) return TRUE; //no reentrant usage busy = TRUE; #ifdef E2_FAM static gboolean cfgdeferred = FALSE; #endif gboolean p1dirty, p2dirty; #ifdef E2_FAM gboolean localcfgdirty; //use a local copy so that the extern value doesn't get cleared before it's noticed extern gboolean cfgdirty; if (userdata != NULL && app.monitor_type != E2_MONITOR_DEFAULT) g_usleep (E2_FAMWAIT); //wait for things to be noticed #endif #ifndef E2_FAM if (userdata != NULL) { p1dirty = p2dirty = TRUE; //force a refresh } else #endif //this reports 'gone' as 'dirty', and then the list-refesh //function handles choosing a replacement dir e2_fs_FAM_check_dirty (&p1dirty, &p2dirty #ifdef E2_FAM , &localcfgdirty #endif ); p1dirty = p1dirty || pane1repeats; p2dirty = p2dirty || pane2repeats; if (p1dirty || p2dirty) { LISTS_LOCK if (p1dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE1))) app.pane1.view.listcontrols.refresh_requested = TRUE; else p1dirty = FALSE; if (p2dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE2))) app.pane2.view.listcontrols.refresh_requested = TRUE; else p2dirty = FALSE; LISTS_UNLOCK if (p1dirty || p2dirty) { if (app.timers[REFRESHBEGIN_T] > 0) { //clear any incomplete refresh request g_source_remove (app.timers[REFRESHBEGIN_T]); } //the delay between attempts will be lengthened later, if appropriate //CHECKME we may be in a timer callback here - are nested timers still acceptable to glib/gtk ? app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 5, (GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (5), (GDestroyNotify) _e2_filelist_timer_shutdown); } } #ifdef E2_FAM if (localcfgdirty || cfgdeferred) { if (!app.rebuild_enabled) { cfgdeferred = TRUE; printd (DEBUG, "config refresh deferred"); } else { cfgdeferred = FALSE; if (localcfgdirty) { cfgdirty = TRUE; //set, but don't clear, the main flag printd (DEBUG, "config refresh flag set"); } } } #endif //def E2_FAM busy = FALSE; return TRUE; } /** @brief initialise timer which polls for various 'dirty' indicator(s) The timer is never suspended or paused, to avoid the risk of overflow from kernel-fam processes. It may be stopped permanently if change-monitoring is abandoned via a config dialog @return */ void e2_filelist_start_refresh_polling (void) { if (e2_option_bool_get ("auto-refresh")) { app.timers[DIRTYCHECK_T] = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_FILESCHECK_INTERVAL_S, #else g_timeout_add (E2_FILESCHECK_INTERVAL, #endif (GSourceFunc) e2_filelist_check_dirty, NULL); } } /** @brief idle function to clear old liststores @param user_data UNUSED data specified when the idle was setup @return FALSE, to stop the callbacks */ gboolean e2_filelist_clear_old_stores (gpointer user_data) { GSList *tmp; #ifdef DEBUG_MESSAGES gint debug = g_slist_length (app.used_stores); #endif for (tmp = app.used_stores; tmp != NULL; tmp = tmp->next) { GtkListStore *store = tmp->data; GtkTreeModel *mdl = GTK_TREE_MODEL (store); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { //it's not empty already //clear file info data structs referred to in the store //CHECKME need to clear anything else? FileInfo *info; do { gtk_tree_model_get (mdl, &iter, FINFO, &info, -1); #ifdef USE_GLIB2_10 g_slice_free1 (sizeof(FileInfo), info); //DEALLOCATE (FileInfo, info); #else DEALLOCATE (FileInfo, info); #endif } while (gtk_tree_model_iter_next (mdl, &iter)); } //CHECKME clear filtermodel ? g_object_unref (G_OBJECT (store)); } g_slist_free (app.used_stores); app.used_stores = NULL; printd (DEBUG, "%d old liststore(s) cleared", debug); return FALSE; } /** @brief create and populate filelist-compatible list store with rows for each item in @a entries @param entries list of FileInfo's to be processed @param view pointer to data struct for the view to which the filled store will apply @return pointer to the liststore, or NULL if a problem occurs */ GtkListStore *e2_filelist_fill_store (GList *entries, ViewInfo *view) { // printd (DEBUG, "start store fill"); GtkListStore *store = e2_filelist_make_store (); if (store == NULL) return NULL; guint i; GList *tmp; FileInfo *infoptr; GtkTreeIter iter; struct tm *tm_ptr; struct passwd *pwd_buf; struct group *grp_buf; gchar size_buf[20]; //enough for 999 Tb gchar modified_buf[25]; gchar accessed_buf[25]; gchar changed_buf[25]; // gchar perm_buf[11]; gchar uid_buf[20]; gchar gid_buf[20]; gchar *buf[NAMEKEY+1]; //pointers to strings to be inserted into each row gchar *freeme; gchar *__utf__; //for e2_utf8_from_locale_fast() macro //get format parameters //format for dates display gchar *strf_string; switch (e2_option_int_get ("date-string")) { case 1: strf_string = "%d/%m/%y %H:%M"; //standard break; case 2: strf_string = "%m/%d/%y %H:%M"; //american break; case 3: strf_string = "%Y-%m-%dT%H:%M"; //ISO8601 break; case 4: /* { strf_string = nl_langinfo (D_T_FMT); if (strf_string != NULL && *strf_string != '\0') break; } */ strf_string = "%x %X"; //localised break; default: strf_string = "%b %d %H:%M"; break; } //get format for size display gchar *comma; switch (e2_option_int_get ("size-string")) { case 1: { comma = nl_langinfo (THOUSEP); if (comma == NULL || *comma == '\0') comma = ","; break; } default: comma = NULL; //signal to use the condensed version break; } gboolean anyconvert = FALSE; gboolean caseignore = ! e2_option_bool_get ("namesort-case-sensitive"); GtkStyle *style = gtk_rc_get_style (curr_view->treeview); GdkColor *default_color = &style->text[GTK_STATE_NORMAL]; #ifdef E2_ASSISTED GdkColor *back_color = (e2_option_bool_get ("color-background-set")) ? e2_option_color_get ("color-background") : NULL; #endif GdkColor *link_color = e2_option_color_get ("color-ft-link"); GdkColor *dir_color = e2_option_color_get ("color-ft-dir"); GdkColor *dev_color = e2_option_color_get ("color-ft-dev"); GdkColor *sock_color = e2_option_color_get ("color-ft-socket"); GdkColor *exec_color = e2_option_color_get ("color-ft-exec"); //iterate through the listed FileInfo's for (tmp = entries; tmp != NULL; tmp = tmp->next) { gboolean convert; //also a cleanup flag gchar *s; register char c; infoptr = (FileInfo *) tmp->data; //with bad unmount behaviour, there can be no actual data if (infoptr == NULL) continue; //CHECKME remove the item from list ? //scan the namestring for non-ASCII char(s) convert = FALSE; for (s = infoptr->filename; (c = *s) != '\0'; s++) { if (c < 0) //non-ASCII { convert = TRUE; anyconvert = TRUE; break; } } //convert to UTF-8 if appropriate if (convert) { buf[FILENAME] = g_filename_display_name (infoptr->filename); /* if (g_utf8_validate (infoptr->filename, -1, NULL)) { convert = FALSE; buf[FILENAME] = infoptr->filename; } else { //_not display_ so that it can be casefolded and/or collated properly ?? buf[FILENAME] = g_locale_to_utf8 (infoptr->filename, -1, NULL, NULL, NULL); if (buf[FILENAME] == NULL) buf[FILENAME] = e2_utf8_from_locale_fallback (infoptr->filename); } */ } else buf[FILENAME] = infoptr->filename; if (caseignore) { freeme = g_utf8_casefold (buf[FILENAME], -1); #ifdef USE_GTK2_8 buf[NAMEKEY] = g_utf8_collate_key_for_filename (freeme, -1); #else buf[NAMEKEY] = g_utf8_collate_key (freeme, -1); #endif g_free (freeme); } else #ifdef USE_GTK2_8 buf[NAMEKEY] = g_utf8_collate_key_for_filename (buf[FILENAME], -1); #else buf[NAMEKEY] = g_utf8_collate_key (buf[FILENAME], -1); #endif //dirlink check done when info was created if (infoptr->statbuf.st_mode & (S_IFDIR | E2_DIRLNK)) { //directories (and links to dirs) get a trailing separator s = buf[FILENAME]; if (convert) { guint l = strlen (s); if ((s = (gchar *)realloc (s, l+2)) != NULL) { buf[FILENAME] = s; s += l; *s++ = G_DIR_SEPARATOR; *s = '\0'; } } else { convert = TRUE; //cleanup this one too buf[FILENAME] = e2_utils_strcat (s, G_DIR_SEPARATOR_S); } } if (comma == NULL) { //use condensed size format if (infoptr->statbuf.st_size < 1024) //less than 1k { g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64, infoptr->statbuf.st_size); } else if (infoptr->statbuf.st_size < 1048576) //less than a meg { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1024.0, _("k")); } else //a meg or more if (infoptr->statbuf.st_size < 1073741824) { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1048576.0, _("M")); } /* else //a gig or more { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1073741824.0, _("G")); } */ } else { //use actual size, with commas g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64, infoptr->statbuf.st_size); guint len = strlen (size_buf); guint ths = len-1; //0-based index while (ths > 2 && len < sizeof(size_buf)) { for (i = len-1; i > ths-3; i--) size_buf[i+1] = size_buf[i]; size_buf[i+1] = *comma; size_buf[++len] = '\0'; ths = i; } } buf[SIZE] = size_buf; //content is ascii/utf-8, no need to convert or free buf[PERM] = e2_fs_get_perm_string (infoptr->statbuf.st_mode); //string is already utf if ((pwd_buf = getpwuid (infoptr->statbuf.st_uid)) == NULL) { g_snprintf (uid_buf, sizeof(uid_buf), "%d", (guint) infoptr->statbuf.st_uid); buf[OWNER] = g_strdup (uid_buf); //content is ascii number, no need to convert to utf } else { buf[OWNER] = e2_utf8_from_locale_fast (pwd_buf->pw_name); } if ((grp_buf = getgrgid (infoptr->statbuf.st_gid)) == NULL) { g_snprintf(gid_buf, sizeof(gid_buf), "%d", (guint) infoptr->statbuf.st_gid); buf[GROUP] = g_strdup (gid_buf); //content is ascii number, no need to convert to utf } else { buf[GROUP] = e2_utf8_from_locale_fast (grp_buf->gr_name); } tm_ptr = localtime (&(infoptr->statbuf.st_mtime)); strftime (modified_buf, sizeof(modified_buf), strf_string, tm_ptr); buf[MODIFIED] = e2_utf8_from_locale_fast (modified_buf); tm_ptr = localtime (&(infoptr->statbuf.st_atime)); strftime (accessed_buf, sizeof(accessed_buf), strf_string, tm_ptr); buf[ACCESSED] = e2_utf8_from_locale_fast (accessed_buf); tm_ptr = localtime(&(infoptr->statbuf.st_ctime)); strftime (changed_buf, sizeof(changed_buf), strf_string, tm_ptr); buf[CHANGED] = e2_utf8_from_locale_fast (changed_buf); GdkColor *foreground; switch (infoptr->statbuf.st_mode & S_IFMT) { case S_IFLNK: foreground = link_color; break; case S_IFDIR: foreground = dir_color; break; case S_IFCHR: case S_IFBLK: foreground = dev_color; break; case S_IFSOCK: foreground = sock_color; break; case S_IFREG: //show as executable if _anyone_ has that permission if ((S_IXUSR & infoptr->statbuf.st_mode) || (S_IXGRP & infoptr->statbuf.st_mode) || (S_IXOTH & infoptr->statbuf.st_mode)) foreground = exec_color; else { #ifdef E2_RAINBOW //find extension of current item //localised text, but assumes . is ascii //hashed extension-strings used for comparison have been localised foreground = NULL; //in case there's no '.' at all gchar *ext = infoptr->filename + 1; //+1 in case item is hidden while ((ext = strchr (ext,'.')) != NULL) { ext++; //pass the '.' //use its color, if any foreground = g_hash_table_lookup (app.colors, ext); if (foreground != NULL) break; } if (foreground == NULL) foreground = default_color; #else foreground = default_color; #endif } break; default: foreground = default_color; break; } //gtk >= 2.10 can handle &iter = NULL gtk_list_store_insert_with_values (store, &iter, -1, FILENAME, buf[FILENAME], SIZE, buf[SIZE], PERM, buf[PERM], OWNER, buf[OWNER], GROUP, buf[GROUP], MODIFIED, buf[MODIFIED], ACCESSED, buf[ACCESSED], CHANGED, buf[CHANGED], NAMEKEY, buf[NAMEKEY], FINFO, infoptr, FORECOLOR, foreground, #ifdef E2_ASSISTED BACKCOLOR, back_color, #endif // VISIBLE, TRUE, -1); //cleanup if (convert) g_free (buf[FILENAME]); for (i = PERM; i <= NAMEKEY; i++) g_free (buf[i]); } if (anyconvert != view->convert) { view->convert = anyconvert; //flag need for change of conversion funcs at a suitable time app.reconvert_requested = TRUE; } /* // gettimeofday (&strt, NULL); //FOR DEBUGGING, check time this load takes // gettimeofday (&nd, NULL); if (nd.tv_usec < strt.tv_usec) { int nsec = (strt.tv_usec - nd.tv_usec) / 1000000 + 1; strt.tv_usec -= 1000000 * nsec; strt.tv_sec += nsec; } if (nd.tv_usec - strt.tv_usec > 1000000) { int nsec = (strt.tv_usec - nd.tv_usec) / 1000000; strt.tv_usec += 1000000 * nsec; strt.tv_sec -= nsec; } result.tv_sec = result.tv_sec + nd.tv_sec - strt.tv_sec; result.tv_usec = result.tv_usec + nd.tv_usec - strt.tv_usec; if (result.tv_usec > 1000000) { int nsec = result.tv_usec / 1000000 + 1; result.tv_usec -= 1000000 * nsec; result.tv_sec += nsec; } // printd (DEBUG, "populating rows took %f seconds", result.tv_sec + result.tv_usec / 1000000.0 ); */ // printd (DEBUG, "populating rows finished"); return store; } /** @brief create list store structure Creates basic structure of list store, for panes filelists No rows or data are added. @return the new liststore */ GtkListStore *e2_filelist_make_store (void) { GtkListStore *store = gtk_list_store_new (MODEL_COLUMNS, G_TYPE_STRING, //FILENAME G_TYPE_STRING, //SIZE G_TYPE_STRING, //PERM G_TYPE_STRING, //OWNER G_TYPE_STRING, //GROUP G_TYPE_STRING, //MODIFIED G_TYPE_STRING, //ACCESSED G_TYPE_STRING, //CHANGED // the rest are not displayed G_TYPE_STRING, //NAMEKEY for i18n name sorts G_TYPE_POINTER, //FINFO pr to FileInfo for the item GDK_TYPE_COLOR, //FORECOLOR line colour GDK_TYPE_COLOR //BACKCOLOR line colour // G_TYPE_BOOLEAN //VISIBLE whether filtered or not ); return store; } #ifdef STORECOPY /** @brief helper function to copy each FileInfo stored in a filelist store @param model the store to be updated @param tpath UNUSED tree path of iter being processed ? @param iter pointer to iter being processed @param user_data UNUSED data specified when foreach was called @return FALSE if the process can continue */ static gboolean _e2_filelist_copy_storeinfos (GtkTreeModel *model, GtkTreePath *tpath, GtkTreeIter *iter, gpointer user_data) { FileInfo *info; gtk_tree_model_get (model, iter, FINFO, &info, -1); //FIXME duplicate the data #ifdef USE_GLIB2_14 info = (FileInfo *) g_slice_copy (sizeof (FileInfo), info); gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info, -1); #else FileInfo *info2 = ALLOCATE (FileInfo); CHECKALLOCATEDWARN (info2, return TRUE;) memcpy (info2, info, sizeof (FileInfo)); gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info2, -1); #endif // gtk_tree_path_free (tpath); CRASHER return FALSE; } /** @brief copy a filelist liststore with all its contents @param original the store to be copied @return the new liststore */ GtkListStore *e2_filelist_copy_store (GtkListStore *original) { gpointer copied; e2_tree_store_copy (GTK_TREE_MODEL (original), FALSE, &copied); gtk_tree_model_foreach (GTK_TREE_MODEL ((GtkListStore *)copied), (GtkTreeModelForeachFunc) _e2_filelist_copy_storeinfos, NULL); return ((GtkListStore *)copied); } #endif