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