OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 "chrome/browser/chromeos/login/user_controller.h" | 5 #include "chrome/browser/chromeos/login/user_controller.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "app/l10n_util.h" | 10 #include "app/l10n_util.h" |
(...skipping 11 matching lines...) Expand all Loading... |
22 #include "chrome/browser/chromeos/login/wizard_controller.h" | 22 #include "chrome/browser/chromeos/login/wizard_controller.h" |
23 #include "chrome/common/notification_service.h" | 23 #include "chrome/common/notification_service.h" |
24 #include "chrome/common/notification_type.h" | 24 #include "chrome/common/notification_type.h" |
25 #include "cros/chromeos_wm_ipc_enums.h" | 25 #include "cros/chromeos_wm_ipc_enums.h" |
26 #include "gfx/canvas.h" | 26 #include "gfx/canvas.h" |
27 #include "grit/generated_resources.h" | 27 #include "grit/generated_resources.h" |
28 #include "grit/theme_resources.h" | 28 #include "grit/theme_resources.h" |
29 #include "views/background.h" | 29 #include "views/background.h" |
30 #include "views/controls/button/native_button.h" | 30 #include "views/controls/button/native_button.h" |
31 #include "views/controls/label.h" | 31 #include "views/controls/label.h" |
| 32 #include "views/controls/throbber.h" |
32 #include "views/grid_layout.h" | 33 #include "views/grid_layout.h" |
33 #include "views/painter.h" | 34 #include "views/painter.h" |
34 #include "views/screen.h" | 35 #include "views/screen.h" |
35 #include "views/widget/root_view.h" | 36 #include "views/widget/root_view.h" |
36 #include "views/widget/widget_gtk.h" | 37 #include "views/widget/widget_gtk.h" |
37 | 38 |
38 using views::ColumnSet; | 39 using views::ColumnSet; |
39 using views::GridLayout; | 40 using views::GridLayout; |
40 using views::WidgetGtk; | 41 using views::WidgetGtk; |
41 | 42 |
(...skipping 21 matching lines...) Expand all Loading... |
63 public: | 64 public: |
64 ClickNotifyingWidget(views::WidgetGtk::Type type, | 65 ClickNotifyingWidget(views::WidgetGtk::Type type, |
65 UserController* controller) | 66 UserController* controller) |
66 : WidgetGtk(type), | 67 : WidgetGtk(type), |
67 controller_(controller) { | 68 controller_(controller) { |
68 } | 69 } |
69 | 70 |
70 private: | 71 private: |
71 gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { | 72 gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { |
72 if (!controller_->is_user_selected()) | 73 if (!controller_->is_user_selected()) |
73 controller_->SelectUser(controller_->user_index()); | 74 controller_->SelectUserRelative(0); |
74 | 75 |
75 return views::WidgetGtk::OnButtonPress(widget, event); | 76 return views::WidgetGtk::OnButtonPress(widget, event); |
76 } | 77 } |
77 | 78 |
78 UserController* controller_; | 79 UserController* controller_; |
79 | 80 |
80 DISALLOW_COPY_AND_ASSIGN(ClickNotifyingWidget); | 81 DISALLOW_COPY_AND_ASSIGN(ClickNotifyingWidget); |
81 }; | 82 }; |
82 | 83 |
83 void CloseWindow(views::WidgetGtk* window) { | 84 void CloseWindow(views::WidgetGtk* window) { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
137 is_guest_(is_guest), | 138 is_guest_(is_guest), |
138 is_owner_(false), | 139 is_owner_(false), |
139 show_name_tooltip_(false), | 140 show_name_tooltip_(false), |
140 delegate_(delegate), | 141 delegate_(delegate), |
141 controls_window_(NULL), | 142 controls_window_(NULL), |
142 image_window_(NULL), | 143 image_window_(NULL), |
143 border_window_(NULL), | 144 border_window_(NULL), |
144 label_window_(NULL), | 145 label_window_(NULL), |
145 unselected_label_window_(NULL), | 146 unselected_label_window_(NULL), |
146 user_view_(NULL), | 147 user_view_(NULL), |
147 new_user_view_(NULL), | |
148 existing_user_view_(NULL), | |
149 guest_user_view_(NULL), | |
150 label_view_(NULL), | 148 label_view_(NULL), |
151 unselected_label_view_(NULL), | 149 unselected_label_view_(NULL), |
| 150 user_input_(NULL), |
| 151 throbber_host_(NULL), |
152 method_factory_(this) { | 152 method_factory_(this) { |
153 registrar_.Add( | 153 registrar_.Add( |
154 this, | 154 this, |
155 NotificationType::LOGIN_USER_IMAGE_CHANGED, | 155 NotificationType::LOGIN_USER_IMAGE_CHANGED, |
156 NotificationService::AllSources()); | 156 NotificationService::AllSources()); |
157 } | 157 } |
158 | 158 |
159 UserController::UserController(Delegate* delegate, | 159 UserController::UserController(Delegate* delegate, |
160 const UserManager::User& user) | 160 const UserManager::User& user) |
161 : user_index_(-1), | 161 : user_index_(-1), |
162 is_user_selected_(false), | 162 is_user_selected_(false), |
163 is_new_user_(false), | 163 is_new_user_(false), |
164 is_guest_(false), | 164 is_guest_(false), |
165 // Empty 'cached_owner()' means that owner hasn't been cached yet, not | 165 // Empty 'cached_owner()' means that owner hasn't been cached yet, not |
166 // that owner has an empty email. | 166 // that owner has an empty email. |
167 is_owner_(user.email() == UserCrosSettingsProvider::cached_owner()), | 167 is_owner_(user.email() == UserCrosSettingsProvider::cached_owner()), |
168 show_name_tooltip_(false), | 168 show_name_tooltip_(false), |
169 user_(user), | 169 user_(user), |
170 delegate_(delegate), | 170 delegate_(delegate), |
171 controls_window_(NULL), | 171 controls_window_(NULL), |
172 image_window_(NULL), | 172 image_window_(NULL), |
173 border_window_(NULL), | 173 border_window_(NULL), |
174 label_window_(NULL), | 174 label_window_(NULL), |
175 unselected_label_window_(NULL), | 175 unselected_label_window_(NULL), |
176 user_view_(NULL), | 176 user_view_(NULL), |
177 new_user_view_(NULL), | |
178 existing_user_view_(NULL), | |
179 guest_user_view_(NULL), | |
180 label_view_(NULL), | 177 label_view_(NULL), |
181 unselected_label_view_(NULL), | 178 unselected_label_view_(NULL), |
| 179 user_input_(NULL), |
| 180 throbber_host_(NULL), |
182 method_factory_(this) { | 181 method_factory_(this) { |
183 DCHECK(!user.email().empty()); | 182 DCHECK(!user.email().empty()); |
184 registrar_.Add( | 183 registrar_.Add( |
185 this, | 184 this, |
186 NotificationType::LOGIN_USER_IMAGE_CHANGED, | 185 NotificationType::LOGIN_USER_IMAGE_CHANGED, |
187 NotificationService::AllSources()); | 186 NotificationService::AllSources()); |
188 } | 187 } |
189 | 188 |
190 UserController::~UserController() { | 189 UserController::~UserController() { |
191 // Reset the widget delegate of every window to NULL, so the user | 190 // Reset the widget delegate of every window to NULL, so the user |
(...skipping 14 matching lines...) Expand all Loading... |
206 controls_window_ = | 205 controls_window_ = |
207 CreateControlsWindow(index, &controls_width, &controls_height, | 206 CreateControlsWindow(index, &controls_width, &controls_height, |
208 need_browse_without_signin); | 207 need_browse_without_signin); |
209 image_window_ = CreateImageWindow(index); | 208 image_window_ = CreateImageWindow(index); |
210 CreateBorderWindow(index, total_user_count, controls_width, controls_height); | 209 CreateBorderWindow(index, total_user_count, controls_width, controls_height); |
211 label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_LABEL); | 210 label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_LABEL); |
212 unselected_label_window_ = | 211 unselected_label_window_ = |
213 CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL); | 212 CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL); |
214 } | 213 } |
215 | 214 |
216 void UserController::SetPasswordEnabled(bool enable) { | 215 void UserController::StartThrobber() { |
217 DCHECK(!is_new_user_); | 216 throbber_host_->StartThrobber(); |
218 existing_user_view_->password_field()->SetEnabled(enable); | 217 } |
219 if (enable) { | 218 |
220 user_view_->StopThrobber(); | 219 void UserController::StopThrobber() { |
221 delegate_->SetStatusAreaEnabled(enable); | 220 throbber_host_->StopThrobber(); |
222 } else { | |
223 delegate_->SetStatusAreaEnabled(enable); | |
224 user_view_->StartThrobber(); | |
225 } | |
226 } | 221 } |
227 | 222 |
228 std::wstring UserController::GetNameTooltip() const { | 223 std::wstring UserController::GetNameTooltip() const { |
229 if (is_new_user_) | 224 if (is_new_user_) |
230 return l10n_util::GetString(IDS_ADD_USER); | 225 return l10n_util::GetString(IDS_ADD_USER); |
231 if (is_guest_) | 226 if (is_guest_) |
232 return l10n_util::GetString(IDS_GO_INCOGNITO_BUTTON); | 227 return l10n_util::GetString(IDS_GO_INCOGNITO_BUTTON); |
233 | 228 |
234 // Tooltip contains user's display name and his email domain to distinguish | 229 // Tooltip contains user's display name and his email domain to distinguish |
235 // this user from the other one with the same display name. | 230 // this user from the other one with the same display name. |
236 const std::string& email = user_.email(); | 231 const std::string& email = user_.email(); |
237 size_t at_pos = email.rfind('@'); | 232 size_t at_pos = email.rfind('@'); |
238 if (at_pos == std::string::npos) { | 233 if (at_pos == std::string::npos) { |
239 NOTREACHED(); | 234 NOTREACHED(); |
240 return std::wstring(); | 235 return std::wstring(); |
241 } | 236 } |
242 size_t domain_start = at_pos + 1; | 237 size_t domain_start = at_pos + 1; |
243 std::string domain = email.substr(domain_start, | 238 std::string domain = email.substr(domain_start, |
244 email.length() - domain_start); | 239 email.length() - domain_start); |
245 return UTF8ToWide(base::StringPrintf("%s (%s)", | 240 return UTF8ToWide(base::StringPrintf("%s (%s)", |
246 user_.GetDisplayName().c_str(), | 241 user_.GetDisplayName().c_str(), |
247 domain.c_str())); | 242 domain.c_str())); |
248 } | 243 } |
249 | 244 |
250 void UserController::ClearAndEnablePassword() { | 245 void UserController::ClearAndEnableFields() { |
251 if (is_new_user_) { | 246 user_input_->ClearAndFocusControls(); |
252 // TODO(avayvod): This code seems not reachable to me. | 247 user_input_->EnableInputControls(true); |
253 new_user_view_->ClearAndEnablePassword(); | 248 SetStatusAreaEnabled(true); |
254 } else { | 249 StopThrobber(); |
255 existing_user_view_->password_field()->SetText(string16()); | |
256 SetPasswordEnabled(true); | |
257 FocusPasswordField(); | |
258 } | |
259 } | 250 } |
260 | 251 |
261 void UserController::ClearAndEnableFields() { | 252 void UserController::ClearAndEnablePassword() { |
262 if (is_new_user_) { | 253 user_input_->ClearAndFocusPassword(); |
263 new_user_view_->ClearAndEnableFields(); | 254 user_input_->EnableInputControls(true); |
264 } else if (is_guest_) { | 255 SetStatusAreaEnabled(true); |
265 guest_user_view_->FocusSignInButton(); | 256 StopThrobber(); |
266 } else { | 257 } |
267 ClearAndEnablePassword(); | 258 |
268 } | 259 gfx::Rect UserController::GetMainInputScreenBounds() const { |
| 260 return user_input_->GetMainInputScreenBounds(); |
269 } | 261 } |
270 | 262 |
271 void UserController::EnableNameTooltip(bool enable) { | 263 void UserController::EnableNameTooltip(bool enable) { |
272 std::wstring tooltip_text; | 264 std::wstring tooltip_text; |
273 if (enable) | 265 if (enable) |
274 tooltip_text = GetNameTooltip(); | 266 tooltip_text = GetNameTooltip(); |
275 | 267 |
276 if (user_view_) | 268 if (user_view_) |
277 user_view_->SetTooltipText(tooltip_text); | 269 user_view_->SetTooltipText(tooltip_text); |
278 if (label_view_) | 270 if (label_view_) |
279 label_view_->SetTooltipText(tooltip_text); | 271 label_view_->SetTooltipText(tooltip_text); |
280 if (unselected_label_view_) | 272 if (unselected_label_view_) |
281 unselected_label_view_->SetTooltipText(tooltip_text); | 273 unselected_label_view_->SetTooltipText(tooltip_text); |
282 } | 274 } |
283 | 275 |
284 void UserController::ButtonPressed(views::Button* sender, | |
285 const views::Event& event) { | |
286 Login(); | |
287 } | |
288 | |
289 bool UserController::HandleKeystroke( | |
290 views::Textfield* sender, | |
291 const views::Textfield::Keystroke& keystroke) { | |
292 if (keystroke.GetKeyboardCode() == app::VKEY_RETURN) { | |
293 Login(); | |
294 return true; | |
295 } else if (keystroke.GetKeyboardCode() == app::VKEY_LEFT) { | |
296 SelectUser(user_index() - 1); | |
297 return true; | |
298 } else if (keystroke.GetKeyboardCode() == app::VKEY_RIGHT) { | |
299 SelectUser(user_index() + 1); | |
300 return true; | |
301 } | |
302 delegate_->ClearErrors(); | |
303 return false; | |
304 } | |
305 | |
306 void UserController::ContentsChanged(views::Textfield* sender, | |
307 const string16& new_contents) { | |
308 } | |
309 | |
310 void UserController::Observe( | 276 void UserController::Observe( |
311 NotificationType type, | 277 NotificationType type, |
312 const NotificationSource& source, | 278 const NotificationSource& source, |
313 const NotificationDetails& details) { | 279 const NotificationDetails& details) { |
314 if (type != NotificationType::LOGIN_USER_IMAGE_CHANGED || | 280 if (type != NotificationType::LOGIN_USER_IMAGE_CHANGED || |
315 !user_view_) | 281 !user_view_) |
316 return; | 282 return; |
317 | 283 |
318 UserManager::User* user = Details<UserManager::User>(details).ptr(); | 284 UserManager::User* user = Details<UserManager::User>(details).ptr(); |
319 if (user_.email() != user->email()) | 285 if (user_.email() != user->email()) |
320 return; | 286 return; |
321 | 287 |
322 user_.set_image(user->image()); | 288 user_.set_image(user->image()); |
323 user_view_->SetImage(user_.image(), user_.image()); | 289 user_view_->SetImage(user_.image(), user_.image()); |
324 } | 290 } |
325 | 291 |
326 void UserController::Login() { | |
327 if (is_guest_) { | |
328 delegate_->LoginOffTheRecord(); | |
329 } else { | |
330 // Delegate will reenable as necessary. | |
331 SetPasswordEnabled(false); | |
332 | |
333 delegate_->Login(this, existing_user_view_->password_field()->text()); | |
334 } | |
335 } | |
336 | |
337 void UserController::IsActiveChanged(bool active) { | 292 void UserController::IsActiveChanged(bool active) { |
338 is_user_selected_ = active; | 293 is_user_selected_ = active; |
339 if (active) { | 294 if (active) { |
340 delegate_->OnUserSelected(this); | 295 delegate_->OnUserSelected(this); |
341 user_view_->SetRemoveButtonVisible( | 296 user_view_->SetRemoveButtonVisible( |
342 !is_new_user_ && !is_guest_ && !is_owner_); | 297 !is_new_user_ && !is_guest_ && !is_owner_); |
343 } else { | 298 } else { |
344 user_view_->SetRemoveButtonVisible(false); | 299 user_view_->SetRemoveButtonVisible(false); |
345 delegate_->ClearErrors(); | 300 delegate_->ClearErrors(); |
346 } | 301 } |
(...skipping 21 matching lines...) Expand all Loading... |
368 | 323 |
369 window->Show(); | 324 window->Show(); |
370 } | 325 } |
371 | 326 |
372 WidgetGtk* UserController::CreateControlsWindow( | 327 WidgetGtk* UserController::CreateControlsWindow( |
373 int index, | 328 int index, |
374 int* width, int* height, | 329 int* width, int* height, |
375 bool need_browse_without_signin) { | 330 bool need_browse_without_signin) { |
376 views::View* control_view; | 331 views::View* control_view; |
377 if (is_new_user_) { | 332 if (is_new_user_) { |
378 new_user_view_ = | 333 NewUserView* new_user_view = |
379 new NewUserView(this, true, need_browse_without_signin); | 334 new NewUserView(this, true, need_browse_without_signin); |
380 new_user_view_->Init(); | 335 new_user_view->Init(); |
381 control_view = new_user_view_; | 336 control_view = new_user_view; |
| 337 user_input_ = new_user_view; |
| 338 throbber_host_ = new_user_view; |
382 } else if (is_guest_) { | 339 } else if (is_guest_) { |
383 guest_user_view_ = new GuestUserView(this); | 340 GuestUserView* guest_user_view = new GuestUserView(this); |
384 guest_user_view_->RecreateFields(); | 341 guest_user_view->RecreateFields(); |
385 control_view = guest_user_view_; | 342 control_view = guest_user_view; |
| 343 user_input_ = guest_user_view; |
| 344 throbber_host_ = guest_user_view; |
386 } else { | 345 } else { |
387 existing_user_view_ = new ExistingUserView(this); | 346 ExistingUserView* existing_user_view = new ExistingUserView(this); |
388 existing_user_view_->RecreateFields(); | 347 existing_user_view->RecreateFields(); |
389 control_view = existing_user_view_; | 348 control_view = existing_user_view; |
| 349 user_input_ = existing_user_view; |
| 350 throbber_host_ = existing_user_view; |
390 } | 351 } |
391 | 352 |
392 *height = kControlsHeight; | 353 *height = kControlsHeight; |
393 *width = kUserImageSize; | 354 *width = kUserImageSize; |
394 if (is_new_user_) { | 355 if (is_new_user_) { |
395 DCHECK(new_user_view_); | 356 gfx::Size size = control_view->GetPreferredSize(); |
396 gfx::Size size = new_user_view_->GetPreferredSize(); | |
397 *width = size.width(); | 357 *width = size.width(); |
398 *height = size.height(); | 358 *height = size.height(); |
399 } | 359 } |
400 | 360 |
401 WidgetGtk* window = new WidgetGtk(WidgetGtk::TYPE_WINDOW); | 361 WidgetGtk* window = new WidgetGtk(WidgetGtk::TYPE_WINDOW); |
402 ConfigureLoginWindow(window, | 362 ConfigureLoginWindow(window, |
403 index, | 363 index, |
404 gfx::Rect(*width, *height), | 364 gfx::Rect(*width, *height), |
405 WM_IPC_WINDOW_LOGIN_CONTROLS, | 365 WM_IPC_WINDOW_LOGIN_CONTROLS, |
406 control_view); | 366 control_view); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
516 login::kSelectedLabelHeight : login::kUnselectedLabelHeight; | 476 login::kSelectedLabelHeight : login::kUnselectedLabelHeight; |
517 WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this); | 477 WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this); |
518 ConfigureLoginWindow(window, | 478 ConfigureLoginWindow(window, |
519 index, | 479 index, |
520 gfx::Rect(0, 0, width, height), | 480 gfx::Rect(0, 0, width, height), |
521 type, | 481 type, |
522 label); | 482 label); |
523 return window; | 483 return window; |
524 } | 484 } |
525 | 485 |
526 gfx::Rect UserController::GetScreenBounds() const { | |
527 if (is_new_user_) | |
528 return new_user_view_->GetUsernameBounds(); | |
529 else | |
530 return existing_user_view_->password_field()->GetScreenBounds(); | |
531 } | |
532 | |
533 void UserController::OnLogin(const std::string& username, | 486 void UserController::OnLogin(const std::string& username, |
534 const std::string& password) { | 487 const std::string& password) { |
535 user_.set_email(username); | 488 if (is_new_user_) |
| 489 user_.set_email(username); |
| 490 |
| 491 user_input_->EnableInputControls(false); |
| 492 SetStatusAreaEnabled(false); |
| 493 StartThrobber(); |
| 494 |
536 delegate_->Login(this, UTF8ToUTF16(password)); | 495 delegate_->Login(this, UTF8ToUTF16(password)); |
537 } | 496 } |
538 | 497 |
539 void UserController::OnCreateAccount() { | 498 void UserController::OnCreateAccount() { |
540 delegate_->ActivateWizard(WizardController::kAccountScreenName); | 499 delegate_->ActivateWizard(WizardController::kAccountScreenName); |
541 } | 500 } |
542 | 501 |
543 void UserController::OnLoginOffTheRecord() { | 502 void UserController::OnLoginOffTheRecord() { |
| 503 user_input_->EnableInputControls(false); |
| 504 SetStatusAreaEnabled(false); |
| 505 StartThrobber(); |
| 506 |
544 delegate_->LoginOffTheRecord(); | 507 delegate_->LoginOffTheRecord(); |
545 } | 508 } |
546 | 509 |
547 void UserController::ClearErrors() { | 510 void UserController::ClearErrors() { |
548 delegate_->ClearErrors(); | 511 delegate_->ClearErrors(); |
549 } | 512 } |
550 | 513 |
551 void UserController::NavigateAway() { | 514 void UserController::NavigateAway() { |
552 SelectUser(user_index() - 1); | 515 SelectUserRelative(-1); |
553 } | 516 } |
554 | 517 |
555 void UserController::OnRemoveUser() { | 518 void UserController::OnRemoveUser() { |
556 // Must not proceed without signature verification. | 519 // Must not proceed without signature verification. |
557 UserCrosSettingsProvider user_settings; | 520 UserCrosSettingsProvider user_settings; |
558 bool trusted_owner_available = user_settings.RequestTrustedOwner( | 521 bool trusted_owner_available = user_settings.RequestTrustedOwner( |
559 method_factory_.NewRunnableMethod(&UserController::OnRemoveUser)); | 522 method_factory_.NewRunnableMethod(&UserController::OnRemoveUser)); |
560 if (!trusted_owner_available) { | 523 if (!trusted_owner_available) { |
561 // Value of owner email is still not verified. | 524 // Value of owner email is still not verified. |
562 // Another attempt will be invoked after verification completion. | 525 // Another attempt will be invoked after verification completion. |
563 return; | 526 return; |
564 } | 527 } |
565 if (user().email() == UserCrosSettingsProvider::cached_owner()) { | 528 if (user().email() == UserCrosSettingsProvider::cached_owner()) { |
566 // Owner is not allowed to be removed from the device. | 529 // Owner is not allowed to be removed from the device. |
567 return; | 530 return; |
568 } | 531 } |
569 delegate_->RemoveUser(this); | 532 delegate_->RemoveUser(this); |
570 } | 533 } |
571 | 534 |
572 void UserController::SelectUser(int index) { | 535 void UserController::SelectUserRelative(int shift) { |
573 delegate_->SelectUser(index); | 536 delegate_->SelectUser(user_index() + shift); |
574 } | |
575 | |
576 void UserController::FocusPasswordField() { | |
577 existing_user_view_->FocusPasswordField(); | |
578 } | 537 } |
579 | 538 |
580 } // namespace chromeos | 539 } // namespace chromeos |
OLD | NEW |