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