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