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