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