1
2#include <stdio.h>
3
4#include <Application.h>
5#include <Message.h>
6#include <MessageQueue.h>
7#include <Messenger.h>
8#include <Window.h>
9
10#include "DrawingEngine.h"
11#include "DrawView.h"
12#include "WindowLayer.h"
13
14#include "Desktop.h"
15
16// constructor
17Desktop::Desktop(DrawView* drawView, DrawingEngine* engine)
18	: BLooper("desktop", B_URGENT_DISPLAY_PRIORITY),
19	  fTracking(false),
20	  fLastMousePos(-1.0, -1.0),
21	  fClickedWindow(NULL),
22	  fScrollingView(NULL),
23	  fResizing(false),
24	  fIs2ndButton(false),
25
26	  fClippingLock("clipping lock"),
27	  fBackgroundRegion(),
28
29	  fMasterClipping(),
30	  fXOffset(0),
31	  fYOffset(0),
32
33	  fDrawView(drawView),
34	  fDrawingEngine(engine),
35
36	  fWindows(64),
37
38	  fFocusFollowsMouse(true),
39	  fFocusWindow(NULL)
40{
41	fDrawView->SetDesktop(this);
42
43	BRegion stillAvailableOnScreen;
44	_RebuildClippingForAllWindows(&stillAvailableOnScreen);
45	_SetBackground(&stillAvailableOnScreen);
46}
47
48// destructor
49Desktop::~Desktop()
50{
51}
52
53// MouseDown
54void
55Desktop::MouseDown(BPoint where, uint32 buttons, int32 clicks)
56{
57	fLastMousePos = where;
58	fClickedWindow = WindowAt(where);
59	fClickTime = system_time();
60	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
61		fTracking = true;
62		if (fClickedWindow) {
63			if (modifiers() & B_SHIFT_KEY) {
64				fScrollingView = fClickedWindow->ViewAt(where);
65			} else if (clicks >= 2) {
66				HideWindow(fClickedWindow);
67				fClickedWindow = NULL;
68			} else {
69				BRect frame(fClickedWindow->Frame());
70				BRect resizeRect(frame.right - 10, frame.bottom - 10,
71								 frame.right + 4, frame.bottom + 4);
72				fResizing = resizeRect.Contains(where);
73			}
74		}
75	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
76		if (fClickedWindow)
77			SendToBack(fClickedWindow);
78
79		fIs2ndButton = true;
80	} else if (buttons == B_TERTIARY_MOUSE_BUTTON) {
81		fDrawView->Invalidate();
82	}
83}
84
85// MouseUp
86void
87Desktop::MouseUp(BPoint where)
88{
89	if (!fIs2ndButton && system_time() - fClickTime < 250000L && fClickedWindow) {
90		BringToFront(fClickedWindow);
91	}
92	fTracking = false;
93	fIs2ndButton = false;
94	fClickedWindow = NULL;
95	fScrollingView = NULL;
96}
97
98// MouseMoved
99void
100Desktop::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
101{
102	WindowLayer* window;
103	if (!fTracking && fFocusFollowsMouse && (window = WindowAt(where))) {
104		SetFocusWindow(window);
105	}
106	if (fTracking) {
107		int32 dx = (int32)(where.x - fLastMousePos.x);
108		int32 dy = (int32)(where.y - fLastMousePos.y);
109		fLastMousePos = where;
110
111		if (dx != 0 || dy != 0) {
112			if (fClickedWindow) {
113				if (fScrollingView) {
114					if (LockClipping()) {
115						fClickedWindow->ScrollViewBy(fScrollingView, -dx, -dy);
116						UnlockClipping();
117					}
118				} else if (fResizing) {
119//bigtime_t now = system_time();
120					ResizeWindowBy(fClickedWindow, dx, dy);
121//printf("resizing: %lld\n", system_time() - now);
122				} else {
123					MoveWindowBy(fClickedWindow, dx, dy);
124//printf("moving: %lld\n", system_time() - now);
125				}
126			}
127		}
128	} else if (fIs2ndButton) {
129		fDrawingEngine->StrokeLine(fLastMousePos, where, (rgb_color){ 0, 0, 0, 255 });
130		fLastMousePos = where;
131	}
132}
133
134// MessageReceived
135void
136Desktop::MessageReceived(BMessage* message)
137{
138	switch (message->what) {
139		case B_MOUSE_DOWN: {
140			BPoint where;
141			uint32 buttons;
142			int32 clicks;
143			if (message->FindPoint("where", &where) >= B_OK &&
144				message->FindInt32("buttons", (int32*)&buttons) >= B_OK &&
145				message->FindInt32("clicks", &clicks) >= B_OK) {
146
147				where.x += fXOffset;
148				where.y += fYOffset;
149
150				MouseDown(where, buttons, clicks);
151			}
152			break;
153		}
154		case B_MOUSE_UP: {
155			BPoint where;
156			if (message->FindPoint("where", &where) >= B_OK) {
157
158				where.x += fXOffset;
159				where.y += fYOffset;
160
161				MouseUp(where);
162			}
163			break;
164		}
165		case B_MOUSE_MOVED: {
166			if (!MessageQueue()->FindMessage(B_MOUSE_MOVED, 0)) {
167				BPoint where;
168				uint32 transit;
169				if (message->FindPoint("where", &where) >= B_OK &&
170					message->FindInt32("be:transit", (int32*)&transit) >= B_OK) {
171
172					where.x += fXOffset;
173					where.y += fYOffset;
174
175					MouseMoved(where, transit, NULL);
176				}
177			}
178			break;
179		}
180
181		case MSG_ADD_WINDOW: {
182			WindowLayer* window;
183			if (message->FindPointer("window", (void**)&window) >= B_OK)
184				AddWindow(window);
185			break;
186		}
187
188		case MSG_QUIT:
189			if (LockClipping()) {
190				int32 count = CountWindows();
191				for (int32 i = 0; i < count; i++)
192					WindowAtFast(i)->PostMessage(B_QUIT_REQUESTED);
193				UnlockClipping();
194			}
195			break;
196
197		default:
198			BLooper::MessageReceived(message);
199	}
200}
201
202// #pragma mark -
203
204// SetMasterClipping
205void
206Desktop::SetMasterClipping(BRegion* clipping)
207{
208	BRegion update = *clipping;
209	update.Exclude(&fMasterClipping);
210
211	fMasterClipping = *clipping;
212	// since parts of the view might have been exposed,
213	// we need a clipping rebuild
214	BRegion background;
215	_RebuildClippingForAllWindows(&background);
216	_SetBackground(&background);
217
218	fDrawingEngine->FillRegion(&fBackgroundRegion, (rgb_color){ 51, 102, 152, 255 });
219
220	// trigger redrawing windows
221	update.Exclude(&fBackgroundRegion);
222	MarkDirty(&update);
223}
224
225// SetOffset
226void
227Desktop::SetOffset(int32 x, int32 y)
228{
229	fXOffset = x;
230	fYOffset = y;
231}
232
233// #pragma mark -
234
235// AddWindow
236bool
237Desktop::AddWindow(WindowLayer* window)
238{
239	bool success = false;
240	if (fWindows.AddItem((void*)window)) {
241		// rebuild the entire screen clipping and draw the new window
242		if (LockClipping()) {
243			BRegion background;
244			_RebuildClippingForAllWindows(&background);
245			fBackgroundRegion.Exclude(&window->VisibleRegion());
246			MarkDirty(&window->VisibleRegion());
247			_SetBackground(&background);
248
249			UnlockClipping();
250		}
251		SetFocusWindow(window);
252
253		success = true;
254	}
255	return success;
256}
257
258// RemoveWindow
259bool
260Desktop::RemoveWindow(WindowLayer* window)
261{
262	bool success = false;
263	if (fWindows.RemoveItem((void*)window)) {
264		// rebuild the entire screen clipping and redraw the exposed windows
265		if (LockClipping()) {
266			BRegion dirty = window->VisibleRegion();
267			BRegion background;
268			_RebuildClippingForAllWindows(&background);
269			MarkDirty(&dirty);
270			_SetBackground(&background);
271
272			UnlockClipping();
273		}
274		success = true;
275	}
276	return success;
277}
278
279// IndexOf
280int32
281Desktop::IndexOf(WindowLayer* window) const
282{
283	return fWindows.IndexOf((void*)window);
284}
285
286// CountWindows
287int32
288Desktop::CountWindows() const
289{
290	return fWindows.CountItems();
291}
292
293// HasWindow
294bool
295Desktop::HasWindow(WindowLayer* window) const
296{
297	return fWindows.HasItem((void*)window);
298}
299
300// WindowAt
301WindowLayer*
302Desktop::WindowAt(int32 index) const
303{
304	return (WindowLayer*)fWindows.ItemAt(index);
305}
306
307// WindowAtFast
308WindowLayer*
309Desktop::WindowAtFast(int32 index) const
310{
311	return (WindowLayer*)fWindows.ItemAtFast(index);
312}
313
314// WindowAt
315WindowLayer*
316Desktop::WindowAt(const BPoint& where) const
317{
318	// NOTE, since the clipping is only changed from this thread,
319	// it is save to use it without locking
320	int32 count = CountWindows();
321	for (int32 i = count - 1; i >= 0; i--) {
322		WindowLayer* window = WindowAtFast(i);
323		if (!window->IsHidden() && window->VisibleRegion().Contains(where))
324			return window;
325	}
326	return NULL;
327}
328
329// TopWindow
330WindowLayer*
331Desktop::TopWindow() const
332{
333	return (WindowLayer*)fWindows.LastItem();
334}
335
336// BottomWindow
337WindowLayer*
338Desktop::BottomWindow() const
339{
340	return (WindowLayer*)fWindows.FirstItem();
341}
342
343#pragma mark -
344
345// MoveWindowBy
346void
347Desktop::MoveWindowBy(WindowLayer* window, int32 x, int32 y)
348{
349	if (!Lock())
350		return;
351
352	if (LockClipping()) {
353		// the dirty region starts with the visible area of the window being moved
354		BRegion newDirtyRegion(window->VisibleRegion());
355
356		window->MoveBy(x, y);
357
358		BRegion background;
359		_RebuildClippingForAllWindows(&background);
360
361		// construct the region that is possible to be blitted
362		// to move the contents of the window
363		BRegion copyRegion(window->VisibleRegion());
364		copyRegion.OffsetBy(-x, -y);
365		copyRegion.IntersectWith(&newDirtyRegion);
366
367		// include the the new visible region of the window being
368		// moved into the dirty region (for now)
369		newDirtyRegion.Include(&window->VisibleRegion());
370
371		fDrawingEngine->CopyRegion(&copyRegion, x, y);
372
373		copyRegion.OffsetBy(x, y);
374		newDirtyRegion.Exclude(&copyRegion);
375
376		MarkDirty(&newDirtyRegion);
377		_SetBackground(&background);
378
379		UnlockClipping();
380	}
381
382	Unlock();
383}
384
385// ResizeWindowBy
386void
387Desktop::ResizeWindowBy(WindowLayer* window, int32 x, int32 y)
388{
389	if (!Lock())
390		return;
391
392	if (LockClipping()) {
393		BRegion newDirtyRegion;
394		BRegion previouslyOccupiedRegion(window->VisibleRegion());
395
396		window->ResizeBy(x, y, &newDirtyRegion);
397
398		BRegion background;
399		_RebuildClippingForAllWindows(&background);
400
401		previouslyOccupiedRegion.Exclude(&window->VisibleRegion());
402
403		newDirtyRegion.IntersectWith(&window->VisibleRegion());
404		newDirtyRegion.Include(&previouslyOccupiedRegion);
405
406		MarkDirty(&newDirtyRegion);
407		_SetBackground(&background);
408
409		UnlockClipping();
410	}
411
412	Unlock();
413}
414
415// ShowWindow
416void
417Desktop::ShowWindow(WindowLayer* window)
418{
419	SetWindowHidden(window, false);
420}
421
422// HideWindow
423void
424Desktop::HideWindow(WindowLayer* window)
425{
426	SetWindowHidden(window, true);
427}
428
429// SetWindowHidden
430void
431Desktop::SetWindowHidden(WindowLayer* window, bool hidden)
432{
433	// TODO: if in ffm mode, make sure to switch focus
434	// if appropriate
435	if (LockClipping()) {
436
437		if (window->IsHidden() != hidden) {
438
439			window->SetHidden(hidden);
440
441			BRegion dirty;
442
443			if (hidden) {
444				// after rebuilding the clipping,
445				// this window will not have a visible
446				// region anymore, so we need to remember
447				// it now
448				// (actually that's not true, since
449				// hidden windows are excluded from the
450				// clipping calculation, but anyways)
451				dirty = window->VisibleRegion();
452			}
453
454			BRegion background;
455			_RebuildClippingForAllWindows(&background);
456			_SetBackground(&background);
457
458			if (!hidden) {
459				// everything that is now visible in the
460				// window needs a redraw, but other windows
461				// are not affected, we can call ProcessDirtyRegion()
462				// of the window, and don't have to use MarkDirty()
463				dirty = window->VisibleRegion();
464				window->ProcessDirtyRegion(&dirty);
465			} else {
466				// when the window was hidden, the dirty region
467				// affects other windows
468				MarkDirty(&dirty);
469			}
470		}
471
472		UnlockClipping();
473	}
474}
475
476
477// BringToFront
478void
479Desktop::BringToFront(WindowLayer* window)
480{
481	if (window == TopWindow())
482		return;
483
484	if (LockClipping()) {
485
486		// we don't need to redraw what is currently
487		// visible of the window
488		BRegion clean(window->VisibleRegion());
489
490		// detach window and re-atach at last position
491		if (fWindows.RemoveItem((void*)window) &&
492			fWindows.AddItem((void*)window)) {
493
494			BRegion dummy;
495			_RebuildClippingForAllWindows(&dummy);
496
497			// redraw what became visible of the window
498			BRegion dirty(window->VisibleRegion());
499			dirty.Exclude(&clean);
500
501			MarkDirty(&dirty);
502		}
503
504		UnlockClipping();
505	}
506
507	if (!fFocusFollowsMouse)
508		SetFocusWindow(TopWindow());
509}
510
511// SendToBack
512void
513Desktop::SendToBack(WindowLayer* window)
514{
515	if (window == BottomWindow())
516		return;
517
518	if (LockClipping()) {
519
520		// what is currently visible of the window
521		// might be dirty after the window is send to back
522		BRegion dirty(window->VisibleRegion());
523
524		// detach window and re-atach at last position
525		if (fWindows.RemoveItem((void*)window) &&
526			fWindows.AddItem((void*)window, 0)) {
527
528			BRegion dummy;
529			_RebuildClippingForAllWindows(&dummy);
530
531			// redraw what was previously visible of the window
532			BRegion clean(window->VisibleRegion());
533			dirty.Exclude(&clean);
534
535			MarkDirty(&dirty);
536		}
537
538		UnlockClipping();
539	}
540
541	if (!fFocusFollowsMouse)
542		SetFocusWindow(TopWindow());
543}
544
545// SetFocusWindow
546void
547Desktop::SetFocusWindow(WindowLayer* window)
548{
549	if (fFocusWindow == window)
550		return;
551
552	if (LockClipping()) {
553
554		if (fFocusWindow)
555			fFocusWindow->SetFocus(false);
556
557		fFocusWindow = window;
558
559		if (fFocusWindow)
560			fFocusWindow->SetFocus(true);
561
562		UnlockClipping();
563	}
564}
565
566
567
568// #pragma mark -
569
570// MarkDirty
571void
572Desktop::MarkDirty(BRegion* region)
573{
574	if (region->CountRects() == 0)
575		return;
576
577	if (LockClipping()) {
578		// send redraw messages to all windows intersecting the dirty region
579		_TriggerWindowRedrawing(region);
580
581		UnlockClipping();
582	}
583}
584
585// WindowDied
586void
587Desktop::WindowDied(WindowLayer* window)
588{
589	// thread is expected expected to have the
590	// write lock!
591	fWindows.RemoveItem(window);
592	if (fWindows.CountItems() == 0)
593		be_app->PostMessage(B_QUIT_REQUESTED);
594}
595
596// #pragma mark -
597
598// _RebuildClippingForAllWindows
599void
600Desktop::_RebuildClippingForAllWindows(BRegion* stillAvailableOnScreen)
601{
602	// the available region on screen starts with the entire screen area
603	// each window on the screen will take a portion from that area
604
605	// figure out what the entire screen area is
606	*stillAvailableOnScreen = fMasterClipping;
607
608	// set clipping of each window
609	int32 count = CountWindows();
610	for (int32 i = count - 1; i >= 0; i--) {
611		WindowLayer* window = WindowAtFast(i);
612		if (!window->IsHidden()) {
613			window->SetClipping(stillAvailableOnScreen);
614			// that windows region is not available on screen anymore
615			stillAvailableOnScreen->Exclude(&window->VisibleRegion());
616		}
617	}
618}
619
620// _TriggerWindowRedrawing
621void
622Desktop::_TriggerWindowRedrawing(BRegion* newDirtyRegion)
623{
624	// send redraw messages to all windows intersecting the dirty region
625	int32 count = CountWindows();
626	for (int32 i = count - 1; i >= 0; i--) {
627		WindowLayer* window = WindowAtFast(i);
628		if (!window->IsHidden() && newDirtyRegion->Intersects(window->VisibleRegion().Frame()))
629			window->ProcessDirtyRegion(newDirtyRegion);
630	}
631}
632
633// _SetBackground
634void
635Desktop::_SetBackground(BRegion* background)
636{
637	// NOTE: the drawing operation is caried out
638	// in the clipping region rebuild, but it is
639	// ok actually, because it also avoids trails on
640	// moving windows
641
642	// remember the region not covered by any windows
643	// and redraw the dirty background
644	BRegion dirtyBackground(*background);
645	dirtyBackground.Exclude(&fBackgroundRegion);
646	dirtyBackground.IntersectWith(background);
647	fBackgroundRegion = *background;
648	if (dirtyBackground.Frame().IsValid()) {
649		fDrawingEngine->FillRegion(&dirtyBackground, (rgb_color){ 51, 102, 152, 255 });
650	}
651}
652