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// ValControl.cpp
33
34#include "ValControl.h"
35#include "ValControlSegment.h"
36
37#include "TextControlFloater.h"
38
39#include <Debug.h>
40#include <String.h>
41#include <Window.h>
42
43#include <algorithm>
44#include <functional>
45#include <cstdio>
46
47using namespace std;
48
49__USE_CORTEX_NAMESPACE
50
51
52const float ValControl::fSegmentPadding = 2.0;
53
54// the decimal point covers one more pixel x and y-ward:
55const float ValControl::fDecimalPointWidth = 2.0;
56const float ValControl::fDecimalPointHeight = 2.0;
57
58
59/*protected*/
60ValControl::ValControl(BRect frame, const char* name, const char* label,
61		BMessage* message, align_mode alignMode, align_flags alignFlags,
62		update_mode updateMode, bool backBuffer)
63	: BControl(frame, name, label, message, B_FOLLOW_TOP|B_FOLLOW_LEFT,
64		B_WILL_DRAW|B_FRAME_EVENTS),
65	fDirty(true),
66	fUpdateMode(updateMode),
67	fLabelFont(be_bold_font),
68	fValueFont(be_bold_font),
69	fAlignMode(alignMode),
70	fAlignFlags(alignFlags),
71	fOrigBounds(Bounds()),
72	fHaveBackBuffer(backBuffer),
73	fBackBuffer(NULL),
74	fBackBufferView(NULL)
75{
76	if (fHaveBackBuffer)
77		_AllocBackBuffer(frame.Width(), frame.Height());
78
79//	m_font.SetSize(13.0);
80//	rgb_color red = {255,0,0,255};
81//	SetViewColor(red);
82}
83
84
85ValControl::~ValControl()
86{
87	delete fBackBuffer;
88}
89
90
91ValControl::update_mode
92ValControl::updateMode() const
93{
94	return fUpdateMode;
95}
96
97
98void
99ValControl::setUpdateMode(update_mode mode)
100{
101	fUpdateMode = mode;
102}
103
104
105const BFont*
106ValControl::labelFont() const
107{
108	return &fLabelFont;
109}
110
111
112void
113ValControl::setLabelFont(const BFont* labelFont)
114{
115	fLabelFont = labelFont;
116	// inform label segments
117	_InvalidateAll();
118}
119
120
121const BFont*
122ValControl::valueFont() const
123{
124	return &fValueFont;
125}
126
127
128void
129ValControl::setValueFont(const BFont* valueFont)
130{
131	fValueFont = valueFont;
132
133	// inform value segments
134	for (int n = CountEntries(); n > 0; --n) {
135		const ValCtrlLayoutEntry& e = _EntryAt(n-1);
136		if (e.type != ValCtrlLayoutEntry::SEGMENT_ENTRY)
137			continue;
138
139		ValControlSegment* s = dynamic_cast<ValControlSegment*>(e.pView);
140		ASSERT(s);
141		s->SetFont(&fValueFont);
142		s->fontChanged(&fValueFont);
143	}
144}
145
146
147float
148ValControl::baselineOffset() const
149{
150	font_height h;
151	be_plain_font->GetHeight(&h);
152	return ceil(h.ascent);
153}
154
155
156float
157ValControl::segmentPadding() const
158{
159	return fSegmentPadding;
160}
161
162
163BView*
164ValControl::backBufferView() const
165{
166	return fBackBufferView;
167}
168
169
170BBitmap*
171ValControl::backBuffer() const
172{
173	return fBackBuffer;
174}
175
176
177void
178ValControl::dump()
179{
180#if defined(DEBUG)
181	BRect f = Frame();
182
183	PRINT((
184		"*** ValControl::dump():\n"
185		"    FRAME    (%.1f,%.1f)-(%.1f,%.1f)\n"
186		"    ENTRIES:\n",
187		f.left, f.top, f.right, f.bottom));
188
189	for (layout_set::const_iterator it = fLayoutSet.begin();
190		it != fLayoutSet.end(); ++it) {
191		const ValCtrlLayoutEntry& e = *it;
192		switch (e.type) {
193			case ValCtrlLayoutEntry::SEGMENT_ENTRY:
194				PRINT(("    Segment       "));
195				break;
196
197			case ValCtrlLayoutEntry::VIEW_ENTRY:
198				PRINT(("    View          "));
199				break;
200
201			case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
202				PRINT(("    Decimal Point "));
203				break;
204
205			default:
206				PRINT(("    ???           "));
207				break;
208		}
209
210		PRINT(("\n      cached frame (%.1f,%.1f)-(%.1f,%.1f) + pad(%.1f)\n",
211			e.frame.left, e.frame.top, e.frame.right, e.frame.bottom,
212			e.fPadding));
213
214		if (e.type == ValCtrlLayoutEntry::SEGMENT_ENTRY
215			|| e.type == ValCtrlLayoutEntry::VIEW_ENTRY) {
216			if (e.pView) {
217				PRINT(("      real frame   (%.1f,%.1f)-(%.1f,%.1f)\n\n",
218					e.pView->Frame().left, e.pView->Frame().top,
219					e.pView->Frame().right, e.pView->Frame().bottom));
220			} else
221				PRINT(("      (no view!)\n\n"));
222		}
223	}
224	PRINT(("\n"));
225#endif
226}
227
228
229void
230ValControl::SetEnabled(bool enabled)
231{
232	// redraw if enabled-state changes
233	_Inherited::SetEnabled(enabled);
234
235	_InvalidateAll();
236}
237
238
239void
240ValControl::_InvalidateAll()
241{
242	Invalidate();
243	int c = CountChildren();
244	for (int n = 0; n < c; ++n)
245		ChildAt(n)->Invalidate();
246}
247
248
249void
250ValControl::AttachedToWindow()
251{
252	// adopt parent view's color
253	if (Parent())
254		SetViewColor(Parent()->ViewColor());
255}
256
257
258void
259ValControl::AllAttached()
260{
261	// move children to requested positions
262	BWindow* pWnd = Window();
263	pWnd->BeginViewTransaction();
264
265	for_each(fLayoutSet.begin(), fLayoutSet.end(),
266#if __GNUC__ <= 2
267		ptr_fun(&ValCtrlLayoutEntry::InitChildFrame)
268#else
269		[](ValCtrlLayoutEntry& entry) { ValCtrlLayoutEntry::InitChildFrame(entry); }
270#endif
271	);
272
273	pWnd->EndViewTransaction();
274}
275
276
277//! Paint decorations (& decimal point)
278void
279ValControl::Draw(BRect updateRect)
280{
281	// draw lightweight entries:
282	for (unsigned int n = 0; n < fLayoutSet.size(); n++) {
283		if (fLayoutSet[n].type == ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY)
284			drawDecimalPoint(fLayoutSet[n]);
285	}
286}
287
288
289void
290ValControl::drawDecimalPoint(ValCtrlLayoutEntry& e)
291{
292	rgb_color dark = {0, 0, 0, 255};
293	rgb_color med = {200, 200, 200, 255};
294//	rgb_color light = {244,244,244,255};
295
296	BPoint center;
297	center.x = e.frame.left + 1;
298	center.y = baselineOffset() - 1;
299
300	SetHighColor(dark);
301	StrokeLine(center, center);
302	SetHighColor(med);
303	StrokeLine(center - BPoint(0, 1), center + BPoint(1, 0));
304	StrokeLine(center - BPoint(1, 0), center + BPoint(0, 1));
305
306//	SetHighColor(light);
307//	StrokeLine(center+BPoint(-1,1), center+BPoint(-1,1));
308//	StrokeLine(center+BPoint(1,1), center+BPoint(1,1));
309//	StrokeLine(center+BPoint(-1,-1), center+BPoint(-1,-1));
310//	StrokeLine(center+BPoint(1,-1), center+BPoint(1,-1));
311}
312
313
314void
315ValControl::FrameResized(float width, float height)
316{
317	_Inherited::FrameResized(width,height);
318	if (fHaveBackBuffer)
319		_AllocBackBuffer(width, height);
320//
321//	PRINT((
322//		"# ValControl::FrameResized(): %.1f, %.1f\n",
323//		width, height));
324}
325
326
327void
328ValControl::GetPreferredSize(float* outWidth, float* outHeight)
329{
330	ASSERT(fLayoutSet.size() > 0);
331
332	*outWidth =
333		fLayoutSet.back().frame.right -
334		fLayoutSet.front().frame.left;
335
336	*outHeight = 0;
337	for(layout_set::const_iterator it = fLayoutSet.begin();
338		it != fLayoutSet.end(); ++it) {
339		if((*it).frame.Height() > *outHeight)
340			*outHeight = (*it).frame.Height();
341	}
342//
343//	PRINT((
344//		"# ValControl::GetPreferredSize(): %.1f, %.1f\n",
345//		*outWidth, *outHeight));
346}
347
348
349void
350ValControl::MakeFocus(bool focused)
351{
352	_Inherited::MakeFocus(focused);
353
354	// +++++ only the underline needs to be redrawn
355	_InvalidateAll();
356}
357
358
359void
360ValControl::MouseDown(BPoint where)
361{
362	MakeFocus(true);
363}
364
365
366void
367ValControl::MessageReceived(BMessage* message)
368{
369	status_t err;
370	const char* stringValue;
371
372//	PRINT((
373//		"ValControl::MessageReceived():\n"));
374//	message->PrintToStream();
375
376	switch (message->what) {
377		case M_SET_VALUE:
378			err = message->FindString("_value", &stringValue);
379			if(err < B_OK) {
380				PRINT((
381					"! ValControl::MessageReceived(): no _value found!\n"));
382				break;
383			}
384
385			// set from string
386			err = setValueFrom(stringValue);
387			if (err < B_OK) {
388				PRINT((
389					"! ValControl::MessageReceived(): setValueFrom('%s'):\n"
390					"  %s\n",
391					stringValue,
392					strerror(err)));
393			}
394
395			// +++++ broadcast new value +++++ [23aug99]
396			break;
397
398		case M_GET_VALUE: // +++++
399			break;
400
401		default:
402			_Inherited::MessageReceived(message);
403	}
404}
405
406
407// -------------------------------------------------------- //
408// archiving/instantiation
409// -------------------------------------------------------- //
410
411ValControl::ValControl(BMessage* archive)
412	: BControl(archive),
413	fDirty(true)
414{
415	// fetch parameters
416	archive->FindInt32("updateMode", (int32*)&fUpdateMode);
417	archive->FindInt32("alignMode", (int32*)&fAlignMode);
418	archive->FindInt32("alignFlags", (int32*)&fAlignFlags);
419
420	// original bounds
421	archive->FindRect("origBounds", &fOrigBounds);
422}
423
424
425status_t
426ValControl::Archive(BMessage* archive, bool deep) const
427{
428	status_t err = _Inherited::Archive(archive, deep);
429
430	// write parameters
431	if (err == B_OK)
432		err = archive->AddInt32("updateMode", (int32)fUpdateMode);
433	if (err == B_OK)
434		err = archive->AddInt32("alignMode", (int32)fAlignMode);
435	if (err == B_OK)
436		err = archive->AddInt32("alignFlags", (int32)fAlignFlags);
437	if (err == B_OK)
438		err = archive->AddRect("origBounds", fOrigBounds);
439	if (err < B_OK)
440		return err;
441
442	// write layout set?
443	if (!deep)
444		return B_OK;
445
446	// yes; spew it:
447	for (layout_set::const_iterator it = fLayoutSet.begin();
448		it != fLayoutSet.end(); it++) {
449
450		// archive entry
451		BMessage layoutSet;
452		ASSERT((*it).pView);
453		err = (*it).pView->Archive(&layoutSet, true);
454		ASSERT(err == B_OK);
455
456		// write it
457		archive->AddMessage("layoutSet", &layoutSet);
458	}
459
460	return B_OK;
461}
462
463
464// -------------------------------------------------------- //
465// internal operations
466// -------------------------------------------------------- //
467
468// add segment view (which is responsible for generating its
469// own ValCtrlLayoutEntry)
470void
471ValControl::_Add(ValControlSegment* segment, entry_location from,
472	uint16 distance)
473{
474	BWindow* pWnd = Window();
475	if(pWnd)
476		pWnd->BeginViewTransaction();
477
478	AddChild(segment);
479
480	segment->SetFont(&fValueFont);
481	segment->fontChanged(&fValueFont);
482
483	uint16 nIndex = _LocationToIndex(from, distance);
484	ValCtrlLayoutEntry entry = segment->makeLayoutEntry();
485	_InsertEntry(entry, nIndex);
486//	linkSegment(segment, nIndex);
487
488	if (pWnd)
489		pWnd->EndViewTransaction();
490}
491
492
493// add general view (manipulator, label, etc.)
494// the entry's frame rectangle will be filled in
495void
496ValControl::_Add(ValCtrlLayoutEntry& entry, entry_location from)
497{
498	BWindow* window = Window();
499	if (window)
500		window->BeginViewTransaction();
501
502	if (entry.pView)
503		AddChild(entry.pView);
504
505	uint16 index = _LocationToIndex(from, 0);
506	_InsertEntry(entry, index);
507
508	if (window)
509		window->EndViewTransaction();
510}
511
512
513// access child-view ValCtrlLayoutEntry
514// (_IndexOf returns index from left)
515const ValCtrlLayoutEntry&
516ValControl::_EntryAt(entry_location from, uint16 distance) const
517{
518	uint16 nIndex = _LocationToIndex(from, distance);
519	ASSERT(nIndex < fLayoutSet.size());
520	return fLayoutSet[nIndex];
521}
522
523
524const ValCtrlLayoutEntry&
525ValControl::_EntryAt(uint16 offset) const
526{
527	uint16 nIndex = _LocationToIndex(FROM_LEFT, offset);
528	ASSERT(nIndex < fLayoutSet.size());
529	return fLayoutSet[nIndex];
530}
531
532
533uint16
534ValControl::_IndexOf(BView* child) const
535{
536	for (uint16 n = 0; n < fLayoutSet.size(); n++) {
537		if (fLayoutSet[n].pView == child)
538			return n;
539	}
540
541	ASSERT(!"shouldn't be here");
542	return 0;
543}
544
545
546uint16
547ValControl::CountEntries() const
548{
549	return fLayoutSet.size();
550}
551
552
553// pop up keyboard input field +++++
554void
555ValControl::showEditField()
556{
557	BString valueString;
558
559#if defined(DEBUG)
560	status_t err = getString(valueString);
561	ASSERT(err == B_OK);
562#endif	// DEBUG
563
564	BRect f = Bounds().OffsetByCopy(4.0, -4.0);
565	ConvertToScreen(&f);
566	//PRINT((
567	//"# ValControl::showEditField(): base bounds (%.1f, %.1f)-(%.1f,%.1f)\n",
568	//f.left, f.top, f.right, f.bottom));
569
570	new TextControlFloater(f, B_ALIGN_RIGHT, &fValueFont, valueString.String(),
571		BMessenger(this), new BMessage(M_SET_VALUE));
572		// TextControlFloater embeds new value
573		// in message: _value (string) +++++ DO NOT HARDCODE
574}
575
576
577//! (Re-)initialize backbuffer
578void
579ValControl::_AllocBackBuffer(float width, float height)
580{
581	ASSERT(fHaveBackBuffer);
582	if (fBackBuffer && fBackBuffer->Bounds().Width() >= width
583		&& fBackBuffer->Bounds().Height() >= height)
584		return;
585
586	if (fBackBuffer) {
587		delete fBackBuffer;
588		fBackBuffer = NULL;
589		fBackBufferView = NULL;
590	}
591
592	BRect bounds(0, 0, width, height);
593	fBackBuffer = new BBitmap(bounds, B_RGB32, true);
594	fBackBufferView = new BView(bounds, "back", B_FOLLOW_NONE, B_WILL_DRAW);
595	fBackBuffer->AddChild(fBackBufferView);
596}
597
598
599// ref'd view must already be a child +++++
600// (due to GetPreferredSize implementation in segment views)
601void
602ValControl::_InsertEntry(ValCtrlLayoutEntry& entry, uint16 index)
603{
604	// view ptr must be 0, or a ValControlSegment that's already a child
605	ValControlSegment* pSeg = dynamic_cast<ValControlSegment*>(entry.pView);
606	if (entry.pView)
607		ASSERT(pSeg);
608	if (pSeg)
609		ASSERT(this == pSeg->Parent());
610
611	// entry must be at one side or the other:
612	ASSERT(!index || index == fLayoutSet.size());
613
614	// figure padding
615	bool bNeedsPadding =
616		!(entry.flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING ||
617		 ((index - 1 >= 0 &&
618		  fLayoutSet[index - 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)) ||
619		 ((index + 1 < static_cast<uint16>(fLayoutSet.size()) &&
620		  fLayoutSet[index + 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)));
621
622	entry.fPadding = (bNeedsPadding) ? fSegmentPadding : 0.0;
623
624	// fetch (and grant) requested frame size
625	BRect frame(0, 0, 0, 0);
626	if (pSeg)
627		pSeg->GetPreferredSize(&frame.right, &frame.bottom);
628	else
629		_GetDefaultEntrySize(entry.type, &frame.right, &frame.bottom);
630
631	// figure amount this entry will displace:
632	float fDisplacement = frame.Width() + entry.fPadding + 1;
633
634	// set entry's top-left position:
635	if (!fLayoutSet.size()) {
636		// sole entry:
637		if (fAlignMode == ALIGN_FLUSH_RIGHT)
638			frame.OffsetBy(Bounds().right - frame.Width(), 0.0);
639	} else if (index) {
640		// insert at right side
641		if (fAlignMode == ALIGN_FLUSH_LEFT)
642			frame.OffsetBy(fLayoutSet.back().frame.right + 1 + entry.fPadding, 0.0);
643		else
644			frame.OffsetBy(fLayoutSet.back().frame.right - frame.Width(), 0.0); //+++++
645	} else {
646		// insert at left side
647		if (fAlignMode == ALIGN_FLUSH_RIGHT)
648			frame.OffsetBy(fLayoutSet.front().frame.left - fDisplacement, 0.0);
649	}
650
651	// add to layout set
652	entry.frame = frame;
653	fLayoutSet.insert(
654		index ? fLayoutSet.end() : fLayoutSet.begin(),
655		entry);
656
657	// slide following or preceding entries (depending on align mode)
658	// to make room:
659	switch (fAlignMode) {
660		case ALIGN_FLUSH_LEFT:
661			// following entries are shifted to the right
662			for(uint32 n = index+1; n < fLayoutSet.size(); n++)
663				_SlideEntry(n, fDisplacement);
664			break;
665
666		case ALIGN_FLUSH_RIGHT: {
667			// preceding entries are shifted to the left
668			for(int n = index-1; n >= 0; n--)
669				_SlideEntry(n, -fDisplacement);
670
671			break;
672		}
673	}
674//
675//	PRINT((
676//		"### added entry: (%.1f,%.1f)-(%.1f,%.1f)\n",
677//		frame.left, frame.top, frame.right, frame.bottom));
678}
679
680
681void
682ValControl::_SlideEntry(int index, float delta)
683{
684	ValCtrlLayoutEntry& e = fLayoutSet[index];
685	e.frame.OffsetBy(delta, 0.0);
686
687	// move & possibly resize view:
688	if (e.pView) {
689		e.pView->MoveTo(e.frame.LeftTop());
690
691		BRect curFrame = e.pView->Frame();
692		float fWidth = e.frame.Width();
693		float fHeight = e.frame.Height();
694		if (curFrame.Width() != fWidth
695			|| curFrame.Height() != fHeight)
696			e.pView->ResizeTo(fWidth + 5.0, fHeight);
697	}
698}
699
700
701uint16
702ValControl::_LocationToIndex(entry_location from, uint16 distance) const
703{
704	uint16 nResult = 0;
705
706	switch (from) {
707		case FROM_LEFT:
708			nResult = distance;
709			break;
710
711		case FROM_RIGHT:
712			nResult = fLayoutSet.size() - distance;
713			break;
714	}
715
716	ASSERT(nResult <= fLayoutSet.size());
717	return nResult;
718}
719
720
721void
722ValControl::_GetDefaultEntrySize(ValCtrlLayoutEntry::entry_type type,
723	float* outWidth, float* outHeight)
724{
725	switch (type) {
726		case ValCtrlLayoutEntry::SEGMENT_ENTRY:
727		case ValCtrlLayoutEntry::VIEW_ENTRY:
728			*outWidth = 1.0;
729			*outHeight = 1.0;
730			break;
731
732		case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
733			*outWidth = fDecimalPointWidth;
734			*outHeight = fDecimalPointHeight;
735			break;
736	}
737}
738