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