xref: /haiku/src/apps/cortex/RouteApp/StatusView.cpp (revision 579f1dbca962a2a03df54f69fdc6e9423f91f20e)
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 // StatusView.cpp
33 
34 #include "StatusView.h"
35 #include "cortex_ui.h"
36 #include "RouteAppNodeManager.h"
37 #include "TipManager.h"
38 
39 // Application Kit
40 #include <Message.h>
41 #include <MessageRunner.h>
42 // Interface Kit
43 #include <Bitmap.h>
44 #include <Font.h>
45 #include <ScrollBar.h>
46 #include <Window.h>
47 // Support Kit
48 #include <Beep.h>
49 
50 __USE_CORTEX_NAMESPACE
51 
52 #include <Debug.h>
53 #define D_ALLOC(x) //PRINT(x)
54 #define D_HOOK(x) //PRINT(x)
55 #define D_MESSAGE(x) //PRINT(x)
56 #define D_OPERATION(x) //PRINT(x)
57 
58 // -------------------------------------------------------- //
59 // *** constants
60 // -------------------------------------------------------- //
61 
62 const bigtime_t TICK_PERIOD = 50000;
63 const bigtime_t TEXT_DECAY_DELAY = 10 * 1000 * 1000;
64 const bigtime_t TEXT_DECAY_TIME = 3 * 1000 * 1000;
65 
66 // width: 8, height:12, color_space: B_CMAP8
67 const unsigned char ERROR_ICON_BITS [] = {
68 	0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,
69 	0xff,0x00,0xfa,0xfa,0x00,0xff,0xff,0xff,
70 	0x00,0x3f,0x3f,0xfa,0xfa,0x00,0xff,0xff,
71 	0x00,0xf9,0xf9,0x3f,0x5d,0x00,0xff,0xff,
72 	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
73 	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
74 	0x00,0x00,0xf9,0x5d,0x00,0x00,0xff,0xff,
75 	0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,
76 	0x00,0xf9,0x00,0x00,0x5d,0x00,0xff,0xff,
77 	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
78 	0x00,0x00,0xf9,0x5d,0x00,0x00,0x0f,0xff,
79 	0xff,0xff,0x00,0x00,0x00,0x0f,0x0f,0x0f,
80 };
81 
82 // width: 8, height:12, color_space: B_CMAP8
83 const unsigned char INFO_ICON_BITS [] = {
84 	0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,
85 	0xff,0x00,0x21,0x21,0x21,0x00,0xff,0xff,
86 	0xff,0x00,0x21,0x92,0x25,0x00,0xff,0xff,
87 	0xff,0x00,0x21,0x25,0x25,0x00,0xff,0xff,
88 	0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,
89 	0xff,0x00,0x92,0x92,0x25,0x00,0xff,0xff,
90 	0xff,0x00,0x21,0x21,0x25,0x00,0xff,0xff,
91 	0xff,0x00,0x21,0x21,0x25,0x00,0xff,0xff,
92 	0x00,0x00,0x21,0x21,0x25,0x00,0x00,0xff,
93 	0x00,0xbe,0x21,0x21,0x92,0xbe,0x25,0x00,
94 	0x00,0x00,0x21,0x21,0x21,0x25,0x00,0x0f,
95 	0xff,0xff,0x00,0x00,0x00,0x00,0x0f,0x0f,
96 };
97 
98 // -------------------------------------------------------- //
99 // *** ctor/dtor
100 // -------------------------------------------------------- //
101 
102 StatusView::StatusView(
103 	BRect frame,
104 	RouteAppNodeManager *manager,
105 	BScrollBar *scrollBar)
106 	:	BStringView(frame, "StatusView", "", B_FOLLOW_LEFT | B_FOLLOW_BOTTOM,
107 					B_FRAME_EVENTS | B_WILL_DRAW),
108 		m_scrollBar(scrollBar),
109 		m_icon(0),
110 		m_opacity(1.0),
111 		m_clock(0),
112 		m_dragging(false),
113 		m_backBitmap(0),
114 		m_backView(0),
115 		m_dirty(true),
116 		m_manager(manager) {
117 	D_ALLOC(("StatusView::StatusView()\n"));
118 
119 	SetViewColor(B_TRANSPARENT_COLOR);
120 	SetFont(be_plain_font);
121 
122 	allocBackBitmap(frame.Width(), frame.Height());
123 }
124 
125 StatusView::~StatusView() {
126 	D_ALLOC(("StatusView::~ParameterContainerView()\n"));
127 
128 	// get the tip manager instance and reset
129 	TipManager *manager = TipManager::Instance();
130 	manager->removeAll(this);
131 
132 	delete m_clock;
133 
134 	freeBackBitmap();
135 }
136 
137 // -------------------------------------------------------- //
138 // *** BScrollView impl
139 // -------------------------------------------------------- //
140 
141 void StatusView::AttachedToWindow() {
142 	D_HOOK(("StatusView::AttachedToWindow()\n"));
143 
144 	if (m_manager) {
145 		m_manager->setLogTarget(BMessenger(this, Window()));
146 	}
147 
148 	allocBackBitmap(Bounds().Width(), Bounds().Height());
149 }
150 
151 void StatusView::Draw(
152 	BRect updateRect) {
153 	D_HOOK(("StatusView::Draw()\n"));
154 
155 	if(!m_backView) {
156 		drawInto(this, updateRect);
157 	} else {
158 		if(m_dirty) {
159 			m_backBitmap->Lock();
160 			drawInto(m_backView, updateRect);
161 			m_backView->Sync();
162 			m_backBitmap->Unlock();
163 			m_dirty = false;
164 		}
165 
166 		SetDrawingMode(B_OP_COPY);
167 		DrawBitmap(m_backBitmap, updateRect, updateRect);
168 	}
169 }
170 
171 void StatusView::FrameResized(
172 	float width,
173 	float height) {
174 	D_HOOK(("StatusView::FrameResized()\n"));
175 
176 	allocBackBitmap(width, height);
177 
178 	// get the tip manager instance and reset
179 	TipManager *manager = TipManager::Instance();
180 	manager->removeAll(this);
181 
182 	// re-truncate the string if necessary
183 	BString text = m_fullText;
184 	if (be_plain_font->StringWidth(text.String()) > Bounds().Width() - 25.0) {
185 		be_plain_font->TruncateString(&text, B_TRUNCATE_END,
186 									  Bounds().Width() - 25.0);
187 		manager->setTip(m_fullText.String(), this);
188 	}
189 	BStringView::SetText(text.String());
190 
191 	float minWidth, maxWidth, minHeight, maxHeight;
192 	Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
193 	minWidth = width + 6 * B_V_SCROLL_BAR_WIDTH;
194 	Window()->SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
195 }
196 
197 void StatusView::MessageReceived(
198 	BMessage *message) {
199 	D_MESSAGE(("StatusView::MessageReceived()\n"));
200 
201 	switch (message->what) {
202 		case 'Tick':
203 			fadeTick();
204 			break;
205 
206 		case RouteAppNodeManager::M_LOG: {
207 			D_MESSAGE((" -> RouteAppNodeManager::M_LOG\n"));
208 
209 			BString title;
210 			if (message->FindString("title", &title) != B_OK) {
211 				return;
212 			}
213 			BString details, line;
214 			for (int32 i = 0; message->FindString("line", i, &line) == B_OK; i++) {
215 				if (details.CountChars() > 0) {
216 					details << "\n";
217 				}
218 				details << line;
219 			}
220 			status_t error = B_OK;
221 			message->FindInt32("error", &error);
222 			setMessage(title, details, error);
223 			break;
224 		}
225 		default: {
226 			BStringView::MessageReceived(message);
227 		}
228 	}
229 }
230 
231 void StatusView::MouseDown(
232 	BPoint point) {
233 	D_HOOK(("StatusView::MouseDown()\n"));
234 
235 	int32 buttons;
236 	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
237 		buttons = B_PRIMARY_MOUSE_BUTTON;
238 	}
239 
240 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
241 		// drag rect
242 		BRect dragRect(Bounds());
243 		dragRect.left = dragRect.right - 10.0;
244 		if (dragRect.Contains(point)) {
245 			// resize
246 			m_dragging = true;
247 			SetMouseEventMask(B_POINTER_EVENTS,
248 							  B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
249 		}
250 	}
251 }
252 
253 void StatusView::MouseMoved(
254 	BPoint point,
255 	uint32 transit,
256 	const BMessage *message) {
257 	D_HOOK(("StatusView::MouseMoved()\n"));
258 
259 	if (m_dragging) {
260 		float x = point.x - (Bounds().right - 5.0);
261 		if ((Bounds().Width() + x) <= 16.0) {
262 			return;
263 		}
264 		if (m_scrollBar
265 		 && ((m_scrollBar->Bounds().Width() - x) <= (6 * B_V_SCROLL_BAR_WIDTH))) {
266 			return;
267 		}
268 		ResizeBy(x, 0.0);
269 		BRect r(Bounds());
270 		r.left = r.right - 5.0;
271 		if (x > 0)
272 			r.left -= x;
273 		m_dirty = true;
274 		Invalidate(r);
275 		if (m_scrollBar) {
276 			m_scrollBar->ResizeBy(-x, 0.0);
277 			m_scrollBar->MoveBy(x, 0.0);
278 		}
279 	}
280 }
281 
282 void StatusView::MouseUp(
283 	BPoint point) {
284 	D_HOOK(("StatusView::MouseUp()\n"));
285 
286 	m_dragging = false;
287 }
288 
289 // -------------------------------------------------------- //
290 // *** internal operations
291 // -------------------------------------------------------- //
292 
293 void
294 StatusView::drawInto(BView *v, BRect updateRect)
295 {
296 	BRect r(Bounds());
297 	D_OPERATION(("StatusView::drawInto(%.1f, %.1f)\n", r.Width(), r.Height()));
298 
299 	// draw border (minus right edge, which the scrollbar draws)
300 	v->SetDrawingMode(B_OP_COPY);
301 	v->BeginLineArray(8);
302 	v->AddLine(r.LeftTop(), r.RightTop(), M_MED_GRAY_COLOR);
303 	BPoint rtop = r.RightTop();
304 	rtop.y++;
305 	v->AddLine(rtop, r.RightBottom(), tint_color(M_MED_GRAY_COLOR, B_LIGHTEN_1_TINT));
306 	v->AddLine(r.RightBottom(), r.LeftBottom(), M_MED_GRAY_COLOR);
307 	v->AddLine(r.LeftBottom(), r.LeftTop(), M_MED_GRAY_COLOR);
308 	r.InsetBy(1.0, 1.0);
309 	v->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_GRAY_COLOR);
310 	rtop.y++;
311 	rtop.x--;
312 	v->AddLine(rtop, r.RightBottom(), M_GRAY_COLOR);
313 	v->AddLine(r.RightBottom(), r.LeftBottom(), tint_color(M_MED_GRAY_COLOR, B_LIGHTEN_1_TINT));
314 	v->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_GRAY_COLOR);
315 	v->EndLineArray();
316 	r.InsetBy(1.0, 1.0);
317 	v->SetLowColor(M_GRAY_COLOR);
318 	v->FillRect(r, B_SOLID_LOW);
319 
320 	r.InsetBy(2.0, 0.0);
321 	v->SetDrawingMode(B_OP_ALPHA);
322 	v->SetHighColor(0, 0, 0, uchar(255 * m_opacity));
323 
324 	// draw icon
325 	if (m_icon) {
326 		v->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
327 		BPoint p = r.LeftTop();
328 		p.y--;
329 		v->DrawBitmap(m_icon, p);
330 	}
331 
332 	// draw text
333 	r.left += 10.0;
334 	font_height fh;
335 	be_plain_font->GetHeight(&fh);
336 	r.bottom = Bounds().bottom - fh.descent
337 		- (Bounds().Height() - fh.ascent - fh.descent) / 2;
338 	v->MovePenTo(r.LeftBottom());
339 	v->DrawString(Text());
340 
341 	// draw resize dragger
342 	v->SetDrawingMode(B_OP_OVER);
343 	r = Bounds();
344 	r.right -= 2.0;
345 	r.left = r.right - 2.0;
346 	r.InsetBy(0.0, 3.0);
347 	r.top += 1.0;
348 	for (int32 i = 0; i < r.IntegerHeight(); i += 3) {
349 		BPoint p = r.LeftTop() + BPoint(0.0, i);
350 		v->SetHighColor(M_MED_GRAY_COLOR);
351 		v->StrokeLine(p, p, B_SOLID_HIGH);
352 		p += BPoint(1.0, 1.0);
353 		v->SetHighColor(M_WHITE_COLOR);
354 		v->StrokeLine(p, p, B_SOLID_HIGH);
355 	}
356 }
357 
358 
359 
360 void StatusView::setMessage(
361 	BString &title,
362 	BString &details,
363 	status_t error) {
364 	D_OPERATION(("StatusView::setMessage(%s)\n", title.String()));
365 
366 	// get the tip manager instance and reset
367 	TipManager *manager = TipManager::Instance();
368 	manager->removeAll(this);
369 
370 	// append error string
371 	if (error) {
372 		title << " (" << strerror(error) << ")";
373 	}
374 
375 	// truncate if necessary
376 	bool truncated = false;
377 	m_fullText = title;
378 	if (be_plain_font->StringWidth(title.String()) > Bounds().Width() - 25.0) {
379 		be_plain_font->TruncateString(&title, B_TRUNCATE_END,
380 									  Bounds().Width() - 25.0);
381 		truncated = true;
382 	}
383 	BStringView::SetText(title.String());
384 
385 	if (truncated || details.CountChars() > 0) {
386 		BString tip = m_fullText;
387 		if (details.CountChars() > 0) {
388 			tip << "\n" << details;
389 		}
390 		manager->setTip(tip.String(), this);
391 	}
392 
393 	if (error) {
394 		beep();
395 		// set icon
396 		if (m_icon) {
397 			delete m_icon;
398 			m_icon = 0;
399 		}
400 		BRect iconRect(0.0, 0.0, 7.0, 11.0);
401 		m_icon = new BBitmap(iconRect, B_CMAP8);
402 		m_icon->SetBits(ERROR_ICON_BITS, 96, 0, B_CMAP8);
403 	}
404 	else {
405 		// set icon
406 		if (m_icon) {
407 			delete m_icon;
408 			m_icon = 0;
409 		}
410 		BRect iconRect(0.0, 0.0, 7.0, 11.0);
411 		m_icon = new BBitmap(iconRect, B_CMAP8);
412 		m_icon->SetBits(INFO_ICON_BITS, 96, 0, B_CMAP8);
413 	}
414 	m_dirty = true;
415 	startFade();
416 	Invalidate();
417 }
418 
419 void StatusView::setErrorMessage(
420 	BString text,
421 	bool error) {
422 	D_OPERATION(("StatusView::setErrorMessage(%s)\n",
423 				 text.String()));
424 
425 	// get the tip manager instance and reset
426 	TipManager *manager = TipManager::Instance();
427 	manager->removeAll(this);
428 
429 	// truncate if necessary
430 	m_fullText = text;
431 	if (be_plain_font->StringWidth(text.String()) > Bounds().Width() - 25.0) {
432 		be_plain_font->TruncateString(&text, B_TRUNCATE_END,
433 									  Bounds().Width() - 25.0);
434 		manager->setTip(m_fullText.String(), this);
435 	}
436 	BStringView::SetText(text.String());
437 
438 	if (error) {
439 		beep();
440 		// set icon
441 		if (m_icon) {
442 			delete m_icon;
443 			m_icon = 0;
444 		}
445 		BRect iconRect(0.0, 0.0, 7.0, 11.0);
446 		m_icon = new BBitmap(iconRect, B_CMAP8);
447 		m_icon->SetBits(ERROR_ICON_BITS, 96, 0, B_CMAP8);
448 	}
449 	else {
450 		// set icon
451 		if (m_icon) {
452 			delete m_icon;
453 			m_icon = 0;
454 		}
455 		BRect iconRect(0.0, 0.0, 7.0, 11.0);
456 		m_icon = new BBitmap(iconRect, B_CMAP8);
457 		m_icon->SetBits(INFO_ICON_BITS, 96, 0, B_CMAP8);
458 	}
459 	m_dirty = true;
460 	startFade();
461 	Invalidate();
462 }
463 
464 void StatusView::startFade() {
465 	D_OPERATION(("StatusView::startFade()\n"));
466 
467 	m_opacity = 1.0;
468 	m_decayDelay = TEXT_DECAY_DELAY;
469 	if(!m_clock) {
470 		m_clock = new BMessageRunner(
471 			BMessenger(this),
472 			new BMessage('Tick'),
473 			TICK_PERIOD);
474 	}
475 }
476 
477 void StatusView::fadeTick() {
478 	D_HOOK(("StatusView::fadeTick()\n"));
479 
480 	if (m_opacity > 0.0) {
481 		if(m_decayDelay > 0) {
482 			m_decayDelay -= TICK_PERIOD;
483 			return;
484 		}
485 
486 		float steps = static_cast<float>(TEXT_DECAY_TIME)
487 					  / static_cast<float>(TICK_PERIOD);
488 		m_opacity -= (1.0 / steps);
489 		if (m_opacity < 0.001) {
490 			m_opacity = 0.0;
491 		}
492 		m_dirty = true;
493 		Invalidate();
494 	}
495 	else if (m_clock) {
496 		delete m_clock;
497 		m_clock = 0;
498 
499 		// get the tip manager instance and reset
500 		TipManager *manager = TipManager::Instance();
501 		manager->removeAll(this);
502 	}
503 }
504 
505 void StatusView::allocBackBitmap(float width, float height) {
506 	D_OPERATION(("StatusView::allocBackBitmap(%.1f, %.1f)\n", width, height));
507 
508 	// sanity check
509 	if(width <= 0.0 || height <= 0.0)
510 		return;
511 
512 	if(m_backBitmap) {
513 		// see if the bitmap needs to be expanded
514 		BRect b = m_backBitmap->Bounds();
515 		if(b.Width() >= width && b.Height() >= height)
516 			return;
517 
518 		// it does; clean up:
519 		freeBackBitmap();
520 	}
521 
522 	BRect b(0.0, 0.0, width, height);
523 	m_backBitmap = new BBitmap(b, B_RGB32, true);
524 	if(!m_backBitmap) {
525 		D_OPERATION(("StatusView::allocBackBitmap(): failed to allocate\n"));
526 		return;
527 	}
528 
529 	m_backView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW);
530 	m_backBitmap->AddChild(m_backView);
531 	m_dirty = true;
532 }
533 
534 void StatusView::freeBackBitmap() {
535 	if(m_backBitmap) {
536 		delete m_backBitmap;
537 		m_backBitmap = 0;
538 		m_backView = 0;
539 	}
540 }
541 
542 
543 // END -- ParameterContainerView.cpp --
544