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