]> git.deb.at Git - pkg/abook.git/blob - edit.c
- possibly improve error reporting when tabs are too wide to display
[pkg/abook.git] / edit.c
1
2 /*
3  * $Id$
4  *
5  * by JH <jheinonen@users.sourceforge.net>
6  *
7  * Copyright (C) Jaakko Heinonen
8  */
9
10 #include <string.h>
11 #include <stdlib.h>
12 #include <ctype.h>
13 #include <assert.h>
14 #include "abook_curses.h"
15 #include "ui.h"
16 #include "abook.h"
17 #include "database.h"
18 #include "gettext.h"
19 #include "list.h"
20 #include "edit.h"
21 #include "misc.h"
22 #include "views.h"
23 #include "xmalloc.h"
24 #ifdef HAVE_CONFIG_H
25 #       include "config.h"
26 #endif
27
28 /*
29  * some extern variables
30  */
31
32
33 extern int curitem;
34 extern int views_count;
35 extern int items;
36
37 WINDOW *editw;
38
39
40 static void
41 editor_tab(const int tab)
42 {
43         int i, j;
44         int x_pos = 2; /* current x pos */
45         char *tab_name;
46
47         mvwhline(editw, TABLINE + 1, 0, UI_HLINE_CHAR, EDITW_COLS);
48
49         for(i = 0; i < views_count; i++) {
50                 view_info(i, &tab_name, NULL);
51                 int width = strwidth(tab_name) + 5;
52
53                 if(x_pos + width + 1 > EDITW_COLS) {
54                         statusline_addstr(_("Tab name too wide for screen"));
55                         break;
56                 }
57
58                 mvwaddch(editw,  TABLINE + 1, x_pos,  UI_TEE_CHAR);
59                 mvwaddch(editw,  TABLINE + 1, x_pos + width - 2, UI_TEE_CHAR);
60
61                 mvwaddch(editw,  TABLINE, x_pos,  UI_ULCORNER_CHAR);
62                 mvwaddch(editw,  TABLINE, x_pos + 1,  UI_LBOXLINE_CHAR);
63                 mvwaddstr(editw, TABLINE, x_pos + 2,  tab_name);
64                 mvwaddch(editw,  TABLINE, x_pos + width - 3, UI_RBOXLINE_CHAR);
65                 mvwaddch(editw,  TABLINE, x_pos + width - 2, UI_URCORNER_CHAR);
66
67                 if(i == tab) {
68                         mvwaddch(editw,  TABLINE + 1, x_pos, UI_LRCORNER_CHAR);
69                         for(j = 0; j < width - 3; j++)
70                                 mvwaddstr(editw,
71                                         TABLINE + 1, x_pos + j + 1, " ");
72                         mvwaddch(editw,  TABLINE + 1, x_pos + width - 2,
73                                 UI_LLCORNER_CHAR);
74                 }
75                 x_pos += width;
76         }
77 }
78
79 void
80 get_first_email(char *str, int item)
81 {
82         char *tmp;
83
84         if(!db_email_get(item)) {
85                 *str = 0;
86                 return;
87         }
88
89         strncpy(str, db_email_get(item), MAX_EMAIL_LEN);
90         if( (tmp = strchr(str, ',')) )
91                 *tmp = 0;
92         else
93                 str[MAX_EMAIL_LEN - 1] = 0;
94 }
95
96 static void
97 roll_emails(int item, enum rotate_dir dir)
98 {
99         abook_list *emails = csv_to_abook_list(db_email_get(item));
100
101         if(!emails)
102                 return;
103
104         free(db_email_get(item));
105         abook_list_rotate(&emails, dir);
106         db_fput(item, EMAIL, abook_list_to_csv(emails));
107         abook_list_free(&emails);
108 }
109
110 static void
111 init_editor()
112 {
113         clear();
114         editw = newwin(EDITW_LINES, EDITW_COLS, EDITW_TOP, EDITW_X);
115         notimeout(editw, TRUE); /* handling of escape key */
116
117         refresh_statusline();
118 }
119
120 enum {
121         BACKUP_ITEM,
122         RESTORE_ITEM,
123         CLEAR_UNDO
124 };
125
126 static int
127 edit_undo(int item, int mode)
128 {
129         static list_item backup = NULL;
130         static int backed_up_item = -1;
131
132         switch(mode) {
133                 case CLEAR_UNDO:
134                         if(backup) {
135                                 item_empty(backup);
136                                 item_free(&backup);
137                         }
138                         break;
139                 case BACKUP_ITEM:
140                         if(backup) {
141                                 item_empty(backup);
142                                 item_free(&backup);
143                         }
144                         backup = item_create();
145                         item_duplicate(backup, db_item_get(item));
146                         backed_up_item = item;
147                         break;
148                 case RESTORE_ITEM:
149                         if(backup) {
150                                 item_empty(db_item_get(backed_up_item));
151                                 item_copy(db_item_get(backed_up_item), backup);
152                                 item_free(&backup);
153                                 return backed_up_item;
154                         }
155                         break;
156                 default:
157                         assert(0);
158         }
159         return item;
160 }
161
162 static void
163 close_editor()
164 {
165         edit_undo(-1, CLEAR_UNDO);
166         delwin(editw);
167         refresh_screen();
168 }
169
170 static void
171 print_editor_header(int item)
172 {
173         char *header;
174         char email[MAX_EMAIL_LEN];
175
176         if((header = xmalloc(EDITW_COLS)) == NULL)
177                 return;
178
179         get_first_email(email, item);
180
181         if(*db_email_get(item))
182                 snprintf(header, EDITW_COLS, "%s <%s>",
183                                 db_name_get(item),
184                                 email);
185         else
186                 snprintf(header, EDITW_COLS, "%s", db_name_get(item));
187
188         mvwaddstr(editw, 0, (EDITW_COLS - strwidth(header)) / 2, header);
189
190         free(header);
191 }
192
193 static void
194 editor_print_data(int tab, int item)
195 {
196         int j = 1, nb;
197         int y, x;
198         abook_field_list *cur;
199         char *str;
200
201         view_info(tab, NULL, &cur);
202
203         for(; cur; cur = cur->next) {
204
205                 if(j > 1) {
206                         getyx(editw, y, x);
207                         y++;
208                 } else
209                         y = FIELDS_START_Y;
210
211                 mvwprintw(editw, y, FIELDS_START_X, "%c - ",
212                                 (j < 10) ? '0' + j : 'A' + j - 10);
213                 mvwaddnstr(editw, y, FIELDS_START_X + 4, cur->field->name,
214                                 bytes2width(cur->field->name,
215                                         FIELDNAME_MAX_WIDTH));
216                 mvwaddch(editw, y, TAB_COLON_POS, ':');
217
218                 if((cur->field->type == FIELD_EMAILS) ||
219                                 (cur->field->type == FIELD_LIST)) {
220                         abook_list *emails, *e;
221                         
222                         find_field_number(cur->field->key, &nb);
223                         emails = csv_to_abook_list(db_fget_byid(item, nb));
224
225                         for(e = emails; e; e = e->next) {
226                                 getyx(editw, y, x);
227                                 mvwaddnstr(editw, y + 1, TAB_COLON_POS + 2,
228                                                 e->data,
229                                                 bytes2width(e->data,
230                                                         FIELD_MAX_WIDTH));
231                                 mvwaddch(editw, y + 1, TAB_COLON_POS,
232                                                 UI_VLINE_CHAR);
233                         }
234                         if(emails) {
235                                 mvwaddch(editw, y + 2, TAB_COLON_POS,
236                                                 UI_LLCORNER_CHAR);
237                                 mvwhline(editw, y + 2, TAB_COLON_POS + 1,
238                                                 UI_HLINE_CHAR,
239                                                 EDITW_COLS - TAB_COLON_POS - 2);
240                         }
241                         abook_list_free(&emails);
242                 } else {
243                         find_field_number(cur->field->key, &nb);
244                         str = safe_str(db_fget_byid(item, nb));
245                         mvwaddnstr(editw, y, TAB_COLON_POS + 2, str,
246                                 bytes2width(str, FIELD_MAX_WIDTH));
247                 }
248
249                 j++;
250         }
251 }
252
253 /*
254  * function: change_field
255  *
256  * parameters:
257  *  (char *msg)
258  *   message to display as a prompt
259  *  (char **field)
260  *   a pointer to a pointer which will point a new string. if the latter
261  *   pointer != NULL it will be freed (if user doesn't cancel)
262  *  (size_t max_len)
263  *   maximum length of field to read from user
264  *
265  * returns (int)
266  *  a nonzero value if user has cancelled and zero if user has typed a
267  *  valid string
268  */
269 static int
270 change_field(char *msg, char **field, int max_len)
271 {
272         char *old;
273         int ret = 0;
274
275         old = *field;
276
277         *field = ui_readline(msg, old, max_len - 1, 0);
278
279         if(*field) {
280                 xfree(old);
281                 if(!**field)
282                         xfree(*field);
283         } else {
284                 *field = old;
285                 ret = 1;
286         }
287
288         clear_statusline();
289         refresh_statusline();
290
291         return ret;
292 }
293
294 static int
295 change_name_field(char *msg, char **field, int max_len)
296 {
297         char *tmp;
298         int ret;
299
300         tmp = xstrdup(*field);
301         ret = change_field(msg, field, max_len);
302
303         if(*field == NULL || ! **field) {
304                 xfree(*field);
305                 *field = xstrdup(tmp);
306         }
307
308         xfree(tmp);
309
310         return ret;
311 }
312
313 static void
314 fix_email_str(char *str)
315 {
316         for(; *str; str++)
317                 *str = *str == ',' ? '_' : *str;
318 }
319
320 static void
321 edit_list(int item, int nb, int isemail)
322 {
323         char *field, *msg, *keys;
324         abook_list *list, *e;
325         int choice = 1, elem_count;
326
327         list = csv_to_abook_list(db_fget_byid(item, nb));
328
329         for(e = list, elem_count = 0; e; e = e->next, elem_count++)
330                 ;
331
332         if(elem_count) {
333                 keys = xstrndup(S_("keybindings_new_123456789|n123456789"),
334                                 elem_count + 1);
335                 msg = strdup_printf(_("Choose %s to modify (<1>%s%c%s%s."),
336                                 isemail ? _("email") : _("item"),
337                                 (elem_count > 1) ? "-<" : "",
338                                 (elem_count > 1) ?  '0' + elem_count : ')',
339                                 (elem_count > 1) ? ">)" : "",
340                                 (elem_count < MAX_LIST_ITEMS) ?
341                                         _(" or <n>ew") : ""
342                                 );
343                 choice = statusline_askchoice(
344                                 msg,
345                                 keys,
346                                 (elem_count < MAX_LIST_ITEMS) ? 1 : 2
347                                 );
348                 free(keys);
349                 free(msg);
350         }
351
352         if(choice == 0)
353                 return;
354
355         field = (choice > 1) ?
356                 xstrdup(abook_list_get(list, choice - 2)->data) :
357                 NULL;
358
359         if(change_field(isemail ? _("E-mail: ") : _("Item: "),
360                                 &field, MAX_EMAIL_LEN))
361                 return; /* user cancelled ( C-g ) */
362
363         /* TODO if list item contains commas, sjould use quotes instead */
364         if(field)
365                 fix_email_str(field);
366
367         if(choice == 1)
368                 abook_list_append(&list, field);
369         else
370                 abook_list_replace(&list, choice - 2, field);
371
372         if(field)
373                 xfree(field);
374
375         field = abook_list_to_csv(list);
376         db_fput_byid(item, nb, field ? field : xstrdup(""));
377         abook_list_free(&list);
378 }
379
380
381 /* input range: 1-9A-Z
382  * output range: 0-34 */
383 static int
384 key_to_field_number(char c)
385 {
386         int n = c - '1';
387         if(n >= 0 && n < 9)
388                 return n;
389
390         n = c - 'A' + 9;
391         if(n > 8 && n < 35)
392                 return n;
393
394         return -1;
395 }
396
397 static void
398 edit_field(int tab, char c, int item_number)
399 {
400         int i = 0, number, idx;
401         char *msg;
402         abook_field_list *f;
403         list_item item;
404
405         if((number = key_to_field_number(c)) < 0)
406                 return;
407
408         edit_undo(item_number, BACKUP_ITEM);
409
410         view_info(tab, NULL, &f);
411
412         while(1) {
413                 if(!f)
414                         return;
415
416                 if(i == number)
417                         break;
418
419                 f = f->next;
420                 i++;
421         }
422
423         find_field_number(f->field->key, &idx);
424         
425         switch(f->field->type) {
426                 case FIELD_STRING:
427                         msg = strdup_printf("%s: ", f->field->name);
428                         item = db_item_get(item_number);
429                         if(strcmp(f->field->key, "name") == 0)
430                                 change_name_field(msg,&item[idx],MAX_FIELD_LEN);
431                         else
432                                 change_field(msg,&item[idx],MAX_FIELD_LEN);
433                         free(msg);
434                         break;
435                 case FIELD_LIST:
436                         edit_list(item_number, idx, 0);
437                         break;
438                 case FIELD_EMAILS:
439                         edit_list(item_number, idx, 1);
440                         break;
441                 case FIELD_DAY:
442                         statusline_msg(_("sorry, input for this field type is "
443                                                 "not yet implemented"));
444                         return;
445                 default:
446                         assert(0);
447         }
448 }
449
450 static int
451 edit_loop(int item)
452 {
453         static int tab = 0; /* first tab */
454         int c;
455
456         werase(editw);
457         headerline(gettext(EDITOR_HELPLINE));
458         refresh_statusline();
459         print_editor_header(item);
460         editor_tab(tab);
461         editor_print_data(tab, item);
462         wmove(editw, EDITW_LINES - 1, EDITW_COLS - 1);
463
464         refresh();
465         wrefresh(editw);
466
467         c = getch();
468         if(c == '\033') {
469                 statusline_addstr("ESC-");
470                 c = getch();
471                 clear_statusline();
472
473                 /* Escaped bindings */
474                 switch(c) {
475                         case 'r': roll_emails(item, ROTATE_RIGHT); break;
476                         default: break;
477                 }
478
479                 return item;
480         }
481
482         /* No uppercase nor numeric key should be used in this menu,
483          * as they are reserved for field selection */
484         switch(c) {
485                 case 'h':
486                 case KEY_LEFT: tab = tab == 0 ? views_count - 1 : tab - 1;
487                                break;
488                 case 'l':
489                 case KEY_RIGHT: tab = tab == views_count - 1 ? 0 : tab + 1;
490                                 break;
491                 case KEY_UP:
492                 case '<':
493                 case 'k': if(is_valid_item(item - 1)) item--; break;
494                 case KEY_DOWN:
495                 case '>':
496                 case 'j': if(is_valid_item(item + 1)) item++; break;
497                 case 'r': roll_emails(item, ROTATE_LEFT); break;
498                 case '?': display_help(HELP_EDITOR); break;
499                 case 'u': item = edit_undo(item, RESTORE_ITEM); break;
500                 case 'm': launch_mutt(item); clearok(stdscr, 1); break;
501                 case 'v': launch_wwwbrowser(item); clearok(stdscr, 1); break;
502                 case 12 : clearok(stdscr, 1); break; /* ^L (refresh screen) */
503                 case 'q': return -1;
504                 default: edit_field(tab, c, item);
505         }
506
507         return item;
508 }
509
510 void
511 edit_item(int item)
512 {
513         if( item < 0 ) {
514                 if( curitem < 0 )
515                         return;
516                 else
517                         item = curitem;
518         }
519
520         init_editor();
521
522         while((item = edit_loop(item)) >= 0)
523                 curitem = item; /* hmm, this is not very clean way to go */
524
525         close_editor();
526 }
527
528 void
529 add_item()
530 {
531         char *field = NULL;
532         list_item item = item_create();
533
534         change_field("Name: ", &field, MAX_FIELD_LEN);
535
536         if( field == NULL )
537                 return;
538
539         item_fput(item, NAME, field);
540
541         add_item2database(item);
542         item_free(&item);
543
544         curitem = LAST_ITEM;
545
546         edit_item(LAST_ITEM);
547 }
548