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(®ion); 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