1 #include "BitmapView.h" 2 #include <Alert.h> 3 #include <BitmapStream.h> 4 #include <Clipboard.h> 5 #include <Font.h> 6 #include <MenuItem.h> 7 #include <Entry.h> 8 #include <TranslationUtils.h> 9 #include <TranslatorRoster.h> 10 #include <TranslatorFormats.h> 11 12 // TODO: Add support for labels 13 14 #define M_REMOVE_IMAGE 'mrmi' 15 #define M_PASTE_IMAGE 'mpsi' 16 17 enum 18 { 19 CLIP_NONE = 0, 20 CLIP_BEOS = 1, 21 CLIP_SHOWIMAGE = 2, 22 CLIP_PRODUCTIVE = 3 23 }; 24 25 26 inline void SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a = 255); 27 28 29 void 30 SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a) 31 { 32 if (col) { 33 col->red = r; 34 col->green = g; 35 col->blue = b; 36 col->alpha = a; 37 } 38 } 39 40 41 BitmapView::BitmapView(BRect frame, const char *name, BMessage *mod, BBitmap *bitmap, 42 const char *label, border_style borderstyle, int32 resize, int32 flags) 43 : BView(frame, name, resize, flags) 44 { 45 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 46 47 if (bitmap && bitmap->IsValid()) 48 fBitmap = bitmap; 49 else 50 fBitmap = NULL; 51 52 if (mod) 53 SetMessage(mod); 54 55 fLabel = label; 56 fBorderStyle = borderstyle; 57 fFixedSize = false; 58 fEnabled = true; 59 fRemovableBitmap = false; 60 fAcceptDrops = true; 61 fAcceptPaste = true; 62 fConstrainDrops = true; 63 fMaxWidth = 100; 64 fMaxHeight = 100; 65 66 fPopUpMenu = new BPopUpMenu("deletepopup", false, false); 67 fPopUpMenu->AddItem(new BMenuItem("Close This Menu", new BMessage(B_CANCEL))); 68 fPopUpMenu->AddSeparatorItem(); 69 70 fPasteItem = new BMenuItem("Paste Photo from Clipboard", new BMessage(M_PASTE_IMAGE)); 71 fPopUpMenu->AddItem(fPasteItem); 72 73 fPopUpMenu->AddSeparatorItem(); 74 75 fRemoveItem = new BMenuItem("Remove Photo", new BMessage(M_REMOVE_IMAGE)); 76 fPopUpMenu->AddItem(fRemoveItem); 77 78 CalculateBitmapRect(); 79 80 // Calculate the offsets for each of the words -- the phrase will be center justified 81 fNoPhotoWidths[0] = StringWidth("Drop"); 82 fNoPhotoWidths[1] = StringWidth("a"); 83 fNoPhotoWidths[2] = StringWidth("Photo"); 84 fNoPhotoWidths[3] = StringWidth("Here"); 85 86 font_height fh; 87 GetFontHeight(&fh); 88 float totalheight = fh.ascent + fh.descent + fh.leading; 89 float yoffset = (Bounds().Height() - 10 - (totalheight * 4)) / 2; 90 fNoPhotoOffsets[0].Set((Bounds().Width() - fNoPhotoWidths[0]) / 2, totalheight + yoffset); 91 fNoPhotoOffsets[1].Set((Bounds().Width() - fNoPhotoWidths[1]) / 2, 92 fNoPhotoOffsets[0].y + totalheight); 93 fNoPhotoOffsets[2].Set((Bounds().Width() - fNoPhotoWidths[2]) / 2, 94 fNoPhotoOffsets[1].y + totalheight); 95 fNoPhotoOffsets[3].Set((Bounds().Width() - fNoPhotoWidths[3]) / 2, 96 fNoPhotoOffsets[2].y + totalheight); 97 } 98 99 100 BitmapView::~BitmapView(void) 101 { 102 delete fPopUpMenu; 103 } 104 105 106 void 107 BitmapView::AttachedToWindow(void) 108 { 109 SetTarget((BHandler*)Window()); 110 fPopUpMenu->SetTargetForItems(this); 111 } 112 113 114 void 115 BitmapView::SetBitmap(BBitmap *bitmap) 116 { 117 if (bitmap && bitmap->IsValid()) { 118 if (fBitmap == bitmap) 119 return; 120 fBitmap = bitmap; 121 } else { 122 if (!fBitmap) 123 return; 124 fBitmap = NULL; 125 } 126 127 CalculateBitmapRect(); 128 if (!IsHidden()) 129 Invalidate(); 130 } 131 132 133 void 134 BitmapView::SetEnabled(bool value) 135 { 136 if (fEnabled != value) { 137 fEnabled = value; 138 Invalidate(); 139 } 140 } 141 142 143 /* 144 void 145 BitmapView::SetLabel(const char *label) 146 { 147 if (fLabel.Compare(label) != 0) { 148 fLabel = label; 149 150 CalculateBitmapRect(); 151 if (!IsHidden()) 152 Invalidate(); 153 } 154 } 155 */ 156 157 158 void 159 BitmapView::SetStyle(border_style style) 160 { 161 if (fBorderStyle != style) { 162 fBorderStyle = style; 163 164 CalculateBitmapRect(); 165 if (!IsHidden()) 166 Invalidate(); 167 } 168 } 169 170 171 void 172 BitmapView::SetFixedSize(bool isfixed) 173 { 174 if (fFixedSize != isfixed) { 175 fFixedSize = isfixed; 176 177 CalculateBitmapRect(); 178 if (!IsHidden()) 179 Invalidate(); 180 } 181 } 182 183 184 void 185 BitmapView::MessageReceived(BMessage *msg) 186 { 187 if (msg->WasDropped() && AcceptsDrops()) { 188 // We'll handle two types of drops: those from Tracker and those from ShowImage 189 if (msg->what == B_SIMPLE_DATA) { 190 int32 actions; 191 if (msg->FindInt32("be:actions", &actions) == B_OK) { 192 // ShowImage drop. This is a negotiated drag&drop, so send a reply 193 BMessage reply(B_COPY_TARGET), response; 194 reply.AddString("be:types", "image/jpeg"); 195 reply.AddString("be:types", "image/png"); 196 197 msg->SendReply(&reply, &response); 198 199 // now, we've gotten the response 200 if (response.what == B_MIME_DATA) { 201 // Obtain and translate the received data 202 uint8 *imagedata; 203 ssize_t datasize; 204 205 // Try JPEG first 206 if (response.FindData("image/jpeg", B_MIME_DATA, 207 (const void **)&imagedata, &datasize) != B_OK) { 208 // Try PNG next and piddle out if unsuccessful 209 if (response.FindData("image/png", B_PNG_FORMAT, 210 (const void **)&imagedata, &datasize) != B_OK) 211 return; 212 } 213 214 // Set up to decode into memory 215 BMemoryIO memio(imagedata, datasize); 216 BTranslatorRoster *roster = BTranslatorRoster::Default(); 217 BBitmapStream bstream; 218 219 if (roster->Translate(&memio, NULL, NULL, &bstream, B_TRANSLATOR_BITMAP) == B_OK) 220 { 221 BBitmap *bmp; 222 if (bstream.DetachBitmap(&bmp) != B_OK) 223 return; 224 SetBitmap(bmp); 225 226 if (fConstrainDrops) 227 ConstrainBitmap(); 228 Invoke(); 229 } 230 } 231 return; 232 } 233 234 entry_ref ref; 235 if (msg->FindRef("refs", &ref) == B_OK) { 236 // Tracker drop 237 BBitmap *bmp = BTranslationUtils::GetBitmap(&ref); 238 if (bmp) { 239 SetBitmap(bmp); 240 241 if (fConstrainDrops) 242 ConstrainBitmap(); 243 Invoke(); 244 } 245 } 246 } 247 return; 248 } 249 250 switch (msg->what) 251 { 252 case M_REMOVE_IMAGE: { 253 BAlert *alert = new BAlert("Mr. Peeps!", "This cannot be undone. Remove the image?", 254 "Remove", "Cancel"); 255 int32 value = alert->Go(); 256 if (value == 0) { 257 SetBitmap(NULL); 258 259 if (Target()) { 260 BMessenger msgr(Target()); 261 262 msgr.SendMessage(new BMessage(M_BITMAP_REMOVED)); 263 return; 264 } 265 } 266 } 267 case M_PASTE_IMAGE: 268 { 269 PasteBitmap(); 270 Invoke(); 271 } 272 } 273 BView::MessageReceived(msg); 274 } 275 276 277 void 278 BitmapView::Draw(BRect rect) 279 { 280 if (fBitmap) 281 DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect); 282 else { 283 SetHighColor(0, 0, 0, 80); 284 SetDrawingMode(B_OP_ALPHA); 285 DrawString("Drop", fNoPhotoOffsets[0]); 286 DrawString("a", fNoPhotoOffsets[1]); 287 DrawString("Photo", fNoPhotoOffsets[2]); 288 DrawString("Here", fNoPhotoOffsets[3]); 289 SetDrawingMode(B_OP_COPY); 290 } 291 292 if (fBorderStyle == B_FANCY_BORDER) { 293 rgb_color base= { 216, 216, 216, 255 }; 294 rgb_color work; 295 296 SetHighColor(base); 297 StrokeRect(Bounds().InsetByCopy(2, 2)); 298 299 BeginLineArray(12); 300 301 BRect r(Bounds()); 302 303 work = tint_color(base, B_DARKEN_2_TINT); 304 AddLine(r.LeftTop(), r.RightTop(), work); 305 AddLine(r.LeftTop(), r.LeftBottom(), work); 306 r.left++; 307 308 work = tint_color(base, B_DARKEN_4_TINT); 309 AddLine(r.RightTop(), r.RightBottom(), work); 310 AddLine(r.LeftBottom(), r.RightBottom(), work); 311 312 r.right--; 313 r.top++; 314 r.bottom--; 315 316 317 work = tint_color(base, B_LIGHTEN_MAX_TINT); 318 AddLine(r.LeftTop(), r.RightTop(), work); 319 AddLine(r.LeftTop(), r.LeftBottom(), work); 320 r.left++; 321 322 work = tint_color(base, B_DARKEN_3_TINT); 323 AddLine(r.RightTop(), r.RightBottom(), work); 324 AddLine(r.LeftBottom(), r.RightBottom(), work); 325 326 // this rect handled by the above StrokeRect, so inset a total of 2 pixels 327 r.left++; 328 r.right -= 2; 329 r.top += 2; 330 r.bottom -= 2; 331 332 333 work = tint_color(base, B_DARKEN_3_TINT); 334 AddLine(r.LeftTop(), r.RightTop(), work); 335 AddLine(r.LeftTop(), r.LeftBottom(), work); 336 r.left++; 337 338 work = tint_color(base, B_LIGHTEN_MAX_TINT); 339 AddLine(r.RightTop(), r.RightBottom(), work); 340 AddLine(r.LeftBottom(), r.RightBottom(), work); 341 342 r.right--; 343 r.top++; 344 r.bottom--; 345 EndLineArray(); 346 347 SetHighColor(tint_color(base, B_DARKEN_2_TINT)); 348 StrokeRect(r); 349 } else { 350 // Plain border 351 SetHighColor(0, 0, 0); 352 StrokeRect(fBitmapRect); 353 } 354 } 355 356 357 void 358 BitmapView::MouseDown(BPoint pt) 359 { 360 BPoint mousept; 361 uint32 buttons; 362 363 GetMouse(&mousept, &buttons); 364 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 365 ConvertToScreen(&mousept); 366 367 mousept.x= (mousept.x>5) ? mousept.x-5 : 0; 368 mousept.y= (mousept.y>5) ? mousept.y-5 : 0; 369 370 if (AcceptsPaste() && ClipboardHasBitmap()) 371 fPasteItem->SetEnabled(true); 372 else 373 fPasteItem->SetEnabled(false); 374 375 if (fRemovableBitmap && fBitmap) 376 fRemoveItem->SetEnabled(true); 377 else 378 fRemoveItem->SetEnabled(false); 379 380 fPopUpMenu->Go(mousept, true, true, true); 381 } 382 } 383 384 385 void 386 BitmapView::FrameResized(float w, float h) 387 { 388 CalculateBitmapRect(); 389 } 390 391 392 void 393 BitmapView::CalculateBitmapRect(void) 394 { 395 if (!fBitmap || fFixedSize) { 396 fBitmapRect = Bounds().InsetByCopy(1, 1); 397 return; 398 } 399 400 uint8 borderwidth = (fBorderStyle == B_FANCY_BORDER) ? 5 : 1; 401 402 BRect r(Bounds()); 403 fBitmapRect= ScaleRectToFit(fBitmap->Bounds(), r.InsetByCopy(borderwidth, borderwidth)); 404 } 405 406 407 void 408 BitmapView::SetAcceptDrops(bool accept) 409 { 410 fAcceptDrops = accept; 411 } 412 413 414 void 415 BitmapView::SetAcceptPaste(bool accept) 416 { 417 fAcceptPaste = accept; 418 } 419 420 421 void 422 BitmapView::SetConstrainDrops(bool value) 423 { 424 fConstrainDrops = value; 425 } 426 427 428 void 429 BitmapView::MaxBitmapSize(float *width, float *height) const 430 { 431 *width = fMaxWidth; 432 *height = fMaxHeight; 433 } 434 435 436 void 437 BitmapView::SetMaxBitmapSize(const float &width, const float &height) 438 { 439 fMaxWidth = width; 440 fMaxHeight = height; 441 442 ConstrainBitmap(); 443 } 444 445 446 void 447 BitmapView::SetBitmapRemovable(bool isremovable) 448 { 449 fRemovableBitmap = isremovable; 450 } 451 452 453 void 454 BitmapView::ConstrainBitmap(void) 455 { 456 if (!fBitmap || fMaxWidth < 1 || fMaxHeight < 1) 457 return; 458 459 BRect r = ScaleRectToFit(fBitmap->Bounds(), BRect(0, 0, fMaxWidth - 1, fMaxHeight - 1)); 460 r.OffsetTo(0, 0); 461 462 BBitmap *scaled = new BBitmap(r, fBitmap->ColorSpace(), true); 463 BView *view = new BView(r, "drawview", 0, 0); 464 465 scaled->Lock(); 466 scaled->AddChild(view); 467 view->DrawBitmap(fBitmap, fBitmap->Bounds(), scaled->Bounds()); 468 scaled->Unlock(); 469 470 delete fBitmap; 471 fBitmap = new BBitmap(scaled, false); 472 } 473 474 475 bool 476 BitmapView::ClipboardHasBitmap(void) 477 { 478 BMessage *clip = NULL, flattened; 479 uint8 clipval = CLIP_NONE; 480 bool returnval; 481 482 if (be_clipboard->Lock()) { 483 clip = be_clipboard->Data(); 484 if (!clip->IsEmpty()) { 485 returnval = (clip->FindMessage("image/bitmap", &flattened) == B_OK); 486 if (returnval) 487 clipval = CLIP_BEOS; 488 else { 489 BString string; 490 returnval = (clip->FindString("class", &string) == B_OK && string == "BBitmap"); 491 492 // Try method Gobe Productive uses if that, too, didn't work 493 if (returnval) 494 clipval = CLIP_SHOWIMAGE; 495 else { 496 returnval = (clip->FindMessage("image/x-vnd.Be-bitmap", &flattened) == B_OK); 497 if (returnval) 498 clipval = CLIP_SHOWIMAGE; 499 else 500 clipval = CLIP_NONE; 501 } 502 } 503 } 504 be_clipboard->Unlock(); 505 } 506 return (clipval != CLIP_NONE)?true:false; 507 } 508 509 510 BBitmap * 511 BitmapView::BitmapFromClipboard(void) 512 { 513 BMessage *clip = NULL, flattened; 514 BBitmap *bitmap; 515 516 if (!be_clipboard->Lock()) 517 return NULL; 518 519 clip = be_clipboard->Data(); 520 if (!clip) 521 return NULL; 522 523 uint8 clipval = CLIP_NONE; 524 525 // Try ArtPaint-style storage 526 status_t status = clip->FindMessage("image/bitmap", &flattened); 527 528 // If that didn't work, try ShowImage-style 529 if (status != B_OK) { 530 BString string; 531 status = clip->FindString("class", &string); 532 533 // Try method Gobe Productive uses if that, too, didn't work 534 if (status == B_OK && string == "BBitmap") 535 clipval = CLIP_SHOWIMAGE; 536 else { 537 status = clip->FindMessage("image/x-vnd.Be-bitmap", &flattened); 538 if (status == B_OK) 539 clipval = CLIP_PRODUCTIVE; 540 else 541 clipval = CLIP_NONE; 542 } 543 } 544 else 545 clipval = CLIP_BEOS; 546 547 be_clipboard->Unlock(); 548 549 switch (clipval) { 550 case CLIP_SHOWIMAGE: { 551 // Showimage does it a slightly different way -- it dumps the BBitmap 552 // data directly to the clipboard message instead of packaging it in 553 // a bitmap like everyone else. 554 555 if (!be_clipboard->Lock()) 556 return NULL; 557 558 BMessage datamsg(*be_clipboard->Data()); 559 560 be_clipboard->Unlock(); 561 562 const void *buffer; 563 int32 bufferLength; 564 565 BRect frame; 566 color_space cspace = B_NO_COLOR_SPACE; 567 568 status = datamsg.FindRect("_frame", &frame); 569 if (status != B_OK) 570 return NULL; 571 572 status = datamsg.FindInt32("_cspace", (int32)cspace); 573 if (status != B_OK) 574 return NULL; 575 cspace = B_RGBA32; 576 bitmap = new BBitmap(frame, cspace, true); 577 578 status = datamsg.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); 579 if (status != B_OK) { 580 delete bitmap; 581 return NULL; 582 } 583 584 memcpy(bitmap->Bits(), buffer, bufferLength); 585 return bitmap; 586 } 587 case CLIP_PRODUCTIVE: 588 // Productive doesn't name the packaged BBitmap data message the same, but 589 // uses exactly the same data format. 590 591 case CLIP_BEOS: { 592 const void *buffer; 593 int32 bufferLength; 594 595 BRect frame; 596 color_space cspace = B_NO_COLOR_SPACE; 597 598 status = flattened.FindRect("_frame", &frame); 599 if (status != B_OK) 600 return NULL; 601 602 status = flattened.FindInt32("_cspace", (int32)cspace); 603 if (status != B_OK) 604 return NULL; 605 cspace = B_RGBA32; 606 bitmap = new BBitmap(frame, cspace, true); 607 608 status = flattened.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); 609 if (status != B_OK) { 610 delete bitmap; 611 return NULL; 612 } 613 614 memcpy(bitmap->Bits(), buffer, bufferLength); 615 return bitmap; 616 } 617 default: 618 return NULL; 619 } 620 621 // shut the compiler up 622 return NULL; 623 } 624 625 626 BRect 627 ScaleRectToFit(const BRect &from, const BRect &to) 628 { 629 // Dynamic sizing algorithm 630 // 1) Check to see if either dimension is bigger than the view's display area 631 // 2) If smaller along both axes, make bitmap rect centered and return 632 // 3) Check to see if scaling is to be horizontal or vertical on basis of longer axis 633 // 4) Calculate scaling factor 634 // 5) Scale both axes down by scaling factor, accounting for border width 635 // 6) Center the rectangle in the direction of the smaller axis 636 637 if (!to.IsValid()) 638 return from; 639 if (!from.IsValid()) 640 return to; 641 642 BRect r(to); 643 644 if ((from.Width() <= r.Width()) && (from.Height() <= r.Height())) { 645 // Smaller than view, so just center and return 646 r = from; 647 r.OffsetBy((to.Width() - r.Width()) / 2, (to.Height() - r.Height()) / 2); 648 return r; 649 } 650 651 float multiplier = from.Width()/from.Height(); 652 if (multiplier > 1) { 653 // Landscape orientation 654 655 // Scale rectangle to bounds width and center height 656 r.bottom = r.top + (r.Width() / multiplier); 657 r.OffsetBy(0, (to.Height() - r.Height()) / 2); 658 } else { 659 // Portrait orientation 660 661 // Scale rectangle to bounds height and center width 662 r.right = r.left + (r.Height() * multiplier); 663 r.OffsetBy((to.Width() - r.Width()) / 2, 0); 664 } 665 return r; 666 } 667 668 669 void 670 BitmapView::RemoveBitmap(void) 671 { 672 SetBitmap(NULL); 673 } 674 675 676 void 677 BitmapView::PasteBitmap(void) 678 { 679 BBitmap *bmp = BitmapFromClipboard(); 680 if (bmp) 681 SetBitmap(bmp); 682 683 if (fConstrainDrops) 684 ConstrainBitmap(); 685 } 686