1/*
2 * Copyright 2001-2012, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Marc Flerackers (mflerackers@androme.be)
7 *		Rene Gollent (rene@gollent.com)
8 *		Alexandre Deckner (alex@zappotek.com)
9 */
10
11
12//!	BDragger represents a replicant "handle".
13
14
15#include <pthread.h>
16#include <stdio.h>
17#include <stdlib.h>
18
19#include <Alert.h>
20#include <Beep.h>
21#include <Bitmap.h>
22#include <Dragger.h>
23#include <MenuItem.h>
24#include <Message.h>
25#include <PopUpMenu.h>
26#include <Shelf.h>
27#include <SystemCatalog.h>
28#include <Window.h>
29
30#include <AutoLocker.h>
31
32#include <AppServerLink.h>
33#include <DragTrackingFilter.h>
34#include <binary_compatibility/Interface.h>
35#include <ServerProtocol.h>
36#include <ViewPrivate.h>
37
38#include "ZombieReplicantView.h"
39
40using BPrivate::gSystemCatalog;
41
42#undef B_TRANSLATION_CONTEXT
43#define B_TRANSLATION_CONTEXT "Dragger"
44
45#undef B_TRANSLATE
46#define B_TRANSLATE(str) \
47	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Dragger")
48
49
50static const uint32 kMsgDragStarted = 'Drgs';
51
52static const unsigned char kHandBitmap[] = {
53	255, 255,   0,   0,   0, 255, 255, 255,
54	255, 255,   0, 131, 131,   0, 255, 255,
55	  0,   0,   0,   0, 131, 131,   0,   0,
56	  0, 131,   0,   0, 131, 131,   0,   0,
57	  0, 131, 131, 131, 131, 131,   0,   0,
58	255,   0, 131, 131, 131, 131,   0,   0,
59	255, 255,   0,   0,   0,   0,   0,   0,
60	255, 255, 255, 255, 255, 255,   0,   0
61};
62
63
64namespace {
65
66struct DraggerManager {
67	bool	visible;
68	bool	visibleInitialized;
69	BList	list;
70
71	DraggerManager()
72		:
73		visible(false),
74		visibleInitialized(false),
75		fLock("BDragger static")
76	{
77	}
78
79	bool Lock()
80	{
81		return fLock.Lock();
82	}
83
84	void Unlock()
85	{
86		fLock.Unlock();
87	}
88
89	static DraggerManager* Default()
90	{
91		if (sDefaultInstance == NULL)
92			pthread_once(&sDefaultInitOnce, &_InitSingleton);
93
94		return sDefaultInstance;
95	}
96
97private:
98	static void _InitSingleton()
99	{
100		sDefaultInstance = new DraggerManager;
101	}
102
103private:
104	BLocker					fLock;
105
106	static pthread_once_t	sDefaultInitOnce;
107	static DraggerManager*	sDefaultInstance;
108};
109
110pthread_once_t DraggerManager::sDefaultInitOnce = PTHREAD_ONCE_INIT;
111DraggerManager* DraggerManager::sDefaultInstance = NULL;
112
113}	// unnamed namespace
114
115
116BDragger::BDragger(BRect frame, BView* target, uint32 resizingMode,
117	uint32 flags)
118	:
119	BView(frame, "_dragger_", resizingMode, flags),
120	fTarget(target),
121	fRelation(TARGET_UNKNOWN),
122	fShelf(NULL),
123	fTransition(false),
124	fIsZombie(false),
125	fErrCount(0),
126	fPopUpIsCustom(false),
127	fPopUp(NULL)
128{
129	_InitData();
130}
131
132
133BDragger::BDragger(BMessage* data)
134	:
135	BView(data),
136	fTarget(NULL),
137	fRelation(TARGET_UNKNOWN),
138	fShelf(NULL),
139	fTransition(false),
140	fIsZombie(false),
141	fErrCount(0),
142	fPopUpIsCustom(false),
143	fPopUp(NULL)
144{
145	data->FindInt32("_rel", (int32*)&fRelation);
146
147	_InitData();
148
149	BMessage popupMsg;
150	if (data->FindMessage("_popup", &popupMsg) == B_OK) {
151		BArchivable* archivable = instantiate_object(&popupMsg);
152
153		if (archivable) {
154			fPopUp = dynamic_cast<BPopUpMenu*>(archivable);
155			fPopUpIsCustom = true;
156		}
157	}
158}
159
160
161BDragger::~BDragger()
162{
163	delete fPopUp;
164	delete fBitmap;
165}
166
167
168BArchivable	*
169BDragger::Instantiate(BMessage* data)
170{
171	if (validate_instantiation(data, "BDragger"))
172		return new BDragger(data);
173	return NULL;
174}
175
176
177status_t
178BDragger::Archive(BMessage* data, bool deep) const
179{
180	status_t ret = BView::Archive(data, deep);
181	if (ret != B_OK)
182		return ret;
183
184	BMessage popupMsg;
185
186	if (fPopUp != NULL && fPopUpIsCustom) {
187		bool windowLocked = fPopUp->Window()->Lock();
188
189		ret = fPopUp->Archive(&popupMsg, deep);
190
191		if (windowLocked) {
192			fPopUp->Window()->Unlock();
193				// TODO: Investigate, in some (rare) occasions the menu window
194				//		 has already been unlocked
195		}
196
197		if (ret == B_OK)
198			ret = data->AddMessage("_popup", &popupMsg);
199	}
200
201	if (ret == B_OK)
202		ret = data->AddInt32("_rel", fRelation);
203	return ret;
204}
205
206
207void
208BDragger::AttachedToWindow()
209{
210	if (fIsZombie) {
211		SetLowColor(kZombieColor);
212		SetViewColor(kZombieColor);
213	} else {
214		SetLowColor(B_TRANSPARENT_COLOR);
215		SetViewColor(B_TRANSPARENT_COLOR);
216	}
217
218	_DetermineRelationship();
219	_AddToList();
220
221	AddFilter(new DragTrackingFilter(this, kMsgDragStarted));
222}
223
224
225void
226BDragger::DetachedFromWindow()
227{
228	_RemoveFromList();
229}
230
231
232void
233BDragger::Draw(BRect update)
234{
235	BRect bounds(Bounds());
236
237	if (AreDraggersDrawn() && (fShelf == NULL || fShelf->AllowsDragging())) {
238		if (Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) {
239			uint32 flags = Parent()->Flags();
240			Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN);
241			Parent()->Draw(Frame() & ConvertToParent(update));
242			Parent()->Flush();
243			Parent()->SetFlags(flags);
244		}
245
246		BPoint where = bounds.RightBottom() - BPoint(fBitmap->Bounds().Width(),
247			fBitmap->Bounds().Height());
248		SetDrawingMode(B_OP_OVER);
249		DrawBitmap(fBitmap, where);
250		SetDrawingMode(B_OP_COPY);
251
252		if (fIsZombie) {
253			// TODO: should draw it differently ?
254		}
255	} else if (IsVisibilityChanging()) {
256		if (Parent() != NULL) {
257			if ((Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) {
258				uint32 flags = Parent()->Flags();
259				Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN);
260				Parent()->Invalidate(Frame() & ConvertToParent(update));
261				Parent()->SetFlags(flags);
262			}
263		} else {
264			SetHighColor(255, 255, 255);
265			FillRect(bounds);
266		}
267	}
268}
269
270
271void
272BDragger::MouseDown(BPoint where)
273{
274	if (fTarget == NULL || !AreDraggersDrawn())
275		return;
276
277	uint32 buttons;
278	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
279
280	if (fShelf != NULL && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
281		_ShowPopUp(fTarget, where);
282}
283
284
285void
286BDragger::MouseUp(BPoint point)
287{
288	BView::MouseUp(point);
289}
290
291
292void
293BDragger::MouseMoved(BPoint point, uint32 code, const BMessage* msg)
294{
295	BView::MouseMoved(point, code, msg);
296}
297
298
299void
300BDragger::MessageReceived(BMessage* msg)
301{
302	switch (msg->what) {
303		case B_TRASH_TARGET:
304			if (fShelf != NULL)
305				Window()->PostMessage(kDeleteReplicant, fTarget, NULL);
306			else {
307				BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
308					B_TRANSLATE("Can't delete this replicant from its original "
309					"application. Life goes on."),
310					B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_FROM_WIDEST,
311					B_WARNING_ALERT);
312				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
313				alert->Go(NULL);
314			}
315			break;
316
317		case _SHOW_DRAG_HANDLES_:
318			// This code is used whenever the "are draggers drawn" option is
319			// changed.
320			if (fRelation == TARGET_IS_CHILD) {
321				fTransition = true;
322				Draw(Bounds());
323				Flush();
324				fTransition = false;
325			} else {
326				if ((fShelf != NULL && fShelf->AllowsDragging()
327						&& AreDraggersDrawn())
328					|| AreDraggersDrawn()) {
329					Show();
330				} else
331					Hide();
332			}
333			break;
334
335		case kMsgDragStarted:
336			if (fTarget != NULL) {
337				BMessage archive(B_ARCHIVED_OBJECT);
338
339				if (fRelation == TARGET_IS_PARENT)
340					fTarget->Archive(&archive);
341				else if (fRelation == TARGET_IS_CHILD)
342					Archive(&archive);
343				else if (fTarget->Archive(&archive)) {
344					BMessage archivedSelf(B_ARCHIVED_OBJECT);
345
346					if (Archive(&archivedSelf))
347						archive.AddMessage("__widget", &archivedSelf);
348				}
349
350				archive.AddInt32("be:actions", B_TRASH_TARGET);
351				BPoint offset;
352				drawing_mode mode;
353				BBitmap* bitmap = DragBitmap(&offset, &mode);
354				if (bitmap != NULL)
355					DragMessage(&archive, bitmap, mode, offset, this);
356				else {
357					DragMessage(&archive, ConvertFromScreen(
358						fTarget->ConvertToScreen(fTarget->Bounds())), this);
359				}
360			}
361			break;
362
363		default:
364			BView::MessageReceived(msg);
365			break;
366	}
367}
368
369
370void
371BDragger::FrameMoved(BPoint newPosition)
372{
373	BView::FrameMoved(newPosition);
374}
375
376
377void
378BDragger::FrameResized(float newWidth, float newHeight)
379{
380	BView::FrameResized(newWidth, newHeight);
381}
382
383
384status_t
385BDragger::ShowAllDraggers()
386{
387	BPrivate::AppServerLink link;
388	link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS);
389	link.Attach<bool>(true);
390
391	status_t status = link.Flush();
392	if (status == B_OK) {
393		DraggerManager* manager = DraggerManager::Default();
394		AutoLocker<DraggerManager> locker(manager);
395		manager->visible = true;
396		manager->visibleInitialized = true;
397	}
398
399	return status;
400}
401
402
403status_t
404BDragger::HideAllDraggers()
405{
406	BPrivate::AppServerLink link;
407	link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS);
408	link.Attach<bool>(false);
409
410	status_t status = link.Flush();
411	if (status == B_OK) {
412		DraggerManager* manager = DraggerManager::Default();
413		AutoLocker<DraggerManager> locker(manager);
414		manager->visible = false;
415		manager->visibleInitialized = true;
416	}
417
418	return status;
419}
420
421
422bool
423BDragger::AreDraggersDrawn()
424{
425	DraggerManager* manager = DraggerManager::Default();
426	AutoLocker<DraggerManager> locker(manager);
427
428	if (!manager->visibleInitialized) {
429		BPrivate::AppServerLink link;
430		link.StartMessage(AS_GET_SHOW_ALL_DRAGGERS);
431
432		status_t status;
433		if (link.FlushWithReply(status) == B_OK && status == B_OK) {
434			link.Read<bool>(&manager->visible);
435			manager->visibleInitialized = true;
436		} else
437			return false;
438	}
439
440	return manager->visible;
441}
442
443
444BHandler*
445BDragger::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
446	int32 form, const char* property)
447{
448	return BView::ResolveSpecifier(message, index, specifier, form, property);
449}
450
451
452status_t
453BDragger::GetSupportedSuites(BMessage* data)
454{
455	return BView::GetSupportedSuites(data);
456}
457
458
459status_t
460BDragger::Perform(perform_code code, void* _data)
461{
462	switch (code) {
463		case PERFORM_CODE_MIN_SIZE:
464			((perform_data_min_size*)_data)->return_value
465				= BDragger::MinSize();
466			return B_OK;
467		case PERFORM_CODE_MAX_SIZE:
468			((perform_data_max_size*)_data)->return_value
469				= BDragger::MaxSize();
470			return B_OK;
471		case PERFORM_CODE_PREFERRED_SIZE:
472			((perform_data_preferred_size*)_data)->return_value
473				= BDragger::PreferredSize();
474			return B_OK;
475		case PERFORM_CODE_LAYOUT_ALIGNMENT:
476			((perform_data_layout_alignment*)_data)->return_value
477				= BDragger::LayoutAlignment();
478			return B_OK;
479		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
480			((perform_data_has_height_for_width*)_data)->return_value
481				= BDragger::HasHeightForWidth();
482			return B_OK;
483		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
484		{
485			perform_data_get_height_for_width* data
486				= (perform_data_get_height_for_width*)_data;
487			BDragger::GetHeightForWidth(data->width, &data->min, &data->max,
488				&data->preferred);
489			return B_OK;
490}
491		case PERFORM_CODE_SET_LAYOUT:
492		{
493			perform_data_set_layout* data = (perform_data_set_layout*)_data;
494			BDragger::SetLayout(data->layout);
495			return B_OK;
496		}
497		case PERFORM_CODE_LAYOUT_INVALIDATED:
498		{
499			perform_data_layout_invalidated* data
500				= (perform_data_layout_invalidated*)_data;
501			BDragger::LayoutInvalidated(data->descendants);
502			return B_OK;
503		}
504		case PERFORM_CODE_DO_LAYOUT:
505		{
506			BDragger::DoLayout();
507			return B_OK;
508		}
509	}
510
511	return BView::Perform(code, _data);
512}
513
514
515void
516BDragger::ResizeToPreferred()
517{
518	BView::ResizeToPreferred();
519}
520
521
522void
523BDragger::GetPreferredSize(float* _width, float* _height)
524{
525	BView::GetPreferredSize(_width, _height);
526}
527
528
529void
530BDragger::MakeFocus(bool state)
531{
532	BView::MakeFocus(state);
533}
534
535
536void
537BDragger::AllAttached()
538{
539	BView::AllAttached();
540}
541
542
543void
544BDragger::AllDetached()
545{
546	BView::AllDetached();
547}
548
549
550status_t
551BDragger::SetPopUp(BPopUpMenu* menu)
552{
553	if (menu != NULL && menu != fPopUp) {
554		delete fPopUp;
555		fPopUp = menu;
556		fPopUpIsCustom = true;
557		return B_OK;
558	}
559	return B_ERROR;
560}
561
562
563BPopUpMenu*
564BDragger::PopUp() const
565{
566	if (fPopUp == NULL && fTarget)
567		const_cast<BDragger*>(this)->_BuildDefaultPopUp();
568
569	return fPopUp;
570}
571
572
573bool
574BDragger::InShelf() const
575{
576	return fShelf != NULL;
577}
578
579
580BView*
581BDragger::Target() const
582{
583	return fTarget;
584}
585
586
587BBitmap*
588BDragger::DragBitmap(BPoint* offset, drawing_mode* mode)
589{
590	return NULL;
591}
592
593
594bool
595BDragger::IsVisibilityChanging() const
596{
597	return fTransition;
598}
599
600
601void BDragger::_ReservedDragger2() {}
602void BDragger::_ReservedDragger3() {}
603void BDragger::_ReservedDragger4() {}
604
605
606BDragger&
607BDragger::operator=(const BDragger&)
608{
609	return *this;
610}
611
612
613/*static*/ void
614BDragger::_UpdateShowAllDraggers(bool visible)
615{
616	DraggerManager* manager = DraggerManager::Default();
617	AutoLocker<DraggerManager> locker(manager);
618
619	manager->visibleInitialized = true;
620	manager->visible = visible;
621
622	for (int32 i = manager->list.CountItems(); i-- > 0;) {
623		BDragger* dragger = (BDragger*)manager->list.ItemAt(i);
624		BMessenger target(dragger);
625		target.SendMessage(_SHOW_DRAG_HANDLES_);
626	}
627}
628
629
630void
631BDragger::_InitData()
632{
633	fBitmap = new BBitmap(BRect(0.0f, 0.0f, 7.0f, 7.0f), B_CMAP8, false, false);
634	fBitmap->SetBits(kHandBitmap, fBitmap->BitsLength(), 0, B_CMAP8);
635}
636
637
638void
639BDragger::_AddToList()
640{
641	DraggerManager* manager = DraggerManager::Default();
642	AutoLocker<DraggerManager> locker(manager);
643	manager->list.AddItem(this);
644
645	bool allowsDragging = true;
646	if (fShelf)
647		allowsDragging = fShelf->AllowsDragging();
648
649	if (!AreDraggersDrawn() || !allowsDragging) {
650		// The dragger is not shown - but we can't hide us in case we're the
651		// parent of the actual target view (because then you couldn't see
652		// it anymore).
653		if (fRelation != TARGET_IS_CHILD && !IsHidden())
654			Hide();
655	}
656}
657
658
659void
660BDragger::_RemoveFromList()
661{
662	DraggerManager* manager = DraggerManager::Default();
663	AutoLocker<DraggerManager> locker(manager);
664	manager->list.RemoveItem(this);
665}
666
667
668status_t
669BDragger::_DetermineRelationship()
670{
671	if (fTarget != NULL) {
672		if (fTarget == Parent())
673			fRelation = TARGET_IS_PARENT;
674		else if (fTarget == ChildAt(0))
675			fRelation = TARGET_IS_CHILD;
676		else
677			fRelation = TARGET_IS_SIBLING;
678	} else {
679		if (fRelation == TARGET_IS_PARENT)
680			fTarget = Parent();
681		else if (fRelation == TARGET_IS_CHILD)
682			fTarget = ChildAt(0);
683		else
684			return B_ERROR;
685	}
686
687	if (fRelation == TARGET_IS_PARENT) {
688		BRect bounds(Frame());
689		BRect parentBounds(Parent()->Bounds());
690		if (!parentBounds.Contains(bounds)) {
691			MoveTo(parentBounds.right - bounds.Width(),
692				parentBounds.bottom - bounds.Height());
693		}
694	}
695
696	return B_OK;
697}
698
699
700status_t
701BDragger::_SetViewToDrag(BView* target)
702{
703	if (target->Window() != Window())
704		return B_ERROR;
705
706	fTarget = target;
707
708	if (Window() != NULL)
709		_DetermineRelationship();
710
711	return B_OK;
712}
713
714
715void
716BDragger::_SetShelf(BShelf* shelf)
717{
718	fShelf = shelf;
719}
720
721
722void
723BDragger::_SetZombied(bool state)
724{
725	fIsZombie = state;
726
727	if (state) {
728		SetLowColor(kZombieColor);
729		SetViewColor(kZombieColor);
730	}
731}
732
733
734void
735BDragger::_BuildDefaultPopUp()
736{
737	fPopUp = new BPopUpMenu("Shelf", false, false, B_ITEMS_IN_COLUMN);
738
739	// About
740	BMessage* msg = new BMessage(B_ABOUT_REQUESTED);
741
742	const char* name = fTarget->Name();
743	if (name != NULL)
744		msg->AddString("target", name);
745
746	BString about(B_TRANSLATE("About %app" B_UTF8_ELLIPSIS));
747	about.ReplaceFirst("%app", name);
748
749	fPopUp->AddItem(new BMenuItem(about.String(), msg));
750	fPopUp->AddSeparatorItem();
751	fPopUp->AddItem(new BMenuItem(B_TRANSLATE("Remove replicant"),
752		new BMessage(kDeleteReplicant)));
753}
754
755
756void
757BDragger::_ShowPopUp(BView* target, BPoint where)
758{
759	BPoint point = ConvertToScreen(where);
760
761	if (fPopUp == NULL && fTarget != NULL)
762		_BuildDefaultPopUp();
763
764	fPopUp->SetTargetForItems(fTarget);
765
766	float menuWidth, menuHeight;
767	fPopUp->GetPreferredSize(&menuWidth, &menuHeight);
768	BRect rect(0, 0, menuWidth, menuHeight);
769	rect.InsetBy(-0.5, -0.5);
770	rect.OffsetTo(point);
771
772	fPopUp->Go(point, true, false, rect, true);
773}
774
775
776#if __GNUC__ < 3
777
778extern "C" BBitmap*
779_ReservedDragger1__8BDragger(BDragger* dragger, BPoint* offset,
780	drawing_mode* mode)
781{
782	return dragger->BDragger::DragBitmap(offset, mode);
783}
784
785#endif
786