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
AppGroupView(const BMessenger & messenger,const char * label)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
Draw(BRect updateRect)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
_DrawCloseButton(const BRect & updateRect)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
MouseDown(BPoint point)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
MessageReceived(BMessage * msg)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
AddInfo(NotificationView * view)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
SetPreviewModeOn(bool enabled)251 AppGroupView::SetPreviewModeOn(bool enabled)
252 {
253 fPreviewModeOn = enabled;
254 }
255
256
257 const BString&
Group() const258 AppGroupView::Group() const
259 {
260 return fLabel;
261 }
262
263
264 void
SetGroup(const char * group)265 AppGroupView::SetGroup(const char* group)
266 {
267 fLabel.SetTo(group);
268 Invalidate();
269 }
270
271
272 bool
HasChildren()273 AppGroupView::HasChildren()
274 {
275 return !fInfo.empty();
276 }
277
278
279 int32
ChildrenCount()280 AppGroupView::ChildrenCount()
281 {
282 return fInfo.size();
283 }
284