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// DiagramView.cpp
33
34#include "DiagramView.h"
35#include "DiagramDefs.h"
36#include "DiagramBox.h"
37#include "DiagramEndPoint.h"
38#include "DiagramWire.h"
39
40#include <Bitmap.h>
41#include <Message.h>
42#include <ScrollBar.h>
43
44__USE_CORTEX_NAMESPACE
45
46#include <Debug.h>
47#define D_METHOD(x) //PRINT (x)
48#define D_HOOK(x) //PRINT (x)
49#define D_MESSAGE(x) //PRINT (x)
50#define D_MOUSE(x) //PRINT (x)
51#define D_DRAW(x) //PRINT (x)
52
53// -------------------------------------------------------- //
54// *** ctor/dtor
55// -------------------------------------------------------- //
56
57DiagramView::DiagramView(
58	BRect frame,
59	const char *name,
60	bool multiSelection,
61	uint32 resizingMode,
62	uint32 flags)
63	: BView(frame, name, resizingMode, B_WILL_DRAW | B_FRAME_EVENTS | flags),
64	  DiagramItemGroup(DiagramItem::M_BOX | DiagramItem::M_WIRE),
65	  m_lastButton(0),
66	  m_clickCount(0),
67	  m_lastClickPoint(-1.0, -1.0),
68	  m_pressedButton(0),
69	  m_draggedWire(0),
70	  m_useBackgroundBitmap(false),
71	  m_backgroundBitmap(0)
72{
73	D_METHOD(("DiagramView::DiagramView()\n"));
74	SetViewColor(B_TRANSPARENT_COLOR);
75	m_dataRect = Bounds();
76}
77
78DiagramView::~DiagramView()
79{
80	D_METHOD(("DiagramView::~DiagramView()\n"));
81}
82
83// -------------------------------------------------------- //
84// *** hook functions
85// -------------------------------------------------------- //
86
87void DiagramView::MessageDragged(
88	BPoint point,
89	uint32 transit,
90	const BMessage *message)
91{
92	D_METHOD(("DiagramView::MessageDragged()\n"));
93	switch (message->what)
94	{
95		case M_WIRE_DRAGGED:
96		{
97			D_MESSAGE(("DiagramView::MessageDragged(M_WIRE_DROPPED)\n"));
98			if (!m_draggedWire)
99			{
100				DiagramEndPoint *fromEndPoint;
101				if (message->FindPointer("from", reinterpret_cast<void **>(&fromEndPoint)) == B_OK)
102				{
103					_beginWireTracking(fromEndPoint);
104				}
105			}
106			trackWire(point);
107			break;
108		}
109	}
110}
111
112void DiagramView::MessageDropped(
113	BPoint point,
114	BMessage *message)
115{
116	D_METHOD(("DiagramView::MessageDropped()\n"));
117	switch (message->what)
118	{
119		case M_WIRE_DRAGGED:
120		{
121			D_MESSAGE(("DiagramView::MessageDropped(M_WIRE_DROPPED)\n"));
122			DiagramEndPoint *fromWhich = 0;
123			if (message->FindPointer("from", reinterpret_cast<void **>(&fromWhich)) == B_OK)
124			{
125				connectionAborted(fromWhich);
126			}
127			break;
128		}
129	}
130}
131
132// -------------------------------------------------------- //
133// *** derived from BView
134// -------------------------------------------------------- //
135
136// initial scrollbar update [e.moon 16nov99]
137void DiagramView::AttachedToWindow()
138{
139	D_METHOD(("DiagramView::AttachedToWindow()\n"));
140	_updateScrollBars();
141}
142
143void DiagramView::Draw(
144	BRect updateRect)
145{
146	D_METHOD(("DiagramView::Draw()\n"));
147	drawBackground(updateRect);
148	DrawItems(updateRect, DiagramItem::M_WIRE);
149	DrawItems(updateRect, DiagramItem::M_BOX);
150}
151
152void DiagramView::FrameResized(
153	float width,
154	float height)
155{
156	D_METHOD(("DiagramView::FrameResized()\n"));
157	_updateScrollBars();
158}
159
160void DiagramView::GetPreferredSize(
161	float *width,
162	float *height) {
163	D_HOOK(("DiagramView::GetPreferredSize()\n"));
164
165	*width = m_dataRect.Width() + 10.0;
166	*height = m_dataRect.Height() + 10.0;
167}
168
169void DiagramView::MessageReceived(
170	BMessage *message)
171{
172	D_METHOD(("DiagramView::MessageReceived()\n"));
173	switch (message->what)
174	{
175		case M_SELECTION_CHANGED:
176		{
177			D_MESSAGE(("DiagramView::MessageReceived(M_SELECTION_CHANGED)\n"));
178			DiagramItem *item;
179			if (message->FindPointer("item", reinterpret_cast<void **>(&item)) == B_OK)
180			{
181				bool deselectOthers = true;
182				message->FindBool("replace", &deselectOthers);
183				if (item->isSelected() && !deselectOthers && MultipleSelection())
184				{
185					if (DeselectItem(item))
186					{
187						SelectionChanged();
188					}
189				}
190				else if (SelectItem(item, deselectOthers))
191				{
192					SortItems(item->type(), &compareSelectionTime);
193					SelectionChanged();
194				}
195			}
196			break;
197		}
198		case M_WIRE_DROPPED:
199		{
200			D_MESSAGE(("DiagramView::MessageReceived(M_WIRE_DROPPED)\n"));
201			DiagramEndPoint *fromWhich = 0;
202			DiagramEndPoint *toWhich = 0;
203			bool success = false;
204			if (message->FindPointer("from", reinterpret_cast<void **>(&fromWhich)) == B_OK)
205			{
206				if ((message->FindPointer("to", reinterpret_cast<void **>(&toWhich)) == B_OK)
207				 && (message->FindBool("success", &success) == B_OK))
208				{
209					if (success && fromWhich && toWhich)
210					{
211						_endWireTracking();
212						DiagramWire *wire = createWire(fromWhich, toWhich);
213						if (wire && AddItem(wire))
214						{
215							connectionEstablished(fromWhich, toWhich);
216							break;
217						}
218					}
219				}
220			}
221			connectionAborted(fromWhich);
222			break;
223		}
224		default:
225		{
226			if (message->WasDropped())
227			{
228				BPoint point = ConvertFromScreen(message->DropPoint());
229				DiagramItem *item = ItemUnder(point);
230				if (item)
231				{
232					item->MessageDropped(point, message);
233					return;
234				}
235				else
236				{
237					MessageDropped(point, message);
238				}
239			}
240			else
241			{
242				BView::MessageReceived(message);
243			}
244		}
245	}
246}
247
248void DiagramView::KeyDown(
249	const char *bytes,
250	int32 numBytes)
251{
252	D_METHOD(("DiagramView::KeyDown()\n"));
253	switch (bytes[0])
254	{
255		case B_LEFT_ARROW:
256		{
257			float x;
258			GetItemAlignment(&x, 0);
259			BRegion updateRegion;
260			DragSelectionBy(-x, 0.0, &updateRegion);
261			for (int32 i = 0; i < updateRegion.CountRects(); i++)
262				Invalidate(updateRegion.RectAt(i));
263			updateDataRect();
264			break;
265		}
266		case B_RIGHT_ARROW:
267		{
268			float x;
269			GetItemAlignment(&x, 0);
270			BRegion updateRegion;
271			DragSelectionBy(x, 0.0, &updateRegion);
272			for (int32 i = 0; i < updateRegion.CountRects(); i++)
273				Invalidate(updateRegion.RectAt(i));
274			updateDataRect();
275			break;
276		}
277		case B_UP_ARROW:
278		{
279			float y;
280			GetItemAlignment(0, &y);
281			BRegion updateRegion;
282			DragSelectionBy(0.0, -y, &updateRegion);
283			for (int32 i = 0; i < updateRegion.CountRects(); i++)
284				Invalidate(updateRegion.RectAt(i));
285			updateDataRect();
286			break;
287		}
288		case B_DOWN_ARROW:
289		{
290			float y;
291			GetItemAlignment(0, &y);
292			BRegion updateRegion;
293			DragSelectionBy(0.0, y, &updateRegion);
294			for (int32 i = 0; i < updateRegion.CountRects(); i++)
295				Invalidate(updateRegion.RectAt(i));
296			updateDataRect();
297			break;
298		}
299		default:
300		{
301			BView::KeyDown(bytes, numBytes);
302			break;
303		}
304	}
305}
306
307void DiagramView::MouseDown(
308	BPoint point)
309{
310	D_METHOD(("DiagramView::MouseDown()\n"));
311
312	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
313
314	// update click count
315	BMessage* message = Window()->CurrentMessage();
316	int32 clicks = message->FindInt32("clicks");
317	int32 buttons = message->FindInt32("buttons");
318
319	bool moved = (fabs(point.x - m_lastClickPoint.x) > 2.0 || fabs(point.y - m_lastClickPoint.y) > 2.0);
320	if (!moved && (buttons == m_lastButton) && (clicks > 1))
321	{
322		m_clickCount++;
323	}
324	else
325	{
326		m_clickCount = 1;
327	}
328	m_lastButton = buttons;
329	m_lastClickPoint = point;
330	m_lastDragPoint = ConvertToScreen(point);
331
332	// [e.moon 16nov99] scroll on 3rd button
333	m_pressedButton = buttons;
334	if(m_pressedButton == B_TERTIARY_MOUSE_BUTTON) {
335		return;
336	}
337
338	// was an item clicked ?
339 	DiagramItem *item = ItemUnder(point);
340	if (item)
341	{
342		item->MouseDown(point, m_lastButton, m_clickCount);
343	}
344	else // no, the background was clicked
345	{
346		if (!(modifiers() & B_SHIFT_KEY) && DeselectAll(DiagramItem::M_ANY))
347			SelectionChanged();
348		if (MultipleSelection() && (m_lastButton == B_PRIMARY_MOUSE_BUTTON) && !(modifiers() & B_CONTROL_KEY))
349			_beginRectTracking(point);
350		BackgroundMouseDown(point, m_lastButton, m_clickCount);
351	}
352}
353
354void DiagramView::MouseMoved(
355	BPoint point,
356	uint32 transit,
357	const BMessage *message)
358{
359	D_METHOD(("DiagramView::MouseMoved()\n"));
360
361
362	// [e.moon 16nov99] 3rd-button scrolling
363	if(m_pressedButton == B_TERTIARY_MOUSE_BUTTON) {
364
365		// fetch/store screen point; calculate distance travelled
366		ConvertToScreen(&point);
367		float xDelta = m_lastDragPoint.x - point.x;
368		float yDelta = m_lastDragPoint.y - point.y;
369		m_lastDragPoint = point;
370
371		// constrain to scrollbar limits
372		BScrollBar* hScroll = ScrollBar(B_HORIZONTAL);
373		if(xDelta && hScroll) {
374			float val = hScroll->Value();
375			float min, max;
376			hScroll->GetRange(&min, &max);
377
378			if(val + xDelta < min)
379				xDelta = 0;
380
381			if(val + xDelta > max)
382				xDelta = 0;
383		}
384
385		BScrollBar* vScroll = ScrollBar(B_VERTICAL);
386		if(yDelta && vScroll) {
387			float val = vScroll->Value();
388			float min, max;
389			vScroll->GetRange(&min, &max);
390
391			if(val + yDelta < min)
392				yDelta = 0;
393
394			if(val + yDelta > max)
395				yDelta = 0;
396		}
397
398		// scroll
399		if(xDelta == 0.0 && yDelta == 0.0)
400			return;
401
402		ScrollBy(xDelta, yDelta);
403		 return;
404	}
405
406	if (message)
407	{
408		switch (message->what)
409		{
410			case M_RECT_TRACKING:
411			{
412				BPoint origin;
413				if (message->FindPoint("origin", &origin) == B_OK)
414				{
415					_trackRect(origin, point);
416				}
417				break;
418			}
419			case M_BOX_DRAGGED:
420			{
421				DiagramBox *box;
422				BPoint offset;
423				if ((message->FindPointer("item", reinterpret_cast<void **>(&box)) == B_OK)
424				 && (message->FindPoint("offset", &offset) == B_OK))
425				{
426					if (box)
427					{
428						BRegion updateRegion;
429						DragSelectionBy(point.x - box->Frame().left - offset.x,
430										point.y - box->Frame().top - offset.y,
431										&updateRegion);
432						updateDataRect();
433						for (int32 i = 0; i < updateRegion.CountRects(); i++)
434						{
435							Invalidate(updateRegion.RectAt(i));
436						}
437					}
438				}
439				break;
440			}
441			default: // unkwown message -> redirect to MessageDragged()
442			{
443				DiagramItem *last = _LastItemUnder();
444				if (transit == B_EXITED_VIEW)
445				{
446					if (last)
447					{
448						last->MessageDragged(point, B_EXITED_VIEW, message);
449						MessageDragged(point, B_EXITED_VIEW, message);
450					}
451				}
452				else
453				{
454					DiagramItem *item = ItemUnder(point);
455					if (item)
456					{
457						if (item != last)
458						{
459							if (last)
460								last->MessageDragged(point, B_EXITED_VIEW, message);
461							item->MessageDragged(point, B_ENTERED_VIEW, message);
462						}
463						else
464						{
465							item->MessageDragged(point, B_INSIDE_VIEW, message);
466						}
467					}
468					else if (last)
469					{
470						last->MessageDragged(point, B_EXITED_VIEW, message);
471						MessageDragged(point, B_ENTERED_VIEW, message);
472					}
473					else
474					{
475						MessageDragged(point, transit, message);
476					}
477				}
478				break;
479			}
480		}
481	}
482	else // no message at all -> redirect to MouseOver()
483	{
484		DiagramItem *last = _LastItemUnder();
485		if ((transit == B_EXITED_VIEW) || (transit == B_OUTSIDE_VIEW))
486		{
487			if (last)
488			{
489				last->MouseOver(point, B_EXITED_VIEW);
490				_ResetItemUnder();
491				MouseOver(point, B_EXITED_VIEW);
492			}
493		}
494		else
495		{
496			DiagramItem *item = ItemUnder(point);
497			if (item)
498			{
499				if (item != last)
500				{
501					if (last)
502						last->MouseOver(point, B_EXITED_VIEW);
503					item->MouseOver(point, B_ENTERED_VIEW);
504				}
505				else
506				{
507					item->MouseOver(point, B_INSIDE_VIEW);
508				}
509			}
510			else if (last)
511			{
512				last->MouseOver(point, B_EXITED_VIEW);
513				MouseOver(point, B_ENTERED_VIEW);
514			}
515		}
516	}
517}
518
519void DiagramView::MouseUp(
520	BPoint point)
521{
522	D_METHOD(("DiagramView::MouseUp()\n"));
523	if (MultipleSelection())
524		EndRectTracking();
525	_endWireTracking();
526
527	// [e.moon 16nov99] mark no button as down
528	m_pressedButton = 0;
529}
530
531// -------------------------------------------------------- //
532// *** derived from DiagramItemGroup (public)
533// -------------------------------------------------------- //
534
535bool DiagramView::AddItem(
536	DiagramItem *item)
537{
538	D_METHOD(("DiagramBox::AddItem()\n"));
539	if (item)
540	{
541		if (DiagramItemGroup::AddItem(item))
542		{
543			item->_SetOwner(this);
544			item->attachedToDiagram();
545			if (item->type() == DiagramItem::M_BOX)
546			{
547				updateDataRect();
548			}
549			return true;
550		}
551	}
552	return false;
553}
554
555bool DiagramView::RemoveItem(
556	DiagramItem *item)
557{
558	D_METHOD(("DiagramBox::RemoveItem()\n"));
559	if (item)
560	{
561		item->detachedFromDiagram();
562		if (DiagramItemGroup::RemoveItem(item))
563		{
564			item->_SetOwner(0);
565			if (item->type() == DiagramItem::M_BOX)
566			{
567				updateDataRect();
568			}
569			return true;
570		}
571	}
572	return false;
573}
574
575// -------------------------------------------------------- //
576// *** operations (public)
577// -------------------------------------------------------- //
578
579void DiagramView::trackWire(
580	BPoint point)
581{
582	D_MOUSE(("DiagramView::trackWire()\n"));
583	if (m_draggedWire)
584	{
585		BRegion region;
586		region.Include(m_draggedWire->Frame());
587		m_draggedWire->m_dragEndPoint = point;
588		m_draggedWire->endPointMoved();
589		region.Include(m_draggedWire->Frame());
590		region.Exclude(&m_boxRegion);
591		for (int32 i = 0; i < region.CountRects(); i++)
592		{
593			Invalidate(region.RectAt(i));
594		}
595	}
596}
597
598// -------------------------------------------------------- //
599// *** internal operations (protected)
600// -------------------------------------------------------- //
601
602void DiagramView::drawBackground(
603	BRect updateRect)
604{
605	D_METHOD(("DiagramView::drawBackground()\n"));
606	if (m_useBackgroundBitmap)
607	{
608		BRegion region;
609		region.Include(updateRect);
610		region.Exclude(&m_boxRegion);
611		BRect bounds = Bounds();
612		PushState();
613		{
614			ConstrainClippingRegion(&region);
615			for (float y = 0; y < bounds.bottom; y += m_backgroundBitmap->Bounds().Height())
616			{
617				for (float x = 0; x < bounds.right; x += m_backgroundBitmap->Bounds().Width())
618				{
619					DrawBitmapAsync(m_backgroundBitmap, BPoint(x, y));
620				}
621			}
622		}
623		PopState();
624	}
625	else
626	{
627		BRegion region;
628		region.Include(updateRect);
629		region.Exclude(&m_boxRegion);
630		PushState();
631		{
632			SetLowColor(m_backgroundColor);
633			FillRegion(&region, B_SOLID_LOW);
634		}
635		PopState();
636	}
637}
638
639void DiagramView::setBackgroundColor(
640	rgb_color color)
641{
642	D_METHOD(("DiagramView::setBackgroundColor()\n"));
643	m_backgroundColor = color;
644	m_useBackgroundBitmap = false;
645}
646
647
648void
649DiagramView::setBackgroundBitmap(BBitmap* bitmap)
650{
651	D_METHOD(("DiagramView::setBackgroundBitmap()\n"));
652	if (m_backgroundBitmap)
653		delete m_backgroundBitmap;
654
655	m_backgroundBitmap = new BBitmap(bitmap);
656	m_useBackgroundBitmap = true;
657}
658
659
660void
661DiagramView::updateDataRect()
662{
663	D_METHOD(("DiagramView::updateDataRect()\n"));
664	// calculate the area in which boxes display
665	m_boxRegion.MakeEmpty();
666	for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) {
667		m_boxRegion.Include(ItemAt(i, DiagramItem::M_BOX)->Frame());
668	}
669	// adapt the data rect to the new region of boxes
670	BRect boxRect = m_boxRegion.Frame();
671	m_dataRect.right = boxRect.right;
672	m_dataRect.bottom = boxRect.bottom;
673	// update the scroll bars
674	_updateScrollBars();
675}
676
677
678//	#pragma mark - internal operations (private)
679
680
681void
682DiagramView::_beginWireTracking(DiagramEndPoint *startPoint)
683{
684	D_METHOD(("DiagramView::beginWireTracking()\n"));
685	m_draggedWire = createWire(startPoint);
686	AddItem(m_draggedWire);
687	SelectItem(m_draggedWire, true);
688	Invalidate(startPoint->Frame());
689}
690
691void DiagramView::_endWireTracking()
692{
693	D_METHOD(("DiagramView::endWireTracking()\n"));
694	if (m_draggedWire)
695	{
696		RemoveItem(m_draggedWire);
697		Invalidate(m_draggedWire->Frame());
698		DiagramEndPoint *endPoint = m_draggedWire->startPoint();
699		if (!endPoint)
700		{
701			endPoint = m_draggedWire->endPoint();
702		}
703		if (endPoint)
704		{
705			Invalidate(endPoint->Frame());
706		}
707		delete m_draggedWire;
708		m_draggedWire = 0;
709	}
710}
711
712void DiagramView::_beginRectTracking(
713	BPoint origin)
714{
715	D_METHOD(("DiagramView::beginRectTracking()\n"));
716	BMessage message(M_RECT_TRACKING);
717	message.AddPoint("origin", origin);
718	DragMessage(&message, BRect(0.0, 0.0, -1.0, -1.0));
719	BView::BeginRectTracking(BRect(origin, origin), B_TRACK_RECT_CORNER);
720}
721
722
723void
724DiagramView::_trackRect(BPoint origin, BPoint current)
725{
726	D_METHOD(("DiagramView::trackRect()\n"));
727	bool changed = false;
728	BRect rect;
729	rect.left = origin.x < current.x ? origin.x : current.x;
730	rect.top = origin.y < current.y ? origin.y : current.y;
731	rect.right = origin.x < current.x ? current.x : origin.x;
732	rect.bottom = origin.y < current.y ? current.y : origin.y;
733	for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) {
734		DiagramBox *box = dynamic_cast<DiagramBox *>(ItemAt(i, DiagramItem::M_BOX));
735		if (box) {
736			if (rect.Intersects(box->Frame()))
737				changed  |= SelectItem(box, false);
738			else
739				changed |= DeselectItem(box);
740		}
741	}
742
743	if (changed) {
744		SortItems(DiagramItem::M_BOX, &compareSelectionTime);
745		SelectionChanged();
746	}
747}
748
749
750void
751DiagramView::_updateScrollBars()
752{
753	D_METHOD(("DiagramView::_updateScrollBars()\n"));
754	// fetch the vertical ScrollBar
755	BScrollBar *scrollBar = ScrollBar(B_VERTICAL);
756	if (scrollBar)
757	{
758		 // Disable the ScrollBar if the view is large enough to display
759		 // the entire data-rect
760		if (Bounds().Height() > m_dataRect.Height())
761		{
762			scrollBar->SetRange(0.0, 0.0);
763			scrollBar->SetProportion(1.0);
764		}
765		else
766		{
767			scrollBar->SetRange(m_dataRect.top, m_dataRect.bottom - Bounds().Height());
768			scrollBar->SetProportion(Bounds().Height() / m_dataRect.Height());
769		}
770	}
771	scrollBar = ScrollBar(B_HORIZONTAL);
772	if (scrollBar)
773	{
774		 // Disable the ScrollBar if the view is large enough to display
775		 // the entire data-rect
776		if (Bounds().Width() > m_dataRect.Width())
777		{
778			scrollBar->SetRange(0.0, 0.0);
779			scrollBar->SetProportion(1.0);
780		}
781		else
782		{
783			scrollBar->SetRange(m_dataRect.left, m_dataRect.right - Bounds().Width());
784			scrollBar->SetProportion(Bounds().Width() / m_dataRect.Width());
785		}
786	}
787}
788
789// END -- DiagramView.cpp --
790