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("ResEdit", "This cannot be undone. " 254 "Remove the image?", "Remove", "Cancel"); 255 alert->SetShortcut(1, B_ESCAPE); 256 int32 value = alert->Go(); 257 if (value == 0) { 258 SetBitmap(NULL); 259 260 if (Target()) { 261 BMessenger msgr(Target()); 262 263 msgr.SendMessage(new BMessage(M_BITMAP_REMOVED)); 264 return; 265 } 266 } 267 } 268 case M_PASTE_IMAGE: 269 { 270 PasteBitmap(); 271 Invoke(); 272 } 273 } 274 BView::MessageReceived(msg); 275 } 276 277 278 void 279 BitmapView::Draw(BRect rect) 280 { 281 if (fBitmap) 282 DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect); 283 else { 284 SetHighColor(0, 0, 0, 80); 285 SetDrawingMode(B_OP_ALPHA); 286 DrawString("Drop", fNoPhotoOffsets[0]); 287 DrawString("a", fNoPhotoOffsets[1]); 288 DrawString("Photo", fNoPhotoOffsets[2]); 289 DrawString("Here", fNoPhotoOffsets[3]); 290 SetDrawingMode(B_OP_COPY); 291 } 292 293 if (fBorderStyle == B_FANCY_BORDER) { 294 rgb_color base= { 216, 216, 216, 255 }; 295 rgb_color work; 296 297 SetHighColor(base); 298 StrokeRect(Bounds().InsetByCopy(2, 2)); 299 300 BeginLineArray(12); 301 302 BRect r(Bounds()); 303 304 work = tint_color(base, B_DARKEN_2_TINT); 305 AddLine(r.LeftTop(), r.RightTop(), work); 306 AddLine(r.LeftTop(), r.LeftBottom(), work); 307 r.left++; 308 309 work = tint_color(base, B_DARKEN_4_TINT); 310 AddLine(r.RightTop(), r.RightBottom(), work); 311 AddLine(r.LeftBottom(), r.RightBottom(), work); 312 313 r.right--; 314 r.top++; 315 r.bottom--; 316 317 318 work = tint_color(base, B_LIGHTEN_MAX_TINT); 319 AddLine(r.LeftTop(), r.RightTop(), work); 320 AddLine(r.LeftTop(), r.LeftBottom(), work); 321 r.left++; 322 323 work = tint_color(base, B_DARKEN_3_TINT); 324 AddLine(r.RightTop(), r.RightBottom(), work); 325 AddLine(r.LeftBottom(), r.RightBottom(), work); 326 327 // this rect handled by the above StrokeRect, so inset a total of 2 pixels 328 r.left++; 329 r.right -= 2; 330 r.top += 2; 331 r.bottom -= 2; 332 333 334 work = tint_color(base, B_DARKEN_3_TINT); 335 AddLine(r.LeftTop(), r.RightTop(), work); 336 AddLine(r.LeftTop(), r.LeftBottom(), work); 337 r.left++; 338 339 work = tint_color(base, B_LIGHTEN_MAX_TINT); 340 AddLine(r.RightTop(), r.RightBottom(), work); 341 AddLine(r.LeftBottom(), r.RightBottom(), work); 342 343 r.right--; 344 r.top++; 345 r.bottom--; 346 EndLineArray(); 347 348 SetHighColor(tint_color(base, B_DARKEN_2_TINT)); 349 StrokeRect(r); 350 } else { 351 // Plain border 352 SetHighColor(0, 0, 0); 353 StrokeRect(fBitmapRect); 354 } 355 } 356 357 358 void 359 BitmapView::MouseDown(BPoint pt) 360 { 361 BPoint mousept; 362 uint32 buttons; 363 364 GetMouse(&mousept, &buttons); 365 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 366 ConvertToScreen(&mousept); 367 368 mousept.x= (mousept.x>5) ? mousept.x-5 : 0; 369 mousept.y= (mousept.y>5) ? mousept.y-5 : 0; 370 371 if (AcceptsPaste() && ClipboardHasBitmap()) 372 fPasteItem->SetEnabled(true); 373 else 374 fPasteItem->SetEnabled(false); 375 376 if (fRemovableBitmap && fBitmap) 377 fRemoveItem->SetEnabled(true); 378 else 379 fRemoveItem->SetEnabled(false); 380 381 fPopUpMenu->Go(mousept, true, true, true); 382 } 383 } 384 385 386 void 387 BitmapView::FrameResized(float w, float h) 388 { 389 CalculateBitmapRect(); 390 } 391 392 393 void 394 BitmapView::CalculateBitmapRect(void) 395 { 396 if (!fBitmap || fFixedSize) { 397 fBitmapRect = Bounds().InsetByCopy(1, 1); 398 return; 399 } 400 401 uint8 borderwidth = (fBorderStyle == B_FANCY_BORDER) ? 5 : 1; 402 403 BRect r(Bounds()); 404 fBitmapRect= ScaleRectToFit(fBitmap->Bounds(), r.InsetByCopy(borderwidth, borderwidth)); 405 } 406 407 408 void 409 BitmapView::SetAcceptDrops(bool accept) 410 { 411 fAcceptDrops = accept; 412 } 413 414 415 void 416 BitmapView::SetAcceptPaste(bool accept) 417 { 418 fAcceptPaste = accept; 419 } 420 421 422 void 423 BitmapView::SetConstrainDrops(bool value) 424 { 425 fConstrainDrops = value; 426 } 427 428 429 void 430 BitmapView::MaxBitmapSize(float *width, float *height) const 431 { 432 *width = fMaxWidth; 433 *height = fMaxHeight; 434 } 435 436 437 void 438 BitmapView::SetMaxBitmapSize(const float &width, const float &height) 439 { 440 fMaxWidth = width; 441 fMaxHeight = height; 442 443 ConstrainBitmap(); 444 } 445 446 447 void 448 BitmapView::SetBitmapRemovable(bool isremovable) 449 { 450 fRemovableBitmap = isremovable; 451 } 452 453 454 void 455 BitmapView::ConstrainBitmap(void) 456 { 457 if (!fBitmap || fMaxWidth < 1 || fMaxHeight < 1) 458 return; 459 460 BRect r = ScaleRectToFit(fBitmap->Bounds(), BRect(0, 0, fMaxWidth - 1, fMaxHeight - 1)); 461 r.OffsetTo(0, 0); 462 463 BBitmap *scaled = new BBitmap(r, fBitmap->ColorSpace(), true); 464 BView *view = new BView(r, "drawview", 0, 0); 465 466 scaled->Lock(); 467 scaled->AddChild(view); 468 view->DrawBitmap(fBitmap, fBitmap->Bounds(), scaled->Bounds()); 469 scaled->Unlock(); 470 471 delete fBitmap; 472 fBitmap = new BBitmap(scaled, false); 473 } 474 475 476 bool 477 BitmapView::ClipboardHasBitmap(void) 478 { 479 BMessage *clip = NULL, flattened; 480 uint8 clipval = CLIP_NONE; 481 bool returnval; 482 483 if (be_clipboard->Lock()) { 484 clip = be_clipboard->Data(); 485 if (!clip->IsEmpty()) { 486 returnval = (clip->FindMessage("image/bitmap", &flattened) == B_OK); 487 if (returnval) 488 clipval = CLIP_BEOS; 489 else { 490 BString string; 491 returnval = (clip->FindString("class", &string) == B_OK && string == "BBitmap"); 492 493 // Try method Gobe Productive uses if that, too, didn't work 494 if (returnval) 495 clipval = CLIP_SHOWIMAGE; 496 else { 497 returnval = (clip->FindMessage("image/x-vnd.Be-bitmap", &flattened) == B_OK); 498 if (returnval) 499 clipval = CLIP_SHOWIMAGE; 500 else 501 clipval = CLIP_NONE; 502 } 503 } 504 } 505 be_clipboard->Unlock(); 506 } 507 return (clipval != CLIP_NONE)?true:false; 508 } 509 510 511 BBitmap * 512 BitmapView::BitmapFromClipboard(void) 513 { 514 BMessage *clip = NULL, flattened; 515 BBitmap *bitmap; 516 517 if (!be_clipboard->Lock()) 518 return NULL; 519 520 clip = be_clipboard->Data(); 521 if (!clip) 522 return NULL; 523 524 uint8 clipval = CLIP_NONE; 525 526 // Try ArtPaint-style storage 527 status_t status = clip->FindMessage("image/bitmap", &flattened); 528 529 // If that didn't work, try ShowImage-style 530 if (status != B_OK) { 531 BString string; 532 status = clip->FindString("class", &string); 533 534 // Try method Gobe Productive uses if that, too, didn't work 535 if (status == B_OK && string == "BBitmap") 536 clipval = CLIP_SHOWIMAGE; 537 else { 538 status = clip->FindMessage("image/x-vnd.Be-bitmap", &flattened); 539 if (status == B_OK) 540 clipval = CLIP_PRODUCTIVE; 541 else 542 clipval = CLIP_NONE; 543 } 544 } 545 else 546 clipval = CLIP_BEOS; 547 548 be_clipboard->Unlock(); 549 550 switch (clipval) { 551 case CLIP_SHOWIMAGE: { 552 // Showimage does it a slightly different way -- it dumps the BBitmap 553 // data directly to the clipboard message instead of packaging it in 554 // a bitmap like everyone else. 555 556 if (!be_clipboard->Lock()) 557 return NULL; 558 559 BMessage datamsg(*be_clipboard->Data()); 560 561 be_clipboard->Unlock(); 562 563 const void *buffer; 564 ssize_t bufferLength; 565 566 BRect frame; 567 color_space cspace = B_NO_COLOR_SPACE; 568 569 status = datamsg.FindRect("_frame", &frame); 570 if (status != B_OK) 571 return NULL; 572 573 status = datamsg.FindInt32("_cspace", (int32)cspace); 574 if (status != B_OK) 575 return NULL; 576 cspace = B_RGBA32; 577 bitmap = new BBitmap(frame, cspace, true); 578 579 status = datamsg.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); 580 if (status != B_OK) { 581 delete bitmap; 582 return NULL; 583 } 584 585 memcpy(bitmap->Bits(), buffer, bufferLength); 586 return bitmap; 587 } 588 case CLIP_PRODUCTIVE: 589 // Productive doesn't name the packaged BBitmap data message the same, but 590 // uses exactly the same data format. 591 592 case CLIP_BEOS: { 593 const void *buffer; 594 ssize_t bufferLength; 595 596 BRect frame; 597 color_space cspace = B_NO_COLOR_SPACE; 598 599 status = flattened.FindRect("_frame", &frame); 600 if (status != B_OK) 601 return NULL; 602 603 status = flattened.FindInt32("_cspace", (int32)cspace); 604 if (status != B_OK) 605 return NULL; 606 cspace = B_RGBA32; 607 bitmap = new BBitmap(frame, cspace, true); 608 609 status = flattened.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); 610 if (status != B_OK) { 611 delete bitmap; 612 return NULL; 613 } 614 615 memcpy(bitmap->Bits(), buffer, bufferLength); 616 return bitmap; 617 } 618 default: 619 return NULL; 620 } 621 622 // shut the compiler up 623 return NULL; 624 } 625 626 627 BRect 628 ScaleRectToFit(const BRect &from, const BRect &to) 629 { 630 // Dynamic sizing algorithm 631 // 1) Check to see if either dimension is bigger than the view's display area 632 // 2) If smaller along both axes, make bitmap rect centered and return 633 // 3) Check to see if scaling is to be horizontal or vertical on basis of longer axis 634 // 4) Calculate scaling factor 635 // 5) Scale both axes down by scaling factor, accounting for border width 636 // 6) Center the rectangle in the direction of the smaller axis 637 638 if (!to.IsValid()) 639 return from; 640 if (!from.IsValid()) 641 return to; 642 643 BRect r(to); 644 645 if ((from.Width() <= r.Width()) && (from.Height() <= r.Height())) { 646 // Smaller than view, so just center and return 647 r = from; 648 r.OffsetBy((to.Width() - r.Width()) / 2, (to.Height() - r.Height()) / 2); 649 return r; 650 } 651 652 float multiplier = from.Width()/from.Height(); 653 if (multiplier > 1) { 654 // Landscape orientation 655 656 // Scale rectangle to bounds width and center height 657 r.bottom = r.top + (r.Width() / multiplier); 658 r.OffsetBy(0, (to.Height() - r.Height()) / 2); 659 } else { 660 // Portrait orientation 661 662 // Scale rectangle to bounds height and center width 663 r.right = r.left + (r.Height() * multiplier); 664 r.OffsetBy((to.Width() - r.Width()) / 2, 0); 665 } 666 return r; 667 } 668 669 670 void 671 BitmapView::RemoveBitmap(void) 672 { 673 SetBitmap(NULL); 674 } 675 676 677 void 678 BitmapView::PasteBitmap(void) 679 { 680 BBitmap *bmp = BitmapFromClipboard(); 681 if (bmp) 682 SetBitmap(bmp); 683 684 if (fConstrainDrops) 685 ConstrainBitmap(); 686 } 687