blob: de9532217468762602f93639d1c5488482a2e6ec [file] [log] [blame]
initial.commit09911bf2008-07-26 23:55:291// Copyright 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include "chrome/browser/history_view.h"
31
32#include "base/string_util.h"
33#include "base/word_iterator.h"
34#include "chrome/browser/browsing_data_remover.h"
35#include "chrome/browser/drag_utils.h"
36#include "chrome/browser/native_ui_contents.h"
37#include "chrome/browser/page_navigator.h"
38#include "chrome/browser/user_metrics.h"
39#include "chrome/browser/views/bookmark_bubble_view.h"
40#include "chrome/browser/views/event_utils.h"
41#include "chrome/browser/views/star_toggle.h"
42#include "chrome/common/gfx/chrome_canvas.h"
43#include "chrome/common/gfx/favicon_size.h"
44#include "chrome/common/resource_bundle.h"
45#include "chrome/common/time_format.h"
46#include "chrome/common/win_util.h"
47#include "chrome/views/link.h"
48
49#include "generated_resources.h"
50
51// The extra-wide space between groups of entries for each new day.
52static const int kDayHeadingHeight = 50;
53
54// The space between groups of entries within a day.
55static const int kSessionBreakHeight = 24;
56
57// Amount of time between page-views that triggers a break (in microseconds).
58static const int64 kSessionBreakTime = 1800 * 1000000; // 30 minutes
59
60// Horizontal space between the left edge of the entries and the
61// left edge of the view.
62static const int kLeftMargin = 38;
63
64// x-position of the page title (massage this so it visually matches
65// kDestinationSearchOffset in native_ui_contents.cc
66static const int kPageTitleOffset = 102;
67
68// x-position of the Time
69static const int kTimeOffset = 24;
70
71// Vertical offset for the delete control (distance from the top of a day
72// break segment).
73static const int kDeleteControlOffset = 30;
74
75// x-position of the session gap filler (currently a thin vertical line
76// joining the times on either side of a session gap).
77static const int kSessionGapOffset = 16;
78
79// Horizontal space between the right edge of the item
80// and the right edge of the view.
81static const int kRightMargin = 20;
82
83// The ideal height of an entry. This may change depending on font line-height.
84static const int kSearchResultsHeight = 72;
85static const int kBrowseResultsHeight = 24;
86
87// How much room to leave above the first result.
88static const int kResultsMargin = 24;
89
90// Height of the results text area.
91static const int kResultTextHeight = 24;
92
93// Height of the area when there are no results to display.
94static const int kNoResultTextHeight = 48;
95static const int kNoResultMinWidth = 512;
96
97// Extra vertical space between the different lines of text.
98// (Note that the height() variables are baseline-to-baseline already.)
99static const int kLeading = 2;
100
101// The amount of space from the edges of an entry to the edges of its contents.
102static const int kEntryPadding = 8;
103
104// Padding between the icons (star, favicon) and other elements.
105static const int kIconPadding = 4;
106
107// SnippetRenderer is a View that can displayed text with bolding and wrapping.
108// It's used to display search result snippets.
109class SnippetRenderer : public ChromeViews::View {
110 public:
111 SnippetRenderer();
112
113 // Set the text snippet.
114 void SetSnippet(const Snippet& snippet);
115
116 int GetLineHeight();
117
118 virtual void Paint(ChromeCanvas* canvas);
119
120 private:
121 // The snippet that we're drawing.
122 Snippet snippet_;
123
124 // Font for plain text.
125 ChromeFont text_font_;
126 // Font for match text. (TODO(evanm): use red for Chinese (bug 844518).)
127 ChromeFont match_font_;
128
129 // Layout/draw a substring of the snippet from [start,end) at (x, y).
130 // ProcessRun is strictly for text in a single line: it doesn't do any
131 // word-wrapping, and is used as a helper for laying out multiple lines
132 // of output in Pain().
133 // match_iter is an iterator in match_runs_ that covers a region
134 // before or at start.
135 // When canvas is NULL, does no drawing and only computes the size.
136 // Returns the pixel width of the run.
137 // TODO(evanm): this could be optimizing by only measuring the text once
138 // and returning the layout, but it's worth profiling first.
139 int ProcessRun(ChromeCanvas* canvas,
140 int x,
141 int y,
142 Snippet::MatchPositions::const_iterator match_iter,
143 int start,
144 int end);
145
146 DISALLOW_EVIL_CONSTRUCTORS(SnippetRenderer);
147};
148
149SnippetRenderer::SnippetRenderer() {
150 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
151
152 text_font_ = resource_bundle.GetFont(ResourceBundle::WebFont);
153 match_font_ = text_font_.DeriveFont(0, ChromeFont::BOLD);
154}
155
156void SnippetRenderer::SetSnippet(const Snippet& snippet) {
157 snippet_ = snippet;
158}
159
160int SnippetRenderer::GetLineHeight() {
161 return std::max(text_font_.height(), match_font_.height()) + kLeading;
162}
163
164void SnippetRenderer::Paint(ChromeCanvas* canvas) {
165 const int line_height = GetLineHeight();
166
167 WordIterator iter(snippet_.text(), WordIterator::BREAK_LINE);
168 if (!iter.Init())
169 return;
170 Snippet::MatchPositions::const_iterator match_iter =
171 snippet_.matches().begin();
172
173 int x = 0;
174 int y = 0;
175 while (iter.Advance()) {
176 // Advance match_iter to a run that potentially covers this region.
177 while (match_iter != snippet_.matches().end() &&
178 match_iter->second <= iter.prev()) {
179 ++match_iter;
180 }
181
182 // The region from iter.prev() to iter.pos() should be on one line.
183 // It can be a mixture of bold and non-bold, so first lay it out to
184 // compute its width.
185 const int width = ProcessRun(NULL, 0, 0,
186 match_iter, iter.prev(), iter.pos());
187 // Advance to the next line if necessary.
188 if (x + width > GetWidth()) {
189 x = 0;
190 y += line_height;
191 if (y >= GetHeight())
192 return; // Out of vertical space.
193 }
194 ProcessRun(canvas, x, y, match_iter, iter.prev(), iter.pos());
195 x += width;
196 }
197}
198
199int SnippetRenderer::ProcessRun(
200 ChromeCanvas* canvas, int x, int y,
201 Snippet::MatchPositions::const_iterator match_iter,
202 int start, int end) {
203 int total_width = 0;
204
205 while (start < end) {
206 // Advance match_iter to the next match that can cover the current
207 // position.
208 while (match_iter != snippet_.matches().end() &&
209 match_iter->second <= start) {
210 ++match_iter;
211 }
212
213 // Determine the next substring to process by examining whether
214 // we're before a match or within a match.
215 ChromeFont* font = &text_font_;
216 int next = end;
217 if (match_iter != snippet_.matches().end()) {
218 if (match_iter->first > start) {
219 // We're in a plain region.
220 next = std::min(match_iter->first, end);
221 } else if (match_iter->first <= start &&
222 match_iter->second > start) {
223 // We're in a match region.
224 font = &match_font_;
225 next = std::min(match_iter->second, end);
226 }
227 }
228
229 // Draw/layout the text.
230 const std::wstring run = snippet_.text().substr(start, next - start);
231 const int width = font->GetStringWidth(run);
232 if (canvas) {
233 canvas->DrawStringInt(run, *font, SkColorSetRGB(0, 0, 0),
234 x + total_width, y,
235 width, GetHeight(),
236 ChromeCanvas::TEXT_VALIGN_BOTTOM);
237 }
238
239 // Advance.
240 total_width += width;
241 start = next;
242 }
243
244 return total_width;
245}
246
247// A View for an individual history result.
248class HistoryItemRenderer : public ChromeViews::View,
249 public ChromeViews::LinkController,
250 public StarToggle::Delegate {
251 public:
252 HistoryItemRenderer(HistoryView* parent, bool show_full);
253 ~HistoryItemRenderer();
254
255 // Set the BaseHistoryModel that this renderer displays.
256 // model_index is the index of this entry, and is passed to all of the
257 // model functions.
258 void SetModel(BaseHistoryModel* model, int model_index);
259
260 // Set whether we should display full size or partial-sized items.
261 void SetDisplayStyle(bool show_full);
262
263 // Layout the contents of this view.
264 void Layout();
265
266 protected:
267 // Overridden to do a drag if over the favicon or thumbnail.
268 virtual int GetDragOperations(int press_x, int press_y);
269 virtual void WriteDragData(int press_x, int press_y, OSExchangeData* data);
270
271 private:
272 // Regions drags may originate from.
273 enum DragRegion {
274 FAV_ICON,
275 THUMBNAIL,
276 NONE
277 };
278
279 // The thickness of the border drawn around thumbnails.
280 static const int kThumbnailBorderWidth = 1;
281
282 // The height of the thumbnail images.
283 static const int kThumbnailHeight = kSearchResultsHeight - kEntryPadding * 2;
284
285 // The width of the thumbnail images.
286 static const int kThumbnailWidth = static_cast<int>(1.44 * kThumbnailHeight);
287
288 // The maximum width of a snippet - we want to constrain this to make
289 // snippets easier to read (like Google search results).
290 static const int kMaxSnippetWidth = 500;
291
292 // Returns the bounds of the thumbnail.
293 void GetThumbnailBounds(CRect* rect);
294
295 // Convert a GURL into a displayable string.
296 std::wstring DisplayURL(const GURL& url);
297
298 virtual void Paint(ChromeCanvas* canvas);
299
300 // Notification that the star was changed.
301 virtual void StarStateChanged(bool state);
302
303 // Notification that the link was clicked.
304 virtual void LinkActivated(ChromeViews::Link* source, int event_flags);
305
306 // Returns the region the mouse is over.
307 DragRegion GetDragRegion(int x, int y);
308
309 // The HistoryView containing this view.
310 HistoryView* parent_;
311
312 // Whether we're showing a fullsize item, or a single-line item.
313 bool show_full_;
314
315 // The model and index of this entry within the model.
316 BaseHistoryModel* model_;
317 int model_index_;
318
319 // Widgets.
320 StarToggle* star_toggle_;
321 ChromeViews::Link* title_link_;
322 ChromeViews::Label* time_label_;
323 SnippetRenderer* snippet_label_;
324
325 DISALLOW_EVIL_CONSTRUCTORS(HistoryItemRenderer);
326};
327
328HistoryItemRenderer::HistoryItemRenderer(HistoryView* parent,
329 bool show_full)
330 : parent_(parent),
331 show_full_(show_full) {
332 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
333
334 ChromeFont text_font(resource_bundle.GetFont(ResourceBundle::WebFont));
335
336 star_toggle_ = new StarToggle(this);
337 star_toggle_->set_change_state_immediately(false);
338 AddChildView(star_toggle_);
339
340 title_link_ = new ChromeViews::Link();
341 title_link_->SetFont(text_font);
342 title_link_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT);
343 title_link_->SetController(this);
344 AddChildView(title_link_);
345
346 const SkColor kTimeColor = SkColorSetRGB(136, 136, 136); // Gray.
347
348 time_label_ = new ChromeViews::Label();
349 ChromeFont time_font(text_font);
350 time_label_->SetFont(time_font);
351 time_label_->SetColor(kTimeColor);
352 time_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT);
353 AddChildView(time_label_);
354
355 snippet_label_ = new SnippetRenderer();
356 AddChildView(snippet_label_);
357}
358
359HistoryItemRenderer::~HistoryItemRenderer() {
360}
361
362void HistoryItemRenderer::GetThumbnailBounds(CRect* rect) {
363 DCHECK(rect);
364 rect->right = GetWidth() - kEntryPadding;
365 rect->left = rect->right - kThumbnailWidth;
366 rect->top = kEntryPadding;
367 rect->bottom = rect->top + kThumbnailHeight;
368}
369
370std::wstring HistoryItemRenderer::DisplayURL(const GURL& url) {
371 std::string url_str = url.spec();
372 // Hide the "http://" prefix like web search does.
373 if (url_str.find("http://") == 0)
374 url_str.erase(0, strlen("http://"));
375 return UTF8ToWide(url_str);
376}
377
378void HistoryItemRenderer::Paint(ChromeCanvas* canvas) {
379 ChromeViews::View::Paint(canvas);
380
381 // Draw thumbnail or placeholder.
382 if (show_full_) {
383 SkBitmap* thumbnail = model_->GetThumbnail(model_index_);
384 CRect thumbnail_rect;
385 GetThumbnailBounds(&thumbnail_rect); // Includes border
386
387 // If the UI layout is right-to-left, we must mirror the bounds so that we
388 // render the bitmap in the correct position.
389 gfx::Rect mirrored_rect(thumbnail_rect);
390 thumbnail_rect.MoveToX(MirroredLeftPointForRect(mirrored_rect));
391
392 if (thumbnail) {
393 // This will create a MipMap for the bitmap if one doesn't exist already
394 // (it's a NOP if a MipMap already exists). This will give much smoother
395 // results for the scaled-down thumbnails.
396 thumbnail->buildMipMap(false);
397
398 canvas->DrawBitmapInt(
399 *thumbnail,
400 0, 0, thumbnail->width(), thumbnail->height(),
401 thumbnail_rect.left, thumbnail_rect.top,
402 thumbnail_rect.Width(), thumbnail_rect.Height(),
403 true);
404 } else {
405 canvas->FillRectInt(SK_ColorWHITE,
406 thumbnail_rect.left, thumbnail_rect.top,
407 thumbnail_rect.Width(), thumbnail_rect.Height());
408 }
409 canvas->DrawRectInt(SkColorSetRGB(153, 153, 191),
410 thumbnail_rect.left, thumbnail_rect.top,
411 thumbnail_rect.Width(), thumbnail_rect.Height());
412 }
413
414 // Draw the favicon.
415 SkBitmap* favicon = model_->GetFavicon(model_index_);
416 if (favicon) {
417 // WARNING: if you change these values, update the code that determines
418 // whether we should allow a drag (GetDragRegion).
419 CRect title_bounds;
420 title_link_->GetBounds(&title_bounds);
421
422 // We need to tweak the favicon position if the UI layout is RTL.
423 gfx::Rect favicon_bounds;
424 favicon_bounds.set_x(
425 title_bounds.left - kIconPadding - kFavIconSize);
426 favicon_bounds.set_y(kEntryPadding);
427 favicon_bounds.set_width(favicon->width());
428 favicon_bounds.set_height(favicon->height());
429 favicon_bounds.set_x(MirroredLeftPointForRect(favicon_bounds));
430
431 // Drawing the bitmap using the possibly adjusted bounds.
432 canvas->DrawBitmapInt(*favicon, favicon_bounds.x(), favicon_bounds.y());
433 }
434
435 // The remainder of painting is handled by drawing our children, which
436 // is managed by the View class for us.
437}
438
439void HistoryItemRenderer::Layout() {
440 // Figure out the maximum x-position of any text.
441 CRect thumbnail_rect;
442 int max_x;
443 if (show_full_) {
444 GetThumbnailBounds(&thumbnail_rect);
445 max_x = thumbnail_rect.left - kEntryPadding;
446 } else {
447 max_x = GetWidth() - kEntryPadding;
448 }
449
450 // Calculate the ideal positions of some items. If possible, we
451 // want the title to line up with kPageTitleOffset (and we would lay
452 // out the star and the favicon to the left of that), but in cases
453 // where font or language choices cause the time label to be
454 // horizontally large, we need to push everything to the right.
455 //
456 // If you fiddle with the calculations below, you may need to adjust
457 // the favicon painting in Paint() (and in GetDragRegion by extension).
458
459 // First we calculate the ideal position of the title.
460 int title_x = kPageTitleOffset;
461
462 // We calculate the size of the star.
463 CSize star_size;
464 star_toggle_->GetPreferredSize(&star_size);
465
466 // Measure and lay out the time label, and potentially move
467 // our title to suit.
468 CSize time_size;
469 Time visit_time = model_->GetVisitTime(model_index_);
470 int time_x = kTimeOffset;
471 if (visit_time.is_null()) {
472 // We will get null times if the page has never been visited, for example,
473 // bookmarks after you clear history.
474 time_label_->SetText(std::wstring());
475 } else if (show_full_) {
476 time_x = 0;
477 time_label_->SetText(TimeFormat::ShortDate(visit_time));
478 } else {
479 time_label_->SetText(TimeFormat::TimeOfDay(visit_time));
480 }
481 time_label_->GetPreferredSize(&time_size);
482
483 time_label_->SetBounds(time_x, kEntryPadding,
484 time_size.cx, time_size.cy);
485
486 // Calculate the position of the favicon.
487 int favicon_x = title_x - kFavIconSize - kIconPadding;
488
489 // Now we look to see if the favicon overlaps the time label,
490 // and if so, we push the title to the right. If we're not
491 // showing the time label, then ignore this step.
492 int overlap = favicon_x - (time_x + time_size.cx + kIconPadding);
493 if (overlap < 0) {
494 title_x -= overlap;
495 }
496
497 // Populate and measure the title label.
498 const std::wstring& title = model_->GetTitle(model_index_);
499 if (!title.empty())
500 title_link_->SetText(title);
501 else
502 title_link_->SetText(l10n_util::GetString(IDS_HISTORY_UNTITLED_TITLE));
503 CSize title_size;
504 title_link_->GetPreferredSize(&title_size);
505
506 // Lay out the title label.
507 int max_title_x;
508
509 max_title_x = std::max(0, max_x - title_x);
510
511 if (title_size.cx + kEntryPadding > max_title_x) {
512 // We need to shrink the title to make everything fit.
513 title_size.cx = max_title_x - kEntryPadding;
514 }
515 title_link_->SetBounds(title_x, kEntryPadding,
516 title_size.cx, title_size.cy);
517
518 // Lay out the star.
519 if (model_->IsStarred(model_index_)) {
520 star_toggle_->SetBounds(title_x + title_size.cx + kIconPadding,
521 kEntryPadding, star_size.cx, star_size.cy);
522 star_toggle_->SetState(true);
523 star_toggle_->SetVisible(true);
524 } else {
525 star_toggle_->SetVisible(false);
526 }
527
528 // Lay out the snippet label.
529 snippet_label_->SetVisible(show_full_);
530 if (show_full_) {
531 const Snippet& snippet = model_->GetSnippet(model_index_);
532 if (snippet.text().empty()) {
533 snippet_label_->SetSnippet(Snippet()); // Bug 843469 will fix this.
534 } else {
535 snippet_label_->SetSnippet(snippet);
536 }
537 snippet_label_->SetBounds(title_x,
538 kEntryPadding + snippet_label_->GetLineHeight(),
539 std::min(
540 static_cast<int>(thumbnail_rect.left -
541 title_x),
542 kMaxSnippetWidth) -
543 kEntryPadding * 2,
544 snippet_label_->GetLineHeight() * 2);
545 }
546}
547
548int HistoryItemRenderer::GetDragOperations(int x, int y) {
549 if (GetDragRegion(x, y) != NONE)
550 return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK;
551 return DragDropTypes::DRAG_NONE;
552}
553
554void HistoryItemRenderer::WriteDragData(int press_x,
555 int press_y,
556 OSExchangeData* data) {
557 DCHECK(GetDragOperations(press_x, press_y) != DragDropTypes::DRAG_NONE);
558
559 if (GetDragRegion(press_x, press_y) == FAV_ICON)
560 UserMetrics::RecordAction(L"History_DragIcon", model_->profile());
561 else
562 UserMetrics::RecordAction(L"History_DragThumbnail", model_->profile());
563
564 SkBitmap icon;
565 if (model_->GetFavicon(model_index_))
566 icon = *model_->GetFavicon(model_index_);
567
568 drag_utils::SetURLAndDragImage(model_->GetURL(model_index_),
569 model_->GetTitle(model_index_),
570 icon, data);
571}
572
573void HistoryItemRenderer::SetModel(BaseHistoryModel* model, int model_index) {
574 DCHECK(model_index < model->GetItemCount());
575 model_ = model;
576 model_index_ = model_index;
577}
578
579void HistoryItemRenderer::SetDisplayStyle(bool show_full) {
580 show_full_ = show_full;
581}
582
583void HistoryItemRenderer::StarStateChanged(bool state) {
584 // Show the user a tip that can be used to edit the bookmark/star.
585 CPoint star_location(0, 0);
586 ChromeViews::View::ConvertPointToScreen(star_toggle_, &star_location);
587 // Shift the location to make the bubble appear at a visually pleasing
588 // location.
589 gfx::Rect star_bounds(star_location.x, star_location.y + 4,
590 star_toggle_->GetWidth(),
591 star_toggle_->GetHeight());
592 HWND parent = GetViewContainer()->GetHWND();
593 Profile* profile = model_->profile();
594 GURL url = model_->GetURL(model_index_);
595
596 if (state) {
597 // Only change the star state if the page is not starred. The user can
598 // unstar by way of the bubble.
599 star_toggle_->SetState(true);
600 model_->SetPageStarred(model_index_, true);
601 }
602 // WARNING: if state is true, we've been deleted.
603 BookmarkBubbleView::Show(parent, star_bounds, NULL, profile, url, state);
604}
605
606void HistoryItemRenderer::LinkActivated(ChromeViews::Link* link,
607 int event_flags) {
608 if (link == title_link_) {
609 const GURL& url = model_->GetURL(model_index_);
610 PageNavigator* navigator = parent_->navigator();
611 if (navigator && !url.is_empty()) {
612 UserMetrics::RecordAction(L"Destination_History_OpenURL",
613 model_->profile());
614 navigator->OpenURL(url,
615 event_utils::DispositionFromEventFlags(event_flags),
616 PageTransition::AUTO_BOOKMARK);
617 // WARNING: call to OpenURL likely deleted us.
618 return;
619 }
620 }
621}
622
623HistoryItemRenderer::DragRegion HistoryItemRenderer::GetDragRegion(int x,
624 int y) {
625 // Is the location over the favicon?
626 SkBitmap* favicon = model_->GetFavicon(model_index_);
627 CRect title_bounds;
628 title_link_->GetBounds(&title_bounds);
629 if (favicon) {
630 // If the UI layout is right-to-left, we must make sure we mirror the
631 // favicon position before doing any hit testing.
632 gfx::Rect favicon_bounds;
633 favicon_bounds.set_x(
634 title_bounds.left - kIconPadding - kFavIconSize);
635 favicon_bounds.set_y(kEntryPadding);
636 favicon_bounds.set_width(favicon->width());
637 favicon_bounds.set_height(favicon->height());
638 favicon_bounds.set_x(MirroredLeftPointForRect(favicon_bounds));
639 if (favicon_bounds.Contains(x, y)) {
640 return FAV_ICON;
641 }
642 }
643
644 // Is it over the thumbnail?
645 if (show_full_ && model_->GetThumbnail(model_index_)) {
646 CRect thumbnail_loc;
647 GetThumbnailBounds(&thumbnail_loc);
648
649 // If the UI layout is right-to-left, we mirror the thumbnail bounds before
650 // we check whether or not it contains the point in question.
651 gfx::Rect mirrored_loc(thumbnail_loc);
652 thumbnail_loc.MoveToX(MirroredLeftPointForRect(mirrored_loc));
653 if (gfx::Rect(thumbnail_loc).Contains(x, y))
654 return THUMBNAIL;
655 }
656
657 return NONE;
658}
659
660HistoryView::HistoryView(SearchableUIContainer* container,
661 BaseHistoryModel* model,
662 PageNavigator* navigator)
663 : container_(container),
664 renderer_(NULL),
665 model_(model),
666 navigator_(navigator),
667 scroll_helper_(this),
668 line_height_(-1),
669 show_results_(false),
670 show_delete_controls_(false),
671 delete_control_width_(0),
672 loading_(true) {
673 DCHECK(model_.get());
674 DCHECK(navigator_);
675 model_->SetObserver(this);
676
677 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
678 day_break_font_ = resource_bundle.GetFont(ResourceBundle::WebFont);
679
680 // Ensure break_offsets_ is never empty.
681 BreakValue s = {0, 0};
682 break_offsets_.insert(std::make_pair(0, s));
683}
684
685HistoryView::~HistoryView() {
686 if (renderer_)
687 delete renderer_;
688}
689
690void HistoryView::EnsureRenderer() {
691 if (!renderer_)
692 renderer_ = new HistoryItemRenderer(this, show_results_);
693 if (show_delete_controls_ && !delete_renderer_.get()) {
694 delete_renderer_.reset(
695 new ChromeViews::Link(
696 l10n_util::GetString(IDS_HISTORY_DELETE_PRIOR_VISITS_LINK)));
697 delete_renderer_->SetFont(day_break_font_);
698 }
699}
700
701int HistoryView::GetLastEntryMaxY() {
702 if (break_offsets_.empty())
703 return 0;
704 BreakOffsets::iterator last_entry_i = break_offsets_.end();
705 last_entry_i--;
706 return last_entry_i->first;
707}
708
709int HistoryView::GetEntryHeight() {
710 if (line_height_ == -1) {
711 ChromeFont font = ResourceBundle::GetSharedInstance()
712 .GetFont(ResourceBundle::WebFont);
713 line_height_ = font.height() + font.height() - font.baseline();
714 }
715 if (show_results_) {
716 return std::max(line_height_ * 3 + kEntryPadding, kSearchResultsHeight);
717 } else {
718 return std::max(line_height_ + kEntryPadding, kBrowseResultsHeight);
719 }
720}
721
722void HistoryView::ModelChanged(bool result_set_changed) {
723 DetachAllFloatingViews();
724
725 if (!result_set_changed) {
726 // Only item metadata changed. We don't need to do a full re-layout,
727 // but we may need to redraw the affected items.
728 SchedulePaint();
729 return;
730 }
731
732 // TODO(evanm): this could be optimized by computing break_offsets_ lazily.
733 // It'd be especially nice because of our incremental search; right now
734 // we recompute the entire layout with each key you press.
735 break_offsets_.clear();
736
737 const int count = model_->GetItemCount();
738
739 // If we're not viewing bookmarks and we are looking at search results, then
740 // show the items in a results (larger) style.
741 show_results_ = model_->IsSearchResults();
742 if (renderer_)
743 renderer_->SetDisplayStyle(show_results_);
744
745 // If we're viewing bookmarks or we're viewing the larger results, we don't
746 // need to insert break offsets between items.
747 if (show_results_) {
748 BreakValue s = {0, true};
749 break_offsets_.insert(std::make_pair(kResultsMargin, s));
750 if (count > 0) {
751 BreakValue s = {count, true};
752 break_offsets_.insert(
753 std::make_pair(GetEntryHeight() * count + kResultsMargin, s));
754 }
755 } else {
756 int y = 0;
757 Time last_time;
758 Time last_day;
759
760 // Loop through our list of items and find places to insert breaks.
761 for (int i = 0; i < count; ++i) {
762 // NOTE: if you change how we calculate breaks you'll need to update
763 // the deletion code as well (DeleteDayAtModelIndex).
764 Time time = model_->GetVisitTime(i);
765 Time day = time.LocalMidnight();
766 if (i == 0 ||
767 (last_time - time).ToInternalValue() > kSessionBreakTime ||
768 day != last_day) {
769 // We've detected something that needs a break.
770
771 bool day_separation = true;
772
773 // If it's not the first item, figure out if it's a day
774 // break or session break.
775 if (i != 0)
776 day_separation = (day != last_day);
777
778 BreakValue s = {i, day_separation};
779
780 break_offsets_.insert(std::make_pair(y, s));
781 y += GetBreakOffsetHeight(s);
782 }
783 last_time = time;
784 last_day = day;
785 y += GetEntryHeight();
786 }
787
788 // Insert ending day.
789 BreakValue s = {count, true};
790 break_offsets_.insert(std::make_pair(y, s));
791 }
792
793 // Find our ScrollView and layout.
794 if (GetParent() && GetParent()->GetParent())
795 GetParent()->GetParent()->Layout();
796}
797
798void HistoryView::ModelBeginWork() {
799 loading_ = true;
800 if (container_)
801 container_->StartThrobber();
802}
803
804void HistoryView::ModelEndWork() {
805 loading_ = false;
806 if (container_)
807 container_->StopThrobber();
808 if (model_->GetItemCount() == 0)
809 SchedulePaint();
810}
811
812void HistoryView::SetShowDeleteControls(bool show_delete_controls) {
813 if (show_delete_controls == show_delete_controls_)
814 return;
815
816 show_delete_controls_ = show_delete_controls;
817
818 delete_renderer_.reset(NULL);
819
820 // Be sure and rebuild the display, otherwise the floating view indices are
821 // off.
822 ModelChanged(true);
823}
824
825int HistoryView::GetPageScrollIncrement(
826 ChromeViews::ScrollView* scroll_view, bool is_horizontal,
827 bool is_positive) {
828 return scroll_helper_.GetPageScrollIncrement(scroll_view, is_horizontal,
829 is_positive);
830}
831
832int HistoryView::GetLineScrollIncrement(
833 ChromeViews::ScrollView* scroll_view, bool is_horizontal,
834 bool is_positive) {
835 return scroll_helper_.GetLineScrollIncrement(scroll_view, is_horizontal,
836 is_positive);
837}
838
839ChromeViews::VariableRowHeightScrollHelper::RowInfo
840 HistoryView::GetRowInfo(int y) {
841 // Get the time separator header for a given Y click.
842 BreakOffsets::iterator i = GetBreakOffsetIteratorForY(y);
843 int index = i->second.index;
844 int current_y = i->first;
845
846 // Check if the click is on the separator header.
847 if (y < current_y + GetBreakOffsetHeight(i->second)) {
848 return ChromeViews::VariableRowHeightScrollHelper::RowInfo(
849 current_y, GetBreakOffsetHeight(i->second));
850 }
851
852 // Otherwise increment current_y by the item height until it goes past y.
853 current_y += GetBreakOffsetHeight(i->second);
854
855 while (index < model_->GetItemCount()) {
856 int next_y = current_y + GetEntryHeight();
857 if (y < next_y)
858 break;
859 current_y = next_y;
860 }
861
862 // Find the item that corresponds to this new current_y value.
863 return ChromeViews::VariableRowHeightScrollHelper::RowInfo(
864 current_y, GetEntryHeight());
865}
866
867bool HistoryView::IsVisible() {
868 ChromeViews::ViewContainer* vc = GetViewContainer();
869 return vc && vc->IsVisible();
870}
871
872void HistoryView::DidChangeBounds(const CRect& previous, const CRect& current) {
873 SchedulePaint();
874}
875
876void HistoryView::Layout() {
877 DetachAllFloatingViews();
878
879 View* parent = GetParent();
880 if (!parent)
881 return;
882
883 CRect bounds;
884 parent->GetLocalBounds(&bounds, true);
885
886 // If not visible, have zero size so we don't compute anything.
887 int width = 0;
888 int height = 0;
889 if (IsVisible()) {
890 width = bounds.Width();
891 height = std::max(GetLastEntryMaxY(),
892 kEntryPadding + kNoResultTextHeight);
893 }
894
895 int x = GetX();
896 int y = GetY();
897 SetBounds(x, y, width, height);
898}
899
900HistoryView::BreakOffsets::iterator HistoryView::GetBreakOffsetIteratorForY(
901 int y) {
902 BreakOffsets::iterator iter = break_offsets_.upper_bound(y);
903 DCHECK(iter != break_offsets_.end());
904 // Move to the first offset smaller than y.
905 if (iter != break_offsets_.begin())
906 --iter;
907 return iter;
908}
909
910int HistoryView::GetBreakOffsetHeight(HistoryView::BreakValue value) {
911 if (show_results_)
912 return 0;
913
914 if (value.day) {
915 return kDayHeadingHeight;
916 } else {
917 return kSessionBreakHeight;
918 }
919}
920
921void HistoryView::Paint(ChromeCanvas* canvas) {
922 ChromeViews::View::Paint(canvas);
923
924 EnsureRenderer();
925
926 SkRect clip;
927 if (!canvas->getClipBounds(&clip))
928 return;
929
930 const int content_width = GetWidth() - kLeftMargin - kRightMargin;
931
932 const int x1 = kLeftMargin;
933 int clip_y = SkScalarRound(clip.fTop);
934 int clip_max_y = SkScalarRound(clip.fBottom);
935
936 if (model_->GetItemCount() == 0) {
937 // Display text indicating that no results were found.
938 int result_id;
939
940 if (loading_)
941 result_id = IDS_HISTORY_LOADING;
942 else if (show_results_)
943 result_id = IDS_HISTORY_NO_RESULTS;
944 else
945 result_id = IDS_HISTORY_NO_ITEMS;
946
947 canvas->DrawStringInt(l10n_util::GetString(result_id),
948 day_break_font_,
949 SkColorSetRGB(0, 0, 0),
950 x1, kEntryPadding,
951 std::max(content_width, kNoResultMinWidth),
952 kNoResultTextHeight,
953 ChromeCanvas::MULTI_LINE);
954 }
955
956 if (clip_y >= GetLastEntryMaxY())
957 return;
958
959 BreakOffsets::iterator break_offsets_iter =
960 GetBreakOffsetIteratorForY(clip_y);
961 int item_index = break_offsets_iter->second.index;
962 int y = break_offsets_iter->first;
963
964 // Display the "Search results for 'xxxx'" text.
965 if (show_results_ && model_->GetItemCount() > 0) {
966 canvas->DrawStringInt(l10n_util::GetStringF(IDS_HISTORY_SEARCH_STRING,
967 model_->GetSearchText()),
968 day_break_font_,
969 SkColorSetRGB(0, 0, 0),
970 x1, kEntryPadding,
971 content_width, kResultTextHeight,
972 ChromeCanvas::TEXT_VALIGN_BOTTOM);
973 }
974
975 Time midnight_today = Time::Now().LocalMidnight();
976 while (y < clip_max_y && item_index < model_->GetItemCount()) {
977 if (!show_results_ && y == break_offsets_iter->first) {
978 if (y + kDayHeadingHeight > clip_y) {
979 if (break_offsets_iter->second.day) {
980 // We're at a day break, draw the day break appropriately.
981 Time visit_time = model_->GetVisitTime(item_index);
982 DCHECK(visit_time.ToInternalValue() > 0);
983
984 // If it's the first day, then it has a special presentation.
985 std::wstring date_str = TimeFormat::FriendlyDay(visit_time,
986 &midnight_today);
987 if (date_str.empty()) {
988 date_str = TimeFormat::FriendlyDate(visit_time);
989 } else {
990 date_str = l10n_util::GetStringF(
991 IDS_HISTORY_DATE_WITH_RELATIVE_TIME,
992 date_str, TimeFormat::FriendlyDate(visit_time));
993 }
994
995 // Draw date
996 canvas->DrawStringInt(date_str,
997 day_break_font_,
998 SkColorSetRGB(0, 0, 0),
999 x1, y + kDayHeadingHeight -
1000 kBrowseResultsHeight + kEntryPadding,
1001 content_width, kBrowseResultsHeight,
1002 ChromeCanvas::TEXT_VALIGN_BOTTOM);
1003
1004 // Draw delete controls.
1005 if (show_delete_controls_) {
1006 gfx::Rect delete_bounds = CalculateDeleteControlBounds(y);
1007 if (!HasFloatingViewForPoint(delete_bounds.x(),
1008 delete_bounds.y())) {
1009 PaintFloatingView(canvas, delete_renderer_.get(),
1010 delete_bounds.x(), delete_bounds.y(),
1011 delete_bounds.width(), delete_bounds.height());
1012 }
1013 }
1014 } else {
1015 // Draw session separator. Note that we must mirror the position of
1016 // the separator if we run in an RTL locale because we draw the
1017 // separator directly on the canvas.
1018 gfx::Rect separator_bounds(x1 + kSessionGapOffset + kTimeOffset,
1019 y,
1020 1,
1021 kBrowseResultsHeight);
1022 separator_bounds.set_x(MirroredLeftPointForRect(separator_bounds));
1023 canvas->FillRectInt(SkColorSetRGB(178, 178, 178),
1024 separator_bounds.x(), separator_bounds.y(),
1025 separator_bounds.width(),
1026 separator_bounds.height());
1027 }
1028 }
1029
1030 y += GetBreakOffsetHeight(break_offsets_iter->second);
1031 }
1032
1033 if (y + GetEntryHeight() > clip_y && !HasFloatingViewForPoint(x1, y)) {
1034 renderer_->SetModel(model_.get(), item_index);
1035 PaintFloatingView(canvas, renderer_, x1, y, content_width,
1036 GetEntryHeight());
1037 }
1038
1039 y += GetEntryHeight();
1040
1041 BreakOffsets::iterator next_break_offsets = break_offsets_iter;
1042 ++next_break_offsets;
1043 if (next_break_offsets != break_offsets_.end() &&
1044 y >= next_break_offsets->first) {
1045 break_offsets_iter = next_break_offsets;
1046 }
1047
1048 ++item_index;
1049 }
1050}
1051
1052int HistoryView::GetYCoordinateForViewID(int id,
1053 int* model_index,
1054 bool* is_delete_control) {
1055 DCHECK(id < GetMaxViewID());
1056
1057 // Loop through our views and figure out model ids and y coordinates
1058 // of the various items as we go until we find the item that matches.
1059 // the supplied id. This should closely match the code in Paint().
1060 //
1061 // Watch out, this will be is_null when there is no visit.
1062 Time last_time = model_->GetVisitTime(0);
1063
1064 int current_model_index = 0;
1065 int y = show_results_ ? kResultsMargin : 0;
1066
1067 bool show_breaks = !show_results_;
1068
1069 for (int i = 0; i <= id; i++) {
1070 // Consider day and session breaks also between when moving between groups
1071 // of unvisited (visit_time().is_null()) and visited URLs.
1072 Time time = model_->GetVisitTime(current_model_index);
1073 bool at_day_break = last_time.is_null() != time.is_null() ||
1074 (i == 0 || last_time.LocalMidnight() != time.LocalMidnight());
1075 bool at_session_break = last_time.is_null() != time.is_null() ||
1076 (!at_day_break &&
1077 (last_time - time).ToInternalValue() > kSessionBreakTime);
1078 bool at_result = (i == id);
1079
1080 // If we're showing breaks, are a day break and are showing delete
1081 // controls, this view id must be a delete control.
1082 if (show_breaks && at_day_break && show_delete_controls_) {
1083 if (at_result) {
1084 // We've found what we're looking for.
1085 *is_delete_control = true;
1086 *model_index = current_model_index;
1087 return y;
1088 } else {
1089 // This isn't what we're looking for, but it is a valid view, so carry
1090 // on through the loop, but don't increment our current_model_index,
1091 // as the next view will have the same model index.
1092 y += kDayHeadingHeight;
1093 last_time = time;
1094 }
1095 } else {
1096 if (show_breaks) {
1097 if (at_day_break) {
1098 y += kDayHeadingHeight;
1099 } else if (at_session_break) {
1100 y += kSessionBreakHeight;
1101 }
1102 }
1103
1104 // We're on a result item.
1105 if (at_result) {
1106 *is_delete_control = false;
1107 *model_index = current_model_index;
1108 return y;
1109 }
1110
1111 // It wasn't the one we're looking for, so increment our y coordinate and
1112 // model index and move on to the next view.
1113 current_model_index++;
1114 last_time = time;
1115 y += GetEntryHeight();
1116 }
1117 }
1118
1119 return y;
1120}
1121
1122bool HistoryView::GetFloatingViewIDForPoint(int x, int y, int* id) {
1123 // Here's a picture of the various offsets used here.
1124 // Let the (*) on entry #5 below represent the mouse position.
1125 // +--------------
1126 // | entry #2
1127 // +--------------
1128 // <- base_y is the y coordinate of the break.
1129 // +-------------- <- break_offsets->second.index points at this entry
1130 // | entry #3 base_index is this entry index (3).
1131 // +--------------
1132 // +--------------
1133 // | entry #4
1134 // +--------------
1135 // +--------------
1136 // | entry #5 (*) <- y is this y coordinate
1137 // +--------------
1138
1139 // First, verify the x coordinate is within the correct region.
1140 if (x < kLeftMargin || x > GetWidth() - kRightMargin ||
1141 y >= GetLastEntryMaxY()) {
1142 return false;
1143 }
1144
1145 // Find the closest break to this y-coordinate.
1146 BreakOffsets::const_iterator break_offsets_iter =
1147 GetBreakOffsetIteratorForY(y);
1148
1149 // Get the model index of the first item after that break.
1150 int base_index = break_offsets_iter->second.index;
1151
1152 // Get the view id of that item by adding the number of deletes prior to
1153 // this item. (See comments for break_offsets_);
1154 if (show_delete_controls_) {
1155 base_index += CalculateDeleteOffset(break_offsets_iter);
1156
1157 // The current break contains a delete, we need to account for that.
1158 if (break_offsets_iter->second.day)
1159 base_index++;
1160 }
1161
1162 // base_y is the top of the break block.
1163 int base_y = break_offsets_iter->first;
1164
1165 // Add the height of the break.
1166 if (!show_results_)
1167 base_y += GetBreakOffsetHeight(break_offsets_iter->second);
1168
1169 // If y is less than base_y, then it must be over the break and so the
1170 // only view the mouse could be over would be the delete link.
1171 if (y < base_y) {
1172 if (show_delete_controls_ &&
1173 break_offsets_iter->second.day) {
1174 gfx::Rect delete_bounds =
1175 CalculateDeleteControlBounds(base_y - kDayHeadingHeight);
1176
1177 // The delete link bounds must be mirrored if the locale is RTL since the
1178 // point we check against is in LTR coordinates.
1179 delete_bounds.set_x(MirroredLeftPointForRect(delete_bounds));
1180 if (x >= delete_bounds.x() && x < delete_bounds.right()) {
1181 *id = base_index - 1;
1182 return true;
1183 }
1184 }
1185 return false; // Point is over the day heading.
1186 }
1187
1188 // y_delta is the distance from the top of the first item in
1189 // this block to the target y point.
1190 const int y_delta = y - base_y;
1191
1192 int view_id = base_index + (y_delta / GetEntryHeight());
1193 *id = view_id;
1194 return true;
1195}
1196
1197bool HistoryView::EnumerateFloatingViews(
1198 ChromeViews::View::FloatingViewPosition position,
1199 int starting_id,
1200 int* id) {
1201 DCHECK(id);
1202 return View::EnumerateFloatingViewsForInterval(0, GetMaxViewID(),
1203 true,
1204 position, starting_id, id);
1205}
1206
1207ChromeViews::View* HistoryView::ValidateFloatingViewForID(int id) {
1208 if (id >= GetMaxViewID())
1209 return NULL;
1210
1211 bool is_delete_control;
1212 int model_index;
1213 View* floating_view;
1214
1215 int y = GetYCoordinateForViewID(id, &model_index, &is_delete_control);
1216 if (is_delete_control) {
1217 ChromeViews::Link* delete_link = new ChromeViews::Link(
1218 l10n_util::GetString(IDS_HISTORY_DELETE_PRIOR_VISITS_LINK));
1219 delete_link->SetID(model_index);
1220 delete_link->SetFont(day_break_font_);
1221 delete_link->SetController(this);
1222
1223 gfx::Rect delete_bounds = CalculateDeleteControlBounds(y);
1224 delete_link->SetBounds(delete_bounds.x(), delete_bounds.y(),
1225 delete_bounds.width(), delete_bounds.height());
1226 floating_view = delete_link;
1227 } else {
1228 HistoryItemRenderer* renderer =
1229 new HistoryItemRenderer(this,
1230 show_results_);
1231 renderer->SetModel(model_.get(), model_index);
1232 renderer->SetBounds(kLeftMargin, y,
1233 GetWidth() - kLeftMargin - kRightMargin,
1234 GetEntryHeight());
1235 floating_view = renderer;
1236 }
1237 floating_view->Layout();
1238 AttachFloatingView(floating_view, id);
1239
1240#ifdef DEBUG_FLOATING_VIEWS
1241 floating_view->SetBackground(ChromeViews::Background::CreateSolidBackground(
1242 SkColorSetRGB(255, 0, 0)));
1243 floating_view->SchedulePaint();
1244#endif
1245 return floating_view;
1246}
1247
1248int HistoryView::GetMaxViewID() {
1249 if (!show_delete_controls_)
1250 return model_->GetItemCount();
1251
1252 // Figure out how many delete controls we are displaying.
1253 int deletes = 0;
1254 for (BreakOffsets::iterator i = break_offsets_.begin();
1255 i != break_offsets_.end(); ++i) {
1256 if (i->second.day)
1257 deletes++;
1258 }
1259
1260 // Subtract one because we don't display a delete control at the end.
1261 deletes--;
1262
1263 return std::max(0, deletes + model_->GetItemCount());
1264}
1265
1266void HistoryView::LinkActivated(ChromeViews::Link* source, int event_flags) {
1267 DeleteDayAtModelIndex(source->GetID());
1268}
1269
1270void HistoryView::DeleteDayAtModelIndex(int index) {
1271 std::wstring text = l10n_util::GetString(
1272 IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING);
1273 std::wstring caption = l10n_util::GetString(
1274 IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING_TITLE);
1275 UINT flags = MB_OKCANCEL | MB_ICONWARNING | MB_TOPMOST | MB_SETFOREGROUND;
1276
1277 if (win_util::MessageBox(GetViewContainer()->GetHWND(),
1278 text, caption, flags) != IDOK) {
1279 return;
1280 }
1281
1282 UserMetrics::RecordAction(L"History_DeleteHistory", model_->profile());
1283
1284 // BrowsingDataRemover deletes itself when done.
1285 // index refers to the last page at the very end of the day, so we delete
1286 // everything after the start of the day and before the end of the day.
1287 Time delete_begin = model_->GetVisitTime(index).LocalMidnight();
1288 Time delete_end =
1289 (model_->GetVisitTime(index) + TimeDelta::FromDays(1)).LocalMidnight();
1290
1291 BrowsingDataRemover* remover =
1292 new BrowsingDataRemover(model_->profile(),
1293 delete_begin,
1294 delete_end);
1295 remover->Remove(BrowsingDataRemover::REMOVE_HISTORY |
1296 BrowsingDataRemover::REMOVE_COOKIES |
1297 BrowsingDataRemover::REMOVE_CACHE);
1298
1299 model_->Refresh();
1300
1301 // Scroll to the origin, otherwise the scroll position isn't changed and the
1302 // user is left looking at a region they originally weren't viewing.
1303 ScrollRectToVisible(0, 0, 0, 0);
1304}
1305
1306int HistoryView::CalculateDeleteOffset(
1307 const BreakOffsets::const_iterator& it) {
1308 DCHECK(show_delete_controls_);
1309 int offset = 0;
1310 for (BreakOffsets::iterator i = break_offsets_.begin(); i != it; ++i) {
1311 if (i->second.day)
1312 offset++;
1313 }
1314 return offset;
1315}
1316
1317int HistoryView::GetDeleteControlWidth() {
1318 if (delete_control_width_)
1319 return delete_control_width_;
1320 CSize pref;
1321 EnsureRenderer();
1322 delete_renderer_->GetPreferredSize(&pref);
1323 delete_control_width_ = pref.cx;
1324 return delete_control_width_;
1325}
1326
1327gfx::Rect HistoryView::CalculateDeleteControlBounds(int base_y) {
1328 // NOTE: the height here is too big, it should be just big enough to show
1329 // the link. Additionally this should be baseline aligned with the date. I'm
1330 // not doing that now as a redesign of HistoryView is in the works.
1331 const int delete_width = GetDeleteControlWidth();
1332 const int delete_x = GetWidth() - kRightMargin - delete_width;
1333 return gfx::Rect(delete_x,
1334 base_y + kDeleteControlOffset,
1335 delete_width,
1336 kBrowseResultsHeight);
1337}