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_MENU_SELECTED_ITEM_TEXT_COLOR); 277 backColor = ui_color(B_MENU_SELECTED_BACKGROUND_COLOR); 278 } else { 279 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 280 backColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 281 } 282 matchColor = tint_color(backColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2); 283 nonMatchTextColor = tint_color(backColor, 1.7); 284 285 BFont font; 286 font_height fontHeight; 287 owner->GetFont(&font); 288 font.GetHeight(&fontHeight); 289 float xPos = frame.left + 1; 290 float yPos = frame.top + fontHeight.ascent; 291 float w; 292 if (fPreText.Length()) { 293 w = owner->StringWidth(fPreText.String()); 294 owner->SetLowColor(backColor); 295 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 296 B_SOLID_LOW); 297 owner->SetHighColor(nonMatchTextColor); 298 owner->DrawString(fPreText.String(), BPoint(xPos, yPos)); 299 xPos += w; 300 } 301 if (fMatchText.Length()) { 302 w = owner->StringWidth(fMatchText.String()); 303 owner->SetLowColor(matchColor); 304 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 305 B_SOLID_LOW); 306 owner->SetHighColor(textColor); 307 owner->DrawString(fMatchText.String(), BPoint(xPos, yPos)); 308 xPos += w; 309 } 310 if (fPostText.Length()) { 311 w = owner->StringWidth(fPostText.String()); 312 owner->SetLowColor(backColor); 313 owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), 314 B_SOLID_LOW); 315 owner->SetHighColor(nonMatchTextColor); 316 owner->DrawString(fPostText.String(), BPoint(xPos, yPos)); 317 } 318 } 319 320 321 // #pragma mark - BDefaultChoiceView 322 323 324 BDefaultChoiceView::BDefaultChoiceView() 325 : 326 fWindow(NULL), 327 fListView(NULL), 328 fMaxVisibleChoices(8) 329 { 330 331 } 332 333 334 BDefaultChoiceView::~BDefaultChoiceView() 335 { 336 HideChoices(); 337 } 338 339 340 void 341 BDefaultChoiceView::SelectChoiceAt(int32 index) 342 { 343 if (fListView && fListView->LockLooper()) { 344 if (index < 0) 345 fListView->DeselectAll(); 346 else { 347 fListView->Select(index); 348 fListView->ScrollToSelection(); 349 } 350 fListView->UnlockLooper(); 351 } 352 } 353 354 355 void 356 BDefaultChoiceView::ShowChoices(BAutoCompleter::CompletionStyle* completer) 357 { 358 if (!completer) 359 return; 360 361 HideChoices(); 362 363 BAutoCompleter::ChoiceModel* choiceModel = completer->GetChoiceModel(); 364 BAutoCompleter::EditView* editView = completer->GetEditView(); 365 366 if (!editView || !choiceModel || choiceModel->CountChoices() == 0) 367 return; 368 369 fListView = new ListView(completer); 370 int32 count = choiceModel->CountChoices(); 371 for(int32 i = 0; i<count; ++i) { 372 fListView->AddItem( 373 new ListItem(choiceModel->ChoiceAt(i)) 374 ); 375 } 376 377 BScrollView *scrollView = NULL; 378 if (count > fMaxVisibleChoices) { 379 scrollView = new BScrollView("", fListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER); 380 } 381 382 fWindow = new BWindow(BRect(0, 0, 100, 100), "", B_BORDERED_WINDOW_LOOK, 383 B_NORMAL_WINDOW_FEEL, B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK 384 | B_AVOID_FOCUS | B_ASYNCHRONOUS_CONTROLS); 385 if (scrollView != NULL) 386 fWindow->AddChild(scrollView); 387 else 388 fWindow->AddChild(fListView); 389 390 int32 visibleCount = min_c(count, fMaxVisibleChoices); 391 float listHeight = fListView->ItemFrame(visibleCount - 1).bottom + 1; 392 393 BRect pvRect = editView->GetAdjustmentFrame(); 394 BRect listRect = pvRect; 395 listRect.bottom = listRect.top + listHeight - 1; 396 BRect screenRect = BScreen().Frame(); 397 if (listRect.bottom + 1 + listHeight <= screenRect.bottom) 398 listRect.OffsetTo(pvRect.left, pvRect.bottom + 1); 399 else 400 listRect.OffsetTo(pvRect.left, pvRect.top - listHeight); 401 402 if (scrollView != NULL) { 403 // Moving here to cut off the scrollbar top 404 scrollView->MoveTo(0, -1); 405 // Adding the 1 and 2 to cut-off the scroll-bar top, right and bottom 406 scrollView->ResizeTo(listRect.Width() + 1, listRect.Height() + 2); 407 // Move here to compensate for the above 408 fListView->MoveTo(0, 1); 409 fListView->ResizeTo(listRect.Width() - B_V_SCROLL_BAR_WIDTH, listRect.Height()); 410 } else { 411 fListView->MoveTo(0, 0); 412 fListView->ResizeTo(listRect.Width(), listRect.Height()); 413 } 414 fWindow->MoveTo(listRect.left, listRect.top); 415 fWindow->ResizeTo(listRect.Width(), listRect.Height()); 416 fWindow->Show(); 417 } 418 419 420 void 421 BDefaultChoiceView::HideChoices() 422 { 423 if (fWindow && fWindow->Lock()) { 424 fWindow->Quit(); 425 fWindow = NULL; 426 fListView = NULL; 427 } 428 } 429 430 431 bool 432 BDefaultChoiceView::ChoicesAreShown() 433 { 434 return (fWindow != NULL); 435 } 436 437 438 int32 439 BDefaultChoiceView::CountVisibleChoices() const 440 { 441 if (fListView) 442 return min_c(fMaxVisibleChoices, fListView->CountItems()); 443 else 444 return 0; 445 } 446 447 448 void 449 BDefaultChoiceView::SetMaxVisibleChoices(int32 choices) 450 { 451 if (choices < 1) 452 choices = 1; 453 if (choices == fMaxVisibleChoices) 454 return; 455 456 fMaxVisibleChoices = choices; 457 458 // TODO: Update live? 459 } 460 461 462 int32 463 BDefaultChoiceView::MaxVisibleChoices() const 464 { 465 return fMaxVisibleChoices; 466 } 467 468