"Fossies" - the Fresh Open Source Software Archive

Member "jpilot-2_0_1/category.c" (3 Apr 2021, 37367 Bytes) of package /linux/privat/jpilot-2_0_1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "category.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.8.2_vs_2_0_1.

    1 /*******************************************************************************
    2  * category.c
    3  * A module of J-Pilot http://jpilot.org
    4  *
    5  * Copyright (C) 2009-2014 by Judd Montgomery
    6  *
    7  * This program is free software; you can redistribute it and/or modify
    8  * it under the terms of the GNU General Public License as published by
    9  * the Free Software Foundation; version 2 of the License.
   10  *
   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.
   15  *
   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   19  ******************************************************************************/
   20 
   21 /********************************* Includes ***********************************/
   22 #include "config.h"
   23 #include <gtk/gtk.h>
   24 #include <stdlib.h>
   25 #include <string.h>
   26 #include <sys/stat.h>
   27 #include <sys/types.h>
   28 #include <utime.h>
   29 #include <unistd.h>
   30 #include <stdio.h>
   31 #include <pi-file.h>
   32 
   33 #include "i18n.h"
   34 #include "utils.h"
   35 #include "log.h"
   36 #include "prefs.h"
   37 
   38 /********************************* Constants **********************************/
   39 #define EDIT_CAT_START        100
   40 #define EDIT_CAT_NEW          101
   41 #define EDIT_CAT_RENAME       102
   42 #define EDIT_CAT_DELETE       103
   43 #define EDIT_CAT_ENTRY_OK     104
   44 #define EDIT_CAT_ENTRY_CANCEL 105
   45 
   46 /* #define EDIT_CATS_DEBUG 1 */
   47 
   48 /****************************** Prototypes ************************************/
   49 struct dialog_cats_data {
   50     int button_hit;
   51     int selected;
   52     int state;
   53     GtkTreeView *treeView;
   54     GtkListStore *listStore;
   55     GtkWidget *button_box;
   56     GtkWidget *entry_box;
   57     GtkWidget *entry;
   58     GtkWidget *label;
   59     char *entryText;
   60     char db_name[16];
   61     struct CategoryAppInfo cai1;
   62     struct CategoryAppInfo cai2;
   63 };
   64 
   65 enum {
   66     CATEGORY_TITLE_COLUMN_ENUM,
   67     CATEGORY_DATA_COLUMN_ENUM,
   68     CATEGORY_NUM_COLS
   69 };
   70 
   71 /****************************** Main Code *************************************/
   72 static int count_records_in_cat(char *db_name, int cat_index) {
   73     GList *records;
   74     GList *temp_list;
   75     int count, num;
   76     buf_rec *br;
   77 
   78     jp_logf(JP_LOG_DEBUG, "count_records_in_cat\n");
   79 
   80     count = 0;
   81 
   82     num = jp_read_DB_files(db_name, &records);
   83     if (-1 == num)
   84         return 0;
   85 
   86     for (temp_list = records; temp_list; temp_list = temp_list->next) {
   87         if (temp_list->data) {
   88             br = temp_list->data;
   89         } else {
   90             continue;
   91         }
   92 
   93         if (!br->buf) continue;
   94         if ((br->rt == DELETED_PALM_REC) ||
   95             (br->rt == DELETED_PC_REC) ||
   96             (br->rt == MODIFIED_PALM_REC))
   97             continue;
   98         if ((br->attrib & 0x0F) != cat_index) continue;
   99 
  100         count++;
  101     }
  102 
  103     jp_free_DB_records(&records);
  104 
  105     jp_logf(JP_LOG_DEBUG, "Leaving count_records_in_cat()\n");
  106 
  107     return count;
  108 }
  109 
  110 static int edit_cats_delete_cats_pc3(char *DB_name, int cat) {
  111     char local_pc_file[FILENAME_MAX];
  112     FILE *pc_in;
  113     PC3RecordHeader header;
  114     int num;
  115     int rec_len;
  116     int count = 0;
  117 
  118     g_snprintf(local_pc_file, sizeof(local_pc_file), "%s.pc3", DB_name);
  119 
  120     pc_in = jp_open_home_file(local_pc_file, "r+");
  121     if (pc_in == NULL) {
  122         jp_logf(JP_LOG_WARN, _("Unable to open file: %s\n"), local_pc_file);
  123         return EXIT_FAILURE;
  124     }
  125 
  126     while (!feof(pc_in)) {
  127         num = read_header(pc_in, &header);
  128         if (num != 1) {
  129             if (ferror(pc_in)) break;
  130             if (feof(pc_in)) break;
  131         }
  132 
  133         rec_len = (int) header.rec_len;
  134         if (rec_len > 0x10000) {
  135             jp_logf(JP_LOG_WARN, _("PC file corrupt?\n"));
  136             fclose(pc_in);
  137             return EXIT_FAILURE;
  138         }
  139         if (((header.rt == NEW_PC_REC) || (header.rt == REPLACEMENT_PALM_REC)) &&
  140             ((header.attrib & 0x0F) == cat)) {
  141             if (fseek(pc_in, -(header.header_len), SEEK_CUR)) {
  142                 jp_logf(JP_LOG_WARN, _("fseek failed - fatal error\n"));
  143                 fclose(pc_in);
  144                 return EXIT_FAILURE;
  145             }
  146             header.rt = DELETED_PC_REC;
  147             write_header(pc_in, &header);
  148             count++;
  149         }
  150         /* Skip this record now that we are done with it */
  151         if (fseek(pc_in, rec_len, SEEK_CUR)) {
  152             jp_logf(JP_LOG_WARN, _("fseek failed - fatal error\n"));
  153             fclose(pc_in);
  154             return EXIT_FAILURE;
  155         }
  156     }
  157 
  158     fclose(pc_in);
  159     return count;
  160 }
  161 
  162 /* Helper routine to change categories.
  163  * Function changes category regardless of record type */
  164 static int _edit_cats_change_cats_pc3(char *DB_name,
  165                                       int old_cat, int new_cat,
  166                                       int swap) {
  167     char local_pc_file[FILENAME_MAX];
  168     FILE *pc_in;
  169     PC3RecordHeader header;
  170     int rec_len;
  171     int num;
  172     int current_cat;
  173     int count = 0;
  174 
  175     g_snprintf(local_pc_file, sizeof(local_pc_file), "%s.pc3", DB_name);
  176 
  177     pc_in = jp_open_home_file(local_pc_file, "r+");
  178     if (pc_in == NULL) {
  179         jp_logf(JP_LOG_WARN, _("Unable to open file: %s\n"), local_pc_file);
  180         return EXIT_FAILURE;
  181     }
  182 
  183     while (!feof(pc_in)) {
  184         num = read_header(pc_in, &header);
  185         if (num != 1) {
  186             if (ferror(pc_in)) break;
  187             if (feof(pc_in)) break;
  188         }
  189         rec_len = (int) header.rec_len;
  190         if (rec_len > 0x10000) {
  191             jp_logf(JP_LOG_WARN, _("PC file corrupt?\n"));
  192             fclose(pc_in);
  193             return EXIT_FAILURE;
  194         }
  195 
  196         current_cat = header.attrib & 0x0F;
  197         if (current_cat == old_cat) {
  198             if (fseek(pc_in, -(header.header_len), SEEK_CUR)) {
  199                 jp_logf(JP_LOG_WARN, _("fseek failed - fatal error\n"));
  200                 fclose(pc_in);
  201                 return EXIT_FAILURE;
  202             }
  203             header.attrib = (unsigned char) ((header.attrib & 0xFFFFFFF0) | new_cat);
  204             write_header(pc_in, &header);
  205             count++;
  206         }
  207         if ((swap) && (current_cat == new_cat)) {
  208             if (fseek(pc_in, -(header.header_len), SEEK_CUR)) {
  209                 jp_logf(JP_LOG_WARN, _("fseek failed - fatal error\n"));
  210                 fclose(pc_in);
  211                 return EXIT_FAILURE;
  212             }
  213             header.attrib = (unsigned char) ((header.attrib & 0xFFFFFFF0) | old_cat);
  214             write_header(pc_in, &header);
  215             count++;
  216         }
  217         /* Skip the rest of the record now that we are done with it */
  218         if (fseek(pc_in, rec_len, SEEK_CUR)) {
  219             jp_logf(JP_LOG_WARN, _("fseek failed - fatal error\n"));
  220             fclose(pc_in);
  221             return EXIT_FAILURE;
  222         }
  223     }
  224 
  225     fclose(pc_in);
  226     return count;
  227 }
  228 
  229 /* Exported routine to change categories in PC3 file */
  230 int edit_cats_change_cats_pc3(char *DB_name, int old_cat, int new_cat) {
  231     return _edit_cats_change_cats_pc3(DB_name, old_cat, new_cat, FALSE);
  232 }
  233 
  234 /* Exported routine to swap categories in PC3 file */
  235 int edit_cats_swap_cats_pc3(char *DB_name, int old_cat, int new_cat) {
  236     return _edit_cats_change_cats_pc3(DB_name, old_cat, new_cat, TRUE);
  237 }
  238 
  239 /*
  240  * This routine changes records from old_cat to new_cat.
  241  *  It does not modify the local pdb file.
  242  *  It does this by writing a modified record to the pc3 file.
  243  */
  244 int edit_cats_change_cats_pdb(char *DB_name, int old_cat, int new_cat) {
  245     GList *temp_list;
  246     GList *records;
  247     buf_rec *br;
  248     int num, count;
  249 
  250     jp_logf(JP_LOG_DEBUG, "edit_cats_change_cats_pdb\n");
  251 
  252     count = 0;
  253     num = jp_read_DB_files(DB_name, &records);
  254     if (-1 == num)
  255         return 0;
  256 
  257     for (temp_list = records; temp_list; temp_list = temp_list->next) {
  258         if (temp_list->data) {
  259             br = temp_list->data;
  260         } else {
  261             continue;
  262         }
  263 
  264         if (!br->buf) continue;
  265         if ((br->rt == DELETED_PALM_REC) || (br->rt == MODIFIED_PALM_REC)) continue;
  266 
  267         if ((br->attrib & 0x0F) == old_cat) {
  268             if (new_cat == -1) {
  269                 /* write a deleted rec */
  270                 jp_delete_record(DB_name, br, DELETE_FLAG);
  271                 count++;
  272             } else {
  273                 /* write a deleted rec */
  274                 br->attrib = (unsigned char) ((br->attrib & 0xFFFFFFF0) | (new_cat & 0x0F));
  275                 jp_delete_record(DB_name, br, MODIFY_FLAG);
  276                 br->rt = REPLACEMENT_PALM_REC;
  277                 jp_pc_write(DB_name, br);
  278                 count++;
  279             }
  280         }
  281     }
  282 
  283     jp_free_DB_records(&records);
  284 
  285     return count;
  286 }
  287 
  288 /*
  289  * This helper routine changes the category index of records in the pdb file.
  290  * It will change all old_index records to new_index and with the swap 
  291  * option will also change new_index records to old_index ones.
  292  * Returns the number of records where the category was changed.
  293  */
  294 static int _change_cat_pdb(char *DB_name,
  295                            int old_index, int new_index,
  296                            int swap) {
  297     char local_pdb_file[FILENAME_MAX];
  298     char full_local_pdb_file[FILENAME_MAX];
  299     char full_local_pdb_file2[FILENAME_MAX];
  300     pi_file_t *pf1, *pf2;
  301     struct DBInfo infop;
  302     void *app_info;
  303     void *sort_info;
  304     void *record;
  305     size_t size;
  306     pi_uid_t uid;
  307     struct stat statb;
  308     struct utimbuf times;
  309     int idx;
  310     int attr;
  311     int cat;
  312     int count;
  313 
  314     jp_logf(JP_LOG_DEBUG, "_change_cat_pdb\n");
  315 
  316     g_snprintf(local_pdb_file, sizeof(local_pdb_file), "%s.pdb", DB_name);
  317     get_home_file_name(local_pdb_file, full_local_pdb_file, sizeof(full_local_pdb_file));
  318     strcpy(full_local_pdb_file2, full_local_pdb_file);
  319     strcat(full_local_pdb_file2, "2");
  320 
  321     /* After we are finished, set the create and modify times of new file
  322        to the same as the old */
  323     stat(full_local_pdb_file, &statb);
  324     times.actime = statb.st_atime;
  325     times.modtime = statb.st_mtime;
  326 
  327     pf1 = pi_file_open(full_local_pdb_file);
  328     if (!pf1) {
  329         jp_logf(JP_LOG_WARN, _("Unable to open file: %s\n"), full_local_pdb_file);
  330         return EXIT_FAILURE;
  331     }
  332     pi_file_get_info(pf1, &infop);
  333     pf2 = pi_file_create(full_local_pdb_file2, &infop);
  334     if (!pf2) {
  335         jp_logf(JP_LOG_WARN, _("Unable to open file: %s\n"), full_local_pdb_file2);
  336         return EXIT_FAILURE;
  337     }
  338 
  339     pi_file_get_app_info(pf1, &app_info, &size);
  340     pi_file_set_app_info(pf2, app_info, size);
  341 
  342     pi_file_get_sort_info(pf1, &sort_info, &size);
  343     pi_file_set_sort_info(pf2, sort_info, size);
  344 
  345     idx = 0;
  346     count = 0;
  347     while ((pi_file_read_record(pf1, idx, &record, &size, &attr, &cat, &uid)) > 0) {
  348         if (cat == old_index) {
  349             cat = new_index;
  350             count++;
  351         } else if ((swap) && (cat == new_index)) {
  352             cat = old_index;
  353             count++;
  354         }
  355         pi_file_append_record(pf2, record, size, attr, cat, uid);
  356         idx++;
  357     }
  358 
  359     pi_file_close(pf1);
  360     pi_file_close(pf2);
  361 
  362     if (rename(full_local_pdb_file2, full_local_pdb_file) < 0) {
  363         jp_logf(JP_LOG_WARN, "pdb_file_change_indexes(): %s\n, ", _("rename failed"));
  364     }
  365 
  366     utime(full_local_pdb_file, &times);
  367 
  368     return EXIT_SUCCESS;
  369 }
  370 
  371 /* Exported routine to change categories in pdb file */
  372 int pdb_file_change_indexes(char *DB_name, int old_cat, int new_cat) {
  373     return _change_cat_pdb(DB_name, old_cat, new_cat, FALSE);
  374 }
  375 
  376 /* Exported routine to swap categories in pdb file */
  377 int pdb_file_swap_indexes(char *DB_name, int old_cat, int new_cat) {
  378     return _change_cat_pdb(DB_name, old_cat, new_cat, TRUE);
  379 }
  380 
  381 /*
  382  * This routine changes deletes records in category cat.
  383  *  It does not modify a local pdb file.
  384  *  It does this by writing a modified record to the pc3 file.
  385  */
  386 static int edit_cats_delete_cats_pdb(char *DB_name, int cat) {
  387     jp_logf(JP_LOG_DEBUG, "edit_cats_delete_cats_pdb\n");
  388 
  389     return edit_cats_change_cats_pdb(DB_name, cat, -1);
  390 }
  391 
  392 gboolean
  393 deleteCategory(GtkTreeModel *model,
  394                GtkTreePath *path,
  395                GtkTreeIter *iter,
  396                gpointer data) {
  397     struct dialog_cats_data *Pdata;
  398     Pdata = data;
  399     int *i = gtk_tree_path_get_indices(path);
  400     if (Pdata->selected == i[0]) {
  401         gtk_list_store_remove(GTK_LIST_STORE(Pdata->listStore), iter);
  402         return TRUE;
  403     }
  404     return FALSE;
  405 }
  406 
  407 gboolean
  408 renameCategory(GtkTreeModel *model,
  409                GtkTreePath *path,
  410                GtkTreeIter *iter,
  411                gpointer data) {
  412     struct dialog_cats_data *Pdata;
  413     char *text;
  414     Pdata = data;
  415     int *i = gtk_tree_path_get_indices(path);
  416     if (Pdata->selected == i[0]) {
  417         text = Pdata->entryText;
  418         gtk_list_store_set(GTK_LIST_STORE(Pdata->listStore), iter, CATEGORY_TITLE_COLUMN_ENUM, text, -1);
  419         return TRUE;
  420     }
  421     return FALSE;
  422 }
  423 
  424 gboolean
  425 editCategoryRename(GtkTreeModel *model,
  426                    GtkTreePath *path,
  427                    GtkTreeIter *iter,
  428                    gpointer data) {
  429     struct dialog_cats_data *Pdata;
  430     char *text;
  431     Pdata = data;
  432 
  433     int *i = gtk_tree_path_get_indices(path);
  434     if (Pdata->selected == i[0]) {
  435         gtk_tree_model_get(GTK_TREE_MODEL(Pdata->listStore), iter, CATEGORY_TITLE_COLUMN_ENUM, &text,-1);
  436         gtk_label_set_text(GTK_LABEL(Pdata->label), _("Enter New Category Name"));
  437         gtk_entry_set_text(GTK_ENTRY(Pdata->entry), text);
  438         gtk_widget_show(Pdata->entry_box);
  439         gtk_widget_hide(Pdata->button_box);
  440         gtk_widget_grab_focus(GTK_WIDGET(Pdata->entry));
  441         Pdata->state = EDIT_CAT_RENAME;
  442         return TRUE;
  443     }
  444     return FALSE;
  445 }
  446 
  447 gboolean
  448 selectCategoryRecordByRow(GtkTreeModel *model,
  449                           GtkTreePath *path,
  450                           GtkTreeIter *iter,
  451                           gpointer data) {
  452     struct dialog_cats_data *Pdata;
  453     Pdata = data;
  454     int *i = gtk_tree_path_get_indices(path);
  455     int rowToCheck = Pdata->selected;
  456     //rowToCheck is > than number of rows, so select the last row.
  457     if (Pdata->selected >= gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL)) {
  458         rowToCheck--;
  459     }
  460     if (i[0] == rowToCheck) {
  461         GtkTreeSelection *selection = NULL;
  462         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(Pdata->treeView));
  463         gtk_tree_selection_select_path(selection, path);
  464         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(Pdata->treeView), path, CATEGORY_TITLE_COLUMN_ENUM, FALSE, 1.0, 0.0);
  465         return TRUE;
  466     }
  467 
  468     return FALSE;
  469 }
  470 
  471 static void cb_edit_button(GtkWidget *widget, gpointer data) {
  472     struct dialog_cats_data *Pdata;
  473     int i, r, count;
  474     long char_set;
  475     int id;
  476     int button;
  477     int catnum;
  478     char pilotentry[HOSTCAT_NAME_SZ];   /* entry text, in Pilot character set */
  479     char *button_text[] = {N_("OK")};
  480     char *move_text[] = {N_("Move"), N_("Delete"), N_("Cancel")};
  481     const char *entry_text;
  482     char temp[256];
  483 
  484     get_pref(PREF_CHAR_SET, &char_set, NULL); /* JPA be prepared to make conversions */
  485     button = GPOINTER_TO_INT(data);
  486     Pdata =  g_object_get_data(G_OBJECT(gtk_widget_get_toplevel(widget)), "dialog_cats_data");
  487 
  488     /* JPA get the selected category number */
  489     catnum = 0;
  490     i = 0;
  491     while ((i <= Pdata->selected) && (catnum < NUM_CATEGORIES)) {
  492         if (Pdata->cai2.name[++catnum][0]) i++;
  493     }
  494     if (catnum >= NUM_CATEGORIES) catnum = -1;  /* not found */
  495 
  496     if (Pdata) {
  497         switch (button) {
  498             case EDIT_CAT_NEW:
  499                 count = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(Pdata->listStore), NULL);
  500                 if (count > NUM_CATEGORIES - 2) {
  501                     dialog_generic(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  502                                    _("Edit Categories"), DIALOG_ERROR,
  503                                    _("The maximum number of categories (16) are already used"), 1, button_text);
  504                     return;
  505                 }
  506                 gtk_label_set_text(GTK_LABEL(Pdata->label), _("Enter New Category"));
  507                 gtk_entry_set_text(GTK_ENTRY(Pdata->entry), "");
  508                 gtk_widget_show(Pdata->entry_box);
  509                 gtk_widget_hide(Pdata->button_box);
  510                 gtk_widget_grab_focus(GTK_WIDGET(Pdata->entry));
  511                 Pdata->state = EDIT_CAT_NEW;
  512                 break;
  513 
  514             case EDIT_CAT_RENAME:
  515                 if ((catnum < 0) || (Pdata->cai2.name[catnum][0] == '\0')) {
  516                     dialog_generic(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  517                                    _("Edit Categories Error"), DIALOG_ERROR,
  518                                    _("You must select a category to rename"), 1, button_text);
  519                     return;
  520                 }
  521 #ifdef EDIT_CATS_DEBUG
  522                 if (catnum == 0) {
  523                    printf("Trying to rename category 0!\n");
  524                 }
  525 #endif
  526                 gtk_tree_model_foreach(GTK_TREE_MODEL(Pdata->listStore), editCategoryRename, Pdata);
  527 
  528                 break;
  529 
  530             case EDIT_CAT_DELETE:
  531 #ifdef EDIT_CATS_DEBUG
  532                 printf("delete cat\n");
  533 #endif
  534                 if (catnum < 0) {
  535                     dialog_generic(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  536                                    _("Edit Categories Error"), DIALOG_ERROR,
  537                                    _("You must select a category to delete"), 1, button_text);
  538                     return;
  539                 }
  540 #ifdef EDIT_CATS_DEBUG
  541                 if (catnum == 0) {
  542                    printf("Trying to delete category 0!\n");
  543                 }
  544 #endif
  545                 /* Check if category is empty */
  546                 if (Pdata->cai2.name[catnum][0] == '\0') {
  547                     return;
  548                 }
  549                 /* Check to see if there are records are in this category */
  550                 count = count_records_in_cat(Pdata->db_name, catnum);
  551 #ifdef EDIT_CATS_DEBUG
  552                 printf("count=%d\n", count);
  553 #endif
  554                 if (count > 0) {
  555                     g_snprintf(temp, sizeof(temp), _("There are %d records in %s.\n"
  556                                                      "Do you want to move them to %s, or delete them?"),
  557                                count, Pdata->cai1.name[catnum], Pdata->cai1.name[0]);
  558                     r = dialog_generic(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  559                                        _("Edit Categories"), DIALOG_QUESTION,
  560                                        temp, 3, move_text);
  561                     switch (r) {
  562                         case DIALOG_SAID_1:
  563 #ifdef EDIT_CATS_DEBUG
  564                             printf("MOVE THEM\n");
  565 #endif
  566                             r = edit_cats_change_cats_pc3(Pdata->db_name, catnum, 0);
  567 #ifdef EDIT_CATS_DEBUG
  568                             printf("moved %d pc records\n", r);
  569 #endif
  570                             r = edit_cats_change_cats_pdb(Pdata->db_name, catnum, 0);
  571 #ifdef EDIT_CATS_DEBUG
  572                             printf("moved %d pdb->pc records\n", r);
  573 #endif
  574                             break;
  575                         case DIALOG_SAID_2:
  576 #ifdef EDIT_CATS_DEBUG
  577                             printf("DELETE THEM\n");
  578 #endif
  579                             r = edit_cats_delete_cats_pc3(Pdata->db_name, catnum);
  580 #ifdef EDIT_CATS_DEBUG
  581                             printf("deleted %d pc records\n", r);
  582 #endif
  583                             r = edit_cats_delete_cats_pdb(Pdata->db_name, catnum);
  584 #ifdef EDIT_CATS_DEBUG
  585                             printf("deleted %d pdb->pc records\n", r);
  586 #endif
  587                             break;
  588                         case DIALOG_SAID_3:
  589 #ifdef EDIT_CATS_DEBUG
  590                             printf("Delete Canceled\n");
  591 #endif
  592                             return;
  593                         default:
  594                             return;
  595                     }
  596                 }
  597                 /* delete the category */
  598 #ifdef EDIT_CATS_DEBUG
  599                 printf("DELETE category\n");
  600 #endif
  601                 Pdata->cai2.name[catnum][0] = '\0';
  602                 Pdata->cai2.ID[catnum] = Pdata->cai1.ID[catnum];
  603                 Pdata->cai2.renamed[catnum] = 0;
  604                 /* JPA move category names upward in listbox */
  605                 /* we get the old text from listbox, to avoid making */
  606                 /* character set conversions */
  607                 gtk_tree_model_foreach(GTK_TREE_MODEL(Pdata->listStore), deleteCategory, Pdata);
  608                 gtk_tree_model_foreach(GTK_TREE_MODEL(Pdata->listStore), selectCategoryRecordByRow, Pdata);
  609 
  610                 break;
  611 
  612             case EDIT_CAT_ENTRY_OK:
  613                 if ((Pdata->state != EDIT_CAT_RENAME) && (Pdata->state != EDIT_CAT_NEW)) {
  614                     jp_logf(JP_LOG_WARN, _("invalid state file %s line %d\n"), __FILE__, __LINE__);
  615                     return;
  616                 }
  617                 entry_text = gtk_entry_get_text(GTK_ENTRY(Pdata->entry));
  618 
  619                 /* Can't make an empty category, could do a dialog telling user */
  620                 if ((!entry_text) || (!entry_text[0])) {
  621                     return;
  622                 }
  623 
  624                 if ((Pdata->state == EDIT_CAT_RENAME) || (Pdata->state == EDIT_CAT_NEW)) {
  625                     /* Check for category being used more than once */
  626                     /* moved here by JPA for use in both new and rename cases */
  627                     /* JPA convert entry to Pilot character set before checking */
  628                     /* note : don't know Pilot size until converted */
  629                     g_strlcpy(pilotentry, entry_text, HOSTCAT_NAME_SZ);
  630                     charset_j2p(pilotentry, HOSTCAT_NAME_SZ, char_set);
  631                     pilotentry[PILOTCAT_NAME_SZ - 1] = '\0';
  632                     for (i = 0; i < NUM_CATEGORIES; i++) {
  633                         /* JPA allow a category to be renamed to its previous name */
  634                         if (((i != catnum) || (Pdata->state != EDIT_CAT_RENAME))
  635                             && !strcmp(pilotentry, Pdata->cai2.name[i])) {
  636                             sprintf(temp, _("The category %s can't be used more than once"), entry_text);
  637                             dialog_generic(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  638                                            _("Edit Categories"), DIALOG_ERROR,
  639                                            temp, 1, button_text);
  640                             return;
  641                         }
  642                     }
  643                 }
  644                 if (Pdata->state == EDIT_CAT_RENAME) {
  645 #ifdef EDIT_CATS_DEBUG
  646                     printf("rename cat\n");
  647 #endif
  648                     Pdata->entryText = (char *) entry_text;
  649                     gtk_tree_model_foreach(GTK_TREE_MODEL(Pdata->listStore), renameCategory, Pdata);
  650                     /* JPA enter new category name in Palm Pilot character set */
  651                     charset_j2p((char *) entry_text, HOSTCAT_NAME_SZ, char_set);
  652                     g_strlcpy(Pdata->cai2.name[catnum], entry_text, PILOTCAT_NAME_SZ);
  653                     Pdata->cai2.renamed[catnum] = 1;
  654 
  655                 }
  656 
  657                 if (Pdata->state == EDIT_CAT_NEW) {
  658 #ifdef EDIT_CATS_DEBUG
  659                     printf("new cat\n");
  660 #endif
  661                     /* JPA have already checked category is not being used more than once */
  662                     /* Find a new category ID */
  663                     id = 128;
  664                     for (i = 1; i < NUM_CATEGORIES; i++) {
  665                         if (Pdata->cai2.ID[i] == id) {
  666                             id++;
  667                             i = 1;
  668                         }
  669                     }
  670                     /* Find an empty slot */
  671                     /* When the new button was pressed we already checked for an empty slot */
  672 
  673                     GtkTreeIter iter;
  674                     gtk_list_store_append(Pdata->listStore, &iter);
  675                     gtk_list_store_set(Pdata->listStore, &iter, CATEGORY_TITLE_COLUMN_ENUM, entry_text, -1);
  676                     for (i = 1; i < NUM_CATEGORIES; i++) {
  677                         if (Pdata->cai2.name[i][0] == '\0') {
  678 #ifdef EDIT_CATS_DEBUG
  679                             printf("slot %d is empty\n", i);
  680 #endif
  681                             /* JPA get the old text from listbox, to avoid making */
  682                             /* character set conversions */
  683                             strcpy(Pdata->cai2.name[i], pilotentry);
  684                             Pdata->cai2.ID[i] = (unsigned char) id;
  685                             Pdata->cai2.renamed[i] = 1;
  686 
  687                             break;
  688                         }
  689                     }
  690                 }
  691                 gtk_widget_hide(Pdata->entry_box);
  692                 gtk_widget_show(Pdata->button_box);
  693                 Pdata->state = EDIT_CAT_START;
  694                 break;
  695 
  696             case EDIT_CAT_ENTRY_CANCEL:
  697                 gtk_widget_hide(Pdata->entry_box);
  698                 gtk_widget_show(Pdata->button_box);
  699                 Pdata->state = EDIT_CAT_START;
  700                 break;
  701 
  702             default:
  703                 jp_logf(JP_LOG_WARN, "cb_edit_button(): %s\n", "unknown button");
  704         }
  705     }
  706 }
  707 
  708 static gboolean handleCategorySelection(GtkTreeSelection *selection,
  709                                         GtkTreeModel *model,
  710                                         GtkTreePath *path,
  711                                         gboolean path_currently_selected,
  712                                         gpointer data) {
  713     struct dialog_cats_data *Pdata;
  714     GtkTreeIter iter;
  715     Pdata = data;
  716     if ((gtk_tree_model_get_iter(model, &iter, path)) && (!path_currently_selected)) {
  717         int *i = gtk_tree_path_get_indices(path);
  718         if (Pdata->state == EDIT_CAT_START) {
  719             Pdata->selected = i[0];
  720         }
  721     }
  722     return TRUE;
  723 }
  724 
  725 
  726 #ifdef EDIT_CATS_DEBUG
  727 static void cb_edit_cats_debug(GtkWidget *widget, gpointer data)
  728 {
  729    struct dialog_cats_data *Pdata;
  730    int i;
  731 
  732    Pdata=data;
  733    for (i=0; i<NUM_CATEGORIES; i++) {
  734       printf("cai %2d [%16s] ID %3d %d: [%16s] ID %3d %d\n", i,
  735              Pdata->cai1.name[i], Pdata->cai1.ID[i], Pdata->cai1.renamed[i],
  736              Pdata->cai2.name[i], Pdata->cai2.ID[i], Pdata->cai2.renamed[i]);
  737    }
  738 }
  739 #endif
  740 
  741 static void cb_dialog_button(GtkWidget *widget, gpointer data) {
  742     struct dialog_cats_data *Pdata;
  743     GtkWidget *w;
  744 
  745     w = gtk_widget_get_toplevel(widget);
  746     Pdata =  g_object_get_data(G_OBJECT(w), "dialog_cats_data");
  747     Pdata->button_hit = GPOINTER_TO_INT(data);
  748 
  749     gtk_widget_destroy(GTK_WIDGET(w));
  750 }
  751 
  752 static gboolean cb_destroy_dialog(GtkWidget *widget) {
  753     gtk_main_quit();
  754 
  755     return TRUE;
  756 }
  757 
  758 int edit_cats(GtkWidget *widget, char *db_name, struct CategoryAppInfo *cai) {
  759     GtkWidget *button;
  760     GtkWidget *hbox, *vbox;
  761     GtkWidget *vbox1, *vbox2, *vbox3;
  762     GtkWidget *dialog;
  763     GtkListStore *listStore;
  764     GtkWidget *treeView;
  765     GtkWidget *entry;
  766     GtkWidget *label;
  767     GtkWidget *separator;
  768     struct dialog_cats_data Pdata;
  769     int i, j;
  770     long char_set;
  771     char *catname_hchar;    /* Category names in host character set */
  772 
  773     jp_logf(JP_LOG_DEBUG, "edit_cats\n");
  774 
  775     Pdata.selected = -1;
  776     Pdata.state = EDIT_CAT_START;
  777     g_strlcpy(Pdata.db_name, db_name, 16);
  778 #ifdef EDIT_CATS_DEBUG
  779     for (i = 0; i < NUM_CATEGORIES; i++) {
  780        if (cai->name[i][0] != '\0') {
  781           printf("cat %d [%s] ID %d renamed %d\n", i, cai->name[i],
  782                  cai->ID[i], cai->renamed[i]);
  783        }
  784     }
  785 #endif
  786 
  787 /* removed by JPA : do not change category names as they will
  788  * be written back to file.
  789  *  We will however have to make a conversion for host dialog display
  790  *
  791  *   get_pref(PREF_CHAR_SET, &char_set, NULL);
  792  *   if (char_set != CHAR_SET_LATIN1) {
  793  *      for (i = 0; i < 16; i++) {
  794  *       if (cai->name[i][0] != '\0') {
  795  *          charset_p2j((unsigned char*)cai->name[i], 16, char_set);
  796  *       }
  797  *      }
  798  *   }
  799  *
  800  */
  801 
  802     dialog = gtk_widget_new(GTK_TYPE_WINDOW,
  803                             "type", GTK_WINDOW_TOPLEVEL,
  804                             "title", _("Edit Categories"),
  805                             NULL);
  806 
  807     gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
  808     gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
  809     gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gtk_widget_get_toplevel(widget)));
  810 
  811     g_signal_connect(G_OBJECT(dialog), "destroy",
  812                        G_CALLBACK(cb_destroy_dialog), dialog);
  813 
  814     vbox3 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  815     gtk_container_add(GTK_CONTAINER(dialog), vbox3);
  816 
  817     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  818     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
  819     gtk_container_add(GTK_CONTAINER(vbox3), hbox);
  820 
  821     vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  822     gtk_box_pack_start(GTK_BOX(hbox), vbox1, FALSE, FALSE, 1);
  823 
  824     vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  825     gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 1);
  826     listStore = gtk_list_store_new(CATEGORY_NUM_COLS, G_TYPE_STRING, G_TYPE_INT);
  827     treeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(listStore));
  828     GtkCellRenderer *titleRenderer = gtk_cell_renderer_text_new();
  829     GtkTreeViewColumn *titleColumn = gtk_tree_view_column_new_with_attributes("Category",
  830                                                                               titleRenderer,
  831                                                                               "text", CATEGORY_TITLE_COLUMN_ENUM,
  832                                                                               NULL);
  833     gtk_tree_view_column_set_clickable(titleColumn, gtk_false());
  834     gtk_tree_view_column_set_min_width(titleColumn, 100);
  835     gtk_tree_view_column_set_sizing(titleColumn, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  836     gtk_tree_view_insert_column(GTK_TREE_VIEW(treeView), titleColumn, CATEGORY_TITLE_COLUMN_ENUM);
  837     gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeView)),
  838                                 GTK_SELECTION_BROWSE);
  839 
  840     gtk_tree_selection_set_select_function(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeView)),
  841                                            handleCategorySelection, &Pdata, NULL);
  842     gtk_widget_set_events(treeView, GDK_BUTTON1_MOTION_MASK);
  843     g_signal_connect (G_OBJECT(treeView), "motion_notify_event",
  844                       G_CALLBACK(motion_notify_event), NULL);
  845     g_signal_connect (G_OBJECT(treeView), "button-press-event",
  846                       G_CALLBACK(button_pressed_for_motion), NULL);
  847     g_signal_connect (G_OBJECT(treeView), "button-release-event",
  848                       G_CALLBACK(button_released_for_motion), NULL);
  849     gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(treeView), TRUE, TRUE, 1);
  850 
  851     /* Fill list with categories except for category 0, Unfiled,
  852      * which is not editable */
  853     get_pref(PREF_CHAR_SET, &char_set, NULL);
  854     GtkTreeIter iter;
  855     for (i = j = 1; i < NUM_CATEGORIES; i++, j++) {
  856 
  857 
  858         /* Hide void category names */
  859         while ((j < NUM_CATEGORIES) && ((cai->name[j][0] == '\0') || (!cai->ID[j]))) {
  860             /* Remove categories which have a null ID
  861              * to facilitate recovering from errors, */
  862             /* however we cannot synchronize them to the Palm Pilot */
  863             if (!cai->ID[j]) cai->name[j][0] = '\0';
  864             j++;
  865         }
  866         if (j < NUM_CATEGORIES) {
  867             /* Must do character set conversion from Palm to Host */
  868             catname_hchar = charset_p2newj(cai->name[j], PILOTCAT_NAME_SZ, (int) char_set);
  869             gtk_list_store_append(listStore, &iter);
  870             gtk_list_store_set(listStore, &iter, CATEGORY_TITLE_COLUMN_ENUM, catname_hchar, CATEGORY_DATA_COLUMN_ENUM,
  871                                j, -1);
  872 
  873             free(catname_hchar);
  874         }
  875     }
  876 
  877 
  878     Pdata.treeView = GTK_TREE_VIEW(treeView);
  879     Pdata.listStore = listStore;
  880 
  881     /* Buttons */
  882     hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
  883     gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
  884     gtk_button_box_set_layout(GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  885     gtk_box_set_spacing(GTK_BOX(hbox), 6);
  886     gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 1);
  887 
  888 #ifdef ENABLE_STOCK_BUTTONS
  889     button = gtk_button_new_with_label("_New");
  890 #else
  891     button = gtk_button_new_with_label(_("New"));
  892 #endif
  893     g_signal_connect(G_OBJECT(button), "clicked",
  894                        G_CALLBACK(cb_edit_button),
  895                        GINT_TO_POINTER(EDIT_CAT_NEW));
  896     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
  897 
  898     button = gtk_button_new_with_label(_("Rename"));
  899     g_signal_connect(G_OBJECT(button), "clicked",
  900                        G_CALLBACK(cb_edit_button),
  901                        GINT_TO_POINTER(EDIT_CAT_RENAME));
  902     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
  903 
  904 #ifdef ENABLE_STOCK_BUTTONS
  905     button = gtk_button_new_with_label("_Delete");
  906 #else
  907     button = gtk_button_new_with_label(_("Delete"));
  908 #endif
  909     g_signal_connect(G_OBJECT(button), "clicked",
  910                        G_CALLBACK(cb_edit_button),
  911                        GINT_TO_POINTER(EDIT_CAT_DELETE));
  912     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
  913 
  914     Pdata.button_box = hbox;
  915 
  916     /* Edit entry and boxes, etc */
  917     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  918     gtk_box_pack_start(GTK_BOX(vbox2), vbox, FALSE, FALSE, 10);
  919     separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
  920     gtk_box_pack_start(GTK_BOX(vbox), separator, FALSE, FALSE, 0);
  921     label = gtk_label_new("");
  922     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
  923     separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
  924     gtk_box_pack_start(GTK_BOX(vbox), separator, FALSE, FALSE, 0);
  925 
  926     Pdata.label = label;
  927 
  928     entry = gtk_entry_new();
  929     gtk_entry_set_max_length(GTK_ENTRY(entry), HOSTCAT_NAME_SZ - 1);
  930     g_signal_connect(G_OBJECT(entry), "activate",
  931                        G_CALLBACK(cb_edit_button),
  932                        GINT_TO_POINTER(EDIT_CAT_ENTRY_OK));
  933     gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
  934 
  935     hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
  936     gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
  937     gtk_button_box_set_layout(GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  938     gtk_box_set_spacing(GTK_BOX(hbox), 6);
  939     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 1);
  940     button = gtk_button_new_with_label("Cancel");
  941     g_signal_connect(G_OBJECT(button), "clicked",
  942                        G_CALLBACK(cb_edit_button),
  943                        GINT_TO_POINTER(EDIT_CAT_ENTRY_CANCEL));
  944     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
  945 
  946     button = gtk_button_new_with_label("OK");
  947     g_signal_connect(G_OBJECT(button), "clicked",
  948                        G_CALLBACK(cb_edit_button),
  949                        GINT_TO_POINTER(EDIT_CAT_ENTRY_OK));
  950     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
  951 
  952     separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
  953     gtk_box_pack_start(GTK_BOX(vbox), separator, FALSE, FALSE, 0);
  954 
  955     Pdata.entry_box = vbox;
  956     Pdata.entry = entry;
  957 
  958     /* Button Box */
  959     hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
  960     gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
  961     gtk_button_box_set_layout(GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  962     gtk_box_set_spacing(GTK_BOX(hbox), 6);
  963     gtk_box_pack_start(GTK_BOX(vbox3), hbox, FALSE, FALSE, 2);
  964 
  965     /* Buttons */
  966     button = gtk_button_new_with_label("Cancel");
  967     g_signal_connect(G_OBJECT(button), "clicked",
  968                        G_CALLBACK(cb_dialog_button),
  969                        GINT_TO_POINTER(DIALOG_SAID_2));
  970     gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
  971 
  972     button = gtk_button_new_with_label("OK");
  973     g_signal_connect(G_OBJECT(button), "clicked",
  974                        G_CALLBACK(cb_dialog_button),
  975                        GINT_TO_POINTER(DIALOG_SAID_1));
  976     gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
  977 
  978 #ifdef EDIT_CATS_DEBUG
  979     button = gtk_button_new_with_label("DEBUG");
  980     g_signal_connect(G_OBJECT(button), "clicked",
  981                        G_CALLBACK(cb_edit_cats_debug), &Pdata);
  982     gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
  983 #endif
  984 
  985     /* Set the default button pressed to CANCEL */
  986     Pdata.button_hit = DIALOG_SAID_2;
  987     /* Initialize data structures */
  988     memcpy(&(Pdata.cai1), cai, sizeof(struct CategoryAppInfo));
  989     memcpy(&(Pdata.cai2), cai, sizeof(struct CategoryAppInfo));
  990      g_object_set_data(G_OBJECT(dialog), "dialog_cats_data", &Pdata);
  991 
  992     gtk_widget_show_all(dialog);
  993     gtk_widget_hide(Pdata.entry_box);
  994 
  995     gtk_main();
  996 
  997     /* OK, back from gtk_main loop */
  998 #ifdef EDIT_CATS_DEBUG
  999     if (Pdata.button_hit==DIALOG_SAID_1) {
 1000        printf("pressed 1\n");
 1001     }
 1002 #endif
 1003     if (Pdata.button_hit == DIALOG_SAID_2) {
 1004 #ifdef EDIT_CATS_DEBUG
 1005         printf("pressed 2\n");
 1006 #endif
 1007         return DIALOG_SAID_2;
 1008     }
 1009 #ifdef EDIT_CATS_DEBUG
 1010     for (i=0; i<NUM_CATEGORIES; i++) {
 1011        printf("name %d [%s] ID %d [%s] ID %d\n", i,
 1012                           Pdata.cai1.name[i], Pdata.cai1.ID[i],
 1013                           Pdata.cai2.name[i], Pdata.cai2.ID[i]);
 1014     }
 1015 #endif
 1016 
 1017     memcpy(cai, &(Pdata.cai2), sizeof(struct CategoryAppInfo));
 1018 
 1019     return EXIT_SUCCESS;
 1020 }