1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35//
36// ComboBox.cpp
37//
38//
39
40/*
41	TODO:
42		- Better up/down arrow handling (if text in input box matches a list item,
43		  pressing down should select the next item, pressing up should select the
44		  previous.  If no item matched, the first or last item should be selected.
45		  In any case, pressing up or down should show the popup if it is hidden
46		- Properly draw the label, taking alignment into account
47		- Draw nicer border around text input and popup window
48		- Escaping out of the popup menu should restore the text in the input to the
49		  value it had previous to popping up the menu.
50		- Fix popup behavior when the widget is near the bottom of the screen.  The
51		  popup window should be able to go above the text input area.  Also, the popup
52		  should size itself in a smart manner so that it is small if there are few
53		  choices and large if there are many and the window under it is big.  Perhaps
54		  the developer should be able to influence the size of the popup.
55		- Improve button drawing and (?) button behavior
56		- Fix and test enable/disable behavior
57		- Add auto-scrolling and/or drag-scrolling to the poup-menu
58		- Add support for other navigation keys, like page up, page down, home, end.
59		- Fix up choice functions (remove choice, add at index, etc) and make sure they
60		  properly invalidate/scroll/etc the list when it is visible
61		- Change auto-complete behavior to be non-greedy, or perhaps add some type of
62		  tab-cycling to the choices
63		- Add mode whereby you can pop up a list of only those items that match
64*/
65
66#include <Button.h>
67#include <Debug.h>
68#include <InterfaceDefs.h>
69#include <ListItem.h>
70#include <ListView.h>
71#include <Menu.h>			// for menu_info
72#include <MessageFilter.h>
73#include "ObjectList.h"
74#include <ScrollBar.h>
75#include <String.h>
76#include <TextControl.h>
77#include <Window.h>
78#include <stdio.h>
79#include <stdlib.h>
80#include <string.h>
81#include <ctype.h>
82#include "ComboBox.h"
83
84//static const uint32 kTextControlInvokeMessage	= 'tCIM';
85static const uint32 kTextInputModifyMessage = 'tIMM';
86static const uint32 kPopupButtonInvokeMessage = 'pBIM';
87static const uint32 kPopupWindowHideMessage = 'pUWH';
88static const uint32 kWindowMovedMessage = 'wMOV';
89
90static const float kTextInputMargin = (float)3.0;
91static const float kLabelRightMargin = (float)6.0;
92static const float kButtonWidth = (float)15.0;
93
94#define disable_color(_c_) tint_color(_c_, B_DISABLED_LABEL_TINT)
95
96#define TV_MARGIN 3.0
97#define TV_DIVIDER_MARGIN 6.0
98
99rgb_color create_color(uchar r, uchar g, uchar b, uchar a = 255);
100
101rgb_color create_color(uchar r, uchar g, uchar b, uchar a) {
102	rgb_color col;
103	col.red = r;
104	col.green = g;
105	col.blue = b;
106	col.alpha = a;
107	return col;
108}
109
110class StringObjectList : public BObjectList<BString> {};
111
112// ----------------------------------------------------------------------------
113
114// ChoiceListView is similar to a BListView, but it's implementation is tied to
115// BComboBox.  BListView is not used because it requires that a BStringItem be
116// created for each choice.  ChoiceListView just pulls the choice strings
117// directly from the BComboBox and draws them.
118class BComboBox::ChoiceListView : public BView
119{
120	public:
121		ChoiceListView(	BRect frame, BComboBox *parent);
122		virtual ~ChoiceListView();
123
124		virtual void Draw(BRect update);
125		virtual void MouseDown(BPoint where);
126		virtual void MouseUp(BPoint where);
127		virtual void MouseMoved(BPoint where, uint32 transit,
128			const BMessage *dragMessage);
129		virtual void KeyDown(const char *bytes, int32 numBytes);
130		virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL);
131
132		void ScrollToSelection();
133		void InvalidateItem(int32 index, bool force = false);
134		BRect ItemFrame(int32 index);
135		void AdjustScrollBar();
136		// XXX: add BArchivable functionality
137
138	private:
139		inline float LineHeight();
140
141		BPoint fClickLoc;
142		font_height fFontHeight;
143		bigtime_t fClickTime;
144		rgb_color fForeCol;
145		rgb_color fBackCol;
146		rgb_color fSelCol;
147		int32 fSelIndex;
148		BComboBox *fParent;
149		bool fTrackingMouseDown;
150};
151
152
153// ----------------------------------------------------------------------------
154
155// TextInput is a somewhat modified version of the _BTextInput_ class defined
156// in TextControl.cpp.
157
158class BComboBox::TextInput : public BTextView {
159	public:
160		TextInput(BRect rect, BRect trect, ulong rMask, ulong flags);
161		TextInput(BMessage *data);
162		virtual ~TextInput();
163		static BArchivable *Instantiate(BMessage *data);
164		virtual status_t Archive(BMessage *data, bool deep = true) const;
165
166		virtual void KeyDown(const char *bytes, int32 numBytes);
167		virtual void MakeFocus(bool state);
168		virtual void FrameResized(float x, float y);
169		virtual void Paste(BClipboard *clipboard);
170
171		void AlignTextRect();
172
173		void SetInitialText();
174		void SetFilter(text_input_filter_hook hook);
175
176		// XXX: add BArchivable functionality
177
178	protected:
179		virtual void InsertText(const char *inText, int32 inLength, int32 inOffset,
180			const text_run_array *inRuns);
181		virtual void DeleteText(int32 fromOffset, int32 toOffset);
182
183	private:
184		char *fInitialText;
185		text_input_filter_hook fFilter;
186		bool fClean;
187};
188
189// ----------------------------------------------------------------------------
190
191class BComboBox::ComboBoxWindow : public BWindow
192{
193	public:
194		ComboBoxWindow(BComboBox *box);
195		virtual ~ComboBoxWindow();
196		virtual void WindowActivated(bool active);
197		virtual void FrameResized(float width, float height);
198
199		void DoPosition();
200		BComboBox::ChoiceListView *ListView();
201		BScrollBar *ScrollBar();
202
203		// XXX: add BArchivable functionality
204
205	private:
206		BScrollBar			*fScrollBar;
207		ChoiceListView		*fListView;
208		BComboBox			*fParent;
209};
210
211// ----------------------------------------------------------------------------
212
213// In BeOS R4.5, SetEventMask(B_POINTER_EVENTS, ...) does not work for getting
214// all mouse events as they happen.  Specifically, when the user clicks on the
215// window dressing (the borders or the title tab) no mouse event will be
216// delivered until after the user releases the mouse button.  This has the
217// unfortunate side effect of allowing the user to move the window that
218// contains the BComboBox around with no notification being sent to the
219// BComboBox.  We need to intercept the B_WINDOW_MOVED messages so that we can
220// hide the popup window when the window moves.
221
222class BComboBox::MovedMessageFilter : public BMessageFilter
223{
224	public:
225		MovedMessageFilter(BHandler *target);
226		virtual filter_result Filter(BMessage *message, BHandler **target);
227
228	private:
229		BHandler *fTarget;
230};
231
232
233// ----------------------------------------------------------------------------
234
235
236BComboBox::ChoiceListView::ChoiceListView(BRect frame, BComboBox *parent)
237	: BView(frame, "_choice_list_view_", B_FOLLOW_ALL_SIDES, B_WILL_DRAW
238			| B_NAVIGABLE),
239	fClickLoc(-100, -100)
240{
241	fParent = parent;
242	GetFontHeight(&fFontHeight);
243	menu_info mi;
244	get_menu_info(&mi);
245	fForeCol = create_color(0, 0, 0);
246	fBackCol = mi.background_color;
247	fSelCol  = create_color(144, 144, 144);
248	SetViewColor(B_TRANSPARENT_COLOR);
249	SetHighColor(fForeCol);
250	fTrackingMouseDown = false;
251	fClickTime = 0;
252}
253
254
255BComboBox::ChoiceListView::~ChoiceListView()
256{
257}
258
259
260void BComboBox::ChoiceListView::Draw(BRect update)
261{
262	float h = LineHeight();
263	BRect rect(Bounds());
264	int32 index;
265	int32 choices = fParent->fChoiceList->CountChoices();
266	int32 selected = (fTrackingMouseDown) ? fSelIndex : fParent->CurrentSelection();
267
268	// draw each visible item
269	for (index = (int32)floor(update.top / h); index < choices; index++)
270	{
271		rect.top = index * h;
272		rect.bottom = rect.top + h;
273		SetLowColor((index == selected) ? fSelCol : fBackCol);
274		FillRect(rect, B_SOLID_LOW);
275		DrawString(fParent->fChoiceList->ChoiceAt(index), BPoint(rect.left + 2,
276			rect.bottom - fFontHeight.descent - 1));
277	}
278
279	// draw empty area on bottom
280	if (rect.bottom < update.bottom)
281	{
282		update.top = rect.bottom;
283		SetLowColor(fBackCol);
284		FillRect(update, B_SOLID_LOW);
285	}
286}
287
288
289void BComboBox::ChoiceListView::MouseDown(BPoint where)
290{
291	BRect rect(Window()->Frame());
292	ConvertFromScreen(&rect);
293	if (!rect.Contains(where))
294	{
295		// hide the popup window when the user clicks outside of it
296		if (fParent->Window()->Lock())
297		{
298			fParent->HidePopupWindow();
299			fParent->Window()->Unlock();
300		}
301
302		// HACK: the window is locked and unlocked so that it will get
303		// activated before we potentially send the mouse down event in the
304		// code below.  Is there a way to wait until the window is activated
305		// before sending the mouse down? Should we call
306		// fParent->Window()->MakeActive(true) here?
307
308		if (fParent->Window()->Lock())
309		{
310			// resend the mouse event to the textinput, if necessary
311			BTextView *text = fParent->TextView();
312			BPoint screenWhere(ConvertToScreen(where));
313			rect = text->Window()->ConvertToScreen(text->Frame());
314			if (rect.Contains(screenWhere))
315			{
316				//printf("  resending mouse down to textinput\n");
317				BMessage *msg = new BMessage(*Window()->CurrentMessage());
318				msg->RemoveName("be:view_where");
319				text->ConvertFromScreen(&screenWhere);
320				msg->AddPoint("be:view_where", screenWhere);
321				text->Window()->PostMessage(msg, text);
322				delete msg;
323			}
324			fParent->Window()->Unlock();
325		}
326
327		return;
328	}
329
330	rect = Bounds();
331	if (!rect.Contains(where))
332		return;
333
334	fTrackingMouseDown = true;
335	// check for double click
336	bigtime_t now = system_time();
337	bigtime_t clickSpeed;
338	get_click_speed(&clickSpeed);
339	if ((now - fClickTime < clickSpeed)
340		&& ((abs((int)(fClickLoc.x - where.x)) < 3)
341		&& (abs((int)(fClickLoc.y - where.y)) < 3)))
342	{
343		// this is a double click
344		// XXX: what to do here?
345		printf("BComboBox::ChoiceListView::MouseDown() -- unhandled double click\n");
346	}
347	fClickTime = now;
348	fClickLoc = where;
349
350	float h = LineHeight();
351	int32 oldIndex = fSelIndex;
352	fSelIndex = (int32)floor(where.y / h);
353	int32 choices = fParent->fChoiceList->CountChoices();
354	if (fSelIndex < 0 || fSelIndex >= choices)
355		fSelIndex = -1;
356
357	if (oldIndex != fSelIndex)
358	{
359		InvalidateItem(oldIndex);
360		InvalidateItem(fSelIndex);
361	}
362	// XXX: this probably isn't necessary since we are doing a SetEventMask
363	// whenever the popup window becomes visible which routes all mouse events
364	// to this view
365//	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
366}
367
368
369void BComboBox::ChoiceListView::MouseUp(BPoint /*where*/)
370{
371	if (fTrackingMouseDown)
372	{
373		fTrackingMouseDown = false;
374		if (fSelIndex >= 0)
375			fParent->Select(fSelIndex, true);
376		else
377			fParent->Deselect();
378	}
379//	fClickLoc = where;
380}
381
382
383void BComboBox::ChoiceListView::MouseMoved(BPoint where, uint32 /*transit*/,
384	const BMessage */*dragMessage*/)
385{
386	if (fTrackingMouseDown)
387	{
388		float h = LineHeight();
389		int32 oldIndex = fSelIndex;
390		fSelIndex = (int32)floor(where.y / h);
391		int32 choices = fParent->fChoiceList->CountChoices();
392		if (fSelIndex < 0 || fSelIndex >= choices)
393			fSelIndex = -1;
394
395		if (oldIndex != fSelIndex)
396		{
397			InvalidateItem(oldIndex);
398			InvalidateItem(fSelIndex);
399		}
400	}
401}
402
403
404void BComboBox::ChoiceListView::KeyDown(const char *bytes, int32 /*numBytes*/)
405{
406	BComboBox *cb = fParent;
407	BWindow *win = cb->Window();
408	BComboBox::TextInput *text = dynamic_cast<BComboBox::TextInput*>(cb->TextView());
409	uchar aKey = bytes[0];
410
411	switch (aKey)
412	{
413		case B_UP_ARROW:	// fall through
414		case B_DOWN_ARROW:
415			if (win->Lock())
416			{
417				// change the selection
418				int32 index = cb->CurrentSelection();
419				int32 choices = cb->fChoiceList->CountChoices();
420				if (choices > 0)
421				{
422					if (index < 0)
423					{
424						// no previous selection, so select first or last item
425						// depending on whether this is a up or down arrow
426						cb->Select((aKey == B_UP_ARROW) ? choices - 1 : 0);
427					}
428					else
429					{
430						// select the previous or the next item, if possible,
431						// depending on whether this is an up or down arrow
432						if (aKey == B_UP_ARROW && (index - 1 >= 0))
433							cb->Select(index - 1, true);
434						else if (aKey == B_DOWN_ARROW && (index + 1 < choices))
435							cb->Select(index + 1, true);
436					}
437				}
438				win->Unlock();
439			}
440			break;
441		default:
442		{	// send all other key down events to the text input view
443			BMessage *msg = Window()->DetachCurrentMessage();
444			if (msg) {
445				win->PostMessage(msg, text);
446				delete msg;
447			}
448			break;
449		}
450	}
451}
452
453
454void BComboBox::ChoiceListView::SetFont(const BFont *font, uint32 properties)
455{
456	BView::SetFont(font, properties);
457	GetFontHeight(&fFontHeight);
458	Invalidate();
459}
460
461
462void BComboBox::ChoiceListView::ScrollToSelection()
463{
464	int32 selected = fParent->CurrentSelection();
465	if (selected >= 0)
466	{
467		BRect frame(ItemFrame(selected));
468		BRect bounds(Bounds());
469		float newY = -1.0; // dummy value -- not used
470        bool doScroll = false;
471
472		if (frame.bottom > bounds.bottom)
473		{
474			newY = frame.bottom - bounds.Height();
475			doScroll = true;
476		}
477		else if (frame.top < bounds.top)
478		{
479			newY = frame.top;
480			doScroll = true;
481		}
482		if (doScroll)
483			ScrollTo(bounds.left, newY);
484	}
485}
486
487// InvalidateItem() only does a real invalidate if the index is valid or the
488// force flag is turned on
489
490void BComboBox::ChoiceListView::InvalidateItem(int32 index, bool force)
491{
492	int32 choices = fParent->fChoiceList->CountChoices();
493	if ((index >= 0 && index < choices) || force) {
494		Invalidate(ItemFrame(index));
495	}
496}
497
498// This method doesn't check the index to see if it is valid, it just returns
499// the BRect that an item and the index would have if it existed.
500
501BRect BComboBox::ChoiceListView::ItemFrame(int32 index)
502{
503	BRect rect(Bounds());
504	float h = LineHeight();
505	rect.top = index * h;
506	rect.bottom = rect.top + h;
507	return rect;
508}
509
510// The window must be locked before this method is called
511
512void BComboBox::ChoiceListView::AdjustScrollBar()
513{
514	BScrollBar *sb = ScrollBar(B_VERTICAL);
515	if (sb) {
516		float h = LineHeight();
517		float max = h * fParent->fChoiceList->CountChoices();
518		BRect frame(Frame());
519		float diff = max - frame.Height();
520		float prop = frame.Height() / max;
521		if (diff < 0) {
522			diff = 0.0;
523			prop = 1.0;
524		}
525		sb->SetSteps(h, h * (frame.IntegerHeight() / h));
526		sb->SetRange(0.0, diff);
527		sb->SetProportion(prop);
528	}
529}
530
531
532float BComboBox::ChoiceListView::LineHeight()
533{
534	return fFontHeight.ascent + fFontHeight.descent + fFontHeight.leading + 2;
535}
536
537
538// ----------------------------------------------------------------------------
539//	#pragma mark -
540
541
542BComboBox::TextInput::TextInput(BRect rect, BRect textRect, ulong rMask,
543		ulong flags)
544	: BTextView(rect, "_input_", textRect, be_plain_font, NULL, rMask, flags),
545	fFilter(NULL)
546{
547	MakeResizable(true);
548	fInitialText = NULL;
549	fClean = false;
550}
551
552
553BComboBox::TextInput::TextInput(BMessage *data)
554	: BTextView(data),
555	fFilter(NULL)
556{
557	MakeResizable(true);
558	fInitialText = NULL;
559	fClean = false;
560}
561
562
563BComboBox::TextInput::~TextInput()
564{
565	free(fInitialText);
566}
567
568
569status_t
570BComboBox::TextInput::Archive(BMessage* data, bool /*deep*/) const
571{
572	return BTextView::Archive(data);
573}
574
575
576BArchivable *
577BComboBox::TextInput::Instantiate(BMessage* data)
578{
579	// XXX: is "TextInput" the correct name for this class? Perhaps
580	// BComboBox::TextInput?
581	if (!validate_instantiation(data, "TextInput"))
582		return NULL;
583
584	return new TextInput(data);
585}
586
587
588void
589BComboBox::TextInput::SetInitialText()
590{
591	if (fInitialText) {
592		free(fInitialText);
593		fInitialText = NULL;
594	}
595	if (Text())
596		fInitialText = strdup(Text());
597}
598
599
600void
601BComboBox::TextInput::SetFilter(text_input_filter_hook hook)
602{
603	fFilter = hook;
604}
605
606
607void
608BComboBox::TextInput::KeyDown(const char *bytes, int32 numBytes)
609{
610	BComboBox* cb;
611	uchar aKey = bytes[0];
612
613	switch (aKey) {
614		case B_RETURN:
615			cb = dynamic_cast<BComboBox*>(Parent());
616			ASSERT(cb);
617
618			if (!cb->IsEnabled())
619				break;
620
621			ASSERT(fInitialText);
622			if (strcmp(fInitialText, Text()) != 0)
623				cb->CommitValue();
624			free(fInitialText);
625			fInitialText = strdup(Text());
626			{
627				int32 end = TextLength();
628				Select(end, end);
629			}
630			// hide popup window if it's showing when the user presses the
631			// enter key
632			if (cb->fPopupWindow && cb->fPopupWindow->Lock()) {
633				if (!cb->fPopupWindow->IsHidden()) {
634					cb->HidePopupWindow();
635				}
636				cb->fPopupWindow->Unlock();
637			}
638			break;
639		case B_TAB:
640//			cb = dynamic_cast<BComboBox*>Parent());
641//			ASSERT(cb);
642//			if (cb->fAutoComplete && cb->fCompletionIndex >= 0) {
643//				int32 from, to;
644//				cb->fText->GetSelection(&from, &to);
645//				if (from == to) {
646//					// HACK: this should never happen.  The rest of the class
647//					// should be fixed so that fCompletionIndex is set to -1 if the
648//					// text is modified
649//					printf("BComboBox::TextInput::KeyDown() -- HACK! this shouldn't happen!");
650//					cb->fCompletionIndex = -1;
651//				}
652//
653//				const char *text = cb->fText->Text();
654//				BString prefix;
655//				prefix.Append(text, from);
656//
657//				int32 match;
658//				const char *completion;
659//				if (cb->fChoiceList->GetMatch(	prefix.String(),
660//											  	cb->fCompletionIndex + 1,
661//												&match,
662//												&completion) == B_OK)
663//				{
664//					cb->fText->Delete(); 			// delete the selection
665//					cb->fText->Insert(completion);
666//					cb->fText->Select(from, from + strlen(completion));
667//					cb->fCompletionIndex = match;
668//					cb->Select(cb->fCompletionIndex);
669//				} else {
670//					//system_beep();
671//				}
672//			} else {
673				BView::KeyDown(bytes, numBytes);
674//			}
675			break;
676#if 0
677		case B_UP_ARROW:		// fall through
678		case B_DOWN_ARROW:
679			cb = dynamic_cast<BComboBox*>(Parent());
680			ASSERT(cb);
681			if (cb->fChoiceList) {
682				cb = dynamic_cast<BComboBox*>(Parent());
683				ASSERT(cb);
684				if (!(cb->fPopupWindow)) {
685					cb->fPopupWindow = cb->CreatePopupWindow();
686				}
687				if (cb->fPopupWindow->Lock()) {
688					// show popup window, if needed
689					if (cb->fPopupWindow->IsHidden()) {
690						cb->ShowPopupWindow();
691					} else {
692						printf("Whoa!!! Erroneously got up/down arrow key down in TextInput::KeyDown()!\n");
693					}
694					int32 index = cb->CurrentSelection();
695					int32 choices = cb->fChoiceList->CountChoices();
696					// select something, if no selection
697					if (index < 0 && choices > 0) {
698						if (aKey == B_UP_ARROW) {
699							cb->Select(choices - 1);
700						} else {
701							cb->Select(0);
702						}
703					}
704					cb->fPopupWindow->Unlock();
705				}
706			}
707			break;
708#endif
709		case B_ESCAPE:
710			cb = dynamic_cast<BComboBox*>(Parent());
711			ASSERT(cb);
712			if (cb->fChoiceList)
713			{
714				cb = dynamic_cast<BComboBox*>(Parent());
715				ASSERT(cb);
716				if (cb->fPopupWindow && cb->fPopupWindow->Lock())
717				{
718					if (!cb->fPopupWindow->IsHidden())
719						cb->HidePopupWindow();
720
721					cb->fPopupWindow->Unlock();
722				}
723			}
724			break;
725		case ',':
726			{
727				int32 startSel, endSel;
728				GetSelection(&startSel, &endSel);
729				int32 length = TextLength();
730				if (endSel == length)
731					Select(endSel, endSel);
732				BTextView::KeyDown(bytes, numBytes);
733			}
734			break;
735		default:
736			BTextView::KeyDown(bytes, numBytes);
737			break;
738	}
739}
740
741
742void
743BComboBox::TextInput::MakeFocus(bool state)
744{
745//+	PRINT(("_BTextInput_::MakeFocus(state=%d, view=%s)\n", state,
746//+		Parent()->Name()));
747	if (state == IsFocus())
748		return;
749
750	BComboBox* parent = dynamic_cast<BComboBox*>(Parent());
751	ASSERT(parent);
752
753	BTextView::MakeFocus(state);
754
755	if (state) {
756		SetInitialText();
757		fClean = true;			// text hasn't been dirtied yet.
758
759		BMessage *m;
760		if (Window() && (m = Window()->CurrentMessage()) != 0
761			&& m->what == B_KEY_DOWN) {
762			// we're being focused by a keyboard event, so
763			// select all...
764			SelectAll();
765		}
766	} else {
767		ASSERT(fInitialText);
768		if (strcmp(fInitialText, Text()) != 0)
769			parent->CommitValue();
770
771		free(fInitialText);
772		fInitialText = NULL;
773		fClean = false;
774		BMessage *m;
775		if (Window() && (m = Window()->CurrentMessage()) != 0 && m->what == B_MOUSE_DOWN)
776			Select(0,0);
777
778		// hide popup window if it's showing when the text input loses focus
779		if (parent->fPopupWindow && parent->fPopupWindow->Lock()) {
780			if (!parent->fPopupWindow->IsHidden())
781				parent->HidePopupWindow();
782
783			parent->fPopupWindow->Unlock();
784		}
785	}
786
787	// make sure the focus indicator gets drawn or undrawn
788	if (Window()) {
789		BRect invalRect(Bounds());
790		invalRect.InsetBy(-kTextInputMargin, -kTextInputMargin);
791		parent->Draw(invalRect);
792		parent->Flush();
793	}
794}
795
796
797void
798BComboBox::TextInput::FrameResized(float x, float y)
799{
800	BTextView::FrameResized(x, y);
801	AlignTextRect();
802}
803
804
805void
806BComboBox::TextInput::Paste(BClipboard *clipboard)
807{
808	BTextView::Paste(clipboard);
809	Invalidate();
810}
811
812
813// What a hack...
814void
815BComboBox::TextInput::AlignTextRect()
816{
817	BRect bounds = Bounds();
818	BRect textRect = TextRect();
819
820	switch (Alignment()) {
821		default:
822		case B_ALIGN_LEFT:
823			textRect.OffsetTo(B_ORIGIN);
824			break;
825
826		case B_ALIGN_CENTER:
827			textRect.OffsetTo((bounds.Width() - textRect.Width()) / 2,
828				textRect.top);
829			break;
830
831		case B_ALIGN_RIGHT:
832			textRect.OffsetTo(bounds.Width() - textRect.Width(), textRect.top);
833			break;
834	}
835
836	SetTextRect(textRect);
837}
838
839
840void
841BComboBox::TextInput::InsertText(const char *inText, int32 inLength,
842	int32 inOffset, const text_run_array *inRuns)
843{
844	char* ptr = NULL;
845
846	// strip out any return characters
847	// limiting to a reasonable amount of chars for a text control.
848	// otherwise this code could malloc some huge amount which isn't good.
849	if (strpbrk(inText, "\r\n") && inLength <= 1024) {
850		int32 len = inLength;
851		ptr = (char *)malloc(len + 1);
852		if (ptr) {
853			strncpy(ptr, inText, len);
854			ptr[len] = '\0';
855
856			char *p = ptr;
857
858			while (len--) {
859				if (*p == '\n')
860					*p = ' ';
861				else if (*p == '\r')
862					*p = ' ';
863
864				p++;
865			}
866		}
867	}
868
869	if (fFilter != NULL)
870		inText = fFilter(inText, inLength, inRuns);
871	BTextView::InsertText(ptr ? ptr : inText, inLength, inOffset, inRuns);
872
873	BComboBox *parent = dynamic_cast<BComboBox *>(Parent());
874	if (parent) {
875		if (parent->fModificationMessage)
876			parent->Invoke(parent->fModificationMessage);
877
878		BMessage *msg;
879		parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage),
880			parent);
881		delete msg;
882	}
883
884	if (ptr)
885		free(ptr);
886}
887
888
889void
890BComboBox::TextInput::DeleteText(int32 fromOffset, int32 toOffset)
891{
892	BTextView::DeleteText(fromOffset, toOffset);
893	BComboBox *parent = dynamic_cast<BComboBox *>(Parent());
894	if (parent) {
895		if (parent->fModificationMessage)
896			parent->Invoke(parent->fModificationMessage);
897
898		BMessage *msg;
899		parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage),
900			parent);
901		delete msg;
902	}
903}
904
905
906//	#pragma mark -
907
908
909BComboBox::ComboBoxWindow::ComboBoxWindow(BComboBox *box)
910	: BWindow(BRect(0, 0, 10, 10), NULL,  B_BORDERED_WINDOW_LOOK,
911		B_FLOATING_SUBSET_WINDOW_FEEL, B_NOT_MOVABLE | B_NOT_RESIZABLE
912			| B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
913			| B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS)
914{
915	fParent = box;
916	DoPosition();
917	BWindow *parentWin = fParent->Window();
918	if (parentWin)
919		AddToSubset(parentWin);
920
921	BRect rect(Bounds());
922	rect.right -= B_V_SCROLL_BAR_WIDTH;
923	fListView = new ChoiceListView(rect, fParent);
924	AddChild(fListView);
925	rect.left = rect.right;
926	rect.right += B_V_SCROLL_BAR_WIDTH;
927	fScrollBar = new BScrollBar(rect, "_popup_scroll_bar_", fListView, 0, 1000,
928		B_VERTICAL);
929	AddChild(fScrollBar);
930	fListView->AdjustScrollBar();
931}
932
933
934BComboBox::ComboBoxWindow::~ComboBoxWindow()
935{
936	fListView->RemoveSelf();
937	delete fListView;
938}
939
940
941void BComboBox::ComboBoxWindow::WindowActivated(bool /*active*/)
942{
943//	if (active)
944//		fListView->AdjustScrollBar();
945}
946
947
948void BComboBox::ComboBoxWindow::FrameResized(float /*width*/, float /*height*/)
949{
950	fListView->AdjustScrollBar();
951}
952
953
954void BComboBox::ComboBoxWindow::DoPosition()
955{
956	BRect winRect(fParent->fText->Frame());
957	winRect = fParent->ConvertToScreen(winRect);
958//	winRect.left += fParent->Divider() + 5;
959	winRect.right -= 2;
960	winRect.OffsetTo(winRect.left, winRect.bottom + kTextInputMargin);
961	winRect.bottom = winRect.top + 100;
962	MoveTo(winRect.LeftTop());
963	ResizeTo(winRect.IntegerWidth(), winRect.IntegerHeight());
964}
965
966
967BComboBox::ChoiceListView *BComboBox::ComboBoxWindow::ListView()
968{
969	return fListView;
970}
971
972
973BScrollBar *BComboBox::ComboBoxWindow::ScrollBar()
974{
975	return fScrollBar;
976}
977
978
979// ----------------------------------------------------------------------------
980//	#pragma mark -
981
982
983BComboBox::BComboBox(BRect frame, const char *name, const char *label,
984	BMessage *message, uint32 resizeMask, uint32 flags)
985	: BControl(frame, name, label, message, resizeMask,
986		flags | B_WILL_DRAW | B_FRAME_EVENTS),
987	  fPopupWindow(NULL),
988	  fModificationMessage(NULL),
989	  fChoiceList(0),
990	  fLabelAlign(B_ALIGN_LEFT),
991	  fAutoComplete(false),
992	  fButtonDepressed(false),
993	  fDepressedWhenClicked(false),
994	  fTrackingButtonDown(false),
995	  fFrameCache(frame)
996{
997	// If the user wants this control to be keyboard navigable, then we really
998	// want the underlying text view to be navigable, not this view.
999	bool navigate = ((Flags() & B_NAVIGABLE) != 0);
1000	if (navigate)
1001	{
1002		fSkipSetFlags = true;
1003		SetFlags(Flags() & ~B_NAVIGABLE);	// disable navigation for this
1004		fSkipSetFlags = false;
1005	}
1006
1007	fDivider = StringWidth(label);
1008
1009	BRect rect(frame);
1010	rect.OffsetTo(0, 0);
1011	rect.left += fDivider + kLabelRightMargin;
1012//	rect.right -= kButtonWidth + 1;
1013//	rect.right;
1014	rect.InsetBy(kTextInputMargin, kTextInputMargin);
1015	BRect textRect(rect);
1016	textRect.OffsetTo(0, 0);
1017	textRect.left += 2;
1018	textRect.right -= 2;
1019
1020	fText = new TextInput(rect, textRect, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT,
1021		B_WILL_DRAW | B_FRAME_EVENTS | (navigate ? B_NAVIGABLE : 0));
1022	float height = fText->LineHeight();
1023	rect.bottom = rect.top + height;
1024//	fText->ResizeTo(rect.IntegerWidth(), height);
1025	AddChild(fText);
1026
1027	font_height	fontInfo;
1028	GetFontHeight(&fontInfo);
1029	float h1 = ceil(fontInfo.ascent + fontInfo.descent + fontInfo.leading);
1030	float h2 = fText->LineHeight();
1031
1032	// Height of main view must be the larger of h1 and h2+(TV_MARGIN*2)
1033	float h = (h1 > h2 + (TV_MARGIN*2)) ? h1 : h2 + (TV_MARGIN*2);
1034	BRect b = Bounds();
1035	ResizeTo(b.Width(), h);
1036	b.bottom = h;
1037
1038	// set height and position of text entry view
1039	fText->ResizeTo(fText->Bounds().Width(), h2);
1040	// vertically center this view
1041	fText->MoveBy(0, (b.Height() - (h2+(TV_MARGIN*2))) / 2);
1042
1043	rect.left = rect.right + 1;
1044	rect.right = rect.left + kButtonWidth;
1045
1046	fButtonRect = rect;
1047	fTextEnd = 0;
1048	fSelected = -1;
1049	fCompletionIndex = -1;
1050	fWinMovedFilter = new MovedMessageFilter(this);
1051}
1052
1053
1054BComboBox::~BComboBox()
1055{
1056	if (fPopupWindow && fPopupWindow->Lock())
1057		fPopupWindow->Quit();
1058
1059	RemoveChild(fText);
1060	delete fText;
1061
1062	if (fWinMovedFilter->Looper())
1063		fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter);
1064
1065	delete fWinMovedFilter;
1066
1067}
1068
1069
1070void BComboBox::SetChoiceList(BChoiceList *list)
1071{
1072//	delete fChoiceList;
1073	fChoiceList = list;
1074	ChoiceListUpdated();
1075}
1076
1077
1078BChoiceList *BComboBox::ChoiceList()
1079{
1080	return fChoiceList;
1081}
1082
1083
1084void BComboBox::ChoiceListUpdated()
1085{
1086	if (fPopupWindow && fPopupWindow->Lock())
1087	{
1088		if (!fPopupWindow->IsHidden())
1089		{
1090			// do an invalidate on the choice list
1091			fPopupWindow->ListView()->Invalidate();
1092			fPopupWindow->ListView()->AdjustScrollBar();
1093			// XXX: change the selection and select the proper item, if possible
1094		}
1095		fPopupWindow->Unlock();
1096	}
1097}
1098
1099
1100//void BComboBox::AddChoice(const char *text)
1101//{
1102//	fChoiceList.AddItem((char *)text);
1103//	if (fPopupWindow && fPopupWindow->Lock()) {
1104//		if (!fPopupWindow->IsHidden()) {
1105//			// do an invalidate on the new item's location
1106//			int32 index = CountChoices() - 1;
1107//			fPopupWindow->ListView()->InvalidateItem(index);
1108//			fPopupWindow->ListView()->AdjustScrollBar();
1109//		}
1110//		fPopupWindow->Unlock();
1111//	}
1112//}
1113
1114
1115//const char *BComboBox::ChoiceAt(int32 index)
1116//{
1117//	return (const char *)fChoiceList.ItemAt(index);
1118//}
1119
1120
1121//int32 BComboBox::CountChoices()
1122//{
1123//	return fChoiceList.CountItems();
1124//}
1125
1126
1127void
1128BComboBox::Select(int32 index, bool changeTextSelection)
1129{
1130	int32 oldIndex = fSelected;
1131	if (index < fChoiceList->CountChoices() && index >= 0) {
1132		BWindow *win = Window();
1133		bool gotLock = (win && win->Lock());
1134		if (!win || gotLock) {
1135			fSelected = index;
1136			if (fPopupWindow && fPopupWindow->Lock()) {
1137				ChoiceListView *lv = fPopupWindow->ListView();
1138				lv->InvalidateItem(oldIndex);
1139				lv->InvalidateItem(fSelected);
1140				lv->ScrollToSelection();
1141				fPopupWindow->Unlock();
1142			}
1143
1144			if (changeTextSelection) {
1145				// Find last coma
1146				const char *ptr = fText->Text();
1147				const char *end;
1148				int32 tlength = fText->TextLength();
1149
1150				for (end = ptr+tlength-1; end>ptr; end--) {
1151					if (*end == ',') {
1152						// Find end of whitespace
1153						for (end++; isspace(*end); end++) {}
1154						break;
1155					}
1156				}
1157				int32 soffset = end-ptr;
1158				int32 eoffset = tlength;
1159				if (end != 0)
1160					fText->Delete(soffset, eoffset);
1161
1162				tlength = strlen(fChoiceList->ChoiceAt(fSelected));
1163				fText->Insert(soffset, fChoiceList->ChoiceAt(fSelected), tlength);
1164				eoffset = fText->TextLength();
1165				fText->Select(soffset, eoffset);
1166//				fText->SetText(fChoiceList->ChoiceAt(fSelected));
1167//				fText->SelectAll();
1168			}
1169
1170			if (gotLock)
1171				win->Unlock();
1172		}
1173	} else {
1174		Deselect();
1175		return;
1176	}
1177}
1178
1179
1180void
1181BComboBox::Deselect()
1182{
1183	BWindow *win = Window();
1184	bool gotLock = (win && win->Lock());
1185	if (!win || gotLock) {
1186		int32 oldIndex = fSelected;
1187		fSelected = -1;
1188		// invalidate the old selected item, if needed
1189		if (oldIndex >= 0 && fPopupWindow && fPopupWindow->Lock()) {
1190			fPopupWindow->ListView()->InvalidateItem(oldIndex);
1191			fPopupWindow->Unlock();
1192		}
1193
1194		if (gotLock)
1195			win->Unlock();
1196	}
1197}
1198
1199
1200int32
1201BComboBox::CurrentSelection()
1202{
1203	return fSelected;
1204}
1205
1206
1207void
1208BComboBox::SetAutoComplete(bool on)
1209{
1210	fAutoComplete = on;
1211}
1212
1213
1214bool
1215BComboBox::GetAutoComplete()
1216{
1217	return fAutoComplete;
1218}
1219
1220
1221void
1222BComboBox::SetLabel(const char *text)
1223{
1224	BControl::SetLabel(text);
1225	BRect invalRect = Bounds();
1226	invalRect.right = fDivider;
1227	Invalidate(invalRect);
1228}
1229
1230
1231void
1232BComboBox::SetValue(int32 value)
1233{
1234	BControl::SetValue(value);
1235}
1236
1237
1238void
1239BComboBox::SetText(const char *text)
1240{
1241	fText->SetText(text);
1242	if (fText->IsFocus())
1243		fText->SetInitialText();
1244
1245	fText->Invalidate();
1246}
1247
1248
1249const char *
1250BComboBox::Text() const
1251{
1252	return fText->Text();
1253}
1254
1255
1256BTextView *
1257BComboBox::TextView()
1258{
1259	return fText;
1260}
1261
1262
1263void
1264BComboBox::SetDivider(float divide)
1265{
1266	float diff = fDivider - divide;
1267	fDivider = divide;
1268
1269	fText->MoveBy(-diff, 0);
1270	fText->ResizeBy(diff, 0);
1271
1272	if (Window()) {
1273		fText->Invalidate();
1274		Invalidate();
1275	}
1276}
1277
1278
1279float
1280BComboBox::Divider() const
1281{
1282	return fDivider;
1283}
1284
1285
1286void
1287BComboBox::SetAlignment(alignment label, alignment text)
1288{
1289	fText->SetAlignment(text);
1290	fText->AlignTextRect();
1291
1292	if (fLabelAlign != label) {
1293		fLabelAlign = label;
1294		Invalidate();
1295	}
1296}
1297
1298
1299void
1300BComboBox::GetAlignment(alignment *label, alignment *text) const
1301{
1302	*text = fText->Alignment();
1303	*label = fLabelAlign;
1304}
1305
1306
1307void
1308BComboBox::SetModificationMessage(BMessage *message)
1309{
1310	delete fModificationMessage;
1311	fModificationMessage = message;
1312}
1313
1314
1315BMessage *
1316BComboBox::ModificationMessage() const
1317{
1318	return fModificationMessage;
1319}
1320
1321
1322void
1323BComboBox::SetFilter(text_input_filter_hook hook)
1324{
1325	fText->SetFilter(hook);
1326}
1327
1328
1329void
1330BComboBox::GetPreferredSize(float */*width*/, float */*height*/)
1331{
1332//	BFont font;
1333//	GetFont(&font);
1334//
1335//	*width = Bounds().IntegerWidth();
1336//	if (Label() != NULL) {
1337//		float strWidth = font.StringWidth(Label());
1338//		*width = ceil(kTextInputMargin + strWidth + kLabelRightMargin +
1339//					  (strWidth * 1.50) + kTextInputMargin);
1340//	}
1341//
1342//	font_height	finfo;
1343//	float		h1;
1344//	float		h2;
1345//
1346//	font.GetHeight(&finfo);
1347//	h1 = ceil(finfo.ascent + finfo.descent + finfo.leading);
1348//	h2 = fText->LineHeight();
1349//
1350//	// Height of main view must be the larger of h1 and h2+(kTextInputMargin*2)
1351//	*height = ceil((h1 > h2 + (kTextInputMargin*2)) ? h1 : h2 + (kTextInputMargin*2));
1352}
1353
1354
1355void
1356BComboBox::ResizeToPreferred()
1357{
1358	BControl::ResizeToPreferred();
1359}
1360
1361
1362void
1363BComboBox::FrameMoved(BPoint new_position)
1364{
1365	if (fPopupWindow && fPopupWindow->Lock()) {
1366		fPopupWindow->MoveBy(new_position.x - fFrameCache.left,
1367			new_position.y - fFrameCache.top);
1368		fPopupWindow->Unlock();
1369	}
1370	fFrameCache.OffsetTo(new_position);
1371}
1372
1373
1374void
1375BComboBox::FrameResized(float new_width, float new_height)
1376{
1377	// It's the cheese!
1378	float dx = new_width - fFrameCache.Width();
1379	float dy = new_height - fFrameCache.Height();
1380	if (dx != 0 && Window()) {
1381//		BRect inval(fFrameCache.right, fFrameCache.top,
1382//			fFrameCache.right+dx, fFrameCache.bottom);
1383		BRect inval(Bounds());
1384		if (dx > 0)
1385			inval.left = inval.right-dx-1;
1386		else
1387			inval.left = inval.right-3;
1388//		Window()->ConvertToScreen(&inval);
1389//		ConvertFromScreen(&inval);
1390		Invalidate(inval);
1391	}
1392
1393	fFrameCache.right += dx;
1394	fFrameCache.bottom += dy;
1395//	fButtonRect.OffsetBy(dx, 0);
1396
1397	if (fPopupWindow && fPopupWindow->Lock()) {
1398		if (!fPopupWindow->IsHidden())
1399			HidePopupWindow();
1400
1401		fPopupWindow->Unlock();
1402	}
1403}
1404
1405
1406void
1407BComboBox::WindowActivated(bool /*active*/)
1408{
1409	if (fText->IsFocus())
1410		Draw(Bounds());
1411}
1412
1413
1414void
1415BComboBox::Draw(BRect /*updateRect*/)
1416{
1417	BRect bounds = Bounds();
1418	font_height	fInfo;
1419	rgb_color high = HighColor();
1420	rgb_color base = ViewColor();
1421	bool focused;
1422	bool enabled;
1423	rgb_color white = {255, 255, 255, 255};
1424	rgb_color black = { 0, 0, 0, 255 };
1425
1426	enabled = IsEnabled();
1427	focused = fText->IsFocus() && Window()->IsActive();
1428
1429	BRect fr = fText->Frame();
1430
1431	fr.InsetBy(-3, -3);
1432	fr.bottom -= 1;
1433	if (enabled)
1434		SetHighColor(tint_color(base, B_DARKEN_1_TINT));
1435	else
1436		SetHighColor(base);
1437
1438	StrokeLine(fr.LeftBottom(), fr.LeftTop());
1439	StrokeLine(fr.RightTop());
1440
1441	if (enabled)
1442		SetHighColor(white);
1443	else
1444		SetHighColor(tint_color(base, B_LIGHTEN_2_TINT));
1445
1446	StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom());
1447	StrokeLine(fr.RightTop()+BPoint(0,1));
1448	fr.InsetBy(1,1);
1449
1450	if (focused) {
1451		// draw UI indication for 'active'
1452		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
1453		StrokeRect(fr);
1454	} else {
1455		if (enabled)
1456			SetHighColor(tint_color(base, B_DARKEN_4_TINT));
1457		else
1458			SetHighColor(tint_color(base, B_DARKEN_2_TINT));
1459		StrokeLine(fr.LeftBottom(), fr.LeftTop());
1460		StrokeLine(fr.RightTop());
1461		SetHighColor(base);
1462		StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom());
1463		StrokeLine(fr.RightTop()+BPoint(0,1));
1464	}
1465
1466	fr.InsetBy(1,1);
1467
1468	if (!enabled)
1469		SetHighColor(tint_color(base, B_DISABLED_MARK_TINT));
1470	else
1471		SetHighColor(white);
1472
1473	StrokeRect(fr);
1474	SetHighColor(high);
1475
1476	bounds.right = bounds.left + fDivider;
1477	if ((Label()) && (fDivider > 0.0)) {
1478		BPoint	loc;
1479		GetFontHeight(&fInfo);
1480
1481		switch (fLabelAlign) {
1482			default:
1483			case B_ALIGN_LEFT:
1484				loc.x = bounds.left + TV_MARGIN;
1485				break;
1486			case B_ALIGN_CENTER:
1487			{
1488				float width = StringWidth(Label());
1489				float center = (bounds.right - bounds.left) / 2;
1490				loc.x = center - (width/2);
1491				break;
1492			}
1493			case B_ALIGN_RIGHT:
1494			{
1495				float width = StringWidth(Label());
1496				loc.x = bounds.right - width - TV_MARGIN;
1497				break;
1498			}
1499		}
1500
1501		uint32 rmode = ResizingMode();
1502		if ((rmode & _rule_(0xf, 0, 0xf, 0)) == _rule_(_VIEW_TOP_, 0, _VIEW_BOTTOM_, 0))
1503			loc.y = fr.bottom - 2;
1504		else
1505			loc.y = bounds.bottom - (2 + ceil(fInfo.descent));
1506
1507		MovePenTo(loc);
1508		SetHighColor(black);
1509		DrawString(Label());
1510		SetHighColor(high);
1511	}
1512}
1513
1514
1515void
1516BComboBox::MessageReceived(BMessage *msg)
1517{
1518	switch (msg->what) {
1519		case kTextInputModifyMessage:
1520			TryAutoComplete();
1521			break;
1522		case kPopupButtonInvokeMessage:
1523			if (fChoiceList && fChoiceList->CountChoices() && !fPopupWindow)
1524				fPopupWindow = CreatePopupWindow();
1525
1526			if (fPopupWindow->Lock()) {
1527				if (fPopupWindow->IsHidden())
1528					ShowPopupWindow();
1529				else
1530					HidePopupWindow();
1531
1532				fPopupWindow->Unlock();
1533			}
1534			break;
1535		case kWindowMovedMessage:
1536			if (fPopupWindow && fPopupWindow->Lock()) {
1537				if (!fPopupWindow->IsHidden())
1538					HidePopupWindow();
1539
1540				fPopupWindow->Unlock();
1541			}
1542			break;
1543		default:
1544			BControl::MessageReceived(msg);
1545	}
1546}
1547
1548
1549void
1550BComboBox::MouseDown(BPoint where)
1551{
1552//	printf("BComboBox::MouseDown(%f, %f)\n", where.x, where.y);
1553	/*if (fButtonRect.Contains(where)) { 		// clicked in button area
1554		fDepressedWhenClicked = fButtonDepressed;
1555		fButtonDepressed = !fButtonDepressed;
1556		fTrackingButtonDown = true;
1557		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
1558		Invalidate(fButtonRect);
1559		fText->MakeFocus(true);
1560	}*/
1561	BControl::MouseDown(where);
1562}
1563
1564
1565void
1566BComboBox::MouseUp(BPoint /*where*/)
1567{
1568	if (fTrackingButtonDown) {
1569		// send an invoke message when the button changes state
1570		if (fButtonDepressed != fDepressedWhenClicked) {
1571			BMessage *msg;
1572			Window()->PostMessage(msg = new BMessage(kPopupButtonInvokeMessage),this);
1573			delete msg;
1574		}
1575		fTrackingButtonDown = false;
1576	}
1577}
1578
1579
1580void
1581BComboBox::MouseMoved(BPoint where, uint32 /*transit*/,const BMessage */*dragMessage*/)
1582{
1583	if (fTrackingButtonDown) {
1584		BRect sloppyRect = fButtonRect;
1585		sloppyRect.InsetBy(-3, -3);
1586
1587		bool oldState = fButtonDepressed;
1588		fButtonDepressed = sloppyRect.Contains(where) ? !fDepressedWhenClicked
1589			: fDepressedWhenClicked;
1590
1591		if (oldState != fButtonDepressed)
1592			Invalidate(fButtonRect);
1593	}
1594}
1595
1596
1597status_t
1598BComboBox::Invoke(BMessage *msg)
1599{
1600	return BControl::Invoke(msg);
1601}
1602
1603
1604void
1605BComboBox::AttachedToWindow()
1606{
1607	Window()->AddFilter(fWinMovedFilter);
1608	if (Parent()) {
1609		SetViewColor(Parent()->ViewColor());
1610		SetLowColor(ViewColor());
1611	}
1612
1613	bool enabled = IsEnabled();
1614	rgb_color mc = HighColor();
1615	rgb_color base;
1616	BFont textFont;
1617
1618	// mc used to be base in this line
1619	if (mc.red == 255 && mc.green == 255 && mc.blue == 255)
1620		base = ViewColor();
1621	else
1622		base = LowColor();
1623
1624	fText->GetFontAndColor(0, &textFont);
1625	mc = enabled ? mc : disable_color(base);
1626
1627	fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc);
1628
1629	if (!enabled)
1630		base = tint_color(base, B_DISABLED_MARK_TINT);
1631	else
1632		base.red = base.green = base.blue = 255;
1633
1634	fText->SetLowColor(base);
1635	fText->SetViewColor(base);
1636
1637	fText->MakeEditable(enabled);
1638}
1639
1640
1641void
1642BComboBox::DetachedFromWindow()
1643{
1644	fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter);
1645}
1646
1647
1648void
1649BComboBox::SetFlags(uint32 flags)
1650{
1651	if (!fSkipSetFlags) {
1652		uint32 te_flags = fText->Flags();
1653		bool te_nav = ((te_flags & B_NAVIGABLE) != 0);
1654		bool wants_nav = ((flags & B_NAVIGABLE) != 0);
1655
1656		// the ComboBox should never be navigable
1657		ASSERT((Flags() & B_NAVIGABLE) == 0);
1658
1659		if (!te_nav && wants_nav) {
1660			// The combo box wants to be navigable. Pass that along to
1661			// the text view
1662			fText->SetFlags(te_flags | B_NAVIGABLE);
1663		} else if (te_nav && !wants_nav) {
1664			// Caller wants to end NAV on the text view;
1665			fText->SetFlags(te_flags & ~B_NAVIGABLE);
1666		}
1667
1668		flags = flags & ~B_NAVIGABLE;	// never want NAV for the combo box
1669	}
1670	BControl::SetFlags(flags);
1671}
1672
1673
1674void
1675BComboBox::SetEnabled(bool enabled)
1676{
1677	if (enabled == IsEnabled())
1678		return;
1679
1680	if (Window()) {
1681		fText->MakeEditable(enabled);
1682		rgb_color mc = HighColor();
1683		rgb_color base = ViewColor();
1684
1685		mc = (enabled) ? mc : disable_color(base);
1686		BFont textFont;
1687		fText->GetFontAndColor(0, &textFont);
1688		fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc);
1689
1690		if (!enabled)
1691			base = tint_color(base, B_DISABLED_MARK_TINT);
1692		else
1693			base.red = base.green = base.blue = 255;
1694
1695		fText->SetLowColor(base);
1696		fText->SetViewColor(base);
1697
1698		fText->Invalidate();
1699		Window()->UpdateIfNeeded();
1700	}
1701
1702	fSkipSetFlags = true;
1703	BControl::SetEnabled(enabled);
1704	fSkipSetFlags = false;
1705
1706//+	// Want the sub_view to be the navigable one. We always want to be able
1707//+	// to navigate to that view, even if disabled since Copy still works.
1708//+	fText->SetFlags(fText->Flags() | B_NAVIGABLE);
1709//+	SetFlags(Flags() & ~B_NAVIGABLE);
1710}
1711
1712
1713//void BComboBox::AllAttached()
1714//{
1715//}
1716
1717
1718BComboBox::ComboBoxWindow*
1719BComboBox::CreatePopupWindow()
1720{
1721	ComboBoxWindow *win = new ComboBoxWindow(this);
1722	return win;
1723}
1724
1725
1726void
1727BComboBox::CommitValue()
1728{
1729	Invoke();
1730}
1731
1732
1733void
1734BComboBox::TryAutoComplete()
1735{
1736	int32 from, to;
1737	fText->GetSelection(&from, &to);
1738	if (fAutoComplete && from == to) {
1739		bool autoCompleted = false;
1740		const char *ptr = fText->Text();
1741		if (to > fTextEnd && from == fText->TextLength()) {
1742			const char *completion;
1743			// find the first matching choice and do auto-completion
1744
1745			// Find last comma
1746			const char *end;
1747			for (end = fText->Text()+fText->TextLength()-1; end>ptr; end--) {
1748				if (*end == ',') {
1749					// Find end of whitespace
1750					for (end++; isspace(*end); end++) {}
1751					if (*end == 0)
1752						return;
1753					break;
1754				}
1755			}
1756			if (fChoiceList->GetMatch(end, 0, &fCompletionIndex, &completion) == B_OK) {
1757				fText->Insert(completion);
1758				fText->Select(to, to + strlen(completion));
1759				Select(fCompletionIndex);
1760				autoCompleted = true;
1761			} else
1762				fCompletionIndex = -1;
1763		}
1764		fTextEnd = to;
1765
1766		if (!autoCompleted) {
1767			int32 sel = CurrentSelection();
1768			if (sel >= 0) {
1769				const char *selText = fChoiceList->ChoiceAt(sel);
1770				if (selText && !strcmp(ptr, selText)) {
1771					// don't Deselect() if the text input matches the selection
1772					return;
1773				}
1774			}
1775			fCompletionIndex = -1;
1776			Deselect();
1777		}
1778	}
1779}
1780
1781
1782// fPopupWindow must exist and already be locked & hidden when this function
1783// is called
1784void
1785BComboBox::ShowPopupWindow()
1786{
1787	// adjust position of the popup window
1788	fPopupWindow->DoPosition();
1789	fPopupWindow->ListView()->SetEventMask(B_POINTER_EVENTS, 0);
1790	fPopupWindow->Show();
1791	fPopupWindow->ListView()->MakeFocus(true);
1792}
1793
1794
1795// fPopupWindow must exist and already be locked & shown when this function
1796// is called
1797void
1798BComboBox::HidePopupWindow()
1799{
1800	fPopupWindow->Hide();
1801	fPopupWindow->ListView()->SetEventMask(0, 0);
1802	fButtonDepressed = false;
1803	Invalidate(fButtonRect);
1804}
1805
1806
1807void
1808BComboBox::MakeFocus(bool state)
1809{
1810	fText->MakeFocus(state);
1811	if (state)
1812		fText->SelectAll();
1813}
1814
1815
1816//	#pragma mark -
1817
1818
1819BComboBox::MovedMessageFilter::MovedMessageFilter(BHandler *target)
1820	: BMessageFilter(B_WINDOW_MOVED)
1821{
1822	fTarget = target;
1823}
1824
1825
1826filter_result
1827BComboBox::MovedMessageFilter::Filter(BMessage *message,BHandler **/*target*/)
1828{
1829	BMessage *dup = new BMessage(*message);
1830	dup->what = kWindowMovedMessage;
1831	if (fTarget->Looper())
1832		fTarget->Looper()->PostMessage(dup, fTarget);
1833
1834	delete dup;
1835	return B_DISPATCH_MESSAGE;
1836}
1837
1838
1839//	#pragma mark -
1840
1841
1842BDefaultChoiceList::BDefaultChoiceList(BComboBox *owner)
1843{
1844	fOwner = owner;
1845	fList = new StringObjectList();
1846}
1847
1848
1849BDefaultChoiceList::~BDefaultChoiceList()
1850{
1851	BString *string;
1852	while ((string = fList->RemoveItemAt(0)) != NULL) {
1853		delete string;
1854	}
1855
1856	delete fList;
1857}
1858
1859
1860const char*
1861BDefaultChoiceList::ChoiceAt(int32 index)
1862{
1863	BString *string = fList->ItemAt(index);
1864	if (string)
1865		return string->String();
1866
1867	return NULL;
1868}
1869
1870
1871status_t
1872BDefaultChoiceList::GetMatch(const char *prefix, int32 startIndex,
1873	int32 *matchIndex, const char **completionText)
1874{
1875	BString *str;
1876	int32 len = strlen(prefix);
1877	int32 choices = fList->CountItems();
1878
1879	for (int32 i = startIndex; i < choices; i++) {
1880		str = fList->ItemAt(i);
1881		if (!str->ICompare(prefix, len)) {
1882			// prefix matches
1883			*matchIndex = i;
1884			*completionText = str->String() + len;
1885			return B_OK;
1886		}
1887	}
1888	*matchIndex = -1;
1889	*completionText = NULL;
1890	return B_ERROR;
1891}
1892
1893
1894int32
1895BDefaultChoiceList::CountChoices()
1896{
1897	return fList->CountItems();
1898}
1899
1900
1901status_t
1902BDefaultChoiceList::AddChoice(const char *toAdd)
1903{
1904	BString *str = new BString(toAdd);
1905	bool r = fList->AddItem(str);
1906	if (fOwner)
1907		fOwner->ChoiceListUpdated();
1908
1909	return (r) ? B_OK : B_ERROR;
1910}
1911
1912
1913status_t
1914BDefaultChoiceList::AddChoiceAt(const char *toAdd, int32 index)
1915{
1916	BString *str = new BString(toAdd);
1917	bool r = fList->AddItem(str, index);
1918	if (fOwner)
1919		fOwner->ChoiceListUpdated();
1920
1921	return r ? B_OK : B_ERROR;
1922}
1923
1924
1925//int BStringCompareFunction(const BString *s1, const BString *s2)
1926//{
1927//	return s1->Compare(s2);
1928//}
1929
1930
1931status_t
1932BDefaultChoiceList::RemoveChoice(const char *toRemove)
1933{
1934	BString *string;
1935	int32 choices = fList->CountItems();
1936	for (int32 i = 0; i < choices; i++) {
1937		string = fList->ItemAt(i);
1938		if (!string->Compare(toRemove)) {
1939			fList->RemoveItemAt(i);
1940			if (fOwner)
1941				fOwner->ChoiceListUpdated();
1942
1943			return B_OK;
1944		}
1945	}
1946	return B_ERROR;
1947}
1948
1949
1950status_t
1951BDefaultChoiceList::RemoveChoiceAt(int32 index)
1952{
1953	BString *string = fList->RemoveItemAt(index);
1954	if (string) {
1955		delete string;
1956		if (fOwner)
1957			fOwner->ChoiceListUpdated();
1958
1959		return B_OK;
1960	}
1961	return B_ERROR;
1962}
1963
1964
1965void
1966BDefaultChoiceList::SetOwner(BComboBox *owner)
1967{
1968	fOwner = owner;
1969}
1970
1971
1972BComboBox*
1973BDefaultChoiceList::Owner()
1974{
1975	return fOwner;
1976}
1977
1978