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