1 /**************************************************************************
2  **
3  ** sngrep - SIP Messages flow viewer
4  **
5  ** Copyright (C) 2013-2018 Ivan Alonso (Kaian)
6  ** Copyright (C) 2013-2018 Irontec SL. All rights reserved.
7  **
8  ** This program is free software: you can redistribute it and/or modify
9  ** it under the terms of the GNU General Public License as published by
10  ** the Free Software Foundation, either version 3 of the License, or
11  ** (at your option) any later version.
12  **
13  ** This program is distributed in the hope that it will be useful,
14  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  ** GNU General Public License for more details.
17  **
18  ** You should have received a copy of the GNU General Public License
19  ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  **
21  ****************************************************************************/
22 
23 /**
24  * @file ui_column_select.c
25  * @author Ivan Alonso [aka Kaian] <kaian@irontec.com>
26  *
27  * @brief Source of functions defined in ui_column_select.h
28  *
29  */
30 #include <string.h>
31 #include <stdlib.h>
32 #include <regex.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include "ui_manager.h"
36 #include "ui_call_list.h"
37 #include "ui_column_select.h"
38 
39 /**
40  * Ui Structure definition for Message Diff panel
41  */
42 ui_t ui_column_select = {
43     .type = PANEL_COLUMN_SELECT,
44     .panel = NULL,
45     .create = column_select_create,
46     .handle_key = column_select_handle_key,
47     .destroy = column_select_destroy
48 };
49 
50 void
column_select_create(ui_t * ui)51 column_select_create(ui_t *ui)
52 {
53     int attr_id, column;
54     MENU *menu;
55     column_select_info_t *info;
56 
57     // Cerate a new indow for the panel and form
58     ui_panel_create(ui, 20, 60);
59 
60     // Initialize Filter panel specific data
61     info = sng_malloc(sizeof(column_select_info_t));
62 
63     // Store it into panel userptr
64     set_panel_userptr(ui->panel, (void*) info);
65 
66     // Initialize the fields
67     info->fields[FLD_COLUMNS_ACCEPT] = new_field(1, 10, ui->height - 2, 13, 0, 0);
68     info->fields[FLD_COLUMNS_SAVE]   = new_field(1, 10, ui->height - 2, 25, 0, 0);
69     info->fields[FLD_COLUMNS_CANCEL] = new_field(1, 10, ui->height - 2, 37, 0, 0);
70     info->fields[FLD_COLUMNS_COUNT] = NULL;
71 
72     // Field Labels
73     set_field_buffer(info->fields[FLD_COLUMNS_ACCEPT], 0, "[ Accept ]");
74     set_field_buffer(info->fields[FLD_COLUMNS_SAVE],   0, "[  Save  ]");
75     set_field_buffer(info->fields[FLD_COLUMNS_CANCEL], 0, "[ Cancel ]");
76 
77     // Create the form and post it
78     info->form = new_form(info->fields);
79     set_form_sub(info->form, ui->win);
80     post_form(info->form);
81 
82     // Create a subwin for the menu area
83     info->menu_win = derwin(ui->win, 10, ui->width - 2, 7, 0);
84 
85     // Initialize one field for each attribute
86     for (attr_id = 0; attr_id < SIP_ATTR_COUNT; attr_id++) {
87         // Create a new field for this column
88         info->items[attr_id] = new_item("[ ]", sip_attr_get_description(attr_id));
89         set_item_userptr(info->items[attr_id], (void*) sip_attr_get_name(attr_id));
90     }
91     info->items[SIP_ATTR_COUNT] = NULL;
92 
93     // Create the columns menu and post it
94     info->menu = menu = new_menu(info->items);
95 
96     // Set current enabled fields
97     // FIXME Stealing Call list columns :/
98     call_list_info_t *list_info = call_list_info(ui_find_by_type(PANEL_CALL_LIST));
99 
100     // Enable current enabled fields and move them to the top
101     for (column = 0; column < list_info->columncnt; column++) {
102         const char *attr = list_info->columns[column].attr;
103         for (attr_id = 0; attr_id < item_count(menu); attr_id++) {
104             if (!strcmp(item_userptr(info->items[attr_id]), attr)) {
105                 column_select_toggle_item(ui, info->items[attr_id]);
106                 column_select_move_item(ui, info->items[attr_id], column);
107                 break;
108             }
109         }
110     }
111 
112     // Set main window and sub window
113     set_menu_win(menu, ui->win);
114     set_menu_sub(menu, derwin(ui->win, 10, ui->width - 5, 7, 2));
115     set_menu_format(menu, 10, 1);
116     set_menu_mark(menu, "");
117     set_menu_fore(menu, COLOR_PAIR(CP_DEF_ON_BLUE));
118     menu_opts_off(menu, O_ONEVALUE);
119     post_menu(menu);
120 
121     // Draw a scrollbar to the right
122     info->scroll = ui_set_scrollbar(info->menu_win, SB_VERTICAL, SB_RIGHT);
123     info->scroll.max = item_count(menu) - 1;
124     ui_scrollbar_draw(info->scroll);
125 
126     // Set the window title and boxes
127     mvwprintw(ui->win, 1, ui->width / 2 - 14, "Call List columns selection");
128     wattron(ui->win, COLOR_PAIR(CP_BLUE_ON_DEF));
129     title_foot_box(ui->panel);
130     mvwhline(ui->win, 6, 1, ACS_HLINE, ui->width - 1);
131     mvwaddch(ui->win, 6, 0, ACS_LTEE);
132     mvwaddch(ui->win, 6, ui->width - 1, ACS_RTEE);
133     wattroff(ui->win, COLOR_PAIR(CP_BLUE_ON_DEF));
134 
135     // Some brief explanation abotu what window shows
136     wattron(ui->win, COLOR_PAIR(CP_CYAN_ON_DEF));
137     mvwprintw(ui->win, 3, 2, "This windows show the list of columns displayed on Call");
138     mvwprintw(ui->win, 4, 2, "List. You can enable/disable using Space Bar and reorder");
139     mvwprintw(ui->win, 5, 2, "them using + and - keys.");
140     wattroff(ui->win, COLOR_PAIR(CP_CYAN_ON_DEF));
141 
142     info->form_active = 0;
143 }
144 
145 void
column_select_destroy(ui_t * ui)146 column_select_destroy(ui_t *ui)
147 {
148     int i;
149     column_select_info_t *info = column_select_info(ui);
150 
151     // Remove menu and items
152     unpost_menu(info->menu);
153     free_menu(info->menu);
154     for (i = 0; i < SIP_ATTR_COUNT; i++)
155         free_item(info->items[i]);
156 
157     // Remove form and fields
158     unpost_form(info->form);
159     free_form(info->form);
160     for (i = 0; i < FLD_COLUMNS_COUNT; i++)
161         free_field(info->fields[i]);
162 
163     sng_free(info);
164 
165     // Remove panel window and custom info
166     ui_panel_destroy(ui);
167 }
168 
169 
170 column_select_info_t *
column_select_info(ui_t * ui)171 column_select_info(ui_t *ui)
172 {
173     return (column_select_info_t*) panel_userptr(ui->panel);
174 }
175 
176 int
column_select_handle_key(ui_t * ui,int key)177 column_select_handle_key(ui_t *ui, int key)
178 {
179     // Get panel information
180     column_select_info_t *info = column_select_info(ui);
181 
182     if (info->form_active) {
183         return column_select_handle_key_form(ui, key);
184     } else {
185         return column_select_handle_key_menu(ui, key);
186     }
187     return 0;
188 }
189 
190 int
column_select_handle_key_menu(ui_t * ui,int key)191 column_select_handle_key_menu(ui_t *ui, int key)
192 {
193     MENU *menu;
194     ITEM *current;
195     int current_idx;
196     int action = -1;
197 
198     // Get panel information
199     column_select_info_t *info = column_select_info(ui);
200 
201     menu = info->menu;
202     current = current_item(menu);
203     current_idx = item_index(current);
204 
205     // Check actions for this key
206     while ((action = key_find_action(key, action)) != ERR) {
207         // Check if we handle this action
208         switch (action) {
209             case ACTION_DOWN:
210                 menu_driver(menu, REQ_DOWN_ITEM);
211                 break;
212             case ACTION_UP:
213                 menu_driver(menu, REQ_UP_ITEM);
214                 break;
215             case ACTION_NPAGE:
216                 menu_driver(menu, REQ_SCR_DPAGE);
217                 break;
218             case ACTION_PPAGE:
219                 menu_driver(menu, REQ_SCR_UPAGE);
220                 break;
221             case ACTION_SELECT:
222                 column_select_toggle_item(ui, current);
223                 column_select_update_menu(ui);
224                 break;
225             case ACTION_COLUMN_MOVE_DOWN:
226                 column_select_move_item(ui, current, current_idx + 1);
227                 column_select_update_menu(ui);
228                 break;
229             case ACTION_COLUMN_MOVE_UP:
230                 column_select_move_item(ui, current, current_idx - 1);
231                 column_select_update_menu(ui);
232                 break;
233             case ACTION_NEXT_FIELD:
234                 info->form_active = 1;
235                 set_menu_fore(menu, COLOR_PAIR(CP_DEFAULT));
236                 set_field_back(info->fields[FLD_COLUMNS_ACCEPT], A_REVERSE);
237                 form_driver(info->form, REQ_VALIDATION);
238                 break;
239             case ACTION_CONFIRM:
240                 column_select_update_columns(ui);
241                 ui_destroy(ui);
242                 return KEY_HANDLED;
243             default:
244                 // Parse next action
245                 continue;
246         }
247 
248         // This panel has handled the key successfully
249         break;
250     }
251 
252     // Draw a scrollbar to the right
253     info->scroll.pos = top_row(menu);
254     ui_scrollbar_draw(info->scroll);
255     wnoutrefresh(info->menu_win);
256 
257     // Return if this panel has handled or not the key
258     return (action == ERR) ? KEY_NOT_HANDLED : KEY_HANDLED;
259 }
260 
261 int
column_select_handle_key_form(ui_t * ui,int key)262 column_select_handle_key_form(ui_t *ui, int key)
263 {
264     int field_idx, new_field_idx;
265     char field_value[48];
266     int action = -1;
267 
268     // Get panel information
269     column_select_info_t *info = column_select_info(ui);
270 
271     // Get current field id
272     field_idx = field_index(current_field(info->form));
273 
274     // Get current field value.
275     memset(field_value, 0, sizeof(field_value));
276     strcpy(field_value, field_buffer(current_field(info->form), 0));
277     strtrim(field_value);
278 
279     // Check actions for this key
280     while ((action = key_find_action(key, action)) != ERR) {
281         // Check if we handle this action
282         switch (action) {
283             case ACTION_RIGHT:
284             case ACTION_NEXT_FIELD:
285                 form_driver(info->form, REQ_NEXT_FIELD);
286                 break;
287             case ACTION_LEFT:
288             case ACTION_PREV_FIELD:
289                 form_driver(info->form, REQ_PREV_FIELD);
290                 break;
291             case ACTION_SELECT:
292             case ACTION_CONFIRM:
293                 switch(field_idx) {
294                     case FLD_COLUMNS_ACCEPT:
295                         column_select_update_columns(ui);
296                         ui_destroy(ui);
297                         return KEY_HANDLED;
298                     case FLD_COLUMNS_CANCEL:
299                         ui_destroy(ui);
300                         return KEY_HANDLED;
301                     case FLD_COLUMNS_SAVE:
302                         column_select_update_columns(ui);
303                         column_select_save_columns(ui);
304                         ui_destroy(ui);
305                         return KEY_HANDLED;
306                 }
307                 break;
308             default:
309                 // Parse next action
310                 continue;
311         }
312 
313         // This panel has handled the key successfully
314         break;
315     }
316 
317     // Validate all input data
318     form_driver(info->form, REQ_VALIDATION);
319 
320     // Change background and cursor of "button fields"
321     set_field_back(info->fields[FLD_COLUMNS_ACCEPT], A_NORMAL);
322     set_field_back(info->fields[FLD_COLUMNS_SAVE],   A_NORMAL);
323     set_field_back(info->fields[FLD_COLUMNS_CANCEL], A_NORMAL);
324 
325     // Get current selected field
326     new_field_idx = field_index(current_field(info->form));
327 
328     // Swap between menu and form
329     if (field_idx == FLD_COLUMNS_CANCEL && new_field_idx == FLD_COLUMNS_ACCEPT) {
330         set_menu_fore(info->menu, COLOR_PAIR(CP_DEF_ON_BLUE));
331         info->form_active = 0;
332     } else {
333         // Change current field background
334         set_field_back(info->fields[new_field_idx], A_REVERSE);
335     }
336 
337     // Return if this panel has handled or not the key
338     return (action == ERR) ? KEY_NOT_HANDLED : KEY_HANDLED;
339 }
340 
341 void
column_select_update_columns(ui_t * ui)342 column_select_update_columns(ui_t *ui)
343 {
344     int column, attr_id;
345 
346     // Get panel information
347     column_select_info_t *info = column_select_info(ui);
348 
349     // Set enabled fields
350     ui_t *ui_list = ui_find_by_type(PANEL_CALL_LIST);
351     call_list_info_t *list_info = call_list_info(ui_list);
352 
353     // Reset column count
354     list_info->columncnt = 0;
355 
356     // Add all selected columns
357     for (column = 0; column < item_count(info->menu); column++) {
358         // If column is active
359         if (!strncmp(item_name(info->items[column]), "[ ]", 3))
360             continue;
361 
362         // Get column attribute
363         attr_id = sip_attr_from_name(item_userptr(info->items[column]));
364         // Add a new column to the list
365         call_list_add_column(ui_list, attr_id, sip_attr_get_name(attr_id),
366                              sip_attr_get_title(attr_id), sip_attr_get_width(attr_id));
367     }
368 }
369 
370 void
column_select_save_columns(ui_t * ui)371 column_select_save_columns(ui_t *ui)
372 {
373     int column;
374     FILE *fi, *fo;
375     char columnopt[128];
376     char line[1024];
377     char *rcfile;
378     char *userconf = NULL;
379     char *tmpfile  = NULL;
380 
381     // Use current $SNGREPRC or $HOME/.sngreprc file
382     if ((rcfile = getenv("SNGREPRC"))) {
383         if ((userconf = sng_malloc(strlen(rcfile) + RCFILE_EXTRA_LEN))) {
384             if ((tmpfile = sng_malloc(strlen(rcfile) + RCFILE_EXTRA_LEN))) {
385                 sprintf(userconf, "%s", rcfile);
386                 sprintf(tmpfile, "%s.old", rcfile);
387             } else {
388                 sng_free(userconf);
389                 return;
390             }
391         } else {
392             return;
393         }
394     } else if ((rcfile = getenv("HOME"))) {
395         if ((userconf = sng_malloc(strlen(rcfile) + RCFILE_EXTRA_LEN))) {
396             if ((tmpfile = sng_malloc(strlen(rcfile) + RCFILE_EXTRA_LEN))) {
397                 sprintf(userconf, "%s/.sngreprc", rcfile);
398                 sprintf(tmpfile, "%s/.sngreprc.old", rcfile);
399             } else {
400                 sng_free(userconf);
401                 return;
402             }
403         } else {
404             return;
405         }
406     } else {
407         return;
408     }
409 
410     // Remove old config file
411     unlink(tmpfile);
412 
413     // Move user conf file to temporal file
414     rename(userconf, tmpfile);
415 
416     // Create a new user conf file
417     if (!(fo = fopen(userconf, "w"))) {
418         dialog_run("Unable to open %s: %s", userconf, strerror(errno));
419         sng_free(userconf);
420         sng_free(tmpfile);
421         return;
422     }
423 
424     // Read all lines of old sngreprc file
425     if ((fi = fopen(tmpfile, "r"))) {
426 
427         // Read all configuration file
428         while (fgets(line, 1024, fi) != NULL) {
429             // Ignore lines starting with set (but keep settings)
430             if (strncmp(line, "set ", 4) || strncmp(line, "set cl.column", 13)) {
431                 // Put everyting in new user conf file
432                 fputs(line, fo);
433             }
434         }
435         fclose(fi);
436     }
437 
438     // Get panel information
439     column_select_info_t *info = column_select_info(ui);
440 
441     // Add all selected columns
442     for (column = 0; column < item_count(info->menu); column++) {
443         // If column is active
444         if (!strncmp(item_name(info->items[column]), "[ ]", 3))
445             continue;
446 
447         // Add the columns settings
448         sprintf(columnopt, "set cl.column%d %s\n", column, (const char*) item_userptr(info->items[column]));
449         fputs(columnopt, fo);
450     }
451     fclose(fo);
452 
453     // Show a information dialog
454     dialog_run("Column layout successfully saved to %s", userconf);
455 
456     sng_free(userconf);
457     sng_free(tmpfile);
458 }
459 
460 
461 void
column_select_move_item(ui_t * ui,ITEM * item,int pos)462 column_select_move_item(ui_t *ui, ITEM *item, int pos)
463 {
464     // Get panel information
465     column_select_info_t *info = column_select_info(ui);
466 
467     // Check we have a valid position
468     if (pos == item_count(info->menu) || pos < 0)
469         return;
470 
471     // Swap position with destination
472     int item_pos = item_index(item);
473     info->items[item_pos] = info->items[pos];
474     info->items[pos] = item;
475     set_menu_items(info->menu, info->items);
476 }
477 
478 void
column_select_toggle_item(ui_t * ui,ITEM * item)479 column_select_toggle_item(ui_t *ui, ITEM *item)
480 {
481     // Get panel information
482     column_select_info_t *info = column_select_info(ui);
483 
484     int pos = item_index(item);
485 
486     // Change item name
487     if (!strncmp(item_name(item), "[ ]", 3)) {
488         info->items[pos] = new_item("[*]", item_description(item));
489     } else {
490         info->items[pos] = new_item("[ ]", item_description(item));
491     }
492 
493     // Restore menu item
494     set_item_userptr(info->items[pos], item_userptr(item));
495     set_menu_items(info->menu, info->items);
496 
497     // Destroy old item
498     free_item(item);
499 }
500 
501 void
column_select_update_menu(ui_t * ui)502 column_select_update_menu(ui_t *ui)
503 {
504     // Get panel information
505     column_select_info_t *info = column_select_info(ui);
506     ITEM *current = current_item(info->menu);
507     int top_idx = top_row(info->menu);
508 
509     // Remove the menu from the subwindow
510     unpost_menu(info->menu);
511     // Set menu items
512     set_menu_items(info->menu, info->items);
513     // Put the menu agin into its subwindow
514     post_menu(info->menu);
515 
516     // Move until the current position is set
517     set_top_row(info->menu, top_idx);
518     set_current_item(info->menu, current);
519 
520     // Force menu redraw
521     menu_driver(info->menu, REQ_UP_ITEM);
522     menu_driver(info->menu, REQ_DOWN_ITEM);
523 }
524