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
62const bigtime_t TICK_PERIOD = 50000;
63const bigtime_t TEXT_DECAY_DELAY = 10 * 1000 * 1000;
64const bigtime_t TEXT_DECAY_TIME = 3 * 1000 * 1000;
65
66// width: 8, height:12, color_space: B_CMAP8
67const 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
83const 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
102StatusView::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
125StatusView::~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
141void 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
151void 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
171void 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
197void 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
231void 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
253void 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
282void StatusView::MouseUp(
283	BPoint point) {
284	D_HOOK(("StatusView::MouseUp()\n"));
285
286	m_dragging = false;
287}
288
289// -------------------------------------------------------- //
290// *** internal operations
291// -------------------------------------------------------- //
292
293void
294StatusView::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
360void 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
419void 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
464void 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
477void 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
505void 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
534void 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