/***********************************************************************/ /* Open Visualization Data Explorer */ /* (C) Copyright IBM Corp. 1989,1999 */ /* ALL RIGHTS RESERVED */ /* This code licensed under the */ /* "IBM PUBLIC LICENSE - Open Visualization Data Explorer" */ /***********************************************************************/ #include #include #include #include #include #include #include #include #include "TextSelector.h" #include "ListIterator.h" #include "IBMApplication.h" #include "Strings.h" #include "lex.h" #include // for isalnum #define ELLIPSIS_WIDTH 23 #define SELECTOR_HEIGHT 23 #define MAX_VISIBLE 12 Cursor TextSelector::GrabCursor = 0; TextSelector* TextSelector::GrabOwner = NUL(TextSelector*); // // The color #ddddddddd is chosen because it will both stand out, and go easy // on the colormap since it's the same as WorkSpace.selectColor // String TextSelector::DefaultResources[] = { "*popupMenu.borderWidth: 0", "*popupMenu.allowShellResize: True", "*diagButton.labelString: ...", "*diagButton.recomputeSize: False", "*diagButton.indicatorOn: False", "*diagButton.topOffset: 0", "*diagButton.leftOffset: 0", "*diagButton.rightOffset: 0", "*diagButton.bottomOffset: 0", NUL(char*) }; boolean TextSelector::ClassInitialized = FALSE; TextSelector::TextSelector () : UIComponent ("textSelector") { this->parent = NUL(Widget); this->button_release_timer = 0; this->diag_button = NUL(Widget); this->popupMenu = NUL(Widget); this->popupList = NUL(Widget); this->is_grabbed = FALSE; this->starting_button = 1; this->action_hook = 0; this->vsb = this->hsb = NUL(Widget); this->old_event = NUL(XEvent*); this->remove_hook_wpid = NUL(XtWorkProcId); this->item_list.clear(); this->selected_items = NUL(int*); this->selected_cnt = 0; this->auto_fill_wpid = NUL(XtWorkProcId); if (!TextSelector::ClassInitialized) { TextSelector::ClassInitialized = TRUE; this->setDefaultResources(theIBMApplication->getRootWidget(), TextSelector::DefaultResources); } } TextSelector::~TextSelector() { if (this->button_release_timer) XtRemoveTimeOut(this->button_release_timer); if (this->is_grabbed) this->ungrab(); if (this->old_event) delete this->old_event; if (this->action_hook) XtRemoveActionHook (this->action_hook); if (this->remove_hook_wpid) XtRemoveWorkProc (this->remove_hook_wpid); ListIterator it(this->item_list); char* item; while (item = (char*)it.getNext()) delete item; if (this->selected_items) XtFree((char*)this->selected_items); if (this->auto_fill_wpid) XtRemoveWorkProc (this->auto_fill_wpid); } void TextSelector::setItems(char* items[], int nitems) { this->enableModifyCB(FALSE); // // Copy the items; // int i; this->item_list.clear(); for (i=0; iitem_list.appendElement((void*)cp); } if (this->popupMenu) this->updateList(); if (this->selected_items) XtFree ((char*)this->selected_items); this->selected_items = NUL(int*); if (nitems) this->enableModifyCB(TRUE); } void TextSelector::setSelectedItem (int selected) { if (this->selected_items) XtFree((char*)this->selected_items); this->selected_items = NUL(int*); this->selected_cnt = 0; if (selected <= 0) return ; if (selected > this->item_list.getSize()) return ; this->selected_cnt = 1; //this->selected_items = new int[this->selected_cnt]; this->selected_items = (int*)XtMalloc(sizeof(int) * this->selected_cnt); this->selected_items[0] = selected; char *cp = (char*)this->item_list.getElement(selected); this->enableModifyCB(FALSE); XmTextSetString (this->textField, cp); this->enableModifyCB(TRUE); } int TextSelector::getSelectedItem (char* seli) { if (seli != NUL(char*)) seli[0] = '\0'; if (this->selected_items) XtFree((char*)this->selected_items); this->selected_items = NUL(int*); this->selected_cnt = 0; boolean sel = XmListGetSelectedPos (this->popupList, &this->selected_items, &this->selected_cnt); if (sel == False) return 0; if (!this->selected_items) return 0; int selected = this->selected_items[0]; const char* cp = (char*)this->item_list.getElement(selected); if ((cp)&&(seli)) strcpy (seli, cp); return selected; } void TextSelector::enableModifyCB(boolean enab) { if (enab) { XtVaSetValues (this->textField, XmNeditable, True, NULL); XtAddCallback (this->textField, XmNmodifyVerifyCallback, (XtCallbackProc) TextSelector_ModifyCB, (XtPointer)this); XtAddCallback (this->textField, XmNactivateCallback, (XtCallbackProc) TextSelector_ResolveCB, (XtPointer)this); } else { XtVaSetValues (this->textField, XmNeditable, False, NULL); XtRemoveCallback (this->textField, XmNmodifyVerifyCallback, (XtCallbackProc) TextSelector_ModifyCB, (XtPointer)this); XtRemoveCallback (this->textField, XmNmodifyVerifyCallback, (XtCallbackProc) TextSelector_ResolveCB, (XtPointer)this); } } void TextSelector::createTextSelector(Widget parent, XtCallbackProc cbp, XtPointer cdata) { ASSERT (this->parent == NUL(Widget)); this->parent = parent; // // Layout a text widget and a ... button inside a form. // Widget form = XtVaCreateWidget(this->name,xmFormWidgetClass, parent, NULL); this->setRootWidget(form); this->textField = XtVaCreateManagedWidget("textField", xmTextWidgetClass,form, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 2, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 0, NULL); this->diag_button = XtVaCreateManagedWidget("diagButton", xmPushButtonWidgetClass,form, XmNrightAttachment, XmATTACH_FORM, XmNrightOffset, 2, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 2, XmNwidth, ELLIPSIS_WIDTH, XmNheight, SELECTOR_HEIGHT, XmNshadowThickness, 1, NULL); XtUninstallTranslations(this->diag_button); XtAddEventHandler (this->diag_button, ButtonPressMask, False, (XtEventHandler)TextSelector_EllipsisEH, (XtPointer)this); XtVaSetValues(this->textField, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, this->diag_button, XmNrightOffset, 10, NULL); // // Create the menu // Pixel fg,bg; XtVaGetValues (this->textField, XmNforeground, &fg, XmNbackground, &bg, NULL); Arg args[20]; int n = 0; this->popupMenu = XtCreatePopupShell ("popupMenu", overrideShellWidgetClass, this->getRootWidget(), args, n); Widget rcForm = XtVaCreateManagedWidget ("rcForm", xmFormWidgetClass, this->popupMenu, XmNshadowThickness, 1, XmNshadowType, XmSHADOW_OUT, XmNresizePolicy, XmRESIZE_ANY, XmNforeground, fg, XmNbackground, bg, NULL); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNtopOffset, 3); n++; XtSetArg (args[n], XmNleftOffset, 3); n++; XtSetArg (args[n], XmNrightOffset, 3); n++; XtSetArg (args[n], XmNbottomOffset, 3); n++; XtSetArg (args[n], XmNlistSizePolicy, XmCONSTANT); n++; XtSetArg (args[n], XmNvisibleItemCount, MAX_VISIBLE); n++; XtSetArg (args[n], XmNselectionPolicy, XmBROWSE_SELECT); n++; this->popupList = XmCreateScrolledList (rcForm, "itemList", args, n); XtVaSetValues (this->popupList, XmNshadowThickness, 0, NULL); XtManageChild (this->popupList); XtAddCallback (this->popupList, XmNsingleSelectionCallback, (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this); XtAddCallback (this->popupList, XmNbrowseSelectionCallback, (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this); XtAddCallback (this->popupList, XmNdefaultActionCallback, (XtCallbackProc)TextSelector_SelectCB, (XtPointer)this); XtAddEventHandler(this->popupList, ButtonReleaseMask, False, (XtEventHandler) TextSelector_RemoveGrabEH, (XtPointer)this); XtAddEventHandler(this->popupList, EnterWindowMask, False, (XtEventHandler) TextSelector_ProcessOldEventEH, (XtPointer)this); XtVaGetValues (XtParent(this->popupList), XmNverticalScrollBar, &this->vsb, XmNhorizontalScrollBar, &this->hsb, NULL); if (cbp) { XtAddCallback (this->popupList, XmNsingleSelectionCallback, cbp, cdata); XtAddCallback (this->popupList, XmNbrowseSelectionCallback, cbp, cdata); XtAddCallback (this->popupList, XmNdefaultActionCallback, cbp, cdata); } this->enableModifyCB(this->item_list.getSize() > 0); } // // Put an entry into the scrolled list for each entry in the dictionary. // void TextSelector::updateList() { // // Clear the list // XmListDeleteAllItems(this->popupList); // // Fill in the list with the contents of our item_list // XmString* strTable = new XmString[this->item_list.getSize()]; int next = 0; ListIterator it(this->item_list); char* name; while (name = (char*)it.getNext()) { strTable[next++] = XmStringCreateLtoR (name, "small_bold"); } XtVaSetValues (this->popupList, XmNvisibleItemCount, (next<=MAX_VISIBLE?next:MAX_VISIBLE), NULL); XtVaSetValues (XtParent(this->popupList), XmNwidth, 150, NULL); // // Show the item as selected in the list and make sure that position is showing. // if (this->selected_items) { int select_this_item = this->selected_items[0]; XmListAddItemsUnselected (this->popupList, strTable, next, 1); XmListSelectPos (this->popupList, select_this_item, False); int toppos; int maxtoppos = 1 + next - MAX_VISIBLE; if (select_this_item <= MAX_VISIBLE) toppos = 1; else toppos = select_this_item - 3; if (toppos >= maxtoppos) toppos = 0; if (toppos) XmListSetPos (this->popupList, toppos); else // // According to the Motif doc passing 0 to this func makes the last // item in the list, the last visible item. I've never seen it work, though. // The result is that if you've selected the last item, then the list will // never be scrolled so that that item is showing. // XmListSetBottomPos (this->popupList, 0); } int i; for (i=0; iis_grabbed) return ; XtUngrabPointer (this->popupList, CurrentTime); XtPopdown (this->popupMenu); if (this->button_release_timer) { XtRemoveTimeOut (this->button_release_timer); this->button_release_timer = 0; } if (this->old_event) { delete this->old_event; this->old_event = NUL(XEvent*); } this->is_grabbed = FALSE; } void TextSelector::grab(XEvent* e) { if (!TextSelector::GrabCursor) { TextSelector::GrabCursor = XCreateFontCursor (XtDisplay(this->popupMenu), XC_arrow); } if (TextSelector::GrabOwner != NUL(TextSelector*)) { if (TextSelector::GrabOwner != this) TextSelector::GrabOwner->ungrab(); else return ; } TextSelector::GrabOwner = this; XtPopup (this->popupMenu, XtGrabNone); XtAppContext apcxt = theApplication->getApplicationContext(); this->action_hook = XtAppAddActionHook (apcxt, (XtActionHookProc) TextSelector_PopupListAH, (XtPointer)this); XtGrabPointer (this->popupList, True, ButtonPressMask | ButtonReleaseMask , GrabModeAsync, GrabModeAsync, None, TextSelector::GrabCursor, e->xbutton.time); this->is_button_release_grabbed = FALSE; if (this->button_release_timer) XtRemoveTimeOut (this->button_release_timer); this->button_release_timer = XtAppAddTimeOut (apcxt, 500, (XtTimerCallbackProc) TextSelector_GrabReleaseTO, (XtPointer)this); // // Squirrel away the XEvent so that it can be processed if the user drags // into the list. Use the following line to process it. // XtCallActionProc (this->popupList, "ListBeginSelect", e, NULL, 0); // You know it needs to be processed if you get EnterWindow on the popupList // with the mouse button down. // if (this->old_event) delete this->old_event; this->old_event = new XEvent; memcpy (this->old_event, e, sizeof(XEvent)); this->is_grabbed = TRUE; } extern "C" { void TextSelector_GrabReleaseTO(XtPointer clientData, XtIntervalId* ) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); psel->is_button_release_grabbed = TRUE; psel->button_release_timer = 0; } void TextSelector_ProcessOldEventEH(Widget w, XtPointer clientData, XEvent *e, Boolean *) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); if ((e->type == EnterNotify) && (psel->old_event)) { XCrossingEvent* xce = (XCrossingEvent*)e; int state = xce->state; if (state & Button1Mask) { XtCallActionProc (psel->popupList, "ListBeginSelect", psel->old_event, NULL, 0); delete psel->old_event; psel->old_event = NUL(XEvent*); } } if (psel->old_event) { delete psel->old_event; psel->old_event = NUL(XEvent*); } } void TextSelector_RemoveGrabEH(Widget w, XtPointer clientData, XEvent *, Boolean *) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); if (psel->is_button_release_grabbed) psel->ungrab(); if (psel->button_release_timer) { XtRemoveTimeOut (psel->button_release_timer); psel->button_release_timer = 0; } psel->is_button_release_grabbed = TRUE; } void TextSelector_EllipsisEH(Widget , XtPointer clientData, XEvent *e, Boolean *) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); if (psel->is_grabbed == TRUE) return ; psel->updateList(); Dimension width, height; Screen *scrptr; XtVaGetValues (XtParent(psel->popupList), XmNwidth, &width, XmNheight, &height, XmNscreen, &scrptr, NULL); Position x = e->xbutton.x_root; Position y = e->xbutton.y_root; x-= e->xbutton.x; y-= e->xbutton.y; y+= SELECTOR_HEIGHT; if ((x+width) > WidthOfScreen(scrptr)) x = WidthOfScreen(scrptr) - (width+4); if ((y+height) > HeightOfScreen(scrptr)) y-= (SELECTOR_HEIGHT + height + 7); XtVaSetValues (psel->popupMenu, XmNx, x, XmNy, y, NULL); e->xbutton.x = e->xbutton.y = 0; psel->grab(e); } // // If the widget of the event is not the popuplist or one of its scrollbars, // then popdown the list. // void TextSelector_PopupListAH (Widget w, XtPointer clientData, String, XEvent* xev, String*, Cardinal*) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); if (psel->is_grabbed) { if ((xev->type == ButtonPress) || (xev->type == KeyPress)) { if ((w != psel->popupList) && (w != psel->hsb) && (w != psel->vsb)) psel->ungrab(); } } else { // // It would be nice to remove the action hook here, but the intrinsics // assume that if you do that inside the action hook proc, then you would // like to reference freed memory and core dump. Usually we prefer not // to do that, so we add a work proc and remove the action hook there. // It isn't harmful to run the action hook too long, anyway. // if (psel->remove_hook_wpid == 0) { XtAppContext apcxt = theApplication->getApplicationContext(); psel->remove_hook_wpid = XtAppAddWorkProc (apcxt, TextSelector_RemoveHookWP, (XtPointer)psel); } } } Boolean TextSelector_RemoveHookWP (XtPointer clientData) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); if (psel->action_hook) XtRemoveActionHook (psel->action_hook); psel->action_hook = 0; psel->remove_hook_wpid = NUL(XtWorkProcId); return TRUE; } } // extern "C" // // Get the text from a text widget and clip off the leading and // trailing white space. // The return string must be deleted by the caller. // char *TextSelector::GetTextWidgetToken(Widget textWidget) { char *name = XmTextGetString(textWidget); ASSERT(name); int i,len = STRLEN(name); for (i=len-1 ; i>=0 ; i--) { if (IsWhiteSpace(name,i)) name[i] = '\0'; else break; } i=0; SkipWhiteSpace(name,i); char *s = DuplicateString(name+i); XtFree(name); return s; } void TextSelector_SelectCB(Widget , XtPointer clientData, XtPointer cbs) { TextSelector* psel = (TextSelector*)clientData; ASSERT(psel); psel->ungrab(); // // Error check hacking... If the mouse event was outside the list widget, // then ignore it. // XmListCallbackStruct *lcs = (XmListCallbackStruct*)cbs; XEvent* xev = lcs->event; boolean inside = TRUE; if ((xev->type == ButtonPress) || (xev->type == ButtonRelease)) { XButtonEvent* xbe = (XButtonEvent*)xev; Dimension width, height; XtVaGetValues (psel->popupList, XmNwidth, &width, XmNheight, &height, NULL); if ((xbe->x < 0) || (xbe->y < 0) || (xbe->x > width) || (xbe->y > height)) inside = FALSE; } // // Make the selection based on list contents // if ((inside) && (lcs->selected_item_count)) { int pos = lcs->item_position; if ((pos >= 1) && (pos <= psel->item_list.getSize())) { // // Put a new item into the text widget // char* cp = (char*)psel->item_list.getElement(lcs->item_position); ASSERT(cp); XmTextSetString (psel->textField, cp); // // Remember what's currently selected // if (psel->selected_items) XtFree((char*)psel->selected_items); boolean sel = XmListGetSelectedPos (psel->popupList, &psel->selected_items, &psel->selected_cnt); if (sel == False) { psel->selected_items = NUL(int*); psel->selected_cnt = 0; } } } } void TextSelector::setMenuColors (Arg args[], int n) { XtSetValues (this->popupMenu, args, n); XtSetValues (XtParent(this->popupList), args, n); XtSetValues (XtParent(XtParent(this->popupList)), args, n); XtSetValues (this->popupList, args, n); XtSetValues (this->vsb, args, n); XtSetValues (this->hsb, args, n); } // // If the proposed modification to the text widget would result in a text // string which is represented in the list, then allow the modification to // proceed, else not. // extern "C" void TextSelector_ModifyCB (Widget, XtPointer cdata, XtPointer callData) { TextSelector* tsel = (TextSelector*)cdata; ASSERT (tsel); XmTextVerifyCallbackStruct *tvcs = (XmTextVerifyCallbackStruct*)callData; boolean good_chars = TRUE; XmTextBlock tbrec = tvcs->text; if ((tbrec->length) && (tvcs->reason == XmCR_MODIFYING_TEXT_VALUE)) { char* cp = XmTextGetString (tsel->textField); int plen = (cp?strlen(cp):0) + tbrec->length + 1; char* proposed = new char[plen]; // // you might expect cp to be if there were no chars in the text // widget but, it really should be "" in that case, so it should never // be . // ASSERT (cp); strcpy (proposed, cp); strcpy (&proposed[tvcs->startPos], tbrec->ptr); strcat (proposed, &cp[tvcs->startPos]); // // Now lookup proposed in the list of items // ListIterator it(tsel->item_list); const char* item; int found = 0; boolean match = FALSE; int most_recent; int i = 1; while (item = (char*)it.getNext()) { if (EqualString (item, cp)) { match = TRUE; most_recent = i; break; } else if (EqualSubstring (item, proposed, plen-1)) { found++; most_recent = i; } i++; } if (match) { } else if (found == 0) good_chars = FALSE; else if (found == 1) { // // This is the place to complete the entry automatically // It can't be done here because the proposed modification to // the string would still happen after you've updated the contents. //tsel->setSelectedItem (most_recent); if (tsel->auto_fill_wpid) XtRemoveWorkProc (tsel->auto_fill_wpid); XtAppContext apcxt = theApplication->getApplicationContext(); tsel->auto_fill_wpid = XtAppAddWorkProc (apcxt, (XtWorkProc) TextSelector_FillWP, (XtPointer) tsel); } if (proposed) delete proposed; } tvcs->doit = (Boolean)good_chars; } extern "C" Boolean TextSelector_FillWP (XtPointer cdata) { TextSelector* tsel = (TextSelector*)cdata; ASSERT (tsel); tsel->auto_fill_wpid = NUL(XtWorkProcId); tsel->autoFill(); return True; } boolean TextSelector::autoFill() { boolean retVal = FALSE; char* cp = XmTextGetString (this->textField); if ((!cp) || (!cp[0])) return FALSE; int len = strlen(cp); // // Now lookup proposed in the list of items // ListIterator it(this->item_list); const char* item; int found = 0; boolean match = FALSE; int most_recent; int i = 1; while (item = (char*)it.getNext()) { if (EqualString (item, cp)) { match = TRUE; most_recent = i; break; } else if (EqualSubstring (item, cp, len)) { found++; most_recent = i; } i++; } XtFree(cp); if ((match) || (found == 1)) { this->setSelectedItem (most_recent); XmListSelectPos (this->popupList, most_recent, True); retVal = TRUE; } return retVal; } extern "C" void TextSelector_ResolveCB (Widget, XtPointer cdata, XtPointer) { TextSelector* tsel = (TextSelector*)cdata; ASSERT (tsel); if (tsel->autoFill() == FALSE) { if (tsel->selected_items) { XmListSelectPos (tsel->popupList, tsel->selected_items[0], True); } else { XmListDeselectAllItems (tsel->popupList); } } }