xref: /haiku/src/apps/cortex/DiagramView/DiagramItemGroup.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
1 /*
2  * Copyright (c) 1999-2000, Eric Moon.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions, and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions, and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 
32 // DiagramItemGroup.cpp
33 
34 /*! \class DiagramItemGroup.
35 	\brief Basic class for managing and accessing DiagramItem objects.
36 
37 	Objects of this class can manage one or more of the DiagramItem
38 	type M_BOX, M_WIRE and M_ENDPOINT. Many methods let you specify
39 	which type of item you want to deal with.
40 */
41 
42 #include "DiagramItemGroup.h"
43 #include "DiagramItem.h"
44 
45 #include <Region.h>
46 
47 __USE_CORTEX_NAMESPACE
48 
49 #include <Debug.h>
50 #define D_METHOD(x) //PRINT (x)
51 
52 
53 DiagramItemGroup::DiagramItemGroup(uint32 acceptedTypes, bool multiSelection)
54 	:fBoxes(0),
55 	fWires(0),
56 	fEndPoints(0),
57 	fSelection(0),
58 	fTypes(acceptedTypes),
59 	fItemAlignment(1.0, 1.0),
60 	fMultiSelection(multiSelection),
61 	fLastItemUnder(0)
62 {
63 	D_METHOD(("DiagramItemGroup::DiagramItemGroup()\n"));
64 	fSelection = new BList(1);
65 }
66 
67 
68 DiagramItemGroup::~DiagramItemGroup()
69 {
70 	D_METHOD(("DiagramItemGroup::~DiagramItemGroup()\n"));
71 
72 	int32 count = 0;
73 	if (fWires && (fTypes & DiagramItem::M_WIRE)) {
74 		count = fWires->CountItems();
75 		for (int32 i = 0; i < count; ++i)
76 			delete static_cast<DiagramItem*>(fWires->ItemAt(i));
77 		delete fWires;
78 	}
79 
80 	if (fBoxes && (fTypes & DiagramItem::M_BOX)) {
81 		count = fBoxes->CountItems();
82 		for (int32 i = 0; i < count; ++i)
83 			delete static_cast<DiagramItem*>(fBoxes->ItemAt(i));
84 		delete fBoxes;
85 	}
86 
87 	if (fEndPoints && (fTypes & DiagramItem::M_ENDPOINT)) {
88 		count = fEndPoints->CountItems();
89 		for (int32 i = 0; i < count; ++i)
90 			delete static_cast<DiagramItem*>(fEndPoints->ItemAt(i));
91 		delete fEndPoints;
92 	}
93 
94 	if (fSelection)
95 		delete fSelection;
96 }
97 
98 
99 //	#pragma mark - item accessors
100 
101 
102 /*! Returns the number of items in the group (optionally only those
103 	of the given type \param whichType)
104 */
105 uint32
106 DiagramItemGroup::CountItems(uint32 whichType) const
107 {
108 	D_METHOD(("DiagramItemGroup::CountItems()\n"));
109 	uint32 count = 0;
110 	if (whichType & fTypes) {
111 		if (whichType & DiagramItem::M_BOX) {
112 			if (fBoxes)
113 				count += fBoxes->CountItems();
114 		}
115 
116 		if (whichType & DiagramItem::M_WIRE) {
117 			if (fWires)
118 				count += fWires->CountItems();
119 		}
120 
121 		if (whichType & DiagramItem::M_ENDPOINT) {
122 			if (fEndPoints)
123 				count += fEndPoints->CountItems();
124 		}
125 	}
126 
127 	return count;
128 }
129 
130 
131 /*! Returns a pointer to the item in the lists which is
132 	at the given index; if none is found, this function
133 	returns 0
134 */
135 DiagramItem*
136 DiagramItemGroup::ItemAt(uint32 index, uint32 whichType) const
137 {
138 	D_METHOD(("DiagramItemGroup::ItemAt()\n"));
139 	if (fTypes & whichType) {
140 		if (whichType & DiagramItem::M_BOX) {
141 			if (fBoxes && (index < CountItems(DiagramItem::M_BOX)))
142 				return static_cast<DiagramItem *>(fBoxes->ItemAt(index));
143 			else
144 				index -= CountItems(DiagramItem::M_BOX);
145 		}
146 
147 		if (whichType & DiagramItem::M_WIRE) {
148 			if (fWires && (index < CountItems(DiagramItem::M_WIRE)))
149 				return static_cast<DiagramItem *>(fWires->ItemAt(index));
150 			else
151 				index -= CountItems(DiagramItem::M_WIRE);
152 		}
153 
154 		if (whichType & DiagramItem::M_ENDPOINT) {
155 			if (fEndPoints && (index < CountItems(DiagramItem::M_ENDPOINT)))
156 				return static_cast<DiagramItem *>(fEndPoints->ItemAt(index));
157 		}
158 	}
159 
160 	return 0;
161 }
162 
163 
164 /*! This function returns the first box or endpoint found that
165 	contains the given \param point. For connections it looks at all
166 	wires that 'might' contain the point and calls their method
167 	howCloseTo() to find the one closest to the point.
168 	The lists should be sorted by selection time for proper results!
169 */
170 DiagramItem*
171 DiagramItemGroup::ItemUnder(BPoint point)
172 {
173 	D_METHOD(("DiagramItemGroup::ItemUnder()\n"));
174 	if (fTypes & DiagramItem::M_BOX) {
175 		for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) {
176 			DiagramItem *item = ItemAt(i, DiagramItem::M_BOX);
177 			if (item->Frame().Contains(point) && (item->howCloseTo(point) == 1.0)) {
178 				// DiagramItemGroup *group = dynamic_cast<DiagramItemGroup *>(item);
179 				return (fLastItemUnder = item);
180 			}
181 		}
182 	}
183 
184 	if (fTypes & DiagramItem::M_WIRE) {
185 		float closest = 0.0;
186 		DiagramItem *closestItem = 0;
187 		for (uint32 i = 0; i < CountItems(DiagramItem::M_WIRE); i++) {
188 			DiagramItem *item = ItemAt(i, DiagramItem::M_WIRE);
189 			if (item->Frame().Contains(point)) {
190 				float howClose = item->howCloseTo(point);
191 				if (howClose > closest) {
192 					closestItem = item;
193 					if (howClose == 1.0)
194 						return (fLastItemUnder = item);
195 					closest = howClose;
196 				}
197 			}
198 		}
199 
200 		if (closest > 0.5)
201 			return (fLastItemUnder = closestItem);
202 	}
203 
204 	if (fTypes & DiagramItem::M_ENDPOINT) {
205 		for (uint32 i = 0; i < CountItems(DiagramItem::M_ENDPOINT); i++) {
206 			DiagramItem *item = ItemAt(i, DiagramItem::M_ENDPOINT);
207 			if (item->Frame().Contains(point) && (item->howCloseTo(point) == 1.0))
208 				return (fLastItemUnder = item);
209 		}
210 	}
211 
212 	return (fLastItemUnder = 0); // no item was found!
213 }
214 
215 
216 //	#pragma mark - item operations
217 
218 
219 //! Adds an \param item to the group; returns true on success.
220 bool
221 DiagramItemGroup::AddItem(DiagramItem *item)
222 {
223 	D_METHOD(("DiagramItemGroup::AddItem()\n"));
224 	if (item && (fTypes & item->type())) {
225 		if (item->m_group)
226 			item->m_group->RemoveItem(item);
227 
228 		switch (item->type()) {
229 			case DiagramItem::M_BOX:
230 				if (!fBoxes)
231 					fBoxes = new BList();
232 				item->m_group = this;
233 				return fBoxes->AddItem(static_cast<void *>(item));
234 
235 			case DiagramItem::M_WIRE:
236 				if (!fWires)
237 					fWires = new BList();
238 				item->m_group = this;
239 				return fWires->AddItem(static_cast<void *>(item));
240 
241 			case DiagramItem::M_ENDPOINT:
242 				if (!fEndPoints)
243 					fEndPoints = new BList();
244 				item->m_group = this;
245 				return fEndPoints->AddItem(static_cast<void *>(item));
246 		}
247 	}
248 
249 	return false;
250 }
251 
252 
253 //! Removes an \param item from the group; returns true on success.
254 bool
255 DiagramItemGroup::RemoveItem(DiagramItem* item)
256 {
257 	D_METHOD(("DiagramItemGroup::RemoveItem()\n"));
258 	if (item && (fTypes & item->type())) {
259 		// reset the lastItemUnder-pointer if it pointed to this item
260 		if (fLastItemUnder == item)
261 			fLastItemUnder = 0;
262 
263 		// remove it from the selection list if it was selected
264 		if (item->isSelected())
265 			fSelection->RemoveItem(static_cast<void *>(item));
266 
267 		// try to remove the item from its list
268 		switch (item->type()) {
269 			case DiagramItem::M_BOX:
270 				if (fBoxes) {
271 					item->m_group = 0;
272 					return fBoxes->RemoveItem(static_cast<void *>(item));
273 				}
274 				break;
275 
276 			case DiagramItem::M_WIRE:
277 				if (fWires) {
278 					item->m_group = 0;
279 					return fWires->RemoveItem(static_cast<void *>(item));
280 				}
281 				break;
282 
283 			case DiagramItem::M_ENDPOINT:
284 				if (fEndPoints) {
285 					item->m_group = 0;
286 					return fEndPoints->RemoveItem(static_cast<void *>(item));
287 				}
288 		}
289 	}
290 
291 	return false;
292 }
293 
294 
295 /*! Performs a quicksort on a list of items with the provided
296 	compare function (one is already defined in the DiagramItem
297 	implementation); can't handle more than one item type at a
298 	time!
299 */
300 void
301 DiagramItemGroup::SortItems(uint32 whichType,
302 	int (*compareFunc)(const void *, const void *))
303 {
304 	D_METHOD(("DiagramItemGroup::SortItems()\n"));
305 	if ((whichType != DiagramItem::M_ANY) && (fTypes & whichType)) {
306 		switch (whichType) {
307 			case DiagramItem::M_BOX:
308 				if (fBoxes)
309 					fBoxes->SortItems(compareFunc);
310 				break;
311 
312 			case DiagramItem::M_WIRE:
313 				if (fWires)
314 					fWires->SortItems(compareFunc);
315 				break;
316 
317 			case DiagramItem::M_ENDPOINT:
318 				if (fEndPoints)
319 					fEndPoints->SortItems(compareFunc);
320 				break;
321 		}
322 	}
323 }
324 
325 
326 /*! Fires a Draw() command at all items of a specific type that
327 	intersect with the \param updateRect;
328 	items are drawn in reverse order; they should be sorted by
329 	selection time before this function gets called, so that
330 	the more recently selected item are drawn above others.
331 */
332 void
333 DiagramItemGroup::DrawItems(BRect updateRect, uint32 whichType, BRegion* updateRegion)
334 {
335 	D_METHOD(("DiagramItemGroup::DrawItems()\n"));
336 	if (whichType & DiagramItem::M_WIRE) {
337 		for (int32 i = CountItems(DiagramItem::M_WIRE) - 1; i >= 0; i--) {
338 			DiagramItem *item = ItemAt(i, DiagramItem::M_WIRE);
339 			if (item->Frame().Intersects(updateRect))
340 				item->Draw(updateRect);
341 		}
342 	}
343 
344 	if (whichType & DiagramItem::M_BOX) {
345 		for (int32 i = CountItems(DiagramItem::M_BOX) - 1; i >= 0; i--) {
346 			DiagramItem *item = ItemAt(i, DiagramItem::M_BOX);
347 			if (item && item->Frame().Intersects(updateRect)) {
348 				item->Draw(updateRect);
349 				if (updateRegion)
350 					updateRegion->Exclude(item->Frame());
351 			}
352 		}
353 	}
354 
355 	if (whichType & DiagramItem::M_ENDPOINT) {
356 		for (int32 i = CountItems(DiagramItem::M_ENDPOINT) - 1; i >= 0; i--) {
357 			DiagramItem *item = ItemAt(i, DiagramItem::M_ENDPOINT);
358 			if (item && item->Frame().Intersects(updateRect))
359 				item->Draw(updateRect);
360 		}
361 	}
362 }
363 
364 
365 /*!	Returns in outRegion the \param region of items that lay "over" the given
366 	DiagramItem in \param which; returns false if no items are above or the item
367 	doesn't exist.
368 */
369 bool
370 DiagramItemGroup::GetClippingAbove(DiagramItem *which, BRegion *region)
371 {
372 	D_METHOD(("DiagramItemGroup::GetClippingAbove()\n"));
373 	bool found = false;
374 	if (which && region) {
375 		switch (which->type()) {
376 			case DiagramItem::M_BOX:
377 			{
378 				int32 index = fBoxes->IndexOf(which);
379 				if (index >= 0) { // the item was found
380 					BRect r = which->Frame();
381 					for (int32 i = 0; i < index; i++) {
382 						DiagramItem *item = ItemAt(i, DiagramItem::M_BOX);
383 						if (item && item->Frame().Intersects(r)) {
384 							region->Include(item->Frame() & r);
385 							found = true;
386 						}
387 					}
388 				}
389 				break;
390 			}
391 
392 			case DiagramItem::M_WIRE:
393 			{
394 				BRect r = which->Frame();
395 				for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) {
396 					DiagramItem *item = ItemAt(i, DiagramItem::M_BOX);
397 					if (item && item->Frame().Intersects(r)) {
398 						region->Include(item->Frame() & r);
399 						found = true;
400 					}
401 				}
402 				break;
403 			}
404 		}
405 	}
406 
407 	return found;
408 }
409 
410 
411 //	#pragma mark - selection accessors
412 
413 
414 /*!	Returns the type of DiagramItems in the current selection
415 	(currently only one type at a time is supported!)
416 */
417 uint32
418 DiagramItemGroup::SelectedType() const
419 {
420 	D_METHOD(("DiagramItemGroup::SelectedType()\n"));
421 	if (CountSelectedItems() > 0)
422 		return SelectedItemAt(0)->type();
423 
424 	return 0;
425 }
426 
427 
428 //!	Returns the number of items in the current selection
429 uint32
430 DiagramItemGroup::CountSelectedItems() const
431 {
432 	D_METHOD(("DiagramItemGroup::CountSelectedItems()\n"));
433 	if (fSelection)
434 		return fSelection->CountItems();
435 
436 	return 0;
437 }
438 
439 
440 /*!	Returns a pointer to the item in the list which is
441 	at the given \param index; if none is found, this function
442 	returns 0
443 */
444 DiagramItem*
445 DiagramItemGroup::SelectedItemAt(uint32 index) const
446 {
447 	D_METHOD(("DiagramItemGroup::SelectedItemAt()\n"));
448 	if (fSelection)
449 		return static_cast<DiagramItem *>(fSelection->ItemAt(index));
450 
451 	return 0;
452 }
453 
454 
455 //	#pragma mark - selection related operations
456 
457 
458 /*!	Selects an item, optionally replacing the complete former
459 	selection. If the type of the item to be selected differs
460 	from the type of items currently selected, this methods
461 	automatically replaces the former selection
462 */
463 bool
464 DiagramItemGroup::SelectItem(DiagramItem* which, bool deselectOthers)
465 {
466 	D_METHOD(("DiagramItemGroup::SelectItem()\n"));
467 	bool selectionChanged = false;
468 	if (which && !which->isSelected() && which->isSelectable()) {
469 		// check if the item's type is the same as of the other
470 		// selected items
471 		if (fMultiSelection) {
472 			if (which->type() != SelectedType())
473 				deselectOthers = true;
474 		}
475 
476 		// check if the former selection has to be deselected
477 		if (deselectOthers || !fMultiSelection) {
478 			while (CountSelectedItems() > 0)
479 				DeselectItem(SelectedItemAt(0));
480 		}
481 
482 		// select the item
483 		if (deselectOthers || CountSelectedItems() == 0)
484 			which->select();
485 		else
486 			which->selectAdding();
487 
488 		fSelection->AddItem(which);
489 		selectionChanged = true;
490 	}
491 
492 	// resort the lists if necessary
493 	if (selectionChanged) {
494 		SortItems(which->type(), compareSelectionTime);
495 		SortSelectedItems(compareSelectionTime);
496 		return true;
497 	}
498 
499 	return false;
500 }
501 
502 
503 //!	Simply deselects one item
504 bool
505 DiagramItemGroup::DeselectItem(DiagramItem* which)
506 {
507 	D_METHOD(("DiagramItemGroup::DeselectItem()\n"));
508 	if (which && which->isSelected()) {
509 		fSelection->RemoveItem(which);
510 		which->deselect();
511 		SortItems(which->type(), compareSelectionTime);
512 		SortSelectedItems(compareSelectionTime);
513 		return true;
514 	}
515 
516 	return false;
517 }
518 
519 
520 //! Selects all items of the given \param itemType
521 bool
522 DiagramItemGroup::SelectAll(uint32 itemType)
523 {
524 	D_METHOD(("DiagramItemGroup::SelectAll()\n"));
525 	bool selectionChanged = false;
526 	if (fTypes & itemType) {
527 		for (uint32 i = 0; i < CountItems(itemType); i++) {
528 			if (SelectItem(ItemAt(i, itemType), false))
529 				selectionChanged = true;
530 		}
531 	}
532 
533 	return selectionChanged;
534 }
535 
536 
537 //! Deselects all items of the given \param itemType
538 bool
539 DiagramItemGroup::DeselectAll(uint32 itemType)
540 {
541 	D_METHOD(("DiagramItemGroup::DeselectAll()\n"));
542 	bool selectionChanged = false;
543 	if (fTypes & itemType) {
544 		for (uint32 i = 0; i < CountItems(itemType); i++) {
545 			if (DeselectItem(ItemAt(i, itemType)))
546 				selectionChanged = true;
547 		}
548 	}
549 
550 	return selectionChanged;
551 }
552 
553 
554 /*!	Performs a quicksort on the list of selected items with the
555 	provided compare function (one is already defined in the DiagramItem
556 	implementation)
557 */
558 void
559 DiagramItemGroup::SortSelectedItems(int (*compareFunc)(const void *, const void *))
560 {
561 	D_METHOD(("DiagramItemGroup::SortSelectedItems()\n"));
562 	fSelection->SortItems(compareFunc);
563 }
564 
565 
566 /*!	Moves all selected items by a given amount, taking
567 	item alignment into account; in updateRegion the areas
568 	that still require updating by the caller are returned
569 */
570 void
571 DiagramItemGroup::DragSelectionBy(float x, float y, BRegion* updateRegion)
572 {
573 	D_METHOD(("DiagramItemGroup::DragSelectionBy()\n"));
574 	if (SelectedType() == DiagramItem::M_BOX) {
575 		Align(&x, &y);
576 		if ((x != 0) || (y != 0)) {
577 			for (int32 i = CountSelectedItems() - 1; i >= 0; i--) {
578 				DiagramItem *item = dynamic_cast<DiagramItem *>(SelectedItemAt(i));
579 				if (item->isDraggable())
580 					item->MoveBy(x, y, updateRegion);
581 			}
582 		}
583 	}
584 }
585 
586 
587 //!	Removes all selected items from the group
588 void
589 DiagramItemGroup::RemoveSelection()
590 {
591 	D_METHOD(("DiagramItemGroup::RemoveSelection()\n"));
592 	for (uint32 i = 0; i < CountSelectedItems(); i++)
593 		RemoveItem(SelectedItemAt(i));
594 }
595 
596 
597 //	#pragma mark - alignment related accessors & operations
598 
599 
600 void
601 DiagramItemGroup::GetItemAlignment(float *horizontal, float *vertical)
602 {
603 	D_METHOD(("DiagramItemGroup::GetItemAlignment()\n"));
604 	if (horizontal)
605 		*horizontal = fItemAlignment.x;
606 	if (vertical)
607 		*vertical = fItemAlignment.y;
608 }
609 
610 
611 //! Align a given point(\param x, \param y) to the current grid
612 void
613 DiagramItemGroup::Align(float *x, float *y) const
614 {
615 	D_METHOD(("DiagramItemGroup::Align()\n"));
616 	*x = ((int)*x / (int)fItemAlignment.x) * fItemAlignment.x;
617 	*y = ((int)*y / (int)fItemAlignment.y) * fItemAlignment.y;
618 }
619 
620 
621 //! Align a given \param point to the current grid
622 BPoint
623 DiagramItemGroup::Align(BPoint point) const
624 {
625 	D_METHOD(("DiagramItemGroup::Align()\n"));
626 	float x = point.x, y = point.y;
627 	Align(&x, &y);
628 	return BPoint(x, y);
629 }
630