summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/tab_contents/sad_tab_view_cocoa.mm
blob: dab060a6ebecbf306b38bdc34cd82199197d16bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/cocoa/tab_contents/sad_tab_view_cocoa.h"

#include <stddef.h>

#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/grit/components_scaled_resources.h"
#include "components/strings/grit/components_strings.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#import "ui/base/cocoa/controls/blue_label_button.h"
#import "ui/base/cocoa/controls/hyperlink_text_view.h"
#import "ui/base/cocoa/nscolor_additions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"

// Maximum width used by page contents.
static const CGFloat kMaxContainerWidth = 600;
// Padding between icon and title.
static const CGFloat kIconTitleSpacing = 40;
// Padding between title and message.
static const CGFloat kTitleMessageSpacing = 18;
// Padding between message and link.
static const CGFloat kMessageLinkSpacing = 50;
// Padding between message and button.
static const CGFloat kMessageButtonSpacing = 44;
// Minimum margins on all sides.
static const CGFloat kTabMargin = 13;
// Maximum margin on top.
static const CGFloat kMaxTopMargin = 130;

@interface SadTabTextView : NSTextField

- (id)initWithStringResourceID:(int)stringResourceID;

@end

@implementation SadTabTextView

- (id)initWithStringResourceID:(int)stringResourceID {
  if (self = [super init]) {
    base::scoped_nsobject<NSMutableParagraphStyle> style(
        [[NSMutableParagraphStyle alloc] init]);
    [style setLineSpacing:6];
    base::scoped_nsobject<NSAttributedString> title([[NSAttributedString alloc]
        initWithString:l10n_util::GetNSString(stringResourceID)
            attributes:@{ NSParagraphStyleAttributeName : style }]);
    [self setAttributedStringValue:title];

    [self setAlignment:NSLeftTextAlignment];
    [self setEditable:NO];
    [self setBezeled:NO];
    [self setAutoresizingMask:NSViewWidthSizable|NSViewMaxYMargin];
  }
  return self;
}

- (BOOL)isOpaque {
  return YES;
}

@end

@interface SadTabContainerView : NSView<NSTextViewDelegate> {
 @private
  base::scoped_nsobject<NSImageView> image_;
  base::scoped_nsobject<NSTextField> title_;
  base::scoped_nsobject<NSTextField> message_;
  base::scoped_nsobject<HyperlinkTextView> help_;
  base::scoped_nsobject<NSButton> button_;
}

- (instancetype)initWithBackgroundColor:(NSColor*)backgroundColor;

@property(readonly,nonatomic) NSButton* reloadButton;

// The height to fit the content elements within the current width.
@property(readonly,nonatomic) CGFloat contentHeight;

@end

@implementation SadTabContainerView

- (instancetype)initWithBackgroundColor:(NSColor*)backgroundColor {
  if ((self = [super initWithFrame:NSZeroRect])) {
    // Load resource for image and set it.
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    NSImage* iconImage = rb.GetNativeImageNamed(IDR_CRASH_SAD_TAB).ToNSImage();
    NSRect imageFrame = NSZeroRect;
    imageFrame.size = [iconImage size];
    image_.reset([[NSImageView alloc] initWithFrame:imageFrame]);
    [image_ setImage:iconImage];
    [image_ setAutoresizingMask:NSViewMaxXMargin|NSViewMaxYMargin];
    [self addSubview:image_];

    // Set up the title.
    title_.reset(
        [[SadTabTextView alloc] initWithStringResourceID:IDS_SAD_TAB_TITLE]);
    [title_ setFont:[NSFont systemFontOfSize:24]];
    [title_ setBackgroundColor:backgroundColor];
    [title_ setTextColor:[NSColor colorWithCalibratedWhite:38.0f/255.0f
                                                     alpha:1.0]];
    [title_ sizeToFit];
    [title_ setFrameOrigin:
        NSMakePoint(0, NSMaxY(imageFrame) + kIconTitleSpacing)];
    [self addSubview:title_];

    // Set up the message.
    message_.reset(
        [[SadTabTextView alloc] initWithStringResourceID:IDS_SAD_TAB_MESSAGE]);
    [message_ setFont:[NSFont systemFontOfSize:14]];
    [message_ setBackgroundColor:backgroundColor];
    [message_ setTextColor:[NSColor colorWithCalibratedWhite:81.0f/255.0f
                                                       alpha:1.0]];
    [message_ setFrameOrigin:
        NSMakePoint(0, NSMaxY([title_ frame]) + kTitleMessageSpacing)];
    [self addSubview:message_];

    [self initializeHelpText];

    button_.reset([[BlueLabelButton alloc] init]);
    [button_ setTitle:l10n_util::GetNSString(IDS_SAD_TAB_RELOAD_LABEL)];
    [button_ sizeToFit];
    [button_ setTarget:self];
    [button_ setAction:@selector(reloadPage:)];
    [self addSubview:button_];
  }
  return self;
}

