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