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