xref: /haiku/src/kits/interface/Dragger.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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(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 
161 BDragger::~BDragger()
162 {
163 	delete fPopUp;
164 	delete fBitmap;
165 }
166 
167 
168 BArchivable	*
169 BDragger::Instantiate(BMessage* data)
170 {
171 	if (validate_instantiation(data, "BDragger"))
172 		return new BDragger(data);
173 	return NULL;
174 }
175 
176 
177 status_t
178 BDragger::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 
207 void
208 BDragger::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 
225 void
226 BDragger::DetachedFromWindow()
227 {
228 	_RemoveFromList();
229 }
230 
231 
232 void
233 BDragger::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 
271 void
272 BDragger::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 
285 void
286 BDragger::MouseUp(BPoint point)
287 {
288 	BView::MouseUp(point);
289 }
290 
291 
292 void
293 BDragger::MouseMoved(BPoint point, uint32 code, const BMessage* msg)
294 {
295 	BView::MouseMoved(point, code, msg);
296 }
297 
298 
299 void
300 BDragger::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 
370 void
371 BDragger::FrameMoved(BPoint newPosition)
372 {
373 	BView::FrameMoved(newPosition);
374 }
375 
376 
377 void
378 BDragger::FrameResized(float newWidth, float newHeight)
379 {
380 	BView::FrameResized(newWidth, newHeight);
381 }
382 
383 
384 status_t
385 BDragger::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 
403 status_t
404 BDragger::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 
422 bool
423 BDragger::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 
444 BHandler*
445 BDragger::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 
452 status_t
453 BDragger::GetSupportedSuites(BMessage* data)
454 {
455 	return BView::GetSupportedSuites(data);
456 }
457 
458 
459 status_t
460 BDragger::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 
515 void
516 BDragger::ResizeToPreferred()
517 {
518 	BView::ResizeToPreferred();
519 }
520 
521 
522 void
523 BDragger::GetPreferredSize(float* _width, float* _height)
524 {
525 	BView::GetPreferredSize(_width, _height);
526 }
527 
528 
529 void
530 BDragger::MakeFocus(bool state)
531 {
532 	BView::MakeFocus(state);
533 }
534 
535 
536 void
537 BDragger::AllAttached()
538 {
539 	BView::AllAttached();
540 }
541 
542 
543 void
544 BDragger::AllDetached()
545 {
546 	BView::AllDetached();
547 }
548 
549 
550 status_t
551 BDragger::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 
563 BPopUpMenu*
564 BDragger::PopUp() const
565 {
566 	if (fPopUp == NULL && fTarget)
567 		const_cast<BDragger*>(this)->_BuildDefaultPopUp();
568 
569 	return fPopUp;
570 }
571 
572 
573 bool
574 BDragger::InShelf() const
575 {
576 	return fShelf != NULL;
577 }
578 
579 
580 BView*
581 BDragger::Target() const
582 {
583 	return fTarget;
584 }
585 
586 
587 BBitmap*
588 BDragger::DragBitmap(BPoint* offset, drawing_mode* mode)
589 {
590 	return NULL;
591 }
592 
593 
594 bool
595 BDragger::IsVisibilityChanging() const
596 {
597 	return fTransition;
598 }
599 
600 
601 void BDragger::_ReservedDragger2() {}
602 void BDragger::_ReservedDragger3() {}
603 void BDragger::_ReservedDragger4() {}
604 
605 
606 BDragger&
607 BDragger::operator=(const BDragger&)
608 {
609 	return *this;
610 }
611 
612 
613 /*static*/ void
614 BDragger::_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 
630 void
631 BDragger::_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 
638 void
639 BDragger::_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 
659 void
660 BDragger::_RemoveFromList()
661 {
662 	DraggerManager* manager = DraggerManager::Default();
663 	AutoLocker<DraggerManager> locker(manager);
664 	manager->list.RemoveItem(this);
665 }
666 
667 
668 status_t
669 BDragger::_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 
700 status_t
701 BDragger::_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 
715 void
716 BDragger::_SetShelf(BShelf* shelf)
717 {
718 	fShelf = shelf;
719 }
720 
721 
722 void
723 BDragger::_SetZombied(bool state)
724 {
725 	fIsZombie = state;
726 
727 	if (state) {
728 		SetLowColor(kZombieColor);
729 		SetViewColor(kZombieColor);
730 	}
731 }
732 
733 
734 void
735 BDragger::_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 
756 void
757 BDragger::_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 
778 extern "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