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