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