xref: /haiku/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.cpp (revision 197dbe53134181699e64eea000c1341f9b6ec351)
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
SelectPatternBounds(const BString & text,int32 caretPos,int32 * start,int32 * length)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 
BDefaultCompletionStyle(BAutoCompleter::EditView * editView,BAutoCompleter::ChoiceModel * choiceModel,BAutoCompleter::ChoiceView * choiceView,BAutoCompleter::PatternSelector * patternSelector)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 
~BDefaultCompletionStyle()49 BDefaultCompletionStyle::~BDefaultCompletionStyle()
50 {
51 }
52 
53 
54 bool
Select(int32 index)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
SelectNext(bool wrap)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
SelectPrevious(bool wrap)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
IsChoiceSelected() const103 BDefaultCompletionStyle::IsChoiceSelected() const
104 {
105 	return fSelectedIndex >= 0;
106 }
107 
108 
109 int32
SelectedChoiceIndex() const110 BDefaultCompletionStyle::SelectedChoiceIndex() const
111 {
112 	return fSelectedIndex;
113 }
114 
115 
116 void
ApplyChoice(bool hideChoices)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
CancelChoice()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
EditViewStateChanged(bool updateChoices)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 
ListView(BAutoCompleter::CompletionStyle * completer)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
AttachedToWindow()213 BDefaultChoiceView::ListView::AttachedToWindow()
214 {
215 	SetTarget(this);
216 	SetInvocationMessage(new BMessage(MSG_INVOKED));
217 	BListView::AttachedToWindow();
218 }
219 
220 
221 void
SelectionChanged()222 BDefaultChoiceView::ListView::SelectionChanged()
223 {
224 	fCompleter->Select(CurrentSelection(0));
225 }
226 
227 
228 void
MessageReceived(BMessage * message)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
MouseDown(BPoint point)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 
ListItem(const BAutoCompleter::Choice * choice)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
DrawItem(BView * owner,BRect frame,bool complete)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 
BDefaultChoiceView()326 BDefaultChoiceView::BDefaultChoiceView()
327 	:
328 	fWindow(NULL),
329 	fListView(NULL),
330 	fMaxVisibleChoices(8)
331 {
332 
333 }
334 
335 
~BDefaultChoiceView()336 BDefaultChoiceView::~BDefaultChoiceView()
337 {
338 	HideChoices();
339 }
340 
341 
342 void
SelectChoiceAt(int32 index)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
ShowChoices(BAutoCompleter::CompletionStyle * completer)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
HideChoices()423 BDefaultChoiceView::HideChoices()
424 {
425 	if (fWindow && fWindow->Lock()) {
426 		fWindow->Quit();
427 		fWindow = NULL;
428 		fListView = NULL;
429 	}
430 }
431 
432 
433 bool
ChoicesAreShown()434 BDefaultChoiceView::ChoicesAreShown()
435 {
436 	return (fWindow != NULL);
437 }
438 
439 
440 int32
CountVisibleChoices() const441 BDefaultChoiceView::CountVisibleChoices() const
442 {
443 	if (fListView)
444 		return min_c(fMaxVisibleChoices, fListView->CountItems());
445 	else
446 		return 0;
447 }
448 
449 
450 void
SetMaxVisibleChoices(int32 choices)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
MaxVisibleChoices() const465 BDefaultChoiceView::MaxVisibleChoices() const
466 {
467 	return fMaxVisibleChoices;
468 }
469 
470