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