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