1/* 2Open Tracker License 3 4Terms and Conditions 5 6Copyright (c) 1991-2001, Be Incorporated. All rights reserved. 7 8Permission is hereby granted, free of charge, to any person obtaining a copy of 9this software and associated documentation files (the "Software"), to deal in 10the Software without restriction, including without limitation the rights to 11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12of the Software, and to permit persons to whom the Software is furnished to do 13so, subject to the following conditions: 14 15The above copyright notice and this permission notice applies to all licensees 16and shall be included in all copies or substantial portions of the Software. 17 18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25Except as contained in this notice, the name of Be Incorporated shall not be 26used in advertising or otherwise to promote the sale, use or other dealings in 27this Software without prior written authorization from Be Incorporated. 28 29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30of Be Incorporated in the United States and other countries. Other brand product 31names are registered trademarks or trademarks of their respective holders. 32All rights reserved. 33*/ 34 35// 36// ComboBox.cpp 37// 38// 39 40/* 41 TODO: 42 - Better up/down arrow handling (if text in input box matches a list item, 43 pressing down should select the next item, pressing up should select the 44 previous. If no item matched, the first or last item should be selected. 45 In any case, pressing up or down should show the popup if it is hidden 46 - Properly draw the label, taking alignment into account 47 - Draw nicer border around text input and popup window 48 - Escaping out of the popup menu should restore the text in the input to the 49 value it had previous to popping up the menu. 50 - Fix popup behavior when the widget is near the bottom of the screen. The 51 popup window should be able to go above the text input area. Also, the popup 52 should size itself in a smart manner so that it is small if there are few 53 choices and large if there are many and the window under it is big. Perhaps 54 the developer should be able to influence the size of the popup. 55 - Improve button drawing and (?) button behavior 56 - Fix and test enable/disable behavior 57 - Add auto-scrolling and/or drag-scrolling to the poup-menu 58 - Add support for other navigation keys, like page up, page down, home, end. 59 - Fix up choice functions (remove choice, add at index, etc) and make sure they 60 properly invalidate/scroll/etc the list when it is visible 61 - Change auto-complete behavior to be non-greedy, or perhaps add some type of 62 tab-cycling to the choices 63 - Add mode whereby you can pop up a list of only those items that match 64*/ 65 66#include <Button.h> 67#include <Debug.h> 68#include <InterfaceDefs.h> 69#include <ListItem.h> 70#include <ListView.h> 71#include <Menu.h> // for menu_info 72#include <MessageFilter.h> 73#include "ObjectList.h" 74#include <ScrollBar.h> 75#include <String.h> 76#include <TextControl.h> 77#include <Window.h> 78#include <stdio.h> 79#include <stdlib.h> 80#include <string.h> 81#include <ctype.h> 82#include "ComboBox.h" 83 84//static const uint32 kTextControlInvokeMessage = 'tCIM'; 85static const uint32 kTextInputModifyMessage = 'tIMM'; 86static const uint32 kPopupButtonInvokeMessage = 'pBIM'; 87static const uint32 kPopupWindowHideMessage = 'pUWH'; 88static const uint32 kWindowMovedMessage = 'wMOV'; 89 90static const float kTextInputMargin = (float)3.0; 91static const float kLabelRightMargin = (float)6.0; 92static const float kButtonWidth = (float)15.0; 93 94#define disable_color(_c_) tint_color(_c_, B_DISABLED_LABEL_TINT) 95 96#define TV_MARGIN 3.0 97#define TV_DIVIDER_MARGIN 6.0 98 99rgb_color create_color(uchar r, uchar g, uchar b, uchar a = 255); 100 101rgb_color create_color(uchar r, uchar g, uchar b, uchar a) { 102 rgb_color col; 103 col.red = r; 104 col.green = g; 105 col.blue = b; 106 col.alpha = a; 107 return col; 108} 109 110class StringObjectList : public BObjectList<BString> {}; 111 112// ---------------------------------------------------------------------------- 113 114// ChoiceListView is similar to a BListView, but it's implementation is tied to 115// BComboBox. BListView is not used because it requires that a BStringItem be 116// created for each choice. ChoiceListView just pulls the choice strings 117// directly from the BComboBox and draws them. 118class BComboBox::ChoiceListView : public BView 119{ 120 public: 121 ChoiceListView( BRect frame, BComboBox *parent); 122 virtual ~ChoiceListView(); 123 124 virtual void Draw(BRect update); 125 virtual void MouseDown(BPoint where); 126 virtual void MouseUp(BPoint where); 127 virtual void MouseMoved(BPoint where, uint32 transit, 128 const BMessage *dragMessage); 129 virtual void KeyDown(const char *bytes, int32 numBytes); 130 virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL); 131 132 void ScrollToSelection(); 133 void InvalidateItem(int32 index, bool force = false); 134 BRect ItemFrame(int32 index); 135 void AdjustScrollBar(); 136 // XXX: add BArchivable functionality 137 138 private: 139 inline float LineHeight(); 140 141 BPoint fClickLoc; 142 font_height fFontHeight; 143 bigtime_t fClickTime; 144 rgb_color fForeCol; 145 rgb_color fBackCol; 146 rgb_color fSelCol; 147 int32 fSelIndex; 148 BComboBox *fParent; 149 bool fTrackingMouseDown; 150}; 151 152 153// ---------------------------------------------------------------------------- 154 155// TextInput is a somewhat modified version of the _BTextInput_ class defined 156// in TextControl.cpp. 157 158class BComboBox::TextInput : public BTextView { 159 public: 160 TextInput(BRect rect, BRect trect, ulong rMask, ulong flags); 161 TextInput(BMessage *data); 162 virtual ~TextInput(); 163 static BArchivable *Instantiate(BMessage *data); 164 virtual status_t Archive(BMessage *data, bool deep = true) const; 165 166 virtual void KeyDown(const char *bytes, int32 numBytes); 167 virtual void MakeFocus(bool state); 168 virtual void FrameResized(float x, float y); 169 virtual void Paste(BClipboard *clipboard); 170 171 void AlignTextRect(); 172 173 void SetInitialText(); 174 void SetFilter(text_input_filter_hook hook); 175 176 // XXX: add BArchivable functionality 177 178 protected: 179 virtual void InsertText(const char *inText, int32 inLength, int32 inOffset, 180 const text_run_array *inRuns); 181 virtual void DeleteText(int32 fromOffset, int32 toOffset); 182 183 private: 184 char *fInitialText; 185 text_input_filter_hook fFilter; 186 bool fClean; 187}; 188 189// ---------------------------------------------------------------------------- 190 191class BComboBox::ComboBoxWindow : public BWindow 192{ 193 public: 194 ComboBoxWindow(BComboBox *box); 195 virtual ~ComboBoxWindow(); 196 virtual void WindowActivated(bool active); 197 virtual void FrameResized(float width, float height); 198 199 void DoPosition(); 200 BComboBox::ChoiceListView *ListView(); 201 BScrollBar *ScrollBar(); 202 203 // XXX: add BArchivable functionality 204 205 private: 206 BScrollBar *fScrollBar; 207 ChoiceListView *fListView; 208 BComboBox *fParent; 209}; 210 211// ---------------------------------------------------------------------------- 212 213// In BeOS R4.5, SetEventMask(B_POINTER_EVENTS, ...) does not work for getting 214// all mouse events as they happen. Specifically, when the user clicks on the 215// window dressing (the borders or the title tab) no mouse event will be 216// delivered until after the user releases the mouse button. This has the 217// unfortunate side effect of allowing the user to move the window that 218// contains the BComboBox around with no notification being sent to the 219// BComboBox. We need to intercept the B_WINDOW_MOVED messages so that we can 220// hide the popup window when the window moves. 221 222class BComboBox::MovedMessageFilter : public BMessageFilter 223{ 224 public: 225 MovedMessageFilter(BHandler *target); 226 virtual filter_result Filter(BMessage *message, BHandler **target); 227 228 private: 229 BHandler *fTarget; 230}; 231 232 233// ---------------------------------------------------------------------------- 234 235 236BComboBox::ChoiceListView::ChoiceListView(BRect frame, BComboBox *parent) 237 : BView(frame, "_choice_list_view_", B_FOLLOW_ALL_SIDES, B_WILL_DRAW 238 | B_NAVIGABLE), 239 fClickLoc(-100, -100) 240{ 241 fParent = parent; 242 GetFontHeight(&fFontHeight); 243 menu_info mi; 244 get_menu_info(&mi); 245 fForeCol = create_color(0, 0, 0); 246 fBackCol = mi.background_color; 247 fSelCol = create_color(144, 144, 144); 248 SetViewColor(B_TRANSPARENT_COLOR); 249 SetHighColor(fForeCol); 250 fTrackingMouseDown = false; 251 fClickTime = 0; 252} 253 254 255BComboBox::ChoiceListView::~ChoiceListView() 256{ 257} 258 259 260void BComboBox::ChoiceListView::Draw(BRect update) 261{ 262 float h = LineHeight(); 263 BRect rect(Bounds()); 264 int32 index; 265 int32 choices = fParent->fChoiceList->CountChoices(); 266 int32 selected = (fTrackingMouseDown) ? fSelIndex : fParent->CurrentSelection(); 267 268 // draw each visible item 269 for (index = (int32)floor(update.top / h); index < choices; index++) 270 { 271 rect.top = index * h; 272 rect.bottom = rect.top + h; 273 SetLowColor((index == selected) ? fSelCol : fBackCol); 274 FillRect(rect, B_SOLID_LOW); 275 DrawString(fParent->fChoiceList->ChoiceAt(index), BPoint(rect.left + 2, 276 rect.bottom - fFontHeight.descent - 1)); 277 } 278 279 // draw empty area on bottom 280 if (rect.bottom < update.bottom) 281 { 282 update.top = rect.bottom; 283 SetLowColor(fBackCol); 284 FillRect(update, B_SOLID_LOW); 285 } 286} 287 288 289void BComboBox::ChoiceListView::MouseDown(BPoint where) 290{ 291 BRect rect(Window()->Frame()); 292 ConvertFromScreen(&rect); 293 if (!rect.Contains(where)) 294 { 295 // hide the popup window when the user clicks outside of it 296 if (fParent->Window()->Lock()) 297 { 298 fParent->HidePopupWindow(); 299 fParent->Window()->Unlock(); 300 } 301 302 // HACK: the window is locked and unlocked so that it will get 303 // activated before we potentially send the mouse down event in the 304 // code below. Is there a way to wait until the window is activated 305 // before sending the mouse down? Should we call 306 // fParent->Window()->MakeActive(true) here? 307 308 if (fParent->Window()->Lock()) 309 { 310 // resend the mouse event to the textinput, if necessary 311 BTextView *text = fParent->TextView(); 312 BPoint screenWhere(ConvertToScreen(where)); 313 rect = text->Window()->ConvertToScreen(text->Frame()); 314 if (rect.Contains(screenWhere)) 315 { 316 //printf(" resending mouse down to textinput\n"); 317 BMessage *msg = new BMessage(*Window()->CurrentMessage()); 318 msg->RemoveName("be:view_where"); 319 text->ConvertFromScreen(&screenWhere); 320 msg->AddPoint("be:view_where", screenWhere); 321 text->Window()->PostMessage(msg, text); 322 delete msg; 323 } 324 fParent->Window()->Unlock(); 325 } 326 327 return; 328 } 329 330 rect = Bounds(); 331 if (!rect.Contains(where)) 332 return; 333 334 fTrackingMouseDown = true; 335 // check for double click 336 bigtime_t now = system_time(); 337 bigtime_t clickSpeed; 338 get_click_speed(&clickSpeed); 339 if ((now - fClickTime < clickSpeed) 340 && ((abs((int)(fClickLoc.x - where.x)) < 3) 341 && (abs((int)(fClickLoc.y - where.y)) < 3))) 342 { 343 // this is a double click 344 // XXX: what to do here? 345 printf("BComboBox::ChoiceListView::MouseDown() -- unhandled double click\n"); 346 } 347 fClickTime = now; 348 fClickLoc = where; 349 350 float h = LineHeight(); 351 int32 oldIndex = fSelIndex; 352 fSelIndex = (int32)floor(where.y / h); 353 int32 choices = fParent->fChoiceList->CountChoices(); 354 if (fSelIndex < 0 || fSelIndex >= choices) 355 fSelIndex = -1; 356 357 if (oldIndex != fSelIndex) 358 { 359 InvalidateItem(oldIndex); 360 InvalidateItem(fSelIndex); 361 } 362 // XXX: this probably isn't necessary since we are doing a SetEventMask 363 // whenever the popup window becomes visible which routes all mouse events 364 // to this view 365// SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 366} 367 368 369void BComboBox::ChoiceListView::MouseUp(BPoint /*where*/) 370{ 371 if (fTrackingMouseDown) 372 { 373 fTrackingMouseDown = false; 374 if (fSelIndex >= 0) 375 fParent->Select(fSelIndex, true); 376 else 377 fParent->Deselect(); 378 } 379// fClickLoc = where; 380} 381 382 383void BComboBox::ChoiceListView::MouseMoved(BPoint where, uint32 /*transit*/, 384 const BMessage */*dragMessage*/) 385{ 386 if (fTrackingMouseDown) 387 { 388 float h = LineHeight(); 389 int32 oldIndex = fSelIndex; 390 fSelIndex = (int32)floor(where.y / h); 391 int32 choices = fParent->fChoiceList->CountChoices(); 392 if (fSelIndex < 0 || fSelIndex >= choices) 393 fSelIndex = -1; 394 395 if (oldIndex != fSelIndex) 396 { 397 InvalidateItem(oldIndex); 398 InvalidateItem(fSelIndex); 399 } 400 } 401} 402 403 404void BComboBox::ChoiceListView::KeyDown(const char *bytes, int32 /*numBytes*/) 405{ 406 BComboBox *cb = fParent; 407 BWindow *win = cb->Window(); 408 BComboBox::TextInput *text = dynamic_cast<BComboBox::TextInput*>(cb->TextView()); 409 uchar aKey = bytes[0]; 410 411 switch (aKey) 412 { 413 case B_UP_ARROW: // fall through 414 case B_DOWN_ARROW: 415 if (win->Lock()) 416 { 417 // change the selection 418 int32 index = cb->CurrentSelection(); 419 int32 choices = cb->fChoiceList->CountChoices(); 420 if (choices > 0) 421 { 422 if (index < 0) 423 { 424 // no previous selection, so select first or last item 425 // depending on whether this is a up or down arrow 426 cb->Select((aKey == B_UP_ARROW) ? choices - 1 : 0); 427 } 428 else 429 { 430 // select the previous or the next item, if possible, 431 // depending on whether this is an up or down arrow 432 if (aKey == B_UP_ARROW && (index - 1 >= 0)) 433 cb->Select(index - 1, true); 434 else if (aKey == B_DOWN_ARROW && (index + 1 < choices)) 435 cb->Select(index + 1, true); 436 } 437 } 438 win->Unlock(); 439 } 440 break; 441 default: 442 { // send all other key down events to the text input view 443 BMessage *msg = Window()->DetachCurrentMessage(); 444 if (msg) { 445 win->PostMessage(msg, text); 446 delete msg; 447 } 448 break; 449 } 450 } 451} 452 453 454void BComboBox::ChoiceListView::SetFont(const BFont *font, uint32 properties) 455{ 456 BView::SetFont(font, properties); 457 GetFontHeight(&fFontHeight); 458 Invalidate(); 459} 460 461 462void BComboBox::ChoiceListView::ScrollToSelection() 463{ 464 int32 selected = fParent->CurrentSelection(); 465 if (selected >= 0) 466 { 467 BRect frame(ItemFrame(selected)); 468 BRect bounds(Bounds()); 469 float newY = -1.0; // dummy value -- not used 470 bool doScroll = false; 471 472 if (frame.bottom > bounds.bottom) 473 { 474 newY = frame.bottom - bounds.Height(); 475 doScroll = true; 476 } 477 else if (frame.top < bounds.top) 478 { 479 newY = frame.top; 480 doScroll = true; 481 } 482 if (doScroll) 483 ScrollTo(bounds.left, newY); 484 } 485} 486 487// InvalidateItem() only does a real invalidate if the index is valid or the 488// force flag is turned on 489 490void BComboBox::ChoiceListView::InvalidateItem(int32 index, bool force) 491{ 492 int32 choices = fParent->fChoiceList->CountChoices(); 493 if ((index >= 0 && index < choices) || force) { 494 Invalidate(ItemFrame(index)); 495 } 496} 497 498// This method doesn't check the index to see if it is valid, it just returns 499// the BRect that an item and the index would have if it existed. 500 501BRect BComboBox::ChoiceListView::ItemFrame(int32 index) 502{ 503 BRect rect(Bounds()); 504 float h = LineHeight(); 505 rect.top = index * h; 506 rect.bottom = rect.top + h; 507 return rect; 508} 509 510// The window must be locked before this method is called 511 512void BComboBox::ChoiceListView::AdjustScrollBar() 513{ 514 BScrollBar *sb = ScrollBar(B_VERTICAL); 515 if (sb) { 516 float h = LineHeight(); 517 float max = h * fParent->fChoiceList->CountChoices(); 518 BRect frame(Frame()); 519 float diff = max - frame.Height(); 520 float prop = frame.Height() / max; 521 if (diff < 0) { 522 diff = 0.0; 523 prop = 1.0; 524 } 525 sb->SetSteps(h, h * (frame.IntegerHeight() / h)); 526 sb->SetRange(0.0, diff); 527 sb->SetProportion(prop); 528 } 529} 530 531 532float BComboBox::ChoiceListView::LineHeight() 533{ 534 return fFontHeight.ascent + fFontHeight.descent + fFontHeight.leading + 2; 535} 536 537 538// ---------------------------------------------------------------------------- 539// #pragma mark - 540 541 542BComboBox::TextInput::TextInput(BRect rect, BRect textRect, ulong rMask, 543 ulong flags) 544 : BTextView(rect, "_input_", textRect, be_plain_font, NULL, rMask, flags), 545 fFilter(NULL) 546{ 547 MakeResizable(true); 548 fInitialText = NULL; 549 fClean = false; 550} 551 552 553BComboBox::TextInput::TextInput(BMessage *data) 554 : BTextView(data), 555 fFilter(NULL) 556{ 557 MakeResizable(true); 558 fInitialText = NULL; 559 fClean = false; 560} 561 562 563BComboBox::TextInput::~TextInput() 564{ 565 free(fInitialText); 566} 567 568 569status_t 570BComboBox::TextInput::Archive(BMessage* data, bool /*deep*/) const 571{ 572 return BTextView::Archive(data); 573} 574 575 576BArchivable * 577BComboBox::TextInput::Instantiate(BMessage* data) 578{ 579 // XXX: is "TextInput" the correct name for this class? Perhaps 580 // BComboBox::TextInput? 581 if (!validate_instantiation(data, "TextInput")) 582 return NULL; 583 584 return new TextInput(data); 585} 586 587 588void 589BComboBox::TextInput::SetInitialText() 590{ 591 if (fInitialText) { 592 free(fInitialText); 593 fInitialText = NULL; 594 } 595 if (Text()) 596 fInitialText = strdup(Text()); 597} 598 599 600void 601BComboBox::TextInput::SetFilter(text_input_filter_hook hook) 602{ 603 fFilter = hook; 604} 605 606 607void 608BComboBox::TextInput::KeyDown(const char *bytes, int32 numBytes) 609{ 610 BComboBox* cb; 611 uchar aKey = bytes[0]; 612 613 switch (aKey) { 614 case B_RETURN: 615 cb = dynamic_cast<BComboBox*>(Parent()); 616 ASSERT(cb); 617 618 if (!cb->IsEnabled()) 619 break; 620 621 ASSERT(fInitialText); 622 if (strcmp(fInitialText, Text()) != 0) 623 cb->CommitValue(); 624 free(fInitialText); 625 fInitialText = strdup(Text()); 626 { 627 int32 end = TextLength(); 628 Select(end, end); 629 } 630 // hide popup window if it's showing when the user presses the 631 // enter key 632 if (cb->fPopupWindow && cb->fPopupWindow->Lock()) { 633 if (!cb->fPopupWindow->IsHidden()) { 634 cb->HidePopupWindow(); 635 } 636 cb->fPopupWindow->Unlock(); 637 } 638 break; 639 case B_TAB: 640// cb = dynamic_cast<BComboBox*>Parent()); 641// ASSERT(cb); 642// if (cb->fAutoComplete && cb->fCompletionIndex >= 0) { 643// int32 from, to; 644// cb->fText->GetSelection(&from, &to); 645// if (from == to) { 646// // HACK: this should never happen. The rest of the class 647// // should be fixed so that fCompletionIndex is set to -1 if the 648// // text is modified 649// printf("BComboBox::TextInput::KeyDown() -- HACK! this shouldn't happen!"); 650// cb->fCompletionIndex = -1; 651// } 652// 653// const char *text = cb->fText->Text(); 654// BString prefix; 655// prefix.Append(text, from); 656// 657// int32 match; 658// const char *completion; 659// if (cb->fChoiceList->GetMatch( prefix.String(), 660// cb->fCompletionIndex + 1, 661// &match, 662// &completion) == B_OK) 663// { 664// cb->fText->Delete(); // delete the selection 665// cb->fText->Insert(completion); 666// cb->fText->Select(from, from + strlen(completion)); 667// cb->fCompletionIndex = match; 668// cb->Select(cb->fCompletionIndex); 669// } else { 670// //system_beep(); 671// } 672// } else { 673 BView::KeyDown(bytes, numBytes); 674// } 675 break; 676#if 0 677 case B_UP_ARROW: // fall through 678 case B_DOWN_ARROW: 679 cb = dynamic_cast<BComboBox*>(Parent()); 680 ASSERT(cb); 681 if (cb->fChoiceList) { 682 cb = dynamic_cast<BComboBox*>(Parent()); 683 ASSERT(cb); 684 if (!(cb->fPopupWindow)) { 685 cb->fPopupWindow = cb->CreatePopupWindow(); 686 } 687 if (cb->fPopupWindow->Lock()) { 688 // show popup window, if needed 689 if (cb->fPopupWindow->IsHidden()) { 690 cb->ShowPopupWindow(); 691 } else { 692 printf("Whoa!!! Erroneously got up/down arrow key down in TextInput::KeyDown()!\n"); 693 } 694 int32 index = cb->CurrentSelection(); 695 int32 choices = cb->fChoiceList->CountChoices(); 696 // select something, if no selection 697 if (index < 0 && choices > 0) { 698 if (aKey == B_UP_ARROW) { 699 cb->Select(choices - 1); 700 } else { 701 cb->Select(0); 702 } 703 } 704 cb->fPopupWindow->Unlock(); 705 } 706 } 707 break; 708#endif 709 case B_ESCAPE: 710 cb = dynamic_cast<BComboBox*>(Parent()); 711 ASSERT(cb); 712 if (cb->fChoiceList) 713 { 714 cb = dynamic_cast<BComboBox*>(Parent()); 715 ASSERT(cb); 716 if (cb->fPopupWindow && cb->fPopupWindow->Lock()) 717 { 718 if (!cb->fPopupWindow->IsHidden()) 719 cb->HidePopupWindow(); 720 721 cb->fPopupWindow->Unlock(); 722 } 723 } 724 break; 725 case ',': 726 { 727 int32 startSel, endSel; 728 GetSelection(&startSel, &endSel); 729 int32 length = TextLength(); 730 if (endSel == length) 731 Select(endSel, endSel); 732 BTextView::KeyDown(bytes, numBytes); 733 } 734 break; 735 default: 736 BTextView::KeyDown(bytes, numBytes); 737 break; 738 } 739} 740 741 742void 743BComboBox::TextInput::MakeFocus(bool state) 744{ 745//+ PRINT(("_BTextInput_::MakeFocus(state=%d, view=%s)\n", state, 746//+ Parent()->Name())); 747 if (state == IsFocus()) 748 return; 749 750 BComboBox* parent = dynamic_cast<BComboBox*>(Parent()); 751 ASSERT(parent); 752 753 BTextView::MakeFocus(state); 754 755 if (state) { 756 SetInitialText(); 757 fClean = true; // text hasn't been dirtied yet. 758 759 BMessage *m; 760 if (Window() && (m = Window()->CurrentMessage()) != 0 761 && m->what == B_KEY_DOWN) { 762 // we're being focused by a keyboard event, so 763 // select all... 764 SelectAll(); 765 } 766 } else { 767 ASSERT(fInitialText); 768 if (strcmp(fInitialText, Text()) != 0) 769 parent->CommitValue(); 770 771 free(fInitialText); 772 fInitialText = NULL; 773 fClean = false; 774 BMessage *m; 775 if (Window() && (m = Window()->CurrentMessage()) != 0 && m->what == B_MOUSE_DOWN) 776 Select(0,0); 777 778 // hide popup window if it's showing when the text input loses focus 779 if (parent->fPopupWindow && parent->fPopupWindow->Lock()) { 780 if (!parent->fPopupWindow->IsHidden()) 781 parent->HidePopupWindow(); 782 783 parent->fPopupWindow->Unlock(); 784 } 785 } 786 787 // make sure the focus indicator gets drawn or undrawn 788 if (Window()) { 789 BRect invalRect(Bounds()); 790 invalRect.InsetBy(-kTextInputMargin, -kTextInputMargin); 791 parent->Draw(invalRect); 792 parent->Flush(); 793 } 794} 795 796 797void 798BComboBox::TextInput::FrameResized(float x, float y) 799{ 800 BTextView::FrameResized(x, y); 801 AlignTextRect(); 802} 803 804 805void 806BComboBox::TextInput::Paste(BClipboard *clipboard) 807{ 808 BTextView::Paste(clipboard); 809 Invalidate(); 810} 811 812 813// What a hack... 814void 815BComboBox::TextInput::AlignTextRect() 816{ 817 BRect bounds = Bounds(); 818 BRect textRect = TextRect(); 819 820 switch (Alignment()) { 821 default: 822 case B_ALIGN_LEFT: 823 textRect.OffsetTo(B_ORIGIN); 824 break; 825 826 case B_ALIGN_CENTER: 827 textRect.OffsetTo((bounds.Width() - textRect.Width()) / 2, 828 textRect.top); 829 break; 830 831 case B_ALIGN_RIGHT: 832 textRect.OffsetTo(bounds.Width() - textRect.Width(), textRect.top); 833 break; 834 } 835 836 SetTextRect(textRect); 837} 838 839 840void 841BComboBox::TextInput::InsertText(const char *inText, int32 inLength, 842 int32 inOffset, const text_run_array *inRuns) 843{ 844 char* ptr = NULL; 845 846 // strip out any return characters 847 // limiting to a reasonable amount of chars for a text control. 848 // otherwise this code could malloc some huge amount which isn't good. 849 if (strpbrk(inText, "\r\n") && inLength <= 1024) { 850 int32 len = inLength; 851 ptr = (char *)malloc(len + 1); 852 if (ptr) { 853 strncpy(ptr, inText, len); 854 ptr[len] = '\0'; 855 856 char *p = ptr; 857 858 while (len--) { 859 if (*p == '\n') 860 *p = ' '; 861 else if (*p == '\r') 862 *p = ' '; 863 864 p++; 865 } 866 } 867 } 868 869 if (fFilter != NULL) 870 inText = fFilter(inText, inLength, inRuns); 871 BTextView::InsertText(ptr ? ptr : inText, inLength, inOffset, inRuns); 872 873 BComboBox *parent = dynamic_cast<BComboBox *>(Parent()); 874 if (parent) { 875 if (parent->fModificationMessage) 876 parent->Invoke(parent->fModificationMessage); 877 878 BMessage *msg; 879 parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), 880 parent); 881 delete msg; 882 } 883 884 if (ptr) 885 free(ptr); 886} 887 888 889void 890BComboBox::TextInput::DeleteText(int32 fromOffset, int32 toOffset) 891{ 892 BTextView::DeleteText(fromOffset, toOffset); 893 BComboBox *parent = dynamic_cast<BComboBox *>(Parent()); 894 if (parent) { 895 if (parent->fModificationMessage) 896 parent->Invoke(parent->fModificationMessage); 897 898 BMessage *msg; 899 parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), 900 parent); 901 delete msg; 902 } 903} 904 905 906// #pragma mark - 907 908 909BComboBox::ComboBoxWindow::ComboBoxWindow(BComboBox *box) 910 : BWindow(BRect(0, 0, 10, 10), NULL, B_BORDERED_WINDOW_LOOK, 911 B_FLOATING_SUBSET_WINDOW_FEEL, B_NOT_MOVABLE | B_NOT_RESIZABLE 912 | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE 913 | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS) 914{ 915 fParent = box; 916 DoPosition(); 917 BWindow *parentWin = fParent->Window(); 918 if (parentWin) 919 AddToSubset(parentWin); 920 921 BRect rect(Bounds()); 922 rect.right -= B_V_SCROLL_BAR_WIDTH; 923 fListView = new ChoiceListView(rect, fParent); 924 AddChild(fListView); 925 rect.left = rect.right; 926 rect.right += B_V_SCROLL_BAR_WIDTH; 927 fScrollBar = new BScrollBar(rect, "_popup_scroll_bar_", fListView, 0, 1000, 928 B_VERTICAL); 929 AddChild(fScrollBar); 930 fListView->AdjustScrollBar(); 931} 932 933 934BComboBox::ComboBoxWindow::~ComboBoxWindow() 935{ 936 fListView->RemoveSelf(); 937 delete fListView; 938} 939 940 941void BComboBox::ComboBoxWindow::WindowActivated(bool /*active*/) 942{ 943// if (active) 944// fListView->AdjustScrollBar(); 945} 946 947 948void BComboBox::ComboBoxWindow::FrameResized(float /*width*/, float /*height*/) 949{ 950 fListView->AdjustScrollBar(); 951} 952 953 954void BComboBox::ComboBoxWindow::DoPosition() 955{ 956 BRect winRect(fParent->fText->Frame()); 957 winRect = fParent->ConvertToScreen(winRect); 958// winRect.left += fParent->Divider() + 5; 959 winRect.right -= 2; 960 winRect.OffsetTo(winRect.left, winRect.bottom + kTextInputMargin); 961 winRect.bottom = winRect.top + 100; 962 MoveTo(winRect.LeftTop()); 963 ResizeTo(winRect.IntegerWidth(), winRect.IntegerHeight()); 964} 965 966 967BComboBox::ChoiceListView *BComboBox::ComboBoxWindow::ListView() 968{ 969 return fListView; 970} 971 972 973BScrollBar *BComboBox::ComboBoxWindow::ScrollBar() 974{ 975 return fScrollBar; 976} 977 978 979// ---------------------------------------------------------------------------- 980// #pragma mark - 981 982 983BComboBox::BComboBox(BRect frame, const char *name, const char *label, 984 BMessage *message, uint32 resizeMask, uint32 flags) 985 : BControl(frame, name, label, message, resizeMask, 986 flags | B_WILL_DRAW | B_FRAME_EVENTS), 987 fPopupWindow(NULL), 988 fModificationMessage(NULL), 989 fChoiceList(0), 990 fLabelAlign(B_ALIGN_LEFT), 991 fAutoComplete(false), 992 fButtonDepressed(false), 993 fDepressedWhenClicked(false), 994 fTrackingButtonDown(false), 995 fFrameCache(frame) 996{ 997 // If the user wants this control to be keyboard navigable, then we really 998 // want the underlying text view to be navigable, not this view. 999 bool navigate = ((Flags() & B_NAVIGABLE) != 0); 1000 if (navigate) 1001 { 1002 fSkipSetFlags = true; 1003 SetFlags(Flags() & ~B_NAVIGABLE); // disable navigation for this 1004 fSkipSetFlags = false; 1005 } 1006 1007 fDivider = StringWidth(label); 1008 1009 BRect rect(frame); 1010 rect.OffsetTo(0, 0); 1011 rect.left += fDivider + kLabelRightMargin; 1012// rect.right -= kButtonWidth + 1; 1013// rect.right; 1014 rect.InsetBy(kTextInputMargin, kTextInputMargin); 1015 BRect textRect(rect); 1016 textRect.OffsetTo(0, 0); 1017 textRect.left += 2; 1018 textRect.right -= 2; 1019 1020 fText = new TextInput(rect, textRect, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, 1021 B_WILL_DRAW | B_FRAME_EVENTS | (navigate ? B_NAVIGABLE : 0)); 1022 float height = fText->LineHeight(); 1023 rect.bottom = rect.top + height; 1024// fText->ResizeTo(rect.IntegerWidth(), height); 1025 AddChild(fText); 1026 1027 font_height fontInfo; 1028 GetFontHeight(&fontInfo); 1029 float h1 = ceil(fontInfo.ascent + fontInfo.descent + fontInfo.leading); 1030 float h2 = fText->LineHeight(); 1031 1032 // Height of main view must be the larger of h1 and h2+(TV_MARGIN*2) 1033 float h = (h1 > h2 + (TV_MARGIN*2)) ? h1 : h2 + (TV_MARGIN*2); 1034 BRect b = Bounds(); 1035 ResizeTo(b.Width(), h); 1036 b.bottom = h; 1037 1038 // set height and position of text entry view 1039 fText->ResizeTo(fText->Bounds().Width(), h2); 1040 // vertically center this view 1041 fText->MoveBy(0, (b.Height() - (h2+(TV_MARGIN*2))) / 2); 1042 1043 rect.left = rect.right + 1; 1044 rect.right = rect.left + kButtonWidth; 1045 1046 fButtonRect = rect; 1047 fTextEnd = 0; 1048 fSelected = -1; 1049 fCompletionIndex = -1; 1050 fWinMovedFilter = new MovedMessageFilter(this); 1051} 1052 1053 1054BComboBox::~BComboBox() 1055{ 1056 if (fPopupWindow && fPopupWindow->Lock()) 1057 fPopupWindow->Quit(); 1058 1059 RemoveChild(fText); 1060 delete fText; 1061 1062 if (fWinMovedFilter->Looper()) 1063 fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); 1064 1065 delete fWinMovedFilter; 1066 1067} 1068 1069 1070void BComboBox::SetChoiceList(BChoiceList *list) 1071{ 1072// delete fChoiceList; 1073 fChoiceList = list; 1074 ChoiceListUpdated(); 1075} 1076 1077 1078BChoiceList *BComboBox::ChoiceList() 1079{ 1080 return fChoiceList; 1081} 1082 1083 1084void BComboBox::ChoiceListUpdated() 1085{ 1086 if (fPopupWindow && fPopupWindow->Lock()) 1087 { 1088 if (!fPopupWindow->IsHidden()) 1089 { 1090 // do an invalidate on the choice list 1091 fPopupWindow->ListView()->Invalidate(); 1092 fPopupWindow->ListView()->AdjustScrollBar(); 1093 // XXX: change the selection and select the proper item, if possible 1094 } 1095 fPopupWindow->Unlock(); 1096 } 1097} 1098 1099 1100//void BComboBox::AddChoice(const char *text) 1101//{ 1102// fChoiceList.AddItem((char *)text); 1103// if (fPopupWindow && fPopupWindow->Lock()) { 1104// if (!fPopupWindow->IsHidden()) { 1105// // do an invalidate on the new item's location 1106// int32 index = CountChoices() - 1; 1107// fPopupWindow->ListView()->InvalidateItem(index); 1108// fPopupWindow->ListView()->AdjustScrollBar(); 1109// } 1110// fPopupWindow->Unlock(); 1111// } 1112//} 1113 1114 1115//const char *BComboBox::ChoiceAt(int32 index) 1116//{ 1117// return (const char *)fChoiceList.ItemAt(index); 1118//} 1119 1120 1121//int32 BComboBox::CountChoices() 1122//{ 1123// return fChoiceList.CountItems(); 1124//} 1125 1126 1127void 1128BComboBox::Select(int32 index, bool changeTextSelection) 1129{ 1130 int32 oldIndex = fSelected; 1131 if (index < fChoiceList->CountChoices() && index >= 0) { 1132 BWindow *win = Window(); 1133 bool gotLock = (win && win->Lock()); 1134 if (!win || gotLock) { 1135 fSelected = index; 1136 if (fPopupWindow && fPopupWindow->Lock()) { 1137 ChoiceListView *lv = fPopupWindow->ListView(); 1138 lv->InvalidateItem(oldIndex); 1139 lv->InvalidateItem(fSelected); 1140 lv->ScrollToSelection(); 1141 fPopupWindow->Unlock(); 1142 } 1143 1144 if (changeTextSelection) { 1145 // Find last coma 1146 const char *ptr = fText->Text(); 1147 const char *end; 1148 int32 tlength = fText->TextLength(); 1149 1150 for (end = ptr+tlength-1; end>ptr; end--) { 1151 if (*end == ',') { 1152 // Find end of whitespace 1153 for (end++; isspace(*end); end++) {} 1154 break; 1155 } 1156 } 1157 int32 soffset = end-ptr; 1158 int32 eoffset = tlength; 1159 if (end != 0) 1160 fText->Delete(soffset, eoffset); 1161 1162 tlength = strlen(fChoiceList->ChoiceAt(fSelected)); 1163 fText->Insert(soffset, fChoiceList->ChoiceAt(fSelected), tlength); 1164 eoffset = fText->TextLength(); 1165 fText->Select(soffset, eoffset); 1166// fText->SetText(fChoiceList->ChoiceAt(fSelected)); 1167// fText->SelectAll(); 1168 } 1169 1170 if (gotLock) 1171 win->Unlock(); 1172 } 1173 } else { 1174 Deselect(); 1175 return; 1176 } 1177} 1178 1179 1180void 1181BComboBox::Deselect() 1182{ 1183 BWindow *win = Window(); 1184 bool gotLock = (win && win->Lock()); 1185 if (!win || gotLock) { 1186 int32 oldIndex = fSelected; 1187 fSelected = -1; 1188 // invalidate the old selected item, if needed 1189 if (oldIndex >= 0 && fPopupWindow && fPopupWindow->Lock()) { 1190 fPopupWindow->ListView()->InvalidateItem(oldIndex); 1191 fPopupWindow->Unlock(); 1192 } 1193 1194 if (gotLock) 1195 win->Unlock(); 1196 } 1197} 1198 1199 1200int32 1201BComboBox::CurrentSelection() 1202{ 1203 return fSelected; 1204} 1205 1206 1207void 1208BComboBox::SetAutoComplete(bool on) 1209{ 1210 fAutoComplete = on; 1211} 1212 1213 1214bool 1215BComboBox::GetAutoComplete() 1216{ 1217 return fAutoComplete; 1218} 1219 1220 1221void 1222BComboBox::SetLabel(const char *text) 1223{ 1224 BControl::SetLabel(text); 1225 BRect invalRect = Bounds(); 1226 invalRect.right = fDivider; 1227 Invalidate(invalRect); 1228} 1229 1230 1231void 1232BComboBox::SetValue(int32 value) 1233{ 1234 BControl::SetValue(value); 1235} 1236 1237 1238void 1239BComboBox::SetText(const char *text) 1240{ 1241 fText->SetText(text); 1242 if (fText->IsFocus()) 1243 fText->SetInitialText(); 1244 1245 fText->Invalidate(); 1246} 1247 1248 1249const char * 1250BComboBox::Text() const 1251{ 1252 return fText->Text(); 1253} 1254 1255 1256BTextView * 1257BComboBox::TextView() 1258{ 1259 return fText; 1260} 1261 1262 1263void 1264BComboBox::SetDivider(float divide) 1265{ 1266 float diff = fDivider - divide; 1267 fDivider = divide; 1268 1269 fText->MoveBy(-diff, 0); 1270 fText->ResizeBy(diff, 0); 1271 1272 if (Window()) { 1273 fText->Invalidate(); 1274 Invalidate(); 1275 } 1276} 1277 1278 1279float 1280BComboBox::Divider() const 1281{ 1282 return fDivider; 1283} 1284 1285 1286void 1287BComboBox::SetAlignment(alignment label, alignment text) 1288{ 1289 fText->SetAlignment(text); 1290 fText->AlignTextRect(); 1291 1292 if (fLabelAlign != label) { 1293 fLabelAlign = label; 1294 Invalidate(); 1295 } 1296} 1297 1298 1299void 1300BComboBox::GetAlignment(alignment *label, alignment *text) const 1301{ 1302 *text = fText->Alignment(); 1303 *label = fLabelAlign; 1304} 1305 1306 1307void 1308BComboBox::SetModificationMessage(BMessage *message) 1309{ 1310 delete fModificationMessage; 1311 fModificationMessage = message; 1312} 1313 1314 1315BMessage * 1316BComboBox::ModificationMessage() const 1317{ 1318 return fModificationMessage; 1319} 1320 1321 1322void 1323BComboBox::SetFilter(text_input_filter_hook hook) 1324{ 1325 fText->SetFilter(hook); 1326} 1327 1328 1329void 1330BComboBox::GetPreferredSize(float */*width*/, float */*height*/) 1331{ 1332// BFont font; 1333// GetFont(&font); 1334// 1335// *width = Bounds().IntegerWidth(); 1336// if (Label() != NULL) { 1337// float strWidth = font.StringWidth(Label()); 1338// *width = ceil(kTextInputMargin + strWidth + kLabelRightMargin + 1339// (strWidth * 1.50) + kTextInputMargin); 1340// } 1341// 1342// font_height finfo; 1343// float h1; 1344// float h2; 1345// 1346// font.GetHeight(&finfo); 1347// h1 = ceil(finfo.ascent + finfo.descent + finfo.leading); 1348// h2 = fText->LineHeight(); 1349// 1350// // Height of main view must be the larger of h1 and h2+(kTextInputMargin*2) 1351// *height = ceil((h1 > h2 + (kTextInputMargin*2)) ? h1 : h2 + (kTextInputMargin*2)); 1352} 1353 1354 1355void 1356BComboBox::ResizeToPreferred() 1357{ 1358 BControl::ResizeToPreferred(); 1359} 1360 1361 1362void 1363BComboBox::FrameMoved(BPoint new_position) 1364{ 1365 if (fPopupWindow && fPopupWindow->Lock()) { 1366 fPopupWindow->MoveBy(new_position.x - fFrameCache.left, 1367 new_position.y - fFrameCache.top); 1368 fPopupWindow->Unlock(); 1369 } 1370 fFrameCache.OffsetTo(new_position); 1371} 1372 1373 1374void 1375BComboBox::FrameResized(float new_width, float new_height) 1376{ 1377 // It's the cheese! 1378 float dx = new_width - fFrameCache.Width(); 1379 float dy = new_height - fFrameCache.Height(); 1380 if (dx != 0 && Window()) { 1381// BRect inval(fFrameCache.right, fFrameCache.top, 1382// fFrameCache.right+dx, fFrameCache.bottom); 1383 BRect inval(Bounds()); 1384 if (dx > 0) 1385 inval.left = inval.right-dx-1; 1386 else 1387 inval.left = inval.right-3; 1388// Window()->ConvertToScreen(&inval); 1389// ConvertFromScreen(&inval); 1390 Invalidate(inval); 1391 } 1392 1393 fFrameCache.right += dx; 1394 fFrameCache.bottom += dy; 1395// fButtonRect.OffsetBy(dx, 0); 1396 1397 if (fPopupWindow && fPopupWindow->Lock()) { 1398 if (!fPopupWindow->IsHidden()) 1399 HidePopupWindow(); 1400 1401 fPopupWindow->Unlock(); 1402 } 1403} 1404 1405 1406void 1407BComboBox::WindowActivated(bool /*active*/) 1408{ 1409 if (fText->IsFocus()) 1410 Draw(Bounds()); 1411} 1412 1413 1414void 1415BComboBox::Draw(BRect /*updateRect*/) 1416{ 1417 BRect bounds = Bounds(); 1418 font_height fInfo; 1419 rgb_color high = HighColor(); 1420 rgb_color base = ViewColor(); 1421 bool focused; 1422 bool enabled; 1423 rgb_color white = {255, 255, 255, 255}; 1424 rgb_color black = { 0, 0, 0, 255 }; 1425 1426 enabled = IsEnabled(); 1427 focused = fText->IsFocus() && Window()->IsActive(); 1428 1429 BRect fr = fText->Frame(); 1430 1431 fr.InsetBy(-3, -3); 1432 fr.bottom -= 1; 1433 if (enabled) 1434 SetHighColor(tint_color(base, B_DARKEN_1_TINT)); 1435 else 1436 SetHighColor(base); 1437 1438 StrokeLine(fr.LeftBottom(), fr.LeftTop()); 1439 StrokeLine(fr.RightTop()); 1440 1441 if (enabled) 1442 SetHighColor(white); 1443 else 1444 SetHighColor(tint_color(base, B_LIGHTEN_2_TINT)); 1445 1446 StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); 1447 StrokeLine(fr.RightTop()+BPoint(0,1)); 1448 fr.InsetBy(1,1); 1449 1450 if (focused) { 1451 // draw UI indication for 'active' 1452 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 1453 StrokeRect(fr); 1454 } else { 1455 if (enabled) 1456 SetHighColor(tint_color(base, B_DARKEN_4_TINT)); 1457 else 1458 SetHighColor(tint_color(base, B_DARKEN_2_TINT)); 1459 StrokeLine(fr.LeftBottom(), fr.LeftTop()); 1460 StrokeLine(fr.RightTop()); 1461 SetHighColor(base); 1462 StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); 1463 StrokeLine(fr.RightTop()+BPoint(0,1)); 1464 } 1465 1466 fr.InsetBy(1,1); 1467 1468 if (!enabled) 1469 SetHighColor(tint_color(base, B_DISABLED_MARK_TINT)); 1470 else 1471 SetHighColor(white); 1472 1473 StrokeRect(fr); 1474 SetHighColor(high); 1475 1476 bounds.right = bounds.left + fDivider; 1477 if ((Label()) && (fDivider > 0.0)) { 1478 BPoint loc; 1479 GetFontHeight(&fInfo); 1480 1481 switch (fLabelAlign) { 1482 default: 1483 case B_ALIGN_LEFT: 1484 loc.x = bounds.left + TV_MARGIN; 1485 break; 1486 case B_ALIGN_CENTER: 1487 { 1488 float width = StringWidth(Label()); 1489 float center = (bounds.right - bounds.left) / 2; 1490 loc.x = center - (width/2); 1491 break; 1492 } 1493 case B_ALIGN_RIGHT: 1494 { 1495 float width = StringWidth(Label()); 1496 loc.x = bounds.right - width - TV_MARGIN; 1497 break; 1498 } 1499 } 1500 1501 uint32 rmode = ResizingMode(); 1502 if ((rmode & _rule_(0xf, 0, 0xf, 0)) == _rule_(_VIEW_TOP_, 0, _VIEW_BOTTOM_, 0)) 1503 loc.y = fr.bottom - 2; 1504 else 1505 loc.y = bounds.bottom - (2 + ceil(fInfo.descent)); 1506 1507 MovePenTo(loc); 1508 SetHighColor(black); 1509 DrawString(Label()); 1510 SetHighColor(high); 1511 } 1512} 1513 1514 1515void 1516BComboBox::MessageReceived(BMessage *msg) 1517{ 1518 switch (msg->what) { 1519 case kTextInputModifyMessage: 1520 TryAutoComplete(); 1521 break; 1522 case kPopupButtonInvokeMessage: 1523 if (fChoiceList && fChoiceList->CountChoices() && !fPopupWindow) 1524 fPopupWindow = CreatePopupWindow(); 1525 1526 if (fPopupWindow->Lock()) { 1527 if (fPopupWindow->IsHidden()) 1528 ShowPopupWindow(); 1529 else 1530 HidePopupWindow(); 1531 1532 fPopupWindow->Unlock(); 1533 } 1534 break; 1535 case kWindowMovedMessage: 1536 if (fPopupWindow && fPopupWindow->Lock()) { 1537 if (!fPopupWindow->IsHidden()) 1538 HidePopupWindow(); 1539 1540 fPopupWindow->Unlock(); 1541 } 1542 break; 1543 default: 1544 BControl::MessageReceived(msg); 1545 } 1546} 1547 1548 1549void 1550BComboBox::MouseDown(BPoint where) 1551{ 1552// printf("BComboBox::MouseDown(%f, %f)\n", where.x, where.y); 1553 /*if (fButtonRect.Contains(where)) { // clicked in button area 1554 fDepressedWhenClicked = fButtonDepressed; 1555 fButtonDepressed = !fButtonDepressed; 1556 fTrackingButtonDown = true; 1557 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 1558 Invalidate(fButtonRect); 1559 fText->MakeFocus(true); 1560 }*/ 1561 BControl::MouseDown(where); 1562} 1563 1564 1565void 1566BComboBox::MouseUp(BPoint /*where*/) 1567{ 1568 if (fTrackingButtonDown) { 1569 // send an invoke message when the button changes state 1570 if (fButtonDepressed != fDepressedWhenClicked) { 1571 BMessage *msg; 1572 Window()->PostMessage(msg = new BMessage(kPopupButtonInvokeMessage),this); 1573 delete msg; 1574 } 1575 fTrackingButtonDown = false; 1576 } 1577} 1578 1579 1580void 1581BComboBox::MouseMoved(BPoint where, uint32 /*transit*/,const BMessage */*dragMessage*/) 1582{ 1583 if (fTrackingButtonDown) { 1584 BRect sloppyRect = fButtonRect; 1585 sloppyRect.InsetBy(-3, -3); 1586 1587 bool oldState = fButtonDepressed; 1588 fButtonDepressed = sloppyRect.Contains(where) ? !fDepressedWhenClicked 1589 : fDepressedWhenClicked; 1590 1591 if (oldState != fButtonDepressed) 1592 Invalidate(fButtonRect); 1593 } 1594} 1595 1596 1597status_t 1598BComboBox::Invoke(BMessage *msg) 1599{ 1600 return BControl::Invoke(msg); 1601} 1602 1603 1604void 1605BComboBox::AttachedToWindow() 1606{ 1607 Window()->AddFilter(fWinMovedFilter); 1608 if (Parent()) { 1609 SetViewColor(Parent()->ViewColor()); 1610 SetLowColor(ViewColor()); 1611 } 1612 1613 bool enabled = IsEnabled(); 1614 rgb_color mc = HighColor(); 1615 rgb_color base; 1616 BFont textFont; 1617 1618 // mc used to be base in this line 1619 if (mc.red == 255 && mc.green == 255 && mc.blue == 255) 1620 base = ViewColor(); 1621 else 1622 base = LowColor(); 1623 1624 fText->GetFontAndColor(0, &textFont); 1625 mc = enabled ? mc : disable_color(base); 1626 1627 fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); 1628 1629 if (!enabled) 1630 base = tint_color(base, B_DISABLED_MARK_TINT); 1631 else 1632 base.red = base.green = base.blue = 255; 1633 1634 fText->SetLowColor(base); 1635 fText->SetViewColor(base); 1636 1637 fText->MakeEditable(enabled); 1638} 1639 1640 1641void 1642BComboBox::DetachedFromWindow() 1643{ 1644 fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); 1645} 1646 1647 1648void 1649BComboBox::SetFlags(uint32 flags) 1650{ 1651 if (!fSkipSetFlags) { 1652 uint32 te_flags = fText->Flags(); 1653 bool te_nav = ((te_flags & B_NAVIGABLE) != 0); 1654 bool wants_nav = ((flags & B_NAVIGABLE) != 0); 1655 1656 // the ComboBox should never be navigable 1657 ASSERT((Flags() & B_NAVIGABLE) == 0); 1658 1659 if (!te_nav && wants_nav) { 1660 // The combo box wants to be navigable. Pass that along to 1661 // the text view 1662 fText->SetFlags(te_flags | B_NAVIGABLE); 1663 } else if (te_nav && !wants_nav) { 1664 // Caller wants to end NAV on the text view; 1665 fText->SetFlags(te_flags & ~B_NAVIGABLE); 1666 } 1667 1668 flags = flags & ~B_NAVIGABLE; // never want NAV for the combo box 1669 } 1670 BControl::SetFlags(flags); 1671} 1672 1673 1674void 1675BComboBox::SetEnabled(bool enabled) 1676{ 1677 if (enabled == IsEnabled()) 1678 return; 1679 1680 if (Window()) { 1681 fText->MakeEditable(enabled); 1682 rgb_color mc = HighColor(); 1683 rgb_color base = ViewColor(); 1684 1685 mc = (enabled) ? mc : disable_color(base); 1686 BFont textFont; 1687 fText->GetFontAndColor(0, &textFont); 1688 fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); 1689 1690 if (!enabled) 1691 base = tint_color(base, B_DISABLED_MARK_TINT); 1692 else 1693 base.red = base.green = base.blue = 255; 1694 1695 fText->SetLowColor(base); 1696 fText->SetViewColor(base); 1697 1698 fText->Invalidate(); 1699 Window()->UpdateIfNeeded(); 1700 } 1701 1702 fSkipSetFlags = true; 1703 BControl::SetEnabled(enabled); 1704 fSkipSetFlags = false; 1705 1706//+ // Want the sub_view to be the navigable one. We always want to be able 1707//+ // to navigate to that view, even if disabled since Copy still works. 1708//+ fText->SetFlags(fText->Flags() | B_NAVIGABLE); 1709//+ SetFlags(Flags() & ~B_NAVIGABLE); 1710} 1711 1712 1713//void BComboBox::AllAttached() 1714//{ 1715//} 1716 1717 1718BComboBox::ComboBoxWindow* 1719BComboBox::CreatePopupWindow() 1720{ 1721 ComboBoxWindow *win = new ComboBoxWindow(this); 1722 return win; 1723} 1724 1725 1726void 1727BComboBox::CommitValue() 1728{ 1729 Invoke(); 1730} 1731 1732 1733void 1734BComboBox::TryAutoComplete() 1735{ 1736 int32 from, to; 1737 fText->GetSelection(&from, &to); 1738 if (fAutoComplete && from == to) { 1739 bool autoCompleted = false; 1740 const char *ptr = fText->Text(); 1741 if (to > fTextEnd && from == fText->TextLength()) { 1742 const char *completion; 1743 // find the first matching choice and do auto-completion 1744 1745 // Find last comma 1746 const char *end; 1747 for (end = fText->Text()+fText->TextLength()-1; end>ptr; end--) { 1748 if (*end == ',') { 1749 // Find end of whitespace 1750 for (end++; isspace(*end); end++) {} 1751 if (*end == 0) 1752 return; 1753 break; 1754 } 1755 } 1756 if (fChoiceList->GetMatch(end, 0, &fCompletionIndex, &completion) == B_OK) { 1757 fText->Insert(completion); 1758 fText->Select(to, to + strlen(completion)); 1759 Select(fCompletionIndex); 1760 autoCompleted = true; 1761 } else 1762 fCompletionIndex = -1; 1763 } 1764 fTextEnd = to; 1765 1766 if (!autoCompleted) { 1767 int32 sel = CurrentSelection(); 1768 if (sel >= 0) { 1769 const char *selText = fChoiceList->ChoiceAt(sel); 1770 if (selText && !strcmp(ptr, selText)) { 1771 // don't Deselect() if the text input matches the selection 1772 return; 1773 } 1774 } 1775 fCompletionIndex = -1; 1776 Deselect(); 1777 } 1778 } 1779} 1780 1781 1782// fPopupWindow must exist and already be locked & hidden when this function 1783// is called 1784void 1785BComboBox::ShowPopupWindow() 1786{ 1787 // adjust position of the popup window 1788 fPopupWindow->DoPosition(); 1789 fPopupWindow->ListView()->SetEventMask(B_POINTER_EVENTS, 0); 1790 fPopupWindow->Show(); 1791 fPopupWindow->ListView()->MakeFocus(true); 1792} 1793 1794 1795// fPopupWindow must exist and already be locked & shown when this function 1796// is called 1797void 1798BComboBox::HidePopupWindow() 1799{ 1800 fPopupWindow->Hide(); 1801 fPopupWindow->ListView()->SetEventMask(0, 0); 1802 fButtonDepressed = false; 1803 Invalidate(fButtonRect); 1804} 1805 1806 1807void 1808BComboBox::MakeFocus(bool state) 1809{ 1810 fText->MakeFocus(state); 1811 if (state) 1812 fText->SelectAll(); 1813} 1814 1815 1816// #pragma mark - 1817 1818 1819BComboBox::MovedMessageFilter::MovedMessageFilter(BHandler *target) 1820 : BMessageFilter(B_WINDOW_MOVED) 1821{ 1822 fTarget = target; 1823} 1824 1825 1826filter_result 1827BComboBox::MovedMessageFilter::Filter(BMessage *message,BHandler **/*target*/) 1828{ 1829 BMessage *dup = new BMessage(*message); 1830 dup->what = kWindowMovedMessage; 1831 if (fTarget->Looper()) 1832 fTarget->Looper()->PostMessage(dup, fTarget); 1833 1834 delete dup; 1835 return B_DISPATCH_MESSAGE; 1836} 1837 1838 1839// #pragma mark - 1840 1841 1842BDefaultChoiceList::BDefaultChoiceList(BComboBox *owner) 1843{ 1844 fOwner = owner; 1845 fList = new StringObjectList(); 1846} 1847 1848 1849BDefaultChoiceList::~BDefaultChoiceList() 1850{ 1851 BString *string; 1852 while ((string = fList->RemoveItemAt(0)) != NULL) { 1853 delete string; 1854 } 1855 1856 delete fList; 1857} 1858 1859 1860const char* 1861BDefaultChoiceList::ChoiceAt(int32 index) 1862{ 1863 BString *string = fList->ItemAt(index); 1864 if (string) 1865 return string->String(); 1866 1867 return NULL; 1868} 1869 1870 1871status_t 1872BDefaultChoiceList::GetMatch(const char *prefix, int32 startIndex, 1873 int32 *matchIndex, const char **completionText) 1874{ 1875 BString *str; 1876 int32 len = strlen(prefix); 1877 int32 choices = fList->CountItems(); 1878 1879 for (int32 i = startIndex; i < choices; i++) { 1880 str = fList->ItemAt(i); 1881 if (!str->ICompare(prefix, len)) { 1882 // prefix matches 1883 *matchIndex = i; 1884 *completionText = str->String() + len; 1885 return B_OK; 1886 } 1887 } 1888 *matchIndex = -1; 1889 *completionText = NULL; 1890 return B_ERROR; 1891} 1892 1893 1894int32 1895BDefaultChoiceList::CountChoices() 1896{ 1897 return fList->CountItems(); 1898} 1899 1900 1901status_t 1902BDefaultChoiceList::AddChoice(const char *toAdd) 1903{ 1904 BString *str = new BString(toAdd); 1905 bool r = fList->AddItem(str); 1906 if (fOwner) 1907 fOwner->ChoiceListUpdated(); 1908 1909 return (r) ? B_OK : B_ERROR; 1910} 1911 1912 1913status_t 1914BDefaultChoiceList::AddChoiceAt(const char *toAdd, int32 index) 1915{ 1916 BString *str = new BString(toAdd); 1917 bool r = fList->AddItem(str, index); 1918 if (fOwner) 1919 fOwner->ChoiceListUpdated(); 1920 1921 return r ? B_OK : B_ERROR; 1922} 1923 1924 1925//int BStringCompareFunction(const BString *s1, const BString *s2) 1926//{ 1927// return s1->Compare(s2); 1928//} 1929 1930 1931status_t 1932BDefaultChoiceList::RemoveChoice(const char *toRemove) 1933{ 1934 BString *string; 1935 int32 choices = fList->CountItems(); 1936 for (int32 i = 0; i < choices; i++) { 1937 string = fList->ItemAt(i); 1938 if (!string->Compare(toRemove)) { 1939 fList->RemoveItemAt(i); 1940 if (fOwner) 1941 fOwner->ChoiceListUpdated(); 1942 1943 return B_OK; 1944 } 1945 } 1946 return B_ERROR; 1947} 1948 1949 1950status_t 1951BDefaultChoiceList::RemoveChoiceAt(int32 index) 1952{ 1953 BString *string = fList->RemoveItemAt(index); 1954 if (string) { 1955 delete string; 1956 if (fOwner) 1957 fOwner->ChoiceListUpdated(); 1958 1959 return B_OK; 1960 } 1961 return B_ERROR; 1962} 1963 1964 1965void 1966BDefaultChoiceList::SetOwner(BComboBox *owner) 1967{ 1968 fOwner = owner; 1969} 1970 1971 1972BComboBox* 1973BDefaultChoiceList::Owner() 1974{ 1975 return fOwner; 1976} 1977 1978