#include #include #include #include #include #include "tlmpdia.h" #include "tlmpdia.m" #include #include using namespace std; void _F_editmenu::exec(const char *, int) { } void _F_editmenu::execthread(const char *, int) { } struct EDITMENU_PRIVATE { SSTRINGS actives; // Which menu entries are already active const char *key; int sel; PRIVATE_MESSAGE msg; int nbthread; }; static void ft(void *p) { _F_editmenu *c = (_F_editmenu*)p; EDITMENU_PRIVATE *priv = c->priv; const char *key = priv->key; // Make sure we are not reentering the same option twice if (priv->actives.lookup(key)==-1){ SSTRING *s = new SSTRING(key); priv->actives.add (s); c->execthread (key,priv->sel); priv->actives.remove_del(s); } priv->nbthread--; dialog_sendmessage (priv->msg); } void editmenu ( _F_editmenu &c, const char *title, const char *intro, HELP_FILE &help, const char *menuopt[]) { EDITMENU_PRIVATE priv; c.priv = &priv; priv.nbthread = 0; priv.key = NULL; DIALOG_MENU dia; dia.new_menuitems (menuopt); int nof = 0; if (title == NULL) title = MSG_U(T_NOTITLE,"Untitled"); while (1){ MENU_STATUS code = dia.editmenu (title,intro,help,nof,0); if (code == MENU_QUIT || code == MENU_ESCAPE){ break; }else if (code == MENU_OK){ priv.sel = nof; priv.key = dia.getmenustr(nof); c.exec (priv.key,nof); if (dialog_mode == DIALOG_GUI){ uithread (ft,(void*)&c); priv.nbthread++; }else{ c.execthread (priv.key,nof); } } } if (priv.nbthread > 0){ // The user has quitted from the menu // or we are in treemenu mode and we are escaping away // back to the root of the application. // But we still have some threads alive and they are using // the key parameter passed to them. So we have to loop // until all threads have ended. dia.hide(); // Unfortunatly, we can call dia.editmenu() because in treemenu // mode, it returns immediatly with MENU_ESCAPE without processing // any messages. In fact, in treemenu mode, it returns immediatly // without allowing other threads to run (Other thread are only // allowed to run when this thread is in the wait loop in diagui.cc // So we have to wait anyway while (priv.nbthread > 0){ dialog_waitformessage(priv.msg); } } } class SORT_RECORD: public ARRAY_OBJ{ public: char *s1; char *s2; char *dcs; const char *sortpt1; // Pointer inside the record where the key // is located const char *sortpt2; // Extra columns to sort int pos; // Remember the original order of the record bool numeric; int val; SORT_RECORD( const char *_s1, const char *_s2, int column, int position, bool _numeric, const char *_dcs) { s1 = strdup(_s1); s2 = strdup(_s2); dcs = _dcs != NULL ? strdup(_dcs) : NULL; pos = position; numeric = _numeric; if (column == 0){ sortpt1 = s1; sortpt2 = s2; }else{ column--; sortpt1 = s2; sortpt2 = ""; while (column > 0){ while (*sortpt1 != '\0' && *sortpt1 != '\t') sortpt1++; if (*sortpt1 == '\t') sortpt1++; column--; } } val = atoi(sortpt1); } ~SORT_RECORD() { free (s1); free (s2); free (dcs); } }; class SORT_RECORDS: public ARRAY_OBJS{ public: int cnvpos (int pos){ if (size() > 0){ SORT_RECORD *s = getitem(pos); pos = s->pos; } return pos; } }; static int cmp_asc (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { const SORT_RECORD *s1 = (const SORT_RECORD*)o1; const SORT_RECORD *s2 = (const SORT_RECORD*)o2; int ret; if (s1->numeric){ ret = s1->val - s2->val; }else{ ret = strcasecmp(s1->sortpt1,s2->sortpt1); if (ret == 0) ret = strcasecmp(s1->sortpt2,s2->sortpt2); } return ret; } enum DIALOG_LOAD { LOAD_REAL, LOAD_TEST}; struct EDITRECORDS_PRIVATE { DIALOG *dia; DIALOG_RECORDS *diarec; FIELD_CLIST *clist; PRIVATE_MESSAGE listmsg; int buttons; int nbitems; bool loadedonce; // We have executed head() and load() once bool mustexit; // We must terminate edition int nof; // Current item or current field int listsel; // In GUI mode, current item // Extra buttons defined using DIALOG::new_button and new_button_icon PRIVATE_MESSAGE tbmsg[20]; int tbid[20]; FIELD_BUTTON_ICON *tbicon[20]; FIELD_BUTTON_TEXT *tbtext[20]; int nbbut; PRIVATE_MESSAGE helpmsg; bool help_button; int vsize; SSTRING hdispo; char *dcs; // Display context for the next records const char *tagdcs; // Display context for marked records map lookup; map lookup_str; struct { int nbcol; int column; SORT_ORDER order; bool active; char *policy; // How to sort the various columns // Used also as a flag to mean, record // and then sort after functag load has // been called. SORT_RECORDS records; }sort; FRAMEWORK_MSGS *fmsgs; EDITRECORDS_PRIVATE(){ nbbut = 0; memset (tbicon,0,sizeof(tbicon)); memset (tbtext,0,sizeof(tbtext)); vsize = 15; sort.order = SORT_NONE; sort.active = false; sort.column = 0; sort.nbcol = 0; sort.policy = NULL; dcs = NULL; const char *font = guiid_setfont (12,GFONT_ID_DEFAULT,GFONT_STYLE_DEFAULT,GFONT_WEIGHT_BOLD,false); tagdcs = guiid_setdc (font,NULL,NULL); fmsgs = NULL; } ~EDITRECORDS_PRIVATE(){ free (sort.policy); free (dcs); } }; /* Generally called from editone to request editrecords to teminate */ void _F_editrecords::endedit() { priv->mustexit = true; } /* Set the current field */ void _F_editrecords::setcursor(int no) { priv->nof = no; } /* Set the number of visible row */ void _F_editrecords::setvsize (int n) { priv->vsize = n; } void _F_editrecords::setkeyformat (HTML_KEY_TYPE key_type) { if (priv->diarec != NULL) priv->diarec->setkeyformat (key_type); } /* Set the horizontal disposition of the columns. l for left, c for center, r for right. One letter per columns. Default is left align for the unspecified columns. */ void _F_editrecords::sethdispo (const char *hdisp) { priv->hdispo.setfrom (hdisp); if (priv->clist != NULL) priv->clist->sethdispo (hdisp); } void _F_editrecords::new_menuitem (const char *s1, const char *s2) { int noitem = priv->nbitems++; if (priv->sort.policy != NULL && priv->sort.order != SORT_NONE){ int column = priv->sort.column; priv->sort.records.add (new SORT_RECORD(s1,s2 ,column,noitem ,priv->sort.policy[column] == 'n',priv->dcs)); }else if (priv->clist != NULL){ priv->clist->setrecordf (noitem,"%s\t%s",s1,s2); }else{ priv->dia->set_menuitem (noitem,s1,s2); } priv->lookup[noitem] = noitem; if (priv->dcs != NULL) setnextdcs (NULL); } void _F_editrecords::new_menuitemf (const char *s1, const char *s2, ...) { va_list list; va_start (list,s2); char tmp[1000]; vsnprintf (tmp,sizeof(tmp)-1,s2,list); va_end (list); new_menuitem (s1,tmp); } void _F_editrecords::set_lookup (int no) { int noitem = priv->nbitems-1; priv->lookup[noitem] = no; } void _F_editrecords::set_lookup (const char *key) { int noitem = priv->nbitems-1; priv->lookup_str[noitem] = key; } const char *_F_editrecords::get_lookup(int no) { return priv->lookup_str[no].c_str(); } void _F_editrecords::newf_head (const char *s) { if (priv->clist != NULL){ priv->clist->setheader (s); }else{ priv->dia->newf_head ("",s); } priv->sort.nbcol = 1; while (*s != '\0'){ if (*s == '\t') priv->sort.nbcol++; s++; } } void _F_editrecords::addwhat(const char *s) { if (!priv->loadedonce){ priv->dia->addwhat (s); priv->buttons |= MENUBUT_ADD; } } void _F_editrecords::delwhat(const char *s) { if (!priv->loadedonce){ priv->dia->delwhat (s); priv->buttons |= MENUBUT_DEL; } } void _F_editrecords::setbutinfo (int id, const char *title, const char *icon) { if (!priv->loadedonce){ priv->dia->setbutinfo (id,title,icon); int butmask = MENUBUT_USR1 << (id - MENU_USR1); priv->buttons |= butmask; } } void _F_editrecords::add () { } void _F_editrecords::otherbuttons (MENU_STATUS, int ) { } void _F_editrecords::message () { } void _F_editrecords::setcontext (const char *s) { priv->dia->setcontext (s); } void _F_editrecords::set_button_on_side () { priv->dia->set_button_on_side (); } void _F_editrecords::waitfor (PRIVATE_MESSAGE &msg) { priv->dia->waitfor (msg); } void _F_editrecords::handle (FRAMEWORK_MSGS &msgs) { priv->fmsgs = &msgs; priv->dia->waitfor (msgs.ending); priv->dia->waitfor (msgs.mayend); } void _F_editrecords::settype (DIALOG_TYPE type) { priv->dia->settype (type); } void _F_editrecords::editone (int no, UISTATE &state) { pickone (no,state); endedit(); } void _F_editrecords::pickone (int , UISTATE &) { xconf_notice (MSG_U(N_NOPICKONE,"Function/tag pickone or editone must be defined")); } void _F_editrecords::newf_clist() { if (priv->clist != NULL){ xconf_error ("editrecords: newf_clist may only be called once"); }else{ priv->clist = priv->dia->newf_clist (NULL,priv->vsize,priv->listmsg,priv->listsel); priv->clist->sethdispo (priv->hdispo.get()); } } void _F_editrecords::guidialog() { } void _F_editrecords::gui_passthrough (int cmd, const char *ctl, ...) { va_list list; va_start (list,ctl); priv->dia->gui_passthroughv (cmd,ctl,list); va_end (list); } void _F_editrecords::new_button_icon (int id, const char *icon, const char *help) { int nobut = priv->nbbut++; FIELD_BUTTON_ICON *b = priv->dia->new_button_icon (icon,help ,priv->tbmsg[nobut]); priv->tbid[nobut] = id; priv->tbicon[nobut] = b; } void _F_editrecords::new_button (int id, const char *text, const char *help) { int nobut = priv->nbbut++; FIELD_BUTTON_TEXT *b = priv->dia->new_button (text,help ,priv->tbmsg[nobut]); priv->tbid[nobut] = id; priv->tbtext[nobut] = b; } /* Change the icon of a button */ void _F_editrecords::set_button_icon (int id, const char *icon) { int i; for (i=0; inbbut; i++){ if (priv->tbid[i] == id){ FIELD_BUTTON_ICON *b = priv->tbicon[i]; if (b != NULL){ b->seticon (icon); }else{ fprintf (stderr,MSG_U(E_NOTICON ,"editrecords: button id %d is not a button icon\n"),id); } break; } } if (i==priv->nbbut){ fprintf (stderr,MSG_U(E_NOBUTID,"editrecords: button id %d not defined\n"),id); } } /* Change the text of a button */ void _F_editrecords::set_button (int id, const char *text) { int i; for (i=0; inbbut; i++){ if (priv->tbid[i] == id){ FIELD_BUTTON_TEXT *b = priv->tbtext[i]; if (b != NULL){ b->settext (text); }else{ fprintf (stderr,MSG_U(E_NOTTEXT ,"editrecords: button id %d is not a text button\n"),id); } break; } } if (i==priv->nbbut){ fprintf (stderr,MSG_R(E_NOBUTID),id); } } void _F_editrecords::new_button_help () { priv->dia->new_button_icon ("qmark","",priv->helpmsg); priv->help_button = true; } void _F_editrecords::newline () { priv->dia->newline(); } void _F_editrecords::newf_chk (const char *prompt, char &var, const char *title) { priv->dia->newf_chk (prompt,var,title); } void _F_editrecords::newf_str (const char *prompt, SSTRING &var) { priv->dia->newf_str (prompt,var); } void _F_editrecords::nobutton() { priv->buttons &= ~MENUBUT_QUIT; } /* Record the fact that column are sortable. The clickhead functag will be called when the user click on columns. By default (if sortable has not been called), column title are not presented as buttons. sortable() is seldom used. sortpolicy() is. If sortpolicy() is used, then editrecords will manage the sorting. So in general, calling sortable() alone is only useful of the load functag is handling the sort itself (which it may do for custom sorting). */ void _F_editrecords::sortable() { priv->sort.active = true; if (priv->sort.order == SORT_NONE){ priv->sort.order = SORT_ASC; priv->sort.column = 0; } } /* Record the policy used to sort the columns. editrecord is now responsible to perform the sorting */ void _F_editrecords::sortpolicy(const char *policy) { free (priv->sort.policy); priv->sort.policy = NULL; if (policy != NULL){ sortable(); // In case the programmer forgot asprintf (&priv->sort.policy,"%saaaaaaaaaaaaaaaaaaaaaa",policy); } } /* Select the sorting for the list. */ void _F_editrecords::sortcolumn(int column, SORT_ORDER order) { priv->sort.column = column; priv->sort.order = order; } /* Select the sorting based on which column title was clicked */ void _F_editrecords::selectsort (int head) { SORT_ORDER order = SORT_NONE; if (priv->sort.column == head){ if (priv->sort.order == SORT_ASC){ order = SORT_DESC; }else{ order = SORT_ASC; } }else{ priv->sort.column = head; order = SORT_ASC; } priv->sort.order = order; if (priv->clist != NULL){ priv->clist->sethsign (head,order == SORT_ASC ? 'd' : 'u'); } } void _F_editrecords::clickhead ( int head, // Which column was clicked UISTATE &state) { selectsort(head); } /* Drawing contexts used to the next records defined */ void _F_editrecords::setnextdcs (const char *dcs) { free (priv->dcs); priv->dcs = NULL; if (dcs != NULL) priv->dcs = strdup(dcs); if (priv->clist != NULL) priv->clist->setnextdcs(dcs); } /* Select a drawing contexts used for the next records defined so it represents a "tagged" record. This function must be called before every new_menuitem() called. */ void _F_editrecords::setnexttagged () { setnextdcs (priv->tagdcs); } /* Default drawing contexts for the records */ void _F_editrecords::setdefaultdcs (const char *dcs) { if (priv->clist != NULL) priv->clist->setdcs(dcs); } void _F_editrecords::_slot1(){} void _F_editrecords::_slot2(){} void _F_editrecords::_slot3(){} void _F_editrecords::_slot4(){} void _F_editrecords::_slot5(){} void _F_editrecords::_slot6(){} void _F_editrecords::_slot7(){} void _F_editrecords::_slot8(){} void _F_editrecords::_slot9(){} /* Return the next record based on the current ordering. pos represent a position in the physical record list (known to the application) Return -1 if no next record. */ int _F_editrecords::getnext(int pos) { int ret = -1; int n = priv->sort.records.size(); if (n == 0){ int nb = priv->lookup.size(); if (pos < nb - 1) ret = pos + 1; }else{ bool found = false; for (int i=0; isort.records.getitem(i); if (found){ ret = r->pos; break; }else if (r->pos == pos){ found = true; // So this is the next record } } } return ret; } /* Return the previous record based on the current ordering. pos represent a position in the physical record list (known to the application) Return -1 if no previous record. */ int _F_editrecords::getprev(int pos) { int ret = -1; int n = priv->sort.records.size(); if (n == 0){ // Not sorted by us if (pos > 0) ret = pos - 1; }else{ int maybe_prev = -1; for (int i=0; isort.records.getitem(i); if (r->pos == pos){ ret = maybe_prev; break; } maybe_prev = r->pos; } } return ret; } static void editcommon ( DIALOG &dia, DIALOG_RECORDS *diarec, _F_editrecords &c, const char *title, const char *intro, HELP_FILE &help, const char *diactx) { dialog_clear(); // Make sure the UI toolkit is initialised /* The implementation is a little weird. In GUI mode, it is using the DIALOG::newf_clist while in text mode, it relies on DIALOG_RECORDS. At this point, the FIELD_CLIST is not implemented in text mode and html, so we use it only in GUI mode. This cause a couple of "if" here and there. On the client side (caller of editrecords), this is transparent. */ EDITRECORDS_PRIVATE priv; dia.setcontext (diactx); c.priv = &priv; priv.nof = -1; priv.dia = &dia; priv.diarec = diarec; priv.clist = NULL; priv.listsel = 0; priv.loadedonce = false; priv.buttons = MENUBUT_QUIT; priv.mustexit = false; priv.nbbut = 0; priv.help_button = false; HELP_FILE *pthelp = &help; if (dialog_mode == DIALOG_GUI){ c.guidialog(); if (priv.clist == NULL) c.newf_clist(); } c.head (); if (priv.sort.active){ if (priv.clist != NULL) priv.clist->mayclickhead (); priv.sort.column = -1; // To force SORT_ASC when selectsort() is called c.selectsort (0); } int nbhead = dia.getnb(); int nof = 0; PRIVATE_MESSAGE sort_right,sort_left,sort_reverse; dia.add_intercept ('+',sort_right); dia.add_intercept ('-',sort_left); dia.add_intercept (' ',sort_reverse); while (1){ if (priv.mustexit) break; priv.nbitems = 0; c.setnextdcs (NULL); priv.sort.records.remove_all(); c.load (priv.sort.column,priv.sort.order); if (priv.sort.records.size() > 0){ priv.sort.records.sort (cmp_asc); if (priv.sort.order == SORT_DESC) priv.sort.records.invert(); for (int i=0; isetnextdcs (s->dcs); priv.clist->setrecordf (i,"%s\t%s",s->s1,s->s2); }else{ dia.set_menuitem (i,s->s1,s->s2); } } } priv.loadedonce = true; if (dialog_mode == DIALOG_GUI){ priv.clist->remove_last (priv.nbitems); }else{ dia.remove_last (priv.nbitems + nbhead); } MENU_STATUS code; if (priv.help_button) pthelp = &help_none; if (dialog_mode == DIALOG_GUI){ if (priv.nof != -1){ dia.show (title,intro,*pthelp,nof,priv.buttons); priv.clist->setcursor (priv.nof,false); priv.nof = -1; } code = dia.edit (title,intro,*pthelp ,nof,priv.buttons); }else{ dia.set_column (priv.sort.column); code = dia.editmenu (title,intro,*pthelp ,priv.nof,priv.buttons); } if (code == MENU_QUIT || code == MENU_ESCAPE){ break; }else if (code == MENU_ADD){ c.add(); }else if (code == MENU_OK){ if (priv.nof >= 0 && priv.nof < priv.nbitems){ UISTATE state; diajava_lastmousestate (state); int no = priv.sort.records.cnvpos(priv.nof); c.editone (priv.lookup[no],state); } }else if (code == MENU_MESSAGE){ dia.save(); if (dialog_testmessage(sort_right)){ if (priv.sort.column < priv.sort.nbcol-1) priv.sort.column++; }else if (dialog_testmessage(sort_left)){ if (priv.sort.column > 0) priv.sort.column--; }else if (dialog_testmessage(sort_reverse)){ if (priv.sort.order == SORT_DESC){ priv.sort.order = SORT_ASC; }else{ priv.sort.order = SORT_DESC; } }else if (dialog_testmessage(priv.listmsg)){ int head = priv.clist->whichcolumn(); UISTATE state; diajava_lastmousestate (state); if (head != -1){ c.clickhead (head,state); }else if (priv.listsel >= 0 && priv.listsel < priv.nbitems){ int no = priv.sort.records.cnvpos (priv.listsel); c.editone (priv.lookup[no],state); } }else if (dialog_testmessage(priv.helpmsg)){ diagui_showhelp (help); }else{ bool found = false; for (int i=0; iis_mayend()){ priv.fmsgs->endok(); found = true; }else if (priv.fmsgs->is_ending()){ priv.mustexit = true; found = true; } } if (!found) c.message(); } }else{ dia.save(); c.otherbuttons(code,-1); } } } void editmanyrecords ( _F_editmanyrecords &c, const char *title, const char *intro, HELP_FILE &help, const char *diactx) { DIALOG_LISTE dia; editcommon (dia,&dia,c,title,intro,help,diactx); } void editmanyrecords ( _F_editmanyrecords &c, const char *title, const char *intro, HELP_FILE &help) { editmanyrecords (c,title,intro,help,NULL); } void editrecords ( _F_editrecords &c, const char *title, const char *intro, HELP_FILE &help, const char *diactx) { if (dialog_mode != DIALOG_GUI){ DIALOG_RECORDS dia; editcommon (dia,&dia,c,title,intro,help,diactx); }else{ DIALOG dia; editcommon (dia,NULL,c,title,intro,help,diactx); } } void editrecords ( _F_editrecords &c, const char *title, const char *intro, HELP_FILE &help) { editrecords (c,title,intro,help,NULL); }