⛏️ index : haiku.git

/*
 * Copyright (c) 1999-2000, Eric Moon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


// StatusView.cpp

#include "StatusView.h"
#include "cortex_ui.h"
#include "RouteAppNodeManager.h"
#include "TipManager.h"

// Application Kit
#include <Message.h>
#include <MessageRunner.h>
// Interface Kit
#include <Bitmap.h>
#include <Font.h>
#include <ScrollBar.h>
#include <Window.h>
// Support Kit
#include <Beep.h>

__USE_CORTEX_NAMESPACE

#include <Debug.h>
#define D_ALLOC(x) //PRINT(x)
#define D_HOOK(x) //PRINT(x)
#define D_MESSAGE(x) //PRINT(x)
#define D_OPERATION(x) //PRINT(x)

// -------------------------------------------------------- //
// *** constants
// -------------------------------------------------------- //

const bigtime_t TICK_PERIOD = 50000;
const bigtime_t TEXT_DECAY_DELAY = 10 * 1000 * 1000;
const bigtime_t TEXT_DECAY_TIME = 3 * 1000 * 1000;

// width: 8, height:12, color_space: B_CMAP8
const unsigned char ERROR_ICON_BITS [] = {
	0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,
	0xff,0x00,0xfa,0xfa,0x00,0xff,0xff,0xff,
	0x00,0x3f,0x3f,0xfa,0xfa,0x00,0xff,0xff,
	0x00,0xf9,0xf9,0x3f,0x5d,0x00,0xff,0xff,
	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
	0x00,0x00,0xf9,0x5d,0x00,0x00,0xff,0xff,
	0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,
	0x00,0xf9,0x00,0x00,0x5d,0x00,0xff,0xff,
	0x00,0xf9,0xf9,0x5d,0x5d,0x00,0xff,0xff,
	0x00,0x00,0xf9,0x5d,0x00,0x00,0x0f,0xff,
	0xff,0xff,0x00,0x00,0x00,0x0f,0x0f,0x0f,
};

// width: 8, height:12, color_space: B_CMAP8
const unsigned char INFO_ICON_BITS [] = {
	0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,
	0xff,0x00,0x21,0x21,0x21,0x00,0xff,0xff,
	0xff,0x00,0x21,0x92,0x25,0x00,0xff,0xff,
	0xff,0x00,0x21,0x25,0x25,0x00,0xff,0xff,
	0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,
	0xff,0x00,0x92,0x92,0x25,0x00,0xff,0xff,
	0xff,0x00,0x21,0x21,0x25,0x00,0xff,0xff,
	0xff,0x00,0x21,0x21,0x25,0x00,0xff,0xff,
	0x00,0x00,0x21,0x21,0x25,0x00,0x00,0xff,
	0x00,0xbe,0x21,0x21,0x92,0xbe,0x25,0x00,
	0x00,0x00,0x21,0x21,0x21,0x25,0x00,0x0f,
	0xff,0xff,0x00,0x00,0x00,0x00,0x0f,0x0f,
};

// -------------------------------------------------------- //
// *** ctor/dtor
// -------------------------------------------------------- //

StatusView::StatusView(
	BRect frame,
	RouteAppNodeManager *manager,
	BScrollBar *scrollBar)
	:	BStringView(frame, "StatusView", "", B_FOLLOW_LEFT | B_FOLLOW_BOTTOM,
					B_FRAME_EVENTS | B_WILL_DRAW),
		m_scrollBar(scrollBar),
		m_icon(0),
		m_opacity(1.0),
		m_clock(0),
		m_dragging(false),
		m_backBitmap(0),
		m_backView(0),
		m_dirty(true),
		m_manager(manager) {
	D_ALLOC(("StatusView::StatusView()\n"));

	SetViewColor(B_TRANSPARENT_COLOR);
	SetFont(be_plain_font);
	
	allocBackBitmap(frame.Width(), frame.Height());
}

StatusView::~StatusView() {
	D_ALLOC(("StatusView::~ParameterContainerView()\n"));

	// get the tip manager instance and reset
	TipManager *manager = TipManager::Instance();
	manager->removeAll(this);
	
	delete m_clock;
	
	freeBackBitmap();
}

// -------------------------------------------------------- //
// *** BScrollView impl
// -------------------------------------------------------- //

void StatusView::AttachedToWindow() {
	D_HOOK(("StatusView::AttachedToWindow()\n"));

	if (m_manager) {
		m_manager->setLogTarget(BMessenger(this, Window()));
	}
	
	allocBackBitmap(Bounds().Width(), Bounds().Height());
}

void StatusView::Draw(
	BRect updateRect) {
	D_HOOK(("StatusView::Draw()\n"));
	
	if(!m_backView) {
		drawInto(this, updateRect);
	} else {
		if(m_dirty) {
			m_backBitmap->Lock();
			drawInto(m_backView, updateRect);
			m_backView->Sync();
			m_backBitmap->Unlock();
			m_dirty = false;
		}

		SetDrawingMode(B_OP_COPY);
		DrawBitmap(m_backBitmap, updateRect, updateRect);
	}
}

void StatusView::FrameResized(
	float width,
	float height) {
	D_HOOK(("StatusView::FrameResized()\n"));

	allocBackBitmap(width, height);

	// get the tip manager instance and reset
	TipManager *manager = TipManager::Instance();
	manager->removeAll(this);

	// re-truncate the string if necessary
	BString text = m_fullText;
	if (be_plain_font->StringWidth(text.String()) > Bounds().Width() - 25.0) {
		be_plain_font->TruncateString(&text, B_TRUNCATE_END,
									  Bounds().Width() - 25.0);
		manager->setTip(m_fullText.String(), this);
	}
	BStringView::SetText(text.String());

	float minWidth, maxWidth, minHeight, maxHeight;
	Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
	minWidth = width + 6 * B_V_SCROLL_BAR_WIDTH;
	Window()->SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
}

void StatusView::MessageReceived(
	BMessage *message) {
	D_MESSAGE(("StatusView::MessageReceived()\n"));

	switch (message->what) {
		case 'Tick':
			fadeTick();
			break;
			
		case RouteAppNodeManager::M_LOG: {
			D_MESSAGE((" -> RouteAppNodeManager::M_LOG\n"));

			BString title;
			if (message->FindString("title", &title) != B_OK) {
				return;
			}
			BString details, line;
			for (int32 i = 0; message->FindString("line", i, &line) == B_OK; i++) {
				if (details.CountChars() > 0) {
					details << "\n";
				}
				details << line;
			}
			status_t error = B_OK;
			message->FindInt32("error", &error);
			setMessage(title, details, error);
			break;
		}
		default: {
			BStringView::MessageReceived(message);
		}
	}
}

void StatusView::MouseDown(
	BPoint point) {
	D_HOOK(("StatusView::MouseDown()\n"));

	int32 buttons;
	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
		buttons = B_PRIMARY_MOUSE_BUTTON;
	}

	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
		// drag rect
		BRect dragRect(Bounds());
		dragRect.left = dragRect.right - 10.0;
		if (dragRect.Contains(point)) {
			// resize
			m_dragging = true;
			SetMouseEventMask(B_POINTER_EVENTS,
							  B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
		}
	}
}

void StatusView::MouseMoved(
	BPoint point,
	uint32 transit,
	const BMessage *message) {
	D_HOOK(("StatusView::MouseMoved()\n"));

	if (m_dragging) {
		float x = point.x - (Bounds().right - 5.0);
		if ((Bounds().Width() + x) <= 16.0) {
			return;
		}
		if (m_scrollBar
		 && ((m_scrollBar->Bounds().Width() - x) <= (6 * B_V_SCROLL_BAR_WIDTH))) {
			return;
		}
		ResizeBy(x, 0.0);
		BRect r(Bounds());
		r.left = r.right - 5.0;
		if (x > 0)
			r.left -= x;
		m_dirty = true;
		Invalidate(r);
		if (m_scrollBar) {
			m_scrollBar->ResizeBy(-x, 0.0);
			m_scrollBar->MoveBy(x, 0.0);
		}
	}
}

void StatusView::MouseUp(
	BPoint point) {
	D_HOOK(("StatusView::MouseUp()\n"));

	m_dragging = false;
}

// -------------------------------------------------------- //
// *** internal operations
// -------------------------------------------------------- //

void 
StatusView::drawInto(BView *v, BRect updateRect)
{
	BRect r(Bounds());
	D_OPERATION(("StatusView::drawInto(%.1f, %.1f)\n", r.Width(), r.Height()));

	// draw border (minus right edge, which the scrollbar draws)
	v->SetDrawingMode(B_OP_COPY);
	v->BeginLineArray(8);
	v->AddLine(r.LeftTop(), r.RightTop(), M_MED_GRAY_COLOR);
	BPoint rtop = r.RightTop();
	rtop.y++;
	v->AddLine(rtop, r.RightBottom(), tint_color(M_MED_GRAY_COLOR, B_LIGHTEN_1_TINT));
	v->AddLine(r.RightBottom(), r.LeftBottom(), M_MED_GRAY_COLOR);
	v->AddLine(r.LeftBottom(), r.LeftTop(), M_MED_GRAY_COLOR);
	r.InsetBy(1.0, 1.0);
	v->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_GRAY_COLOR);
	rtop.y++;
	rtop.x--;
	v->AddLine(rtop, r.RightBottom(), M_GRAY_COLOR);
	v->AddLine(r.RightBottom(), r.LeftBottom(), tint_color(M_MED_GRAY_COLOR, B_LIGHTEN_1_TINT));
	v->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_GRAY_COLOR);
	v->EndLineArray();
	r.InsetBy(1.0, 1.0);
	v->SetLowColor(M_GRAY_COLOR);
	v->FillRect(r, B_SOLID_LOW);

	r.InsetBy(2.0, 0.0);
	v->SetDrawingMode(B_OP_ALPHA);
	v->SetHighColor(0, 0, 0, uchar(255 * m_opacity));

	// draw icon
	if (m_icon) {
		v->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
		BPoint p = r.LeftTop();
		p.y--;
		v->DrawBitmap(m_icon, p);
	}

	// draw text
	r.left += 10.0;
	font_height fh;
	be_plain_font->GetHeight(&fh);
	r.bottom = Bounds().bottom - fh.descent
		- (Bounds().Height() - fh.ascent - fh.descent) / 2;
	v->MovePenTo(r.LeftBottom());
	v->DrawString(Text());

	// draw resize dragger
	v->SetDrawingMode(B_OP_OVER);
	r = Bounds();
	r.right -= 2.0;
	r.left = r.right - 2.0;
	r.InsetBy(0.0, 3.0);
	r.top += 1.0;
	for (int32 i = 0; i < r.IntegerHeight(); i += 3) {
		BPoint p = r.LeftTop() + BPoint(0.0, i);
		v->SetHighColor(M_MED_GRAY_COLOR);
		v->StrokeLine(p, p, B_SOLID_HIGH);
		p += BPoint(1.0, 1.0);
		v->SetHighColor(M_WHITE_COLOR);
		v->StrokeLine(p, p, B_SOLID_HIGH);
	}
}



void StatusView::setMessage(
	BString &title,
	BString &details,
	status_t error) {
	D_OPERATION(("StatusView::setMessage(%s)\n", title.String()));

	// get the tip manager instance and reset
	TipManager *manager = TipManager::Instance();
	manager->removeAll(this);

	// append error string
	if (error) {
		title << " (" << strerror(error) << ")";
	}

	// truncate if necessary
	bool truncated = false;
	m_fullText = title;
	if (be_plain_font->StringWidth(title.String()) > Bounds().Width() - 25.0) {
		be_plain_font->TruncateString(&title, B_TRUNCATE_END,
									  Bounds().Width() - 25.0);
		truncated = true;
	}
	BStringView::SetText(title.String());

	if (truncated || details.CountChars() > 0) {
		BString tip = m_fullText;
		if (details.CountChars() > 0) {
			tip << "\n" << details;
		}
		manager->setTip(tip.String(), this);
	}

	if (error) {
		beep();
		// set icon
		if (m_icon) {
			delete m_icon;
			m_icon = 0;
		}
		BRect iconRect(0.0, 0.0, 7.0, 11.0);
		m_icon = new BBitmap(iconRect, B_CMAP8);
		m_icon->SetBits(ERROR_ICON_BITS, 96, 0, B_CMAP8);
	}
	else {
		// set icon
		if (m_icon) {
			delete m_icon;
			m_icon = 0;
		}
		BRect iconRect(0.0, 0.0, 7.0, 11.0);
		m_icon = new BBitmap(iconRect, B_CMAP8);
		m_icon->SetBits(INFO_ICON_BITS, 96, 0, B_CMAP8);
	}
	m_dirty = true;
	startFade();
	Invalidate();
}

void StatusView::setErrorMessage(
	BString text,
	bool error) {
	D_OPERATION(("StatusView::setErrorMessage(%s)\n",
				 text.String()));

	// get the tip manager instance and reset
	TipManager *manager = TipManager::Instance();
	manager->removeAll(this);

	// truncate if necessary
	m_fullText = text;
	if (be_plain_font->StringWidth(text.String()) > Bounds().Width() - 25.0) {
		be_plain_font->TruncateString(&text, B_TRUNCATE_END,
									  Bounds().Width() - 25.0);
		manager->setTip(m_fullText.String(), this);
	}
	BStringView::SetText(text.String());

	if (error) {
		beep();
		// set icon
		if (m_icon) {
			delete m_icon;
			m_icon = 0;
		}
		BRect iconRect(0.0, 0.0, 7.0, 11.0);
		m_icon = new BBitmap(iconRect, B_CMAP8);
		m_icon->SetBits(ERROR_ICON_BITS, 96, 0, B_CMAP8);
	}
	else {
		// set icon
		if (m_icon) {
			delete m_icon;
			m_icon = 0;
		}
		BRect iconRect(0.0, 0.0, 7.0, 11.0);
		m_icon = new BBitmap(iconRect, B_CMAP8);
		m_icon->SetBits(INFO_ICON_BITS, 96, 0, B_CMAP8);
	}
	m_dirty = true;
	startFade();
	Invalidate();
}

void StatusView::startFade() {
	D_OPERATION(("StatusView::startFade()\n"));
	
	m_opacity = 1.0;
	m_decayDelay = TEXT_DECAY_DELAY;
	if(!m_clock) {
		m_clock = new BMessageRunner(
			BMessenger(this),
			new BMessage('Tick'),
			TICK_PERIOD);
	}
}

void StatusView::fadeTick() {
	D_HOOK(("StatusView::fadeTick()\n"));

	if (m_opacity > 0.0) {
		if(m_decayDelay > 0) {
			m_decayDelay -= TICK_PERIOD;
			return;
		}
		
		float steps = static_cast<float>(TEXT_DECAY_TIME)
					  / static_cast<float>(TICK_PERIOD);
		m_opacity -= (1.0 / steps);
		if (m_opacity < 0.001) {
			m_opacity = 0.0;
		}
		m_dirty = true;
		Invalidate();
	}
	else if (m_clock) {
		delete m_clock;
		m_clock = 0;
		
		// get the tip manager instance and reset
		TipManager *manager = TipManager::Instance();
		manager->removeAll(this);
	}
}

void StatusView::allocBackBitmap(float width, float height) {
	D_OPERATION(("StatusView::allocBackBitmap(%.1f, %.1f)\n", width, height));

	// sanity check
	if(width <= 0.0 || height <= 0.0)
		return;

	if(m_backBitmap) {
		// see if the bitmap needs to be expanded
		BRect b = m_backBitmap->Bounds();
		if(b.Width() >= width && b.Height() >= height)
			return;

		// it does; clean up:
		freeBackBitmap();
	}

	BRect b(0.0, 0.0, width, height);
	m_backBitmap = new BBitmap(b, B_RGB32, true);
	if(!m_backBitmap) {
		D_OPERATION(("StatusView::allocBackBitmap(): failed to allocate\n"));
		return;
	}
	
	m_backView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW);
	m_backBitmap->AddChild(m_backView);
	m_dirty = true;
}

void StatusView::freeBackBitmap() {
	if(m_backBitmap) {
		delete m_backBitmap;
		m_backBitmap = 0;
		m_backView = 0;
	}
}


// END -- ParameterContainerView.cpp --