- (BOOL)isFlipped {
  return YES;
}

- (NSButton*)reloadButton {
  return button_;
}

- (CGFloat)contentHeight {
  return NSMaxY([button_ frame]);
}

- (void)resizeSubviewsWithOldSize:(NSSize)oldSize {
  [super resizeSubviewsWithOldSize:oldSize];

  // |message_| can wrap to variable number of lines.
  [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:message_];

  [help_ setFrameOrigin:
      NSMakePoint(0, NSMaxY([message_ frame]) + kMessageLinkSpacing)];

  [button_ setFrameOrigin:
      NSMakePoint(NSMaxX([self bounds]) - NSWidth([button_ frame]),
                  NSMaxY([message_ frame]) + kMessageButtonSpacing)];
}

- (void)initializeHelpText {
  // Programmatically create the help link. Note that the frame's initial
  // height must be set for the programmatic resizing to work.
  NSFont* helpFont = [message_ font];
  NSRect helpFrame = NSMakeRect(0, 0, 1, [helpFont pointSize] + 4);
  help_.reset([[HyperlinkTextView alloc] initWithFrame:helpFrame]);
  [help_ setAutoresizingMask:NSViewWidthSizable|NSViewMaxYMargin];
  [help_ setDrawsBackground:YES];
  [help_ setBackgroundColor:[message_ backgroundColor]];
  [[help_ textContainer] setLineFragmentPadding:2];  // To align with message_.
  [self addSubview:help_];
  [help_ setDelegate:self];

  // Get the help text and link.
  size_t linkOffset = 0;
  const base::string16 helpLink =
      l10n_util::GetStringUTF16(IDS_SAD_TAB_LEARN_MORE_LINK);
  NSString* helpMessage(base::SysUTF16ToNSString(helpLink));
  [help_ setMessage:helpMessage
           withFont:helpFont
       messageColor:[message_ textColor]];
  [help_ addLinkRange:NSMakeRange(linkOffset, helpLink.length())
              withURL:@(chrome::kCrashReasonURL)
            linkColor:[message_ textColor]];
  [help_ setAlignment:NSLeftTextAlignment];
  [help_ sizeToFit];
}

// Called when someone clicks on the embedded link.
- (BOOL)textView:(NSTextView*)textView
   clickedOnLink:(id)link
         atIndex:(NSUInteger)charIndex {
  [NSApp sendAction:@selector(openLearnMoreAboutCrashLink:) to:nil from:self];
  return YES;
}

@end

@implementation SadTabView

+ (NSColor*)backgroundColor {
  return [NSColor colorWithCalibratedWhite:245.0f/255.0f alpha:1.0];
}

- (instancetype)initWithFrame:(NSRect)frame {
  if ((self = [super initWithFrame:frame])) {
    [self setWantsLayer:YES];

    container_.reset([[SadTabContainerView alloc]
        initWithBackgroundColor:[SadTabView backgroundColor]]);
    [self addSubview:container_];
  }
  return self;
}

- (CALayer*)makeBackingLayer {
  CALayer* layer = [super makeBackingLayer];
  [layer setBackgroundColor:[[SadTabView backgroundColor] cr_CGColor]];
  return layer;
}

- (BOOL)isFlipped {
  return YES;
}

- (void)resizeSubviewsWithOldSize:(NSSize)oldSize {
  NSRect bounds = [self bounds];

  // Set the container size first because its contentHeight will depend on its
  // width.
  NSSize frameSize = NSMakeSize(
      std::min(NSWidth(bounds) - 2 * kTabMargin, kMaxContainerWidth),
      NSHeight(bounds));
  [container_ setFrameSize:frameSize];

  // Center horizontally.
  // Top margin is at least kTabMargin and at most kMaxTopMargin.
  [container_ setFrameOrigin:NSMakePoint(
      floor((NSWidth(bounds) - frameSize.width) / 2),
      std::min(kMaxTopMargin, std::max(kTabMargin,
          NSHeight(bounds) - [container_ contentHeight] - kTabMargin)))];
}

- (NSButton*)reloadButton {
  return [container_ reloadButton];
}

@end