xref: /haiku/src/servers/notification/AppGroupView.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
26 const float kCloseSize				= 6;
27 const float kEdgePadding			= 2;
28 
29 
30 AppGroupView::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 
47 void
48 AppGroupView::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 
104 void
105 AppGroupView::_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 
133 void
134 AppGroupView::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 
176 void
177 AppGroupView::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 
211 void
212 AppGroupView::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 
250 void
251 AppGroupView::SetPreviewModeOn(bool enabled)
252 {
253 	fPreviewModeOn = enabled;
254 }
255 
256 
257 const BString&
258 AppGroupView::Group() const
259 {
260 	return fLabel;
261 }
262 
263 
264 void
265 AppGroupView::SetGroup(const char* group)
266 {
267 	fLabel.SetTo(group);
268 	Invalidate();
269 }
270 
271 
272 bool
273 AppGroupView::HasChildren()
274 {
275 	return !fInfo.empty();
276 }
277 
278 
279 int32
280 AppGroupView::ChildrenCount()
281 {
282 	return fInfo.size();
283 }
284