Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: chrome/browser/ui/views/web_intent_picker_views.cc

Issue 12225076: Delete most web intents code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <vector>
7
8 #include "base/memory/scoped_vector.h"
9 #include "base/time.h"
10 #include "base/timer.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/download/download_util.h"
13 #include "chrome/browser/tab_contents/tab_util.h"
14 #include "chrome/browser/ui/browser_finder.h"
15 #include "chrome/browser/ui/browser_navigator.h"
16 #include "chrome/browser/ui/chrome_style.h"
17 #include "chrome/browser/ui/intents/web_intent_inline_disposition_delegate.h"
18 #include "chrome/browser/ui/intents/web_intent_picker.h"
19 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h"
20 #include "chrome/browser/ui/intents/web_intent_picker_model.h"
21 #include "chrome/browser/ui/intents/web_intent_picker_model_observer.h"
22 #include "chrome/browser/ui/views/constrained_window_views.h"
23 #include "chrome/browser/ui/views/frame/browser_view.h"
24 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
25 #include "chrome/browser/ui/views/toolbar_view.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_view.h"
29 #include "grit/chromium_strings.h"
30 #include "grit/generated_resources.h"
31 #include "grit/google_chrome_strings.h"
32 #include "grit/theme_resources.h"
33 #include "grit/ui_resources.h"
34 #include "ipc/ipc_message.h"
35 #include "third_party/skia/include/core/SkColor.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/base/text/text_elider.h"
39 #include "ui/gfx/canvas.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/gfx/image/image_skia_operations.h"
42 #include "ui/views/border.h"
43 #include "ui/views/controls/button/image_button.h"
44 #include "ui/views/controls/button/text_button.h"
45 #include "ui/views/controls/image_view.h"
46 #include "ui/views/controls/label.h"
47 #include "ui/views/controls/link.h"
48 #include "ui/views/controls/link_listener.h"
49 #include "ui/views/controls/separator.h"
50 #include "ui/views/controls/throbber.h"
51 #include "ui/views/controls/webview/webview.h"
52 #include "ui/views/layout/box_layout.h"
53 #include "ui/views/layout/fill_layout.h"
54 #include "ui/views/layout/grid_layout.h"
55 #include "ui/views/layout/layout_constants.h"
56 #include "ui/views/view.h"
57 #include "ui/views/widget/widget.h"
58 #include "ui/views/window/dialog_delegate.h"
59 #include "ui/views/window/non_client_view.h"
60
61 using content::WebContents;
62 using views::GridLayout;
63
64 namespace {
65
66 // The color used to dim disabled elements.
67 const SkColor kHalfOpacityWhite = SkColorSetARGB(128, 255, 255, 255);
68
69 // The color used to display an enabled label.
70 const SkColor kEnabledLabelColor = SkColorSetRGB(51, 51, 51);
71
72 // The color used to display an enabled link.
73 const SkColor kEnabledLinkColor = SkColorSetRGB(17, 85, 204);
74
75 // The color used to display a disabled link.
76 const SkColor kDisabledLinkColor = SkColorSetRGB(128, 128, 128);
77
78 // The time between successive throbber frames in milliseconds.
79 const int kThrobberFrameTimeMs = 50;
80
81 // Width of IntentView action button in pixels
82 const int kButtonWidth = 130;
83
84 // Minimum number of action buttons - fill up with suggestions as needed.
85 const int kMinRowCount = 4;
86
87 // Maximum number of action buttons - do not add suggestions to reach.
88 const int kMaxRowCount = 8;
89
90 // The vertical padding around the UI elements in the waiting view.
91 const int kWaitingViewVerticalPadding = 40;
92
93 // Enables or disables all child views of |view|.
94 void EnableChildViews(views::View* view, bool enabled) {
95 for (int i = 0; i < view->child_count(); ++i) {
96 views::View* child = view->child_at(i);
97 child->SetEnabled(enabled);
98 }
99 }
100
101 // Create a "close" button.
102 views::ImageButton* CreateCloseButton(views::ButtonListener* listener) {
103 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
104 views::ImageButton* close_button = new views::ImageButton(listener);
105 close_button->SetImage(views::CustomButton::STATE_NORMAL,
106 rb.GetImageSkiaNamed(IDR_CLOSE_DIALOG));
107 close_button->SetImage(views::CustomButton::STATE_HOVERED,
108 rb.GetImageSkiaNamed(IDR_CLOSE_DIALOG_H));
109 close_button->SetImage(views::CustomButton::STATE_PRESSED,
110 rb.GetImageSkiaNamed(IDR_CLOSE_DIALOG_P));
111 return close_button;
112 }
113
114 // Creates a label.
115 views::Label* CreateLabel() {
116 views::Label* label = new views::Label();
117 label->SetEnabledColor(kEnabledLabelColor);
118 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
119 return label;
120 }
121
122 // Creates a title-style label.
123 views::Label* CreateTitleLabel() {
124 views::Label* label = CreateLabel();
125 label->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
126 ui::ResourceBundle::MediumFont));
127 const int kLabelBuiltinTopPadding = 5;
128 label->set_border(views::Border::CreateEmptyBorder(
129 WebIntentPicker::kContentAreaBorder -
130 chrome_style::kCloseButtonPadding -
131 kLabelBuiltinTopPadding,
132 0, 0, 0));
133 return label;
134 }
135
136 // Creates a link.
137 views::Link* CreateLink() {
138 views::Link* link = new views::Link();
139 link->SetEnabledColor(kEnabledLinkColor);
140 link->SetDisabledColor(kDisabledLinkColor);
141 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
142 return link;
143 }
144
145 // Creates a header for the inline disposition dialog.
146 views::View* CreateInlineDispositionHeader(
147 views::ImageView* app_icon,
148 views::Label* app_title,
149 views::Link* use_another_service_link) {
150 views::View* header = new views::View();
151 views::GridLayout* grid_layout = new views::GridLayout(header);
152 const int kIconBuiltinTopPadding = 6;
153 grid_layout->SetInsets(
154 WebIntentPicker::kContentAreaBorder -
155 chrome_style::kCloseButtonPadding -
156 kIconBuiltinTopPadding,
157 0, 0, 0);
158 header->SetLayoutManager(grid_layout);
159 views::ColumnSet* header_cs = grid_layout->AddColumnSet(0);
160 header_cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
161 GridLayout::USE_PREF, 0, 0); // App icon.
162 header_cs->AddPaddingColumn(0, WebIntentPicker::kIconTextPadding);
163 header_cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
164 GridLayout::USE_PREF, 0, 0); // App title.
165 header_cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
166 header_cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
167 GridLayout::USE_PREF, 0, 0); // Use another app link.
168 grid_layout->StartRow(0, 0);
169 grid_layout->AddView(app_icon);
170 grid_layout->AddView(app_title);
171 grid_layout->AddView(use_another_service_link);
172 header->Layout();
173 return header;
174 }
175
176 // Checks whether the inline disposition dialog should show the link for using
177 // another service.
178 bool IsUseAnotherServiceVisible(WebIntentPickerModel* model) {
179 DCHECK(model);
180 return model->show_use_another_service() &&
181 (model->GetInstalledServiceCount() > 1 ||
182 model->GetSuggestedExtensionCount());
183 }
184
185
186 // StarsView -------------------------------------------------------------------
187
188 // A view that displays 5 stars: empty, full or half full, given a rating in
189 // the range [0,5].
190 class StarsView : public views::View {
191 public:
192 explicit StarsView(double rating);
193 virtual ~StarsView();
194
195 private:
196 // The star rating to display, in the range [0,5].
197 double rating_;
198
199 DISALLOW_COPY_AND_ASSIGN(StarsView);
200 };
201
202 StarsView::StarsView(double rating)
203 : rating_(rating) {
204 const int kSpacing = 1; // Spacing between stars in pixels.
205
206 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
207 SetLayoutManager(
208 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, kSpacing));
209
210 for (int i = 0; i < 5; ++i) {
211 views::ImageView* image = new views::ImageView();
212 image->SetImage(rb.GetImageSkiaNamed(
213 WebIntentPicker::GetNthStarImageIdFromCWSRating(rating, i)));
214 AddChildView(image);
215 }
216
217 // TODO(binji): Add tooltip with text rating
218 // "Average Rating: X.XX stars (YYYYY)"
219 // Y = "1: Hated it, 2: Disliked it, 3: It was okay, 4: Liked it,
220 // 5: Loved it"
221 // Choose Y based on rounded X.
222 }
223
224 StarsView::~StarsView() {
225 }
226
227
228 // ThrobberNativeTextButton ----------------------------------------------------
229
230 // A native text button that can display a throbber in place of its icon. Much
231 // of the logic of this class is copied from ui/views/controls/throbber.h.
232 class ThrobberNativeTextButton : public views::NativeTextButton {
233 public:
234 ThrobberNativeTextButton(views::ButtonListener* listener,
235 const string16& text);
236 virtual ~ThrobberNativeTextButton();
237
238 // Start or stop the throbber.
239 void StartThrobber();
240 void StopThrobber();
241
242 // Set the throbber bitmap to use. IDR_THROBBER is used by default.
243 void SetFrames(const gfx::ImageSkia* frames);
244
245 // Provide a preferred size, accomodating buttons wider than their text.
246 virtual gfx::Size GetPreferredSize() OVERRIDE;
247
248 // Set the width desired for this button.
249 void set_preferred_width(int width) { preferred_width_ = width; }
250
251 protected:
252 virtual const gfx::ImageSkia& GetImageToPaint() const OVERRIDE;
253
254 private:
255 // The timer callback to schedule painting this view.
256 void Run();
257
258 // Image that contains the throbber frames.
259 const gfx::ImageSkia* frames_;
260
261 // The currently displayed frame, given to GetImageToPaint.
262 mutable gfx::ImageSkia this_frame_;
263
264 // How long one frame is displayed.
265 base::TimeDelta frame_time_;
266
267 // Used to schedule Run calls.
268 base::RepeatingTimer<ThrobberNativeTextButton> timer_;
269
270 // How many frames we have.
271 int frame_count_;
272
273 // Time when StartThrobber was called.
274 base::TimeTicks start_time_;
275
276 // Whether the throbber is shown an animating.
277 bool running_;
278
279 // The width this button should assume.
280 int preferred_width_;
281
282 DISALLOW_COPY_AND_ASSIGN(ThrobberNativeTextButton);
283 };
284
285 ThrobberNativeTextButton::ThrobberNativeTextButton(
286 views::ButtonListener* listener, const string16& text)
287 : NativeTextButton(listener, text),
288 frame_time_(base::TimeDelta::FromMilliseconds(kThrobberFrameTimeMs)),
289 frame_count_(0),
290 running_(false),
291 preferred_width_(0) {
292 SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
293 IDR_THROBBER).ToImageSkia());
294 }
295
296 ThrobberNativeTextButton::~ThrobberNativeTextButton() {
297 StopThrobber();
298 }
299
300 void ThrobberNativeTextButton::StartThrobber() {
301 if (running_)
302 return;
303
304 start_time_ = base::TimeTicks::Now();
305 timer_.Start(FROM_HERE, frame_time_, this, &ThrobberNativeTextButton::Run);
306 running_ = true;
307
308 SchedulePaint();
309 }
310
311 void ThrobberNativeTextButton::StopThrobber() {
312 if (!running_)
313 return;
314
315 timer_.Stop();
316 running_ = false;
317 }
318
319 void ThrobberNativeTextButton::SetFrames(const gfx::ImageSkia* frames) {
320 frames_ = frames;
321 DCHECK(frames_->width() > 0 && frames_->height() > 0);
322 DCHECK(frames_->width() % frames_->height() == 0);
323 frame_count_ = frames_->width() / frames_->height();
324 PreferredSizeChanged();
325 }
326
327 gfx::Size ThrobberNativeTextButton::GetPreferredSize() {
328 gfx::Size size = NativeTextButton::GetPreferredSize();
329 if (preferred_width_)
330 size.set_width(preferred_width_);
331 return size;
332 }
333
334 const gfx::ImageSkia& ThrobberNativeTextButton::GetImageToPaint() const {
335 if (!running_)
336 return NativeTextButton::GetImageToPaint();
337
338 const base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_;
339 const int current_frame =
340 static_cast<int>(elapsed_time / frame_time_) % frame_count_;
341 const int image_size = frames_->height();
342 const int image_offset = current_frame * image_size;
343
344 gfx::Rect subset_rect(image_offset, 0, image_size, image_size);
345 this_frame_ = gfx::ImageSkiaOperations::ExtractSubset(*frames_, subset_rect);
346 return this_frame_;
347 }
348
349 void ThrobberNativeTextButton::Run() {
350 DCHECK(running_);
351
352 SchedulePaint();
353 }
354
355
356 // SpinnerProgressIndicator ----------------------------------------------------
357 class SpinnerProgressIndicator : public views::View {
358 public:
359 SpinnerProgressIndicator();
360 virtual ~SpinnerProgressIndicator();
361
362 void SetPercentDone(int percent);
363 void SetIndeterminate(bool indetereminate);
364
365 // Overridden from views::View.
366 virtual void Paint(gfx::Canvas* canvas) OVERRIDE;
367 virtual gfx::Size GetPreferredSize() OVERRIDE;
368
369 private:
370 void UpdateTimer();
371 int GetProgressAngle();
372
373 static const int kTimerIntervalMs = 1000 / 30;
374 static const int kSpinRateDegreesPerSecond = 270;
375
376 int percent_done_;
377 int indeterminate_;
378
379 base::TimeTicks start_time_;
380 base::RepeatingTimer<SpinnerProgressIndicator> timer_;
381
382 DISALLOW_COPY_AND_ASSIGN(SpinnerProgressIndicator);
383 };
384
385 SpinnerProgressIndicator::SpinnerProgressIndicator()
386 : percent_done_(0),
387 indeterminate_(true) {}
388
389 SpinnerProgressIndicator::~SpinnerProgressIndicator() {
390 }
391
392 void SpinnerProgressIndicator::SetPercentDone(int percent) {
393 percent_done_ = percent;
394 SchedulePaint();
395 UpdateTimer();
396 }
397
398 void SpinnerProgressIndicator::SetIndeterminate(bool indetereminate) {
399 indeterminate_ = indetereminate;
400 SchedulePaint();
401 UpdateTimer();
402 }
403
404 void SpinnerProgressIndicator::Paint(gfx::Canvas* canvas) {
405 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
406 gfx::ImageSkia* fg = rb.GetImageSkiaNamed(IDR_WEB_INTENT_PROGRESS_FOREGROUND);
407 gfx::ImageSkia* bg = rb.GetImageSkiaNamed(IDR_WEB_INTENT_PROGRESS_BACKGROUND);
408 download_util::PaintCustomDownloadProgress(
409 canvas,
410 *bg,
411 *fg,
412 fg->width(),
413 bounds(),
414 GetProgressAngle(),
415 indeterminate_ ? -1 : percent_done_);
416 }
417
418 gfx::Size SpinnerProgressIndicator::GetPreferredSize() {
419 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
420 gfx::ImageSkia* fg = rb.GetImageSkiaNamed(IDR_WEB_INTENT_PROGRESS_FOREGROUND);
421 return fg->size();
422 }
423
424 void SpinnerProgressIndicator::UpdateTimer() {
425 if (!parent() || !indeterminate_) {
426 timer_.Stop();
427 return;
428 }
429
430 if (!timer_.IsRunning()) {
431 start_time_ = base::TimeTicks::Now();
432 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimerIntervalMs),
433 this, &SpinnerProgressIndicator::SchedulePaint);
434 }
435 }
436
437 int SpinnerProgressIndicator::GetProgressAngle() {
438 if (!indeterminate_)
439 return download_util::kStartAngleDegrees;
440 base::TimeDelta delta = base::TimeTicks::Now() - start_time_;
441 int angle = delta.InSecondsF() * kSpinRateDegreesPerSecond;
442 return angle % 360;
443 }
444
445
446 // WaitingView ----------------------------------------------------------
447 class WaitingView : public views::View {
448 public:
449 WaitingView(views::ButtonListener* listener, bool use_close_button);
450 virtual ~WaitingView();
451
452 private:
453 DISALLOW_COPY_AND_ASSIGN(WaitingView);
454 };
455
456 WaitingView::WaitingView(views::ButtonListener* listener,
457 bool use_close_button) {
458 views::GridLayout* layout = new views::GridLayout(this);
459 layout->set_minimum_size(gfx::Size(WebIntentPicker::kWindowMinWidth, 0));
460 const int kMessageBuiltinBottomPadding = 3;
461 layout->SetInsets(chrome_style::kCloseButtonPadding,
462 0,
463 kWaitingViewVerticalPadding - kMessageBuiltinBottomPadding,
464 0);
465 SetLayoutManager(layout);
466
467 enum GridLayoutColumnSets {
468 HEADER_ROW,
469 CONTENT_ROW,
470 };
471 views::ColumnSet* header_cs = layout->AddColumnSet(HEADER_ROW);
472 header_cs->AddPaddingColumn(1, 1);
473 header_cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
474 GridLayout::USE_PREF, 0, 0);
475 header_cs->AddPaddingColumn(
476 0, chrome_style::kCloseButtonPadding);
477
478 views::ColumnSet* content_cs = layout->AddColumnSet(CONTENT_ROW);
479 content_cs->AddPaddingColumn(0, views::kPanelHorizIndentation);
480 content_cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 1,
481 GridLayout::USE_PREF, 0, 0);
482 content_cs->AddPaddingColumn(0, views::kPanelHorizIndentation);
483
484 // Close button
485 layout->StartRow(0, HEADER_ROW);
486 views::ImageButton* close_button = CreateCloseButton(listener);
487 layout->AddView(close_button);
488 close_button->SetVisible(use_close_button);
489
490 // Throbber
491 layout->AddPaddingRow(0,
492 kWaitingViewVerticalPadding -
493 chrome_style::kCloseButtonPadding -
494 close_button->GetPreferredSize().height());
495 layout->StartRow(0, CONTENT_ROW);
496 SpinnerProgressIndicator* throbber = new SpinnerProgressIndicator();
497 layout->AddView(throbber);
498
499 // Message
500 const int kMessageBuiltinTopPadding = 5;
501 layout->AddPaddingRow(0,
502 chrome_style::kRowPadding -
503 kMessageBuiltinTopPadding);
504 layout->StartRow(0, CONTENT_ROW);
505 views::Label* label = CreateLabel();
506 label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
507 label->SetText(l10n_util::GetStringUTF16(IDS_INTENT_PICKER_WAIT_FOR_CWS));
508 layout->AddView(label);
509
510 // Start the throbber.
511 throbber->SetIndeterminate(true);
512 }
513
514 WaitingView::~WaitingView() {
515 }
516
517
518 // IntentRowView --------------------------------------------------
519
520 // A view for each row in the IntentsView. It displays information
521 // for both installed and suggested intent handlers.
522 class IntentRowView : public views::View,
523 public views::ButtonListener,
524 public views::LinkListener {
525 public:
526 enum ActionType {
527 ACTION_UNKNOWN,
528 ACTION_INSTALL,
529 ACTION_INVOKE
530 };
531
532 class Delegate {
533 public:
534 // Called when the user clicks the "Add to Chrome" button.
535 virtual void OnExtensionInstallClicked(const std::string& extension_id) = 0;
536
537 // Called when the user clicks the extension title link.
538 virtual void OnExtensionLinkClicked(
539 const std::string& extension_id,
540 WindowOpenDisposition disposition) = 0;
541
542 // Called when the action button is clicked. |type| indicates the requested
543 // kind of action, and |tag| identifies the service or extension to
544 // operate on.
545 virtual void OnActionButtonClicked(ActionType type, size_t tag) = 0;
546
547 protected:
548 virtual ~Delegate() {}
549 };
550
551 virtual ~IntentRowView();
552
553 // Creates a new view for |service| or |extension| depending on which
554 // value is not NULL.
555 static IntentRowView* CreateHandlerRow(
556 const WebIntentPickerModel::InstalledService* service,
557 const WebIntentPickerModel::SuggestedExtension* extension,
558 int tag,
559 IntentRowView::Delegate* delegate,
560 int preferred_width);
561
562 // ButtonListener implementation.
563 virtual void ButtonPressed(views::Button* sender,
564 const ui::Event& event) OVERRIDE;
565
566 // LinkListener implementation.
567 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
568
569 // Start an animating throbber for this row, and hide the star rating and the
570 // install button.
571 void StartThrobber();
572
573 void MarkBusy(const std::string& extension_id);
574
575 // Stop the throbber for this row, and show the star rating and the install
576 // button.
577 void StopThrobber();
578
579 protected:
580 virtual void OnEnabledChanged() OVERRIDE;
581
582 virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
583
584 private:
585 IntentRowView(ActionType type, size_t tag);
586
587 // Gets the proper message string associated with |type_|.
588 string16 GetActionButtonMessage();
589
590 // Identifier for the suggested extension displayed in this row.
591 std::string extension_id_;
592
593 // A delegate to respond to button presses and clicked links.
594 Delegate* delegate_;
595
596 // The icon of the extension.
597 views::ImageView* icon_;
598
599 // The title of the extension, which links to the CWS detailed description of
600 // this extension.
601 views::View* title_link_;
602
603 // The star rating of this extension.
604 StarsView* stars_;
605
606 // A button to install the extension.
607 ThrobberNativeTextButton* install_button_;
608
609 // The type of action that is invoked from the row's button.
610 ActionType type_;
611
612 // A tag identifying the data associated with this row. For both installed
613 // and suggested services, this is an index into the respective collections
614 // on the WebIntentPickerModel.
615 size_t tag_;
616
617 DISALLOW_COPY_AND_ASSIGN(IntentRowView);
618 };
619
620 IntentRowView::IntentRowView(ActionType type, size_t tag)
621 : delegate_(NULL),
622 icon_(NULL),
623 title_link_(NULL),
624 stars_(NULL),
625 install_button_(NULL),
626 type_(type),
627 tag_(tag) {}
628
629 IntentRowView* IntentRowView::CreateHandlerRow(
630 const WebIntentPickerModel::InstalledService* service,
631 const WebIntentPickerModel::SuggestedExtension* extension,
632 int tag,
633 IntentRowView::Delegate* delegate,
634 int preferred_width) {
635
636 // one or the other must be set...exclusively
637 DCHECK((service != NULL || extension != NULL) &&
638 (service == NULL || extension == NULL));
639
640
641 const string16& title = (service != NULL)
642 ? service->title : extension->title;
643
644 // TODO(groby): Once links are properly sized (see SuggestionRowViewLayout
645 // refactor notes), can simply SetElideBehavior(views::Label::ELIDE_AT_END).
646 // Note: Verify that views links do not treat empty space at the end as
647 // part of the link, if this change happens.
648 string16 elided_title = ui::ElideText(title, gfx::Font(),
649 WebIntentPicker::kTitleLinkMaxWidth,
650 ui::ELIDE_AT_END);
651
652 const gfx::ImageSkia* icon = NULL;
653 StarsView* stars = NULL;
654 views::Label* label = NULL;
655 IntentRowView* view;
656 if (service != NULL) {
657 view = new IntentRowView(ACTION_INVOKE, tag);
658 icon = service->favicon.ToImageSkia();
659 label = CreateLabel();
660 label->SetText(elided_title);
661 } else {
662 view = new IntentRowView(ACTION_INSTALL, tag);
663 view->extension_id_ = extension->id;
664 icon = extension->icon.ToImageSkia();
665 views::Link* link = CreateLink();
666 link->SetText(elided_title);
667 link->set_listener(view);
668 label = link;
669 stars = new StarsView(extension->average_rating);
670 }
671
672 view->delegate_ = delegate;
673
674 views::GridLayout* grid_layout = new views::GridLayout(view);
675 view->SetLayoutManager(grid_layout);
676
677 views::ColumnSet* columns = grid_layout->AddColumnSet(0);
678 columns->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
679 GridLayout::USE_PREF, 0, 0); // Icon.
680 columns->AddPaddingColumn(0, WebIntentPicker::kIconTextPadding);
681 columns->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
682 GridLayout::FIXED, WebIntentPicker::kTitleLinkMaxWidth, 0);
683 const int kStarRatingHorizontalSpacing = 20;
684 columns->AddPaddingColumn(0, kStarRatingHorizontalSpacing);
685 if (stars != NULL) {
686 columns->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
687 GridLayout::USE_PREF, 0, 0); // Star rating.
688 columns->AddPaddingColumn(0, kStarRatingHorizontalSpacing);
689 }
690 columns->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
691 GridLayout::FIXED, preferred_width, 0); // Button.
692
693 grid_layout->StartRow(0, 0);
694
695 view->icon_ = new views::ImageView();
696 view->icon_->SetImage(icon);
697 grid_layout->AddView(view->icon_);
698
699 view->title_link_ = label;
700 grid_layout->AddView(view->title_link_);
701
702 if (stars != NULL) {
703 view->stars_ = stars;
704 grid_layout->AddView(view->stars_);
705 }
706
707 view->install_button_ = new ThrobberNativeTextButton(
708 view, view->GetActionButtonMessage());
709 view->install_button_->set_preferred_width(preferred_width);
710 grid_layout->AddView(view->install_button_);
711
712 return view;
713 }
714
715 IntentRowView::~IntentRowView() {}
716
717 void IntentRowView::ButtonPressed(views::Button* sender,
718 const ui::Event& event) {
719 if (type_ == ACTION_INSTALL)
720 delegate_->OnExtensionInstallClicked(extension_id_);
721 else
722 delegate_->OnActionButtonClicked(type_, tag_);
723 }
724
725 void IntentRowView::LinkClicked(views::Link* source,
726 int event_flags) {
727 delegate_->OnExtensionLinkClicked(
728 extension_id_, ui::DispositionFromEventFlags(event_flags));
729 }
730
731 void IntentRowView::MarkBusy(const std::string& extension_id) {
732 SetEnabled(false);
733 if (extension_id == extension_id_)
734 StartThrobber();
735 }
736
737 void IntentRowView::StartThrobber() {
738 install_button_->StartThrobber();
739 install_button_->SetText(string16());
740 }
741
742 void IntentRowView::StopThrobber() {
743 install_button_->StopThrobber();
744 install_button_->SetText(GetActionButtonMessage());
745 }
746
747 void IntentRowView::OnEnabledChanged() {
748 title_link_->SetEnabled(enabled());
749 if (stars_)
750 stars_->SetEnabled(enabled());
751 install_button_->SetEnabled(enabled());
752 View::OnEnabledChanged();
753 Layout();
754 }
755
756 void IntentRowView::PaintChildren(gfx::Canvas* canvas) {
757 View::PaintChildren(canvas);
758 if (!enabled())
759 canvas->FillRect(GetLocalBounds(), kHalfOpacityWhite);
760 }
761
762 string16 IntentRowView::GetActionButtonMessage() {
763 int message_id = 0;
764
765 if (type_ == ACTION_INVOKE)
766 message_id = IDS_INTENT_PICKER_SELECT_INTENT;
767 else if (type_ == ACTION_INSTALL)
768 message_id = IDS_INTENT_PICKER_INSTALL_EXTENSION;
769 else
770 NOTREACHED();
771
772 return l10n_util::GetStringUTF16(message_id);
773 }
774
775
776 // IntentsView -----------------------------------------------------
777
778 // A view that contains both installed services and suggested extensions
779 // from the Chrome Web Store that provide an intent service matching the
780 // action/type pair.
781 class IntentsView : public views::View {
782 public:
783 IntentsView(const WebIntentPickerModel* model,
784 IntentRowView::Delegate* delegate);
785
786 virtual ~IntentsView();
787
788 // Update the view to the new model data.
789 void Update();
790
791 // Show the install throbber for the row containing |extension_id|. This
792 // function also hides hides and disables other buttons and links.
793 void StartThrobber(const std::string& extension_id);
794
795 // Hide the install throbber. This function re-enables all buttons and links.
796 void StopThrobber();
797
798 // Adjusts a given width to account for language-specific strings.
799 int AdjustWidth(int old_width);
800
801 protected:
802 virtual void OnEnabledChanged() OVERRIDE;
803
804 private:
805 const WebIntentPickerModel* model_;
806 IntentRowView::Delegate* delegate_;
807
808 // Width for the action button, adjusted to be wide enough for all possible
809 // strings.
810 int button_width_;
811
812 DISALLOW_COPY_AND_ASSIGN(IntentsView);
813 };
814
815 IntentsView::IntentsView(
816 const WebIntentPickerModel* model,
817 IntentRowView::Delegate* delegate)
818 : model_(model),
819 delegate_(delegate),
820 button_width_(0){
821 Update();
822 }
823
824 IntentsView::~IntentsView() {
825 }
826
827 void IntentsView::Update() {
828 RemoveAllChildViews(true);
829
830 ThrobberNativeTextButton size_helper(
831 NULL, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_INSTALL_EXTENSION));
832 size_helper.SetText(
833 l10n_util::GetStringUTF16(IDS_INTENT_PICKER_SELECT_INTENT));
834 button_width_ = std::max(
835 kButtonWidth, size_helper.GetPreferredSize().width());
836
837 const int kAppRowVerticalSpacing = 10;
838 views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical,
839 0, 0, kAppRowVerticalSpacing);
840 SetLayoutManager(layout);
841
842 int available_rows = kMaxRowCount;
843
844 for (size_t i = 0;
845 available_rows > 0 && i < model_->GetInstalledServiceCount();
846 ++i, --available_rows) {
847 const WebIntentPickerModel::InstalledService& service =
848 model_->GetInstalledServiceAt(i);
849 AddChildView(IntentRowView::CreateHandlerRow(&service, NULL, i,
850 delegate_, button_width_));
851 }
852
853 // Only fill up with suggestions if we filled less than kMinRowCount rows.
854 available_rows -= (kMaxRowCount - kMinRowCount);
855
856 for (size_t i = 0;
857 available_rows > 0 && i < model_->GetSuggestedExtensionCount();
858 ++i, --available_rows) {
859 const WebIntentPickerModel::SuggestedExtension& extension =
860 model_->GetSuggestedExtensionAt(i);
861 AddChildView(IntentRowView::CreateHandlerRow(NULL, &extension, i,
862 delegate_, button_width_));
863 }
864 }
865
866 void IntentsView::StartThrobber(const std::string& extension_id) {
867 for (int i = 0; i < child_count(); ++i)
868 static_cast<IntentRowView*>(child_at(i))->MarkBusy(extension_id);
869 }
870
871 void IntentsView::StopThrobber() {
872 for (int i = 0; i < child_count(); ++i) {
873 IntentRowView* row =
874 static_cast<IntentRowView*>(child_at(i));
875 row->SetEnabled(true);
876 row->StopThrobber();
877 }
878 }
879
880 int IntentsView::AdjustWidth(int old_width) {
881 return old_width - kButtonWidth + button_width_;
882 }
883
884 void IntentsView::OnEnabledChanged() {
885 EnableChildViews(this, enabled());
886 View::OnEnabledChanged();
887 }
888
889 } // namespace
890
891
892 // WebIntentPickerViews --------------------------------------------------------
893
894 // Views implementation of WebIntentPicker.
895 class WebIntentPickerViews : public views::ButtonListener,
896 public views::WidgetDelegate,
897 public views::LinkListener,
898 public WebIntentPicker,
899 public WebIntentPickerModelObserver,
900 public IntentRowView::Delegate {
901 public:
902 WebIntentPickerViews(WebContents* web_contents,
903 WebIntentPickerDelegate* delegate,
904 WebIntentPickerModel* model);
905 virtual ~WebIntentPickerViews();
906
907 // views::ButtonListener implementation.
908 // This method is called when the user cancels the picker dialog.
909 virtual void ButtonPressed(views::Button* sender,
910 const ui::Event& event) OVERRIDE;
911
912 // views::WidgetDelegate implementation.
913 virtual void WindowClosing() OVERRIDE;
914 virtual void DeleteDelegate() OVERRIDE;
915 virtual views::Widget* GetWidget() OVERRIDE;
916 virtual const views::Widget* GetWidget() const OVERRIDE;
917 virtual views::View* GetContentsView() OVERRIDE;
918
919 // LinkListener implementation.
920 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
921
922 // WebIntentPicker implementation.
923 virtual void Close() OVERRIDE;
924 virtual void SetActionString(const string16& action) OVERRIDE;
925 virtual void OnExtensionInstallSuccess(const std::string& id) OVERRIDE;
926 virtual void OnExtensionInstallFailure(const std::string& id) OVERRIDE;
927 virtual void OnInlineDispositionAutoResize(const gfx::Size& size) OVERRIDE;
928 virtual void OnPendingAsyncCompleted() OVERRIDE;
929 virtual void InvalidateDelegate() OVERRIDE;
930 virtual void OnInlineDispositionWebContentsLoaded(
931 content::WebContents* web_contents) OVERRIDE;
932
933 // WebIntentPickerModelObserver implementation.
934 virtual void OnModelChanged(WebIntentPickerModel* model) OVERRIDE;
935 virtual void OnFaviconChanged(WebIntentPickerModel* model,
936 size_t index) OVERRIDE;
937 virtual void OnExtensionIconChanged(WebIntentPickerModel* model,
938 const std::string& extension_id) OVERRIDE;
939 virtual void OnInlineDisposition(const string16& title,
940 const GURL& url) OVERRIDE;
941
942 // SuggestedExtensionsRowView::Delegate implementation.
943 virtual void OnExtensionInstallClicked(
944 const std::string& extension_id) OVERRIDE;
945 virtual void OnExtensionLinkClicked(
946 const std::string& extension_id,
947 WindowOpenDisposition disposition) OVERRIDE;
948 virtual void OnActionButtonClicked(
949 IntentRowView::ActionType type,
950 size_t tag) OVERRIDE;
951
952 private:
953 enum WebIntentPickerViewsState {
954 INITIAL,
955 WAITING,
956 NO_SERVICES,
957 LIST_SERVICES,
958 INLINE_SERVICE,
959 } state_;
960
961 // Update picker contents to reflect the current state of the model.
962 void UpdateContents();
963
964 // Shows a spinner and notifies the user that we are waiting for information
965 // from the Chrome Web Store.
966 void ShowWaitingForSuggestions();
967
968 // Updates the dialog with the list of available services, suggestions,
969 // and a nice link to CWS to find more suggestions. This is the "Main"
970 // view of the picker.
971 void ShowAvailableServices();
972
973 // Informs the user that there are no services available to handle
974 // the intent, and that there are no suggestions from the Chrome Web Store.
975 void ShowNoServicesMessage();
976
977 // Restore the contents of the picker to the initial contents.
978 void ResetContents();
979
980 // Resize the constrained window to the size of its contents.
981 void SizeToContents();
982
983 // Clear the contents of the picker.
984 void ClearContents();
985
986 // Returns the service selection question text used in the title
987 // of the picker.
988 const string16 GetActionTitle();
989
990 // Refresh the icon for the inline disposition service that is being
991 // displayed.
992 void RefreshInlineServiceIcon();
993
994 // Refresh the extensions control in the picker.
995 void RefreshExtensions();
996
997 // A weak pointer to the WebIntentPickerDelegate to notify when the user
998 // chooses a service or cancels.
999 WebIntentPickerDelegate* delegate_;
1000
1001 // A weak pointer to the picker model.
1002 WebIntentPickerModel* model_;
1003
1004 // A weak pointer to the action string label.
1005 // Created locally, owned by Views.
1006 views::Label* action_label_;
1007
1008 // A weak pointer to the intents view.
1009 // Created locally, owned by Views view hierarchy.
1010 IntentsView* extensions_;
1011
1012 // Delegate for inline disposition tab contents.
1013 scoped_ptr<WebIntentInlineDispositionDelegate> inline_disposition_delegate_;
1014
1015 // A weak pointer to the WebContents this picker is in.
1016 WebContents* web_contents_;
1017
1018 // A weak pointer to the WebView that hosts the WebContents being displayed.
1019 // Created locally, owned by Views.
1020 views::WebView* webview_;
1021
1022 // A weak pointer to the view that contains all other views in the picker.
1023 // Created locally, owned by Views.
1024 views::View* contents_;
1025
1026 // A weak pointer to the constrained window.
1027 // Created locally, owned by Views.
1028 ConstrainedWindowViews* window_;
1029
1030 // A weak pointer to the more suggestions link.
1031 // Created locally, owned by Views.
1032 views::Link* more_suggestions_link_;
1033
1034 // The icon for the inline disposition service.
1035 views::ImageView* inline_service_icon_;
1036
1037 // A weak pointer to the choose another service link.
1038 // Created locally, owned by Views.
1039 views::Link* choose_another_service_link_;
1040
1041 // Weak pointer to "Waiting for CWS" display. Owned by parent view.
1042 WaitingView* waiting_view_;
1043
1044 // The text for the current action.
1045 string16 action_text_;
1046
1047 // Ownership of the WebContents we are displaying in the inline disposition.
1048 scoped_ptr<WebContents> inline_web_contents_;
1049
1050 // Indicate if dialog should display its own close button.
1051 // TODO(groby): Only relevant until new WebContentsModalDialog is implemented,
1052 // from then on always true.
1053 bool use_close_button_;
1054
1055 // Signals if the picker can be closed. False during extension install.
1056 bool can_close_;
1057
1058 DISALLOW_COPY_AND_ASSIGN(WebIntentPickerViews);
1059 };
1060
1061 // static
1062 WebIntentPicker* WebIntentPicker::Create(content::WebContents* web_contents,
1063 WebIntentPickerDelegate* delegate,
1064 WebIntentPickerModel* model) {
1065 return new WebIntentPickerViews(web_contents, delegate, model);
1066 }
1067
1068 WebIntentPickerViews::WebIntentPickerViews(WebContents* web_contents,
1069 WebIntentPickerDelegate* delegate,
1070 WebIntentPickerModel* model)
1071 : state_(INITIAL),
1072 delegate_(delegate),
1073 model_(model),
1074 action_label_(NULL),
1075 extensions_(NULL),
1076 web_contents_(web_contents),
1077 webview_(new views::WebView(
1078 Profile::FromBrowserContext(web_contents->GetBrowserContext()))),
1079 window_(NULL),
1080 more_suggestions_link_(NULL),
1081 inline_service_icon_(NULL),
1082 choose_another_service_link_(NULL),
1083 waiting_view_(NULL),
1084 can_close_(true) {
1085 use_close_button_ = false;
1086
1087 model_->set_observer(this);
1088 contents_ = new views::View();
1089 contents_->set_background(views::Background::CreateSolidBackground(
1090 chrome_style::GetBackgroundColor()));
1091
1092 // Show the dialog.
1093 window_ = ConstrainedWindowViews::Create(web_contents, this);
1094 if (model_->IsInlineDisposition())
1095 OnInlineDisposition(string16(), model_->inline_disposition_url());
1096 else
1097 UpdateContents();
1098 }
1099
1100 WebIntentPickerViews::~WebIntentPickerViews() {
1101 model_->set_observer(NULL);
1102 }
1103
1104 void WebIntentPickerViews::ButtonPressed(views::Button* sender,
1105 const ui::Event& event) {
1106 DCHECK(delegate_);
1107 delegate_->OnUserCancelledPickerDialog();
1108 }
1109
1110 void WebIntentPickerViews::WindowClosing() {
1111 if (delegate_)
1112 delegate_->OnClosing();
1113 }
1114
1115 void WebIntentPickerViews::DeleteDelegate() {
1116 delete this;
1117 }
1118
1119 views::Widget* WebIntentPickerViews::GetWidget() {
1120 return contents_->GetWidget();
1121 }
1122
1123 const views::Widget* WebIntentPickerViews::GetWidget() const {
1124 return contents_->GetWidget();
1125 }
1126
1127 views::View* WebIntentPickerViews::GetContentsView() {
1128 return contents_;
1129 }
1130
1131 void WebIntentPickerViews::LinkClicked(views::Link* source, int event_flags) {
1132 DCHECK(delegate_);
1133 if (source == more_suggestions_link_) {
1134 delegate_->OnSuggestionsLinkClicked(
1135 ui::DispositionFromEventFlags(event_flags));
1136 } else if (source == choose_another_service_link_) {
1137 // Signal cancellation of inline disposition.
1138 delegate_->OnChooseAnotherService();
1139 ResetContents();
1140 } else {
1141 NOTREACHED();
1142 }
1143 }
1144
1145 void WebIntentPickerViews::Close() {
1146 window_->CloseWebContentsModalDialog();
1147 }
1148
1149 void WebIntentPickerViews::SetActionString(const string16& action) {
1150 action_text_ = action;
1151 if (action_label_) {
1152 action_label_->SetText(GetActionTitle());
1153 contents_->Layout();
1154 SizeToContents();
1155 }
1156 }
1157
1158 void WebIntentPickerViews::OnExtensionInstallSuccess(const std::string& id) {
1159 can_close_ = true;
1160 }
1161
1162 void WebIntentPickerViews::OnExtensionInstallFailure(const std::string& id) {
1163 extensions_->StopThrobber();
1164 extensions_->SetEnabled(true);
1165 more_suggestions_link_->SetEnabled(true);
1166 can_close_ = true;
1167 contents_->Layout();
1168 SizeToContents();
1169
1170 // TODO(binji): What to display to user on failure?
1171 }
1172
1173 void WebIntentPickerViews::OnInlineDispositionAutoResize(
1174 const gfx::Size& size) {
1175 webview_->SetPreferredSize(size);
1176 contents_->Layout();
1177 SizeToContents();
1178 }
1179
1180 void WebIntentPickerViews::OnPendingAsyncCompleted() {
1181 UpdateContents();
1182 }
1183
1184 void WebIntentPickerViews::InvalidateDelegate() {
1185 delegate_ = NULL;
1186 }
1187
1188 void WebIntentPickerViews::ShowNoServicesMessage() {
1189 if (state_ == NO_SERVICES)
1190 return;
1191 state_ = NO_SERVICES;
1192
1193 ClearContents();
1194 views::GridLayout* layout = new views::GridLayout(contents_);
1195 layout->set_minimum_size(gfx::Size(WebIntentPicker::kWindowMinWidth, 0));
1196 const int kContentBuiltinBottomPadding = 3;
1197 layout->SetInsets(chrome_style::kCloseButtonPadding,
1198 0,
1199 chrome_style::kClientBottomPadding -
1200 kContentBuiltinBottomPadding,
1201 0);
1202 contents_->SetLayoutManager(layout);
1203
1204 enum GridLayoutColumnSets {
1205 HEADER_ROW,
1206 CONTENT_ROW,
1207 };
1208 views::ColumnSet* header_cs = layout->AddColumnSet(HEADER_ROW);
1209 header_cs->AddPaddingColumn(
1210 0, chrome_style::kHorizontalPadding);
1211 header_cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
1212 GridLayout::USE_PREF, 0, 0); // Title
1213 header_cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
1214 GridLayout::USE_PREF, 0, 0); // Close button
1215 header_cs->AddPaddingColumn(
1216 0, chrome_style::kCloseButtonPadding);
1217
1218 views::ColumnSet* content_cs = layout->AddColumnSet(CONTENT_ROW);
1219 content_cs->AddPaddingColumn(
1220 0, chrome_style::kHorizontalPadding);
1221 content_cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
1222 GridLayout::USE_PREF, 0, 0); // Body
1223 content_cs->AddPaddingColumn(
1224 0, chrome_style::kHorizontalPadding);
1225
1226 // Header
1227 layout->StartRow(0, HEADER_ROW);
1228 views::Label* title = CreateTitleLabel();
1229 title->SetText(l10n_util::GetStringUTF16(
1230 IDS_INTENT_PICKER_NO_SERVICES_TITLE));
1231 layout->AddView(title);
1232
1233 views::ImageButton* close_button = CreateCloseButton(this);
1234 layout->AddView(close_button);
1235 close_button->SetVisible(use_close_button_);
1236
1237 // Content
1238 const int kHeaderBuiltinBottomPadding = 4;
1239 const int kContentBuiltinTopPadding = 5;
1240 layout->AddPaddingRow(0,
1241 chrome_style::kRowPadding -
1242 kHeaderBuiltinBottomPadding -
1243 kContentBuiltinTopPadding);
1244 layout->StartRow(0, CONTENT_ROW);
1245 views::Label* body = CreateLabel();
1246 body->SetMultiLine(true);
1247 body->SetText(l10n_util::GetStringUTF16(IDS_INTENT_PICKER_NO_SERVICES));
1248 layout->AddView(body);
1249
1250 int height = contents_->GetHeightForWidth(WebIntentPicker::kWindowMinWidth);
1251 contents_->SetSize(gfx::Size(WebIntentPicker::kWindowMinWidth, height));
1252 contents_->Layout();
1253 }
1254
1255 void WebIntentPickerViews::OnInlineDispositionWebContentsLoaded(
1256 content::WebContents* web_contents) {
1257 if (state_ == INLINE_SERVICE)
1258 return;
1259 state_ = INLINE_SERVICE;
1260
1261 // Replace the picker with the inline disposition.
1262 ClearContents();
1263 views::GridLayout* grid_layout = new views::GridLayout(contents_);
1264 grid_layout->set_minimum_size(gfx::Size(WebIntentPicker::kWindowMinWidth, 0));
1265 grid_layout->SetInsets(chrome_style::kCloseButtonPadding, 0,
1266 chrome_style::kClientBottomPadding, 0);
1267 contents_->SetLayoutManager(grid_layout);
1268
1269 enum GridLayoutColumnSets {
1270 HEADER_ROW,
1271 SEPARATOR_ROW,
1272 WEB_CONTENTS_ROW,
1273 };
1274 views::ColumnSet* header_cs = grid_layout->AddColumnSet(HEADER_ROW);
1275 header_cs->AddPaddingColumn(
1276 0, chrome_style::kHorizontalPadding);
1277 header_cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
1278 GridLayout::USE_PREF, 0, 0); // Icon, title, link.
1279 header_cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1280 header_cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
1281 GridLayout::USE_PREF, 0, 0); // Close button.
1282 header_cs->AddPaddingColumn(
1283 0, chrome_style::kCloseButtonPadding);
1284
1285 views::ColumnSet* sep_cs = grid_layout->AddColumnSet(SEPARATOR_ROW);
1286 sep_cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
1287 GridLayout::USE_PREF, 0, 0); // Separator.
1288
1289 views::ColumnSet* contents_cs = grid_layout->AddColumnSet(WEB_CONTENTS_ROW);
1290 contents_cs->AddPaddingColumn(0, 1);
1291 contents_cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 1,
1292 GridLayout::USE_PREF, 0, 0); // Web contents.
1293 contents_cs->AddPaddingColumn(0, 1);
1294
1295 // Header.
1296 grid_layout->StartRow(0, HEADER_ROW);
1297
1298 const WebIntentPickerModel::InstalledService* service =
1299 model_->GetInstalledServiceWithURL(model_->inline_disposition_url());
1300
1301 if (!inline_service_icon_)
1302 inline_service_icon_ = new views::ImageView();
1303 inline_service_icon_->SetImage(service->favicon.ToImageSkia());
1304
1305 views::Label* title = CreateLabel();
1306 title->SetText(ui::ElideText(
1307 service->title, title->font(), kTitleLinkMaxWidth, ui::ELIDE_AT_END));
1308
1309 if (!choose_another_service_link_)
1310 choose_another_service_link_ = CreateLink();
1311 choose_another_service_link_->SetText(l10n_util::GetStringUTF16(
1312 IDS_INTENT_PICKER_USE_ALTERNATE_SERVICE));
1313 choose_another_service_link_->set_listener(this);
1314
1315 grid_layout->AddView(CreateInlineDispositionHeader(
1316 inline_service_icon_, title, choose_another_service_link_));
1317 choose_another_service_link_->SetVisible(IsUseAnotherServiceVisible(model_));
1318
1319 views::ImageButton* close_button = CreateCloseButton(this);
1320 grid_layout->AddView(close_button);
1321 close_button->SetVisible(use_close_button_);
1322
1323 // Separator.
1324 const int kHeaderBuiltinBottomPadding = 4;
1325 grid_layout->AddPaddingRow(0,
1326 chrome_style::kRowPadding -
1327 kHeaderBuiltinBottomPadding);
1328 grid_layout->StartRow(0, SEPARATOR_ROW);
1329 grid_layout->AddView(new views::Separator());
1330
1331 // Inline web contents.
1332 const int kSeparatorBottomPadding = 3;
1333 grid_layout->AddPaddingRow(0, kSeparatorBottomPadding);
1334 grid_layout->StartRow(0, WEB_CONTENTS_ROW);
1335 grid_layout->AddView(webview_);
1336
1337 contents_->Layout();
1338 SizeToContents();
1339 }
1340
1341 void WebIntentPickerViews::OnModelChanged(WebIntentPickerModel* model) {
1342 if (state_ == WAITING && !model->IsWaitingForSuggestions())
1343 UpdateContents();
1344
1345 if (choose_another_service_link_) {
1346 choose_another_service_link_->SetVisible(IsUseAnotherServiceVisible(model));
1347 contents_->Layout();
1348 SizeToContents();
1349 }
1350
1351 if (extensions_)
1352 RefreshExtensions();
1353 }
1354
1355 void WebIntentPickerViews::OnFaviconChanged(WebIntentPickerModel* model,
1356 size_t index) {
1357 // TODO(groby): Update favicons on extensions_;
1358 if (inline_service_icon_)
1359 RefreshInlineServiceIcon();
1360 if (extensions_)
1361 RefreshExtensions();
1362 }
1363
1364 void WebIntentPickerViews::OnExtensionIconChanged(
1365 WebIntentPickerModel* model,
1366 const std::string& extension_id) {
1367 OnFaviconChanged(model, -1);
1368 }
1369
1370 void WebIntentPickerViews::OnInlineDisposition(
1371 const string16&, const GURL& url) {
1372 DCHECK(delegate_);
1373 Profile* profile =
1374 Profile::FromBrowserContext(web_contents_->GetBrowserContext());
1375 if (!webview_)
1376 webview_ = new views::WebView(profile);
1377
1378 inline_web_contents_.reset(
1379 delegate_->CreateWebContentsForInlineDisposition(profile, url));
1380
1381 // Does not take ownership, so we keep a scoped_ptr
1382 // for the WebContents locally.
1383 webview_->SetWebContents(inline_web_contents_.get());
1384 Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
1385 inline_disposition_delegate_.reset(
1386 new WebIntentInlineDispositionDelegate(this, inline_web_contents_.get(),
1387 browser));
1388
1389 inline_web_contents_->GetController().LoadURL(
1390 url,
1391 content::Referrer(),
1392 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1393 std::string());
1394
1395 // Disable all buttons.
1396 // TODO(groby): Add throbber for inline dispo - see http://crbug.com/142519.
1397 if (extensions_)
1398 extensions_->SetEnabled(false);
1399 if (more_suggestions_link_)
1400 more_suggestions_link_->SetEnabled(false);
1401 contents_->Layout();
1402 }
1403
1404 void WebIntentPickerViews::OnExtensionInstallClicked(
1405 const std::string& extension_id) {
1406 DCHECK(delegate_);
1407 can_close_ = false;
1408 extensions_->StartThrobber(extension_id);
1409 more_suggestions_link_->SetEnabled(false);
1410 contents_->Layout();
1411 delegate_->OnExtensionInstallRequested(extension_id);
1412 }
1413
1414 void WebIntentPickerViews::OnExtensionLinkClicked(
1415 const std::string& extension_id,
1416 WindowOpenDisposition disposition) {
1417 DCHECK(delegate_);
1418 delegate_->OnExtensionLinkClicked(extension_id, disposition);
1419 }
1420
1421 void WebIntentPickerViews::OnActionButtonClicked(
1422 IntentRowView::ActionType type, size_t tag) {
1423 DCHECK(delegate_);
1424 DCHECK_EQ(IntentRowView::ACTION_INVOKE, type);
1425 const WebIntentPickerModel::InstalledService& service =
1426 model_->GetInstalledServiceAt(tag);
1427 delegate_->OnServiceChosen(service.url, service.disposition,
1428 WebIntentPickerDelegate::kEnableDefaults);
1429 }
1430
1431 void WebIntentPickerViews::UpdateContents() {
1432 if (model_ && model_->IsInlineDisposition())
1433 return;
1434
1435 if (model_ && model_->IsWaitingForSuggestions()) {
1436 ShowWaitingForSuggestions();
1437 } else if (model_ && (model_->GetInstalledServiceCount() ||
1438 model_->GetSuggestedExtensionCount())) {
1439 ShowAvailableServices();
1440 } else {
1441 ShowNoServicesMessage();
1442 }
1443 SizeToContents();
1444 }
1445
1446 void WebIntentPickerViews::ShowWaitingForSuggestions() {
1447 if (state_ == WAITING)
1448 return;
1449 state_ = WAITING;
1450 ClearContents();
1451 contents_->SetLayoutManager(new views::FillLayout());
1452 waiting_view_ = new WaitingView(this, use_close_button_);
1453 contents_->AddChildView(waiting_view_);
1454 int height = contents_->GetHeightForWidth(kWindowMinWidth);
1455 contents_->SetSize(gfx::Size(kWindowMinWidth, height));
1456 contents_->Layout();
1457 }
1458
1459 const string16 WebIntentPickerViews::GetActionTitle() {
1460 return action_text_.empty() ?
1461 l10n_util::GetStringUTF16(IDS_INTENT_PICKER_CHOOSE_SERVICE) :
1462 action_text_;
1463 }
1464
1465 void WebIntentPickerViews::ShowAvailableServices() {
1466 ClearContents();
1467 state_ = LIST_SERVICES;
1468 extensions_ = new IntentsView(model_, this);
1469 gfx::Size min_size = gfx::Size(extensions_->AdjustWidth(kWindowMinWidth), 0);
1470
1471 views::GridLayout* grid_layout = new views::GridLayout(contents_);
1472 grid_layout->set_minimum_size(min_size);
1473 const int kIconBuiltinBottomPadding = 4;
1474 grid_layout->SetInsets(chrome_style::kCloseButtonPadding,
1475 0,
1476 chrome_style::kClientBottomPadding -
1477 kIconBuiltinBottomPadding,
1478 0);
1479 contents_->SetLayoutManager(grid_layout);
1480
1481 enum GridLayoutColumnSets {
1482 HEADER_ROW,
1483 CONTENT_ROW,
1484 };
1485 views::ColumnSet* header_cs = grid_layout->AddColumnSet(HEADER_ROW);
1486 header_cs->AddPaddingColumn(
1487 0, chrome_style::kHorizontalPadding);
1488 header_cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
1489 GridLayout::USE_PREF, 0, 0); // Action title
1490 header_cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
1491 GridLayout::USE_PREF, 0, 0); // Close button
1492 header_cs->AddPaddingColumn(
1493 0, chrome_style::kCloseButtonPadding);
1494
1495 views::ColumnSet* content_cs = grid_layout->AddColumnSet(CONTENT_ROW);
1496 content_cs->AddPaddingColumn(
1497 0, chrome_style::kHorizontalPadding);
1498 content_cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
1499 GridLayout::USE_PREF, 0, 0); // Content.
1500 content_cs->AddPaddingColumn(
1501 0, chrome_style::kHorizontalPadding);
1502
1503 // Header.
1504 grid_layout->StartRow(0, HEADER_ROW);
1505 if (!action_label_)
1506 action_label_ = CreateTitleLabel();
1507 action_label_->SetText(GetActionTitle());
1508 grid_layout->AddView(action_label_);
1509
1510 views::ImageButton* close_button = CreateCloseButton(this);
1511 grid_layout->AddView(close_button);
1512 close_button->SetVisible(use_close_button_);
1513
1514 // Extensions.
1515 const int kHeaderBuiltinBottomPadding = 4;
1516 grid_layout->AddPaddingRow(0,
1517 chrome_style::kRowPadding -
1518 kHeaderBuiltinBottomPadding);
1519 grid_layout->StartRow(0, CONTENT_ROW);
1520 grid_layout->AddView(extensions_);
1521
1522
1523 // Row with "more suggestions" link.
1524 const int kIconBuiltinTopPadding = 6;
1525 grid_layout->AddPaddingRow(0,
1526 chrome_style::kRowPadding -
1527 kIconBuiltinTopPadding);
1528 grid_layout->StartRow(0, CONTENT_ROW);
1529 views::View* more_view = new views::View();
1530 more_view->SetLayoutManager(new views::BoxLayout(
1531 views::BoxLayout::kHorizontal, 0, 0, kIconTextPadding));
1532 views::ImageView* icon = new views::ImageView();
1533 icon->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1534 IDR_WEBSTORE_ICON_16));
1535 more_view->AddChildView(icon);
1536 if (!more_suggestions_link_)
1537 more_suggestions_link_ = CreateLink();
1538 more_suggestions_link_->SetText(
1539 l10n_util::GetStringUTF16(IDS_FIND_MORE_INTENT_HANDLER_MESSAGE));
1540 more_suggestions_link_->set_listener(this);
1541 more_view->AddChildView(more_suggestions_link_);
1542 grid_layout->AddView(more_view, 1, 1,
1543 GridLayout::LEADING, GridLayout::CENTER);
1544
1545 contents_->Layout();
1546 }
1547
1548 void WebIntentPickerViews::ResetContents() {
1549 // Abandon both web contents and webview.
1550 webview_->SetWebContents(NULL);
1551 inline_web_contents_.reset();
1552 webview_ = NULL;
1553
1554 // Re-initialize the UI.
1555 UpdateContents();
1556
1557 // Restore previous state.
1558 if (extensions_)
1559 extensions_->Update();
1560 if (action_label_)
1561 action_label_->SetText(action_text_);
1562 contents_->Layout();
1563 SizeToContents();
1564 }
1565
1566 void WebIntentPickerViews::SizeToContents() {
1567 gfx::Size client_size = contents_->GetPreferredSize();
1568 gfx::Rect client_bounds(client_size);
1569 gfx::Rect new_window_bounds = window_->non_client_view()->frame_view()->
1570 GetWindowBoundsForClientBounds(client_bounds);
1571 window_->CenterWindow(new_window_bounds.size());
1572 }
1573
1574 void WebIntentPickerViews::ClearContents() {
1575 DCHECK(contents_);
1576 // The call RemoveAllChildViews(true) deletes all children of |contents|. If
1577 // we do not set our weak pointers to NULL, then they will continue to point
1578 // to where the deleted objects used to be, i.e. unitialized memory. This
1579 // would cause hard-to-explain crashes.
1580 contents_->RemoveAllChildViews(true);
1581 action_label_ = NULL;
1582 extensions_ = NULL;
1583 more_suggestions_link_ = NULL;
1584 inline_service_icon_ = NULL;
1585 choose_another_service_link_ = NULL;
1586 }
1587
1588 void WebIntentPickerViews::RefreshInlineServiceIcon() {
1589 DCHECK(inline_service_icon_);
1590 const WebIntentPickerModel::InstalledService* inline_service =
1591 model_->GetInstalledServiceWithURL(model_->inline_disposition_url());
1592 if (inline_service)
1593 inline_service_icon_->SetImage(inline_service->favicon.ToImageSkia());
1594 }
1595
1596 void WebIntentPickerViews::RefreshExtensions() {
1597 DCHECK(extensions_);
1598 extensions_->Update();
1599 contents_->Layout();
1600 SizeToContents();
1601 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698