#include #include #include "diadef.h" #include "dialog.h" #include "dialog.m" #include "internal.h" #include "../diajava/proto.h" #include PUBLIC FIELD_MENU::FIELD_MENU( const char *_icon, const char *_tag, const char *_str) : FIELD_STRING ("",(char*)_str,200,false) { key_type = HTML_KEY_FULL; icon = NULL; if (_icon != NULL) icon = strdup (_icon); tag = strdup(_tag); strip_end (tag); setwidths (strlen(_tag)+strlen(buf),NULL); // Fixed to some value until // setwidth1() is called, much later set_readonly(); } PUBLIC FIELD_MENU::~FIELD_MENU() { free (tag); free (icon); } /* Build a key that uniquely identify this field in the dialog */ PUBLIC void FIELD_MENU::format_htmlkey(char *key, int no) { if (key_type == HTML_KEY_TAG){ /* #Specification: dialog / html mode / field key Each field (either input field or menu entry) is identified with a key (the name= field of html) which has to be unique in the dialog. For input dialog, this is simply the field title (the prompt) combined with the field number (sequence number in the dialog). This create a somewhat meaningless key, but unique. For menu, we use the text of the menu and this yield also a unique key. Even better, this key is valid even if the menu change (a new option is inserted). In linuxconf, there are two types of menus: The normal menus and the lists. With lists, there is a problem with this strategy because lists are often build with multiple columns information. The first columns represent the ID of a record (user account, host name) and the rest represents some information about the record (some values). Unfortunatly, the values are changing while we edit records and this change the key we have used to generate some html dialogs. Sometime this even change the content of some menus several level higher in the hierarchy. This cause problem with the html strategy of linuxconf. Remember that between each http connection, linuxconf is indeed escaping to its main menu and travel to the target dialog of the request by playing back some scenario. This is working unless the various keys identifying the menus are changing. Hope you are following me. So for lists, we are only using the tag (the first column) or each record to build the key. This is controlled by the DIALOG_LISTE object. It sets the tag_is_key flag in each FIELD_MENU objets. */ html_formatkey (key,"%s",tag); }else if (key_type == HTML_KEY_INDEX){ html_formatkey (key,"index%d",no); }else{ html_formatkey (key,"%s %s",tag,buf); } } /* Fix the final disposition of the menu item (The start of the second column) */ PUBLIC void FIELD_MENU::setwidths (int total_width, int tb[]) { if (tb != NULL) memcpy (tbcol,tb,sizeof(tbcol)); box.width = total_width; } /* Compute the width of each word in a string. Words are separated by \t. Return the number of words */ int menubox_getwidths(const char *pt, int tb[]) { int ret = 0; const char *pt0 = pt; while (*pt != '\0'){ if (*pt == '\t'){ tb[ret] = (int)(pt-pt0)+1; ret++; pt0 = pt+1; } pt++; } tb[ret++] = (int)(pt - pt0)+1; return ret; } /* Get the width of each sub-columns of the second column. Return the number of sub-columns. */ PROTECTED int FIELD_MENU::getwidths (int tb[], int &) { tb[0] = strlen(tag)+2; // The tag is always one column return menubox_getwidths(buf,tb+1) + 1; } /* Return the second string of a menu item */ PUBLIC const char *FIELD_MENU::getmenustr() const { return str; } PUBLIC const char *FIELD_MENU::getmenuicon() const { return icon; } /* A FIELD_MENU is not really a string editor. The following function are just there to prevent writing over constant string. */ PROTECTED void FIELD_MENU::save() { } PROTECTED void FIELD_MENU::restore() { } PROTECTED MENU_STATUS FIELD_MENU::dokey( WINDOW *, int, FIELD_MSG &, bool & ) { return MENU_NULL; } static void menubox_addch ( WINDOW *win, const char ch, const int offset, int &pos) { if (pos >= offset) waddch (win,ch); pos++; } static void menubox_addstr ( WINDOW *win, const char *str, const int offset, int &pos) { while (*str != '\0') menubox_addch (win,*str++,offset,pos); } void menubox_drawcols( const char *pt, int tbcol[], int max_width, WINDOW *win, const int hoffset, int pos, int selcol, // Which column is currently selected (or -1) unsigned attr, unsigned attrsel) { int nocol = 0; int ptcol = 0; int offset = 0; max_width += hoffset; wattrset (win,selcol == nocol ? attrsel : attr); while (*pt != '\0' && offset < max_width){ if (*pt == '\t'){ int wcol = tbcol[nocol]; while (ptcol < wcol && offset < max_width){ menubox_addch(win,' ',hoffset,pos); ptcol++; offset++; } ptcol = 0; nocol++; wattrset (win,selcol == nocol ? attrsel : attr); }else{ menubox_addch(win, *pt,hoffset,pos); ptcol++; offset++; } pt++; } } /* Print menu item */ PUBLIC void FIELD_MENU::drawgen(WINDOW *win, bool selected, int offset) { wattrset(win, menubox_attr); wmove(win, box.y, box.x); // Cleanup for (int i = 0; i < box.width; i++) waddch(win, ' '); wmove(win, box.y, box.x); char *pttag = tag; int pos = 0; if (tag[0] != ' ' && tag[0] != '\0'){ wattrset(win, selected ? tag_key_selected_attr : tag_key_attr); menubox_addch(win, tag[0],offset,pos); pttag++; } wattrset(win, selected ? tag_selected_attr : tag_attr); menubox_addstr(win, pttag,offset,pos); while (pos < tbcol[0]) menubox_addch (win,' ',offset,pos); // wmove(win, box.y, box.x + tbcol[0]); //wattrset(win, selected ? item_selected_attr : item_attr); menubox_drawcols (buf,tbcol+1,box.width-tbcol[0],win,offset,pos,-1 ,selected ? item_selected_attr : item_attr ,tag_attr); wmove(win, box.y, box.x); } PUBLIC void FIELD_MENU::unselect(WINDOW *dialog, int offset) { drawgen(dialog,false,offset); } PUBLIC void FIELD_MENU::setcursor(WINDOW *dialog, int offset) { drawgen (dialog,true,offset); } PUBLIC void FIELD_MENU::drawtxt(WINDOW *dialog, int offset, int, int) { drawgen (dialog,false,offset); } PUBLIC void FIELD_MENU::html_draw(int nof) { char key[300]; format_htmlkey (key,nof); html_printf (""); if (may_select){ html_setaref (key,tag); }else{ html_printf ("%s",tag); } const char *pt = buf; char tdbuf[300]; char *ptdst = tdbuf; html_printf (""); while (*pt != '\0'){ if (*pt == '\t'){ *ptdst = '\0'; if (may_select){ html_setaref (key,tdbuf); }else{ html_printf ("%s",tdbuf); } html_printf (""); ptdst = tdbuf; }else{ *ptdst++ = *pt; } pt++; } *ptdst = '\0'; if (may_select){ html_setaref (key,tdbuf); }else{ html_printf ("%s",tdbuf); } html_printf ("\n"); } PUBLIC void FIELD_MENU::gui_draw(int nof, SSTRINGS &) { if (key_type == HTML_KEY_TAG || key_type == HTML_KEY_INDEX){ // Hack to differentiate tables from menu. To fix later... ftitle_clist (nof,tag,buf,false,"\"\"",false); }else{ if (!diajava_nogfx){ if (icon != NULL){ char name_sent[PATH_MAX]; diagui_sendxpm (icon,name_sent); /*if (may_select){ diagui_sendcmd (P_Button_xpm,"M%d %s -\n",nof,name_sent); }else{*/ diagui_sendcmd (P_Icon_xpm,"%s\n",name_sent); //} }else{ diagui_sendcmd (P_Skip,"1\n"); } } if (may_select && tag[0] != '\0'){ char tmp[1000]; diagui_sendcmd (P_Buttonfill,"M%d %s\n",nof,diagui_quote(tag,tmp)); }else{ diagui_sendcmd (P_Skip,"1\n"); } diagui_sendcmd (P_Dispolast,"c 1 c 1\n"); const char *pt = buf; char tdbuf[300]; char *ptdst = tdbuf; while (*pt != '\0'){ if (*pt == '\t'){ *ptdst = '\0'; if (may_select){ char tmp[1000]; diagui_sendcmd (P_Buttonfill,"M%d %s\n",nof ,diagui_quote(tdbuf,tmp)); }else{ diagui_send_Label (tdbuf); } diagui_sendcmd (P_Dispolast,"l 1 c 1\n"); ptdst = tdbuf; }else{ *ptdst++ = *pt; } pt++; } *ptdst = '\0'; if (may_select){ char tmp[1000]; diagui_sendcmd (P_Buttonfill,"M%d %s\n",nof,diagui_quote(tdbuf,tmp)); }else{ diagui_send_Label (tdbuf); } diagui_sendcmd (P_Dispolast,"l 1 c 1\n"); } } PUBLIC char FIELD_MENU::getidprefix () { return 'M'; } PUBLIC void FIELD_MENU::popup_draw(int id, int &) { if (buf[0] == '-'){ diagui_sendcmd (P_Menuentry,"0 -\n"); }else{ SSTRING s; s.setfromf("%s %s",tag,buf); char tmp[1000]; diagui_sendcmd (P_Menuentry,"%d %s\n",id,diagui_quote(s.get(),tmp)); } } PUBLIC int FIELD_MENU::html_validate(int) { return 0; } static int menubox_evalwidth ( int max_width, int tbcol[], int nbcol) { int max_w = 0; for (int i=0; i max_width) max_width = max_w; return max_width; } /* Evaluate the width of the first column. Options are organised in 2 columns. */ PRIVATE void DIALOG::fixwidth1() { int i; int tbcol[DIALOG_MAXCOL]; memset (tbcol,0,sizeof(tbcol)); int nbcol = 0; int n = getnb(); int max_width = 0; for (i=0; igetwidths(tbf, reset); if (reset){ // Reset but first compute the width of the columns so far max_width = menubox_evalwidth (max_width,tbcol,nbcol); memset (tbcol,0,sizeof(tbcol)); for (int c=0; c tbcol[c]) tbcol[c] = tbf[c]; } } if (nbc > nbcol) nbcol = nbc; } max_width = menubox_evalwidth (max_width,tbcol,nbcol); if (max_width > COLS - 6) max_width = COLS - 6; for (i=0; isetwidths(max_width,tbcol); } /* Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly. */ PUBLIC void DIALOG::new_menuline ( const char *icon, // Icon file name (without extension) or NULL const char *prompt1, const char *prompt2, bool may_select) { if (prompt1==NULL) prompt1 = "(null)"; if (prompt2==NULL) prompt2 = "(null)"; if (strcmp(prompt1,"-")==0){ /* #Specification: dialog / menus / splitter If the first member of a menu entry is a single '-', this entry is drawn as a full splitter line The second entry will be used as the splitter title. */ newf_title (prompt2,1,"",prompt2); }else{ // if (prompt1[0] == '\0') prompt1 = " "; FIELD_MENU *men = new FIELD_MENU (icon,prompt1,prompt2); men->set_selectable (may_select); add (men); } } /* Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly. */ PUBLIC void DIALOG::new_menuitem (const char *prompt1, const char *prompt2) { new_menuline (NULL,prompt1,prompt2,true); } PUBLIC void DIALOG::new_menuitem ( const char *icon, const char *prompt1, const char *prompt2) { new_menuline (icon,prompt1,prompt2,true); } /* Redefine one line of a menu or list */ PUBLIC void DIALOG::set_menuitem ( int no, const char *prompt1, const char *prompt2) { FIELD *first = getitem(0); if (first != NULL && first->is_head) no++; // if (prompt1[0] == '\0') prompt1 = " "; FIELD_MENU *old = (FIELD_MENU*)getitem(no); if (old == NULL || strcmp(prompt1,old->tag)!= 0 || strcmp(prompt2,old->buf)!= 0){ bool setval; if (old == NULL){ old = new FIELD_MENU(NULL,prompt1,prompt2); old->set_selectable (true); ARRAY::add (old); setval = false; }else{ free (old->tag); old->tag = strdup(prompt1); strncpy (old->buf,prompt2,old->size); old->buf[old->size] = '\0'; setval = true; } SSTRING tmp; const char *dianame = setguiname(tmp); if (dianame != NULL){ char tmp1[1000]; if (first != NULL){ old->guipath.setfrom(first->guipath); dianame = old->formatpath(tmp1,dianame); } ftitle_clist (no,prompt1,prompt2,false,dianame,setval); }else{ reset_guidone(); } } } /* Add a new info line in a menu (non selectable) */ PUBLIC void DIALOG::new_menuinfo (const char *prompt1, const char *prompt2) { new_menuline (NULL,prompt1,prompt2,false); } /* Add a new menu item to a dialog You will have to use the DIALOG::editmenu instead of just DIALOG::edit() or your dialog will lack some buttons and won't be layout properly. */ PUBLIC void DIALOG::new_menuitem (const char *prompt1, const SSTRING &prompt2) { new_menuitem (prompt1,prompt2.get()); } PUBLIC void DIALOG::new_menuitem (const SSTRING &prompt1, const SSTRING &prompt2) { new_menuitem (prompt1.get(),prompt2); } PRIVATE void DIALOG::new_menuitem_parse( const char *item1, const char *item2) { const char *icon = item1; const char *text = strchr(icon,':'); char buf[PATH_MAX]; if (text!=NULL){ char *pt = buf; while (icon < text) *pt++ = *icon++; *pt = '\0'; icon = buf; text++; }else{ text = icon; icon = NULL; } new_menuitem (icon,text,item2); } PUBLIC void DIALOG::new_menuitems (const char *items[], int item_no) { for (int i=0; i=0 && choice < getnb()){ ret = getitem(choice)->getmenustr(); } return ret; } /* Record a small help for the Save button */ PUBLIC void DIALOG::savewhat (const char *help) { internal->what.save.setfrom (help); } /* Record a small help for the Del button */ PUBLIC void DIALOG::delwhat (const char *help) { internal->what.del.setfrom (help); } /* Record a small help for the Ins button */ PUBLIC void DIALOG::inswhat (const char *help) { internal->what.ins.setfrom (help); } /* Record a small help for the Add button */ PUBLIC void DIALOG::addwhat (const char *help) { internal->what.add.setfrom (help); } static void append_what ( int &options, int optval, const SSTRING &str, SSTRING &explan) { if (!str.is_empty()){ char buf[100]; snprintf (buf,sizeof(buf)-1,"\n%s",str.get()); options |= optval; explan.append (buf); } } static void (*fcollect)(const char *, const char *, const char *); /* Register a collect function which will be called for each menu option when running in tree mode */ EXPORT void menubox_setcollect (void (*f)(const char *, const char *, const char *)) { fcollect = f; } /* Edit a menu */ PUBLIC VIRTUAL MENU_STATUS DIALOG::editmenu( const char *title, const char *prompt, HELP_FILE &helpfile, int &sel, // Will hold the selection or -1 if escape // It must already contains the initial selection int options) // MENUBUT_ADD | MENUBUT_INS | MENUBUT_DEL | MENUBUT_SAVE { FIELD *first = getitem(0); MENU_STATUS ret = MENU_ESCAPE; SSTRING tmp_explan (prompt); int spc_options = 0; append_what (spc_options,MENUBUT_SAVE,internal->what.save,tmp_explan); append_what (spc_options,MENUBUT_ADD,internal->what.add,tmp_explan); append_what (spc_options,MENUBUT_INS,internal->what.ins,tmp_explan); append_what (spc_options,MENUBUT_DEL,internal->what.del,tmp_explan); if (first != NULL && first->is_head) sel++; ret = edit (title,tmp_explan.get(),helpfile,sel ,options|spc_options|MENUBUT_OK|MENUBUT_QUIT); if (first != NULL && first->is_head) sel--; return ret; } static void menubox_collect(int sublevel, const char *txt, const char *icon) { if (fcollect != NULL){ char key[100]; char *ptkey = key; for (int i=0; i<=ui_context.treemenu_level+sublevel; i++){ ptkey += sprintf (ptkey,"%d/",treemenu_pos[i]-1); } *ptkey = '\0'; //fprintf (stderr,"key :%s: :%s:\n",key,txt); (*fcollect)(key,txt,icon); } } /* Edit a menu */ PUBLIC VIRTUAL MENU_STATUS DIALOG_MENU::editmenu( const char *title, const char *prompt, HELP_FILE &helpfile, int &sel, // Will hold the selection or -1 if escape // It must already contains the initial selection int options) // MENUBUT_ADD | MENUBUT_INS | MENUBUT_DEL | MENUBUT_SAVE { MENU_STATUS ret = MENU_ESCAPE; if (dialog_mode == DIALOG_TREE){ // Menu with an Add button are not really menu, The code // should be changed so those menu becomes DIALOG_RECORDS // or DIALOG_LISTE if ((options & MENUBUT_ADD)==0 && internal->what.add.is_empty()){ /*~Specification: Tree menu / strategy The tree menu is built dynamically, by running linuxconf in a special "silent" mode where it collects all menu entries. It does that by simulating that each menu options are selected one after the other. Two global variables are used to record the state of each menu and the current level (treemenu_pos[] and treemenu_level). Each time we leave a menu, we increase the level. Each time we enter a non menu dialog, we decrease the level, returning to the menu. Each time we reach the end of a menu, we also decrease the level. There is one problem with this strategy. If a menu option trigger an action instead of a sub-dialog, noone will decrease the level. We will reenter the same menu with one level higher. We will never go out. We use a trick here. Each menu remember its treelevel. This assumes that a DIALOG_MENU object lives until the menu is dismissed, which is the case. A DIALOG_MENU object is always used like this # DIALOG_MENU dia; // Menu setup while (1){ MENU_STATUS code = dia.editmenu(...); . . } # */ /*~Specification: Tree menu / strategy / sub-sections A menu may be made of sub-sections. In graphic mode those sections are broken in several notebook pages. When building the tree, we present the sub-sections as a menu level and each sub-section open up as a sub-menu. This is making the tree wider and shorter. So it creates a set of smaller sub-menus. The encoding of the tree will show that. Note: Only menu with one sub-level are supported. It is possible to express menu that will show as sub-notebook object, but this code does not support it. */ if (internal->treelevel != -1){ ui_context.treemenu_level = internal->treelevel; }else{ internal->treelevel = ui_context.treemenu_level; } int sublevel = 0; int tblevel[getnb()][2]; const char *tags[getnb()]; { // Parse the menu into sub-menu if needed int level1=0,level2=0; const char *lasttag = ""; for (int i=0; imay_select){ FIELD_MENU *fm = (FIELD_MENU*)f; if (fm->tag[0] > ' ') lasttag = fm->tag; }else{ lasttag = ""; } tags[i] = lasttag; if (f->getnotepadlevel() > 0){ level1 += sublevel; // Trick so the level start // at 0. We must pre-increment // level1 so each sub-item // will share the same level1 // as the ftitle field // (Same trick used in treejump mode) sublevel = 1; tblevel[i][0] = level1; level2 = 0; }else{ tblevel[i][0] = level1; if (sublevel){ level2++; }else{ level1++; } } } } sel = internal->last_visited + 1; while (sel < getnb()){ FIELD *f = getitem(sel); if (f != NULL){ treemenu_pos[ui_context.treemenu_level] = tblevel[sel][0]+1; treemenu_pos[ui_context.treemenu_level+1] = tblevel[sel][1]+1; SSTRING ss; ss.setfromf("%s %s",tags[sel],f->getmenustr()); const char *icon = f->getmenuicon(); if (f->may_select){ //FIELD_MENU *m = (FIELD_MENU*)f; menubox_collect (sublevel,ss.get(),icon); ret = MENU_OK; // Set the level for the next dialog/menu ui_context.treemenu_level += sublevel + 1; break; }else { menubox_collect (0,ss.get(),icon); } } sel++; } internal->last_visited = sel; } if (ret == MENU_ESCAPE) dialog_endlevel(); }else{ if (ui_context.treejump_level > 0){ if (internal->treelevel != -1){ // This menu has been visited once, while in // treemenu_jump mode, so we step out of it (return to main) // treelevel is set to != -1 3 lines below. return MENU_ESCAPE; }else if(ui_context.treemenu_level < ui_context.treejump_level){ internal->treelevel = ui_context.treemenu_level; // Parse the menu into sub-menu if needed int level1=0,level2=0,sublevel=0; int pos1 = treemenu_pos[ui_context.treemenu_level++]; int pos2 = treemenu_pos[ui_context.treemenu_level]; for (int i=0; igetnotepadlevel() > 0){ level1 += sublevel; sublevel = 1; level2 = 0; }else{ if (sublevel){ if (pos1 == level1 && pos2 == level2){ sel = i; break; } level2++; }else{ if (pos1 == level1){ sel = i; break; } level1++; } } } ui_context.treemenu_level += sublevel; if (sel != -1) return MENU_OK; }else{ // We are visiting a menu deeper than the treemenu // By raising the treemenu_level, we are enabling // the uithread again. See uithread(). ui_context.treemenu_level = ui_context.treejump_level + 1; } } ret = DIALOG::editmenu (title,prompt,helpfile,sel,options); } return ret; } /* * Display a menu for choosing among a number of options * Return MENU_OK, MENU_QUIT or MENU_ESCAPE * Depending on the options parameter, may return MENU_INS, MENU_SAVE * MENU_ADD or MENU_DEL. */ MENU_STATUS dialog_menu( const char *title, const char *prompt, HELP_FILE &helpfile, int options, // MENUBUT_ADD | MENUBUT_INS | MENUBUT_DEL | MENUBUT_SAVE int item_no, char **items, int &sel) // Will hold the selection or -1 if escape // It must already contains the initial selection { DIALOG dia; dia.new_menuitems((const char**)items,item_no); return dia.editmenu (title,prompt,helpfile,sel,options); } #ifdef TEST int main (int argc, char *argv[]) { int ret,choice=0; static char *tb[]={ "a", "message", "allo", "alal", "b", "bbbbbb", "bozo", "bzbzbzbz", "c", "coco", "coco", "cocococococ", "d", "dddd", "dozo", "dozozododo", }; init_dialog(); if (argc == 2){ dialog_settimeout(atoi(argv[1]), '\n'); } ret = dialog_menu( "This is a test" ,"Are you\nsure you want to exit" ,"/tmp/dialog.bak" ,MENUBUT_SAVE ,8,tb,choice); ret = dialog_menu( "This is a test" ,"Are you\nsure you want to exit\n" ,"/tmp/dialog.bak" ,MENUBUT_SAVE | MENUBUT_ADD | MENUBUT_DEL ,8,tb,choice); endwin(); printf ("ret = %d choice = %d\n",ret,choice); return 0; } #endif /* Request that modules add various entries to this menu */ PUBLIC void DIALOG_MENU::setmenu (MENU_CONTEXT ctx) { module_setmenu (*this,ctx); } /* Present a Popup menu. title may be NULL. Return MENU_OK if a selection was made. sel contain the entry selected. */ PUBLIC MENU_STATUS DIALOG_MENUPOPUP::editmenu(const char *title, int &sel) { MENU_STATUS ret = MENU_NULL; if (title == NULL) title = ""; if (dialog_mode != DIALOG_GUI || !diajava_menu){ ret = DIALOG_MENU::editmenu (title,"",help_nil,sel,0); }else{ char menuid[10]; extern int uithread_id; sprintf (menuid,"menu-%d",uithread_id); char tmp[1000]; diagui_sendcmd (P_Popupmenu,"%s %s\n",menuid,diagui_quote(title,tmp)); int curlevel = 0; for (int i=0; ipopup_draw (i,curlevel); } for (int i=0; i<=curlevel; i++) diagui_sendcmd (P_End,"\n"); while (1){ SSTRING action,path,menubar; diagui_sync (menuid,path,action,menubar); if (!menubar.is_empty()){ sel = menubar.getval(); if (sel >= 0 && sel