/* filelist.c * This file is part of "Pharmacy: A GNOME CVS front-end" * Copyright 1998 Reklaw (N. Adam Walker) * Copyright 2000, 2001 Matthias Kranz * "Pharmacy" is released under the GPL. * See the LICENCE for more details. */ #include #include #include "pharmacy.h" #include "filelist.h" #include "status.h" #include #include #include /* Icons */ #include "stock.h" #include "plussm.xpm" #include "xsm.xpm" #include "bandaidsm.xpm" #include "questionsm.xpm" #include "disksm.xpm" #include "mergesm.xpm" #define BUFSIZE 255 static GtkCListClass *parent_class = NULL; static void filelist_class_init (FileListClass * klass); static void filelist_destroy (GtkObject * object); static void filelist_init (FileList * object); /*************************************************** * filelist_class_init * * Description: * The inits the class. It's called by the framework. ***************************************************/ static void filelist_class_init (FileListClass * klass) { GtkObjectClass *objectclass = GTK_OBJECT_CLASS (klass); g_assert (klass); objectclass->destroy = filelist_destroy; } /*************************************************** * filelist_new * * Description: * Creates a new filelist widget. ***************************************************/ GtkWidget * filelist_new () { GtkWidget *retval = NULL; FileList *filelist = NULL; retval = gtk_type_new (filelist_get_type ()); filelist = FILE_LIST (retval); return retval; } static void select_row_cb (GtkCList * pList, gint nRow, gint nColumn) { gdk_flush (); pharmacy_update_ui ("file-select"); } static void unselect_row_cb (GtkCList * pList, gint nRow, gint nColumn) { gdk_flush (); pharmacy_update_ui ("file-unselect"); } /*************************************************** * filelist_new_with_titles * * Description: * Creates a new filelist widget. Inits titles ***************************************************/ GtkWidget * filelist_new_with_titles (const int count, gchar ** titles) { GtkWidget *retval = NULL; FileList *filelist = NULL; register gint i; g_assert (count); g_assert (titles); retval = gtk_type_new (filelist_get_type ()); filelist = FILE_LIST (retval); gtk_clist_construct (GTK_CLIST (retval), count, titles); gtk_signal_connect (GTK_OBJECT (filelist), "select-row", select_row_cb, NULL); gtk_signal_connect (GTK_OBJECT (filelist), "unselect-row", unselect_row_cb, NULL); return retval; } /*************************************************** * filelist_get_type * * Description: * return the widget's type id. Used by the framework. ***************************************************/ guint filelist_get_type () { static guint type = 0; if (!type) { GtkTypeInfo info = { "FileList", sizeof (FileList), sizeof (FileListClass), (GtkClassInitFunc) filelist_class_init, (GtkObjectInitFunc) filelist_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; type = gtk_type_unique (gtk_clist_get_type (), &info); parent_class = gtk_type_class (gtk_clist_get_type ()); } return type; } /*************************************************** * filelistnode_new * * Description: * Creates a new filelistnode object. ***************************************************/ FileListNode * filelistnode_new () { FileListNode *pRet = g_malloc (sizeof (FileListNode)); pRet->pgcName = NULL; pRet->pgcPath = NULL; pRet->pDirTreeNode = NULL; pRet->eStatus = FLN_STATUS_UNKNOWN; return pRet; } /*************************************************** * filelist_init * * Description: * inits a filelist widget. Called by the framework. ***************************************************/ static void filelist_init (FileList * object) { g_assert (object && IS_FILE_LIST (object)); gtk_clist_set_selection_mode (GTK_CLIST (object), GTK_SELECTION_MULTIPLE); object->nName = 0; object->nVersion = 1; object->nStatus = 2; object->nDate = 3; object->nCols = 4; object->pmModified = NULL; object->mkModified = NULL; gdk_imlib_data_to_pixmap (questionsm_xpm, &object->pmQuestion, &object->mkQuestion); gdk_imlib_data_to_pixmap (plussm_xpm, &object->pmAdded, &object->mkAdded); gdk_imlib_data_to_pixmap (xsm_xpm, &object->pmRemoved, &object->mkRemoved); gdk_imlib_data_to_pixmap (bandaidsm_xpm, &object->pmNeedsPatch, &object->mkNeedsPatch); gdk_imlib_data_to_pixmap (mergesm_xpm, &object->pmNeedsMerge, &object->mkNeedsMerge); gdk_imlib_data_to_pixmap (disksm_xpm, &object->pmModified, &object->mkModified); gnome_stock_pixmap_gdk (PHARMACY_STOCK_PIXMAP_BOLT, GNOME_STOCK_PIXMAP_REGULAR, &object->pmNeedsCheckout, &object->mkNeedsCheckout); object->entries = g_list_alloc (); } /*************************************************** * filelist_destroy * * Description: * Deallocate a filelist widget ***************************************************/ static void filelist_destroy (GtkObject * object) { FileList *pList = NULL; g_assert (object && IS_FILE_LIST (object)); pList = FILE_LIST (object); filelist_cleanse (pList); } /*************************************************** * filelistnode_set * * Description: * Set the values in a filelistnode ***************************************************/ void filelistnode_set (FileListNode * pNode, gchar * name, DirTreeNode * pTreeNode) { g_assert (pNode); g_assert (pTreeNode); g_assert (name); pNode->pgcName = g_strdup (name); pNode->pDirTreeNode = dirtreenode_add_ref (pTreeNode); g_assert (pTreeNode->pgcDir); pNode->pgcPath = g_strconcat (pTreeNode->pgcDir, name, NULL); } #define SET_MAPS(a) pPixmap = pList->pm##a;pMask = pList->mk##a /*************************************************** * filelist_row_set_status * * Description: * Set the status for a file in the FileList. Will * update the Icon for the row. ***************************************************/ void filelist_row_set_status (FileList * pList, gint row, FileListNodeStatus eStatus) { FileListNode *pNode = NULL; GdkPixmap *pPixmap = NULL; GdkBitmap *pMask = NULL; gint bUseIcons = TRUE; g_assert (pList && IS_FILE_LIST (pList)); pNode = gtk_clist_get_row_data (GTK_CLIST (pList), row); if (pNode) { pNode->eStatus = eStatus; switch (pNode->eStatus) { case FLN_STATUS_NORMAL: /* Status Normal: Don't display an icon */ break; case FLN_STATUS_ADDED: SET_MAPS (Added); break; case FLN_STATUS_REMOVED: SET_MAPS (Removed); break; case FLN_STATUS_NEEDS_PATCH: SET_MAPS (NeedsPatch); break; case FLN_STATUS_MODIFIED: SET_MAPS (Modified); break; case FLN_STATUS_NEEDS_CHECKOUT: SET_MAPS (NeedsCheckout); break; case FLN_STATUS_NEEDS_MERGE: SET_MAPS (NeedsMerge); break; case FLN_STATUS_IGNORE: break; case FLN_STATUS_NOT_IN_CVS: break; case FLN_STATUS_UNKNOWN: default: SET_MAPS (Question); } if (bUseIcons && pPixmap && pMask) { gtk_clist_set_pixtext (GTK_CLIST (pList), row, /*column */ pList->nName, /*Text */ pNode->pgcName, /*spaceing */ 1, /*pixmap */ pPixmap, /*mask */ pMask); } else { g_assert (pList->nName == 0); gtk_clist_set_text (GTK_CLIST (pList), row, /*column */ pList->nName, pNode->pgcName); } } } /*************************************************** * filelistnode_free * * Description: * Deallocate a filelistnode object. ***************************************************/ void filelistnode_free (FileListNode * pNode) { if (pNode) { g_free (pNode->pgcName); g_free (pNode->pgcPath); dirtreenode_free (pNode->pDirTreeNode); } } /*************************************************** ***************************************************/ void filelist_cleanse (FileList * pList) { const gint count = GTK_CLIST (pList)->rows; register gint i; FileListNode *pflNode = NULL; g_assert (pList); g_assert (IS_FILE_LIST (pList)); for (i = count - 1; i >= 0; i--) { /* Get Row Data and free it */ pflNode = (FileListNode *) gtk_clist_get_row_data (GTK_CLIST (pList), i); filelistnode_free (pflNode); /* Remove item */ gtk_clist_remove (GTK_CLIST (pList), i); } } /*************************************************** ***************************************************/ static gchar * get_file_name (gchar * buf) { gchar *ret = g_malloc (sizeof (gchar) * 255); const gint count = strlen (buf); register gint i; register gint j = 0; g_assert (buf); // g_assert (buf[0] == '/'); if (buf[0] == '/') i = 1; else i = 2; for (; i < count && buf[i] != '/'; i++) ret[j++] = buf[i]; ret[j] = 0; return ret; } static gchar *parse_entry (gchar * buf, const gint nSlash); /*************************************************** ***************************************************/ static gchar * get_version (gchar * buf) { gchar *p; g_assert (buf); p = parse_entry (buf, 2); if (p == NULL) p = ""; return p; } /*************************************************** ***************************************************/ static void get_timestamp (struct tm *tmEntry, gchar * buf) { gchar *pgcTime = parse_entry (buf, 3); g_assert (pgcTime); #ifdef HAVE_STRPTIME strptime (pgcTime, "%a %h %d %T %Y", tmEntry); #endif g_free (pgcTime); } /* * return the entry after the nth slash in CVS/Entries file */ static gchar * parse_entry (gchar * buf, const gint nSlash) { gchar *ret = g_malloc (sizeof (gchar) * 25); const gint count = strlen (buf); gint slashes = 0; register gint i; register gint j = 0; memset (ret, 0, sizeof buf); g_assert (buf); // g_assert (buf[0] == '/'); for (i = 0; i < count; i++) { if (slashes == nSlash) { if (buf[i] == '/') { break; } else { ret[j++] = buf[i]; } } else { if (buf[i] == '/') slashes++; } } ret[j] = 0; return ret; } void filelist_fill_entries (FileList * pList, gchar * path) { gint bCont = TRUE; FileListNode *pflNode = NULL; DirTreeNode *pdtNode = NULL; FILE *pEntries; gchar *filename, *dirname; gchar buf[BUFSIZE]; struct tm tmEntry; time_t tEntry; struct stat sStat; static gchar *cvsbit = "CVS/Entries"; FileListNodeStatus eStatus = FLN_STATUS_UNKNOWN; g_assert (pList); g_assert (IS_FILE_LIST (pList)); g_assert (path); filename = g_strconcat (path, cvsbit, NULL); pEntries = fopen (filename, "r"); g_free (filename); if (pEntries) { while (bCont) { /* Let window updates happen */ gdk_flush (); bCont = fgets (buf, sizeof (buf), pEntries) ? TRUE : FALSE; if (bCont) { if (buf[0] == '/') { /* Make a list entry */ pflNode = filelistnode_new (); pflNode->type = CVS_FILE; pflNode->pgcName = get_file_name (buf); pflNode->pgcPath = path; pflNode->pgcVersion = get_version (buf); if (pflNode->pgcVersion[0] != '0') get_timestamp (&tmEntry, buf); eStatus = FLN_STATUS_UNKNOWN; if (!stat (pflNode->pgcPath, &sStat)) { #ifdef HAVE_STRPTIME tEntry = mktime (&tmEntry); if (sStat.st_mtime > tEntry) { if (atoi (pflNode->pgcVersion)) { eStatus = FLN_STATUS_MODIFIED; } else { eStatus = FLN_STATUS_ADDED; } } #endif } else { if (atoi (pflNode->pgcVersion) < 0) { eStatus = FLN_STATUS_REMOVED; } else { eStatus = FLN_STATUS_NEEDS_CHECKOUT; } } } else if (buf[0] == 'D' && buf[1] == '/') { /* Make a dir entry */ pdtNode = dirtreenode_new (); pdtNode->type = CVS_DIR; pdtNode->pgcName = get_file_name (buf); dirname = g_strconcat (path, pdtNode->pgcName, "/", NULL); dirtreenode_set_dir (pdtNode, dirname); g_free (dirname); if (!stat (pdtNode->pgcDir, &sStat) && sStat.st_nlink > 3) pdtNode->hasSubdirs = 1; else pdtNode->hasSubdirs = 0; g_debug_message3 ("dir %s found in %s", pdtNode->pgcName, path); g_list_append (pList->entries, pdtNode); } else if (buf[0] != 'D') g_debug_message3 ("Unknown entry \"%s\" found in %sCVS/Entries", buf, path); } } fclose (pEntries); } } GList * filelist_get_dir_entries (FileList * pList) { g_assert (pList); g_assert (IS_FILE_LIST (pList)); return pList->entries; } typedef struct _FLSelectData FLSelectData; struct _FLSelectData { gchar *pStr; FileList *pFileList; }; /*************************************************** ***************************************************/ static void get_selected_cb (gpointer pData, gpointer pSelection) { FLSelectData *pSelectData = (FLSelectData *) pSelection; gint nRow = (gint) pData; FileListNode *pNode = NULL; gchar *pTemp = NULL; g_return_if_fail (pSelectData); g_return_if_fail (pSelectData->pFileList); pNode = gtk_clist_get_row_data (GTK_CLIST (pSelectData->pFileList), nRow); g_return_if_fail (pNode); pTemp = g_strconcat (pNode->pgcName, " ", NULL); concat_to_buffer (&(pSelectData->pStr), pTemp); g_free (pTemp); } /*************************************************** ***************************************************/ gchar * filelist_get_selected_files (FileList * pList) { FLSelectData *pData = NULL; gchar *pRet = NULL; g_return_val_if_fail (pList, NULL); g_assert (IS_FILE_LIST (pList)); pData = g_malloc (sizeof (FLSelectData)); memset (pData, 0, sizeof (FLSelectData)); pData->pFileList = pList; /* Fire! */ g_list_foreach (GTK_CLIST (pList)->selection, get_selected_cb, pData); pRet = pData->pStr; g_free (pData); return pRet; } /*************************************************** ***************************************************/ gint filelist_find_row_with_filename (FileList * pList, const gchar * pgcTarget) { gint nRet = -1; gint i = 0; FileListNode *pNode = NULL; const gint max = (GTK_CLIST (pList))->rows; g_return_val_if_fail (pList, nRet); g_assert (IS_FILE_LIST (pList)); g_return_val_if_fail (pgcTarget, nRet); for (; i < max; i++) { pNode = gtk_clist_get_row_data (GTK_CLIST (pList), i); if (pNode) { if (pNode->pgcName && !strcmp (pgcTarget, pNode->pgcName)) { nRet = i; break; } } } return nRet; } /*************************************************** ***************************************************/ void filelist_build_list (FileList * pList, DirTreeNode * pTreeNode) { gint bCont = TRUE; gint row = 0; FileListNode *pflNode = NULL; FILE *pEntries; gchar *text[5]; gchar *path = NULL; gchar buf[BUFSIZE]; struct tm tmEntry; time_t tEntry; struct stat sStat; static gchar *cvsbit = "CVS/Entries"; FileListNodeStatus eStatus = FLN_STATUS_UNKNOWN; // Counteract the growing widget behaviour gtk_widget_set_usize (GTK_WIDGET (pList), GTK_WIDGET (pList)->allocation.width, GTK_WIDGET (pList)->allocation.height); g_assert (pList); g_assert (pTreeNode); g_assert (IS_FILE_LIST (pList)); gtk_clist_freeze (GTK_CLIST (pList)); gtk_clist_set_auto_sort (GTK_CLIST (pList), TRUE); /* Build File Name */ path = g_strconcat (pTreeNode->pgcDir, cvsbit, NULL); filelist_fill_entries (pList, pTreeNode->pgcDir); pEntries = fopen (path, "r"); if (pEntries) { while (bCont) { /* Let window updates happen */ gdk_flush (); bCont = fgets (buf, sizeof (buf), pEntries) ? TRUE : FALSE; if (bCont) { if (buf[0] == '/') { /* Make a list entry */ text[pList->nName] = get_file_name (buf); text[pList->nVersion] = get_version (buf); text[pList->nStatus] = g_strdup (status_to_text (FLN_STATUS_UNKNOWN)); get_timestamp (&tmEntry, buf); text[pList->nDate] = g_strdup (asctime (&tmEntry)); row = filelist_find_row_with_filename (pList, text[pList-> nName]); if (row > -1) { /* found */ filelist_set_row_text (pList, row, text); pflNode = gtk_clist_get_row_data (GTK_CLIST (pList), row); } else { /* Not found */ row = gtk_clist_append (GTK_CLIST (pList), text); pflNode = filelistnode_new (); filelistnode_set (pflNode, text[0], pTreeNode); gtk_clist_set_row_data (GTK_CLIST (pList), row, pflNode); } eStatus = FLN_STATUS_UNKNOWN; if (!stat (pflNode->pgcPath, &sStat)) { #ifdef HAVE_STRPTIME /* sStat.stxmtime is the actual timestamp of a file * tmEntry is the entry in the CVS/Entries file * convert to UTC */ struct tm *ptmMTime, *zwischen; time_t mtime; ptmMTime = gmtime (&sStat.st_mtime); mtime = mktime (ptmMTime); tEntry = mktime (&tmEntry); if (mtime > tEntry) { if (atoi (text[pList->nVersion])) { eStatus = FLN_STATUS_MODIFIED; } else { eStatus = FLN_STATUS_ADDED; } // if !atoi text[1] } // if mtime > tEntry else if (mtime == tEntry) { eStatus = FLN_STATUS_NORMAL; } // if mtime == tEntry #endif filelist_row_set_status (pList, row, eStatus); } // if !stat else { if (atoi (text[pList->nVersion]) < 0) { eStatus = FLN_STATUS_REMOVED; } else { eStatus = FLN_STATUS_NEEDS_CHECKOUT; } // if !atoi text[1] filelist_row_set_status (pList, row, eStatus); } // if stat g_assert (pList->nStatus != 0); gtk_clist_set_text (GTK_CLIST (pList), row, pList->nStatus, g_strdup (status_to_text (eStatus))); } // if buf[0] == '/' } // if bcont } // while cont fclose (pEntries); } // if pEntries gtk_clist_thaw (GTK_CLIST (pList)); if (path) g_free (path); } /*************************************************** ***************************************************/ void filelist_rebuild_list (FileList * pList, DirTreeNode * pTreeNode) { filelist_build_list (pList, pTreeNode); #if 0 gint bCont = TRUE; gint row = 0; FileListNode *pflNode = NULL; FILE *pEntries; gchar *text[10]; gchar *path = NULL; gchar *version = NULL; gchar buf[BUFSIZE]; struct tm tmEntry; time_t tEntry; struct stat sStat; static gchar *cvsbit = "CVS/Entries"; FileListNodeStatus eStatus = FLN_STATUS_UNKNOWN; g_assert (pList); g_assert (pTreeNode); /* Build File Name */ path = g_strconcat (pTreeNode->pgcDir, cvsbit, NULL); pEntries = fopen (path, "r"); if (pEntries) { gtk_clist_freeze (GTK_CLIST (pList)); while (bCont) { /* Let window updates happen */ gdk_flush (); bCont = fgets (buf, sizeof (buf), pEntries) ? TRUE : FALSE; if (bCont) { if (buf[0] == '/') { /* Make a list entry */ text[pList->nName] = get_file_name (buf); text[pList->nVersion] = get_version (buf); text[pList->nStatus] = g_strdup (status_to_text (FLN_STATUS_UNKNOWN)); get_timestamp (&tmEntry, buf); text[pList->nDate] = g_strdup (asctime (&tmEntry)); row = gtk_clist_append (GTK_CLIST (pList), text); pflNode = filelistnode_new (); filelistnode_set (pflNode, text[pList->nName], pTreeNode); gtk_clist_set_row_data (GTK_CLIST (pList), row, pflNode); eStatus = FLN_STATUS_UNKNOWN; if (!stat (pflNode->pgcPath, &sStat)) { #ifdef HAVE_STRPTIME tEntry = mktime (&tmEntry); if (sStat.st_mtime > tEntry) { if (atoi (text[pList->nVersion])) { eStatus = FLN_STATUS_MODIFIED; } else { eStatus = FLN_STATUS_ADDED; } } #endif filelist_row_set_status (pList, row, eStatus); } else { if (atoi (text[pList->nVersion]) < 0) { eStatus = FLN_STATUS_REMOVED; } else { eStatus = FLN_STATUS_NEEDS_CHECKOUT; } filelist_row_set_status (pList, row, eStatus); } g_free (text[pList->nName]); if (version && g_strcasecmp (version, text[pList->nVersion]) != 0) { gtk_clist_set_text (GTK_CLIST (pList), row, pList->nVersion, text[pList->nVersion]); } } gtk_clist_set_text (GTK_CLIST (pList), row, pList->nStatus, g_strdup (status_to_text (eStatus))); } } fclose (pEntries); gtk_clist_thaw (GTK_CLIST (pList)); } if (path) g_free (path); #endif } #define CONFIG_MW_FILELIST_WIDTH "/pharmacy/Main Window/filelistwidth" #define CONFIG_MW_FILELIST_HEIGHT "/pharmacy/Main Window/filelistheight" /*************************************************** ***************************************************/ void filelist_load_size (GtkWidget * pWidget) { gint16 nWidth = 0; gint16 nHeight = 0; g_assert (pWidget && GTK_IS_WIDGET (pWidget)); nWidth = gnome_config_get_int (CONFIG_MW_FILELIST_WIDTH); nHeight = gnome_config_get_int (CONFIG_MW_FILELIST_HEIGHT); if (nWidth == 0) nWidth = 266; if (nHeight == 0) nHeight = 200; g_assert (pWidget && GTK_IS_WIDGET (pWidget)); gtk_widget_set_usize (pWidget, nWidth, nHeight); g_debug_message3 ("Filelist size load: %i %i", nWidth, nHeight); } /*************************************************** ***************************************************/ void filelist_save_size (GtkWidget * pWidget) { gint nWidth = 0, nHeight = 0; g_assert (pWidget && GTK_IS_WIDGET (pWidget)); /* Is there a better way to get these ? */ nWidth = pWidget->allocation.width; nHeight = pWidget->allocation.height; g_debug_message3 ("Filelist size save: %i %i", nWidth, nHeight); gnome_config_set_int (CONFIG_MW_FILELIST_WIDTH, nWidth); gnome_config_set_int (CONFIG_MW_FILELIST_HEIGHT, nHeight); } void filelist_set_row_text (FileList * pList, const gint nRow, gchar ** text) { gint n = 0; g_assert (pList && IS_FILE_LIST (pList)); g_assert (nRow >= 0); g_assert (pList->nCols > 0); g_assert (text); for (n = 0; n < pList->nCols; n++) { if ((text[n])) { gtk_clist_set_text (GTK_CLIST (pList), nRow, n, text[n]); } } }