Chromium Code Reviews| Index: ui/views/controls/styled_label.cc | 
| diff --git a/ui/views/controls/styled_label.cc b/ui/views/controls/styled_label.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..9ef7934d4960bde79e8c7670d10aac01b42a43ea | 
| --- /dev/null | 
| +++ b/ui/views/controls/styled_label.cc | 
| @@ -0,0 +1,168 @@ | 
| +// Copyright 2013 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "ui/views/controls/styled_label.h" | 
| + | 
| +#include <vector> | 
| + | 
| +#include "base/string_util.h" | 
| +#include "ui/base/resource/resource_bundle.h" | 
| +#include "ui/base/text/text_elider.h" | 
| +#include "ui/views/controls/label.h" | 
| +#include "ui/views/controls/link.h" | 
| +#include "ui/views/controls/styled_label_listener.h" | 
| + | 
| +namespace views { | 
| + | 
| +bool StyledLabel::LinkRange::operator<(const StyledLabel::LinkRange& other) | 
| 
 
msw
2013/03/14 23:24:30
nit: move the arg onto the next line so const isn'
 
Evan Stade
2013/03/15 02:42:27
is this really a part of our style guide? I see 89
 
sky
2013/03/15 03:32:18
It was in Google's c++ style guide, but was remove
 
msw
2013/03/15 08:38:02
Sorry about that, i'm behind the times!
 
 | 
| + const { | 
| + // Intentionally reversed so the priority queue is sorted by smallest first. | 
| + return range.start() > other.range.start(); | 
| +} | 
| + | 
| +StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener) | 
| + : text_(text), | 
| + listener_(listener) {} | 
| + | 
| +StyledLabel::~StyledLabel() {} | 
| + | 
| +void StyledLabel::AddLink(const ui::Range& range) { | 
| + DCHECK(!range.is_reversed()); | 
| + DCHECK(!range.is_empty()); | 
| + DCHECK(ui::Range(0, text_.size()).Contains(range)); | 
| + link_ranges_.push(LinkRange(range)); | 
| 
 
msw
2013/03/14 23:24:30
Should this InvalidateLayout? I don't think it nee
 
Evan Stade
2013/03/15 02:42:27
it does need to do both because adding a link may
 
 | 
| +} | 
| + | 
| +gfx::Insets StyledLabel::GetInsets() const { | 
| + gfx::Insets insets = View::GetInsets(); | 
| + gfx::Insets focus_border_padding(1, 1, 1, 1); | 
| 
 
msw
2013/03/14 23:24:30
nit: const
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + insets += focus_border_padding; | 
| + return insets; | 
| +} | 
| + | 
| +int StyledLabel::GetHeightForWidth(int w) { | 
| + if (w != calculated_size_.width()) | 
| + calculated_size_ = gfx::Size(w, CalculateAndDoLayout(w, true)); | 
| + | 
| + return calculated_size_.height(); | 
| +} | 
| + | 
| +void StyledLabel::Layout() { | 
| + CalculateAndDoLayout(GetLocalBounds().width(), false); | 
| +} | 
| + | 
| +void StyledLabel::LinkClicked(Link* source, int event_flags) { | 
| + listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); | 
| +} | 
| + | 
| +int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { | 
| + if (!dry_run) { | 
| + RemoveAllChildViews(true); | 
| + link_targets_.clear(); | 
| + } | 
| + | 
| + width -= GetInsets().width(); | 
| + if (width <= 0) | 
| + return 0; | 
| + | 
| + int line_height = CalculateLineHeight(); | 
| 
 
msw
2013/03/14 23:24:30
nit: const
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + // The index of the line we're on. | 
| + int line = 0; | 
| + // The x position (in pixels) of the line we're on, relative to content | 
| + // bounds. | 
| + int x = 0; | 
| + | 
| + string16 remaining_string = text_; | 
| + std::priority_queue<LinkRange> link_ranges = link_ranges_; | 
| + | 
| + // Iterate over the text, creating a bunch of labels and links and laying them | 
| + // out in the appropriate positions. | 
| + while (!remaining_string.empty()) { | 
| + // Don't put whitespace at beginning of a line. | 
| + if (x == 0) | 
| + TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); | 
| + | 
| + ui::Range range(ui::Range::InvalidRange()); | 
| + if (!link_ranges.empty()) | 
| + range = link_ranges.top().range; | 
| + | 
| + gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); | 
| 
 
