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