Add text wrapping.

The layout engine now first tries to wrap some text if too wide before
trying to use scrollbars.
This commit is contained in:
Mark de Wever 2008-10-11 17:04:18 +00:00
parent 6329273f9e
commit a36e1ee696
12 changed files with 453 additions and 45 deletions

View file

@ -47,6 +47,19 @@ public:
/***** ***** ***** ***** Inherited ***** ***** ***** *****/
/** Inherited from twidget. */
bool can_wrap() const { return grid_.can_wrap() || twidget::can_wrap(); }
/**
* Inherited from twidget.
*
* @todo Adjust for our border.
*/
bool set_width_constrain(const unsigned width) { return grid_.set_width_constrain(width); }
/** Inherited from twidget. */
void clear_width_constrain() { grid_.clear_width_constrain(); }
/**
* Inherited from twidget.
*

View file

@ -20,17 +20,18 @@
namespace gui2 {
tcontrol::tcontrol(const unsigned canvas_count) :
visible_(true),
label_(),
multiline_label_(false),
use_tooltip_on_label_overflow_(true),
tooltip_(),
help_message_(),
canvas_(canvas_count),
restorer_(),
config_(0),
renderer_()
tcontrol::tcontrol(const unsigned canvas_count)
: visible_(true)
, label_()
, multiline_label_(false)
, use_tooltip_on_label_overflow_(true)
, tooltip_()
, help_message_()
, canvas_(canvas_count)
, restorer_()
, config_(0)
, renderer_()
, text_maximum_width_(0)
{
}
@ -84,31 +85,104 @@ void tcontrol::load_config()
tpoint tcontrol::get_minimum_size() const
{
assert(config_);
const tpoint min_size(config_->min_width, config_->min_height);
if(label_.empty()) {
return min_size;
tpoint result(config_->min_width, config_->min_height);
if(! label_.empty()) {
// If no label text set we use the predefined value.
/**
* @todo The value send should subtract the border size
* and readd it after calculation to get the proper result.
*/
result = get_best_text_size(result);
}
return get_best_text_size(min_size);
DBG_G_L << "tcontrol(" + get_control_type() + ") " + __func__ + ":"
<< " empty label " << label_.empty()
<< " result " << result
<< ".\n";
return result;
}
tpoint tcontrol::get_best_size() const
{
assert(config_);
tpoint result(config_->default_width, config_->default_height);
if(! label_.empty()) {
// If no label text set we use the predefined value.
// Return default on an empty label.
const tpoint default_size(config_->default_width, config_->default_height);
if(label_.empty()) {
return default_size;
/**
* @todo The value send should subtract the border size
* and readd it after calculation to get the proper result.
*/
result = get_best_text_size(result);
}
return get_best_text_size(default_size);
DBG_G_L << "tcontrol(" + get_control_type() + ") " + __func__ + ":"
<< " empty label " << label_.empty()
<< " result " << result
<< ".\n";
return result;
}
tpoint tcontrol::get_best_size(const tpoint& maximum_size) const
{
assert(config_);
tpoint result(0, 0);
if(! label_.empty()) {
// If no label text set we use the predefined value.
/**
* @todo The value send should subtract the border size
* and readd it after calculation to get the proper result.
*/
result = get_best_text_size(tpoint(0,0), maximum_size);
}
DBG_G_L << "tcontrol(" + get_control_type() + ") " + __func__ + ":"
<< " empty label " << label_.empty()
<< " result " << result
<< ".\n";
return result;
}
tpoint tcontrol::get_maximum_size() const
{
assert(config_);
return tpoint(config_->max_width, config_->max_height);
tpoint result = tpoint(config_->max_width, config_->max_height);
DBG_G_L << "tcontrol(" + get_control_type() + ") " + __func__ + ":"
<< " result " << result
<< ".\n";
return result;
}
bool tcontrol::set_width_constrain(const unsigned width)
{
assert(can_wrap());
assert(text_maximum_width_ == 0);
bool result = false;
if(label_.empty()) {
// Return true on empty label but don't set the value.
result = true;
} else if(get_best_text_size(tpoint(width, 0)).y <= static_cast<int>(width)) {
// Test whether we can achieve the wanted size.
text_maximum_width_ = width;
result = true;
}
DBG_G_L << "tcontrol(" + get_control_type() + ") " + __func__ + ":"
<< " empty label " << label_.empty()
<< " result " << result
<< ".\n";
return result;
}
void tcontrol::clear_width_constrain()
{
text_maximum_width_ = 0;
}
void tcontrol::draw(surface& surface, const bool force,
@ -206,14 +280,18 @@ void tcontrol::update_canvas()
canvas.set_variable("text", variant(label_));
canvas.set_variable("text_maximum_width", variant(max_width));
canvas.set_variable("text_maximum_height", variant(max_height));
canvas.set_variable("text_wrap_mode", variant(can_wrap()
? PANGO_ELLIPSIZE_NONE : PANGO_ELLIPSIZE_END));
}
}
int tcontrol::get_text_maximum_width() const
{
assert(config_);
return get_width() - config_->text_extra_width;
return text_maximum_width_ != 0
? text_maximum_width_
: get_width() - config_->text_extra_width;
}
int tcontrol::get_text_maximum_height() const
@ -235,8 +313,10 @@ void tcontrol::restore_background(surface& dst)
gui2::restore_background(restorer_, dst, get_rect());
}
tpoint tcontrol::get_best_text_size(const tpoint& minimum_size) const
tpoint tcontrol::get_best_text_size(const tpoint& minimum_size, const tpoint& maximum_size) const
{
log_scope2(gui_layout, "tcontrol(" + get_control_type() + ") " + __func__);
assert(!label_.empty());
const tpoint border(config_->text_extra_width, config_->text_extra_height);
@ -247,13 +327,34 @@ tpoint tcontrol::get_best_text_size(const tpoint& minimum_size) const
renderer_.set_font_style(config_->text_font_style);
// Try with the minimum wanted size.
renderer_.set_maximum_width(size.x);
const int maximum_width = text_maximum_width_ != 0
? text_maximum_width_
: maximum_size.x;
renderer_.set_maximum_width(maximum_width);
if(can_wrap()) {
renderer_.set_ellipse_mode(PANGO_ELLIPSIZE_NONE);
}
if(multiline_label_) {
// FIXME multiline seems to be unused
renderer_.set_maximum_height(size.y);
}
DBG_G_L << "tcontrol(" + get_control_type() + ") status:\n";
DBG_G_L << "minimum_size " << minimum_size
<< " maximum_size " << maximum_size
<< " text_maximum_width_ " << text_maximum_width_
<< " can_wrap " << can_wrap()
<< " truncated " << renderer_.is_truncated()
<< " renderer size " << renderer_.get_size()
<< ".\n";
// If doesn't fit try the maximum.
if(renderer_.is_truncated()) {
if(renderer_.is_truncated() && !can_wrap()) {
// FIXME if maximum size is defined we should look at that
// but also we don't adjust for the extra text space yet!!!
const tpoint maximum_size(config_->max_width, config_->max_height);
renderer_.set_maximum_width(maximum_size.x ? maximum_size.x - border.x : -1);
if(multiline_label_) {
@ -270,7 +371,8 @@ tpoint tcontrol::get_best_text_size(const tpoint& minimum_size) const
if(size.y < minimum_size.y) {
size.y = minimum_size.y;
}
DBG_G_L << "tcontrol(" + get_control_type() + ") result " << size << ".\n";
return size;
}

View file

@ -98,12 +98,18 @@ public:
/** Inherited from twidget. */
tpoint get_best_size() const;
/** Import overloaded versions. */
using twidget::get_best_size;
/** Inherited from twidget. */
tpoint get_best_size(const tpoint& maximum_size) const;
/** Inherited from twidget. */
tpoint get_maximum_size() const;
/** Inherited from twidget. */
bool set_width_constrain(const unsigned width);
/** Inherited from twidget. */
void clear_width_constrain();
/** Inherited from twidget. */
void draw(surface& surface, const bool force = false,
const bool invalidate_background = false);
@ -334,10 +340,14 @@ private:
* Gets the best size for a text.
*
* @param minimum_size The minimum size of the text.
* @param maximum_size The wanted maximum size of the text, if not
* possible it's ignored. A value of 0 means
* that it's ignored as well.
*
* @returns The best size.
*/
tpoint get_best_text_size(const tpoint& minimum_size) const;
tpoint get_best_text_size(const tpoint& minimum_size,
const tpoint& maximum_size = tpoint(0,0)) const;
/**
* Contains a helper cache for the rendering.
@ -351,6 +361,9 @@ private:
* as wanted.
*/
mutable font::ttext renderer_;
/** The maximum width for the text in a control. */
int text_maximum_width_;
};
} // namespace gui2

View file

@ -281,6 +281,129 @@ tpoint tgrid::get_best_size(const tpoint& maximum_size) const
return tpoint(size.x, size.y);
}
bool tgrid::can_wrap() const
{
foreach(const tchild& child, children_) {
if(child.can_wrap()) {
return true;
}
}
// Inherit
return twidget::can_wrap();
}
bool tgrid::set_width_constrain(const unsigned maximum_width)
{
/*
* 1. Test the width of (every) row.
* 2. Row wider as wanted?
* - No goto 3
* - Yes
* 2.1 Test every column in the row.
* 2.2 Can be resized?
* - No goto 2.1.
* - Yes can be sized small enough?
* - No FAILURE.
* - Yes goto 3.
* 2.3 Last column?
* - No goto 2.1
* - Yes FAILURE.
* 3. Last row?
* - No goto 1.
* - Yes SUCCESS.
*/
log_scope2(gui_layout, std::string("tgrid ") + __func__);
DBG_G_L << "tgrid: maximum_width " << maximum_width << ".\n";
std::vector<int> widths(cols_);
for(unsigned row = 0; row < rows_; ++row) {
for(unsigned col = 0; col < cols_; ++col) {
widths[col] = (child(row, col)./**widget()->**/get_best_size()).x;
}
if(std::accumulate(widths.begin(), widths.end(), 0) >
static_cast<int>(maximum_width)) {
DBG_G_L << "tgrid: row " << row << " out of bounds needs "
<< std::accumulate(widths.begin(), widths.end(), 0)
<< " available " << maximum_width
<< ", try to resize.\n";
log_scope2(gui_layout, "tgrid: testing all columns");
int width = 0;
for(unsigned col = 0; col < cols_; ++col) {
log_scope2(gui_layout, "tgrid: column "
+ lexical_cast<std::string>(col));
tchild& chld = child(row, col);
if(!chld.can_wrap()) {
DBG_G_L << "tgrid: column can't wrap, skip.\n";
continue;
}
if(widths[col] == 0) {
DBG_G_L << "tgrid: column has zero width, skip.\n";
}
width = widths[col];
widths[col] = 0;
const int widget_width = maximum_width
- std::accumulate(widths.begin(), widths.end(), 0);
if(widget_width <=0) {
DBG_G_L << "tgrid: column is too small to resize, skip.\n";
widths[col] = width;
width = 0;
continue;
}
if(chld.get_best_size(tpoint(widget_width, 0)).x <= widget_width
&& chld.set_width_constrain(widget_width)) {
DBG_G_L << "tgrid: column resize succeeded.\n";
break;
}
DBG_G_L << "tgrid: column resize failed.\n";
widths[col] = width;
width = 0;
}
if(width == 0) {
DBG_G_L << "tgrid: no solution found.\n";
return false;
}
} else {
DBG_G_L << "tgrid: row " << row << " in bounds.\n";
}
}
DBG_G_L << "tgrid: found solution.\n";
return true;
}
void tgrid::clear_width_constrain()
{
foreach(tchild& cell, children_) {
if(cell.widget() && cell.widget()->can_wrap()) {
cell.widget()->clear_width_constrain();
}
}
// Inherit
twidget::clear_width_constrain();
}
bool tgrid::has_vertical_scrollbar() const
{
for(std::vector<tchild>::const_iterator itor = children_.begin();
@ -674,6 +797,13 @@ tpoint tgrid::tchild::get_maximum_size() const
return maximum_size_;
}
bool tgrid::tchild::set_width_constrain(const unsigned width)
{
assert(widget_ && widget_->can_wrap());
return widget_->set_width_constrain(width - border_space().x);
}
tpoint tgrid::tchild::border_space() const
{
tpoint result(0, 0);

View file

@ -183,6 +183,15 @@ public:
/** Inherited from twidget. */
tpoint get_maximum_size() const;
/** Inherited from twidget. */
bool can_wrap() const;
/** Inherited from twidget. */
bool set_width_constrain(const unsigned width);
/** Inherited from twidget. */
void clear_width_constrain();
/** Inherited from twidget. */
bool has_vertical_scrollbar() const;
@ -260,6 +269,12 @@ private:
/** Returns the maximum size for the cell. */
tpoint get_maximum_size() const;
/** Returns the can_wrap for the cell. */
bool can_wrap() const { return widget_ ? widget_->can_wrap() : false; }
/** Returns the set_width_constrain for the cell. */
bool set_width_constrain(const unsigned width);
/**
* Sets the size of the widget in the cell.
*

View file

@ -24,12 +24,16 @@ class tlabel : public tcontrol
{
public:
tlabel() :
tcontrol(COUNT),
state_(ENABLED)
tlabel()
: tcontrol(COUNT)
, state_(ENABLED)
, can_wrap_(false)
{
}
/** Inherited from twidget. */
bool can_wrap() const { return can_wrap_; }
/** Inherited from tcontrol. */
void set_active(const bool active)
{ if(get_active() != active) set_state(active ? ENABLED : DISABLED); };
@ -40,6 +44,10 @@ public:
/** Inherited from tcontrol. */
unsigned get_state() const { return state_; }
/***** ***** ***** setters / getters for members ***** ****** *****/
void set_can_wrap(const bool wrap) { can_wrap_ = wrap; }
private:
/**
@ -59,6 +67,9 @@ private:
*/
tstate state_;
/** Holds the label can wrap or not. */
bool can_wrap_;
/** Inherited from tcontrol. */
const std::string& get_control_type() const
{ static const std::string type = "label"; return type; }

View file

@ -77,14 +77,33 @@ const tspacer* tscroll_label::find_spacer(const bool must_exist) const
void tscroll_label::finalize()
{
find_label(true)->set_label(label());
tspacer* spacer = new tspacer();
spacer->set_id("_label");
spacer->set_definition("default");
label_ = dynamic_cast<tlabel*>(grid().swap_child("_label", spacer, true));
assert(label_);
label_->set_label(label());
label_->set_can_wrap(true);
}
bool tscroll_label::set_content_width_constrain(const unsigned width)
{
bool result = !label_ ? true : label_->set_width_constrain(width);
DBG_G_L << "tscroll_label " << __func__ << ":"
<< " no label " << !label_
<< " result " << result
<< ".\n";
return result;
}
void tscroll_label::clear_content_width_constrain()
{
if(label_) {
label_->clear_width_constrain();
}
}
tpoint tscroll_label::get_content_best_size(const tpoint& maximum_size) const

View file

@ -38,9 +38,6 @@ public:
~tscroll_label();
/** Inherited from twidget. */
bool has_vertical_scrollbar() const { return true; }
/** Inherited from tcontrol. */
void set_label(const t_string& label);
@ -104,6 +101,15 @@ private:
const std::string& get_control_type() const
{ static const std::string type = "scroll_label"; return type; }
/** Inherited from tvertical_scrollbar_container_. */
bool can_content_wrap() const { return true; }
/** Inherited from tvertical_scrollbar_container_. */
bool set_content_width_constrain(const unsigned width);
/** Inherited from tvertical_scrollbar_container_. */
void clear_content_width_constrain();
/** Inherited from tvertical_scrollbar_container_. */
tpoint get_content_best_size(const tpoint& maximum_size) const;

View file

@ -156,9 +156,28 @@ void tvertical_scrollbar_container_::key_press(tevent_handler& /*event*/,
}
}
bool tvertical_scrollbar_container_::set_width_constrain(const unsigned width)
{
log_scope2(gui_layout,
std::string("tvertical_scrollbar_container_ ") + __func__);
const unsigned scrollbar_width = scrollbar_mode_ == HIDE
? 0 : find_scrollbar_grid()->get_best_size().x;
const bool result = set_content_width_constrain(width - scrollbar_width);
DBG_G_L << "tvertical_scrollbar_container_ "
<< " width " << width
<< " scrollbar_width " << scrollbar_width
<< " result " << result
<< ".\n";
return result;
}
tpoint tvertical_scrollbar_container_::get_best_size() const
{
log_scope2(gui_layout, std::string("tvertical_scrollbar_container_ ") + __func__);
log_scope2(gui_layout,
std::string("tvertical_scrollbar_container_ ") + __func__);
const tpoint content = get_content_best_size();
if(scrollbar_mode_ == HIDE) {
@ -187,8 +206,11 @@ tpoint tvertical_scrollbar_container_::get_best_size() const
tpoint tvertical_scrollbar_container_::get_best_size(const tpoint& maximum_size) const
{
log_scope2(gui_layout, std::string("tvertical_scrollbar_container_ ") + __func__);
DBG_G_L << "tvertical_scrollbar_container_ maximum_size " << maximum_size << ".\n";
log_scope2(gui_layout,
std::string("tvertical_scrollbar_container_ ") + __func__);
DBG_G_L << "tvertical_scrollbar_container_ maximum_size "
<< maximum_size << ".\n";
if(scrollbar_mode_ == HIDE) {
// No scrollbar hope the normal size is small enough. Don't send the
@ -201,7 +223,8 @@ tpoint tvertical_scrollbar_container_::get_best_size(const tpoint& maximum_size)
} else {
// The scrollbar also can't be resized so ask the best size.
const tpoint scrollbar = find_scrollbar_grid()->get_best_size();
const tpoint content = get_content_best_size(tpoint(maximum_size.x - scrollbar.x, maximum_size.y));
const tpoint content = get_content_best_size(tpoint(
maximum_size.x - scrollbar.x, maximum_size.y));
// Width and height same rules as above.
if(scrollbar_mode_ == SHOW) {

View file

@ -76,6 +76,15 @@ public:
void key_press(tevent_handler& event, bool& handled,
SDLKey key, SDLMod modifier, Uint16 unicode);
/** Inherited from twidget. */
bool can_wrap() const { return can_content_wrap(); }
/** Inherited from twidget. */
bool set_width_constrain(const unsigned width);
/** Inherited from twidget. */
void clear_width_constrain() { clear_content_width_constrain(); }
/** Inherited from twidget. */
bool has_vertical_scrollbar() const { return true; }
@ -214,7 +223,29 @@ private:
/** Returns the selected row. */
virtual unsigned get_selected_row() const;
/***** ***** pure virtuals for the subclasses ****** *****/
/***** ***** (pure) virtuals for the subclasses ****** *****/
/**
* Returns whether or not the content can wrap.
*
* See can_wrap() for more info.
*/
virtual bool can_content_wrap() const { return false; }
/**
* Sets the content width constrain.
*
* See set_width_contrain() for more info.
*/
virtual bool set_content_width_constrain(const unsigned /*width*/)
{return false; }
/**
* Clears the content width constrain.
*
* See clear_width_constrain() for more info.
*/
virtual void clear_content_width_constrain() {}
/**
* Returns the best size for the content part.

View file

@ -325,8 +325,7 @@ public:
* @returns The best size for the widget.
* @retval 0,0 The best size is 0,0.
*/
virtual tpoint get_best_size(const tpoint& /*maximum_size*/) const
{ return get_best_size(); }
virtual tpoint get_best_size(const tpoint& maximum_size) const = 0;
/**
* Gets the maximum size for the widget.
@ -336,6 +335,32 @@ public:
*/
virtual tpoint get_maximum_size() const = 0;
/**
* Can the widget wrap.
*
* When a widget can wrap it can reduce it's width by increasing it's
* height. When a layout is too wide it should first try to wrap and if
* that fails it should check the vertical scrollbar status. After wrapping
* the height might (probably will) change so the layout engine needs to
* recalculate.
*/
virtual bool can_wrap() const { return false; }
/**
* Limits the maximum width for a widget.
*
* This function should only be called on widgets that can wrap.
*
* @param width The maximum width for the widget.
*
* @returns True if the widget can wrap in the wanted
* width, false otherwise.
*/
virtual bool set_width_constrain(const unsigned /*width*/) { return false; }
/** Clears the width constrains set. */
virtual void clear_width_constrain() {}
/**
* Does the widget have a vertical scrollbar.
*

View file

@ -360,6 +360,10 @@ SDL_Rect twindow::get_client_rect() const
void twindow::layout()
{
boost::intrusive_ptr<const twindow_definition::tresolution> conf =
boost::dynamic_pointer_cast<const twindow_definition::tresolution>(config());
assert(conf);
if(automatic_placement_) {
log_scope2(gui_layout, "Window: Recalculate size");
@ -368,6 +372,20 @@ void twindow::layout()
DBG_G_L << "twindow " << __func__ << ": " << size << " screen size "
<< settings::screen_width << ',' << settings::screen_height << ".\n";
// Too wide and we can wrap, try that.
if(static_cast<size_t>(size.x) > settings::screen_width && can_wrap()) {
DBG_G_L << "twindow " << __func__ << ": start wrapping.\n";
if(set_width_constrain(settings::screen_width
- conf->left_border - conf->right_border)) {
size = get_best_size();
DBG_G_L << "twindow " << __func__
<< ": After wrapping : " << size << ".\n";
} else {
DBG_G_L << "twindow " << __func__ << ": wrapping failed.\n";
}
}
// If too big try it gracefully.
if(static_cast<size_t>(size.x) > settings::screen_width
|| static_cast<size_t>(size.y) > settings::screen_height) {
@ -424,6 +442,8 @@ void twindow::layout()
x_(variables), y_(variables), w_(variables), h_(variables)));
}
// Make sure the contrains are cleared, they might be partially set.
clear_width_constrain();
need_layout_ = false;
}