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..f46739b8e11a2c7739237e363cd3c2b5a27ab32d |
| --- /dev/null |
| +++ b/ui/views/controls/styled_label.cc |
| @@ -0,0 +1,148 @@ |
| +// 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) |
| + 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::SetLink(const ui::Range& range) { |
| + DCHECK(ui::Range(0, text_.size()).Contains(range)); |
| + link_ranges_.push(LinkRange(range)); |
| +} |
| + |
| +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); |
| +} |
| + |
| +int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { |
|
sky
2013/03/14 14:43:58
Can you also add some tests for coverage of this?
|
| + width -= GetInsets().width(); |
| + if (width <= 0) |
| + return 0; |
| + |
| + if (!dry_run) { |
|
sky
2013/03/14 14:43:58
Should this be before the early return on 49?
Evan Stade
2013/03/14 19:07:40
Done.
|
| + RemoveAllChildViews(true); |
| + link_targets_.clear(); |
| + } |
| + |
| + gfx::Font font = |
| + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); |
| + |
| + int line_height = font.GetHeight(); |
| + // The index of the line we're on. |
| + int line = 0; |
| + // The x position (in pixels) of the line we're on. |
| + int x = 0; |
|
sky
2013/03/14 14:43:58
Doesn't this need to be set to GetInsets().left()
Evan Stade
2013/03/14 19:07:40
L132 needs to account for insets, but that's it (f
|
| + |
| + 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); |
| + std::vector<string16> substrings; |
| + 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(); |
| + 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 height matches |
| + // links' preferred heights. |
| + label->SetHasFocusBorder(true); |
| + view.reset(label); |
| + } |
| + |
| + gfx::Size view_size = view->GetPreferredSize(); |
| + if (!dry_run) { |
| + view->SetBoundsRect(gfx::Rect(gfx::Point(x, line * line_height), |
| + view_size)); |
| + AddChildView(view.release()); |
| + } |
| + x += view_size.width(); |
| + |
| + remaining_string = remaining_string.substr(chunk.size()); |
| + } |
| + |
| + return (line + 1) * line_height + GetInsets().height(); |
| +} |
| + |
| +void StyledLabel::LinkClicked(Link* source, int event_flags) { |
| + listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); |
| +} |
| + |
| +} // namespace views |