xref: /haiku/src/kits/interface/Dragger.cpp (revision 5ac9b506412b11afb993bb52d161efe7666958a5)
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 
40 using 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 
50 static const uint32 kMsgDragStarted = 'Drgs';
51 
52 static 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 
64 namespace {
65 
66 struct 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 
97 private:
98 	static void _InitSingleton()
99 	{
100 		sDefaultInstance = new DraggerManager;
101 	}
102 
103 private:
104 	BLocker					fLock;
105 
106 	static pthread_once_t	sDefaultInitOnce;
107 	static DraggerManager*	sDefaultInstance;
108 };
109 
110 pthread_once_t DraggerManager::sDefaultInitOnce = PTHREAD_ONCE_INIT;
111 DraggerManager* DraggerManager::sDefaultInstance = NULL;
112 
113 }	// unnamed namespace
114 
115 
116 BDragger::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 
133 BDragger::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 
149 BDragger::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 
177 BDragger::~BDragger()
178 {
179 	delete fPopUp;
180 	delete fBitmap;
181 }
182 
183 
184 BArchivable	*
185 BDragger::Instantiate(BMessage* data)
186 {
187 	if (validate_instantiation(data, "BDragger"))
188 		return new BDragger(data);
189 	return NULL;
190 }
191 
192 
193 status_t
194 BDragger::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 
223 void
224 BDragger::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 
241 void
242 BDragger::DetachedFromWindow()
243 {
244 	_RemoveFromList();
245 }
246 
247 
248 void
249 BDragger::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 
289 void
290 BDragger::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 
303 void
304 BDragger::MouseUp(BPoint point)
305 {
306 	BView::MouseUp(point);
307 }
308 
309 
310 void
311 BDragger::MouseMoved(BPoint point, uint32 code, const BMessage* msg)
312 {
313 	BView::MouseMoved(point, code, msg);
314 }
315 
316 
317 void
318 BDragger::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 
388 void
389 BDragger::FrameMoved(BPoint newPosition)
390 {
391 	BView::FrameMoved(newPosition);
392 }
393 
394 
395 void
396 BDragger::FrameResized(float newWidth, float newHeight)
397 {
398 	BView::FrameResized(newWidth, newHeight);
399 }
400 
401 
402 status_t
403 BDragger::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 
421 status_t
422 BDragger::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 
440 bool
441 BDragger::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 
462 BHandler*
463 BDragger::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 
470 status_t
471 BDragger::GetSupportedSuites(BMessage* data)
472 {
473 	return BView::GetSupportedSuites(data);
474 }
475 
476 
477 status_t
478 BDragger::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 
533 void
534 BDragger::ResizeToPreferred()
535 {
536 	BView::ResizeToPreferred();
537 }
538 
539 
540 void
541 BDragger::GetPreferredSize(float* _width, float* _height)
542 {
543 	BView::GetPreferredSize(_width, _height);
544 }
545 
546 
547 void
548 BDragger::MakeFocus(bool state)
549 {
550 	BView::MakeFocus(state);
551 }
552 
553 
554 void
555 BDragger::AllAttached()
556 {
557 	BView::AllAttached();
558 }
559 
560 
561 void
562 BDragger::AllDetached()
563 {
564 	BView::AllDetached();
565 }
566 
567 
568 status_t
569 BDragger::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 
581 BPopUpMenu*
582 BDragger::PopUp() const
583 {
584 	if (fPopUp == NULL && fTarget)
585 		const_cast<BDragger*>(this)->_BuildDefaultPopUp();
586 
587 	return fPopUp;
588 }
589 
590 
591 bool
592 BDragger::InShelf() const
593 {
594 	return fShelf != NULL;
595 }
596 
597 
598 BView*
599 BDragger::Target() const
600 {
601 	return fTarget;
602 }
603 
604 
605 BBitmap*
606 BDragger::DragBitmap(BPoint* offset, drawing_mode* mode)
607 {
608 	return NULL;
609 }
610 
611 
612 bool
613 BDragger::IsVisibilityChanging() const
614 {
615 	return fTransition;
616 }
617 
618 
619 void BDragger::_ReservedDragger2() {}
620 void BDragger::_ReservedDragger3() {}
621 void BDragger::_ReservedDragger4() {}
622 
623 
624 BDragger&
625 BDragger::operator=(const BDragger&)
626 {
627 	return *this;
628 }
629 
630 
631 /*static*/ void
632 BDragger::_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 
648 void
649 BDragger::_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 
656 void
657 BDragger::_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 
677 void
678 BDragger::_RemoveFromList()
679 {
680 	DraggerManager* manager = DraggerManager::Default();
681 	AutoLocker<DraggerManager> locker(manager);
682 	manager->list.RemoveItem(this);
683 }
684 
685 
686 status_t
687 BDragger::_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 
718 status_t
719 BDragger::_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 
733 void
734 BDragger::_SetShelf(BShelf* shelf)
735 {
736 	fShelf = shelf;
737 }
738 
739 
740 void
741 BDragger::_SetZombied(bool state)
742 {
743 	fIsZombie = state;
744 
745 	if (state) {
746 		SetLowColor(kZombieColor);
747 		SetViewColor(kZombieColor);
748 	}
749 }
750 
751 
752 void
753 BDragger::_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 
774 void
775 BDragger::_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 
796 extern "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