1/*
2 * Copyright 2006-2009, Stephan A��mus <superstippi@gmx.de>
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7#include "LaunchButton.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include <AppDefs.h>
14#include <AppFileInfo.h>
15#include <Application.h>
16#include <Bitmap.h>
17#include <Catalog.h>
18#include <File.h>
19#include <Node.h>
20#include <NodeInfo.h>
21#include <Region.h>
22#include <Roster.h>
23#include <Window.h>
24
25#include "PadView.h"
26#include "MainWindow.h"
27
28
29#undef B_TRANSLATION_CONTEXT
30#define B_TRANSLATION_CONTEXT "LaunchBox"
31
32
33static const float kDragStartDist = 10.0;
34static const float kDragBitmapAlphaScale = 0.6;
35static const char* kEmptyHelpString = B_TRANSLATE("You can drag an icon here.");
36
37
38bigtime_t LaunchButton::sClickSpeed = 0;
39bool LaunchButton::sIgnoreDoubleClick = true;
40
41
42LaunchButton::LaunchButton(const char* name, const char* label,
43		BMessage* message, BHandler* target)
44	:
45	BIconButton(name, label, message, target),
46	fRef(NULL),
47	fAppSig(NULL),
48	fDescription(""),
49	fAnticipatingDrop(false),
50	fLastClickTime(0),
51	fIconSize(DEFAULT_ICON_SIZE)
52{
53	if (sClickSpeed == 0 || get_click_speed(&sClickSpeed) != B_OK)
54		sClickSpeed = 500000;
55}
56
57
58LaunchButton::~LaunchButton()
59{
60	delete fRef;
61	free(fAppSig);
62}
63
64
65void
66LaunchButton::AttachedToWindow()
67{
68	BIconButton::AttachedToWindow();
69	_UpdateToolTip();
70}
71
72
73void
74LaunchButton::Draw(BRect updateRect)
75{
76	if (fAnticipatingDrop) {
77		rgb_color color = fRef ? ui_color(B_KEYBOARD_NAVIGATION_COLOR)
78			: (rgb_color){ 0, 130, 60, 255 };
79		SetHighColor(color);
80		// limit clipping region to exclude the blue rect we just drew
81		BRect r(Bounds());
82		StrokeRect(r);
83		r.InsetBy(1.0, 1.0);
84		BRegion region(r);
85		ConstrainClippingRegion(&region);
86	}
87	if (IsValid()) {
88		BIconButton::Draw(updateRect);
89	} else {
90		rgb_color background = LowColor();
91		rgb_color lightShadow = tint_color(background,
92			(B_NO_TINT + B_DARKEN_1_TINT) / 2.0);
93		rgb_color shadow = tint_color(background, B_DARKEN_1_TINT);
94		rgb_color light = tint_color(background, B_LIGHTEN_1_TINT);
95		BRect r(Bounds());
96		_DrawFrame(r, shadow, light, lightShadow, lightShadow);
97		r.InsetBy(2.0, 2.0);
98		SetHighColor(lightShadow);
99		FillRect(r);
100	}
101}
102
103
104void
105LaunchButton::MessageReceived(BMessage* message)
106{
107	switch (message->what) {
108		case B_SIMPLE_DATA:
109		case B_REFS_RECEIVED: {
110			entry_ref ref;
111			if (message->FindRef("refs", &ref) == B_OK) {
112				if (fRef) {
113					if (ref != *fRef) {
114						BEntry entry(fRef, true);
115						if (entry.IsDirectory()) {
116							message->PrintToStream();
117							// copy stuff into the directory
118						} else {
119							message->what = B_REFS_RECEIVED;
120							team_id team;
121							if (fAppSig)
122								team = be_roster->TeamFor(fAppSig);
123							else
124								team = be_roster->TeamFor(fRef);
125							if (team < 0) {
126								if (fAppSig)
127									be_roster->Launch(fAppSig, message, &team);
128								else
129									be_roster->Launch(fRef, message, &team);
130							} else {
131								app_info appInfo;
132								if (team >= 0
133									&& be_roster->GetRunningAppInfo(team,
134										&appInfo) == B_OK) {
135									BMessenger messenger(appInfo.signature,
136										team);
137									if (messenger.IsValid())
138										messenger.SendMessage(message);
139								}
140							}
141						}
142					}
143				} else {
144					SetTo(&ref);
145				}
146			}
147			break;
148		}
149		case B_PASTE:
150		case B_MODIFIERS_CHANGED:
151		default:
152			BIconButton::MessageReceived(message);
153			break;
154	}
155}
156
157
158void
159LaunchButton::MouseDown(BPoint where)
160{
161	bigtime_t now = system_time();
162	bool callInherited = true;
163	if (sIgnoreDoubleClick && now - fLastClickTime < sClickSpeed)
164		callInherited = false;
165	fLastClickTime = now;
166	if (BMessage* message = Window()->CurrentMessage()) {
167		uint32 buttons;
168		message->FindInt32("buttons", (int32*)&buttons);
169		if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && IsInside()) {
170			if (PadView* parent = dynamic_cast<PadView*>(Parent())) {
171				parent->DisplayMenu(ConvertToParent(where), this);
172				SetInside(false);
173				callInherited = false;
174			}
175		} else {
176			fDragStart = where;
177		}
178	}
179	if (callInherited)
180		BIconButton::MouseDown(where);
181}
182
183
184void
185LaunchButton::MouseUp(BPoint where)
186{
187	if (fAnticipatingDrop) {
188		fAnticipatingDrop = false;
189		Invalidate();
190	}
191	BIconButton::MouseUp(where);
192}
193
194
195void
196LaunchButton::MouseMoved(BPoint where, uint32 transit,
197	const BMessage* dragMessage)
198{
199	if ((dragMessage && (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW))
200		&& ((dragMessage->what == B_SIMPLE_DATA
201			|| dragMessage->what == B_REFS_RECEIVED) || fRef)) {
202		if (!fAnticipatingDrop) {
203			fAnticipatingDrop = true;
204			Invalidate();
205		}
206	}
207	if (!dragMessage || (transit == B_EXITED_VIEW || transit == B_OUTSIDE_VIEW)) {
208		if (fAnticipatingDrop) {
209			fAnticipatingDrop = false;
210			Invalidate();
211		}
212	}
213	// see if we should create a drag message
214	if (IsTracking() && fRef != NULL) {
215		BPoint diff = where - fDragStart;
216		float dist = sqrtf(diff.x * diff.x + diff.y * diff.y);
217		if (dist >= kDragStartDist) {
218			// stop tracking
219			SetTracking(false);
220			SetPressed(false);
221			SetInside(false);
222
223			// create drag bitmap and message
224			if (BBitmap* bitmap = Bitmap()) {
225				if (bitmap->ColorSpace() == B_RGB32) {
226					// make semitransparent
227					uint8* bits = (uint8*)bitmap->Bits();
228					uint32 width = bitmap->Bounds().IntegerWidth() + 1;
229					uint32 height = bitmap->Bounds().IntegerHeight() + 1;
230					uint32 bpr = bitmap->BytesPerRow();
231					for (uint32 y = 0; y < height; y++) {
232						uint8* bitsHandle = bits;
233						for (uint32 x = 0; x < width; x++) {
234							bitsHandle[3] = uint8(bitsHandle[3]
235								* kDragBitmapAlphaScale);
236							bitsHandle += 4;
237						}
238						bits += bpr;
239					}
240				}
241				BMessage message(B_SIMPLE_DATA);
242				message.AddPointer("button", this);
243				message.AddRef("refs", fRef);
244				// DragMessage takes ownership of the bitmap.
245				DragMessage(&message, bitmap, B_OP_ALPHA, fDragStart);
246			}
247		}
248	}
249	BIconButton::MouseMoved(where, transit, dragMessage);
250}
251
252
253BSize
254LaunchButton::MinSize()
255{
256	return PreferredSize();
257}
258
259
260BSize
261LaunchButton::PreferredSize()
262{
263	float minWidth = fIconSize;
264	float minHeight = fIconSize;
265
266	float hPadding = max_c(6.0, ceilf(minHeight / 3.0));
267	float vPadding = max_c(6.0, ceilf(minWidth / 3.0));
268
269	if (Label() != NULL && Label()[0] != '\0') {
270		font_height fh;
271		GetFontHeight(&fh);
272		minHeight += ceilf(fh.ascent + fh.descent) + vPadding;
273		minWidth += StringWidth(Label()) + vPadding;
274	}
275
276	return BSize(minWidth + hPadding, minHeight + vPadding);
277}
278
279
280BSize
281LaunchButton::MaxSize()
282{
283	return PreferredSize();
284}
285
286
287// #pragma mark -
288
289
290void
291LaunchButton::SetTo(const entry_ref* ref)
292{
293	free(fAppSig);
294	fAppSig = NULL;
295
296	delete fRef;
297	if (ref) {
298		fRef = new entry_ref(*ref);
299		// follow links
300		BEntry entry(fRef, true);
301		entry.GetRef(fRef);
302
303		_UpdateIcon(fRef);
304		// see if this is an application
305		BFile file(ref, B_READ_ONLY);
306		BAppFileInfo info;
307		if (info.SetTo(&file) == B_OK) {
308			char mimeSig[B_MIME_TYPE_LENGTH];
309			if (info.GetSignature(mimeSig) == B_OK) {
310				SetTo(mimeSig, false);
311			} else {
312				fprintf(stderr, "no MIME signature for '%s'\n", fRef->name);
313			}
314		} else {
315			fprintf(stderr, "no BAppFileInfo for '%s'\n", fRef->name);
316		}
317	} else {
318		fRef = NULL;
319		ClearIcon();
320	}
321	_UpdateToolTip();
322	_NotifySettingsChanged();
323}
324
325
326entry_ref*
327LaunchButton::Ref() const
328{
329	return fRef;
330}
331
332
333void
334LaunchButton::SetTo(const char* appSig, bool updateIcon)
335{
336	if (appSig) {
337		free(fAppSig);
338		fAppSig = strdup(appSig);
339		if (updateIcon) {
340			entry_ref ref;
341			if (be_roster->FindApp(fAppSig, &ref) == B_OK)
342				SetTo(&ref);
343		}
344	}
345	_UpdateToolTip();
346	_NotifySettingsChanged();
347}
348
349
350void
351LaunchButton::SetDescription(const char* text)
352{
353	fDescription.SetTo(text);
354	_UpdateToolTip();
355	_NotifySettingsChanged();
356}
357
358
359void
360LaunchButton::SetIconSize(uint32 size)
361{
362	if (fIconSize == size)
363		return;
364
365	fIconSize = size;
366	_UpdateIcon(fRef);
367
368	InvalidateLayout();
369	Invalidate();
370}
371
372
373void
374LaunchButton::SetIgnoreDoubleClick(bool refuse)
375{
376	sIgnoreDoubleClick = refuse;
377}
378
379
380// #pragma mark -
381
382
383void
384LaunchButton::_UpdateToolTip()
385{
386	// TODO: This works around a bug in the tool tip management.
387	// Remove when fixed (although no harm done...)
388	HideToolTip();
389	SetToolTip(static_cast<BToolTip*>(NULL));
390
391	if (fRef) {
392		BString helper(fRef->name);
393		if (fDescription.CountChars() > 0) {
394			if (fDescription != helper)
395				helper << "\n\n" << fDescription.String();
396		} else {
397			BFile file(fRef, B_READ_ONLY);
398			BAppFileInfo appFileInfo;
399			version_info info;
400			if (appFileInfo.SetTo(&file) == B_OK
401				&& appFileInfo.GetVersionInfo(&info,
402					B_APP_VERSION_KIND) == B_OK
403				&& strlen(info.short_info) > 0
404				&& helper.Compare(info.short_info) != 0) {
405				helper << "\n\n" << info.short_info;
406			}
407		}
408		SetToolTip(helper.String());
409	} else {
410		SetToolTip(kEmptyHelpString);
411	}
412}
413
414
415void
416LaunchButton::_UpdateIcon(const entry_ref* ref)
417{
418	BBitmap* icon = new BBitmap(BRect(0.0, 0.0, fIconSize - 1,
419		fIconSize - 1), B_RGBA32);
420	// NOTE: passing an invalid/unknown icon_size argument will cause
421	// the BNodeInfo to ignore it and just use the bitmap bounds.
422	if (BNodeInfo::GetTrackerIcon(ref, icon, (icon_size)fIconSize) == B_OK)
423		SetIcon(icon);
424
425	delete icon;
426}
427
428
429void
430LaunchButton::_NotifySettingsChanged()
431{
432	be_app->PostMessage(MSG_SETTINGS_CHANGED);
433}
434
435
436void
437LaunchButton::_DrawFrame(BRect r, rgb_color col1, rgb_color col2,
438	rgb_color col3, rgb_color col4)
439{
440	BeginLineArray(8);
441		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col1);
442		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col1);
443		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col2);
444		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col2);
445		r.InsetBy(1.0, 1.0);
446		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col3);
447		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col3);
448		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col4);
449		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col4);
450	EndLineArray();
451}