• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 "chrome/browser/ui/views/wrench_menu.h"
6 
7 #include <cmath>
8 
9 #include "base/string_number_conversions.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/metrics/user_metrics.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "content/browser/tab_contents/tab_contents.h"
16 #include "content/common/notification_observer.h"
17 #include "content/common/notification_registrar.h"
18 #include "content/common/notification_source.h"
19 #include "content/common/notification_type.h"
20 #include "grit/chromium_strings.h"
21 #include "grit/generated_resources.h"
22 #include "grit/theme_resources.h"
23 #include "third_party/skia/include/core/SkPaint.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/gfx/canvas_skia.h"
28 #include "ui/gfx/skia_util.h"
29 #include "views/background.h"
30 #include "views/controls/button/image_button.h"
31 #include "views/controls/button/menu_button.h"
32 #include "views/controls/button/text_button.h"
33 #include "views/controls/label.h"
34 #include "views/controls/menu/menu_config.h"
35 #include "views/controls/menu/menu_item_view.h"
36 #include "views/controls/menu/menu_scroll_view_container.h"
37 #include "views/controls/menu/submenu_view.h"
38 #include "views/window/window.h"
39 
40 using ui::MenuModel;
41 using views::CustomButton;
42 using views::ImageButton;
43 using views::Label;
44 using views::MenuConfig;
45 using views::MenuItemView;
46 using views::TextButton;
47 using views::View;
48 
49 namespace {
50 
51 // Colors used for buttons.
52 const SkColor kHotBorderColor = SkColorSetARGB(72, 0, 0, 0);
53 const SkColor kBorderColor = SkColorSetARGB(36, 0, 0, 0);
54 const SkColor kPushedBorderColor = SkColorSetARGB(72, 0, 0, 0);
55 const SkColor kHotBackgroundColor = SkColorSetARGB(204, 255, 255, 255);
56 const SkColor kBackgroundColor = SkColorSetARGB(102, 255, 255, 255);
57 const SkColor kPushedBackgroundColor = SkColorSetARGB(13, 0, 0, 0);
58 
59 // Horizontal padding on the edges of the buttons.
60 const int kHorizontalPadding = 6;
61 
62 // Subclass of ImageButton whose preferred size includes the size of the border.
63 class FullscreenButton : public ImageButton {
64  public:
FullscreenButton(views::ButtonListener * listener)65   explicit FullscreenButton(views::ButtonListener* listener)
66       : ImageButton(listener) { }
67 
GetPreferredSize()68   virtual gfx::Size GetPreferredSize() {
69     gfx::Size pref = ImageButton::GetPreferredSize();
70     gfx::Insets insets;
71     if (border())
72       border()->GetInsets(&insets);
73     pref.Enlarge(insets.width(), insets.height());
74     return pref;
75   }
76 
77  private:
78   DISALLOW_COPY_AND_ASSIGN(FullscreenButton);
79 };
80 
81 // Border for buttons contained in the menu. This is only used for getting the
82 // insets, the actual painting is done in MenuButtonBackground.
83 class MenuButtonBorder : public views::Border {
84  public:
MenuButtonBorder()85   MenuButtonBorder() {}
86 
Paint(const View & view,gfx::Canvas * canvas) const87   virtual void Paint(const View& view, gfx::Canvas* canvas) const {
88     // Painting of border is done in MenuButtonBackground.
89   }
90 
GetInsets(gfx::Insets * insets) const91   virtual void GetInsets(gfx::Insets* insets) const {
92     insets->Set(MenuConfig::instance().item_top_margin,
93                 kHorizontalPadding,
94                 MenuConfig::instance().item_bottom_margin,
95                 kHorizontalPadding);
96   }
97 
98  private:
99   DISALLOW_COPY_AND_ASSIGN(MenuButtonBorder);
100 };
101 
102 // Combination border/background for the buttons contained in the menu. The
103 // painting of the border/background is done here as TextButton does not always
104 // paint the border.
105 class MenuButtonBackground : public views::Background {
106  public:
107   enum ButtonType {
108     LEFT_BUTTON,
109     CENTER_BUTTON,
110     RIGHT_BUTTON,
111     SINGLE_BUTTON,
112   };
113 
MenuButtonBackground(ButtonType type)114   explicit MenuButtonBackground(ButtonType type)
115       : type_(type),
116         left_button_(NULL),
117         right_button_(NULL) {}
118 
119   // Used when the type is CENTER_BUTTON to determine if the left/right edge
120   // needs to be rendered selected.
SetOtherButtons(CustomButton * left_button,CustomButton * right_button)121   void SetOtherButtons(CustomButton* left_button, CustomButton* right_button) {
122     if (base::i18n::IsRTL()) {
123       left_button_ = right_button;
124       right_button_ = left_button;
125     } else {
126       left_button_ = left_button;
127       right_button_ = right_button;
128     }
129   }
130 
Paint(gfx::Canvas * canvas,View * view) const131   virtual void Paint(gfx::Canvas* canvas, View* view) const {
132     CustomButton::ButtonState state =
133         (view->GetClassName() == views::Label::kViewClassName) ?
134         CustomButton::BS_NORMAL : static_cast<CustomButton*>(view)->state();
135     int w = view->width();
136     int h = view->height();
137     switch (TypeAdjustedForRTL()) {
138       case LEFT_BUTTON:
139         canvas->FillRectInt(background_color(state), 1, 1, w, h - 2);
140         canvas->FillRectInt(border_color(state), 2, 0, w, 1);
141         canvas->FillRectInt(border_color(state), 1, 1, 1, 1);
142         canvas->FillRectInt(border_color(state), 0, 2, 1, h - 4);
143         canvas->FillRectInt(border_color(state), 1, h - 2, 1, 1);
144         canvas->FillRectInt(border_color(state), 2, h - 1, w, 1);
145         break;
146 
147       case CENTER_BUTTON: {
148         canvas->FillRectInt(background_color(state), 1, 1, w - 2, h - 2);
149         SkColor left_color = state != CustomButton::BS_NORMAL ?
150             border_color(state) : border_color(left_button_->state());
151         canvas->FillRectInt(left_color, 0, 0, 1, h);
152         canvas->FillRectInt(border_color(state), 1, 0, w - 2, 1);
153         canvas->FillRectInt(border_color(state), 1, h - 1, w - 2, 1);
154         SkColor right_color = state != CustomButton::BS_NORMAL ?
155             border_color(state) : border_color(right_button_->state());
156         canvas->FillRectInt(right_color, w - 1, 0, 1, h);
157         break;
158       }
159 
160       case RIGHT_BUTTON:
161         canvas->FillRectInt(background_color(state), 0, 1, w - 1, h - 2);
162         canvas->FillRectInt(border_color(state), 0, 0, w - 2, 1);
163         canvas->FillRectInt(border_color(state), w - 2, 1, 1, 1);
164         canvas->FillRectInt(border_color(state), w - 1, 2, 1, h - 4);
165         canvas->FillRectInt(border_color(state), w - 2, h - 2, 1, 1);
166         canvas->FillRectInt(border_color(state), 0, h - 1, w - 2, 1);
167         break;
168 
169       case SINGLE_BUTTON:
170         canvas->FillRectInt(background_color(state), 1, 1, w - 2, h - 2);
171         canvas->FillRectInt(border_color(state), 2, 0, w - 4, 1);
172         canvas->FillRectInt(border_color(state), 1, 1, 1, 1);
173         canvas->FillRectInt(border_color(state), 0, 2, 1, h - 4);
174         canvas->FillRectInt(border_color(state), 1, h - 2, 1, 1);
175         canvas->FillRectInt(border_color(state), 2, h - 1, w - 4, 1);
176         canvas->FillRectInt(border_color(state), w - 2, 1, 1, 1);
177         canvas->FillRectInt(border_color(state), w - 1, 2, 1, h - 4);
178         canvas->FillRectInt(border_color(state), w - 2, h - 2, 1, 1);
179         break;
180 
181       default:
182         NOTREACHED();
183         break;
184     }
185   }
186 
187  private:
border_color(CustomButton::ButtonState state)188   static SkColor border_color(CustomButton::ButtonState state) {
189     switch (state) {
190       case CustomButton::BS_HOT:    return kHotBorderColor;
191       case CustomButton::BS_PUSHED: return kPushedBorderColor;
192       default:                      return kBorderColor;
193     }
194   }
195 
background_color(CustomButton::ButtonState state)196   static SkColor background_color(CustomButton::ButtonState state) {
197     switch (state) {
198       case CustomButton::BS_HOT:    return kHotBackgroundColor;
199       case CustomButton::BS_PUSHED: return kPushedBackgroundColor;
200       default:                      return kBackgroundColor;
201     }
202   }
203 
TypeAdjustedForRTL() const204   ButtonType TypeAdjustedForRTL() const {
205     if (!base::i18n::IsRTL())
206       return type_;
207 
208     switch (type_) {
209       case LEFT_BUTTON:   return RIGHT_BUTTON;
210       case RIGHT_BUTTON:  return LEFT_BUTTON;
211       default:            break;
212     }
213     return type_;
214   }
215 
216   const ButtonType type_;
217 
218   // See description above setter for details.
219   CustomButton* left_button_;
220   CustomButton* right_button_;
221 
222   DISALLOW_COPY_AND_ASSIGN(MenuButtonBackground);
223 };
224 
225 // A View subclass that forces SchedulePaint to paint all. Normally when the
226 // mouse enters/exits a button the buttons invokes SchedulePaint. As part of the
227 // button border (MenuButtonBackground) is rendered by the button to the
228 // left/right of it SchedulePaint on the the button may not be enough, so this
229 // forces a paint all.
230 class ScheduleAllView : public views::View {
231  public:
ScheduleAllView()232   ScheduleAllView() {}
233 
SchedulePaintInRect(const gfx::Rect & r)234   virtual void SchedulePaintInRect(const gfx::Rect& r) {
235     if (!IsVisible())
236       return;
237 
238     if (parent())
239       parent()->SchedulePaintInRect(GetMirroredBounds());
240   }
241 
242  private:
243   DISALLOW_COPY_AND_ASSIGN(ScheduleAllView);
244 };
245 
GetAccessibleNameForWrenchMenuItem(MenuModel * model,int item_index,int accessible_string_id)246 string16 GetAccessibleNameForWrenchMenuItem(
247       MenuModel* model, int item_index, int accessible_string_id) {
248   string16 accessible_name = l10n_util::GetStringUTF16(accessible_string_id);
249   string16 accelerator_text;
250 
251   ui::Accelerator menu_accelerator;
252   if (model->GetAcceleratorAt(item_index, &menu_accelerator)) {
253     accelerator_text =
254         views::Accelerator(menu_accelerator.GetKeyCode(),
255                            menu_accelerator.modifiers()).GetShortcutText();
256   }
257 
258   return MenuItemView::GetAccessibleNameForMenuItem(
259       accessible_name, accelerator_text);
260 }
261 
262 // WrenchMenuView is a view that can contain text buttons.
263 class WrenchMenuView : public ScheduleAllView, public views::ButtonListener {
264  public:
WrenchMenuView(WrenchMenu * menu,MenuModel * menu_model)265   WrenchMenuView(WrenchMenu* menu, MenuModel* menu_model)
266       : menu_(menu), menu_model_(menu_model) { }
267 
CreateAndConfigureButton(int string_id,MenuButtonBackground::ButtonType type,int index,MenuButtonBackground ** background)268   TextButton* CreateAndConfigureButton(int string_id,
269                                        MenuButtonBackground::ButtonType type,
270                                        int index,
271                                        MenuButtonBackground** background) {
272     return CreateButtonWithAccName(
273       string_id, type, index, background, string_id);
274   }
275 
CreateButtonWithAccName(int string_id,MenuButtonBackground::ButtonType type,int index,MenuButtonBackground ** background,int acc_string_id)276   TextButton* CreateButtonWithAccName(int string_id,
277                                       MenuButtonBackground::ButtonType type,
278                                       int index,
279                                       MenuButtonBackground** background,
280                                       int acc_string_id) {
281     TextButton* button =
282         new TextButton(this, UTF16ToWide(l10n_util::GetStringUTF16(string_id)));
283     button->SetAccessibleName(
284         GetAccessibleNameForWrenchMenuItem(menu_model_, index, acc_string_id));
285     button->SetFocusable(true);
286     button->set_request_focus_on_press(false);
287     button->set_tag(index);
288     button->SetEnabled(menu_model_->IsEnabledAt(index));
289     button->set_prefix_type(TextButton::PREFIX_HIDE);
290     MenuButtonBackground* bg = new MenuButtonBackground(type);
291     button->set_background(bg);
292     button->SetEnabledColor(MenuConfig::instance().text_color);
293     if (background)
294       *background = bg;
295     button->set_border(new MenuButtonBorder());
296     button->set_alignment(TextButton::ALIGN_CENTER);
297     button->SetNormalHasBorder(true);
298     button->SetFont(views::MenuConfig::instance().font);
299     button->ClearMaxTextSize();
300     AddChildView(button);
301     return button;
302   }
303 
304  protected:
305   // Hosting WrenchMenu.
306   WrenchMenu* menu_;
307 
308   // The menu model containing the increment/decrement/reset items.
309   MenuModel* menu_model_;
310 
311  private:
312   DISALLOW_COPY_AND_ASSIGN(WrenchMenuView);
313 };
314 
315 }  // namespace
316 
317 // CutCopyPasteView ------------------------------------------------------------
318 
319 // CutCopyPasteView is the view containing the cut/copy/paste buttons.
320 class WrenchMenu::CutCopyPasteView : public WrenchMenuView {
321  public:
CutCopyPasteView(WrenchMenu * menu,MenuModel * menu_model,int cut_index,int copy_index,int paste_index)322   CutCopyPasteView(WrenchMenu* menu,
323                    MenuModel* menu_model,
324                    int cut_index,
325                    int copy_index,
326                    int paste_index)
327       : WrenchMenuView(menu, menu_model) {
328     TextButton* cut = CreateAndConfigureButton(
329         IDS_CUT, MenuButtonBackground::LEFT_BUTTON, cut_index, NULL);
330 
331     MenuButtonBackground* copy_background = NULL;
332     CreateAndConfigureButton(
333         IDS_COPY, MenuButtonBackground::CENTER_BUTTON, copy_index,
334         &copy_background);
335 
336     TextButton* paste = CreateAndConfigureButton(
337         IDS_PASTE, MenuButtonBackground::RIGHT_BUTTON, paste_index, NULL);
338 
339     copy_background->SetOtherButtons(cut, paste);
340   }
341 
GetPreferredSize()342   gfx::Size GetPreferredSize() {
343     // Returned height doesn't matter as MenuItemView forces everything to the
344     // height of the menuitemview.
345     return gfx::Size(GetMaxChildViewPreferredWidth() * child_count(), 0);
346   }
347 
Layout()348   void Layout() {
349     // All buttons are given the same width.
350     int width = GetMaxChildViewPreferredWidth();
351     for (int i = 0; i < child_count(); ++i)
352       GetChildViewAt(i)->SetBounds(i * width, 0, width, height());
353   }
354 
355   // ButtonListener
ButtonPressed(views::Button * sender,const views::Event & event)356   virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
357     menu_->CancelAndEvaluate(menu_model_, sender->tag());
358   }
359 
360  private:
361   // Returns the max preferred width of all the children.
GetMaxChildViewPreferredWidth()362   int GetMaxChildViewPreferredWidth() {
363     int width = 0;
364     for (int i = 0; i < child_count(); ++i)
365       width = std::max(width, GetChildViewAt(i)->GetPreferredSize().width());
366     return width;
367   }
368 
369   DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView);
370 };
371 
372 // ZoomView --------------------------------------------------------------------
373 
374 // Padding between the increment buttons and the reset button.
375 static const int kZoomPadding = 6;
376 
377 // ZoomView contains the various zoom controls: two buttons to increase/decrease
378 // the zoom, a label showing the current zoom percent, and a button to go
379 // full-screen.
380 class WrenchMenu::ZoomView : public WrenchMenuView,
381                              public NotificationObserver {
382  public:
ZoomView(WrenchMenu * menu,MenuModel * menu_model,int decrement_index,int increment_index,int fullscreen_index)383   ZoomView(WrenchMenu* menu,
384            MenuModel* menu_model,
385            int decrement_index,
386            int increment_index,
387            int fullscreen_index)
388       : WrenchMenuView(menu, menu_model),
389         fullscreen_index_(fullscreen_index),
390         increment_button_(NULL),
391         zoom_label_(NULL),
392         decrement_button_(NULL),
393         fullscreen_button_(NULL),
394         zoom_label_width_(0) {
395     decrement_button_ = CreateButtonWithAccName(
396         IDS_ZOOM_MINUS2, MenuButtonBackground::LEFT_BUTTON, decrement_index,
397         NULL, IDS_ACCNAME_ZOOM_MINUS2);
398 
399     zoom_label_ = new Label(
400         UTF16ToWide(l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100)));
401     zoom_label_->SetColor(MenuConfig::instance().text_color);
402     zoom_label_->SetHorizontalAlignment(Label::ALIGN_RIGHT);
403     MenuButtonBackground* center_bg =
404         new MenuButtonBackground(MenuButtonBackground::CENTER_BUTTON);
405     zoom_label_->set_background(center_bg);
406     zoom_label_->set_border(new MenuButtonBorder());
407     zoom_label_->SetFont(MenuConfig::instance().font);
408     AddChildView(zoom_label_);
409     zoom_label_width_ = MaxWidthForZoomLabel();
410 
411     increment_button_ = CreateButtonWithAccName(
412         IDS_ZOOM_PLUS2, MenuButtonBackground::RIGHT_BUTTON, increment_index,
413         NULL, IDS_ACCNAME_ZOOM_PLUS2);
414 
415     center_bg->SetOtherButtons(decrement_button_, increment_button_);
416 
417     fullscreen_button_ = new FullscreenButton(this);
418     fullscreen_button_->SetImage(
419         ImageButton::BS_NORMAL,
420         ResourceBundle::GetSharedInstance().GetBitmapNamed(
421             IDR_FULLSCREEN_MENU_BUTTON));
422     fullscreen_button_->SetFocusable(true);
423     fullscreen_button_->set_request_focus_on_press(false);
424     fullscreen_button_->set_tag(fullscreen_index);
425     fullscreen_button_->SetImageAlignment(
426         ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE);
427     fullscreen_button_->set_border(views::Border::CreateEmptyBorder(
428         0, kHorizontalPadding, 0, kHorizontalPadding));
429     fullscreen_button_->set_background(
430         new MenuButtonBackground(MenuButtonBackground::SINGLE_BUTTON));
431     fullscreen_button_->SetAccessibleName(
432         GetAccessibleNameForWrenchMenuItem(
433             menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN));
434     AddChildView(fullscreen_button_);
435 
436     UpdateZoomControls();
437 
438     registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED,
439                    Source<Profile>(menu->browser_->profile()));
440   }
441 
GetPreferredSize()442   gfx::Size GetPreferredSize() {
443     // The increment/decrement button are forced to the same width.
444     int button_width = std::max(increment_button_->GetPreferredSize().width(),
445                                 decrement_button_->GetPreferredSize().width());
446     int fullscreen_width = fullscreen_button_->GetPreferredSize().width();
447     // Returned height doesn't matter as MenuItemView forces everything to the
448     // height of the menuitemview.
449     return gfx::Size(button_width + zoom_label_width_ + button_width +
450                      kZoomPadding + fullscreen_width, 0);
451   }
452 
Layout()453   void Layout() {
454     int x = 0;
455     int button_width = std::max(increment_button_->GetPreferredSize().width(),
456                                 decrement_button_->GetPreferredSize().width());
457     gfx::Rect bounds(0, 0, button_width, height());
458 
459     decrement_button_->SetBoundsRect(bounds);
460 
461     x += bounds.width();
462     bounds.set_x(x);
463     bounds.set_width(zoom_label_width_);
464     zoom_label_->SetBoundsRect(bounds);
465 
466     x += bounds.width();
467     bounds.set_x(x);
468     bounds.set_width(button_width);
469     increment_button_->SetBoundsRect(bounds);
470 
471     x += bounds.width() + kZoomPadding;
472     bounds.set_x(x);
473     bounds.set_width(fullscreen_button_->GetPreferredSize().width());
474     fullscreen_button_->SetBoundsRect(bounds);
475   }
476 
477   // ButtonListener:
ButtonPressed(views::Button * sender,const views::Event & event)478   virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
479     if (sender->tag() == fullscreen_index_) {
480       menu_->CancelAndEvaluate(menu_model_, sender->tag());
481     } else {
482       // Zoom buttons don't close the menu.
483       menu_model_->ActivatedAt(sender->tag());
484     }
485   }
486 
487   // NotificationObserver:
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)488   virtual void Observe(NotificationType type,
489                        const NotificationSource& source,
490                        const NotificationDetails& details) {
491     DCHECK_EQ(NotificationType::ZOOM_LEVEL_CHANGED, type.value);
492     UpdateZoomControls();
493   }
494 
495  private:
UpdateZoomControls()496   void UpdateZoomControls() {
497     bool enable_increment = false;
498     bool enable_decrement = false;
499     TabContents* selected_tab = menu_->browser_->GetSelectedTabContents();
500     int zoom = 100;
501     if (selected_tab)
502       zoom = selected_tab->GetZoomPercent(&enable_increment, &enable_decrement);
503     increment_button_->SetEnabled(enable_increment);
504     decrement_button_->SetEnabled(enable_decrement);
505     zoom_label_->SetText(UTF16ToWide(l10n_util::GetStringFUTF16Int(
506                                      IDS_ZOOM_PERCENT,
507                                      zoom)));
508 
509     zoom_label_width_ = MaxWidthForZoomLabel();
510   }
511 
512   // Calculates the max width the zoom string can be.
MaxWidthForZoomLabel()513   int MaxWidthForZoomLabel() {
514     gfx::Font font = zoom_label_->font();
515     gfx::Insets insets;
516     if (zoom_label_->border())
517       zoom_label_->border()->GetInsets(&insets);
518 
519     int max_w = 0;
520 
521     TabContents* selected_tab = menu_->browser_->GetSelectedTabContents();
522     if (selected_tab) {
523       int min_percent = selected_tab->minimum_zoom_percent();
524       int max_percent = selected_tab->maximum_zoom_percent();
525 
526       int step = (max_percent - min_percent) / 10;
527       for (int i = min_percent; i <= max_percent; i += step) {
528         int w = font.GetStringWidth(
529             l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, i));
530         max_w = std::max(w, max_w);
531       }
532     } else {
533       max_w = font.GetStringWidth(
534           l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100));
535     }
536 
537     return max_w + insets.width();
538   }
539 
540   // Index of the fullscreen menu item in the model.
541   const int fullscreen_index_;
542 
543   NotificationRegistrar registrar_;
544 
545   // Button for incrementing the zoom.
546   TextButton* increment_button_;
547 
548   // Label showing zoom as a percent.
549   Label* zoom_label_;
550 
551   // Button for decrementing the zoom.
552   TextButton* decrement_button_;
553 
554   ImageButton* fullscreen_button_;
555 
556   // Width given to |zoom_label_|. This is the width at 100%.
557   int zoom_label_width_;
558 
559   DISALLOW_COPY_AND_ASSIGN(ZoomView);
560 };
561 
562 // WrenchMenu ------------------------------------------------------------------
563 
WrenchMenu(Browser * browser)564 WrenchMenu::WrenchMenu(Browser* browser)
565     : browser_(browser),
566       selected_menu_model_(NULL),
567       selected_index_(0) {
568 }
569 
Init(ui::MenuModel * model)570 void WrenchMenu::Init(ui::MenuModel* model) {
571   DCHECK(!root_.get());
572   root_.reset(new MenuItemView(this));
573   root_->set_has_icons(true);  // We have checks, radios and icons, set this
574                                // so we get the taller menu style.
575   int next_id = 1;
576   PopulateMenu(root_.get(), model, &next_id);
577 }
578 
RunMenu(views::MenuButton * host)579 void WrenchMenu::RunMenu(views::MenuButton* host) {
580   // Up the ref count while the menu is displaying. This way if the window is
581   // deleted while we're running we won't prematurely delete the menu.
582   // TODO(sky): fix this, the menu should really take ownership of the menu
583   // (57890).
584   scoped_refptr<WrenchMenu> dont_delete_while_running(this);
585   gfx::Point screen_loc;
586   views::View::ConvertPointToScreen(host, &screen_loc);
587   gfx::Rect bounds(screen_loc, host->size());
588   UserMetrics::RecordAction(UserMetricsAction("ShowAppMenu"));
589   root_->RunMenuAt(host->GetWindow()->GetNativeWindow(), host, bounds,
590       base::i18n::IsRTL() ? MenuItemView::TOPLEFT : MenuItemView::TOPRIGHT,
591       true);
592   if (selected_menu_model_)
593     selected_menu_model_->ActivatedAt(selected_index_);
594 }
595 
IsItemChecked(int id) const596 bool WrenchMenu::IsItemChecked(int id) const {
597   const Entry& entry = id_to_entry_.find(id)->second;
598   return entry.first->IsItemCheckedAt(entry.second);
599 }
600 
IsCommandEnabled(int id) const601 bool WrenchMenu::IsCommandEnabled(int id) const {
602   if (id == 0)
603     return false;  // The root item.
604 
605   const Entry& entry = id_to_entry_.find(id)->second;
606   int command_id = entry.first->GetCommandIdAt(entry.second);
607   // The items representing the cut (cut/copy/paste) and zoom menu
608   // (increment/decrement/reset) are always enabled. The child views of these
609   // items enabled state updates appropriately.
610   return command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS ||
611       entry.first->IsEnabledAt(entry.second);
612 }
613 
ExecuteCommand(int id)614 void WrenchMenu::ExecuteCommand(int id) {
615   const Entry& entry = id_to_entry_.find(id)->second;
616   int command_id = entry.first->GetCommandIdAt(entry.second);
617 
618   if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) {
619     // These items are represented by child views. If ExecuteCommand is invoked
620     // it means the user clicked on the area around the buttons and we should
621     // not do anyting.
622     return;
623   }
624 
625   return entry.first->ActivatedAt(entry.second);
626 }
627 
GetAccelerator(int id,views::Accelerator * accelerator)628 bool WrenchMenu::GetAccelerator(int id, views::Accelerator* accelerator) {
629   const Entry& entry = id_to_entry_.find(id)->second;
630   int command_id = entry.first->GetCommandIdAt(entry.second);
631   if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) {
632     // These have special child views; don't show the accelerator for them.
633     return false;
634   }
635 
636   ui::Accelerator menu_accelerator;
637   if (!entry.first->GetAcceleratorAt(entry.second, &menu_accelerator))
638     return false;
639 
640   *accelerator = views::Accelerator(menu_accelerator.GetKeyCode(),
641                                     menu_accelerator.modifiers());
642   return true;
643 }
644 
~WrenchMenu()645 WrenchMenu::~WrenchMenu() {
646 }
647 
PopulateMenu(MenuItemView * parent,MenuModel * model,int * next_id)648 void WrenchMenu::PopulateMenu(MenuItemView* parent,
649                               MenuModel* model,
650                               int* next_id) {
651   int index_offset = model->GetFirstItemIndex(NULL);
652   for (int i = 0, max = model->GetItemCount(); i < max; ++i) {
653     int index = i + index_offset;
654 
655     MenuItemView* item =
656         AppendMenuItem(parent, model, index, model->GetTypeAt(index), next_id);
657 
658     if (model->GetTypeAt(index) == MenuModel::TYPE_SUBMENU)
659       PopulateMenu(item, model->GetSubmenuModelAt(index), next_id);
660 
661     if (model->GetCommandIdAt(index) == IDC_CUT) {
662       DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(index));
663       DCHECK_LT(i + 2, max);
664       DCHECK_EQ(IDC_COPY, model->GetCommandIdAt(index + 1));
665       DCHECK_EQ(IDC_PASTE, model->GetCommandIdAt(index + 2));
666       item->SetTitle(UTF16ToWide(l10n_util::GetStringUTF16(IDS_EDIT2)));
667       item->AddChildView(
668           new CutCopyPasteView(this, model, index, index + 1, index + 2));
669       i += 2;
670     } else if (model->GetCommandIdAt(index) == IDC_ZOOM_MINUS) {
671       DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(index));
672       DCHECK_EQ(IDC_ZOOM_PLUS, model->GetCommandIdAt(index + 1));
673       DCHECK_EQ(IDC_FULLSCREEN, model->GetCommandIdAt(index + 2));
674       item->SetTitle(UTF16ToWide(l10n_util::GetStringUTF16(IDS_ZOOM_MENU2)));
675       item->AddChildView(
676           new ZoomView(this, model, index, index + 1, index + 2));
677       i += 2;
678     }
679   }
680 }
681 
AppendMenuItem(MenuItemView * parent,MenuModel * model,int index,MenuModel::ItemType menu_type,int * next_id)682 MenuItemView* WrenchMenu::AppendMenuItem(MenuItemView* parent,
683                                          MenuModel* model,
684                                          int index,
685                                          MenuModel::ItemType menu_type,
686                                          int* next_id) {
687   int id = (*next_id)++;
688 
689   id_to_entry_[id].first = model;
690   id_to_entry_[id].second = index;
691 
692   MenuItemView* menu_item = parent->AppendMenuItemFromModel(model, index, id);
693 
694   if (menu_item)
695     menu_item->SetVisible(model->IsVisibleAt(index));
696 
697   if (menu_type == MenuModel::TYPE_COMMAND && model->HasIcons()) {
698     SkBitmap icon;
699     if (model->GetIconAt(index, &icon))
700       menu_item->SetIcon(icon);
701   }
702 
703   return menu_item;
704 }
705 
CancelAndEvaluate(MenuModel * model,int index)706 void WrenchMenu::CancelAndEvaluate(MenuModel* model, int index) {
707   selected_menu_model_ = model;
708   selected_index_ = index;
709   root_->Cancel();
710 }
711