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