1/*
2 * Copyright 2010-2017, Haiku, Inc. All Rights Reserved.
3 * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved.
4 * Copyright 2004-2008, Michael Davidson. All Rights Reserved.
5 * Copyright 2004-2007, Mikael Eiman. All Rights Reserved.
6 * Distributed under the terms of the MIT License.
7 *
8 * Authors:
9 *		Michael Davidson, slaad@bong.com.au
10 *		Mikael Eiman, mikael@eiman.tv
11 *		Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
12 *		Brian Hill, supernova@tycho.email
13 */
14
15#include <algorithm>
16
17#include <ControlLook.h>
18#include <GroupLayout.h>
19#include <GroupView.h>
20
21#include "AppGroupView.h"
22
23#include "NotificationWindow.h"
24#include "NotificationView.h"
25
26const float kCloseSize				= 6;
27const float kEdgePadding			= 2;
28
29
30AppGroupView::AppGroupView(const BMessenger& messenger, const char* label)
31	:
32	BGroupView("appGroup", B_VERTICAL, 0),
33	fLabel(label),
34	fMessenger(messenger),
35	fCollapsed(false),
36	fCloseClicked(false),
37	fPreviewModeOn(false)
38{
39	SetFlags(Flags() | B_WILL_DRAW);
40
41	fHeaderSize = be_bold_font->Size()
42		+ be_control_look->ComposeSpacing(B_USE_ITEM_SPACING);
43	static_cast<BGroupLayout*>(GetLayout())->SetInsets(0, fHeaderSize, 0, 0);
44}
45
46
47void
48AppGroupView::Draw(BRect updateRect)
49{
50	rgb_color menuColor = ViewColor();
51	BRect bounds = Bounds();
52	bounds.bottom = bounds.top + fHeaderSize;
53
54	// Draw the header background
55	SetHighColor(tint_color(menuColor, 1.22));
56	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
57	StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
58	uint32 borders = BControlLook::B_TOP_BORDER
59		| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
60	be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
61		0, borders);
62
63	// Draw the buttons
64	fCollapseRect.top = (fHeaderSize - kExpandSize) / 2;
65	fCollapseRect.left = kEdgePadding * 3;
66	fCollapseRect.right = fCollapseRect.left + 1.5 * kExpandSize;
67	fCollapseRect.bottom = fCollapseRect.top + kExpandSize;
68
69	fCloseRect = bounds;
70	fCloseRect.top = (fHeaderSize - kCloseSize) / 2;
71	// Take off the 1 to line this up with the close button on the
72	// notification view
73	fCloseRect.right -= kEdgePadding * 3 - 1;
74	fCloseRect.left = fCloseRect.right - kCloseSize;
75	fCloseRect.bottom = fCloseRect.top + kCloseSize;
76
77	uint32 arrowDirection = fCollapsed
78		? BControlLook::B_DOWN_ARROW : BControlLook::B_UP_ARROW;
79	be_control_look->DrawArrowShape(this, fCollapseRect, fCollapseRect,
80		LowColor(), arrowDirection, 0, B_DARKEN_3_TINT);
81
82	SetPenSize(kPenSize);
83
84	// Draw the dismiss widget
85	_DrawCloseButton(updateRect);
86
87	// Draw the label
88	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
89	BString label = fLabel;
90	if (fCollapsed)
91		label << " (" << fInfo.size() << ")";
92
93	SetFont(be_bold_font);
94	font_height fontHeight;
95	GetFontHeight(&fontHeight);
96	float y = (bounds.top + bounds.bottom - ceilf(fontHeight.ascent)
97		- ceilf(fontHeight.descent)) / 2.0 + ceilf(fontHeight.ascent);
98
99	DrawString(label.String(),
100		BPoint(fCollapseRect.right + 4 * kEdgePadding, y));
101}
102
103
104void
105AppGroupView::_DrawCloseButton(const BRect& updateRect)
106{
107	PushState();
108	BRect closeRect = fCloseRect;
109
110	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
111	float tint = B_DARKEN_2_TINT;
112
113	if (fCloseClicked) {
114		BRect buttonRect(closeRect.InsetByCopy(-4, -4));
115		be_control_look->DrawButtonFrame(this, buttonRect, updateRect,
116			base, base,
117			BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME);
118		be_control_look->DrawButtonBackground(this, buttonRect, updateRect,
119			base, BControlLook::B_ACTIVATED);
120		tint *= 1.2;
121		closeRect.OffsetBy(1, 1);
122	}
123
124	base = tint_color(base, tint);
125	SetHighColor(base);
126	SetPenSize(2);
127	StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
128	StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
129	PopState();
130}
131
132
133void
134AppGroupView::MouseDown(BPoint point)
135{
136	// Preview Mode ignores any mouse clicks
137	if (fPreviewModeOn)
138		return;
139
140	if (BRect(fCloseRect).InsetBySelf(-5, -5).Contains(point)) {
141		int32 children = fInfo.size();
142		for (int32 i = 0; i < children; i++) {
143			fInfo[i]->RemoveSelf();
144			delete fInfo[i];
145		}
146
147		fInfo.clear();
148
149		// Remove ourselves from the parent view
150		BMessage message(kRemoveGroupView);
151		message.AddPointer("view", this);
152		fMessenger.SendMessage(&message);
153	} else if (BRect(fCollapseRect).InsetBySelf(-5, -5).Contains(point)) {
154		fCollapsed = !fCollapsed;
155		int32 children = fInfo.size();
156		if (fCollapsed) {
157			for (int32 i = 0; i < children; i++) {
158				if (!fInfo[i]->IsHidden())
159					fInfo[i]->Hide();
160			}
161			GetLayout()->SetExplicitMaxSize(GetLayout()->MinSize());
162		} else {
163			for (int32 i = 0; i < children; i++) {
164				if (fInfo[i]->IsHidden())
165					fInfo[i]->Show();
166			}
167			GetLayout()->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNSET));
168		}
169
170		InvalidateLayout();
171		Invalidate(); // Need to redraw the collapse indicator and title
172	}
173}
174
175
176void
177AppGroupView::MessageReceived(BMessage* msg)
178{
179	switch (msg->what) {
180		case kRemoveView:
181		{
182			NotificationView* view = NULL;
183			if (msg->FindPointer("view", (void**)&view) != B_OK)
184				return;
185
186			infoview_t::iterator vIt = find(fInfo.begin(), fInfo.end(), view);
187			if (vIt == fInfo.end())
188				break;
189
190			fInfo.erase(vIt);
191			view->RemoveSelf();
192			delete view;
193
194			fMessenger.SendMessage(msg);
195
196			if (!this->HasChildren()) {
197				Hide();
198				BMessage removeSelfMessage(kRemoveGroupView);
199				removeSelfMessage.AddPointer("view", this);
200				fMessenger.SendMessage(&removeSelfMessage);
201			}
202
203			break;
204		}
205		default:
206			BView::MessageReceived(msg);
207	}
208}
209
210
211void
212AppGroupView::AddInfo(NotificationView* view)
213{
214	BString id = view->MessageID();
215	bool found = false;
216
217	if (id.Length() > 0) {
218		int32 children = fInfo.size();
219		for (int32 i = 0; i < children; i++) {
220			if (id == fInfo[i]->MessageID()) {
221				NotificationView* oldView = fInfo[i];
222				oldView->RemoveSelf();
223				delete oldView;
224				fInfo[i] = view;
225				found = true;
226				break;
227			}
228		}
229	}
230
231	// Invalidate all children to show or hide the close buttons in the
232	// notification view
233	int32 children = fInfo.size();
234	for (int32 i = 0; i < children; i++) {
235		fInfo[i]->Invalidate();
236	}
237
238	if (!found) {
239		fInfo.push_back(view);
240	}
241	GetLayout()->AddView(view);
242
243	if (IsHidden())
244		Show();
245	if (view->IsHidden(view) && !fCollapsed)
246		view->Show();
247}
248
249
250void
251AppGroupView::SetPreviewModeOn(bool enabled)
252{
253	fPreviewModeOn = enabled;
254}
255
256
257const BString&
258AppGroupView::Group() const
259{
260	return fLabel;
261}
262
263
264void
265AppGroupView::SetGroup(const char* group)
266{
267	fLabel.SetTo(group);
268	Invalidate();
269}
270
271
272bool
273AppGroupView::HasChildren()
274{
275	return !fInfo.empty();
276}
277
278
279int32
280AppGroupView::ChildrenCount()
281{
282	return fInfo.size();
283}
284