/* * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). * All rights reserved. Distributed under the terms of the MIT License. * * Authors: * Oliver Tappe */ #include "AutoCompleterDefaultImpl.h" #include #include #include #include // #pragma mark - BDefaultPatternSelector void BDefaultPatternSelector::SelectPatternBounds(const BString& text, int32 caretPos, int32* start, int32* length) { if (!start || !length) return; *start = 0; *length = text.Length(); } // #pragma mark - BDefaultCompletionStyle BDefaultCompletionStyle::BDefaultCompletionStyle( BAutoCompleter::EditView* editView, BAutoCompleter::ChoiceModel* choiceModel, BAutoCompleter::ChoiceView* choiceView, BAutoCompleter::PatternSelector* patternSelector) : CompletionStyle(editView, choiceModel, choiceView, patternSelector), fSelectedIndex(-1), fPatternStartPos(0), fPatternLength(0), fIgnoreEditViewStateChanges(false) { } BDefaultCompletionStyle::~BDefaultCompletionStyle() { } bool BDefaultCompletionStyle::Select(int32 index) { if (!fChoiceView || !fChoiceModel || index == fSelectedIndex || index < -1 || index >= fChoiceModel->CountChoices()) { return false; } fSelectedIndex = index; fChoiceView->SelectChoiceAt(index); return true; } bool BDefaultCompletionStyle::SelectNext(bool wrap) { if (!fChoiceModel || fChoiceModel->CountChoices() == 0) return false; int32 newIndex = fSelectedIndex + 1; if (newIndex >= fChoiceModel->CountChoices()) { if (wrap) newIndex = 0; else newIndex = fSelectedIndex; } return Select(newIndex); } bool BDefaultCompletionStyle::SelectPrevious(bool wrap) { if (!fChoiceModel || fChoiceModel->CountChoices() == 0) return false; int32 newIndex = fSelectedIndex - 1; if (newIndex < 0) { if (wrap) newIndex = fChoiceModel->CountChoices() - 1; else newIndex = 0; } return Select(newIndex); } bool BDefaultCompletionStyle::IsChoiceSelected() const { return fSelectedIndex >= 0; } int32 BDefaultCompletionStyle::SelectedChoiceIndex() const { return fSelectedIndex; } void BDefaultCompletionStyle::ApplyChoice(bool hideChoices) { if (!fChoiceModel || !fChoiceView || !fEditView || fSelectedIndex < 0) return; BString completedText(fFullEnteredText); completedText.Remove(fPatternStartPos, fPatternLength); const BString& choiceStr = fChoiceModel->ChoiceAt(fSelectedIndex)->Text(); completedText.Insert(choiceStr, fPatternStartPos); fIgnoreEditViewStateChanges = true; fFullEnteredText = completedText; fPatternLength = choiceStr.Length(); fEditView->SetEditViewState(completedText, fPatternStartPos + choiceStr.Length()); if (hideChoices) { fChoiceView->HideChoices(); Select(-1); } fIgnoreEditViewStateChanges = false; } void BDefaultCompletionStyle::CancelChoice() { if (!fChoiceView || !fEditView) return; if (fChoiceView->ChoicesAreShown()) { fIgnoreEditViewStateChanges = true; fEditView->SetEditViewState(fFullEnteredText, fPatternStartPos + fPatternLength); fChoiceView->HideChoices(); Select(-1); fIgnoreEditViewStateChanges = false; } } void BDefaultCompletionStyle::EditViewStateChanged(bool updateChoices) { if (fIgnoreEditViewStateChanges || !fChoiceModel || !fChoiceView || !fEditView) { return; } BString text; int32 caretPos; fEditView->GetEditViewState(text, &caretPos); if (fFullEnteredText == text) return; fFullEnteredText = text; if (!updateChoices) return; fPatternSelector->SelectPatternBounds(text, caretPos, &fPatternStartPos, &fPatternLength); BString pattern(text.String() + fPatternStartPos, fPatternLength); fChoiceModel->FetchChoicesFor(pattern); Select(-1); // show a single choice only if it doesn't match the pattern exactly: if (fChoiceModel->CountChoices() > 1 || (fChoiceModel->CountChoices() == 1 && pattern.ICompare(fChoiceModel->ChoiceAt(0)->Text())) != 0) { fChoiceView->ShowChoices(this); fChoiceView->SelectChoiceAt(fSelectedIndex); } else fChoiceView->HideChoices(); } // #pragma mark - BDefaultChoiceView::ListView static const int32 MSG_INVOKED = 'invk'; BDefaultChoiceView::ListView::ListView( BAutoCompleter::CompletionStyle* completer) : BListView(BRect(0, 0, 100, 100), "ChoiceViewList"), fCompleter(completer) { // we need to check if user clicks outside of window-bounds: SetEventMask(B_POINTER_EVENTS); } void BDefaultChoiceView::ListView::AttachedToWindow() { SetTarget(this); SetInvocationMessage(new BMessage(MSG_INVOKED)); BListView::AttachedToWindow(); } void BDefaultChoiceView::ListView::SelectionChanged() { fCompleter->Select(CurrentSelection(0)); } void BDefaultChoiceView::ListView::MessageReceived(BMessage* message) { switch(message->what) { case MSG_INVOKED: fCompleter->ApplyChoice(); break; default: BListView::MessageReceived(message); } } void BDefaultChoiceView::ListView::MouseDown(BPoint point) { if (!Window()->Frame().Contains(ConvertToScreen(point))) // click outside of window, so we close it: Window()->Quit(); else BListView::MouseDown(point); } // #pragma mark - BDefaultChoiceView::ListItem BDefaultChoiceView::ListItem::ListItem(const BAutoCompleter::Choice* choice) : BListItem() { fPreText = choice->DisplayText(); if (choice->MatchLen() > 0) { fPreText.MoveInto(fMatchText, choice->MatchPos(), choice->MatchLen()); fPreText.MoveInto(fPostText, choice->MatchPos(), fPreText.Length()); } } void BDefaultChoiceView::ListItem::DrawItem(BView* owner, BRect frame, bool complete) { rgb_color textColor; rgb_color nonMatchTextColor; rgb_color backColor; rgb_color matchColor; if (IsSelected()) { textColor = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR); backColor = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); } else { textColor = ui_color(B_LIST_ITEM_TEXT_COLOR); backColor = ui_color(B_LIST_BACKGROUND_COLOR); } matchColor = tint_color(backColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2); if (textColor.red + textColor.green + textColor.blue > 128 * 3) nonMatchTextColor = tint_color(textColor, 1.2); else nonMatchTextColor = tint_color(textColor, 0.75); BFont font; font_height fontHeight; owner->GetFont(&font); font.GetHeight(&fontHeight); float xPos = frame.left + 1; float yPos = frame.top + fontHeight.ascent; float w; if (fPreText.Length()) { w = owner->StringWidth(fPreText.String()); owner->SetLowColor(backColor); owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), B_SOLID_LOW); owner->SetHighColor(nonMatchTextColor); owner->DrawString(fPreText.String(), BPoint(xPos, yPos)); xPos += w; } if (fMatchText.Length()) { w = owner->StringWidth(fMatchText.String()); owner->SetLowColor(matchColor); owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), B_SOLID_LOW); owner->SetHighColor(textColor); owner->DrawString(fMatchText.String(), BPoint(xPos, yPos)); xPos += w; } if (fPostText.Length()) { w = owner->StringWidth(fPostText.String()); owner->SetLowColor(backColor); owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), B_SOLID_LOW); owner->SetHighColor(nonMatchTextColor); owner->DrawString(fPostText.String(), BPoint(xPos, yPos)); } } // #pragma mark - BDefaultChoiceView BDefaultChoiceView::BDefaultChoiceView() : fWindow(NULL), fListView(NULL), fMaxVisibleChoices(8) { } BDefaultChoiceView::~BDefaultChoiceView() { HideChoices(); } void BDefaultChoiceView::SelectChoiceAt(int32 index) { if (fListView && fListView->LockLooper()) { if (index < 0) fListView->DeselectAll(); else { fListView->Select(index); fListView->ScrollToSelection(); } fListView->UnlockLooper(); } } void BDefaultChoiceView::ShowChoices(BAutoCompleter::CompletionStyle* completer) { if (!completer) return; HideChoices(); BAutoCompleter::ChoiceModel* choiceModel = completer->GetChoiceModel(); BAutoCompleter::EditView* editView = completer->GetEditView(); if (!editView || !choiceModel || choiceModel->CountChoices() == 0) return; fListView = new ListView(completer); int32 count = choiceModel->CountChoices(); for(int32 i = 0; iAddItem( new ListItem(choiceModel->ChoiceAt(i)) ); } BScrollView *scrollView = NULL; if (count > fMaxVisibleChoices) { scrollView = new BScrollView("", fListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER); } fWindow = new BWindow(BRect(0, 0, 100, 100), "", B_BORDERED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK | B_AVOID_FOCUS | B_ASYNCHRONOUS_CONTROLS); if (scrollView != NULL) fWindow->AddChild(scrollView); else fWindow->AddChild(fListView); int32 visibleCount = min_c(count, fMaxVisibleChoices); float listHeight = fListView->ItemFrame(visibleCount - 1).bottom + 1; BRect pvRect = editView->GetAdjustmentFrame(); BRect listRect = pvRect; listRect.bottom = listRect.top + listHeight - 1; BRect screenRect = BScreen().Frame(); if (listRect.bottom + 1 + listHeight <= screenRect.bottom) listRect.OffsetTo(pvRect.left, pvRect.bottom + 1); else listRect.OffsetTo(pvRect.left, pvRect.top - listHeight); if (scrollView != NULL) { // Moving here to cut off the scrollbar top scrollView->MoveTo(0, -1); // Adding the 1 and 2 to cut-off the scroll-bar top, right and bottom scrollView->ResizeTo(listRect.Width() + 1, listRect.Height() + 2); // Move here to compensate for the above fListView->MoveTo(0, 1); fListView->ResizeTo(listRect.Width() - B_V_SCROLL_BAR_WIDTH, listRect.Height()); } else { fListView->MoveTo(0, 0); fListView->ResizeTo(listRect.Width(), listRect.Height()); } fWindow->MoveTo(listRect.left, listRect.top); fWindow->ResizeTo(listRect.Width(), listRect.Height()); fWindow->Show(); } void BDefaultChoiceView::HideChoices() { if (fWindow && fWindow->Lock()) { fWindow->Quit(); fWindow = NULL; fListView = NULL; } } bool BDefaultChoiceView::ChoicesAreShown() { return (fWindow != NULL); } int32 BDefaultChoiceView::CountVisibleChoices() const { if (fListView) return min_c(fMaxVisibleChoices, fListView->CountItems()); else return 0; } void BDefaultChoiceView::SetMaxVisibleChoices(int32 choices) { if (choices < 1) choices = 1; if (choices == fMaxVisibleChoices) return; fMaxVisibleChoices = choices; // TODO: Update live? } int32 BDefaultChoiceView::MaxVisibleChoices() const { return fMaxVisibleChoices; }