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