⛏️ index : haiku.git

/*
 * Copyright 2010-2017, Haiku, Inc. All Rights Reserved.
 * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved.
 * Copyright 2004-2008, Michael Davidson. All Rights Reserved.
 * Copyright 2004-2007, Mikael Eiman. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Davidson, slaad@bong.com.au
 *		Mikael Eiman, mikael@eiman.tv
 *		Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
 *		Brian Hill, supernova@tycho.email
 */

#include <algorithm>

#include <ControlLook.h>
#include <GroupLayout.h>
#include <GroupView.h>

#include "AppGroupView.h"

#include "NotificationWindow.h"
#include "NotificationView.h"


AppGroupView::AppGroupView(const BMessenger& messenger, const char* label)
	:
	BGroupView("appGroup", B_VERTICAL, 0),
	fLabel(label),
	fMessenger(messenger),
	fCollapsed(false),
	fCloseClicked(false),
	fPreviewModeOn(false)
{
	SetFlags(Flags() | B_WILL_DRAW);

	fHeaderSize = be_bold_font->Size() + be_control_look->ComposeSpacing(B_USE_ITEM_SPACING);
	static_cast<BGroupLayout*>(GetLayout())->SetInsets(0, fHeaderSize, 0, 0);
}


void
AppGroupView::Draw(BRect updateRect)
{
	rgb_color menuColor = ViewColor();
	BRect bounds = Bounds();
	bounds.bottom = bounds.top + fHeaderSize;

	// Draw the header background
	SetHighColor(tint_color(menuColor, 1.22));
	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
	StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
	uint32 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER
		| BControlLook::B_RIGHT_BORDER;
	be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor, 0, borders);

	// Draw the buttons
	float buttonSize = CloseButtonSize();
	float arrowButtonSize = buttonSize * 1.5;
		// make arrow button a bit larger

	fCollapseRect.top = (fHeaderSize - arrowButtonSize) / 2;
	fCollapseRect.left = buttonSize + 1;
		// button left padding
	fCollapseRect.right = fCollapseRect.left + arrowButtonSize;
	fCollapseRect.bottom = fCollapseRect.top + arrowButtonSize;

	fCloseRect = bounds;
	fCloseRect.top = (fHeaderSize - buttonSize) / 2;
	fCloseRect.right -= buttonSize - 1;
	fCloseRect.left = fCloseRect.right - buttonSize;
	fCloseRect.bottom = fCloseRect.top + buttonSize;

	SetPenSize(1);

	// Draw the arrow button
	uint32 direction = fCollapsed ? BControlLook::B_DOWN_ARROW : BControlLook::B_UP_ARROW;
	be_control_look->DrawArrowShape(this, fCollapseRect, fCollapseRect, LowColor(), direction, 0,
		B_DARKEN_3_TINT);

	// Draw the dismiss widget
	DrawCloseButton(updateRect);

	// Draw the label
	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
	BString label = fLabel;
	if (fCollapsed)
		label << " (" << fInfo.size() << ")";

	SetFont(be_bold_font);
	font_height fontHeight;
	GetFontHeight(&fontHeight);
	float y = (bounds.top + bounds.bottom - ceilf(fontHeight.ascent)
		- ceilf(fontHeight.descent)) / 2.0 + ceilf(fontHeight.ascent);

	float x = fCollapseRect.right + buttonSize * 1.5;
	DrawString(label.String(), BPoint(x, y));
}


void
AppGroupView::DrawCloseButton(const BRect& updateRect)
{
	PushState();
	BRect closeRect = fCloseRect;

	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
	float tint = B_DARKEN_2_TINT;

	if (fCloseClicked) {
		BRect buttonRect(closeRect.InsetByCopy(-4, -4));
		be_control_look->DrawButtonFrame(this, buttonRect, updateRect, base, base,
			BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME);
		be_control_look->DrawButtonBackground(this, buttonRect, updateRect, base,
			BControlLook::B_ACTIVATED);
		tint *= 1.2;
		closeRect.OffsetBy(1, 1);
	}

	base = tint_color(base, tint);
	SetHighColor(base);
	SetPenSize(2);
	StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
	StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
	PopState();
}


