xref: /haiku/src/apps/cortex/ValControl/ValControl.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 
47 using namespace std;
48 
49 __USE_CORTEX_NAMESPACE
50 
51 
52 const float ValControl::fSegmentPadding = 2.0;
53 
54 // the decimal point covers one more pixel x and y-ward:
55 const float ValControl::fDecimalPointWidth = 2.0;
56 const float ValControl::fDecimalPointHeight = 2.0;
57 
58 
59 /*protected*/
60 ValControl::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 
85 ValControl::~ValControl()
86 {
87 	delete fBackBuffer;
88 }
89 
90 
91 ValControl::update_mode
92 ValControl::updateMode() const
93 {
94 	return fUpdateMode;
95 }
96 
97 
98 void
99 ValControl::setUpdateMode(update_mode mode)
100 {
101 	fUpdateMode = mode;
102 }
103 
104 
105 const BFont*
106 ValControl::labelFont() const
107 {
108 	return &fLabelFont;
109 }
110 
111 
112 void
113 ValControl::setLabelFont(const BFont* labelFont)
114 {
115 	fLabelFont = labelFont;
116 	// inform label segments
117 	_InvalidateAll();
118 }
119 
120 
121 const BFont*
122 ValControl::valueFont() const
123 {
124 	return &fValueFont;
125 }
126 
127 
128 void
129 ValControl::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 
147 float
148 ValControl::baselineOffset() const
149 {
150 	font_height h;
151 	be_plain_font->GetHeight(&h);
152 	return ceil(h.ascent);
153 }
154 
155 
156 float
157 ValControl::segmentPadding() const
158 {
159 	return fSegmentPadding;
160 }
161 
162 
163 BView*
164 ValControl::backBufferView() const
165 {
166 	return fBackBufferView;
167 }
168 
169 
170 BBitmap*
171 ValControl::backBuffer() const
172 {
173 	return fBackBuffer;
174 }
175 
176 
177 void
178 ValControl::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 
229 void
230 ValControl::SetEnabled(bool enabled)
231 {
232 	// redraw if enabled-state changes
233 	_Inherited::SetEnabled(enabled);
234 
235 	_InvalidateAll();
236 }
237 
238 
239 void
240 ValControl::_InvalidateAll()
241 {
242 	Invalidate();
243 	int c = CountChildren();
244 	for (int n = 0; n < c; ++n)
245 		ChildAt(n)->Invalidate();
246 }
247 
248 
249 void
250 ValControl::AttachedToWindow()
251 {
252 	// adopt parent view's color
253 	if (Parent())
254 		SetViewColor(Parent()->ViewColor());
255 }
256 
257 
258 void
259 ValControl::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)
278 void
279 ValControl::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 
289 void
290 ValControl::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 
314 void
315 ValControl::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 
327 void
328 ValControl::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 
349 void
350 ValControl::MakeFocus(bool focused)
351 {
352 	_Inherited::MakeFocus(focused);
353 
354 	// +++++ only the underline needs to be redrawn
355 	_InvalidateAll();
356 }
357 
358 
359 void
360 ValControl::MouseDown(BPoint where)
361 {
362 	MakeFocus(true);
363 }
364 
365 
366 void
367 ValControl::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 
411 ValControl::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 
425 status_t
426 ValControl::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)
470 void
471 ValControl::_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
495 void
496 ValControl::_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)
515 const ValCtrlLayoutEntry&
516 ValControl::_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 
524 const ValCtrlLayoutEntry&
525 ValControl::_EntryAt(uint16 offset) const
526 {
527 	uint16 nIndex = _LocationToIndex(FROM_LEFT, offset);
528 	ASSERT(nIndex < fLayoutSet.size());
529 	return fLayoutSet[nIndex];
530 }
531 
532 
533 uint16
534 ValControl::_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 
546 uint16
547 ValControl::CountEntries() const
548 {
549 	return fLayoutSet.size();
550 }
551 
552 
553 // pop up keyboard input field +++++
554 void
555 ValControl::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
578 void
579 ValControl::_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)
601 void
602 ValControl::_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 
681 void
682 ValControl::_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 
701 uint16
702 ValControl::_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 
721 void
722 ValControl::_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