xref: /haiku/src/apps/launchbox/LaunchButton.cpp (revision cbed190f71b8aff814bf95539c39a1bcfb953ed8)
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 #include "LaunchButton.h"
7 
8 #include <malloc.h> // string.h is not enough on Haiku?!?
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Application.h>
13 #include <AppDefs.h>
14 #include <AppFileInfo.h>
15 #include <Bitmap.h>
16 #include <Catalog.h>
17 #include <File.h>
18 #include <Node.h>
19 #include <NodeInfo.h>
20 #include <Region.h>
21 #include <Roster.h>
22 #include <Window.h>
23 
24 #include "PadView.h"
25 #include "MainWindow.h"
26 
27 #undef B_TRANSLATE_CONTEXT
28 #define B_TRANSLATE_CONTEXT "LaunchBox"
29 
30 static const float kDragStartDist = 10.0;
31 static const float kDragBitmapAlphaScale = 0.6;
32 static const char* kEmptyHelpString = B_TRANSLATE("You can drag an icon here.");
33 
34 
35 bigtime_t
36 LaunchButton::sClickSpeed = 0;
37 
38 bool
39 LaunchButton::sIgnoreDoubleClick = true;
40 
41 
42 LaunchButton::LaunchButton(const char* name, uint32 id, const char* label,
43 		BMessage* message, BHandler* target)
44 	:
45 	BIconButton(name, id, 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 
58 LaunchButton::~LaunchButton()
59 {
60 	delete fRef;
61 	free(fAppSig);
62 }
63 
64 
65 void
66 LaunchButton::AttachedToWindow()
67 {
68 	BIconButton::AttachedToWindow();
69 	_UpdateToolTip();
70 }
71 
72 
73 void
74 LaunchButton::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 
104 void
105 LaunchButton::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 
158 void
159 LaunchButton::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) {
170 			if (PadView* parent = dynamic_cast<PadView*>(Parent())) {
171 				parent->DisplayMenu(ConvertToParent(where), this);
172 				_ClearFlags(STATE_INSIDE);
173 				callInherited = false;
174 			}
175 		} else {
176 			fDragStart = where;
177 		}
178 	}
179 	if (callInherited)
180 		BIconButton::MouseDown(where);
181 }
182 
183 
184 void
185 LaunchButton::MouseUp(BPoint where)
186 {
187 	if (fAnticipatingDrop) {
188 		fAnticipatingDrop = false;
189 		Invalidate();
190 	}
191 	BIconButton::MouseUp(where);
192 }
193 
194 
195 void
196 LaunchButton::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 (_HasFlags(STATE_TRACKING) && fRef) {
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 			_ClearFlags(STATE_PRESSED | STATE_TRACKING | STATE_INSIDE);
220 			// create drag bitmap and message
221 			if (BBitmap* bitmap = Bitmap()) {
222 				if (bitmap->ColorSpace() == B_RGB32) {
223 					// make semitransparent
224 					uint8* bits = (uint8*)bitmap->Bits();
225 					uint32 width = bitmap->Bounds().IntegerWidth() + 1;
226 					uint32 height = bitmap->Bounds().IntegerHeight() + 1;
227 					uint32 bpr = bitmap->BytesPerRow();
228 					for (uint32 y = 0; y < height; y++) {
229 						uint8* bitsHandle = bits;
230 						for (uint32 x = 0; x < width; x++) {
231 							bitsHandle[3] = uint8(bitsHandle[3]
232 								* kDragBitmapAlphaScale);
233 							bitsHandle += 4;
234 						}
235 						bits += bpr;
236 					}
237 				}
238 				BMessage message(B_SIMPLE_DATA);
239 				message.AddPointer("button", this);
240 				message.AddRef("refs", fRef);
241 				// DragMessage takes ownership of the bitmap.
242 				DragMessage(&message, bitmap, B_OP_ALPHA, fDragStart);
243 			}
244 		}
245 	}
246 	BIconButton::MouseMoved(where, transit, dragMessage);
247 }
248 
249 
250 BSize
251 LaunchButton::MinSize()
252 {
253 	return PreferredSize();
254 }
255 
256 
257 BSize
258 LaunchButton::PreferredSize()
259 {
260 	float minWidth = fIconSize;
261 	float minHeight = fIconSize;
262 
263 	float hPadding = max_c(6.0, ceilf(minHeight / 3.0));
264 	float vPadding = max_c(6.0, ceilf(minWidth / 3.0));
265 
266 	if (Label().CountChars() > 0) {
267 		font_height fh;
268 		GetFontHeight(&fh);
269 		minHeight += ceilf(fh.ascent + fh.descent) + vPadding;
270 		minWidth += StringWidth(Label().String()) + vPadding;
271 	}
272 
273 	return BSize(minWidth + hPadding, minHeight + vPadding);
274 }
275 
276 
277 BSize
278 LaunchButton::MaxSize()
279 {
280 	return PreferredSize();
281 }
282 
283 
284 // #pragma mark -
285 
286 
287 void
288 LaunchButton::SetTo(const entry_ref* ref)
289 {
290 	free(fAppSig);
291 	fAppSig = NULL;
292 
293 	delete fRef;
294 	if (ref) {
295 		fRef = new entry_ref(*ref);
296 		// follow links
297 		BEntry entry(fRef, true);
298 		entry.GetRef(fRef);
299 
300 		_UpdateIcon(fRef);
301 		// see if this is an application
302 		BFile file(ref, B_READ_ONLY);
303 		BAppFileInfo info;
304 		if (info.SetTo(&file) == B_OK) {
305 			char mimeSig[B_MIME_TYPE_LENGTH];
306 			if (info.GetSignature(mimeSig) == B_OK) {
307 				SetTo(mimeSig, false);
308 			} else {
309 				fprintf(stderr, "no MIME signature for '%s'\n", fRef->name);
310 			}
311 		} else {
312 			fprintf(stderr, "no BAppFileInfo for '%s'\n", fRef->name);
313 		}
314 	} else {
315 		fRef = NULL;
316 		ClearIcon();
317 	}
318 	_UpdateToolTip();
319 	_NotifySettingsChanged();
320 }
321 
322 
323 entry_ref*
324 LaunchButton::Ref() const
325 {
326 	return fRef;
327 }
328 
329 
330 void
331 LaunchButton::SetTo(const char* appSig, bool updateIcon)
332 {
333 	if (appSig) {
334 		free(fAppSig);
335 		fAppSig = strdup(appSig);
336 		if (updateIcon) {
337 			entry_ref ref;
338 			if (be_roster->FindApp(fAppSig, &ref) == B_OK)
339 				SetTo(&ref);
340 		}
341 	}
342 	_UpdateToolTip();
343 	_NotifySettingsChanged();
344 }
345 
346 
347 void
348 LaunchButton::SetDescription(const char* text)
349 {
350 	fDescription.SetTo(text);
351 	_UpdateToolTip();
352 	_NotifySettingsChanged();
353 }
354 
355 
356 void
357 LaunchButton::SetIconSize(uint32 size)
358 {
359 	if (fIconSize == size)
360 		return;
361 
362 	fIconSize = size;
363 	_UpdateIcon(fRef);
364 
365 	InvalidateLayout();
366 	Invalidate();
367 }
368 
369 
370 void
371 LaunchButton::SetIgnoreDoubleClick(bool refuse)
372 {
373 	sIgnoreDoubleClick = refuse;
374 }
375 
376 
377 // #pragma mark -
378 
379 
380 void
381 LaunchButton::_UpdateToolTip()
382 {
383 	// TODO: This works around a bug in the tool tip management.
384 	// Remove when fixed (although no harm done...)
385 	HideToolTip();
386 	SetToolTip(static_cast<BToolTip*>(NULL));
387 
388 	if (fRef) {
389 		BString helper(fRef->name);
390 		if (fDescription.CountChars() > 0) {
391 			if (fDescription != helper)
392 				helper << "\n\n" << fDescription.String();
393 		} else {
394 			BFile file(fRef, B_READ_ONLY);
395 			BAppFileInfo appFileInfo;
396 			version_info info;
397 			if (appFileInfo.SetTo(&file) == B_OK
398 				&& appFileInfo.GetVersionInfo(&info,
399 					B_APP_VERSION_KIND) == B_OK
400 				&& strlen(info.short_info) > 0
401 				&& helper.Compare(info.short_info) != 0) {
402 				helper << "\n\n" << info.short_info;
403 			}
404 		}
405 		SetToolTip(helper.String());
406 	} else {
407 		SetToolTip(kEmptyHelpString);
408 	}
409 }
410 
411 
412 void
413 LaunchButton::_UpdateIcon(const entry_ref* ref)
414 {
415 	BBitmap* icon = new BBitmap(BRect(0.0, 0.0, fIconSize - 1,
416 		fIconSize - 1), B_RGBA32);
417 	// NOTE: passing an invalid/unknown icon_size argument will cause
418 	// the BNodeInfo to ignore it and just use the bitmap bounds.
419 	if (BNodeInfo::GetTrackerIcon(ref, icon, (icon_size)fIconSize) == B_OK)
420 		SetIcon(icon);
421 
422 	delete icon;
423 }
424 
425 
426 void
427 LaunchButton::_NotifySettingsChanged()
428 {
429 	be_app->PostMessage(MSG_SETTINGS_CHANGED);
430 }
431 
432 
433 void
434 LaunchButton::_DrawFrame(BRect r, rgb_color col1, rgb_color col2,
435 	rgb_color col3, rgb_color col4)
436 {
437 	BeginLineArray(8);
438 		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col1);
439 		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col1);
440 		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col2);
441 		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col2);
442 		r.InsetBy(1.0, 1.0);
443 		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col3);
444 		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col3);
445 		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col4);
446 		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col4);
447 	EndLineArray();
448 }
449