OLD | NEW |
---|---|
(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 "chrome/browser/ui/views/chrome_to_mobile_bubble_view.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/file_util.h" | |
9 #include "base/string16.h" | |
10 #include "base/utf_string_conversions.h" | |
11 #include "base/values.h" | |
12 #include "chrome/app/chrome_command_ids.h" | |
13 #include "chrome/browser/browser_process.h" | |
14 #include "chrome/browser/chrome_to_mobile_service.h" | |
15 #include "chrome/browser/chrome_to_mobile_service_factory.h" | |
16 #include "chrome/browser/profiles/profile.h" | |
17 #include "chrome/browser/ui/browser.h" | |
18 #include "chrome/browser/ui/browser_list.h" | |
19 #include "chrome/browser/ui/views/window.h" | |
20 #include "content/public/browser/browser_thread.h" | |
21 #include "content/public/browser/web_contents.h" | |
22 #include "grit/generated_resources.h" | |
23 #include "grit/theme_resources.h" | |
24 #include "ui/base/animation/throb_animation.h" | |
25 #include "ui/base/keycodes/keyboard_codes.h" | |
26 #include "ui/base/l10n/l10n_util.h" | |
27 #include "ui/base/resource/resource_bundle.h" | |
28 #include "ui/base/text/bytes_formatting.h" | |
29 #include "ui/views/controls/button/checkbox.h" | |
30 #include "ui/views/controls/button/radio_button.h" | |
31 #include "ui/views/controls/button/text_button.h" | |
32 #include "ui/views/controls/label.h" | |
33 #include "ui/views/events/event.h" | |
34 #include "ui/views/layout/grid_layout.h" | |
35 #include "ui/views/layout/layout_constants.h" | |
36 | |
37 using views::GridLayout; | |
38 | |
39 namespace { | |
40 | |
41 // The millisecond interval between refreshes of the "Sending..." progress text. | |
42 const size_t kProgressThrobIntervalMS = 300; | |
43 | |
44 // The bubble's margin for the "Sending..." and "Sent" states. | |
45 const size_t kProgressMargin = 20; | |
46 | |
47 // The snapshot path constant; combined with a guid to store the MHTML snapshot. | |
48 const FilePath::CharType kSnapshotPath[] = | |
49 FILE_PATH_LITERAL("chrome_to_mobile_snapshot_.mhtml"); | |
50 | |
51 void DeleteFilePath(const FilePath& file_path) { | |
52 bool success = file_util::Delete(file_path, false); | |
53 DCHECK(success); | |
54 } | |
55 | |
56 } // namespace | |
57 | |
58 // Declared in browser_dialogs.h so callers don't have to depend on our header. | |
59 | |
60 namespace browser { | |
61 | |
62 void ShowChromeToMobileBubbleView(views::View* anchor_view, Profile* profile) { | |
63 ChromeToMobileBubbleView::ShowBubble(anchor_view, profile); | |
64 } | |
65 | |
66 void HideChromeToMobileBubbleView() { | |
67 ChromeToMobileBubbleView::Hide(); | |
68 } | |
69 | |
70 bool IsChromeToMobileBubbleViewShowing() { | |
71 return ChromeToMobileBubbleView::IsShowing(); | |
72 } | |
73 | |
74 } // namespace browser | |
75 | |
76 // ChromeToMobileBubbleView ---------------------------------------------------- | |
77 | |
78 ChromeToMobileBubbleView* ChromeToMobileBubbleView::bubble_ = NULL; | |
79 | |
80 // static | |
81 void ChromeToMobileBubbleView::ShowBubble(views::View* anchor_view, | |
82 Profile* profile) { | |
83 if (IsShowing()) | |
84 return; | |
85 | |
86 bubble_ = new ChromeToMobileBubbleView(anchor_view, profile); | |
87 browser::CreateViewsBubble(bubble_); | |
88 bubble_->Show(); | |
89 } | |
90 | |
91 // static | |
92 bool ChromeToMobileBubbleView::IsShowing() { | |
93 return bubble_ != NULL; | |
94 } | |
95 | |
96 void ChromeToMobileBubbleView::Hide() { | |
97 if (IsShowing()) | |
98 bubble_->GetWidget()->Close(); | |
99 } | |
100 | |
101 ChromeToMobileBubbleView::~ChromeToMobileBubbleView() {} | |
102 | |
103 views::View* ChromeToMobileBubbleView::GetInitiallyFocusedView() { | |
104 return send_; | |
105 } | |
106 | |
107 gfx::Rect ChromeToMobileBubbleView::GetAnchorRect() { | |
108 // Compensate for some built-in padding in the arrow image. | |
109 gfx::Rect rect(BubbleDelegateView::GetAnchorRect()); | |
110 rect.Inset(0, anchor_view() ? 5 : 0); | |
111 return rect; | |
112 } | |
113 | |
114 void ChromeToMobileBubbleView::WindowClosing() { | |
115 // We have to reset |bubble_| here, not in our destructor, because we'll be | |
116 // destroyed asynchronously and the shown state will be checked before then. | |
117 DCHECK(bubble_ == this); | |
118 bubble_ = NULL; | |
119 | |
120 if (!snapshot_path_.empty()) | |
121 content::BrowserThread::PostBlockingPoolTask(FROM_HERE, | |
sky
2012/03/11 21:41:22
I'm not familiar with this, does it black the UI t
msw
2012/03/12 10:17:18
Nope, See the decl; this is the new FILE thread pa
| |
122 base::Bind(&DeleteFilePath, snapshot_path_)); | |
123 } | |
124 | |
125 bool ChromeToMobileBubbleView::AcceleratorPressed( | |
126 const ui::Accelerator& accelerator) { | |
127 if (accelerator.key_code() == ui::VKEY_RETURN && | |
128 (send_->HasFocus() || cancel_->HasFocus())) { | |
129 HandleButtonPressed(send_->HasFocus() ? send_ : cancel_); | |
130 return true; | |
131 } | |
132 return BubbleDelegateView::AcceleratorPressed(accelerator); | |
133 } | |
134 | |
135 void ChromeToMobileBubbleView::OnSendComplete(bool success) { | |
136 progress_animation_->Stop(); | |
137 progress_label_->SetText(l10n_util::GetStringUTF16(success ? | |
138 IDS_CHROME_TO_MOBILE_BUBBLE_SENT : IDS_CHROME_TO_MOBILE_BUBBLE_ERROR)); | |
139 Layout(); | |
140 } | |
141 | |
142 void ChromeToMobileBubbleView::AnimationProgressed( | |
143 const ui::Animation* animation) { | |
144 if (animation == progress_animation_.get()) { | |
145 // Show the "Sending" string with 0-3 trailing periods "..." | |
146 progress_periods_count_ = (progress_periods_count_ + 1) % 4; | |
147 string16 text = progress_label_->GetText(); | |
148 if (progress_periods_count_ == 0) | |
149 progress_label_->SetText(text.substr(0, text.length() - 3)); | |
150 else | |
151 progress_label_->SetText(text.append(ASCIIToUTF16("."))); | |
sky
2012/03/11 21:41:22
I suspect that adding a '.' to the end of the stri
msw
2012/03/12 10:17:18
I'm now using multiple IDS messages for internatio
| |
152 // Run Layout now but do not resize the bubble when adding/removing periods. | |
153 Layout(); | |
154 return; | |
155 } | |
156 views::BubbleDelegateView::AnimationProgressed(animation); | |
157 } | |
158 | |
159 void ChromeToMobileBubbleView::SnapshotGenerated(const FilePath& snapshot_path, | |
160 int64 snapshot_bytes) { | |
161 snapshot_path_ = snapshot_path; | |
162 send_copy_->SetText( | |
163 l10n_util::GetStringFUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_SEND_COPY, | |
164 ui::FormatBytes(snapshot_bytes))); | |
165 send_copy_->SetEnabled(snapshot_bytes > 0); | |
166 Layout(); | |
167 } | |
168 | |
169 void ChromeToMobileBubbleView::Init() { | |
170 GridLayout* layout = new GridLayout(this); | |
171 SetLayoutManager(layout); | |
172 | |
173 const size_t single_column_set_id = 0; | |
174 views::ColumnSet* cs = layout->AddColumnSet(single_column_set_id); | |
175 cs->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, | |
176 GridLayout::USE_PREF, 0, 0); | |
177 cs->AddPaddingColumn(1, 0); | |
178 | |
179 const size_t button_column_set_id = 1; | |
180 cs = layout->AddColumnSet(button_column_set_id); | |
181 cs->AddPaddingColumn(1, 0); | |
182 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, | |
183 GridLayout::USE_PREF, 0, 0); | |
184 // Subtract 2px for the natural button padding and to correspond with row | |
185 // separation height; like BookmarkBubbleView. | |
186 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing - 2); | |
187 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, | |
188 GridLayout::USE_PREF, 0, 0); | |
189 | |
190 std::vector<DictionaryValue*> mobiles = | |
191 ChromeToMobileServiceFactory::GetForProfile(profile_)->mobiles(); | |
192 DCHECK_GT(mobiles.size(), 0U); | |
193 layout->StartRow(0, single_column_set_id); | |
194 if (mobiles.size() == 1) { | |
195 selected_mobile_ = mobiles[0]; | |
196 string16 mobile_name; | |
197 mobiles[0]->GetString("name", &mobile_name); | |
198 layout->AddView(new views::Label( | |
199 l10n_util::GetStringFUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_SINGLE_TITLE, | |
200 mobile_name))); | |
201 } else { | |
202 layout->AddView(new views::Label( | |
203 l10n_util::GetStringUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_MULTI_TITLE))); | |
204 | |
205 const size_t radio_column_set_id = 2; | |
206 cs = layout->AddColumnSet(radio_column_set_id); | |
207 cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); | |
208 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, | |
209 GridLayout::USE_PREF, 0, 0); | |
210 | |
211 views::RadioButton* radio; | |
212 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); | |
213 for (std::vector<DictionaryValue*>::const_iterator it = mobiles.begin(); | |
214 it != mobiles.end(); ++it) { | |
215 string16 name; | |
216 (*it)->GetString("name", &name); | |
217 radio = new views::RadioButton(name, 0); | |
218 radio->set_listener(this); | |
219 mobile_map_[radio] = *it; | |
220 layout->StartRow(0, radio_column_set_id); | |
221 layout->AddView(radio); | |
222 } | |
223 mobile_map_.begin()->first->SetChecked(true); | |
224 selected_mobile_ = mobile_map_.begin()->second; | |
225 } | |
226 | |
227 send_copy_ = new views::Checkbox( | |
228 l10n_util::GetStringFUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_SEND_COPY, | |
229 l10n_util::GetStringUTF16( | |
230 IDS_CHROME_TO_MOBILE_BUBBLE_SEND_COPY_GENERATING))); | |
231 send_copy_->SetEnabled(false); | |
232 layout->StartRow(0, single_column_set_id); | |
233 layout->AddView(send_copy_); | |
234 | |
235 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); | |
236 send_ = new views::NativeTextButton( | |
237 this, l10n_util::GetStringUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_SEND)); | |
238 send_->SetIsDefault(true); | |
239 cancel_ = new views::NativeTextButton( | |
240 this, l10n_util::GetStringUTF16(IDS_CANCEL)); | |
241 layout->StartRow(0, button_column_set_id); | |
242 layout->AddView(send_); | |
243 layout->AddView(cancel_); | |
244 | |
245 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, 0)); | |
246 } | |
247 | |
248 ChromeToMobileBubbleView::ChromeToMobileBubbleView(views::View* anchor_view, | |
249 Profile* profile) | |
250 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), | |
251 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), | |
252 profile_(profile), | |
253 snapshot_path_(kSnapshotPath), | |
254 send_copy_(NULL), | |
255 send_(NULL), | |
256 cancel_(NULL), | |
257 progress_label_(NULL), | |
258 progress_periods_count_(0) { | |
259 // Generate the MHTML snapshot immediately to report its size in the bubble. | |
260 content::WebContents* web_contents = | |
261 BrowserList::GetLastActiveWithProfile(profile_)->GetSelectedWebContents(); | |
262 web_contents->GenerateMHTML( | |
263 FilePath(kSnapshotPath).InsertBeforeExtensionASCII(guid::GenerateGUID()), | |
264 base::Bind(&ChromeToMobileBubbleView::SnapshotGenerated, | |
265 weak_ptr_factory_.GetWeakPtr())); | |
266 } | |
267 | |
268 void ChromeToMobileBubbleView::ButtonPressed(views::Button* sender, | |
269 const views::Event& event) { | |
270 HandleButtonPressed(sender); | |
271 } | |
272 | |
273 void ChromeToMobileBubbleView::HandleButtonPressed(views::Button* sender) { | |
274 if (sender == send_) { | |
275 Send(); | |
276 } else if (sender == cancel_) { | |
277 GetWidget()->Close(); | |
278 } else { | |
279 // The sender is a mobile radio button | |
280 views::RadioButton* radio = static_cast<views::RadioButton*>(sender); | |
281 DCHECK(mobile_map_.find(radio) != mobile_map_.end()); | |
282 selected_mobile_ = mobile_map_.find(radio)->second; | |
283 } | |
284 } | |
285 | |
286 void ChromeToMobileBubbleView::Send() { | |
287 string16 mobile_id; | |
288 selected_mobile_->GetString("id", &mobile_id); | |
289 content::WebContents* web_contents = | |
290 BrowserList::GetLastActiveWithProfile(profile_)->GetSelectedWebContents(); | |
291 ChromeToMobileServiceFactory::GetForProfile(profile_)->SendToMobile( | |
292 mobile_id, web_contents->GetURL(), web_contents->GetTitle(), | |
293 send_copy_->checked() ? snapshot_path_ : FilePath(), | |
294 weak_ptr_factory_.GetWeakPtr()); | |
295 // Pass ownership of the temp MHTML file to the service, if sending a copy. | |
296 if (send_copy_->checked()) | |
297 snapshot_path_.clear(); | |
298 | |
299 // Re-initialize the view's contents to show progress sending the page. | |
300 RemoveAllChildViews(true); | |
sky
2012/03/11 21:41:22
Reset the fields that point to views this deletes.
msw
2012/03/12 10:17:18
Done.
| |
301 GridLayout* layout = new GridLayout(this); | |
302 SetLayoutManager(layout); | |
303 | |
304 const size_t single_column_set_id = 0; | |
305 views::ColumnSet* cs = layout->AddColumnSet(single_column_set_id); | |
306 cs->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, | |
307 GridLayout::USE_PREF, 0, 0); | |
308 set_margin(kProgressMargin); | |
309 | |
310 // Start the string off with its three periods to properly size the bubble. | |
311 layout->StartRow(0, single_column_set_id); | |
312 progress_periods_count_ = 3; | |
313 progress_label_ = new views::Label( | |
314 l10n_util::GetStringUTF16(IDS_CHROME_TO_MOBILE_BUBBLE_SENDING).append( | |
315 ASCIIToUTF16("..."))); | |
316 layout->AddView(progress_label_); | |
317 SizeToContents(); | |
318 | |
319 progress_animation_.reset(new ui::ThrobAnimation(this)); | |
320 progress_animation_->set_timer_interval( | |
sky
2012/03/11 21:41:22
I think you want SetDuration and don't add set_tim
msw
2012/03/12 10:17:18
Done. Would a RepeatingTimer be better than an Ani
sky
2012/03/12 15:28:12
Either one works, but we typically use animation f
| |
321 base::TimeDelta::FromMilliseconds(kProgressThrobIntervalMS)); | |
322 progress_animation_->StartThrobbing(-1); | |
323 } | |
OLD | NEW |