float
AppGroupView::CloseButtonSize() const
{
	return be_control_look->DefaultLabelSpacing() + 1;
}


void
AppGroupView::MouseDown(BPoint point)
{
	// Preview Mode ignores any mouse clicks
	if (fPreviewModeOn)
		return;

	if (BRect(fCloseRect).InsetBySelf(-5, -5).Contains(point)) {
		int32 children = fInfo.size();
		for (int32 i = 0; i < children; i++) {
			fInfo[i]->RemoveSelf();
			delete fInfo[i];
		}

		fInfo.clear();

		// Remove ourselves from the parent view
		BMessage message(kRemoveGroupView);
		message.AddPointer("view", this);
		fMessenger.SendMessage(&message);
	} else if (BRect(fCollapseRect).InsetBySelf(-5, -5).Contains(point)) {
		fCollapsed = !fCollapsed;
		int32 children = fInfo.size();
		if (fCollapsed) {
			for (int32 i = 0; i < children; i++) {
				if (!fInfo[i]->IsHidden())
					fInfo[i]->Hide();
			}
			GetLayout()->SetExplicitMaxSize(GetLayout()->MinSize());
		} else {
			for (int32 i = 0; i < children; i++) {
				if (fInfo[i]->IsHidden())
					fInfo[i]->Show();
			}
			GetLayout()->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNSET));
		}

		InvalidateLayout();
		Invalidate(); // Need to redraw the collapse indicator and title
	}
}


void
AppGroupView::MessageReceived(BMessage* msg)
{
	switch (msg->what) {
		case kRemoveView:
		{
			NotificationView* view = NULL;
			if (msg->FindPointer("view", (void**)&view) != B_OK)
				return;

			infoview_t::iterator vIt = find(fInfo.begin(), fInfo.end(), view);
			if (vIt == fInfo.end())
				break;

			fInfo.erase(vIt);
			view->RemoveSelf();
			delete view;

			fMessenger.SendMessage(msg);

			if (!this->HasChildren()) {
				Hide();
				BMessage removeSelfMessage(kRemoveGroupView);
				removeSelfMessage.AddPointer("view", this);
				fMessenger.SendMessage(&removeSelfMessage);
			}

			break;
		}
		default:
			BView::MessageReceived(msg);
	}
}


void
AppGroupView::AddInfo(NotificationView* view)
{
	BString id = view->MessageID();
	bool found = false;

	if (id.Length() > 0) {
		int32 children = fInfo.size();
		for (int32 i = 0; i < children; i++) {
			if (id == fInfo[i]->MessageID()) {
				NotificationView* oldView = fInfo[i];
				oldView->RemoveSelf();
				delete oldView;
				fInfo[i] = view;
				found = true;
				break;
			}
		}
	}

	// Invalidate all children to show or hide the close buttons in the
	// notification view
	int32 children = fInfo.size();
	for (int32 i = 0; i < children; i++) {
		fInfo[i]->Invalidate();
	}

	if (!found) {
		fInfo.push_back(view);
	}
	GetLayout()->AddView(view);

	if (IsHidden())
		Show();
	if (view->IsHidden(view) && !fCollapsed)
		view->Show();
}


void
AppGroupView::SetPreviewModeOn(bool enabled)
{
	fPreviewModeOn = enabled;
}


const BString&
AppGroupView::Group() const
{
	return fLabel;
}


void
AppGroupView::SetGroup(const char* group)
{
	fLabel.SetTo(group);
	Invalidate();
}


bool
AppGroupView::HasChildren()
{
	return !fInfo.empty();
}


int32
AppGroupView::ChildrenCount()
{
	return fInfo.size();
}