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