Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 #import <Cocoa/Cocoa.h> | 5 #import <Cocoa/Cocoa.h> |
| 6 | 6 |
| 7 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h" | 7 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h" |
| 8 | 8 |
| 9 #include "base/mac/bundle_locations.h" | 9 #include "base/mac/bundle_locations.h" |
| 10 #include "base/prefs/pref_service.h" | 10 #include "base/prefs/pref_service.h" |
| (...skipping 437 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 448 withController:(ProfileChooserController*)controller; | 448 withController:(ProfileChooserController*)controller; |
| 449 | 449 |
| 450 // Called when the "Change" button is clicked. | 450 // Called when the "Change" button is clicked. |
| 451 - (void)editPhoto:(id)sender; | 451 - (void)editPhoto:(id)sender; |
| 452 | 452 |
| 453 // When hovering over the profile photo, show the "Change" button. | 453 // When hovering over the profile photo, show the "Change" button. |
| 454 - (void)mouseEntered:(NSEvent*)event; | 454 - (void)mouseEntered:(NSEvent*)event; |
| 455 | 455 |
| 456 // When hovering away from the profile photo, hide the "Change" button. | 456 // When hovering away from the profile photo, hide the "Change" button. |
| 457 - (void)mouseExited:(NSEvent*)event; | 457 - (void)mouseExited:(NSEvent*)event; |
| 458 | |
| 459 - (BOOL)acceptsFirstResponder; | |
| 460 - (BOOL)canBecomeKeyView; | |
| 461 - (BOOL)accessibilityIsIgnored; | |
| 462 - (NSArray*)accessibilityActionNames; | |
| 463 - (void)accessibilityPerformAction:(NSString*)action; | |
|
groby-ooo-7-16
2014/08/15 00:38:35
No need to declare any of them - they're all imple
noms (inactive)
2014/08/15 19:26:47
Argh, sorry. I never know when to declare and when
groby-ooo-7-16
2014/08/15 19:52:07
This is one of the few places ObjC pretends to not
| |
| 458 @end | 464 @end |
| 459 | 465 |
| 460 @interface EditableProfilePhoto (Private) | 466 @interface EditableProfilePhoto (Private) |
| 461 // Create the "Change" avatar photo button. | 467 // Create the "Change" avatar photo button. |
| 462 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect; | 468 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect; |
| 463 @end | 469 @end |
| 464 | 470 |
| 465 @implementation EditableProfilePhoto | 471 @implementation EditableProfilePhoto |
| 466 - (id)initWithFrame:(NSRect)frameRect | 472 - (id)initWithFrame:(NSRect)frameRect |
| 467 avatarMenu:(AvatarMenu*)avatarMenu | 473 avatarMenu:(AvatarMenu*)avatarMenu |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 483 [self addTrackingArea:trackingArea_.get()]; | 489 [self addTrackingArea:trackingArea_.get()]; |
| 484 | 490 |
| 485 NSRect bounds = NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide); | 491 NSRect bounds = NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide); |
| 486 if (editingAllowed) { | 492 if (editingAllowed) { |
| 487 changePhotoButton_.reset([self changePhotoButtonWithRect:bounds]); | 493 changePhotoButton_.reset([self changePhotoButtonWithRect:bounds]); |
| 488 [self addSubview:changePhotoButton_]; | 494 [self addSubview:changePhotoButton_]; |
| 489 | 495 |
| 490 // Hide the button until the image is hovered over. | 496 // Hide the button until the image is hovered over. |
| 491 [changePhotoButton_ setHidden:YES]; | 497 [changePhotoButton_ setHidden:YES]; |
| 492 } | 498 } |
| 499 | |
| 500 // Set the image cell's accessibility strings to be the same as the | |
| 501 // button's strings. | |
| 502 [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSString( | |
| 503 editingAllowed ? | |
| 504 IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME : | |
| 505 IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME) | |
| 506 forAttribute:NSAccessibilityTitleAttribute]; | |
| 507 [[self cell] accessibilitySetOverrideValue: | |
| 508 editingAllowed ? NSAccessibilityButtonRole : NSAccessibilityImageRole | |
| 509 forAttribute:NSAccessibilityRoleAttribute]; | |
| 510 [[self cell] accessibilitySetOverrideValue: | |
| 511 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) | |
| 512 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
| 513 | |
| 514 // The button and the cell should read the same thing. | |
| 515 [self accessibilitySetOverrideValue:l10n_util::GetNSString( | |
| 516 editingAllowed ? | |
| 517 IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME : | |
| 518 IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME) | |
| 519 forAttribute:NSAccessibilityTitleAttribute]; | |
| 520 [self accessibilitySetOverrideValue:NSAccessibilityButtonRole | |
| 521 forAttribute:NSAccessibilityRoleAttribute]; | |
| 522 [self accessibilitySetOverrideValue: | |
| 523 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) | |
| 524 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
| 493 } | 525 } |
| 494 return self; | 526 return self; |
| 495 } | 527 } |
| 496 | 528 |
| 497 - (void)drawRect:(NSRect)dirtyRect { | 529 - (void)drawRect:(NSRect)dirtyRect { |
| 498 NSRect bounds = [self bounds]; | 530 NSRect bounds = [self bounds]; |
| 499 | 531 |
| 500 // Display the profile picture as a circle. | 532 // Display the profile picture as a circle. |
| 501 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:bounds]; | 533 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:bounds]; |
| 502 [path addClip]; | 534 [path addClip]; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 514 } | 546 } |
| 515 | 547 |
| 516 - (void)mouseEntered:(NSEvent*)event { | 548 - (void)mouseEntered:(NSEvent*)event { |
| 517 [changePhotoButton_ setHidden:NO]; | 549 [changePhotoButton_ setHidden:NO]; |
| 518 } | 550 } |
| 519 | 551 |
| 520 - (void)mouseExited:(NSEvent*)event { | 552 - (void)mouseExited:(NSEvent*)event { |
| 521 [changePhotoButton_ setHidden:YES]; | 553 [changePhotoButton_ setHidden:YES]; |
| 522 } | 554 } |
| 523 | 555 |
| 556 // Make sure the element is focusable for accessibility. | |
| 557 - (BOOL)canBecomeKeyView { | |
| 558 return YES; | |
| 559 } | |
| 560 | |
| 561 - (BOOL)acceptsFirstResponder { | |
| 562 return YES; | |
| 563 } | |
| 564 | |
| 565 - (BOOL)accessibilityIsIgnored { | |
| 566 return NO; | |
| 567 } | |
| 568 | |
| 569 - (NSArray*)accessibilityActionNames { | |
| 570 NSArray* parentActions = [super accessibilityActionNames]; | |
| 571 return [parentActions arrayByAddingObject:NSAccessibilityPressAction]; | |
| 572 } | |
| 573 | |
| 574 - (void)accessibilityPerformAction:(NSString*)action { | |
| 575 if ([action isEqualToString:NSAccessibilityPressAction]) { | |
| 576 avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex()); | |
| 577 } | |
| 578 | |
| 579 [super accessibilityPerformAction:action]; | |
| 580 } | |
| 581 | |
| 524 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect { | 582 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect { |
| 525 TransparentBackgroundButton* button = | 583 TransparentBackgroundButton* button = |
| 526 [[TransparentBackgroundButton alloc] initWithFrame:rect]; | 584 [[TransparentBackgroundButton alloc] initWithFrame:rect]; |
| 527 [button setImage:ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( | 585 [button setImage:ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( |
| 528 IDR_ICON_PROFILES_EDIT_CAMERA).AsNSImage()]; | 586 IDR_ICON_PROFILES_EDIT_CAMERA).AsNSImage()]; |
| 529 [button setImagePosition:NSImageOnly]; | 587 [button setImagePosition:NSImageOnly]; |
| 530 [button setTarget:self]; | 588 [button setTarget:self]; |
| 531 [button setAction:@selector(editPhoto:)]; | 589 [button setAction:@selector(editPhoto:)]; |
| 532 return button; | 590 return button; |
| 533 } | 591 } |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 546 profileName:(NSString*)profileName | 604 profileName:(NSString*)profileName |
| 547 editingAllowed:(BOOL)editingAllowed | 605 editingAllowed:(BOOL)editingAllowed |
| 548 withController:(ProfileChooserController*)controller; | 606 withController:(ProfileChooserController*)controller; |
| 549 | 607 |
| 550 // Called when the button is clicked. | 608 // Called when the button is clicked. |
| 551 - (void)showEditableView:(id)sender; | 609 - (void)showEditableView:(id)sender; |
| 552 | 610 |
| 553 // Called when enter is pressed in the text field. | 611 // Called when enter is pressed in the text field. |
| 554 - (void)saveProfileName:(id)sender; | 612 - (void)saveProfileName:(id)sender; |
| 555 | 613 |
| 614 - (BOOL)acceptsFirstResponder; | |
| 615 | |
| 556 @end | 616 @end |
| 557 | 617 |
| 558 @implementation EditableProfileNameButton | 618 @implementation EditableProfileNameButton |
| 559 - (id)initWithFrame:(NSRect)frameRect | 619 - (id)initWithFrame:(NSRect)frameRect |
| 560 profile:(Profile*)profile | 620 profile:(Profile*)profile |
| 561 profileName:(NSString*)profileName | 621 profileName:(NSString*)profileName |
| 562 editingAllowed:(BOOL)editingAllowed | 622 editingAllowed:(BOOL)editingAllowed |
| 563 withController:(ProfileChooserController*)controller { | 623 withController:(ProfileChooserController*)controller { |
| 564 if ((self = [super initWithFrame:frameRect])) { | 624 if ((self = [super initWithFrame:frameRect])) { |
| 565 profile_ = profile; | 625 profile_ = profile; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 606 [[profileNameTextField_ cell] setLineBreakMode: | 666 [[profileNameTextField_ cell] setLineBreakMode: |
| 607 NSLineBreakByTruncatingTail]; | 667 NSLineBreakByTruncatingTail]; |
| 608 [self addSubview:profileNameTextField_]; | 668 [self addSubview:profileNameTextField_]; |
| 609 [profileNameTextField_ setTarget:self]; | 669 [profileNameTextField_ setTarget:self]; |
| 610 [profileNameTextField_ setAction:@selector(saveProfileName:)]; | 670 [profileNameTextField_ setAction:@selector(saveProfileName:)]; |
| 611 | 671 |
| 612 // Hide the textfield until the user clicks on the button. | 672 // Hide the textfield until the user clicks on the button. |
| 613 [profileNameTextField_ setHidden:YES]; | 673 [profileNameTextField_ setHidden:YES]; |
| 614 } | 674 } |
| 615 | 675 |
| 676 [[self cell] accessibilitySetOverrideValue:NSAccessibilityButtonRole | |
| 677 forAttribute:NSAccessibilityRoleAttribute]; | |
| 678 [[self cell] accessibilitySetOverrideValue: | |
| 679 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) | |
| 680 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
| 681 | |
| 616 [self setBordered:NO]; | 682 [self setBordered:NO]; |
| 617 [self setFont:[NSFont labelFontOfSize:kTitleFontSize]]; | 683 [self setFont:[NSFont labelFontOfSize:kTitleFontSize]]; |
| 618 [self setAlignment:NSCenterTextAlignment]; | 684 [self setAlignment:NSCenterTextAlignment]; |
| 619 [[self cell] setLineBreakMode:NSLineBreakByTruncatingTail]; | 685 [[self cell] setLineBreakMode:NSLineBreakByTruncatingTail]; |
| 620 [self setTitle:profileName]; | 686 [self setTitle:profileName]; |
| 621 } | 687 } |
| 622 return self; | 688 return self; |
| 623 } | 689 } |
| 624 | 690 |
| 625 - (void)saveProfileName:(id)sender { | 691 - (void)saveProfileName:(id)sender { |
| 626 NSString* text = [profileNameTextField_ stringValue]; | 692 NSString* text = [profileNameTextField_ stringValue]; |
| 627 // Empty profile names are not allowed, and are treated as a cancel. | 693 // Empty profile names are not allowed, and are treated as a cancel. |
| 628 if ([text length] > 0) { | 694 if ([text length] > 0) { |
| 629 profiles::UpdateProfileName(profile_, base::SysNSStringToUTF16(text)); | 695 profiles::UpdateProfileName(profile_, base::SysNSStringToUTF16(text)); |
| 630 [controller_ | 696 [controller_ |
| 631 postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME]; | 697 postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME]; |
| 632 [self setTitle:text]; | 698 [self setTitle:text]; |
| 633 } | 699 } |
| 634 [profileNameTextField_ setHidden:YES]; | 700 [profileNameTextField_ setHidden:YES]; |
| 635 } | 701 } |
| 636 | 702 |
| 637 - (void)showEditableView:(id)sender { | 703 - (void)showEditableView:(id)sender { |
| 638 [profileNameTextField_ setHidden:NO]; | 704 [profileNameTextField_ setHidden:NO]; |
| 639 [[self window] makeFirstResponder:profileNameTextField_]; | 705 [[self window] makeFirstResponder:profileNameTextField_]; |
| 640 } | 706 } |
| 641 | 707 |
| 708 - (BOOL)acceptsFirstResponder { | |
|
groby-ooo-7-16
2014/08/15 00:38:35
Technically, a control's -acceptsFirstResponder is
noms (inactive)
2014/08/15 19:26:47
No. I think I just hoped these would make regular
| |
| 709 return YES; | |
| 710 } | |
| 711 | |
| 642 @end | 712 @end |
| 643 | 713 |
| 644 // A custom button that allows for setting a background color when hovered over. | 714 // A custom button that allows for setting a background color when hovered over. |
| 645 @interface BackgroundColorHoverButton : HoverImageButton { | 715 @interface BackgroundColorHoverButton : HoverImageButton { |
| 646 @private | 716 @private |
| 647 base::scoped_nsobject<NSColor> backgroundColor_; | 717 base::scoped_nsobject<NSColor> backgroundColor_; |
| 648 base::scoped_nsobject<NSColor> hoverColor_; | 718 base::scoped_nsobject<NSColor> hoverColor_; |
| 649 } | 719 } |
| 650 @end | 720 @end |
| 651 | 721 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 679 } | 749 } |
| 680 | 750 |
| 681 - (void)setHoverState:(HoverState)state { | 751 - (void)setHoverState:(HoverState)state { |
| 682 [super setHoverState:state]; | 752 [super setHoverState:state]; |
| 683 bool isHighlighted = ([self hoverState] != kHoverStateNone); | 753 bool isHighlighted = ([self hoverState] != kHoverStateNone); |
| 684 | 754 |
| 685 NSColor* backgroundColor = isHighlighted ? hoverColor_ : backgroundColor_; | 755 NSColor* backgroundColor = isHighlighted ? hoverColor_ : backgroundColor_; |
| 686 [[self cell] setBackgroundColor:backgroundColor]; | 756 [[self cell] setBackgroundColor:backgroundColor]; |
| 687 } | 757 } |
| 688 | 758 |
| 759 - (BOOL)acceptsFirstResponder { | |
| 760 return YES; | |
| 761 } | |
| 762 | |
| 689 @end | 763 @end |
| 690 | 764 |
| 691 // A custom view with the given background color. | 765 // A custom view with the given background color. |
| 692 @interface BackgroundColorView : NSView { | 766 @interface BackgroundColorView : NSView { |
| 693 @private | 767 @private |
| 694 base::scoped_nsobject<NSColor> backgroundColor_; | 768 base::scoped_nsobject<NSColor> backgroundColor_; |
| 695 } | 769 } |
| 696 @end | 770 @end |
| 697 | 771 |
| 698 @implementation BackgroundColorView | 772 @implementation BackgroundColorView |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 975 // If view mode is PROFILE_CHOOSER but there is an auth error, force | 1049 // If view mode is PROFILE_CHOOSER but there is an auth error, force |
| 976 // ACCOUNT_MANAGEMENT mode. | 1050 // ACCOUNT_MANAGEMENT mode. |
| 977 if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER && | 1051 if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER && |
| 978 HasAuthError(browser_->profile()) && | 1052 HasAuthError(browser_->profile()) && |
| 979 switches::IsEnableAccountConsistency() && | 1053 switches::IsEnableAccountConsistency() && |
| 980 avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex()). | 1054 avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex()). |
| 981 signed_in) { | 1055 signed_in) { |
| 982 viewMode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT; | 1056 viewMode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT; |
| 983 } | 1057 } |
| 984 | 1058 |
| 1059 [window accessibilitySetOverrideValue: | |
| 1060 l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME) | |
| 1061 forAttribute:NSAccessibilityTitleAttribute]; | |
| 1062 [window accessibilitySetOverrideValue: | |
| 1063 l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME) | |
| 1064 forAttribute:NSAccessibilityHelpAttribute]; | |
| 1065 | |
| 985 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge]; | 1066 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge]; |
| 986 [[self bubble] setArrowLocation:info_bubble::kNoArrow]; | 1067 [[self bubble] setArrowLocation:info_bubble::kNoArrow]; |
| 987 [[self bubble] setBackgroundColor:GetDialogBackgroundColor()]; | 1068 [[self bubble] setBackgroundColor:GetDialogBackgroundColor()]; |
| 988 [self initMenuContentsWithView:viewMode_]; | 1069 [self initMenuContentsWithView:viewMode_]; |
| 989 } | 1070 } |
| 990 | 1071 |
| 991 return self; | 1072 return self; |
| 992 } | 1073 } |
| 993 | 1074 |
| 994 - (void)initMenuContentsWithView:(profiles::BubbleViewMode)viewToDisplay { | 1075 - (void)initMenuContentsWithView:(profiles::BubbleViewMode)viewToDisplay { |
| (...skipping 392 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1387 link = [self linkButtonWithTitle:email | 1468 link = [self linkButtonWithTitle:email |
| 1388 frameOrigin:rect.origin | 1469 frameOrigin:rect.origin |
| 1389 action:nil]; | 1470 action:nil]; |
| 1390 [link setImage:ui::ResourceBundle::GetSharedInstance(). | 1471 [link setImage:ui::ResourceBundle::GetSharedInstance(). |
| 1391 GetNativeImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR). | 1472 GetNativeImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR). |
| 1392 ToNSImage()]; | 1473 ToNSImage()]; |
| 1393 [link setImagePosition:NSImageRight]; | 1474 [link setImagePosition:NSImageRight]; |
| 1394 [link setTarget:self]; | 1475 [link setTarget:self]; |
| 1395 [link setAction:@selector(showAccountReauthenticationView:)]; | 1476 [link setAction:@selector(showAccountReauthenticationView:)]; |
| 1396 [link setTag:kPrimaryProfileTag]; | 1477 [link setTag:kPrimaryProfileTag]; |
| 1478 [[link cell] | |
| 1479 accessibilitySetOverrideValue:l10n_util::GetNSStringF( | |
| 1480 IDS_PROFILES_ACCOUNT_BUTTON_AUTH_ERROR_ACCESSIBLE_NAME, | |
| 1481 item.sync_state) | |
| 1482 forAttribute:NSAccessibilityTitleAttribute]; | |
| 1397 } else { | 1483 } else { |
| 1398 NSTextField* label = BuildLabel(email, rect.origin, nil); | 1484 NSTextField* label = BuildLabel(email, rect.origin, nil); |
| 1399 [label setAlignment:NSCenterTextAlignment]; | 1485 [label setAlignment:NSCenterTextAlignment]; |
| 1400 [label setFrame:rect]; | 1486 [label setFrame:rect]; |
| 1401 [container addSubview:label]; | 1487 [container addSubview:label]; |
| 1402 } | 1488 } |
| 1403 } | 1489 } |
| 1404 // -linkButtonWithTitle sizeToFit's the link, so re-stretch it so that it | 1490 // -linkButtonWithTitle sizeToFit's the link, so re-stretch it so that it |
| 1405 // can be centered correctly in the view. | 1491 // can be centered correctly in the view. |
| 1406 if (link) { | 1492 if (link) { |
| (...skipping 554 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1961 } | 2047 } |
| 1962 | 2048 |
| 1963 - (bool)shouldShowGoIncognito { | 2049 - (bool)shouldShowGoIncognito { |
| 1964 bool incognitoAvailable = | 2050 bool incognitoAvailable = |
| 1965 IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) != | 2051 IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) != |
| 1966 IncognitoModePrefs::DISABLED; | 2052 IncognitoModePrefs::DISABLED; |
| 1967 return incognitoAvailable && !browser_->profile()->IsGuestSession(); | 2053 return incognitoAvailable && !browser_->profile()->IsGuestSession(); |
| 1968 } | 2054 } |
| 1969 | 2055 |
| 1970 @end | 2056 @end |
| OLD | NEW |