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