xref: /haiku/src/apps/resedit/BitmapView.cpp (revision b028e77473189065f2baefc6f5e10d451cf591e2)
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