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