xref: /haiku/src/servers/notification/AppGroupView.cpp (revision 3995592cdf304335132305e27c40cbb0b1ac46e3)
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 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
53 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
54 	bounds.bottom = bounds.top + fHeaderSize;
55 
56 	// Draw the header background
57 	SetHighColor(tint_color(menuColor, 1.22));
58 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
59 	StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
60 	uint32 borders = BControlLook::B_TOP_BORDER
61 		| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
62 	be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
63 		0, borders);
64 
65 	// Draw the buttons
66 	fCollapseRect.top = (fHeaderSize - kExpandSize) / 2;
67 	fCollapseRect.left = kEdgePadding * 3;
68 	fCollapseRect.right = fCollapseRect.left + 1.5 * kExpandSize;
69 	fCollapseRect.bottom = fCollapseRect.top + kExpandSize;
70 
71 	fCloseRect = bounds;
72 	fCloseRect.top = (fHeaderSize - kCloseSize) / 2;
73 	// Take off the 1 to line this up with the close button on the
74 	// notification view
75 	fCloseRect.right -= kEdgePadding * 3 - 1;
76 	fCloseRect.left = fCloseRect.right - kCloseSize;
77 	fCloseRect.bottom = fCloseRect.top + kCloseSize;
78 
79 	uint32 arrowDirection = fCollapsed
80 		? BControlLook::B_DOWN_ARROW : BControlLook::B_UP_ARROW;
81 	be_control_look->DrawArrowShape(this, fCollapseRect, fCollapseRect,
82 		LowColor(), arrowDirection, 0, B_DARKEN_3_TINT);
83 
84 	SetPenSize(kPenSize);
85 
86 	// Draw the dismiss widget
87 	_DrawCloseButton(updateRect);
88 
89 	// Draw the label
90 	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
91 	BString label = fLabel;
92 	if (fCollapsed)
93 		label << " (" << fInfo.size() << ")";
94 
95 	SetFont(be_bold_font);
96 	font_height fontHeight;
97 	GetFontHeight(&fontHeight);
98 	float y = (bounds.top + bounds.bottom - ceilf(fontHeight.ascent)
99 		- ceilf(fontHeight.descent)) / 2.0 + ceilf(fontHeight.ascent);
100 
101 	DrawString(label.String(),
102 		BPoint(fCollapseRect.right + 4 * kEdgePadding, y));
103 }
104 
105 
106 void
107 AppGroupView::_DrawCloseButton(const BRect& updateRect)
108 {
109 	PushState();
110 	BRect closeRect = fCloseRect;
111 
112 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
113 	float tint = B_DARKEN_2_TINT;
114 
115 	if (fCloseClicked) {
116 		BRect buttonRect(closeRect.InsetByCopy(-4, -4));
117 		be_control_look->DrawButtonFrame(this, buttonRect, updateRect,
118 			base, base,
119 			BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME);
120 		be_control_look->DrawButtonBackground(this, buttonRect, updateRect,
121 			base, BControlLook::B_ACTIVATED);
122 		tint *= 1.2;
123 		closeRect.OffsetBy(1, 1);
124 	}
125 
126 	base = tint_color(base, tint);
127 	SetHighColor(base);
128 	SetPenSize(2);
129 	StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
130 	StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
131 	PopState();
132 }
133 
134 
135 void
136 AppGroupView::MouseDown(BPoint point)
137 {
138 	// Preview Mode ignores any mouse clicks
139 	if (fPreviewModeOn)
140 		return;
141 
142 	if (BRect(fCloseRect).InsetBySelf(-5, -5).Contains(point)) {
143 		int32 children = fInfo.size();
144 		for (int32 i = 0; i < children; i++) {
145 			fInfo[i]->RemoveSelf();
146 			delete fInfo[i];
147 		}
148 
149 		fInfo.clear();
150 
151 		// Remove ourselves from the parent view
152 		BMessage message(kRemoveGroupView);
153 		message.AddPointer("view", this);
154 		fMessenger.SendMessage(&message);
155 	} else if (BRect(fCollapseRect).InsetBySelf(-5, -5).Contains(point)) {
156 		fCollapsed = !fCollapsed;
157 		int32 children = fInfo.size();
158 		if (fCollapsed) {
159 			for (int32 i = 0; i < children; i++) {
160 				if (!fInfo[i]->IsHidden())
161 					fInfo[i]->Hide();
162 			}
163 			GetLayout()->SetExplicitMaxSize(GetLayout()->MinSize());
164 		} else {
165 			for (int32 i = 0; i < children; i++) {
166 				if (fInfo[i]->IsHidden())
167 					fInfo[i]->Show();
168 			}
169 			GetLayout()->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNSET));
170 		}
171 
172 		InvalidateLayout();
173 		Invalidate(); // Need to redraw the collapse indicator and title
174 	}
175 }
176 
177 
178 void
179 AppGroupView::MessageReceived(BMessage* msg)
180 {
181 	switch (msg->what) {
182 		case kRemoveView:
183 		{
184 			NotificationView* view = NULL;
185 			if (msg->FindPointer("view", (void**)&view) != B_OK)
186 				return;
187 
188 			infoview_t::iterator vIt = find(fInfo.begin(), fInfo.end(), view);
189 			if (vIt == fInfo.end())
190 				break;
191 
192 			fInfo.erase(vIt);
193 			view->RemoveSelf();
194 			delete view;
195 
196 			fMessenger.SendMessage(msg);
197 
198 			if (!this->HasChildren()) {
199 				Hide();
200 				BMessage removeSelfMessage(kRemoveGroupView);
201 				removeSelfMessage.AddPointer("view", this);
202 				fMessenger.SendMessage(&removeSelfMessage);
203 			}
204 
205 			break;
206 		}
207 		default:
208 			BView::MessageReceived(msg);
209 	}
210 }
211 
212 
213 void
214 AppGroupView::AddInfo(NotificationView* view)
215 {
216 	BString id = view->MessageID();
217 	bool found = false;
218 
219 	if (id.Length() > 0) {
220 		int32 children = fInfo.size();
221 		for (int32 i = 0; i < children; i++) {
222 			if (id == fInfo[i]->MessageID()) {
223 				NotificationView* oldView = fInfo[i];
224 				oldView->RemoveSelf();
225 				delete oldView;
226 				fInfo[i] = view;
227 				found = true;
228 				break;
229 			}
230 		}
231 	}
232 
233 	// Invalidate all children to show or hide the close buttons in the
234 	// notification view
235 	int32 children = fInfo.size();
236 	for (int32 i = 0; i < children; i++) {
237 		fInfo[i]->Invalidate();
238 	}
239 
240 	if (!found) {
241 		fInfo.push_back(view);
242 	}
243 	GetLayout()->AddView(view);
244 
245 	if (IsHidden())
246 		Show();
247 	if (view->IsHidden(view) && !fCollapsed)
248 		view->Show();
249 }
250 
251 
252 void
253 AppGroupView::SetPreviewModeOn(bool enabled)
254 {
255 	fPreviewModeOn = enabled;
256 }
257 
258 
259 const BString&
260 AppGroupView::Group() const
261 {
262 	return fLabel;
263 }
264 
265 
266 void
267 AppGroupView::SetGroup(const char* group)
268 {
269 	fLabel.SetTo(group);
270 	Invalidate();
271 }
272 
273 
274 bool
275 AppGroupView::HasChildren()
276 {
277 	return !fInfo.empty();
278 }
279 
280 
281 int32
282 AppGroupView::ChildrenCount()
283 {
284 	return fInfo.size();
285 }
286