OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/message_center/message_view_multiple.h" | 5 #include "ui/message_center/message_view_multiple.h" |
6 | 6 |
7 #include "base/utf_string_conversions.h" | 7 #include "base/utf_string_conversions.h" |
8 #include "grit/ui_resources.h" | 8 #include "grit/ui_resources.h" |
9 #include "ui/base/accessibility/accessible_view_state.h" | 9 #include "ui/base/accessibility/accessible_view_state.h" |
10 #include "ui/base/resource/resource_bundle.h" | 10 #include "ui/base/resource/resource_bundle.h" |
11 #include "ui/base/text/text_elider.h" | 11 #include "ui/base/text/text_elider.h" |
12 #include "ui/gfx/canvas.h" | 12 #include "ui/gfx/canvas.h" |
13 #include "ui/gfx/size.h" | 13 #include "ui/gfx/size.h" |
| 14 #include "ui/message_center/message_center_constants.h" |
14 #include "ui/native_theme/native_theme.h" | 15 #include "ui/native_theme/native_theme.h" |
15 #include "ui/views/controls/button/image_button.h" | 16 #include "ui/views/controls/button/image_button.h" |
16 #include "ui/views/controls/image_view.h" | 17 #include "ui/views/controls/image_view.h" |
17 #include "ui/views/controls/label.h" | 18 #include "ui/views/controls/label.h" |
| 19 #include "ui/views/layout/box_layout.h" |
18 #include "ui/views/layout/grid_layout.h" | 20 #include "ui/views/layout/grid_layout.h" |
19 | 21 |
20 namespace { | 22 namespace { |
21 | 23 |
22 // Notification dimensions. | 24 // Notification dimensions. |
23 const int kNotificationPrimaryIconSize = 64; | 25 const int kIconLeftPadding = 0; |
24 const int kNotificationSecondaryIconSize = 15; | 26 const int kIconColumnWidth = message_center::kNotificationIconWidth; |
25 const int kNotificationPadding1Width = 12; | 27 const int kIconToTextPadding = 15; |
26 const int kNotificationColumn1Width = kNotificationPrimaryIconSize; | 28 const int kTextToClosePadding = 10; |
27 const int kNotificationPadding2Width = 12; | 29 const int kCloseColumnWidth = 8; |
28 const int kNotificationPadding3Width = 12; | 30 const int kCloseRightPadding = 6; |
29 const int kNotificationColumn3Width = 26; | 31 const int kIconTopPadding = 0; |
30 const int kNotificationPadding4Width = 10; | 32 const int kTextTopPadding = 9; |
31 const int kNotificationColumn4Width = 8; | 33 const int kCloseTopPadding = 6; |
32 const int kNotificationPadding5Width = 8; | 34 const int kIconBottomPadding = 0; |
33 const int kNotificationColumn1Top = 12; | 35 const int kTextBottomPadding = 12; |
34 const int kNotificationColumn2Top = 9; | 36 const int kItemTitleToDetailsPadding = 3; |
35 const int kNotificationColumn3Top = 4; | |
36 const int kNotificationColumn4Top = 8; | |
37 const int kNotificationPaddingBottom = 19; | |
38 const int kNotificationItemInternalPadding = 12; | |
39 | 37 |
40 // The text background colors below are used only to prevent view::Label from | 38 // Notification colors. The text background colors below are used only to keep |
41 // modifying the text color and will never be used for drawing. See | 39 // view::Label from modifying the text color and will not actually be drawn. |
42 // view::Label's SetEnabledColor() and SetBackgroundColor() for details. | 40 // See view::Label's SetEnabledColor() and SetBackgroundColor() for details. |
43 const SkColor kNotificationBackgroundColor = SkColorSetRGB(254, 254, 254); | 41 const SkColor kBackgroundColor = SkColorSetRGB(255, 255, 255); |
44 const SkColor kNotificationReadBackgroundColor = SkColorSetRGB(250, 250, 250); | 42 const SkColor kTitleColor = SkColorSetRGB(68, 68, 68); |
45 const SkColor kNotificationTitleColor = SkColorSetRGB(68, 68, 68); | 43 const SkColor kTitleBackgroundColor = SK_ColorWHITE; |
46 const SkColor kNotificationTitleBackgroundColor = SK_ColorWHITE; | 44 const SkColor kMessageColor = SkColorSetRGB(136, 136, 136); |
47 const SkColor kNotificationMessageColor = SkColorSetRGB(136, 136, 136); | 45 const SkColor kMessageBackgroundColor = SK_ColorBLACK; |
48 const SkColor kNotificationMessageBackgroundColor = SK_ColorBLACK; | 46 |
49 const SkColor kNotificationTimeColor = SkColorSetRGB(176, 176, 176); | 47 // Static function to create an empty border to be used as padding. |
50 const SkColor kNotificationTimeBackgroundColor = SK_ColorBLACK; | 48 views::Border* MakePadding(int top, int left, int bottom, int right) { |
51 const SkColor kNotificationItemTitleColor = SkColorSetRGB(68, 68, 68); | 49 return views::Border::CreateEmptyBorder(top, left, bottom, right); |
52 const SkColor kNotificationItemMessageColor = SkColorSetRGB(136, 136, 136); | 50 } |
53 const SkColor kNotificationSeparatorColor = SkColorSetRGB(226, 226, 226); | |
54 | 51 |
55 // ItemViews are responsible for drawing each MessageViewMultiple item's title | 52 // ItemViews are responsible for drawing each MessageViewMultiple item's title |
56 // and message next to each other within a single column. | 53 // and message next to each other within a single column. |
57 class ItemView : public views::View { | 54 class ItemView : public views::View { |
58 public: | 55 public: |
59 ItemView(const message_center::NotificationList::NotificationItem& item) | 56 ItemView(const message_center::NotificationList::NotificationItem& item); |
60 : item_(item), preferred_size_(0, 0) { | 57 virtual ~ItemView(); |
61 gfx::Font font = GetDefaultFont(); | |
62 preferred_size_.Enlarge(font.GetStringWidth(item.title), 0); | |
63 preferred_size_.Enlarge(kNotificationItemInternalPadding, 0); | |
64 preferred_size_.Enlarge(font.GetStringWidth(item.message), 0); | |
65 preferred_size_.set_height(font.GetHeight()); | |
66 PreferredSizeChanged(); | |
67 SchedulePaint(); | |
68 } | |
69 | |
70 // Overridden from views::View. | |
71 virtual gfx::Size GetPreferredSize() OVERRIDE; | |
72 virtual int GetBaseline() const OVERRIDE; | |
73 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; | |
74 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; | |
75 virtual bool GetTooltipText(const gfx::Point& point, | |
76 string16* tooltip) const OVERRIDE; | |
77 | |
78 protected: | |
79 // Overridden from views::View. | |
80 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; | |
81 | 58 |
82 private: | 59 private: |
83 static gfx::Font GetDefaultFont(); | |
84 | |
85 message_center::NotificationList::NotificationItem item_; | |
86 gfx::Size preferred_size_; | |
87 | |
88 DISALLOW_COPY_AND_ASSIGN(ItemView); | 60 DISALLOW_COPY_AND_ASSIGN(ItemView); |
89 }; | 61 }; |
90 | 62 |
91 gfx::Size ItemView::GetPreferredSize() { | 63 ItemView::ItemView( |
92 return preferred_size_; | 64 const message_center::NotificationList::NotificationItem& item) { |
| 65 views::BoxLayout* layout = |
| 66 new views::BoxLayout(views::BoxLayout::kHorizontal, |
| 67 0, 0, kItemTitleToDetailsPadding); |
| 68 SetLayoutManager(layout); |
| 69 |
| 70 views::Label* title = new views::Label(item.title); |
| 71 title->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 72 title->SetElideBehavior(views::Label::ELIDE_AT_END); |
| 73 title->SetEnabledColor(kTitleColor); |
| 74 title->SetBackgroundColor(kTitleBackgroundColor); |
| 75 AddChildViewAt(title, 0); |
| 76 |
| 77 views::Label* details = new views::Label(item.message); |
| 78 details->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 79 details->SetElideBehavior(views::Label::ELIDE_AT_END); |
| 80 details->SetEnabledColor(kMessageColor); |
| 81 details->SetBackgroundColor(kMessageBackgroundColor); |
| 82 AddChildViewAt(details, 1); |
| 83 |
| 84 PreferredSizeChanged(); |
| 85 SchedulePaint(); |
93 } | 86 } |
94 | 87 |
95 int ItemView::GetBaseline() const { | 88 ItemView::~ItemView() { |
96 return GetDefaultFont().GetBaseline(); | |
97 } | |
98 | |
99 bool ItemView::HitTestRect(const gfx::Rect& rect) const { | |
100 return false; | |
101 } | |
102 | |
103 void ItemView::GetAccessibleState( | |
104 ui::AccessibleViewState* state) { | |
105 state->role = ui::AccessibilityTypes::ROLE_STATICTEXT; | |
106 state->state = ui::AccessibilityTypes::STATE_READONLY; | |
107 state->name = item_.message; // TODO(dharcourt): Include title localizably. | |
108 } | |
109 | |
110 bool ItemView::GetTooltipText(const gfx::Point& point, | |
111 string16* tooltip) const { | |
112 if (preferred_size_.width() > width()) { | |
113 *tooltip = item_.message; // TODO(dharcourt): Include title localizably. | |
114 return true; | |
115 } | |
116 return false; | |
117 } | |
118 | |
119 void ItemView::OnPaint(gfx::Canvas* canvas) { | |
120 OnPaintBackground(canvas); | |
121 OnPaintBorder(canvas); | |
122 OnPaintFocusBorder(canvas); | |
123 | |
124 gfx::Font font = GetDefaultFont(); | |
125 int y = std::max(0, height() - preferred_size_.height()) / 2; | |
126 canvas->DrawStringInt(item_.title, font, kNotificationItemTitleColor, | |
127 0, y, width(), preferred_size_.height()); | |
128 | |
129 int x = font.GetStringWidth(item_.title) + kNotificationItemInternalPadding; | |
130 if (x < width()) { | |
131 canvas->DrawStringInt( | |
132 ui::ElideText(item_.message, font, width() - x, ui::ELIDE_AT_END), | |
133 font, kNotificationItemMessageColor, | |
134 x, y, width() - x, preferred_size_.height()); | |
135 } | |
136 } | |
137 | |
138 // static | |
139 gfx::Font ItemView::GetDefaultFont() { | |
140 return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); | |
141 } | |
142 | |
143 // BoxView draws a color rectangle or just reserves some space. | |
144 class BoxView : public views::View { | |
145 public: | |
146 BoxView(int width, int height, SkColor color=SkColorSetARGB(0, 0, 0, 0)) | |
147 : size_(width, height) { | |
148 if (SkColorGetA(color) > 0) | |
149 set_background(views::Background::CreateSolidBackground(color)); | |
150 PreferredSizeChanged(); | |
151 SchedulePaint(); | |
152 } | |
153 | |
154 // Overridden from View: | |
155 virtual gfx::Size GetPreferredSize() OVERRIDE; | |
156 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; | |
157 | |
158 private: | |
159 gfx::Size size_; | |
160 | |
161 DISALLOW_COPY_AND_ASSIGN(BoxView); | |
162 }; | |
163 | |
164 gfx::Size BoxView::GetPreferredSize() { | |
165 return size_; | |
166 } | |
167 | |
168 bool BoxView::HitTestRect(const gfx::Rect& rect) const { | |
169 return false; | |
170 } | 89 } |
171 | 90 |
172 } // namespace | 91 } // namespace |
173 | 92 |
174 namespace message_center { | 93 namespace message_center { |
175 | 94 |
176 MessageViewMultiple::MessageViewMultiple( | 95 MessageViewMultiple::MessageViewMultiple( |
177 NotificationList::Delegate* list_delegate, | 96 NotificationList::Delegate* list_delegate, |
178 const NotificationList::Notification& notification) | 97 const NotificationList::Notification& notification) |
179 : MessageView(list_delegate, notification) {} | 98 : MessageView(list_delegate, notification) { |
| 99 } |
180 | 100 |
181 MessageViewMultiple::MessageViewMultiple() {} | 101 MessageViewMultiple::~MessageViewMultiple() { |
182 | 102 } |
183 MessageViewMultiple::~MessageViewMultiple() {} | |
184 | 103 |
185 // TODO(dharcourt): Make this a subclass of BaseFormatView or of a | 104 // TODO(dharcourt): Make this a subclass of BaseFormatView or of a |
186 // BaseFormatView superclass and leverage that class' SetUpView(). | 105 // BaseFormatView superclass and leverage that class' SetUpView(). |
187 void MessageViewMultiple::SetUpView() { | 106 void MessageViewMultiple::SetUpView() { |
188 | 107 set_background(views::Background::CreateSolidBackground(kBackgroundColor)); |
189 SkColor background = notification_.is_read ? | |
190 kNotificationReadBackgroundColor : kNotificationBackgroundColor; | |
191 set_background(views::Background::CreateSolidBackground(background)); | |
192 | 108 |
193 views::GridLayout* layout = new views::GridLayout(this); | 109 views::GridLayout* layout = new views::GridLayout(this); |
194 SetLayoutManager(layout); | 110 SetLayoutManager(layout); |
195 | 111 |
196 // Four columns (icon, messages, time, close) surrounded by padding. The icon, | 112 // Three columns (icon, text, close button) surrounded by padding. The icon |
197 // time, and close columns have fixed width and the message column fills up | 113 // and close button columns and the padding have fixed widths and the text |
198 // the remaining space. Inter-column padding is included within columns | 114 // column fills up the remaining space. To minimize the number of columns and |
199 // whenever horizontal allignment allows for it. | 115 // simplify column spanning padding is applied to each view within columns |
| 116 // instead of through padding columns. |
200 views::ColumnSet* columns = layout->AddColumnSet(0); | 117 views::ColumnSet* columns = layout->AddColumnSet(0); |
201 columns->AddPaddingColumn(0, kNotificationPadding1Width); | 118 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
202 columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING, | |
203 0, views::GridLayout::FIXED, | 119 0, views::GridLayout::FIXED, |
204 kNotificationColumn1Width, kNotificationColumn1Width); | 120 kIconLeftPadding + kIconColumnWidth + kIconToTextPadding, |
205 columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING, | 121 kIconLeftPadding + kIconColumnWidth + kIconToTextPadding); |
206 0, views::GridLayout::FIXED, | 122 // Padding + icon + padding. |
207 kNotificationPadding2Width, kNotificationPadding2Width); | |
208 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, | 123 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
209 100, views::GridLayout::USE_PREF, | 124 100, views::GridLayout::USE_PREF, |
210 0, 0); | 125 0, 0); |
211 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::LEADING, | 126 // Text + padding (kTextToClosePadding). |
| 127 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
212 0, views::GridLayout::FIXED, | 128 0, views::GridLayout::FIXED, |
213 kNotificationPadding3Width + kNotificationColumn3Width, | 129 kCloseColumnWidth + kCloseRightPadding, |
214 kNotificationPadding3Width + kNotificationColumn3Width); | 130 kCloseColumnWidth + kCloseRightPadding); |
215 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::LEADING, | 131 // Close button + padding. |
216 0, views::GridLayout::FIXED, | |
217 kNotificationPadding4Width + kNotificationColumn4Width, | |
218 kNotificationPadding4Width + kNotificationColumn4Width); | |
219 columns->AddPaddingColumn(0, kNotificationPadding5Width); | |
220 | 132 |
221 // First row: Primary icon. | 133 // First row: Icon. This vertically spans the close button padding row, the |
| 134 // close button row, and all item rows. |
222 layout->StartRow(0, 0); | 135 layout->StartRow(0, 0); |
223 views::ImageView* primary_icon = new views::ImageView; | 136 views::ImageView* icon = new views::ImageView(); |
224 primary_icon->SetImageSize(gfx::Size(kNotificationPrimaryIconSize, | 137 icon->SetImageSize(gfx::Size(message_center::kNotificationIconWidth, |
225 kNotificationPrimaryIconSize)); | 138 message_center::kNotificationIconWidth)); |
226 primary_icon->SetImage(notification_.primary_icon); | 139 icon->SetImage(notification_.primary_icon); |
227 primary_icon->set_border(CreateTopBorder(kNotificationColumn1Top)); | 140 icon->SetHorizontalAlignment(views::ImageView::LEADING); |
228 primary_icon->SetVerticalAlignment(views::ImageView::LEADING); | 141 icon->SetVerticalAlignment(views::ImageView::LEADING); |
229 layout->AddView(primary_icon, 1, 3 + 2 * notification_.items.size()); | 142 icon->set_border(MakePadding(kIconTopPadding, kIconLeftPadding, |
| 143 kIconBottomPadding, kIconToTextPadding)); |
| 144 layout->AddView(icon, 1, 2 + notification_.items.size()); |
230 | 145 |
231 // First row: Title. | 146 // First row: Title. This vertically spans the close button padding row and |
| 147 // the close button row. |
232 // TODO(dharcourt): Skip the title Label when there's no title text. | 148 // TODO(dharcourt): Skip the title Label when there's no title text. |
233 layout->SkipColumns(1); | |
234 views::Label* title = new views::Label(notification_.title); | 149 views::Label* title = new views::Label(notification_.title); |
235 title->SetHorizontalAlignment(gfx::ALIGN_LEFT); | 150 title->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
236 title->SetFont(title->font().DeriveFont(4)); | 151 title->SetFont(title->font().DeriveFont(4)); |
237 title->SetEnabledColor(kNotificationTitleColor); | 152 title->SetEnabledColor(kTitleColor); |
238 title->SetBackgroundColor(kNotificationTitleBackgroundColor); | 153 title->SetBackgroundColor(kTitleBackgroundColor); |
239 title->set_border(CreateTopBorder(kNotificationColumn2Top)); | 154 title->set_border(MakePadding(kTextTopPadding, 0, 3, kTextToClosePadding)); |
240 layout->AddView(title, 1, 2); | 155 layout->AddView(title, 1, 2); |
241 | 156 |
242 // First row: Time. | 157 // First row: Close button padding. |
243 // TODO(dharcourt): Timestamp as "1m/5m/1h/5h/1d/5d/..." (ago). | 158 views::View* padding = new views::ImageView(); |
244 views::Label* time = new views::Label(UTF8ToUTF16("")); | 159 padding->set_border(MakePadding(kCloseTopPadding, 1, 0, 0)); |
245 time->SetEnabledColor(kNotificationTimeColor); | 160 layout->AddView(padding); |
246 time->SetBackgroundColor(kNotificationTimeBackgroundColor); | |
247 time->set_border(CreateTopBorder(kNotificationColumn3Top)); | |
248 layout->AddView(time, 1, 2); | |
249 | 161 |
250 // First row: Close button padding. | 162 // Second row: Close button, which has to be on a row of its own because its |
251 layout->AddView(new BoxView(1, kNotificationColumn4Top)); | 163 // top padding can't be set using empty borders (ImageButtons don't support |
252 | 164 // borders). The resize factor of this row (100) is much higher than that of |
253 // Second row: Close button, which has to be on a row of its own because it's | 165 // other rows (0) to ensure the first row's height stays at kCloseTopPadding. |
254 // an ImageButton and so its padding can't be set using borders. This row is | |
255 // set to resize vertically to ensure the first row stays at its minimum | |
256 // height of kNotificationColumn4Top. | |
257 layout->StartRow(100, 0); | 166 layout->StartRow(100, 0); |
258 layout->SkipColumns(4); | 167 layout->SkipColumns(2); |
259 DCHECK(close_button_); | 168 DCHECK(close_button_); |
260 layout->AddView(close_button_); | 169 layout->AddView(close_button_); |
261 | 170 |
262 // Rows for each notification item, including appropriate padding. | 171 // One row for each notification item, including appropriate padding. |
263 layout->AddPaddingRow(0, 3); | 172 for (int i = 0, n = notification_.items.size(); i < n; ++i) { |
264 std::vector<NotificationList::NotificationItem>::const_iterator i; | 173 int bottom_padding = (i < n - 1) ? 4 : (kTextBottomPadding - 2); |
265 for (i = notification_.items.begin(); i != notification_.items.end(); ++i) { | 174 layout->StartRow(0, 0); |
266 layout->StartRowWithPadding(0, 0, 0, 3); | 175 layout->SkipColumns(1); |
267 layout->SkipColumns(2); | 176 ItemView* item = new ItemView(notification_.items[i]); |
268 layout->AddView(new ItemView(*i)); | 177 item->set_border(MakePadding(0, 0, bottom_padding, kTextToClosePadding)); |
269 layout->SkipColumns(2); | 178 layout->AddView(item); |
| 179 layout->SkipColumns(1); |
270 } | 180 } |
271 | |
272 // Rows for horizontal separator line with appropriate padding. | |
273 layout->StartRowWithPadding(0, 0, 0, 6); | |
274 layout->SkipColumns(1); | |
275 layout->AddView(new BoxView(1000000, 1, kNotificationSeparatorColor), 4, 1); | |
276 | |
277 // Last row: Summary message with padding. | |
278 // TODO(dharcourt): Skip the message Label when there's no message text. | |
279 layout->StartRowWithPadding(0, 0, 0, 5); | |
280 layout->SkipColumns(2); | |
281 views::Label* message = new views::Label(notification_.message); | |
282 message->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
283 message->SetMultiLine(true); | |
284 message->SetEnabledColor(kNotificationMessageColor); | |
285 message->SetBackgroundColor(kNotificationMessageBackgroundColor); | |
286 message->set_border(CreateTopBorder(1)); | |
287 layout->AddView(message); | |
288 | |
289 // Last row: Secondary icon. | |
290 layout->SkipColumns(1); | |
291 views::ImageView* secondary_icon = new views::ImageView; | |
292 secondary_icon->SetImageSize(gfx::Size(kNotificationSecondaryIconSize, | |
293 kNotificationSecondaryIconSize)); | |
294 secondary_icon->SetImage(notification_.secondary_icon); | |
295 secondary_icon->SetVerticalAlignment(views::ImageView::LEADING); | |
296 layout->AddView(secondary_icon); | |
297 | |
298 // Final row with the bottom padding. | |
299 layout->AddPaddingRow(0, kNotificationPaddingBottom); | |
300 } | |
301 | |
302 views::Border* MessageViewMultiple::CreateTopBorder(int height) { | |
303 return views::Border::CreateEmptyBorder(height, 0, 0, 0); | |
304 } | 181 } |
305 | 182 |
306 } // namespace message_center | 183 } // namespace message_center |
OLD | NEW |