1 /***********************************************************************/
2 /* Open Visualization Data Explorer                                    */
3 /* (C) Copyright IBM Corp. 1989,1999                                   */
4 /* ALL RIGHTS RESERVED                                                 */
5 /* This code licensed under the                                        */
6 /*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
7 /***********************************************************************/
8 
9 #include <dxconfig.h>
10 #include <defines.h>
11 
12 
13 #include <Xm/PushB.h>
14 #include <Xm/Text.h>
15 #include <Xm/List.h>
16 #include <Xm/MenuShell.h>
17 #include <Xm/Form.h>
18 #include <Xm/Separator.h>
19 #include <X11/cursorfont.h>
20 #include <X11/Shell.h>
21 
22 #include "TextSelector.h"
23 #include "ListIterator.h"
24 #include "IBMApplication.h"
25 #include "DXStrings.h"
26 #include "lex.h"
27 
28 #include <ctype.h> // for isalnum
29 
30 #define ELLIPSIS_WIDTH 23
31 #define SELECTOR_HEIGHT 23
32 #define MAX_VISIBLE 12
33 
34 Cursor TextSelector::GrabCursor = 0;
35 TextSelector* TextSelector::GrabOwner = NUL(TextSelector*);
36 
37 //
38 // The color #ddddddddd is chosen because it will both stand out, and go easy
39 // on the colormap since it's the same as WorkSpace.selectColor
40 //
41 String TextSelector::DefaultResources[] = {
42     "*popupMenu.borderWidth: 0",
43     "*popupMenu.allowShellResize: True",
44     "*diagButton.labelString:		...",
45     "*diagButton.recomputeSize:		False",
46     "*diagButton.indicatorOn:		False",
47     "*diagButton.topOffset: 0",
48     "*diagButton.leftOffset: 0",
49     "*diagButton.rightOffset: 0",
50     "*diagButton.bottomOffset: 0",
51     NUL(char*)
52 };
53 
54 boolean TextSelector::ClassInitialized = FALSE;
55 
TextSelector()56 TextSelector::TextSelector () :
57     UIComponent ("textSelector")
58 {
59     this->parent = NUL(Widget);
60     this->button_release_timer = 0;
61     this->diag_button = NUL(Widget);
62     this->popupMenu = NUL(Widget);
63     this->popupList = NUL(Widget);
64     this->is_grabbed = FALSE;
65     this->starting_button = 1;
66     this->action_hook = 0;
67     this->vsb = this->hsb = NUL(Widget);
68     this->old_event = NUL(XEvent*);
69     this->remove_hook_wpid = NUL(XtWorkProcId);
70     this->item_list.clear();
71     this->selected_items = NUL(int*);
72     this->selected_cnt = 0;
73     this->auto_fill_wpid = NUL(XtWorkProcId);
74     this->case_sensitive = TRUE;
75 
76     if (!TextSelector::ClassInitialized) {
77 	TextSelector::ClassInitialized = TRUE;
78 	this->setDefaultResources(theIBMApplication->getRootWidget(),
79 	    TextSelector::DefaultResources);
80     }
81 }
82 
~TextSelector()83 TextSelector::~TextSelector()
84 {
85     if (this->button_release_timer)
86 	XtRemoveTimeOut(this->button_release_timer);
87     if (this->is_grabbed)
88 	this->ungrab();
89     if (this->old_event)
90 	delete this->old_event;
91     if (this->action_hook)
92 	XtRemoveActionHook (this->action_hook);
93     if (this->remove_hook_wpid)
94 	XtRemoveWorkProc (this->remove_hook_wpid);
95     ListIterator it(this->item_list);
96     char* item;
97     while ( (item = (char*)it.getNext()) )
98 	delete item;
99     if (this->selected_items)
100 	XtFree((char*)this->selected_items);
101     if (this->auto_fill_wpid)
102 	XtRemoveWorkProc (this->auto_fill_wpid);
103 }
104 
setItems(char * items[],int nitems)105 void TextSelector::setItems(char* items[], int nitems)
106 {
107     this->enableModifyCB(FALSE);
108 
109     //
110     // Copy the items;
111     //
112     int i;
113     this->item_list.clear();
114     for (i=0; i<nitems; i++) {
115 	ASSERT (items[i]);
116 	char* cp = DuplicateString(items[i]);
117 	this->item_list.appendElement((void*)cp);
118     }
119     if (this->popupMenu)
120 	this->updateList();
121 
122     if (this->selected_items)
123 	XtFree ((char*)this->selected_items);
124     this->selected_items = NUL(int*);
125 
126     if (nitems)
127 	this->enableModifyCB(TRUE);
128 }
129 
setSelectedItem(int selected)130 void TextSelector::setSelectedItem (int selected)
131 {
132     if (this->selected_items)
133 	XtFree((char*)this->selected_items);
134     this->selected_items = NUL(int*);
135     this->selected_cnt = 0;
136 
137     if (selected <= 0) return ;
138     if (selected > this->item_list.getSize()) return ;
139 
140     this->selected_cnt = 1;
141     //this->selected_items = new int[this->selected_cnt];
142     this->selected_items = (int*)XtMalloc(sizeof(int) * this->selected_cnt);
143     this->selected_items[0] = selected;
144 
145     char *cp = (char*)this->item_list.getElement(selected);
146     this->enableModifyCB(FALSE);
147     XmTextSetString (this->textField, cp);
148     this->enableModifyCB(TRUE);
149 }
150 
getSelectedItem(char * seli)151 int TextSelector::getSelectedItem (char* seli)
152 {
153     if (seli != NUL(char*)) seli[0] = '\0';
154     if (this->selected_items)
155 	XtFree((char*)this->selected_items);
156     this->selected_items = NUL(int*);
157     this->selected_cnt = 0;
158 
159     boolean sel = XmListGetSelectedPos
160 	(this->popupList, &this->selected_items, &this->selected_cnt);
161 
162     if (sel == False)
163 	return 0;
164     if (!this->selected_items)
165 	return 0;
166     int selected = this->selected_items[0];
167     const char* cp = (char*)this->item_list.getElement(selected);
168     if ((cp)&&(seli))
169 	strcpy (seli, cp);
170     return selected;
171 }
172 
enableModifyCB(boolean enab)173 void TextSelector::enableModifyCB(boolean enab)
174 {
175     if (enab) {
176 	XtVaSetValues (this->textField, XmNeditable, True, NULL);
177 	XtAddCallback (this->textField, XmNmodifyVerifyCallback, (XtCallbackProc)
178 	    TextSelector_ModifyCB, (XtPointer)this);
179 	XtAddCallback (this->textField, XmNactivateCallback, (XtCallbackProc)
180 	    TextSelector_ResolveCB, (XtPointer)this);
181     } else {
182 	XtVaSetValues (this->textField, XmNeditable, False, NULL);
183 	XtRemoveCallback (this->textField, XmNmodifyVerifyCallback, (XtCallbackProc)
184 	    TextSelector_ModifyCB, (XtPointer)this);
185 	XtRemoveCallback (this->textField, XmNactivateCallback, (XtCallbackProc)
186 	    TextSelector_ResolveCB, (XtPointer)this);
187     }
188 }
189 
createTextSelector(Widget parent,XtCallbackProc cbp,XtPointer cdata,boolean use_button)190 void TextSelector::createTextSelector(Widget parent, XtCallbackProc cbp, XtPointer cdata, boolean use_button)
191 {
192     ASSERT (this->parent == NUL(Widget));
193     this->parent = parent;
194 
195     //
196     // Layout a text widget and a ... button inside a form.
197     //
198     if (!use_button) {
199 	Widget form = XtVaCreateWidget(this->name,xmFormWidgetClass, parent,
200 	    XmNshadowThickness, 0,
201 	    XmNshadowType, XmSHADOW_OUT,
202 	NULL);
203 	this->setRootWidget(form);
204 
205 	this->textField = XtVaCreateManagedWidget("textField",
206 	    xmTextWidgetClass,form,
207 	    XmNtopAttachment,      XmATTACH_FORM,
208 	    XmNleftAttachment,      XmATTACH_FORM,
209 	    XmNrightAttachment,      XmATTACH_FORM,
210 	    XmNbottomAttachment,      XmATTACH_FORM,
211 	    XmNtopOffset,          0,
212 	    XmNleftOffset,          0,
213 	    XmNrightOffset,          0,
214 	    XmNbottomOffset,          0,
215 	NULL);
216     } else {
217 	Widget form = XtVaCreateWidget(this->name,xmFormWidgetClass, parent, NULL);
218 	this->setRootWidget(form);
219 
220 	this->textField = XtVaCreateManagedWidget("textField",
221 	    xmTextWidgetClass,form,
222 	    XmNleftAttachment,      XmATTACH_FORM,
223 	    XmNleftOffset,          2,
224 	    XmNtopAttachment,       XmATTACH_FORM,
225 	    XmNtopOffset,           0,
226 	NULL);
227 
228 	this->diag_button = XtVaCreateManagedWidget("diagButton",
229 	    xmPushButtonWidgetClass,form,
230 	    XmNrightAttachment,     XmATTACH_FORM,
231 	    XmNrightOffset,         2,
232 	    XmNtopAttachment,       XmATTACH_FORM,
233 	    XmNtopOffset,           2,
234 	    XmNwidth,		ELLIPSIS_WIDTH,
235 	    XmNheight,		SELECTOR_HEIGHT,
236 	    XmNshadowThickness,	1,
237 	NULL);
238 
239 	XtUninstallTranslations(this->diag_button);
240 	XtAddEventHandler (this->diag_button, ButtonPressMask, False,
241 	    (XtEventHandler)TextSelector_EllipsisEH, (XtPointer)this);
242 
243 	XtVaSetValues(this->textField,
244 	    XmNrightAttachment,      XmATTACH_WIDGET,
245 	    XmNrightWidget,          this->diag_button,
246 	    XmNrightOffset,          10,
247 	NULL);
248 
249 
250 	//
251 	// Create the menu
252 	//
253 	Pixel fg,bg;
254 	XtVaGetValues (this->textField, XmNforeground, &fg, XmNbackground, &bg, NULL);
255 
256 	Arg args[20];
257 	int n = 0;
258 	this->popupMenu =
259 	    XtCreatePopupShell ("popupMenu", overrideShellWidgetClass,
260 		this->getRootWidget(), args, n);
261 
262 	Widget rcForm = XtVaCreateManagedWidget ("rcForm",
263 	    xmFormWidgetClass, this->popupMenu,
264 	    XmNshadowThickness, 1,
265 	    XmNshadowType, XmSHADOW_OUT,
266 	    XmNresizePolicy, XmRESIZE_ANY,
267 	    XmNforeground, fg,
268 	    XmNbackground, bg,
269 	NULL);
270 
271 	n = 0;
272 	XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++;
273 	XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++;
274 	XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++;
275 	XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
276 	XtSetArg (args[n], XmNtopOffset, 3); n++;
277 	XtSetArg (args[n], XmNleftOffset, 3); n++;
278 	XtSetArg (args[n], XmNrightOffset, 3); n++;
279 	XtSetArg (args[n], XmNbottomOffset, 3); n++;
280 	XtSetArg (args[n], XmNlistSizePolicy, XmCONSTANT); n++;
281 	XtSetArg (args[n], XmNvisibleItemCount, MAX_VISIBLE); n++;
282 	XtSetArg (args[n], XmNselectionPolicy, XmBROWSE_SELECT); n++;
283 	this->popupList = XmCreateScrolledList (rcForm, "itemList", args, n);
284 	XtVaSetValues (this->popupList, XmNshadowThickness, 0, NULL);
285 	XtManageChild (this->popupList);
286 	XtAddCallback (this->popupList, XmNsingleSelectionCallback,
287 	    (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this);
288 	XtAddCallback (this->popupList, XmNbrowseSelectionCallback,
289 	    (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this);
290 	XtAddCallback (this->popupList, XmNdefaultActionCallback,
291 	    (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this);
292 	XtAddEventHandler(this->popupList, ButtonReleaseMask, False, (XtEventHandler)
293 	    TextSelector_RemoveGrabEH, (XtPointer)this);
294 	XtAddEventHandler(this->popupList, EnterWindowMask, False, (XtEventHandler)
295 	    TextSelector_ProcessOldEventEH, (XtPointer)this);
296 	XtVaGetValues (XtParent(this->popupList),
297 	    XmNverticalScrollBar, &this->vsb,
298 	    XmNhorizontalScrollBar, &this->hsb,
299 	NULL);
300 
301 	if (cbp) {
302 	    XtAddCallback (this->popupList, XmNsingleSelectionCallback, cbp, cdata);
303 	    XtAddCallback (this->popupList, XmNbrowseSelectionCallback, cbp, cdata);
304 	    XtAddCallback (this->popupList, XmNdefaultActionCallback, cbp, cdata);
305 	}
306     }
307 
308     this->enableModifyCB(this->item_list.getSize() > 0);
309 }
310 
311 //
312 // Put an entry into the scrolled list for each entry in the dictionary.
313 //
updateList()314 void TextSelector::updateList()
315 {
316 
317     //
318     // Clear the list
319     //
320     XmListDeleteAllItems(this->popupList);
321 
322     //
323     // Fill in the list with the contents of our item_list
324     //
325     XmString* strTable = new XmString[this->item_list.getSize()];
326     int next = 0;
327     ListIterator it(this->item_list);
328     char* name;
329     while ( (name = (char*)it.getNext()) ) {
330 	strTable[next++] = XmStringCreateLtoR (name, "bold");
331     }
332 
333     XtVaSetValues (this->popupList, XmNvisibleItemCount,
334 	(next<=MAX_VISIBLE?next:MAX_VISIBLE), NULL);
335     XtVaSetValues (XtParent(this->popupList),
336 	XmNwidth, 160, NULL);
337 
338     //
339     // Show the item as selected in the list and make sure that position is showing.
340     //
341     XmListAddItemsUnselected (this->popupList, strTable, next, 1);
342     if (this->selected_items) {
343 	int select_this_item = this->selected_items[0];
344 	XmListSelectPos (this->popupList, select_this_item, False);
345 	int toppos;
346 	int maxtoppos = 1 + next - MAX_VISIBLE;
347 	if (select_this_item <= MAX_VISIBLE) toppos = 1;
348 	else toppos = select_this_item - 3;
349 	if (toppos >= maxtoppos) toppos = 0;
350 	if (toppos)
351 	    XmListSetPos (this->popupList, toppos);
352 	else
353 	    //
354 	    // According to the Motif doc passing 0 to this func makes the last
355 	    // item in the list, the last visible item.  I've never seen it work, though.
356 	    // The result is that if you've selected the last item, then the list will
357 	    // never be scrolled so that that item is showing.
358 	    //
359 	    XmListSetBottomPos (this->popupList, 0);
360     }
361 
362     int i;
363     for (i=0; i<next; i++)
364 	XmStringFree (strTable[i]);
365     delete strTable;
366 }
367 
ungrab()368 void TextSelector::ungrab()
369 {
370     if (TextSelector::GrabOwner == this)
371 	TextSelector::GrabOwner = NUL(TextSelector*);
372     if (!this->is_grabbed) return ;
373     XtUngrabPointer (this->popupList, CurrentTime);
374     XtPopdown (this->popupMenu);
375     if (this->button_release_timer) {
376 	XtRemoveTimeOut (this->button_release_timer);
377 	this->button_release_timer = 0;
378     }
379     if (this->old_event) {
380 	delete this->old_event;
381 	this->old_event = NUL(XEvent*);
382     }
383     this->is_grabbed = FALSE;
384 }
grab(XEvent * e)385 void TextSelector::grab(XEvent* e)
386 {
387     if (!TextSelector::GrabCursor) {
388 	TextSelector::GrabCursor =
389 	    XCreateFontCursor (XtDisplay(this->popupMenu), XC_arrow);
390     }
391 
392     if (TextSelector::GrabOwner != NUL(TextSelector*)) {
393 	if (TextSelector::GrabOwner != this)
394 	    TextSelector::GrabOwner->ungrab();
395 	else
396 	    return ;
397     }
398     TextSelector::GrabOwner = this;
399 
400     XtPopup (this->popupMenu, XtGrabNone);
401     XtAppContext apcxt = theApplication->getApplicationContext();
402     this->action_hook = XtAppAddActionHook (apcxt, (XtActionHookProc)
403 	TextSelector_PopupListAH, (XtPointer)this);
404     XtGrabPointer (this->popupList, True,
405 	ButtonPressMask | ButtonReleaseMask , GrabModeAsync,
406 	GrabModeAsync, None, TextSelector::GrabCursor, e->xbutton.time);
407 
408     this->is_button_release_grabbed = FALSE;
409     if (this->button_release_timer)
410 	XtRemoveTimeOut (this->button_release_timer);
411 
412     this->button_release_timer = XtAppAddTimeOut (apcxt, 500, (XtTimerCallbackProc)
413 	TextSelector_GrabReleaseTO, (XtPointer)this);
414 
415     //
416     // Squirrel away the XEvent so that it can be processed if the user drags
417     // into the list. Use the following line to process it.
418     // XtCallActionProc (this->popupList, "ListBeginSelect", e, NULL, 0);
419     // You know it needs to be processed if you get EnterWindow on the popupList
420     // with the mouse button down.
421     //
422     if (this->old_event)
423 	delete this->old_event;
424     this->old_event = new XEvent;
425     memcpy (this->old_event, e, sizeof(XEvent));
426 
427     this->is_grabbed = TRUE;
428 }
429 
430 extern "C"  {
431 
TextSelector_GrabReleaseTO(XtPointer clientData,XtIntervalId *)432 void TextSelector_GrabReleaseTO(XtPointer clientData, XtIntervalId* )
433 {
434     TextSelector* psel = (TextSelector*)clientData;
435     ASSERT(psel);
436     psel->is_button_release_grabbed = TRUE;
437     psel->button_release_timer = 0;
438 }
439 
440 
TextSelector_ProcessOldEventEH(Widget w,XtPointer clientData,XEvent * e,Boolean *)441 void TextSelector_ProcessOldEventEH(Widget w, XtPointer clientData, XEvent *e, Boolean *)
442 {
443     TextSelector* psel = (TextSelector*)clientData;
444     ASSERT(psel);
445     if ((e->type == EnterNotify) && (psel->old_event)) {
446 	XCrossingEvent* xce = (XCrossingEvent*)e;
447 	int state = xce->state;
448 	if (state & Button1Mask) {
449 	    XtCallActionProc (psel->popupList, "ListBeginSelect",
450 		psel->old_event, NULL, 0);
451 	    delete psel->old_event;
452 	    psel->old_event = NUL(XEvent*);
453 	}
454     }
455     if (psel->old_event) {
456 	delete psel->old_event;
457 	psel->old_event = NUL(XEvent*);
458     }
459 }
460 
TextSelector_RemoveGrabEH(Widget w,XtPointer clientData,XEvent *,Boolean *)461 void TextSelector_RemoveGrabEH(Widget w, XtPointer clientData, XEvent *, Boolean *)
462 {
463     TextSelector* psel = (TextSelector*)clientData;
464     ASSERT(psel);
465     if (psel->is_button_release_grabbed)
466 	psel->ungrab();
467     if (psel->button_release_timer) {
468 	XtRemoveTimeOut (psel->button_release_timer);
469 	psel->button_release_timer = 0;
470     }
471 
472     psel->is_button_release_grabbed = TRUE;
473 }
TextSelector_EllipsisEH(Widget,XtPointer clientData,XEvent * e,Boolean *)474 void TextSelector_EllipsisEH(Widget , XtPointer clientData, XEvent *e, Boolean *)
475 {
476     TextSelector* psel = (TextSelector*)clientData;
477     ASSERT(psel);
478     if (psel->is_grabbed == TRUE)
479 	return ;
480     psel->updateList();
481 
482     Dimension width, height;
483     Screen *scrptr;
484     XtVaGetValues (XtParent(psel->popupList),
485 	XmNwidth, &width, XmNheight, &height,
486 	XmNscreen, &scrptr,
487     NULL);
488 
489     Position x = e->xbutton.x_root;
490     Position y = e->xbutton.y_root;
491 
492     x-= e->xbutton.x;
493     y-= e->xbutton.y;
494     y+= SELECTOR_HEIGHT;
495 
496     if ((x+width) > WidthOfScreen(scrptr))
497 	x = WidthOfScreen(scrptr) - (width+4);
498     if ((y+height) > HeightOfScreen(scrptr))
499 	y-= (SELECTOR_HEIGHT + height + 7);
500 
501     XtVaSetValues (psel->popupMenu, XmNx, x, XmNy, y, NULL);
502 
503     e->xbutton.x = e->xbutton.y = 0;
504     psel->grab(e);
505 }
506 
507 //
508 // If the widget of the event is not the popuplist or one of its scrollbars,
509 // then popdown the list.
510 //
TextSelector_PopupListAH(Widget w,XtPointer clientData,String,XEvent * xev,String *,Cardinal *)511 void TextSelector_PopupListAH (Widget w, XtPointer clientData, String, XEvent* xev,
512 String*, Cardinal*)
513 {
514     TextSelector* psel = (TextSelector*)clientData;
515     ASSERT(psel);
516     if (psel->is_grabbed) {
517 	if ((xev->type == ButtonPress) || (xev->type == KeyPress)) {
518 	    if ((w != psel->popupList) && (w != psel->hsb) && (w != psel->vsb))
519 		psel->ungrab();
520 	}
521     } else {
522 	//
523 	// It would be nice to remove the action hook here, but the intrinsics
524 	// assume that if you do that inside the action hook proc, then you would
525 	// like to reference freed memory and core dump.   Usually we prefer not
526 	// to do that, so we add a work proc and remove the action hook there.
527 	// It isn't harmful to run the action hook too long, anyway.
528 	//
529 	if (psel->remove_hook_wpid == 0) {
530 	    XtAppContext apcxt = theApplication->getApplicationContext();
531 	    psel->remove_hook_wpid = XtAppAddWorkProc (apcxt,
532 		TextSelector_RemoveHookWP, (XtPointer)psel);
533 	}
534     }
535 }
536 
TextSelector_RemoveHookWP(XtPointer clientData)537 Boolean TextSelector_RemoveHookWP (XtPointer clientData)
538 {
539     TextSelector* psel = (TextSelector*)clientData;
540     ASSERT(psel);
541     if (psel->action_hook)
542 	XtRemoveActionHook (psel->action_hook);
543     psel->action_hook = 0;
544     psel->remove_hook_wpid = NUL(XtWorkProcId);
545     return TRUE;
546 }
547 
548 } // extern "C"
549 
550 
551 
552 //
553 // Get the text from a text widget and clip off the leading and
554 // trailing white space.
555 // The return string must be deleted by the caller.
556 //
GetTextWidgetToken(Widget textWidget)557 char *TextSelector::GetTextWidgetToken(Widget textWidget)
558 {
559     char *name = XmTextGetString(textWidget);
560     ASSERT(name);
561     int i,len = STRLEN(name);
562 
563     for (i=len-1 ; i>=0 ; i--) {
564 	if (IsWhiteSpace(name,i))
565 	    name[i] = '\0';
566 	else
567 	    break;
568     }
569 
570     i=0; SkipWhiteSpace(name,i);
571 
572     char *s = DuplicateString(name+i);
573     XtFree(name);
574     return s;
575 }
576 
TextSelector_SelectCB(Widget,XtPointer clientData,XtPointer cbs)577 void TextSelector_SelectCB(Widget , XtPointer clientData, XtPointer cbs)
578 {
579     TextSelector* psel = (TextSelector*)clientData;
580     ASSERT(psel);
581 
582     psel->ungrab();
583 
584     //
585     // Error check hacking...  If the mouse event was outside the list widget,
586     // then ignore it.
587     //
588     XmListCallbackStruct *lcs = (XmListCallbackStruct*)cbs;
589     XEvent* xev = lcs->event;
590     boolean inside = TRUE;
591     if ((xev) && ((xev->type == ButtonPress) || (xev->type == ButtonRelease))) {
592 	XButtonEvent* xbe = (XButtonEvent*)xev;
593 	Dimension width, height;
594 	XtVaGetValues (psel->popupList, XmNwidth, &width, XmNheight, &height, NULL);
595 	if ((xbe->x < 0) || (xbe->y < 0) || (xbe->x > width) || (xbe->y > height))
596 	    inside = FALSE;
597     }
598 
599     //
600     // Make the selection based on list contents
601     //
602     if ((inside) && (lcs->selected_item_count)) {
603 	int pos = lcs->item_position;
604 	if ((pos >= 1) && (pos <= psel->item_list.getSize())) {
605 	    //
606 	    // Put a new item into the text widget
607 	    //
608 	    char* cp = (char*)psel->item_list.getElement(lcs->item_position);
609 	    ASSERT(cp);
610 	    XmTextSetString (psel->textField, cp);
611 
612 	    //
613 	    // Remember what's currently selected
614 	    //
615 	    if (psel->selected_items)
616 		XtFree((char*)psel->selected_items);
617 	    boolean sel = XmListGetSelectedPos
618 		(psel->popupList, &psel->selected_items, &psel->selected_cnt);
619 	    if (sel == False) {
620 		psel->selected_items = NUL(int*);
621 		psel->selected_cnt = 0;
622 	    }
623 	}
624     }
625 }
626 
setMenuColors(Arg args[],int n)627 void TextSelector::setMenuColors (Arg args[], int n)
628 {
629     XtSetValues (this->popupMenu, args, n);
630     XtSetValues (XtParent(this->popupList), args, n);
631     XtSetValues (XtParent(XtParent(this->popupList)), args, n);
632     XtSetValues (this->popupList, args, n);
633     XtSetValues (this->vsb, args, n);
634     XtSetValues (this->hsb, args, n);
635 }
636 
637 //
638 // If the proposed modification to the text widget would result in a text
639 // string which is represented in the list, then allow the modification to
640 // proceed, else not.
641 //
642 extern "C" void
TextSelector_ModifyCB(Widget w,XtPointer cdata,XtPointer callData)643 TextSelector_ModifyCB (Widget w, XtPointer cdata, XtPointer callData)
644 {
645     TextSelector* tsel = (TextSelector*)cdata;
646     ASSERT (tsel);
647     XmTextVerifyCallbackStruct *tvcs = (XmTextVerifyCallbackStruct*)callData;
648     boolean good_chars = TRUE;
649 
650     XmTextBlock tbrec = tvcs->text;
651 
652     if ((tbrec->length) && (tvcs->reason == XmCR_MODIFYING_TEXT_VALUE)) {
653 	char* cp = XmTextGetString (tsel->textField);
654 	int plen = (cp?strlen(cp):0) + tbrec->length + 1;
655 	char* proposed = new char[plen];
656 
657 	//
658 	// you might expect cp to be <nil> if there were no chars in the text
659 	// widget but, it really should be "" in that case, so it should never
660 	// be <nil>.
661 	//
662 	ASSERT (cp);
663 	strcpy (proposed, cp);
664 	strcpy (&proposed[tvcs->startPos], tbrec->ptr);
665 	strcat (proposed, &cp[tvcs->startPos]);
666 
667 	//
668 	// Now lookup proposed in the list of items
669 	//
670 	ListIterator it(tsel->item_list);
671 	const char* item;
672 	int found = 0;
673 	boolean match = FALSE;
674 	int i = 1;
675 	while ( (item = (char*)it.getNext()) ) {
676 	    if (tsel->equalString (item, cp)) {
677 		match = TRUE;
678 		break;
679 	    } else if (tsel->equalSubstring (item, proposed, plen-1)) {
680 		found++;
681 	    }
682 	    i++;
683 	}
684 	if (match) {
685 	} else if (found == 0)
686 	    good_chars = FALSE;
687 	else if (found == 1) {
688 	    //
689 	    // This is the place to complete the entry automatically
690 	    // It can't be done here because the proposed modification to
691 	    // the string would still happen after you've updated the contents.
692 	    //tsel->setSelectedItem (most_recent);
693 	    if (tsel->auto_fill_wpid)
694 		XtRemoveWorkProc (tsel->auto_fill_wpid);
695 	    XtAppContext apcxt = theApplication->getApplicationContext();
696 	    tsel->auto_fill_wpid = XtAppAddWorkProc (apcxt, (XtWorkProc)
697 		TextSelector_FillWP, (XtPointer) tsel);
698 	}
699 
700 	if (proposed) delete proposed;
701     }
702     tvcs->doit = (Boolean)good_chars;
703     if (!tvcs->doit)
704 	XBell(XtDisplay(w), 100);
705 }
706 
707 extern "C" Boolean
TextSelector_FillWP(XtPointer cdata)708 TextSelector_FillWP (XtPointer cdata)
709 {
710     TextSelector* tsel = (TextSelector*)cdata;
711     ASSERT (tsel);
712     tsel->auto_fill_wpid = NUL(XtWorkProcId);
713 
714     tsel->autoFill();
715 
716     return True;
717 }
autoFill(boolean any_match)718 boolean TextSelector::autoFill(boolean any_match)
719 {
720     boolean unused;
721     return this->autoFill(any_match, unused);
722 }
723 
724 // if (any_match) then if we don't find an exact match but
725 // we do find multiple substring matches, we'll accept the
726 // first substring match
autoFill(boolean any_match,boolean & unique_match)727 boolean TextSelector::autoFill(boolean any_match, boolean& unique_match)
728 {
729     boolean retVal = FALSE;
730     unique_match = FALSE;
731     char* cp = XmTextGetString (this->textField);
732     if ((!cp) || (!cp[0])) return FALSE;
733     int len = strlen(cp);
734 
735     //
736     // Now lookup proposed in the list of items
737     //
738     ListIterator it(this->item_list);
739     const char* item;
740     int found = 0;
741     int most_recent=0;
742     int index_of_first_match = -1;
743     int i = 1;
744     while ( (item = (char*)it.getNext()) ) {
745 	// we don't need both tests do we?
746 	if ((this->equalString (item, cp)) || (this->equalSubstring(item,cp,len))) {
747 	    found++;
748 	    most_recent = i;
749 	    if (index_of_first_match == -1)
750 		index_of_first_match = i;
751 	}
752 	i++;
753     }
754     if (found == 1)  {
755 	this->setSelectedItem (most_recent);
756 	if (this->popupList)
757 	    XmListSelectPos (this->popupList, most_recent, True);
758 	retVal = TRUE;
759 	unique_match = TRUE;
760     } else if ((found > 1) && (any_match)) {
761 	ASSERT(cp); // can we have a match with no text?
762 	int start = strlen(cp);
763 	this->setSelectedItem (index_of_first_match);
764 	char *match = (char*)this->item_list.getElement(index_of_first_match);
765 	int end = strlen(match);
766 	XmTextSetSelection (this->textField, start, end,
767 		XtLastTimestampProcessed(XtDisplay(this->textField)));
768 	//
769 	// Some of the text just entered might not be correct since
770 	// we're accepting the first available match out of several.
771 	// Therefore, we'll select the text so that further typing
772 	// overwrites the erroneous portion.
773 	//
774 	if (this->popupList)
775 	    XmListSelectPos (this->popupList, index_of_first_match, True);
776 	retVal = TRUE;
777     }
778     XtFree(cp);
779     return retVal;
780 }
781 
equalString(const char * s1,const char * s2)782 boolean TextSelector::equalString(const char* s1, const char* s2)
783 {
784     if (this->case_sensitive) return EqualString(s1,s2);
785 
786     int len = STRLEN(s1);
787     if (len != STRLEN(s2)) return FALSE;
788 
789     boolean retval = TRUE;
790     for (int i=0; i<len; i++) {
791 	if (s1[i] == s2[i]) continue;
792 	if (tolower(s1[i]) == tolower(s2[i])) continue;
793 	retval = FALSE;
794 	break;
795     }
796 
797     return retval;
798 }
799 
equalSubstring(const char * s1,const char * s2,int n)800 boolean TextSelector::equalSubstring(const char* s1, const char* s2, int n)
801 {
802     if (this->case_sensitive) return EqualSubstring(s1,s2,n);
803 
804     // what's supposed to happen if s2 is ""?
805     if (!s2[0]) return FALSE;
806 
807     boolean retval = TRUE;
808 
809     for (int i=0; i<n; i++) {
810 	if (s2[i]=='\0') break;
811 	if (s1[i]=='\0') {
812 	    retval = FALSE;
813 	    break;
814 	}
815 	if (s1[i] == s2[i]) continue;
816 	if (tolower(s1[i]) == tolower(s2[i])) continue;
817 	retval = FALSE;
818 	break;
819     }
820 
821     return retval;
822 }
823 
824 extern "C" void
TextSelector_ResolveCB(Widget,XtPointer cdata,XtPointer)825 TextSelector_ResolveCB (Widget, XtPointer cdata, XtPointer)
826 {
827     TextSelector* tsel = (TextSelector*)cdata;
828     ASSERT (tsel);
829 
830     if ((tsel->autoFill(TRUE) == FALSE) && (tsel->popupList)) {
831 	if (tsel->selected_items) {
832 	    XmListSelectPos (tsel->popupList, tsel->selected_items[0], True);
833 	} else {
834 	    XmListDeselectAllItems (tsel->popupList);
835 	}
836     }
837 }
838