1 /* 2 * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). 3 * All rights reserved. Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Oliver Tappe <beam@hirschkaefer.de> 7 */ 8 9 #include "AutoCompleterDefaultImpl.h" 10 11 #include <ListView.h> 12 #include <Screen.h> 13 #include <ScrollView.h> 14 #include <Window.h> 15 16 17 // #pragma mark - BDefaultPatternSelector 18 19 20 void 21 BDefaultPatternSelector::SelectPatternBounds(const BString& text, 22 int32 caretPos, int32* start, int32* length) 23 { 24 if (!start || !length) 25 return; 26 *start = 0; 27 *length = text.Length(); 28 } 29 30 31 // #pragma mark - BDefaultCompletionStyle 32 33 34 BDefaultCompletionStyle::BDefaultCompletionStyle( 35 BAutoCompleter::EditView* editView, 36 BAutoCompleter::ChoiceModel* choiceModel, 37 BAutoCompleter::ChoiceView* choiceView, 38 BAutoCompleter::PatternSelector* patternSelector) 39 : 40 CompletionStyle(editView, choiceModel, choiceView, patternSelector), 41 fSelectedIndex(-1), 42 fPatternStartPos(0), 43 fPatternLength(0), 44 fIgnoreEditViewStateChanges(false) 45 { 46 } 47 48 49 BDefaultCompletionStyle::~BDefaultCompletionStyle() 50 { 51 } 52 53 54 bool 55 BDefaultCompletionStyle::Select(int32 index) 56 { 57 if (!fChoiceView || !fChoiceModel || index == fSelectedIndex 58 || index < -1 || index >= fChoiceModel->CountChoices()) { 59 return false; 60 } 61 62 fSelectedIndex = index; 63 fChoiceView->SelectChoiceAt(index); 64 return true; 65 } 66 67 68 bool 69 BDefaultCompletionStyle::SelectNext(bool wrap) 70 { 71 if (!fChoiceModel || fChoiceModel->CountChoices() == 0) 72 return false; 73 74 int32 newIndex = fSelectedIndex + 1; 75 if (newIndex >= fChoiceModel->CountChoices()) { 76 if (wrap) 77 newIndex = 0; 78 else 79 newIndex = fSelectedIndex; 80 } 81 return Select(newIndex); 82 } 83 84 85 bool 86 BDefaultCompletionStyle::SelectPrevious(bool wrap) 87 { 88 if (!fChoiceModel || fChoiceModel->CountChoices() == 0) 89 return false; 90 91 int32 newIndex = fSelectedIndex - 1; 92 if (newIndex < 0) { 93 if (wrap) 94 newIndex = fChoiceModel->CountChoices() - 1; 95 else 96 newIndex = 0; 97 } 98 return Select(newIndex); 99 } 100 101 102 bool 103 BDefaultCompletionStyle::IsChoiceSelected() const 104 { 105 return fSelectedIndex >= 0; 106 } 107 108 109 int32 110 BDefaultCompletionStyle::SelectedChoiceIndex() const 111 { 112 return fSelectedIndex; 113 } 114 115 116 void 117 BDefaultCompletionStyle::ApplyChoice(bool hideChoices) 118 { 119 if (!fChoiceModel || !fChoiceView || !fEditView || fSelectedIndex < 0) 120 return; 121 122 BString completedText(fFullEnteredText); 123 completedText.Remove(fPatternStartPos, fPatternLength); 124 const BString& choiceStr = fChoiceModel->ChoiceAt(fSelectedIndex)->Text(); 125 completedText.Insert(choiceStr, fPatternStartPos); 126 127 fIgnoreEditViewStateChanges = true; 128 129 fFullEnteredText = completedText; 130 fPatternLength = choiceStr.Length(); 131 fEditView->SetEditViewState(completedText, 132 fPatternStartPos + choiceStr.Length()); 133 134 if (hideChoices) { 135 fChoiceView->HideChoices(); 136 Select(-1); 137 } 138 139 fIgnoreEditViewStateChanges = false; 140 } 141 142 143 void 144 BDefaultCompletionStyle::CancelChoice() 145 { 146 if (!fChoiceView || !fEditView) 147 return; 148 if (fChoiceView->ChoicesAreShown()) { 149 fIgnoreEditViewStateChanges = true; 150 151 fEditView->SetEditViewState(fFullEnteredText, 152 fPatternStartPos + fPatternLength); 153 fChoiceView->HideChoices(); 154 Select(-1); 155 156 fIgnoreEditViewStateChanges = false; 157 } 158 } 159 160 void 161 BDefaultCompletionStyle::EditViewStateChanged(bool updateChoices) 162 { 163 if (fIgnoreEditViewStateChanges || !fChoiceModel || !fChoiceView 164 || !fEditView) { 165 return; 166 } 167 168 BString text; 169 int32 caretPos; 170 fEditView->GetEditViewState(text, &caretPos); 171 if (fFullEnteredText == text) 172 return; 173 174 fFullEnteredText = text; 175 176 if (!updateChoices) 177 return; 178 179 fPatternSelector->SelectPatternBounds(text, caretPos, &fPatternStartPos, 180 &fPatternLength); 181 BString pattern(text.String() + fPatternStartPos, fPatternLength); 182 fChoiceModel->FetchChoicesFor(pattern); 183 184 Select(-1); 185 // show a single choice only if it doesn't match the pattern exactly: 186 if (fChoiceModel->CountChoices() > 1 || (fChoiceModel->CountChoices() == 1 187 && pattern.ICompare(fChoiceModel->ChoiceAt(0)->Text())) != 0) { 188 fChoiceView->ShowChoices(this); 189 fChoiceView->SelectChoiceAt(fSelectedIndex); 190 } else 191 fChoiceView->HideChoices(); 192 } 193 194 195 // #pragma mark - BDefaultChoiceView::ListView 196 197 198 static const int32 MSG_INVOKED = 'invk'; 199 200 201 BDefaultChoiceView::ListView::ListView( 202 BAutoCompleter::CompletionStyle* completer) 203 : 204 BListView(BRect(0, 0, 100, 100), "ChoiceViewList"), 205 fCompleter(completer) 206 { 207 // we need to check if user clicks outside of window-bounds: 208 SetEventMask(B_POINTER_EVENTS); 209 } 210 211 212 void 213 BDefaultChoiceView::ListView::AttachedToWindow() 214 { 215 SetTarget(this); 216 SetInvocationMessage(new BMessage(MSG_INVOKED)); 217 BListView::AttachedToWindow(); 218 } 219 220 221 void 222 BDefaultChoiceView::ListView::SelectionChanged() 223 { 224 fCompleter->Select(CurrentSelection(0)); 225 } 226 227 228 void 229 BDefaultChoiceView::ListView::MessageReceived(BMessage* message) 230 { 231 switch(message->what) { 232 case MSG_INVOKED: 233 fCompleter->ApplyChoice(); 234 break; 235 default: 236 BListView::MessageReceived(message); 237 } 238 } 239 240 241 void 242 BDefaultChoiceView::ListView::MouseDown(BPoint point) 243 { 244 if (!Window()->Frame().Contains(ConvertToScreen(point))) 245 // click outside of window, so we close it: 246 Window()->Quit(); 247 else 248 BListView::MouseDown(point); 249 } 250 251 252 // #pragma mark - BDefaultChoiceView::ListItem 253 254 255 BDefaultChoiceView::ListItem::ListItem(const BAutoCompleter::Choice* choice) 256 : 257 BListItem() 258 { 259 fPreText = choice->DisplayText(); 260 if (choice->MatchLen() > 0) { 261 fPreText.MoveInto(fMatchText, choice->MatchPos(), choice->MatchLen()); 262 fPreText.MoveInto(fPostText, choice->MatchPos(), fPreText.Length()); 263 } 264 } 265 266 267 void 268 BDefaultChoiceView::ListItem::DrawItem(BView* owner, BRect frame, 269 bool complete) 270 { 271 rgb_color textColor; 272 rgb_color nonMatchTextColor; 273 rgb_color backColor; 274 rgb_color matchColor; 275 if (IsSelected()) { 276 textColor = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR); 277 backColor = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 278 } else { 279 textColor = ui_color(B_LIST_ITEM_TEXT_COLOR); 280 backColor = ui_color(B_LIST_BACKGROUND_COLOR); 281 } 282 matchColor = tint_color(backColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2); 283 if (textColor.red + textColor.green + textColor.blue > 128 * 3) 284 nonMatchTextColor = tint_color(textColor, 1.2); 285 else 286 nonMatchTextColor = tint_color(textColor, 0.75); 287 BFont font; 288 font_height fontHeight; 289 owner->GetFont(&font); 290 font.GetHeight(&fontHeight); 291 float xPos = frame.left + 1; 292 float yPos = frame.top + fontHeight.ascent; 293 float w; 294 if (fPreText.Length()) { 295 w = owner->StringWidth(fPreText.String()); 296 owner->SetLowColor(backColor); 297 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 298 B_SOLID_LOW); 299 owner->SetHighColor(nonMatchTextColor); 300 owner->DrawString(fPreText.String(), BPoint(xPos, yPos)); 301 xPos += w; 302 } 303 if (fMatchText.Length()) { 304 w = owner->StringWidth(fMatchText.String()); 305 owner->SetLowColor(matchColor); 306 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 307 B_SOLID_LOW); 308 owner->SetHighColor(textColor); 309 owner->DrawString(fMatchText.String(), BPoint(xPos, yPos)); 310 xPos += w; 311 } 312 if (fPostText.Length()) { 313 w = owner->StringWidth(fPostText.String()); 314 owner->SetLowColor(backColor); 315 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 316 B_SOLID_LOW); 317 owner->SetHighColor(nonMatchTextColor); 318 owner->DrawString(fPostText.String(), BPoint(xPos, yPos)); 319 } 320 } 321 322 323 // #pragma mark - BDefaultChoiceView 324 325 326 BDefaultChoiceView::BDefaultChoiceView() 327 : 328 fWindow(NULL), 329 fListView(NULL), 330 fMaxVisibleChoices(8) 331 { 332 333 } 334 335 336 BDefaultChoiceView::~BDefaultChoiceView() 337 { 338 HideChoices(); 339 } 340 341 342 void 343 BDefaultChoiceView::SelectChoiceAt(int32 index) 344 { 345 if (fListView && fListView->LockLooper()) { 346 if (index < 0) 347 fListView->DeselectAll(); 348 else { 349 fListView->Select(index); 350 fListView->ScrollToSelection(); 351 } 352 fListView->UnlockLooper(); 353 } 354 } 355 356 357 void 358 BDefaultChoiceView::ShowChoices(BAutoCompleter::CompletionStyle* completer) 359 { 360 if (!completer) 361 return; 362 363 HideChoices(); 364 365 BAutoCompleter::ChoiceModel* choiceModel = completer->GetChoiceModel(); 366 BAutoCompleter::EditView* editView = completer->GetEditView(); 367 368 if (!editView || !choiceModel || choiceModel->CountChoices() == 0) 369 return; 370 371 fListView = new ListView(completer); 372 int32 count = choiceModel->CountChoices(); 373 for(int32 i = 0; i<count; ++i) { 374 fListView->AddItem( 375 new ListItem(choiceModel->ChoiceAt(i)) 376 ); 377 } 378 379 BScrollView *scrollView = NULL; 380 if (count > fMaxVisibleChoices) { 381 scrollView = new BScrollView("", fListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER); 382 } 383 384 fWindow = new BWindow(BRect(0, 0, 100, 100), "", B_BORDERED_WINDOW_LOOK, 385 B_NORMAL_WINDOW_FEEL, B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK 386 | B_AVOID_FOCUS | B_ASYNCHRONOUS_CONTROLS); 387 if (scrollView != NULL) 388 fWindow->AddChild(scrollView); 389 else 390 fWindow->AddChild(fListView); 391 392 int32 visibleCount = min_c(count, fMaxVisibleChoices); 393 float listHeight = fListView->ItemFrame(visibleCount - 1).bottom + 1; 394 395 BRect pvRect = editView->GetAdjustmentFrame(); 396 BRect listRect = pvRect; 397 listRect.bottom = listRect.top + listHeight - 1; 398 BRect screenRect = BScreen().Frame(); 399 if (listRect.bottom + 1 + listHeight <= screenRect.bottom) 400 listRect.OffsetTo(pvRect.left, pvRect.bottom + 1); 401 else 402 listRect.OffsetTo(pvRect.left, pvRect.top - listHeight); 403 404 if (scrollView != NULL) { 405 // Moving here to cut off the scrollbar top 406 scrollView->MoveTo(0, -1); 407 // Adding the 1 and 2 to cut-off the scroll-bar top, right and bottom 408 scrollView->ResizeTo(listRect.Width() + 1, listRect.Height() + 2); 409 // Move here to compensate for the above 410 fListView->MoveTo(0, 1); 411 fListView->ResizeTo(listRect.Width() - B_V_SCROLL_BAR_WIDTH, listRect.Height()); 412 } else { 413 fListView->MoveTo(0, 0); 414 fListView->ResizeTo(listRect.Width(), listRect.Height()); 415 } 416 fWindow->MoveTo(listRect.left, listRect.top); 417 fWindow->ResizeTo(listRect.Width(), listRect.Height()); 418 fWindow->Show(); 419 } 420 421 422 void 423 BDefaultChoiceView::HideChoices() 424 { 425 if (fWindow && fWindow->Lock()) { 426 fWindow->Quit(); 427 fWindow = NULL; 428 fListView = NULL; 429 } 430 } 431 432 433 bool 434 BDefaultChoiceView::ChoicesAreShown() 435 { 436 return (fWindow != NULL); 437 } 438 439 440 int32 441 BDefaultChoiceView::CountVisibleChoices() const 442 { 443 if (fListView) 444 return min_c(fMaxVisibleChoices, fListView->CountItems()); 445 else 446 return 0; 447 } 448 449 450 void 451 BDefaultChoiceView::SetMaxVisibleChoices(int32 choices) 452 { 453 if (choices < 1) 454 choices = 1; 455 if (choices == fMaxVisibleChoices) 456 return; 457 458 fMaxVisibleChoices = choices; 459 460 // TODO: Update live? 461 } 462 463 464 int32 465 BDefaultChoiceView::MaxVisibleChoices() const 466 { 467 return fMaxVisibleChoices; 468 } 469 470