#include #include #include #include "diadef.h" #include "dialog.h" #include "context.h" #include "internal.h" #include "../diajava/proto.h" #include #include "modregister.h" /* Evaluate the size of a text in a string. (number of line, maximum width) The string may contain tabs. Return the number of line */ int dialog_textsize (const char *txt, int *width) { int nbline=1; int maxlen = 0; if (txt != NULL){ const char *lastpt = txt; char *pt = strchr (txt,'\n'); while (pt != NULL){ int len = 0; while (lastpt < pt){ if (*lastpt == '\t'){ if (len % 8 == 0) len++; while (len % 8 != 0) len++; }else{ len++; } lastpt++; } len += 2; nbline++; if (len > maxlen) maxlen = len; lastpt = pt+1; pt = strchr (lastpt,'\n'); } { int lastlen = strlen (lastpt); if (lastlen > maxlen) maxlen = lastlen; } } *width = maxlen; return nbline; } /* Copy a string, folding oversized line in several lines Return the number of lines produced. */ static int dialog_textfold (const char *txt, int maxwidth, SSTRING &newtxt) { int ret = 0; char line[maxwidth+2]; char *pt = line; while (*txt != '\0'){ if (*txt == '\n' || (int)(pt-line) == maxwidth){ *pt++ = '\n'; *pt = '\0'; newtxt.append (line); if (*txt == '\n') txt++; pt = line; ret++; }else{ *pt++ = *txt++; } } if (pt > line){ *pt++ = '\n'; *pt = '\0'; newtxt.append (line); ret++; } return ret; } /* Open a centered window */ WINDOW *dialog_openwin( int height, int width) { WINDOW *dialog=NULL; if (COLS == 0){ fprintf (stderr,"You forgot init_dialog\n"); exit (-1); }else{ /* center dialog box on screen */ int x = (COLS - width)/2; int y = (LINES - height)/2; // Open larger to draw the shadow int height_1 = height+1; if (height_1 > LINES) height_1 = LINES; int width_2 = width+2; if (width_2 > COLS-1) width_2 = COLS-1; dialog = newwin(height_1, width_2, y, x); keypad(dialog, TRUE); } return dialog; } void dialog_draw ( WINDOW *dialog, const char *title, const char *internal_title, const char *intro, int height, int width) { draw_box(dialog, 0, 0, height, width, dialog_attr , border_attr, border_attr_shadow); draw_shadow(dialog, 0, 0, height, width); if (title != NULL) { wattrset(dialog, title_attr); wmove(dialog, 0, (width - strlen(title))/2 - 1); waddch(dialog, ' '); waddstr(dialog, (char*)title); waddch(dialog, ' '); } int posy = 1; if (internal_title[0] != '\0'){ int len = strlen (internal_title); int pos = (width - len)/2; wmove (dialog,1,pos); waddstr (dialog,internal_title); posy = 3; } if (intro != NULL){ while (*intro != '\0'){ char tmp[COLS+1]; char *pt = tmp; while (*intro != '\0' && *intro != '\n'){ *pt++ = *intro++; } *pt = '\0'; if (*intro == '\n') intro++; wmove (dialog,posy++,2); waddstr (dialog,tmp); } } } /* Compute the layout of the dialog based on its content. */ PRIVATE void DIALOG::setup () { int fields_height = 0; internal->nbvisible = 0; if (getnb()>0){ for (int i=0; ivsize; } internal->nbvisible = fields_height; fields_height += 2; } int button_height = 3; int frame_space = 3; int max_prompt = 0; int intro_height= 0; int intro_width = 0; if (!internal->internal_title.is_empty()){ intro_height = 2; intro_width = internal->internal_title.getlen()+4; } if (!internal->intro.is_empty()){ int tmp; const char *introtxt = internal->intro.get(); int tmp_height = dialog_textsize(introtxt,&tmp); tmp += 4; if (dialog_mode == DIALOG_CURSES && tmp >= COLS){ // The introduction is too wide, we fold the long // lines in several lines SSTRING newintro; tmp_height = dialog_textfold (introtxt,COLS-4,newintro); internal->intro.setfrom (newintro); tmp = COLS-1; } if (tmp > intro_width) intro_width = tmp; intro_height += tmp_height; } internal->height = intro_height + frame_space + button_height + fields_height; if (dialog_mode == DIALOG_CURSES){ int maxl = LINES - 1; if (internal->height > maxl){ internal->nbvisible -= internal->height - maxl; internal->height = maxl; }else if (internal->height_hint){ internal->nbvisible = maxl - (intro_height + frame_space + button_height); internal->height = maxl; } } int max_field = 0; internal->all_protected = true; int posy = 0; int offsety = internal->offset; if (offsety > 0){ // Ok, this dialog was scrolled and offsety represent the first // visible field. We must compute the size of all previous fields int size = 0; for (int i=0; ivsize; } offsety = size; } for (int i=0; ireadonly || f->may_select) internal->all_protected = false; char *prompt = f->prompt; int len = strlen(prompt); if (len > max_prompt) max_prompt = len; f->box.y = 3 + intro_height + posy - offsety; posy += f->vsize; if (max_field < f->box.width) max_field = f->box.width; } int data_width = max_field + 6 + max_prompt; if (dialog_mode == DIALOG_CURSES && data_width > COLS){ if (getenv("LINUXCONF_TRANSLATOR")!=NULL){ // Message should not be translated. It is for translator xconf_error ("Dialog too wide:\n" "Max_prompt = %d Max_field =%d\n" "field area will be truncated" ,max_prompt,max_field); } max_field = COLS - (max_prompt + 6); if (max_field < 0){ } for (int j=0; jprompt); //if (len > max_prompt) f->prompt[max_prompt] = '\0'; if (f->box.width > max_field) f->box.width = max_field; } data_width = max_field + 5 + max_prompt; } internal->width = data_width; if (internal->width < intro_width) internal->width = intro_width; int title_len = internal->title.getlen()+4; int button_len = internal->buttons->evalwidth(); // Buttons can scroll if there are too many. int button_y = internal->height - 3; internal->button_height = 3; if (dialog_mode == DIALOG_CURSES){ if (button_len > COLS - 4){ button_len = COLS-5; // Add another button line internal->height += 3; internal->button_height = 6; int maxl = LINES - 1; if (internal->height > maxl){ internal->nbvisible -= internal->height - maxl; internal->height = maxl; button_y = maxl - 6; } } } if (title_len > internal->width) internal->width = title_len; if (button_len > internal->width) internal->width = button_len; internal->buttons->setup (button_y,internal->width); // Try to center the data box if there is no prompt if (data_width < internal->width && max_prompt < 2){ max_prompt += (internal->width - data_width)/2; } for (int i=0; ibox.x = max_prompt + 3; f->box.width = max_field; } internal->max_prompt = max_prompt; internal->max_field = max_field; internal->first_field = 3 + intro_height; } /* Set the offset of the first visible field and ajust the coordinate of all field so they will know where to draw themselve. This is only useful in text mode. It does nothing otherwise. */ PUBLIC void DIALOG::setoffset (int newoff) { FIELD *curpt = getitem(internal->offset); FIELD *newpt = getitem(newoff); if (curpt != NULL && newpt != NULL){ int diff = newpt->box.y - curpt->box.y; internal->offset = newoff; for (int i=0; ibox.y -= diff; } } } /* Return the number of the first visible field (text mode) This function is used along with setoffset to preserve and restore the exact dialog state. */ PUBLIC int DIALOG::getoffset () const { return internal->offset; } static void draw_blank (WINDOW *dialog, int y, int x, int len) { char tmp[len+1]; memset (tmp,' ',len); tmp[len] = '\0'; wmove (dialog,y,x); waddstr (dialog,tmp); } /* Draw all visible field */ PROTECTED void DIALOG::drawf(WINDOW *dialog) { int size = 0; for (int i=0; size < internal->nbvisible; i++){ FIELD *f = getitem(i+internal->offset); if (f == NULL){ // Wipe the prompt and the field content int y = internal->first_field + size; draw_blank (dialog,y,1,internal->max_prompt); draw_blank (dialog,y,internal->max_prompt+3,internal->max_field); size++; }else if (f->vsize > 0){ int nbl = f->vsize; if (size + nbl > internal->nbvisible){ nbl = internal->nbvisible - size; } f->draw (dialog, internal->hoffset,0,nbl-1); f->draw_helpdia (dialog,i); size += f->vsize; } } } /* Send a message to all fields of a dialog */ PRIVATE void DIALOG::processmsg(WINDOW *dialog, FIELD_MSG &msg) { int lastf = getnb(); int last_visible = internal->height-6; for (int i=0; iprocessmsg (dialog,msg ,i >= internal->offset && f->box.y <= last_visible); } } /* Draw the complete dialog */ PRIVATE void DIALOG::draw (WINDOW *dialog) { dialog_draw (dialog,internal->title.get() ,internal->internal_title.get(),internal->intro.get() ,internal->height,internal->width); wattrset(dialog, dialog_attr); if (getnb()>0){ /* Draw the input field box */ FIELD *finfo = getitem(internal->offset); draw_box(dialog, finfo->box.y-1, finfo->box.x-1 , internal->nbvisible+2 , finfo->box.width+2 , inputbox_attr, border_attr_shadow,border_attr); drawf(dialog); } internal->buttons->draw (dialog,internal->button); } PRIVATE void DIALOG::drawarrow_if( WINDOW *win, bool condition, // Should it be drawn bool & flag, // Does the carac is already drawn bool top, // Top or bottom arrow char carac) // Char to print { if (getnb() > 0){ FIELD *f = getitem(internal->offset); int posx = f->box.x + f->box.width/2; int posy = top ? f->box.y - 1 : internal->height - internal->button_height - 2; if (condition){ if (!flag){ flag = true; wmove (win,posy,posx); wattrset(win,inputbox_border_attr); waddch (win,carac); } }else if (flag){ flag = false; wmove (win,posy,posx); wattrset(win,inputbox_border_attr); waddch (win,ACS_HLINE); } } } PRIVATE void DIALOG::dokeyup(int &nof, WINDOW *dialog) { nof--; if (nof < internal->offset){ if (internal->offset > 0){ setoffset(internal->offset -1); drawf(dialog); }else{ nof = 0; } } } PRIVATE void DIALOG::dokeydown(int &nof, WINDOW *dialog) { int last_nof = nof; //int vskip = getitem(nof)->vsize; nof++; while (nof < getnb() && getitem(nof)->vsize == 0) nof++; if (nof < getnb()){ FIELD *f = getitem(nof); while (f->box.y >= internal->height-internal->button_height - 2){ int newoffset = internal->offset+1; while (newoffset < getnb() && getitem(newoffset)->vsize == 0) newoffset++; setoffset (newoffset); drawf(dialog); } }else{ nof = last_nof; } } /* Interpret a cursor key. Return -1 if it not a cursor key */ PROTECTED VIRTUAL int DIALOG::keymove (WINDOW *dialog, int key, int &nof) { int ret = 0; int nextkey = key; switch (key){ case KEY_PPAGE: if (internal->offset == 0){ nof = 0; }else{ int newoff = internal->offset - internal->nbvisible; if (newoff < 0) newoff = 0; nof -= (internal->offset - newoff); setoffset(newoff); drawf(dialog); } nextkey = KEY_UP; break; case KEY_UP: dokeyup(nof,dialog); break; case KEY_NPAGE: { int maxoffset = getnb() - internal->nbvisible; if (maxoffset < 0) maxoffset = 0; if (internal->offset >= maxoffset){ nof = getnb()-1; }else{ int newoff = internal->offset + internal->nbvisible; if (newoff > maxoffset) newoff = maxoffset; nof += (newoff - internal->offset); setoffset(newoff); drawf(dialog); } } nextkey = KEY_DOWN; break; case KEY_DOWN: dokeydown(nof,dialog); break; default: ret = -1; } skipprotect (nof,nextkey,dialog); return ret; } /* Try to position on a selectable or editable field */ PRIVATE void DIALOG::skipprotect (int &nof, int suggestkey, WINDOW *dialog) { if (!internal->all_protected){ int n = getnb(); // We try many time until we are tired of trying or we finally // position on something. for (int i=0; ireadonly && !f->may_select){ if (suggestkey == KEY_UP && nof == 0) suggestkey = KEY_DOWN; if (suggestkey == KEY_DOWN && nof == n-1) suggestkey = KEY_UP; if (suggestkey == KEY_UP){ dokeyup(nof,dialog); }else{ dokeydown(nof,dialog); } }else{ break; } } } } DIALOG_MODE dialog_mode = DIALOG_CURSES; bool dialog_xul = false; static DIALOG_MODE previous_mode = DIALOG_CURSES; int treemenu_pos[20]; // Assume maximum 20 levels of menu /* Set the basic user interface mode. This function is generally called at startup time */ EXPORT DIALOG_MODE dialog_setmode (DIALOG_MODE mode) { DIALOG_MODE old = dialog_mode; previous_mode = old; dialog_mode = mode; return old; } /* Reset the state used to collect the menu tree */ void dialog_resettree() { ui_context.treemenu_level = 0; memset (treemenu_pos,0,sizeof(treemenu_pos)); } /* Fall to the previous level of the DIALOG_TREE mode. Clear the position of the current level to 0. */ void dialog_endlevel() { treemenu_pos[ui_context.treemenu_level] = 0; ui_context.treemenu_level--; } /* Record the target of a tree jump (Jump in the menu tree). If menupath is NULL, this disable the jump */ EXPORT void dialog_jumpto (const char *menupath) { ui_context.treejump_level = 0; ui_context.treemenu_level = 0; if (menupath != NULL){ while (*menupath != '\0'){ treemenu_pos[ui_context.treejump_level++] = atoi(menupath); while (*menupath != '\0' && *menupath != '/') menupath++; if (*menupath == '/') menupath++; } } } static bool jump2help = false; /* The next dialog visited won't be shown. Instead its help screen will be triggered. */ EXPORT void dialog_jump2help(bool mode) { jump2help = mode; } int multi_alloc_gui_id() { static int gui_id_alloc=1; return gui_id_alloc++; } class DIALOGS: public ARRAY{ public: DIALOG *getitem(int no) const { return (DIALOG*)ARRAY::getitem(no); } }; static DIALOGS tbdia; /* Send various listen directive to the GUI to inform about the "listening" state of a dialog. */ void multi_setlistening () { if (diajava_listen){ for (int i=0; iinternal->guidone){ bool listening = dia->internal->listening; if (listening != dia->internal->gui_listen){ SSTRING tmp; if (dia->setguiname(tmp) != NULL){ diagui_sendcmd (P_Listen,"%s %d\n",tmp.get(),listening ? 1 : 0); } dia->internal->gui_listen = listening; } } } } } /* Invalidate all ncurses window so dialog_restart repaint the screen properly. Called by dialog_restart(). */ void multi_touchwins() { for (int i=0; iinternal->cursw; if (win != NULL){ touchwin (win); } } } PUBLIC DIALOG::DIALOG() { internal = new DIALOG_INTERNAL; internal->buttons = new BUTTONS_INFO; internal->button = 0; internal->selected_button = -1; internal->offset = 0; html_adddialog (this); internal->guidone = false; internal->guidone_once = false; internal->all_protected = false; internal->treelevel = -1; internal->last_visited = -1; internal->autonewline = true; internal->height_hint = false; internal->gui_id = multi_alloc_gui_id(); internal->thread_id = uithread_id; internal->diatype = DIATYPE_STD; internal->cursw = NULL; internal->context_wasset = false; internal->waitprivmsg.neverdelete(); internal->button_on_side = false; internal->hoffset = 0; internal->registry_id = NULL; internal->subdia = NULL; internal->last_nof = -1; // For a curfield command in GUI internal->listening = false; internal->gui_listen = true; // A dialog is created in listen mode // not in gray out mode. internal->insert_pos = -1; internal->sel_column = -1; internal->gui_getdone = false; tbdia.neverdelete(); tbdia.add (this); help_context_setalthelp(this); } PUBLIC DIALOG::~DIALOG() { tbdia.remove (this); html_forgetdialog (this); guidelete(); delete internal->buttons; delete internal; } /* Record a unique string to identify the dialog. Combining this string with a prompt, we can uniquely identify a field of a given dialog. We use this only when two or more dialogs are sharing the same fields and we want to address those field using the registry. The string content does not matter much. Only its address matters. It is a static string. */ PUBLIC void DIALOG::set_registry_id(const char *id) { internal->registry_id = id; } PUBLIC void DIALOG::set_selected_button (int button) { internal->selected_button = button; } PUBLIC void DIALOG::remove_all() { delete internal->subdia; internal->subdia = NULL; ARRAY::remove_all(); reset_guidone(); internal->buttons->delbutinfo(); } /* Record the GUI context in which this dialog will be inserted */ PUBLIC void DIALOG::setcontext (const char *s) { if (s != NULL && diajava_context){ internal->context_wasset = true; internal->context.setfrom (s); } } /* For the GUI to place the dialog buttons on the right side */ PUBLIC void DIALOG::set_button_on_side() { internal->button_on_side = true; } PROTECTED void DIALOG::add (FIELD *f) { reset_guidone(); if (internal->insert_pos != -1){ ARRAY::insert (internal->insert_pos++,f); }else{ ARRAY::add (f); } } /* Specify the position of the next field added to the dialog. You call this function and then newf_something() and the field will be inserted at the position you specified. Every field added from now on will be inserted one after the other at this position. You call the function again and set the position to -1 to disable to behavior. */ PUBLIC void DIALOG::set_nextfield (int no) { internal->insert_pos = no; } PUBLIC int DIALOG::remove_del (FIELD *f) { reset_guidone(); if (internal->subdia != NULL) internal->subdia->remove_del(f); return ARRAY::remove_del (f); } /* Set the DIALOG GUI state to "not done". This means that the dialog will have to be transmitted again to the GUI front-end. This is generally called when something important has changed in the dialog (a new field for example) and it can't be fixed using the GUI protocol. So the dialog will be deleted on the GUI side and redone. */ PUBLIC void DIALOG::reset_guidone() { guidelete(); internal->guidone = false; } PUBLIC int DIALOG::remove_del (int no) { int ret = -1; FIELD *f = getitem(no); if (f != NULL) ret = remove_del (f); return ret; } /* Remove the last entries of the DIALOG, after the "cut" first items */ PUBLIC void DIALOG::remove_last(int cut) { while (getnb() > cut) remove_del(getnb()-1); } /* Present the dialog. Return immedialy */ PUBLIC void DIALOG::show( const char *_title, // Main title const char *_intro, // Mini help describing the purpose // of the dialog HELP_FILE &helpfile, // Help text in a file or NULL int &nof, // Start editing on which field // Will contain the current field. int but_options) // MENUBUT_xxxxx { if (dialog_mode == DIALOG_SILENT || dialog_mode == DIALOG_TREE){ // dialog_endlevel(); return; } dialog_clearinit(); internal->title.setfrom (ui_context.title_prefix); internal->title.append (_title); internal->intro.setfrom (_intro); internal->buttons->set (but_options,helpfile); fixwidth1(); setup (); if (!jump2help){ if (dialog_mode == DIALOG_HTML){ // showhtml (nof); }else if (dialog_mode == DIALOG_GUI){ if (getenv("SHOWXUL")!=NULL && getuid()==0){ // To help start this project. Not functional yet showxul (nof,but_options); } showgui (nof,but_options); }else{ showterm (nof,but_options); } } } /* Multiple field dialog. All field are shown one under each others */ PUBLIC MENU_STATUS DIALOG::edit( const char *_title, // Main title const char *_intro, // Mini help describing the purpose // of the dialog HELP_FILE &helpfile, // Help text in a file or NULL int &nof, // Start editing on which field // Will contain the current field. int but_options) // MENUBUT_xxxxx { if (dialog_mode == DIALOG_GET){ int n = getnb(); for (int i=0;iregistry_id); } return MENU_ESCAPE; } if (dialog_mode == DIALOG_SET){ int n = getnb(); for (int i=0;iregistry_id); } bool allok = true; for (int i=0; ipost_validate()==-1){ allok = false; break; } } if (allok){ save(); // We switch back to the previous user interface mode. // This allow the caller to present some error messages // if need. dialog_mode = previous_mode; return MENU_ACCEPT; }else{ return MENU_CANCEL; } } if (dialog_mode == DIALOG_SILENT || dialog_mode == DIALOG_TREE){ // dialog_endlevel(); return MENU_ESCAPE; } { // Signal which dialog we are currently editing // The first field holding a variable (editable) allows // the registry to notice which dialog is currently edited. int n = getnb(); for (int i=0;iregistry_id) != -1) break; } } MENU_STATUS ret = MENU_NULL; show (_title,_intro,helpfile,nof,but_options); if (jump2help){ jump2help = false; ret = MENU_ESCAPE; internal->buttons->help(NULL); }else{ if (ui_context.treemenu_level > 0){ // Enable uithread again, see uithread() ui_context.treemenu_level = ui_context.treejump_level + 1; } internal->listening = true; while (1){ // Button currently selected internal->button = internal->selected_button; internal->selected_button = -1; if (getnb()==0){ if (internal->button == -1) internal->button = 0; } if (dialog_mode == DIALOG_HTML){ ret = edithtml (nof); }else if (dialog_mode == DIALOG_GUI){ ret = editgui (nof,but_options); }else{ ret = editterm (nof,but_options); } if (ret != MENU_ESCAPE && ret != MENU_QUIT && ret != MENU_CANCEL){ int n = getnb(); int i; for (i=0; ipost_validate()==-1){ nof = i; break; } } if (i==n){ if (dialog_mode == DIALOG_CURSES){ delwin(internal->cursw); internal->cursw = NULL; } break; }else{ show (_title,_intro,helpfile,nof,but_options); } }else{ break; } } internal->listening = false; if (ret == MENU_ACCEPT){ /* Save the content of the edit field into the buffer */ save(); } } return ret; } /* Multiple field dialog. All field are shown one under each others */ PUBLIC MENU_STATUS DIALOG::edit( const char *_title, // Main title const char *intro, // Mini help describing the purpose // of the dialog HELP_FILE &helpfile, // Help text in a file or NULL int &nof) // Start editing on which field { return edit (_title,intro,helpfile,nof ,MENUBUT_ACCEPT|MENUBUT_CANCEL); } /* Multiple field dialog. All field are shown one under each others */ PUBLIC MENU_STATUS DIALOG::edit( const char *_title, // Main title const char *intro, // Mini help describing the purpose // of the dialog HELP_FILE &helpfile) // Help text in a file or NULL { int nof = 0; return edit (_title,intro,helpfile,nof ,MENUBUT_ACCEPT|MENUBUT_CANCEL); } #ifdef TEST #include "translat.h" int main (int argc, char *argv[]) { translat_load ("/tmp","linuxconf-msg-" REVISION ".eng"); init_dialog(); DIALOG dia; char str1[80]; str1[0] = '\0'; char str2[100]; strcpy (str2,"Hello"); SSTRING pass; char addr[10]; addr[0] = '\0'; dia.newf_str ("Question 1",str1,sizeof(str1)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); char chk = 0; dia.newf_chk ("Option",chk,"more option"); dia.newf_pass ("Password",pass); dia.newf_str ("Address",addr,sizeof(addr)-1); SSTRING addr2; FIELD_COMBO *comb = dia.newf_combo ("Combo",addr2); comb->addopt("opt 1"); comb->addopt("opt 2"); comb->addopt("opt 3"); comb->addopt("opt 4"); SSTRING addr3; dia.newf_str ("sstring",addr3); dia.edit ("This is a test",NULL,NULL,0); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dia.newf_str ("Other",str2,sizeof(str2)-1); dialog_settimeout (15,MENU_ESCAPE,1); dia.edit ("This is a test" ,"Please you must reenter the\n" "value\n" "or accept it\n" ,"helpfile" ,2); endwin(); printf ("Question 1 :%s:\n",str1); printf ("Other :%s:\n",str2); printf ("Option :%s:\n",chk ? "Selected" : "Not selected"); printf ("Password :%s:\n",pass.get()); printf ("Address :%s:\n",addr); printf ("Address 2 :%s:\n",addr2.get()); printf ("Address 3 :%s:\n",addr3.get()); return 0; } #endif