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