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