msw
2013/03/14 23:24:30
nit: const
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + std::vector<string16> substrings; | 
| + const gfx::Font& font = | 
| 
 
msw
2013/03/14 23:24:30
nit: Remove this Font init and the resource_bundle
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); | 
| + ui::ElideRectangleText(remaining_string, | 
| + font, | 
| + chunk_bounds.width(), | 
| + chunk_bounds.height(), | 
| + ui::IGNORE_LONG_WORDS, | 
| + &substrings); | 
| + | 
| + string16 chunk = substrings[0]; | 
| + if (chunk.empty()) { | 
| + // Nothing fit on this line. Start a new line. If x is 0, there's no room | 
| + // for anything. Just abort. | 
| + if (x == 0) | 
| + break; | 
| + | 
| + x = 0; | 
| + line++; | 
| + continue; | 
| + } | 
| + | 
| + scoped_ptr<View> view; | 
| + size_t position = text_.size() - remaining_string.size(); | 
| 
 
msw
2013/03/14 23:24:30
nit: const
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + if (position >= range.start()) { | 
| + // This chunk is a link. | 
| + if (chunk.size() < range.length() && x != 0) { | 
| + // Don't wrap links. Try to fit them entirely on one line. | 
| + x = 0; | 
| + line++; | 
| + continue; | 
| + } | 
| + | 
| + chunk = chunk.substr(0, range.length()); | 
| + Link* link = new Link(chunk); | 
| + link->set_listener(this); | 
| + if (!dry_run) | 
| + link_targets_[link] = range; | 
| + view.reset(link); | 
| + link_ranges.pop(); | 
| + } else { | 
| + // This chunk is normal text. | 
| + if (position + chunk.size() > range.start()) | 
| + chunk = chunk.substr(0, range.start() - position); | 
| + | 
| + Label* label = new Label(chunk); | 
| + // Give the label a focus border so that its preferred size matches | 
| + // links' preferred sizes. | 
| + label->SetHasFocusBorder(true); | 
| + view.reset(label); | 
| + } | 
| + | 
| + // Lay out the views to overlap by 1 pixel to compensate for their border | 
| + // spacing. Otherwise, "<a>link</a>," will render as "link ,". | 
| + const int overlap = 1; | 
| + gfx::Size view_size = view->GetPreferredSize(); | 
| 
 
msw
2013/03/14 23:24:30
nit: const
 
Evan Stade
2013/03/15 02:42:27
Done.
 
 | 
| + DCHECK_EQ(line_height, view_size.height() - 2 * overlap); | 
| 
 
msw
2013/03/14 23:24:30
nit: should line_height remove the focus border in
 
Evan Stade
2013/03/15 02:42:27
not sure I understand the question, but no, the li
 
msw
2013/03/15 08:38:02
Ah, I misread and thought that the lines were stac
 
 | 
| + if (!dry_run) { | 
| + view->SetBoundsRect(gfx::Rect( | 
| + gfx::Point(GetInsets().left() + x - overlap, | 
| + GetInsets().top() + line * line_height - overlap), | 
| + view_size)); | 
| + AddChildView(view.release()); | 
| + } | 
| + x += view_size.width() - 2 * overlap; | 
| + | 
| + remaining_string = remaining_string.substr(chunk.size()); | 
| + } | 
| + | 
| + return (line + 1) * line_height + GetInsets().height(); | 
| +} | 
| + | 
| +int StyledLabel::CalculateLineHeight() { | 
| + Label label; | 
| 
 
msw
2013/03/14 23:24:30
nit: static
 
Evan Stade
2013/03/15 02:42:27
class type statics are forbidden.
 
 | 
| + return label.GetPreferredSize().height(); | 
| +} | 
| + | 
| +} // namespace views |