| OLD | NEW |
| 1 // Copyright (c) 2012 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 "chrome/browser/ui/cocoa/browser/avatar_button_controller.h" | 5 #import "chrome/browser/ui/cocoa/browser/avatar_icon_controller.h" |
| 6 | 6 |
| 7 #include "base/strings/sys_string_conversions.h" | 7 #include "base/strings/sys_string_conversions.h" |
| 8 #include "chrome/app/chrome_command_ids.h" | |
| 9 #include "chrome/browser/browser_process.h" | 8 #include "chrome/browser/browser_process.h" |
| 10 #include "chrome/browser/chrome_notification_types.h" | |
| 11 #include "chrome/browser/command_updater.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | 9 #include "chrome/browser/profiles/profile.h" |
| 13 #include "chrome/browser/profiles/profile_info_cache.h" | 10 #include "chrome/browser/profiles/profile_info_cache.h" |
| 14 #include "chrome/browser/profiles/profile_info_util.h" | 11 #include "chrome/browser/profiles/profile_info_util.h" |
| 15 #include "chrome/browser/profiles/profile_manager.h" | 12 #include "chrome/browser/profiles/profile_manager.h" |
| 16 #include "chrome/browser/profiles/profile_metrics.h" | |
| 17 #include "chrome/browser/profiles/profiles_state.h" | |
| 18 #include "chrome/browser/ui/browser.h" | 13 #include "chrome/browser/ui/browser.h" |
| 19 #include "chrome/browser/ui/browser_commands.h" | |
| 20 #include "chrome/browser/ui/browser_window.h" | 14 #include "chrome/browser/ui/browser_window.h" |
| 21 #import "chrome/browser/ui/cocoa/browser/avatar_label_button.h" | 15 #import "chrome/browser/ui/cocoa/browser/avatar_label_button.h" |
| 22 #import "chrome/browser/ui/cocoa/browser/avatar_menu_bubble_controller.h" | |
| 23 #import "chrome/browser/ui/cocoa/base_bubble_controller.h" | |
| 24 #import "chrome/browser/ui/cocoa/browser/profile_chooser_controller.h" | |
| 25 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | 16 #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
| 26 #include "chrome/common/profile_management_switches.h" | |
| 27 #include "content/public/browser/notification_service.h" | |
| 28 #include "grit/generated_resources.h" | 17 #include "grit/generated_resources.h" |
| 29 #include "grit/theme_resources.h" | 18 #include "grit/theme_resources.h" |
| 30 #include "ui/base/l10n/l10n_util_mac.h" | 19 #include "ui/base/l10n/l10n_util_mac.h" |
| 31 #include "ui/base/resource/resource_bundle.h" | 20 #include "ui/base/resource/resource_bundle.h" |
| 32 #include "ui/gfx/image/image.h" | 21 #include "ui/gfx/image/image.h" |
| 33 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" | 22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
| 34 | 23 |
| 35 namespace { | 24 namespace { |
| 36 | 25 |
| 37 // Space between the avatar icon and the avatar menu bubble. | |
| 38 const CGFloat kMenuYOffsetAdjust = 1.0; | |
| 39 | |
| 40 // Space between the avatar label and the left edge of the container containing | 26 // Space between the avatar label and the left edge of the container containing |
| 41 // the label and the icon. | 27 // the label and the icon. |
| 42 const CGFloat kAvatarSpacing = 4; | 28 const CGFloat kAvatarSpacing = 4; |
| 43 | 29 |
| 44 // Space between the bottom of the avatar icon and the bottom of the avatar | 30 // Space between the bottom of the avatar icon and the bottom of the avatar |
| 45 // label. | 31 // label. |
| 46 const CGFloat kAvatarLabelBottomSpacing = 3; | 32 const CGFloat kAvatarLabelBottomSpacing = 3; |
| 47 | 33 |
| 48 // Space between the right edge of the avatar label and the right edge of the | 34 // Space between the right edge of the avatar label and the right edge of the |
| 49 // avatar icon. | 35 // avatar icon. |
| 50 const CGFloat kAvatarLabelRightSpacing = 2; | 36 const CGFloat kAvatarLabelRightSpacing = 2; |
| 51 | 37 |
| 52 } // namespace | 38 } // namespace |
| 53 | 39 |
| 54 @interface AvatarButtonController (Private) | 40 @interface AvatarIconController (Private) |
| 55 - (void)setButtonEnabled:(BOOL)flag; | 41 - (void)setButtonEnabled:(BOOL)flag; |
| 56 - (IBAction)buttonClicked:(id)sender; | |
| 57 - (void)bubbleWillClose:(NSNotification*)notif; | |
| 58 - (NSImage*)compositeImageWithShadow:(NSImage*)image; | 42 - (NSImage*)compositeImageWithShadow:(NSImage*)image; |
| 59 - (void)updateAvatar; | 43 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent; |
| 60 - (void)addOrRemoveButtonIfNecessary; | 44 - (void)addOrRemoveButtonIfNecessary; |
| 61 @end | 45 @end |
| 62 | 46 |
| 63 // Declare a 10.7+ private API. | 47 // Declare a 10.7+ private API. |
| 64 // NSThemeFrame < NSTitledFrame < NSFrameView < NSView. | 48 // NSThemeFrame < NSTitledFrame < NSFrameView < NSView. |
| 65 @interface NSView (NSThemeFrame) | 49 @interface NSView (NSThemeFrame) |
| 66 - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay; | 50 - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay; |
| 67 @end | 51 @end |
| 68 | 52 |
| 69 namespace AvatarButtonControllerInternal { | 53 @implementation AvatarIconController |
| 70 | |
| 71 class Observer : public content::NotificationObserver { | |
| 72 public: | |
| 73 Observer(AvatarButtonController* button) : button_(button) { | |
| 74 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, | |
| 75 content::NotificationService::AllSources()); | |
| 76 } | |
| 77 | |
| 78 // NotificationObserver: | |
| 79 virtual void Observe(int type, | |
| 80 const content::NotificationSource& source, | |
| 81 const content::NotificationDetails& details) OVERRIDE { | |
| 82 switch (type) { | |
| 83 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: | |
| 84 [button_ updateAvatar]; | |
| 85 [button_ addOrRemoveButtonIfNecessary]; | |
| 86 break; | |
| 87 default: | |
| 88 NOTREACHED(); | |
| 89 break; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 private: | |
| 94 content::NotificationRegistrar registrar_; | |
| 95 | |
| 96 AvatarButtonController* button_; // Weak; owns this. | |
| 97 }; | |
| 98 | |
| 99 } // namespace AvatarButtonControllerInternal | |
| 100 | |
| 101 //////////////////////////////////////////////////////////////////////////////// | |
| 102 | |
| 103 @implementation AvatarButtonController | |
| 104 | 54 |
| 105 - (id)initWithBrowser:(Browser*)browser { | 55 - (id)initWithBrowser:(Browser*)browser { |
| 106 if ((self = [super init])) { | 56 if ((self = [super initWithBrowser:browser])) { |
| 107 browser_ = browser; | 57 browser_ = browser; |
| 108 | 58 |
| 109 base::scoped_nsobject<NSView> container( | 59 base::scoped_nsobject<NSView> container( |
| 110 [[NSView alloc] initWithFrame:NSMakeRect( | 60 [[NSView alloc] initWithFrame:NSMakeRect( |
| 111 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); | 61 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); |
| 112 [self setView:container]; | 62 [self setView:container]; |
| 63 |
| 113 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect( | 64 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect( |
| 114 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); | 65 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); |
| 115 NSButtonCell* cell = [button_ cell]; | 66 NSButtonCell* cell = [button_ cell]; |
| 116 [button_ setButtonType:NSMomentaryLightButton]; | 67 [button_ setButtonType:NSMomentaryLightButton]; |
| 117 | 68 |
| 118 [button_ setImagePosition:NSImageOnly]; | 69 [button_ setImagePosition:NSImageOnly]; |
| 119 [cell setImageScaling:NSImageScaleProportionallyDown]; | 70 [cell setImageScaling:NSImageScaleProportionallyDown]; |
| 120 [cell setImagePosition:NSImageBelow]; | 71 [cell setImagePosition:NSImageBelow]; |
| 121 | 72 |
| 122 // AppKit sets a title for some reason when using |-setImagePosition:|. | 73 // AppKit sets a title for some reason when using |-setImagePosition:|. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 149 | 100 |
| 150 if (profile->IsOffTheRecord() || profile->IsGuestSession()) { | 101 if (profile->IsOffTheRecord() || profile->IsGuestSession()) { |
| 151 const int icon_id = profile->IsGuestSession() ? IDR_LOGIN_GUEST : | 102 const int icon_id = profile->IsGuestSession() ? IDR_LOGIN_GUEST : |
| 152 IDR_OTR_ICON; | 103 IDR_OTR_ICON; |
| 153 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed( | 104 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed( |
| 154 icon_id).ToNSImage(); | 105 icon_id).ToNSImage(); |
| 155 [self setImage:[self compositeImageWithShadow:icon]]; | 106 [self setImage:[self compositeImageWithShadow:icon]]; |
| 156 [self setButtonEnabled:profile->IsGuestSession()]; | 107 [self setButtonEnabled:profile->IsGuestSession()]; |
| 157 } else { | 108 } else { |
| 158 [self setButtonEnabled:YES]; | 109 [self setButtonEnabled:YES]; |
| 159 observer_.reset(new AvatarButtonControllerInternal::Observer(self)); | 110 [self updateAvatarButtonAndLayoutParent:NO]; |
| 160 [self updateAvatar]; | |
| 161 | 111 |
| 162 // Managed users cannot enter incognito mode, so we only need to check | 112 // Managed users cannot enter incognito mode, so we only need to check |
| 163 // it in this code path. | 113 // it in this code path. |
| 164 if (profile->IsManaged()) { | 114 if (profile->IsManaged()) { |
| 165 // Initialize the avatar label button. | 115 // Initialize the avatar label button. |
| 166 CGFloat extraWidth = | 116 CGFloat extraWidth = |
| 167 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing; | 117 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing; |
| 168 NSRect frame = NSMakeRect( | 118 NSRect frame = NSMakeRect( |
| 169 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0); | 119 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0); |
| 170 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]); | 120 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]); |
| 171 [labelButton_ setTarget:self]; | 121 [labelButton_ setTarget:self]; |
| 172 [labelButton_ setAction:@selector(buttonClicked:)]; | 122 [labelButton_ setAction:@selector(buttonClicked:)]; |
| 173 [[self view] addSubview:labelButton_]; | 123 [[self view] addSubview:labelButton_]; |
| 174 | 124 |
| 175 // Resize the container and reposition the avatar button. | 125 // Resize the container and reposition the avatar button. |
| 176 NSSize textSize = [[labelButton_ cell] labelTextSize]; | 126 NSSize textSize = [[labelButton_ cell] labelTextSize]; |
| 177 [container setFrameSize: | 127 [container setFrameSize: |
| 178 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing, | 128 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing, |
| 179 profiles::kAvatarIconHeight)]; | 129 profiles::kAvatarIconHeight)]; |
| 180 [button_ | 130 [button_ |
| 181 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)]; | 131 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)]; |
| 182 } | 132 } |
| 183 } | 133 } |
| 184 [[self view] addSubview:button_]; | 134 [[self view] addSubview:button_]; |
| 185 } | 135 } |
| 186 return self; | 136 return self; |
| 187 } | 137 } |
| 188 | 138 |
| 189 - (void)dealloc { | |
| 190 [[NSNotificationCenter defaultCenter] | |
| 191 removeObserver:self | |
| 192 name:NSWindowWillCloseNotification | |
| 193 object:[menuController_ window]]; | |
| 194 [super dealloc]; | |
| 195 } | |
| 196 | |
| 197 - (NSButton*)buttonView { | |
| 198 return button_.get(); | |
| 199 } | |
| 200 | |
| 201 - (NSButton*)labelButtonView { | 139 - (NSButton*)labelButtonView { |
| 202 return labelButton_.get(); | 140 return labelButton_.get(); |
| 203 } | 141 } |
| 204 | 142 |
| 205 - (void)setImage:(NSImage*)image { | 143 - (void)setImage:(NSImage*)image { |
| 206 [button_ setImage:image]; | 144 [button_ setImage:image]; |
| 207 } | 145 } |
| 208 | 146 |
| 209 - (void)showAvatarBubble:(NSView*)anchor { | |
| 210 if (menuController_) | |
| 211 return; | |
| 212 | |
| 213 DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU)); | |
| 214 | |
| 215 NSWindowController* wc = | |
| 216 [browser_->window()->GetNativeWindow() windowController]; | |
| 217 if ([wc isKindOfClass:[BrowserWindowController class]]) { | |
| 218 [static_cast<BrowserWindowController*>(wc) | |
| 219 lockBarVisibilityForOwner:self withAnimation:NO delay:NO]; | |
| 220 } | |
| 221 | |
| 222 NSPoint point = NSMakePoint(NSMidX([anchor bounds]), | |
| 223 NSMaxY([anchor bounds]) - kMenuYOffsetAdjust); | |
| 224 point = [anchor convertPoint:point toView:nil]; | |
| 225 point = [[anchor window] convertBaseToScreen:point]; | |
| 226 | |
| 227 // |menuController_| will automatically release itself on close. | |
| 228 if (switches::IsNewProfileManagement()) { | |
| 229 menuController_ = | |
| 230 [[ProfileChooserController alloc] initWithBrowser:browser_ | |
| 231 anchoredAt:point]; | |
| 232 } else { | |
| 233 menuController_ = | |
| 234 [[AvatarMenuBubbleController alloc] initWithBrowser:browser_ | |
| 235 anchoredAt:point]; | |
| 236 } | |
| 237 [[NSNotificationCenter defaultCenter] | |
| 238 addObserver:self | |
| 239 selector:@selector(bubbleWillClose:) | |
| 240 name:NSWindowWillCloseNotification | |
| 241 object:[menuController_ window]]; | |
| 242 [menuController_ showWindow:self]; | |
| 243 | |
| 244 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE); | |
| 245 } | |
| 246 | |
| 247 // Private ///////////////////////////////////////////////////////////////////// | |
| 248 | |
| 249 - (void)setButtonEnabled:(BOOL)flag { | 147 - (void)setButtonEnabled:(BOOL)flag { |
| 250 [button_ setEnabled:flag]; | 148 [button_ setEnabled:flag]; |
| 251 } | 149 } |
| 252 | 150 |
| 253 - (IBAction)buttonClicked:(id)sender { | |
| 254 DCHECK(sender == button_.get() || sender == labelButton_.get()); | |
| 255 [self showAvatarBubble:button_]; | |
| 256 } | |
| 257 | |
| 258 - (void)bubbleWillClose:(NSNotification*)notif { | |
| 259 NSWindowController* wc = | |
| 260 [browser_->window()->GetNativeWindow() windowController]; | |
| 261 if ([wc isKindOfClass:[BrowserWindowController class]]) { | |
| 262 [static_cast<BrowserWindowController*>(wc) | |
| 263 releaseBarVisibilityForOwner:self withAnimation:YES delay:NO]; | |
| 264 } | |
| 265 menuController_ = nil; | |
| 266 } | |
| 267 | |
| 268 // This will take in an original image and redraw it with a shadow. | 151 // This will take in an original image and redraw it with a shadow. |
| 269 - (NSImage*)compositeImageWithShadow:(NSImage*)image { | 152 - (NSImage*)compositeImageWithShadow:(NSImage*)image { |
| 270 gfx::ScopedNSGraphicsContextSaveGState scopedGState; | 153 gfx::ScopedNSGraphicsContextSaveGState scopedGState; |
| 271 | 154 |
| 272 base::scoped_nsobject<NSImage> destination( | 155 base::scoped_nsobject<NSImage> destination( |
| 273 [[NSImage alloc] initWithSize:[image size]]); | 156 [[NSImage alloc] initWithSize:[image size]]); |
| 274 | 157 |
| 275 NSRect destRect = NSZeroRect; | 158 NSRect destRect = NSZeroRect; |
| 276 destRect.size = [destination size]; | 159 destRect.size = [destination size]; |
| 277 | 160 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 290 fraction:1.0 | 173 fraction:1.0 |
| 291 respectFlipped:YES | 174 respectFlipped:YES |
| 292 hints:nil]; | 175 hints:nil]; |
| 293 | 176 |
| 294 [destination unlockFocus]; | 177 [destination unlockFocus]; |
| 295 | 178 |
| 296 return destination.autorelease(); | 179 return destination.autorelease(); |
| 297 } | 180 } |
| 298 | 181 |
| 299 // Updates the avatar information from the profile cache. | 182 // Updates the avatar information from the profile cache. |
| 300 - (void)updateAvatar { | 183 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent { |
| 301 ProfileInfoCache& cache = | 184 ProfileInfoCache& cache = |
| 302 g_browser_process->profile_manager()->GetProfileInfoCache(); | 185 g_browser_process->profile_manager()->GetProfileInfoCache(); |
| 303 size_t index = | 186 size_t index = |
| 304 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()); | 187 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()); |
| 305 if (index == std::string::npos) | 188 if (index == std::string::npos) |
| 306 return; | 189 return; |
| 190 |
| 307 BOOL is_gaia_picture = | 191 BOOL is_gaia_picture = |
| 308 cache.IsUsingGAIAPictureOfProfileAtIndex(index) && | 192 cache.IsUsingGAIAPictureOfProfileAtIndex(index) && |
| 309 cache.GetGAIAPictureOfProfileAtIndex(index); | 193 cache.GetGAIAPictureOfProfileAtIndex(index); |
| 310 gfx::Image icon = profiles::GetAvatarIconForTitleBar( | 194 gfx::Image icon = profiles::GetAvatarIconForTitleBar( |
| 311 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture, | 195 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture, |
| 312 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight); | 196 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight); |
| 313 [self setImage:icon.ToNSImage()]; | 197 [self setImage:icon.ToNSImage()]; |
| 314 | 198 |
| 315 const base::string16& name = cache.GetNameOfProfileAtIndex(index); | 199 const base::string16& name = cache.GetNameOfProfileAtIndex(index); |
| 316 NSString* nsName = base::SysUTF16ToNSString(name); | 200 NSString* nsName = base::SysUTF16ToNSString(name); |
| 317 [button_ setToolTip:nsName]; | 201 [button_ setToolTip:nsName]; |
| 318 [[button_ cell] | 202 [[button_ cell] |
| 319 accessibilitySetOverrideValue:nsName | 203 accessibilitySetOverrideValue:nsName |
| 320 forAttribute:NSAccessibilityValueAttribute]; | 204 forAttribute:NSAccessibilityValueAttribute]; |
| 205 if (layoutParent) |
| 206 [self addOrRemoveButtonIfNecessary]; |
| 321 } | 207 } |
| 322 | 208 |
| 323 // If the second-to-last profile was removed or a second profile was added, | 209 // If the second-to-last profile was removed or a second profile was added, |
| 324 // show or hide the avatar button from the window frame. | 210 // show or hide the avatar button from the window frame. |
| 325 - (void)addOrRemoveButtonIfNecessary { | 211 - (void)addOrRemoveButtonIfNecessary { |
| 326 if (browser_->profile()->IsOffTheRecord()) | 212 if (browser_->profile()->IsOffTheRecord()) |
| 327 return; | 213 return; |
| 328 | 214 |
| 329 NSWindowController* wc = | 215 NSWindowController* wc = |
| 330 [browser_->window()->GetNativeWindow() windowController]; | 216 [browser_->window()->GetNativeWindow() windowController]; |
| 331 if (![wc isKindOfClass:[BrowserWindowController class]]) | 217 if (![wc isKindOfClass:[BrowserWindowController class]]) |
| 332 return; | 218 return; |
| 333 | 219 |
| 334 size_t count = g_browser_process->profile_manager()->GetNumberOfProfiles(); | 220 size_t count = g_browser_process->profile_manager()->GetNumberOfProfiles(); |
| 335 [self.view setHidden:count < 2]; | 221 [self.view setHidden:count < 2]; |
| 336 | 222 |
| 337 [static_cast<BrowserWindowController*>(wc) layoutSubviews]; | 223 [static_cast<BrowserWindowController*>(wc) layoutSubviews]; |
| 338 | 224 |
| 339 // If the avatar is being added or removed, then the Lion fullscreen button | 225 // If the avatar is being added or removed, then the Lion fullscreen button |
| 340 // needs to be adjusted. Since the fullscreen button is positioned by | 226 // needs to be adjusted. Since the fullscreen button is positioned by |
| 341 // FramedBrowserWindow using private APIs, the easiest way to update the | 227 // FramedBrowserWindow using private APIs, the easiest way to update the |
| 342 // position of the button is through this private API. Resizing the window | 228 // position of the button is through this private API. Resizing the window |
| 343 // also works, but invoking |-display| does not. | 229 // also works, but invoking |-display| does not. |
| 344 NSView* themeFrame = [[[wc window] contentView] superview]; | 230 NSView* themeFrame = [[[wc window] contentView] superview]; |
| 345 if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) | 231 if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) |
| 346 [themeFrame _tileTitlebarAndRedisplay:YES]; | 232 [themeFrame _tileTitlebarAndRedisplay:YES]; |
| 347 } | 233 } |
| 348 | 234 |
| 349 // Testing ///////////////////////////////////////////////////////////////////// | |
| 350 | |
| 351 - (BaseBubbleController*)menuController { | |
| 352 return menuController_; | |
| 353 } | |
| 354 | |
| 355 @end | 235 @end |
| OLD | NEW |