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
53DiagramItemGroup::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
68DiagramItemGroup::~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*/
105uint32
106DiagramItemGroup::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*/
135DiagramItem*
136DiagramItemGroup::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*/
170DiagramItem*
171DiagramItemGroup::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.
220bool
221DiagramItemGroup::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.
254bool
255DiagramItemGroup::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*/
300void
301DiagramItemGroup::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*/
332void
333DiagramItemGroup::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*/
369bool
370DiagramItemGroup::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*/
417uint32
418DiagramItemGroup::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
429uint32
430DiagramItemGroup::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*/
444DiagramItem*
445DiagramItemGroup::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*/
463bool
464DiagramItemGroup::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
504bool
505DiagramItemGroup::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
521bool
522DiagramItemGroup::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
538bool
539DiagramItemGroup::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*/
558void
559DiagramItemGroup::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*/
570void
571DiagramItemGroup::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
588void
589DiagramItemGroup::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
600void
601DiagramItemGroup::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
612void
613DiagramItemGroup::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
622BPoint
623DiagramItemGroup::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