xref: /haiku/src/apps/icon-o-matic/CanvasView.cpp (revision bc3955fea5b07e2e94a27fc05e4bb58fe6f0319b)
1 /*
2  * Copyright 2006-2007, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include "CanvasView.h"
10 
11 #include <Bitmap.h>
12 #include <Cursor.h>
13 #include <Message.h>
14 #include <Region.h>
15 #include <Window.h>
16 
17 #include <stdio.h>
18 
19 #include "cursors.h"
20 #include "ui_defines.h"
21 
22 #include "CommandStack.h"
23 #include "IconRenderer.h"
24 
25 using std::nothrow;
26 
27 // constructor
28 CanvasView::CanvasView(BRect frame)
29 	: StateView(frame, "canvas view", B_FOLLOW_ALL,
30 				B_WILL_DRAW | B_FRAME_EVENTS),
31 	  fBitmap(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)),
32 	  fBackground(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)),
33 	  fIcon(NULL),
34 	  fRenderer(new IconRenderer(fBitmap)),
35 	  fDirtyIconArea(fBitmap->Bounds()),
36 
37 	  fCanvasOrigin(0.0, 0.0),
38 	  fZoomLevel(1.0),
39 
40 	  fSpaceHeldDown(false),
41 	  fScrollTracking(false),
42 	  fScrollTrackingStart(0.0, 0.0),
43 
44 	  fMouseFilterMode(SNAPPING_OFF),
45 
46 	  fOffsreenBitmap(NULL),
47 	  fOffsreenView(NULL)
48 {
49 	_MakeBackground();
50 	fRenderer->SetBackground(fBackground);
51 }
52 
53 // destructor
54 CanvasView::~CanvasView()
55 {
56 	SetIcon(NULL);
57 	delete fRenderer;
58 	delete fBitmap;
59 	delete fBackground;
60 
61 	_FreeBackBitmap();
62 }
63 
64 // #pragma mark -
65 
66 // AttachedToWindow
67 void
68 CanvasView::AttachedToWindow()
69 {
70 	StateView::AttachedToWindow();
71 
72 	SetViewColor(B_TRANSPARENT_COLOR);
73 	SetLowColor(kStripesHigh);
74 	SetHighColor(kStripesLow);
75 
76 	BRect bounds(Bounds());
77 
78 	_AllocBackBitmap(bounds.Width(), bounds.Height());
79 
80 	// layout icon in the center
81 	BRect bitmapBounds(fBitmap->Bounds());
82 	fCanvasOrigin.x = floorf((bounds.left + bounds.right
83 		- bitmapBounds.right - bitmapBounds.left) / 2.0 + 0.5);
84 	fCanvasOrigin.y = floorf((bounds.top + bounds.bottom
85 		- bitmapBounds.bottom - bitmapBounds.top) / 2.0 + 0.5);
86 
87 	_SetZoom(8.0, false);
88 }
89 
90 // FrameResized
91 void
92 CanvasView::FrameResized(float width, float height)
93 {
94 	_AllocBackBitmap(width, height);
95 
96 	// keep canvas centered
97 	BPoint oldCanvasOrigin = fCanvasOrigin;
98 	SetDataRect(_LayoutCanvas());
99 	if (oldCanvasOrigin != fCanvasOrigin)
100 		Invalidate();
101 }
102 
103 // Draw
104 void
105 CanvasView::Draw(BRect updateRect)
106 {
107 	if (!fOffsreenView) {
108 		_DrawInto(this, updateRect);
109 	} else {
110 		BPoint boundsLeftTop = Bounds().LeftTop();
111 		if (fOffsreenBitmap->Lock()) {
112 			fOffsreenView->PushState();
113 
114 			// apply scrolling offset to offscreen view
115 			fOffsreenView->SetOrigin(-boundsLeftTop.x, -boundsLeftTop.y);
116 			// mirror the clipping of this view
117 			// to the offscreen view for performance
118 			BRegion clipping;
119 			GetClippingRegion(&clipping);
120 			fOffsreenView->ConstrainClippingRegion(&clipping);
121 
122 			_DrawInto(fOffsreenView, updateRect);
123 
124 			fOffsreenView->PopState();
125 			fOffsreenView->Sync();
126 
127 			fOffsreenBitmap->Unlock();
128 		}
129 		// compensate scrolling offset in BView
130 		BRect bitmapRect = updateRect;
131 		bitmapRect.OffsetBy(-boundsLeftTop.x, -boundsLeftTop.y);
132 
133 		SetDrawingMode(B_OP_COPY);
134 		DrawBitmap(fOffsreenBitmap, bitmapRect, updateRect);
135 	}
136 }
137 
138 // #pragma mark -
139 
140 // MouseDown
141 void
142 CanvasView::MouseDown(BPoint where)
143 {
144 	if (!IsFocus())
145 		MakeFocus(true);
146 
147 	uint32 buttons;
148 	if (Window()->CurrentMessage()->FindInt32("buttons",
149 		(int32*)&buttons) < B_OK)
150 		buttons = 0;
151 
152 	// handle clicks of the third mouse button ourselves (panning),
153 	// otherwise have StateView handle it (normal clicks)
154 	if (fSpaceHeldDown || buttons & B_TERTIARY_MOUSE_BUTTON) {
155 		// switch into scrolling mode and update cursor
156 		fScrollTracking = true;
157 		where.x = roundf(where.x);
158 		where.y = roundf(where.y);
159 		fScrollOffsetStart = ScrollOffset();
160 		fScrollTrackingStart = where - fScrollOffsetStart;
161 		_UpdateToolCursor();
162 		SetMouseEventMask(B_POINTER_EVENTS,
163 						  B_LOCK_WINDOW_FOCUS | B_SUSPEND_VIEW_FOCUS);
164 	} else {
165 		StateView::MouseDown(where);
166 	}
167 }
168 
169 // MouseUp
170 void
171 CanvasView::MouseUp(BPoint where)
172 {
173 	if (fScrollTracking) {
174 		// stop scroll tracking and update cursor
175 		fScrollTracking = false;
176 		_UpdateToolCursor();
177 		// update StateView mouse position
178 		uint32 transit = Bounds().Contains(where) ?
179 			B_INSIDE_VIEW : B_OUTSIDE_VIEW;
180 		StateView::MouseMoved(where, transit, NULL);
181 	} else {
182 		StateView::MouseUp(where);
183 	}
184 }
185 
186 // MouseMoved
187 void
188 CanvasView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
189 {
190 	if (fScrollTracking) {
191 		uint32 buttons;
192 		GetMouse(&where, &buttons, false);
193 		if (!buttons) {
194 			MouseUp(where);
195 			return;
196 		}
197 		where.x = roundf(where.x);
198 		where.y = roundf(where.y);
199 		where -= ScrollOffset();
200 		BPoint offset = where - fScrollTrackingStart;
201 		SetScrollOffset(fScrollOffsetStart - offset);
202 	} else {
203 		// normal mouse movement handled by StateView
204 		if (!fSpaceHeldDown)
205 			StateView::MouseMoved(where, transit, dragMessage);
206 	}
207 }
208 
209 // FilterMouse
210 void
211 CanvasView::FilterMouse(BPoint* where) const
212 {
213 	switch (fMouseFilterMode) {
214 
215 		case SNAPPING_64:
216 			ConvertToCanvas(where);
217 			where->x = floorf(where->x + 0.5);
218 			where->y = floorf(where->y + 0.5);
219 			ConvertFromCanvas(where);
220 			break;
221 
222 		case SNAPPING_32:
223 			ConvertToCanvas(where);
224 			where->x /= 2.0;
225 			where->y /= 2.0;
226 			where->x = floorf(where->x + 0.5);
227 			where->y = floorf(where->y + 0.5);
228 			where->x *= 2.0;
229 			where->y *= 2.0;
230 			ConvertFromCanvas(where);
231 			break;
232 
233 		case SNAPPING_16:
234 			ConvertToCanvas(where);
235 			where->x /= 4.0;
236 			where->y /= 4.0;
237 			where->x = floorf(where->x + 0.5);
238 			where->y = floorf(where->y + 0.5);
239 			where->x *= 4.0;
240 			where->y *= 4.0;
241 			ConvertFromCanvas(where);
242 			break;
243 
244 		case SNAPPING_OFF:
245 		default:
246 			break;
247 	}
248 }
249 
250 // MouseWheelChanged
251 bool
252 CanvasView::MouseWheelChanged(BPoint where, float x, float y)
253 {
254 	if (!Bounds().Contains(where))
255 		return false;
256 
257 	if (y > 0.0) {
258 		_SetZoom(_NextZoomOutLevel(fZoomLevel));
259 		return true;
260 	} else if (y < 0.0) {
261 		_SetZoom(_NextZoomInLevel(fZoomLevel));
262 		return true;
263 	}
264 	return false;
265 }
266 
267 // #pragma mark -
268 
269 // ScrollOffsetChanged
270 void
271 CanvasView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
272 {
273 	BPoint offset = newOffset - oldOffset;
274 
275 	if (offset == B_ORIGIN)
276 		// prevent circular code (MouseMoved might call ScrollBy...)
277 		return;
278 
279 	ScrollBy(offset.x, offset.y);
280 
281 	if (!fScrollTracking)
282 		MouseMoved(fMouseInfo.position + offset, fMouseInfo.transit, NULL);
283 }
284 
285 // VisibleSizeChanged
286 void
287 CanvasView::VisibleSizeChanged(float oldWidth, float oldHeight,
288 							   float newWidth, float newHeight)
289 {
290 }
291 
292 // #pragma mark -
293 
294 // AreaInvalidated
295 void
296 CanvasView::AreaInvalidated(const BRect& area)
297 {
298 	if (fDirtyIconArea.Contains(area))
299 		return;
300 
301 	fDirtyIconArea = fDirtyIconArea | area;
302 
303 	BRect viewArea(area);
304 	ConvertFromCanvas(&viewArea);
305 	Invalidate(viewArea);
306 }
307 
308 
309 // #pragma mark -
310 
311 // SetIcon
312 void
313 CanvasView::SetIcon(Icon* icon)
314 {
315 	if (fIcon == icon)
316 		return;
317 
318 	if (fIcon)
319 		fIcon->RemoveListener(this);
320 
321 	fIcon = icon;
322 	fRenderer->SetIcon(icon);
323 
324 	if (fIcon)
325 		fIcon->AddListener(this);
326 }
327 
328 // SetMouseFilterMode
329 void
330 CanvasView::SetMouseFilterMode(uint32 mode)
331 {
332 	if (fMouseFilterMode == mode)
333 		return;
334 
335 	fMouseFilterMode = mode;
336 	Invalidate(_CanvasRect());
337 }
338 
339 // ConvertFromCanvas
340 void
341 CanvasView::ConvertFromCanvas(BPoint* point) const
342 {
343 	point->x = point->x * fZoomLevel + fCanvasOrigin.x;
344 	point->y = point->y * fZoomLevel + fCanvasOrigin.y;
345 }
346 
347 // ConvertToCanvas
348 void
349 CanvasView::ConvertToCanvas(BPoint* point) const
350 {
351 	point->x = (point->x - fCanvasOrigin.x) / fZoomLevel;
352 	point->y = (point->y - fCanvasOrigin.y) / fZoomLevel;
353 }
354 
355 // ConvertFromCanvas
356 void
357 CanvasView::ConvertFromCanvas(BRect* r) const
358 {
359 	r->left = r->left * fZoomLevel + fCanvasOrigin.x;
360 	r->top = r->top * fZoomLevel + fCanvasOrigin.y;
361 	r->right++;
362 	r->bottom++;
363 	r->right = r->right * fZoomLevel + fCanvasOrigin.x;
364 	r->bottom = r->bottom * fZoomLevel + fCanvasOrigin.y;
365 	r->right--;
366 	r->bottom--;
367 }
368 
369 // ConvertToCanvas
370 void
371 CanvasView::ConvertToCanvas(BRect* r) const
372 {
373 	r->left = (r->left - fCanvasOrigin.x) / fZoomLevel;
374 	r->top = (r->top - fCanvasOrigin.y) / fZoomLevel;
375 	r->right = (r->right - fCanvasOrigin.x) / fZoomLevel;
376 	r->bottom = (r->bottom - fCanvasOrigin.y) / fZoomLevel;
377 }
378 
379 // #pragma mark -
380 
381 // _HandleKeyDown
382 bool
383 CanvasView::_HandleKeyDown(uint32 key, uint32 modifiers)
384 {
385 	switch (key) {
386 		case 'z':
387 		case 'y':
388 			if (modifiers & B_SHIFT_KEY)
389 				CommandStack()->Redo();
390 			else
391 				CommandStack()->Undo();
392 			break;
393 
394 		case '+':
395 			_SetZoom(_NextZoomInLevel(fZoomLevel));
396 			break;
397 		case '-':
398 			_SetZoom(_NextZoomOutLevel(fZoomLevel));
399 			break;
400 
401 		case B_SPACE:
402 			fSpaceHeldDown = true;
403 			_UpdateToolCursor();
404 			break;
405 
406 		default:
407 			return StateView::_HandleKeyDown(key, modifiers);
408 	}
409 
410 	return true;
411 }
412 
413 // _HandleKeyUp
414 bool
415 CanvasView::_HandleKeyUp(uint32 key, uint32 modifiers)
416 {
417 	switch (key) {
418 		case B_SPACE:
419 			fSpaceHeldDown = false;
420 			_UpdateToolCursor();
421 			break;
422 
423 		default:
424 			return StateView::_HandleKeyUp(key, modifiers);
425 	}
426 
427 	return true;
428 }
429 
430 // _CanvasRect()
431 BRect
432 CanvasView::_CanvasRect() const
433 {
434 	BRect r = fBitmap->Bounds();
435 	ConvertFromCanvas(&r);
436 	return r;
437 }
438 
439 // _AllocBackBitmap
440 void
441 CanvasView::_AllocBackBitmap(float width, float height)
442 {
443 	// sanity check
444 	if (width <= 0.0 || height <= 0.0)
445 		return;
446 
447 	if (fOffsreenBitmap) {
448 		// see if the bitmap needs to be expanded
449 		BRect b = fOffsreenBitmap->Bounds();
450 		if (b.Width() >= width && b.Height() >= height)
451 			return;
452 
453 		// it does; clean up:
454 		_FreeBackBitmap();
455 	}
456 
457 	BRect b(0.0, 0.0, width, height);
458 	fOffsreenBitmap = new (nothrow) BBitmap(b, B_RGB32, true);
459 	if (!fOffsreenBitmap) {
460 		fprintf(stderr, "CanvasView::_AllocBackBitmap(): failed to allocate\n");
461 		return;
462 	}
463 	if (fOffsreenBitmap->IsValid()) {
464 		fOffsreenView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW);
465 		BFont font;
466 		GetFont(&font);
467 		fOffsreenView->SetFont(&font);
468 		fOffsreenView->SetHighColor(HighColor());
469 		fOffsreenView->SetLowColor(LowColor());
470 		fOffsreenView->SetFlags(Flags());
471 		fOffsreenBitmap->AddChild(fOffsreenView);
472 	} else {
473 		_FreeBackBitmap();
474 		fprintf(stderr, "CanvasView::_AllocBackBitmap(): bitmap invalid\n");
475 	}
476 }
477 
478 // _FreeBackBitmap
479 void
480 CanvasView::_FreeBackBitmap()
481 {
482 	if (fOffsreenBitmap) {
483 		delete fOffsreenBitmap;
484 		fOffsreenBitmap = NULL;
485 		fOffsreenView = NULL;
486 	}
487 }
488 
489 // _DrawInto
490 void
491 CanvasView::_DrawInto(BView* view, BRect updateRect)
492 {
493 	if (fDirtyIconArea.IsValid()) {
494 		fRenderer->Render(fDirtyIconArea);
495 		fDirtyIconArea.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN);
496 	}
497 
498 	// icon
499 	BRect canvas(_CanvasRect());
500 	view->DrawBitmap(fBitmap, fBitmap->Bounds(), canvas);
501 
502 	// grid
503 	int32 gridLines = 0;
504 	int32 scale = 1;
505 	switch (fMouseFilterMode) {
506 		case SNAPPING_64:
507 			gridLines = 63;
508 			break;
509 		case SNAPPING_32:
510 			gridLines = 31;
511 			scale = 2;
512 			break;
513 		case SNAPPING_16:
514 			gridLines = 15;
515 			scale = 4;
516 			break;
517 		case SNAPPING_OFF:
518 		default:
519 			break;
520 	}
521 	view->SetDrawingMode(B_OP_BLEND);
522 	for (int32 i = 1; i <= gridLines; i++) {
523 		BPoint cross(i * scale, i * scale);
524 		ConvertFromCanvas(&cross);
525 		view->StrokeLine(BPoint(canvas.left, cross.y),
526 						 BPoint(canvas.right, cross.y));
527 		view->StrokeLine(BPoint(cross.x, canvas.top),
528 						 BPoint(cross.x, canvas.bottom));
529 	}
530 	view->SetDrawingMode(B_OP_COPY);
531 
532 	// outside icon
533 	BRegion outside(Bounds() & updateRect);
534 	outside.Exclude(canvas);
535 	view->FillRegion(&outside, kStripes);
536 
537 	StateView::Draw(view, updateRect);
538 }
539 
540 // _MakeBackground
541 void
542 CanvasView::_MakeBackground()
543 {
544 	uint8* row = (uint8*)fBackground->Bits();
545 	uint32 bpr = fBackground->BytesPerRow();
546 	uint32 width = fBackground->Bounds().IntegerWidth() + 1;
547 	uint32 height = fBackground->Bounds().IntegerHeight() + 1;
548 
549 	const GammaTable& lut = fRenderer->GammaTable();
550 	uint8 redLow = lut.dir(kAlphaLow.red);
551 	uint8 greenLow = lut.dir(kAlphaLow.blue);
552 	uint8 blueLow = lut.dir(kAlphaLow.green);
553 	uint8 redHigh = lut.dir(kAlphaHigh.red);
554 	uint8 greenHigh = lut.dir(kAlphaHigh.blue);
555 	uint8 blueHigh = lut.dir(kAlphaHigh.green);
556 
557 	for (uint32 y = 0; y < height; y++) {
558 		uint8* p = row;
559 		for (uint32 x = 0; x < width; x++) {
560 			p[3] = 255;
561 			if (x % 8 >= 4) {
562 				if (y % 8 >= 4) {
563 					p[0] = blueLow;
564 					p[1] = greenLow;
565 					p[2] = redLow;
566 				} else {
567 					p[0] = blueHigh;
568 					p[1] = greenHigh;
569 					p[2] = redHigh;
570 				}
571 			} else {
572 				if (y % 8 >= 4) {
573 					p[0] = blueHigh;
574 					p[1] = greenHigh;
575 					p[2] = redHigh;
576 				} else {
577 					p[0] = blueLow;
578 					p[1] = greenLow;
579 					p[2] = redLow;
580 				}
581 			}
582 			p += 4;
583 		}
584 		row += bpr;
585 	}
586 }
587 
588 // _UpdateToolCursor
589 void
590 CanvasView::_UpdateToolCursor()
591 {
592 	if (fIcon) {
593 		if (fScrollTracking || fSpaceHeldDown) {
594 			// indicate scrolling mode
595 			const uchar* cursorData = fScrollTracking ? kGrabCursor : kHandCursor;
596 			BCursor cursor(cursorData);
597 			SetViewCursor(&cursor, true);
598 		} else {
599 			// pass on to current state of StateView
600 			UpdateStateCursor();
601 		}
602 	} else {
603 		BCursor cursor(kStopCursor);
604 		SetViewCursor(&cursor, true);
605 	}
606 }
607 
608 // #pragma mark -
609 
610 // _NextZoomInLevel
611 double
612 CanvasView::_NextZoomInLevel(double zoom) const
613 {
614 	if (zoom < 1)
615 		return 1;
616 	if (zoom < 1.5)
617 		return 1.5;
618 	if (zoom < 2)
619 		return 2;
620 	if (zoom < 3)
621 		return 3;
622 	if (zoom < 4)
623 		return 4;
624 	if (zoom < 6)
625 		return 6;
626 	if (zoom < 8)
627 		return 8;
628 	if (zoom < 16)
629 		return 16;
630 	if (zoom < 32)
631 		return 32;
632 	return 64;
633 }
634 
635 // _NextZoomOutLevel
636 double
637 CanvasView::_NextZoomOutLevel(double zoom) const
638 {
639 	if (zoom > 32)
640 		return 32;
641 	if (zoom > 16)
642 		return 16;
643 	if (zoom > 8)
644 		return 8;
645 	if (zoom > 6)
646 		return 6;
647 	if (zoom > 4)
648 		return 4;
649 	if (zoom > 3)
650 		return 3;
651 	if (zoom > 2)
652 		return 2;
653 	if (zoom > 1.5)
654 		return 1.5;
655 	return 1;
656 }
657 
658 // _SetZoom
659 void
660 CanvasView::_SetZoom(double zoomLevel, bool mouseIsAnchor)
661 {
662 	if (fZoomLevel == zoomLevel)
663 		return;
664 
665 	// TODO: still not working 100% correctly
666 
667 	// zoom into center of view
668 	BRect bounds(Bounds());
669 	BPoint anchor;
670 	anchor.x = (bounds.left + bounds.right + 1) / 2.0;
671 	anchor.y = (bounds.top + bounds.bottom + 1) / 2.0;
672 
673 	BPoint canvasAnchor = anchor;
674 	ConvertToCanvas(&canvasAnchor);
675 
676 	fZoomLevel = zoomLevel;
677 	SetDataRect(_LayoutCanvas());
678 
679 	ConvertFromCanvas(&canvasAnchor);
680 
681 	BPoint offset;
682 	offset.x = roundf(canvasAnchor.x - anchor.x);
683 	offset.y = roundf(canvasAnchor.y - anchor.y);
684 
685 	SetScrollOffset(ScrollOffset() + offset);
686 
687 	Invalidate();
688 }
689 
690 // _LayoutCanvas
691 BRect
692 CanvasView::_LayoutCanvas()
693 {
694 	if (!fBitmap)
695 		return BRect(0, 0, -1, -1);
696 
697 	// size of zoomed bitmap
698 	BRect r(fBitmap->Bounds());
699 	r.OffsetTo(0, 0);
700 	r.right = floorf((r.Width() + 1) * fZoomLevel + 0.5) - 1;
701 	r.bottom = floorf((r.Height() + 1) * fZoomLevel + 0.5) - 1;
702 
703 	// TODO: ask manipulators to extend size
704 
705 	BRect bitmapRect = r;
706 
707 	// resize for empty area around bitmap
708 	// (the size we want, but might still be much smaller than view)
709 	r.right += r.Width() * 0.5;
710 	r.bottom += r.Height() * 0.5;
711 
712 	// left top of canvas within empty area
713 	BRect bounds(Bounds());
714 	bounds.OffsetTo(B_ORIGIN);
715 	bounds = bounds | r;
716 	fCanvasOrigin.x = floorf((bounds.left + bounds.right
717 		- bitmapRect.right - bitmapRect.left) / 2.0 + 0.5);
718 	fCanvasOrigin.y = floorf((bounds.top + bounds.bottom
719 		- bitmapRect.bottom - bitmapRect.top) / 2.0 + 0.5);
720 
721 	return bounds;
722 }
723 
724