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