xref: /haiku/src/apps/webpositive/URLInputGroup.cpp (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
1 /*
2  * Copyright 2010 Stephan Aßmus <superstippi@gmx.de>
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "URLInputGroup.h"
7 
8 #include <Bitmap.h>
9 #include <Button.h>
10 #include <Catalog.h>
11 #include <ControlLook.h>
12 #include <Clipboard.h>
13 #include <GroupLayout.h>
14 #include <GroupLayoutBuilder.h>
15 #include <Locale.h>
16 #include <LayoutUtils.h>
17 #include <MenuItem.h>
18 #include <PopUpMenu.h>
19 #include <SeparatorView.h>
20 #include <TextView.h>
21 #include <Window.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 
26 #include "BaseURL.h"
27 #include "BitmapButton.h"
28 #include "BrowsingHistory.h"
29 #include "IconButton.h"
30 #include "IconUtils.h"
31 #include "TextViewCompleter.h"
32 
33 
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "URL Bar"
36 
37 
38 class URLChoice : public BAutoCompleter::Choice {
39 public:
40 	URLChoice(const BString& choiceText, const BString& displayText,
41 			int32 matchPos, int32 matchLen, int32 priority)
42 		:
43 		BAutoCompleter::Choice(choiceText, displayText, matchPos, matchLen),
44 		fPriority(priority)
45 	{
46 	}
47 
48 	bool operator<(const URLChoice& other) const
49 	{
50 		if (fPriority > other.fPriority)
51 			return true;
52 		return DisplayText() < other.DisplayText();
53 	}
54 
55 	bool operator==(const URLChoice& other) const
56 	{
57 		return fPriority == other.fPriority
58 			&& DisplayText() < other.DisplayText();
59 	}
60 
61 private:
62 	int32 fPriority;
63 };
64 
65 
66 class BrowsingHistoryChoiceModel : public BAutoCompleter::ChoiceModel {
67 	virtual void FetchChoicesFor(const BString& pattern)
68 	{
69 		int32 count = CountChoices();
70 		for (int32 i = 0; i < count; i++) {
71 			delete reinterpret_cast<BAutoCompleter::Choice*>(
72 				fChoices.ItemAtFast(i));
73 		}
74 		fChoices.MakeEmpty();
75 
76 		// Search through BrowsingHistory for any matches.
77 		BrowsingHistory* history = BrowsingHistory::DefaultInstance();
78 		if (!history->Lock())
79 			return;
80 
81 		BString lastBaseURL;
82 		int32 priority = INT_MAX;
83 
84 		count = history->CountItems();
85 		for (int32 i = 0; i < count; i++) {
86 			BrowsingHistoryItem item = history->HistoryItemAt(i);
87 			const BString& choiceText = item.URL();
88 			int32 matchPos = choiceText.IFindFirst(pattern);
89 			if (matchPos < 0)
90 				continue;
91 			if (lastBaseURL.Length() > 0
92 				&& choiceText.FindFirst(lastBaseURL) >= 0) {
93 				priority--;
94 			} else
95 				priority = INT_MAX;
96 			lastBaseURL = baseURL(choiceText);
97 			fChoices.AddItem(new URLChoice(choiceText,
98 				choiceText, matchPos, pattern.Length(), priority));
99 		}
100 
101 		history->Unlock();
102 
103 		fChoices.SortItems(_CompareChoices);
104 	}
105 
106 	virtual int32 CountChoices() const
107 	{
108 		return fChoices.CountItems();
109 	}
110 
111 	virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
112 	{
113 		return reinterpret_cast<BAutoCompleter::Choice*>(
114 			fChoices.ItemAt(index));
115 	}
116 
117 	static int _CompareChoices(const void* a, const void* b)
118 	{
119 		const URLChoice* aChoice
120 			= *reinterpret_cast<const URLChoice* const *>(a);
121 		const URLChoice* bChoice
122 			= *reinterpret_cast<const URLChoice* const *>(b);
123 		if (*aChoice < *bChoice)
124 			return -1;
125 		else if (*aChoice == *bChoice)
126 			return 0;
127 		return 1;
128 	}
129 
130 private:
131 	BList fChoices;
132 };
133 
134 
135 // #pragma mark - URLTextView
136 
137 
138 static const float kHorizontalTextRectInset = 4.0;
139 
140 
141 class URLInputGroup::URLTextView : public BTextView {
142 private:
143 	static const uint32 MSG_CLEAR = 'cler';
144 
145 public:
146 								URLTextView(URLInputGroup* parent);
147 	virtual						~URLTextView();
148 
149 	virtual	void				MessageReceived(BMessage* message);
150 	virtual	void				FrameResized(float width, float height);
151 	virtual	void				MouseDown(BPoint where);
152 	virtual	void				KeyDown(const char* bytes, int32 numBytes);
153 	virtual	void				MakeFocus(bool focused = true);
154 
155 	virtual	BSize				MinSize();
156 	virtual	BSize				MaxSize();
157 
158 			void				SetUpdateAutoCompleterChoices(bool update);
159 
160 protected:
161 	virtual	void				InsertText(const char* inText, int32 inLength,
162 									int32 inOffset,
163 									const text_run_array* inRuns);
164 	virtual	void				DeleteText(int32 fromOffset, int32 toOffset);
165 
166 private:
167 			void				_AlignTextRect();
168 
169 private:
170 			URLInputGroup*		fURLInputGroup;
171 			TextViewCompleter*	fURLAutoCompleter;
172 			bool				fUpdateAutoCompleterChoices;
173 };
174 
175 
176 URLInputGroup::URLTextView::URLTextView(URLInputGroup* parent)
177 	:
178 	BTextView("url"),
179 	fURLInputGroup(parent),
180 	fURLAutoCompleter(new TextViewCompleter(this,
181 		new BrowsingHistoryChoiceModel())),
182 	fUpdateAutoCompleterChoices(true)
183 {
184 	MakeResizable(true);
185 	SetStylable(true);
186 	fURLAutoCompleter->SetModificationsReported(true);
187 }
188 
189 
190 URLInputGroup::URLTextView::~URLTextView()
191 {
192 	delete fURLAutoCompleter;
193 }
194 
195 
196 void
197 URLInputGroup::URLTextView::MessageReceived(BMessage* message)
198 {
199 	switch (message->what) {
200 		case MSG_CLEAR:
201 			SetText("");
202 			break;
203 
204 		default:
205 			BTextView::MessageReceived(message);
206 			break;
207 	}
208 }
209 
210 
211 void
212 URLInputGroup::URLTextView::FrameResized(float width, float height)
213 {
214 	BTextView::FrameResized(width, height);
215 	_AlignTextRect();
216 }
217 
218 
219 void
220 URLInputGroup::URLTextView::MouseDown(BPoint where)
221 {
222 	bool wasFocus = IsFocus();
223 	if (!wasFocus)
224 		MakeFocus(true);
225 
226 	int32 buttons;
227 	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK)
228 		buttons = B_PRIMARY_MOUSE_BUTTON;
229 
230 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
231 		// Display context menu
232 		int32 selectionStart;
233 		int32 selectionEnd;
234 		GetSelection(&selectionStart, &selectionEnd);
235 		bool canCutOrCopy = selectionEnd > selectionStart;
236 
237 		bool canPaste = false;
238 		if (be_clipboard->Lock()) {
239 			if (BMessage* data = be_clipboard->Data())
240 				canPaste = data->HasData("text/plain", B_MIME_TYPE);
241 			be_clipboard->Unlock();
242 		}
243 
244 		BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
245 			new BMessage(B_CUT));
246 		BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
247 			new BMessage(B_COPY));
248 		BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
249 			new BMessage(B_PASTE));
250 		BMenuItem* clearItem = new BMenuItem(B_TRANSLATE("Clear"),
251 			new BMessage(MSG_CLEAR));
252 		cutItem->SetEnabled(canCutOrCopy);
253 		copyItem->SetEnabled(canCutOrCopy);
254 		pasteItem->SetEnabled(canPaste);
255 		clearItem->SetEnabled(strlen(Text()) > 0);
256 
257 		BPopUpMenu* menu = new BPopUpMenu("url context");
258 		menu->AddItem(cutItem);
259 		menu->AddItem(copyItem);
260 		menu->AddItem(pasteItem);
261 		menu->AddItem(clearItem);
262 
263 		menu->SetTargetForItems(this);
264 		menu->Go(ConvertToScreen(where), true, true, true);
265 		return;
266 	}
267 
268 	// Only pass through to base class if we already have focus.
269 	if (!wasFocus)
270 		return;
271 
272 	BTextView::MouseDown(where);
273 }
274 
275 
276 void
277 URLInputGroup::URLTextView::KeyDown(const char* bytes, int32 numBytes)
278 {
279 	switch (bytes[0]) {
280 		case B_TAB:
281 			BView::KeyDown(bytes, numBytes);
282 			break;
283 
284 		case B_ESCAPE:
285 			// Text already unlocked && replaced in BrowserWindow,
286 			// now select it.
287 			SelectAll();
288 			break;
289 
290 		case B_RETURN:
291 			// Don't let this through to the text view.
292 			break;
293 
294 		default:
295 		{
296 			BString currentText = Text();
297 			BTextView::KeyDown(bytes, numBytes);
298 			// Lock the URL input if it was modified
299 			if (!fURLInputGroup->IsURLInputLocked()
300 				&& Text() != currentText)
301 				fURLInputGroup->LockURLInput();
302 			break;
303 		}
304 	}
305 }
306 
307 void
308 URLInputGroup::URLTextView::MakeFocus(bool focus)
309 {
310 	// Unlock the URL input if focus was lost.
311 	if (!focus)
312 		fURLInputGroup->LockURLInput(false);
313 
314 	if (focus == IsFocus())
315 		return;
316 
317 	BTextView::MakeFocus(focus);
318 
319 	if (focus)
320 		SelectAll();
321 
322 	fURLInputGroup->Invalidate();
323 }
324 
325 
326 BSize
327 URLInputGroup::URLTextView::MinSize()
328 {
329 	BSize min;
330 	min.height = ceilf(LineHeight(0) + kHorizontalTextRectInset);
331 		// we always add at least one pixel vertical inset top/bottom for
332 		// the text rect.
333 	min.width = min.height * 3;
334 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
335 }
336 
337 
338 BSize
339 URLInputGroup::URLTextView::MaxSize()
340 {
341 	BSize max(MinSize());
342 	max.width = B_SIZE_UNLIMITED;
343 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
344 }
345 
346 
347 void
348 URLInputGroup::URLTextView::SetUpdateAutoCompleterChoices(bool update)
349 {
350 	fUpdateAutoCompleterChoices = update;
351 }
352 
353 
354 void
355 URLInputGroup::URLTextView::InsertText(const char* inText, int32 inLength,
356 	int32 inOffset, const text_run_array* inRuns)
357 {
358 	// Filter all line breaks, note that inText is not terminated.
359 	if (inLength == 1) {
360 		if (*inText == '\n' || *inText == '\r')
361 			BTextView::InsertText(" ", 1, inOffset, inRuns);
362 		else
363 			BTextView::InsertText(inText, 1, inOffset, inRuns);
364 	} else {
365 		BString filteredText(inText, inLength);
366 		filteredText.ReplaceAll('\n', ' ');
367 		filteredText.ReplaceAll('\r', ' ');
368 		BTextView::InsertText(filteredText.String(), inLength, inOffset,
369 			inRuns);
370 	}
371 
372 	// Make the base URL part bold.
373 	BString text(Text(), TextLength());
374 	int32 baseUrlStart = text.FindFirst("://");
375 	if (baseUrlStart >= 0)
376 		baseUrlStart += 3;
377 	else
378 		baseUrlStart = 0;
379 	int32 baseUrlEnd = text.FindFirst("/", baseUrlStart);
380 	if (baseUrlEnd < 0)
381 		baseUrlEnd = TextLength();
382 
383 	BFont font;
384 	GetFont(&font);
385 	const rgb_color hostColor = ui_color(B_DOCUMENT_TEXT_COLOR);
386 	const rgb_color urlColor = tint_color(hostColor,
387 		(hostColor.Brightness() < 128 ? B_LIGHTEN_1_TINT : B_DARKEN_1_TINT));
388 	if (baseUrlStart > 0)
389 		SetFontAndColor(0, baseUrlStart, &font, B_FONT_ALL, &urlColor);
390 	if (baseUrlEnd > baseUrlStart) {
391 		font.SetFace(B_BOLD_FACE);
392 		SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL,
393 			&hostColor);
394 	}
395 	if (baseUrlEnd < TextLength()) {
396 		font.SetFace(B_REGULAR_FACE);
397 		SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL,
398 			&urlColor);
399 	}
400 
401 	fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
402 }
403 
404 
405 void
406 URLInputGroup::URLTextView::DeleteText(int32 fromOffset, int32 toOffset)
407 {
408 	BTextView::DeleteText(fromOffset, toOffset);
409 
410 	fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
411 }
412 
413 
414 void
415 URLInputGroup::URLTextView::_AlignTextRect()
416 {
417 	// Layout the text rect to be in the middle, normally this means there
418 	// is one pixel spacing on each side.
419 	BRect textRect(Bounds());
420 	textRect.left = 0.0;
421 	float vInset = max_c(1,
422 		floorf((textRect.Height() - LineHeight(0)) / 2.0 + 0.5));
423 	float hInset = kHorizontalTextRectInset;
424 
425 	if (be_control_look)
426 		hInset = be_control_look->DefaultLabelSpacing();
427 
428 	textRect.InsetBy(hInset, vInset);
429 	SetTextRect(textRect);
430 }
431 
432 
433 const uint32 kGoBitmapWidth = 14;
434 const uint32 kGoBitmapHeight = 14;
435 const color_space kGoBitmapFormat = B_RGBA32;
436 
437 const unsigned char kGoBitmapBits[] = {
438 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
439 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
440 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
441 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
442 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
443 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
444 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
445 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
446 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
447 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
448 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
449 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
450 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
451 	0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
452 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
453 	0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
454 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
455 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
456 	0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff,
457 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
458 	0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
459 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0,
460 	0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
461 	0x00, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x2f, 0x56, 0x50, 0x50, 0x50, 0xff, 0x4d, 0x4d, 0x4d, 0xed,
462 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
463 	0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
464 	0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
465 	0x50, 0x50, 0x50, 0xff, 0x37, 0x37, 0x37, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
466 	0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff,
467 	0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
468 	0x50, 0x50, 0x50, 0xff, 0x4b, 0x4b, 0x4b, 0xec, 0x37, 0x37, 0x37, 0x77, 0x00, 0x00, 0x00, 0x02,
469 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
470 	0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
471 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
472 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
473 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
474 	0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
475 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
476 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
477 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1,
478 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
479 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
480 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
481 	0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
482 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
483 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
484 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
485 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
486 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
487 };
488 
489 
490 const unsigned char kPlaceholderIcon[] = {
491 	0x6e, 0x63, 0x69, 0x66, 0x04, 0x04, 0x00, 0x66, 0x03, 0x00, 0x3f, 0x80,
492 	0x02, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x3d, 0xa6, 0x64, 0xc2, 0x19,
493 	0x98, 0x00, 0x00, 0x00, 0x4d, 0xce, 0x64, 0x49, 0xac, 0xcc, 0x00, 0xab,
494 	0xd5, 0xff, 0xff, 0x00, 0x6c, 0xd9, 0x02, 0x00, 0x06, 0x02, 0x00, 0x00,
495 	0x00, 0x3d, 0x26, 0x64, 0xc2, 0x19, 0x98, 0x00, 0x00, 0x00, 0x4d, 0xce,
496 	0x64, 0x49, 0xac, 0xcc, 0x00, 0x80, 0xff, 0x80, 0xff, 0x00, 0xb2, 0x00,
497 	0x04, 0x02, 0x04, 0x34, 0x22, 0xbd, 0x9b, 0x22, 0xb8, 0x53, 0x22, 0x28,
498 	0x2e, 0x28, 0xb5, 0xef, 0x28, 0xbb, 0x37, 0x34, 0x3a, 0xb8, 0x53, 0x3a,
499 	0xbd, 0x9b, 0x3a, 0x40, 0x2e, 0x40, 0xbb, 0x37, 0x40, 0xb5, 0xef, 0x02,
500 	0x08, 0xbe, 0xb6, 0xb4, 0xac, 0xc1, 0x46, 0xb4, 0xac, 0xbc, 0x25, 0xb4,
501 	0xac, 0xb8, 0x09, 0xb7, 0x35, 0xb9, 0xcf, 0xb5, 0xa0, 0xb8, 0x05, 0xbe,
502 	0xb6, 0x35, 0xc2, 0xe5, 0x35, 0xbe, 0xb6, 0x35, 0xc5, 0x68, 0xb8, 0x09,
503 	0xc6, 0x36, 0xb8, 0x09, 0xc6, 0x36, 0xb9, 0xcf, 0xc7, 0xca, 0xbe, 0xb6,
504 	0xc8, 0xc1, 0xbc, 0x25, 0xc8, 0xc1, 0xc1, 0xb3, 0xc8, 0xc1, 0xc6, 0x3c,
505 	0xc5, 0x5b, 0xc4, 0x65, 0xc7, 0x70, 0xc6, 0x3e, 0xbe, 0xb6, 0xc2, 0x0f,
506 	0xba, 0x87, 0xc2, 0x0f, 0xbe, 0xb6, 0xc2, 0x0f, 0xb8, 0x05, 0xc5, 0x64,
507 	0xb7, 0x37, 0xc5, 0x64, 0xb7, 0x37, 0xc3, 0x9e, 0xb5, 0xa2, 0x02, 0x04,
508 	0xb8, 0x09, 0xb7, 0x35, 0xb8, 0x05, 0xbe, 0xb6, 0xb5, 0xf8, 0xb9, 0x0c,
509 	0xb4, 0xac, 0xbe, 0xb6, 0xb4, 0xac, 0xbb, 0xba, 0xb4, 0xac, 0xc1, 0xb1,
510 	0xb8, 0x09, 0xc6, 0x36, 0xb5, 0xf8, 0xc4, 0x5e, 0xb9, 0xcf, 0xc7, 0xca,
511 	0x35, 0xc2, 0xe5, 0x35, 0xc5, 0x68, 0x35, 0xbe, 0xb6, 0x02, 0x04, 0x4d,
512 	0x51, 0xc4, 0xf2, 0xbf, 0x04, 0x53, 0x4e, 0xc8, 0xc1, 0xbe, 0x58, 0xc8,
513 	0xc1, 0xc1, 0x55, 0xc8, 0xc1, 0xbb, 0x5d, 0xc5, 0x64, 0xb6, 0xd9, 0xc7,
514 	0x75, 0xb8, 0xb0, 0xc3, 0x9e, 0xb5, 0x44, 0xc2, 0x0f, 0xba, 0x29, 0xc2,
515 	0x0f, 0xb7, 0xa6, 0xc2, 0x0f, 0xbe, 0x58, 0x04, 0x0a, 0x00, 0x01, 0x00,
516 	0x12, 0x42, 0x19, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x19,
517 	0x98, 0xc6, 0x19, 0x93, 0x44, 0x19, 0xa2, 0x01, 0x17, 0x84, 0x00, 0x04,
518 	0x0a, 0x01, 0x01, 0x00, 0x12, 0x42, 0x19, 0x98, 0x00, 0x00, 0x00, 0x00,
519 	0x00, 0x00, 0x42, 0x19, 0x98, 0xc7, 0x26, 0x5f, 0x28, 0x96, 0xf9, 0x01,
520 	0x17, 0x83, 0x00, 0x04, 0x0a, 0x02, 0x01, 0x01, 0x00, 0x0a, 0x03, 0x02,
521 	0x02, 0x03, 0x00
522 };
523 
524 // #pragma mark - PageIconView
525 
526 
527 class URLInputGroup::PageIconView : public BView {
528 public:
529 	PageIconView()
530 		:
531 		BView("page icon view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
532 		fIcon(NULL)
533 	{
534 		SetDrawingMode(B_OP_ALPHA);
535 		SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
536 		SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
537 	}
538 
539 	~PageIconView()
540 	{
541 		delete fIcon;
542 	}
543 
544 	virtual void Draw(BRect updateRect)
545 	{
546 		BRect bounds(Bounds());
547 		BRect iconBounds(0, 0, 15, 15);
548 		iconBounds.OffsetTo(
549 			floorf((bounds.left + bounds.right
550 				- (iconBounds.left + iconBounds.right)) / 2 + 0.5f),
551 			floorf((bounds.top + bounds.bottom
552 				- (iconBounds.top + iconBounds.bottom)) / 2 + 0.5f));
553 		DrawBitmap(fIcon, fIcon->Bounds(), iconBounds,
554 			B_FILTER_BITMAP_BILINEAR);
555 	}
556 
557 	virtual BSize MinSize()
558 	{
559 		return BSize(18, 18);
560 	}
561 
562 	virtual BSize MaxSize()
563 	{
564 		return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
565 	}
566 
567 	virtual BSize PreferredSize()
568 	{
569 		return MinSize();
570 	}
571 
572 	void SetIcon(const BBitmap* icon)
573 	{
574 		delete fIcon;
575 		if (icon)
576 			fIcon = new BBitmap(icon);
577 		else {
578 			fIcon = new BBitmap(BRect(0, 0, 15, 15), B_RGB32);
579 			BIconUtils::GetVectorIcon(kPlaceholderIcon,
580 				sizeof(kPlaceholderIcon), fIcon);
581 		}
582 		Invalidate();
583 	}
584 
585 private:
586 	BBitmap* fIcon;
587 };
588 
589 
590 // #pragma mark - URLInputGroup
591 
592 
593 URLInputGroup::URLInputGroup(BMessage* goMessage)
594 	:
595 	BGroupView(B_HORIZONTAL, 0.0),
596 	fWindowActive(false),
597 	fURLLocked(false)
598 {
599 	GroupLayout()->SetInsets(2, 2, 2, 2);
600 
601 	fIconView = new PageIconView();
602 	GroupLayout()->AddView(fIconView, 0.0f);
603 
604 	fTextView = new URLTextView(this);
605 	AddChild(fTextView);
606 
607 	AddChild(new BSeparatorView(B_VERTICAL, B_PLAIN_BORDER));
608 
609 // TODO: Fix in Haiku, no in-built support for archived BBitmaps from
610 // resources?
611 //	fGoButton = new BitmapButton("kActionGo", NULL);
612 	fGoButton = new BBitmapButton(kGoBitmapBits, kGoBitmapWidth,
613 		kGoBitmapHeight, kGoBitmapFormat, goMessage);
614 	GroupLayout()->AddView(fGoButton, 0.0f);
615 
616 	SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
617 	SetLowColor(ViewColor());
618 	SetViewColor(fTextView->ViewColor());
619 
620 	SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
621 		B_ALIGN_VERTICAL_CENTER));
622 
623 }
624 
625 
626 URLInputGroup::~URLInputGroup()
627 {
628 }
629 
630 
631 void
632 URLInputGroup::AttachedToWindow()
633 {
634 	BGroupView::AttachedToWindow();
635 	fWindowActive = Window()->IsActive();
636 }
637 
638 
639 void
640 URLInputGroup::WindowActivated(bool active)
641 {
642 	BGroupView::WindowActivated(active);
643 	if (fWindowActive != active) {
644 		fWindowActive = active;
645 		Invalidate();
646 	}
647 }
648 
649 
650 void
651 URLInputGroup::Draw(BRect updateRect)
652 {
653 	BRect bounds(Bounds());
654 	rgb_color base(LowColor());
655 	uint32 flags = 0;
656 	if (fWindowActive && fTextView->IsFocus())
657 		flags |= BControlLook::B_FOCUSED;
658 	be_control_look->DrawTextControlBorder(this, bounds, updateRect, base,
659 		flags);
660 }
661 
662 
663 void
664 URLInputGroup::MakeFocus(bool focus)
665 {
666 	// Forward this to the text view, we never accept focus ourselves.
667 	fTextView->MakeFocus(focus);
668 }
669 
670 
671 BTextView*
672 URLInputGroup::TextView() const
673 {
674 	return fTextView;
675 }
676 
677 
678 void
679 URLInputGroup::SetText(const char* text)
680 {
681 	// Ignore setting the text, if the input is locked.
682 	if (fURLLocked)
683 		return;
684 
685 	if (!text || !Text() || strcmp(Text(), text) != 0) {
686 		fTextView->SetUpdateAutoCompleterChoices(false);
687 		fTextView->SetText(text);
688 		fTextView->SetUpdateAutoCompleterChoices(true);
689 	}
690 }
691 
692 
693 const char*
694 URLInputGroup::Text() const
695 {
696 	return fTextView->Text();
697 }
698 
699 
700 BButton*
701 URLInputGroup::GoButton() const
702 {
703 	return fGoButton;
704 }
705 
706 
707 void
708 URLInputGroup::SetPageIcon(const BBitmap* icon)
709 {
710 	fIconView->SetIcon(icon);
711 }
712 
713 
714 bool
715 URLInputGroup::IsURLInputLocked() const
716 {
717 	return fURLLocked;
718 }
719 
720 
721 void
722 URLInputGroup::LockURLInput(bool lock)
723 {
724 	fURLLocked = lock;
725 }
726