/* The treemenu module is a prototype. Maybe this stuff will be merged in the core as it may become the standard menuing strategy. */ #pragma implementation #include #include #include #include #include #include #include #include #include "treemenu.h" #include "treemenu.m" #include "../../diajava/diajava.h" #include "../../diajava/proto.h" #include "../../main/main.h" #include extern HELP_FILE help_mainintro; MODULE_DEFINE_VERSION(treemenu); static const char *tb_about[]={"SSTRINGS*",NULL}; static MESSAGE_DEF msg_about ("about",tb_about); static const char *tb_tree_is_up[]={"guictx-rightside","guictx-logs",NULL}; static MESSAGE_DEF msg_tree_is_up ("tree_is_up",tb_tree_is_up); static const char *tb_menubar[]={"code-to-process",NULL}; static MESSAGE_DEF msg_menubar ("menubar",tb_menubar); static MESSAGE_DEF msg_build_menubar ("build-menubar"); static MESSAGE_DEF msg_build_helpmenu ("build-helpmenu"); static const char *tb_status_win[]={"guictx-rightside",NULL}; static MESSAGE_DEF msg_status_win ("status_win",tb_status_win); PUBLIC MODULE_treemenu::MODULE_treemenu() : LINUXCONF_MODULE("treemenu") { linuxconf_loadmsg ("treemenu",PACKAGE_REV); } static void treemenu_jump(const char *key) { dialog_jumpto (key); linuxconf_main(true); dialog_jumpto (NULL); } static void ft (void *p) { const char *key = (const char *)p; static SSTRINGS tb; /* #specification: treemenu / double trigger The treemenu module avoids trigerring the same dialog twice. It remembers in a table the "key" used to locate the dialog associated with a tree option. Searching the key in a table tells the treemenu module that a dialog was already triggered. This code should be enhanced at some point to deal with focus management, bringing the current dialog associated with this thread to the forefront. */ if (tb.lookup(key)==-1){ SSTRING *s = new SSTRING (key); tb.add (s); treemenu_jump (key); tb.remove_del (s); } free (p); } static const char K_TREESTATE[]="state"; static const char K_OFFOPEN[]="offopen"; static const char K_STATESAVE[]="statesave"; static const char K_TREEPOSITION[]="treeposition"; static const char K_TREEOFFSET[]="treeoffset"; /* Ananlyse the treemenu and assign each item to a level and tell if it is a terminal item or a sub-menu. Return the maximum width of the tree (in character). */ static int treemenu_setup ( SSTRINGS &keys, SSTRINGS &titles, bool statopen[], bool terminal[], int level[], int offopen[], int &offset, int &nof) // Cursor position in the list { int n = keys.getnb(); memset (statopen,0,n*sizeof(bool)); memset (terminal,0,n*sizeof(bool)); memset (level,0,n*sizeof(int)); memset (offopen,0,n*sizeof(int)); nof = 0; offset = 0; terminal[n-1] = true; for (int i=0; iget(); while (*pt != '\0'){ if (*pt == '/') lev++; pt++; } level[i] = lev; } int maxw = 0; for (int i=0; i= level[i+1]){ terminal[i] = true; } int len = titles.getitem(i)->getlen() + (lev-1)*2; if (len > maxw) maxw = len; } { /* #Specification: treemenu / state / session The state of the treemenu is preserved in /var/run/treemenu.cache. This file is normally used to keep the structure of the treemenu since it takes few seconds to walk linuxconf. Given that the file is flushed each time a configuration change occurs (A new module is installed, a new version of linuxconf), it makes sens to store this state information there too. It will be preserved until a new menu is built. The information is stored using the real user id of the user. This means that multiple users are keeping their own state concurently. */ extern CONFIG_FILE f_treemenu; CONFDB db (f_treemenu); char uid[10]; sprintf (uid,"%d",getuid()); if (db.getvalnum(K_STATESAVE,uid,0)){ SSTRINGS states,offopens; nof = db.getvalnum(K_TREEPOSITION,uid,0); offset = db.getvalnum(K_TREEOFFSET,uid,0); db.getall (K_TREESTATE,uid,states,false); db.getall (K_OFFOPEN,uid,offopens,false); for (int i=0; iget(); int pos = keys.lookup (s); if (pos != -1){ statopen[pos] = true; } } for (int i=0; iget(); int nof,off; if (sscanf(s,"%d %d",&nof,&off)==2 && nof >= 0 && nof < n){ offopen[nof] = off; } } }else{ /* #Specification: treemenu / initial state The tree menu first appears to the user half opened. When the user quits, the state is saved and reused for the next sessions. This behavior is used to give a chance for the user to realise that Linuxconf has a very large menu tree and it is worth investigating. Originally, the menu was showned fully opened, but this was annoying. You really had to close most branches to find your way. So this was changed to show only the 3 first levels. */ for (int i=0; iget(); dia.gui_passthrough (P_Label,"%s\n",diagui_quote(s,tmp)); dia.newline(); } int nof = 0; dia.edit (MSG_U(T_ABOUT,"About Linuxconf"),"",help_nil,nof,MENUBUT_CANCEL); } static void treemenu_usage(void *) { linuxconf_usage(); } static const char K_TREEMENU[]="treemenu"; static const char K_TEXTMODE[]="textmode"; static const char K_GUIMODE[]="guimode"; static bool treemenu_guirunning = false; static void treemenu_maingui(bool dontcheck) { dialog_noquitbutton(); SSTRINGS keys; SSTRINGS titles; SSTRINGS icons; linuxconf_gettree(keys,titles,icons); char title[80],sidetitle[80]; linuxconf_setmaintitle(title,sidetitle); if (diajava_context){ /* We have to support both front-end (old and new) The old gnome-linuxconf expects a single treemenu and does not support context. We are using diajava_context to tell apart old and new front-end. With the new front-end, we create a more complex layout. On the left, we create a "leftside" form that will accept 2 tree organised in a notebook. On the right, we create an empty book and set the default context on it. All new dialog will be reparent in this book. At the bottom, we create another book. This will be used by logs which will register various pages there. Because of the old and new, you will find a few "if (diajava_context)" here and there. */ treemenu_guirunning = true; diagui_sendcmd (P_MainForm,"tree-0 \"%s\" $htrigger=2000\n",title); if (diajava_menu){ diagui_sendcmd (P_Menubar,"\n"); diagui_sendcmd (P_Submenu,"%s\n",MSG_U(M_FILE,"File")); diagui_sendcmd (P_Menuentry,"8 \"%s\"\n",MSG_U(M_STATUS,"Status window")); diagui_sendcmd (P_Menuentry,"2 \"%s\"\n",MSG_R(B_ACTIVATE)); diagui_sendcmd (P_Menuentry,"1 \"%s\"\n",MSG_R(B_QUIT)); diagui_sendcmd (P_Menuentry,"4 \"%s\"\n" ,MSG_U(B_FASTQUIT,"Quit (no check)")); diagui_sendcmd (P_End,"\n"); module_sendmessage (msg_build_menubar,0,NULL); diagui_sendcmd (P_Prefsubmenu,"\n"); diagui_sendcmd (P_Submenu,"\"%s\" $right=1\n",MSG_R(B_HELP)); diagui_sendcmd (P_Menuentry,"5 \"%s\"\n" ,MSG_U(M_ABOUT,"About Linuxconf")); diagui_sendcmd (P_Menuentry,"3 \"%s\"\n" ,MSG_U(M_INTRO,"Introduction")); diagui_sendcmd (P_Menuentry,"7 \"%s\"\n" ,MSG_U(M_COMMANDLINE,"Command line options")); diagui_sendcmd (P_Menuentry,"6 \"%s\"\n" ,MSG_U(M_TOPIC,"On topic")); diagui_sendcmd (P_Menuentry,"0 -\n"); module_sendmessage(msg_build_helpmenu,0,NULL); diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_End,"\n"); } diagui_sendcmd (P_Form,"leftside $hexpand=0\n"); diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_Form,"rightside\n"); diagui_sendcmd (P_Book,"book\n"); diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_Newline,"\n"); diagui_sendcmd (P_Book,"logs\n"); diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_Dispolast,"l 2 t 1\n"); diagui_sendcmd (P_Newline,"\n"); //diagui_sendcmd (P_End,"\n"); // The End is done after the tree is layout // to avoid showing a small window // first diagui_setdefaultctx ("tree-0.rightside.book"); } extern int uithread_id; char dianame[10]; sprintf (dianame,"tree-%d",uithread_id); const char *leftside = "tree-0.leftside"; if (diajava_context) diagui_sendcmd (P_Setcontext,"%s\n",leftside); diagui_sendcmd (P_MainForm,"%s \"%s\"\n",dianame,title); int n=keys.getnb(); bool statopen[n],terminal[n]; int level[n]; { int nof,offset,offopen[n]; treemenu_setup (keys,titles,statopen,terminal,level,offopen,offset,nof); } int last_level = 1; int notree = 0; if (!diajava_nogfx){ for (int i=0; iis_empty()) { char name_sent[PATH_MAX]; diagui_sendminixpm (icon->get(),name_sent); icon->setfrom(name_sent); } } } if (diajava_context){ diagui_sendcmd (P_Book,"%s\n",dianame); }else{ // We still support old front-end which are expecting a single tree diagui_sendcmd (P_Treemenu,"%s.tree1.tree\n",dianame); } for (int i=0; i level[i] && j > 1; j--){ diagui_sendcmd (P_End,"\n"); } last_level = level[i]; char tmp[1000]; const char *title = diagui_quote(titles.getitem(i)->get(),tmp); const char *icon = icons.getitem(i)->get(); if (diajava_context && last_level == 1){ if (i != 0){ diagui_sendcmd (P_End,"\n"); } diagui_sendcmd (P_Page,"page %s\n",title); diagui_sendcmd (P_Treemenu,"tree-%d\n",notree++); }else{ if (icon[0] == '\0' || diajava_nogfx){ if (terminal[i]){ diagui_sendcmd (P_Treeelem,"\"\" %s\n" ,title); }else{ diagui_sendcmd (P_Treesub,"%d \"\" %s\n" ,statopen[i] ,title); } }else{ if (terminal[i]){ diagui_sendcmd (P_Treeelem,"%s %s\n" ,icon ,title); }else{ diagui_sendcmd (P_Treesub,"%d %s %s\n" ,statopen[i] ,icon ,title); } } } } for (int j=last_level; j > 1; j--){ diagui_sendcmd (P_End,"\n"); } if (diajava_context){ diagui_sendcmd (P_End,"\n"); } diagui_sendcmd (P_End,"\n"); diagui_sendcmd (P_Newline,"\n"); if (!diajava_menu){ diagui_sendcmd (P_Formbutton,"buttons\n"); diagui_sendcmd (P_Button,"B1 1 %s\n",MSG_U(B_QUIT,"Quit")); if (!dontcheck){ diagui_sendcmd (P_Button,"B2 1 %s\n",MSG_U(B_ACTIVATE,"Act/Changes")); } diagui_sendcmd (P_Button,"B3 0 %s\n",MSG_U(B_HELP,"Help")); diagui_sendcmd (P_End,"\n"); } diagui_sendcmd (P_End,"\n"); static const char *args[]={ "tree-0.rightside.book", "tree-0.logs", NULL }; if (diajava_context){ diagui_sendcmd (P_End,"\n"); // By using nopopup=1, we avoid showing the tree alone // and later growing the window. This way, the main window // appears immediatly with the proper size. // Unless we do that, the tree appears in a corner of the screen // and then it grows outseide of the screen. diagui_sendcmd (P_End,"$nopopup=1\n"); module_sendmessage (msg_tree_is_up,2,args); uithread_yield(); // If no module inserts any dialog inside the framework // then nothing show because of the nopopup=1 diagui_sendcmd (P_Show,"tree-0 1\n"); } bool willjump2help = false; while (1){ SSTRING action,path,menubar; int code = diagui_sync (dianame,path,action,menubar); // fprintf (stderr,"dianame :%s: :%s: :%s: :%s:\n",dianame,path.get(),action.get(),menubar.get()); if (!menubar.is_empty()){ code = menubar.getval(); } if (code == 1 || code == 99 || code == 4){ SSTRINGS states; for (int t=0; t= 100){ SSTRING s; s.setfromf ("%d",code); const char *tbcode[]={ s.get() }; module_sendmessage (msg_menubar,1,tbcode); }else{ if (action.is_empty()) continue; if (diajava_context){ // Rebuild the absolute tree action using the tree id const char *pt = path.get(); pt += strlen(pt)-1; action.setfromf("%s/%s",pt,action.get()); } dialog_jump2help(willjump2help); willjump2help = false; uithread (ft,strdup(action.get())); } } } static void treemenu_maintext(bool dontcheck) { HELP_CONTEXT h2 (MSG_U(T_TREENAVIG,"treemenu key binding"),"treemenu","keybinding"); SSTRINGS keys; SSTRINGS titles; SSTRINGS icons; linuxconf_gettree(keys,titles,icons); int nof = 0; int n = keys.getnb(); bool statopen[n],terminal[n]; int level[n]; int lastpos[n]; // Remember the last relative position // to undo keyleft with keyright memset (lastpos,0,sizeof(lastpos)); int offset; int maxw = treemenu_setup (keys,titles,statopen,terminal,level ,lastpos,offset,nof); DIALOG dia; dia.waitfor (dialog_controlc); dia.waitfor (dialog_keyleft); dia.waitfor (dialog_keyright); int butopt = 0; if (!dontcheck){ butopt = MENUBUT_USR1; } while (1){ dia.remove_all(); int lookup[n]; int nbshow = 0; int visible_level = 1; for (int i=0; iget()); dia.new_menuitem (term ? "" : openchar,tmp); lookup[nbshow++] = i; } } if (offset != -1){ // Restore the state from the previous sesssion dia.setoffset (offset); offset = -1; } dia.setbutinfo (MENU_USR1,MSG_R(B_ACTIVATE),MSG_R(B_ACTIVATE)); // Make sure the size is always the same when the tree // is expanded or collapsed. dia.setheight_hint(); char title[80],sidetitle[80]; linuxconf_setmaintitle(title,sidetitle); SSTRING profile; profile.setfromf (MSG_U(I_PROFILE,"(Profile %s)"),confver_getcur()); MENU_STATUS code = dia.editmenu (title ,profile.get() ,help_mainintro,nof,butopt); if (code == MENU_QUIT || code == MENU_ESCAPE){ if (dontcheck || netconf_checkupdate(true)!=2) break; }else if (code == MENU_MESSAGE){ if (dialog_testmessage (dialog_controlc)){ break; }else if (dialog_testmessage(dialog_keyleft)){ // We close the current branch even if we are not // on the proper node if (nof >= 0 && nof < nbshow){ int oldnof = nof; int lev = level[lookup[nof]]; while (nof > 0 && level[lookup[nof]] >= lev){ nof--; } int no = lookup[nof]; statopen[no] = !statopen[no]; lastpos[no] = oldnof - nof; } }else if (dialog_testmessage(dialog_keyright)){ // We open the branch and try to reposition to undo // the keyleft if (nof >= 0 && nof < nbshow){ int no = lookup[nof]; if (!terminal[no] && !statopen[no]){ statopen[no] = true; nof += lastpos[no]; } } } }else if (code == MENU_USR1){ treemenu_checkupdate(); }else if (nof >= 0 && nof < nbshow){ int no = lookup[nof]; if (terminal[no]){ // Terminal option, let linuxconf pops the dialog const char *key = keys.getitem(no)->get(); treemenu_jump (key); }else{ statopen[no] = !statopen[no]; } } } { SSTRINGS states; // Opened branches SSTRINGS offopens; // Offset in branches for (int i=0; iget())); } if (lastpos[i] != 0){ SSTRING *s = new SSTRING; s->setfromf ("%d %d",i,lastpos[i]); offopens.add (s); } } treemenu_savestate(states,&offopens,dia.getoffset(),nof); } } static int treemenu_main(bool dontcheck) { int ret = LNCF_NOT_APPLICABLE; char textmode = linuxconf_getvalnum (K_TREEMENU,K_TEXTMODE,1); char guimode = linuxconf_getvalnum (K_TREEMENU,K_GUIMODE,1); dialog_clear(); HELP_CONTEXT h1 (MSG_U(T_ABOUTTREE,"About module treemenu"),"treemenu","about_treemenu"); if (dialog_mode == DIALOG_GUI && diajava_treemenu && guimode){ treemenu_maingui(dontcheck); ret = 0; }else if (dialog_mode == DIALOG_CURSES && textmode){ treemenu_maintext(dontcheck); ret = 0; } return ret; } PUBLIC int MODULE_treemenu::dohtml (const char *key) { int ret = LNCF_NOT_APPLICABLE; if (strcmp(key,"treemenu")==0 && diajava_treemenu){ treemenu_main(true); ret = 0; } return ret; } static void usage() { xconf_error (MSG_U(T_USAGE ,"Module treemenu\n" "linuxconf --modulemain treemenu [ specific options ]\n" "\n") ); } PUBLIC int MODULE_treemenu::message ( const char *msg, int argc, const char *argv[]) { int ret = LNCF_NOT_APPLICABLE; static bool inside = false; if (strcmp(msg,"treemenu_guirunning")==0){ ret = treemenu_guirunning ? 0 : -1; }else if (!inside){ inside = true; if (strcmp(msg,"mainmenu")==0 && dialog_mode != DIALOG_HTML){ dialog_clear(); if (diajava_treemenu){ bool dontcheck = argc == 1 && strcmp(argv[0],"1")==0; ret = treemenu_main(dontcheck); } } inside = false; } return ret; } PUBLIC int MODULE_treemenu::execmain (int argc , char *argv[], bool) { int ret = LNCF_NOT_APPLICABLE; const char *pt = strrchr(argv[0],'/'); if (pt != NULL){ pt++; }else{ pt = argv[0]; } if (strcmp(pt,"treemenu")==0){ ret = -1; if (argc == 1){ treemenu_main(false); }else{ // ### Add some option parsing for the module ::usage(); } } return ret; } static MODULE_treemenu treemenu; class TREEMENU_COMNG: public USERACCT_COMNG{ char textmode; char guimode; /*~PROTOBEG~ TREEMENU_COMNG */ public: TREEMENU_COMNG (DICTIONARY&_dict); int save (PRIVILEGE *priv); void setupdia (DIALOG&dia); int validate (DIALOG&, int &nof); /*~PROTOEND~ TREEMENU_COMNG */ }; PUBLIC TREEMENU_COMNG::TREEMENU_COMNG( DICTIONARY &_dict) : USERACCT_COMNG (_dict) { textmode = linuxconf_getvalnum (K_TREEMENU,K_TEXTMODE,1); guimode = linuxconf_getvalnum (K_TREEMENU,K_GUIMODE,1); } PUBLIC void TREEMENU_COMNG::setupdia ( DIALOG &dia) { dia.newf_title (MSG_U(T_MODTREEMENU,"Module treemenu"),1,"",MSG_R(T_MODTREEMENU)); dia.newf_chk ("",textmode,MSG_U(I_TEXTMODE,"Enable treemenu in text mode")); dia.newf_chk ("",guimode,MSG_U(I_GUIMODE,"Enable treemenu in graphic mode")); } PUBLIC int TREEMENU_COMNG::save( PRIVILEGE *priv) { linuxconf_replace (K_TREEMENU,K_TEXTMODE,textmode); linuxconf_replace (K_TREEMENU,K_GUIMODE,guimode); return 0; } PUBLIC int TREEMENU_COMNG::validate( DIALOG &, int &nof) { return 0; } static USERACCT_COMNG *TREEMENU_newcomng( const char *key, DICTIONARY &dict) { USERACCT_COMNG *ret = NULL; if (strcmp(key,"features")==0){ ret = new TREEMENU_COMNG (dict); } return ret; } static REGISTER_USERACCT_COMNG treemenu_comng (TREEMENU_newcomng);