Refactoring the listbox.

Added the option to show or hide the scrollbar. Moved the sizing code
to the tvertical_scrollbar_container_ class which will query the listbox
to as it for the best size for the list.
This commit is contained in:
Mark de Wever 2008-09-13 08:24:07 +00:00
parent 2bf9f805c4
commit b721f865f2
8 changed files with 645 additions and 329 deletions

View file

@ -103,6 +103,7 @@
id = "method_list"
definition = "default"
vertical_scrollbar_mode = "never"
assume_fixed_row_size = "true"
[list_definition]

View file

@ -261,6 +261,17 @@ tline::tline(const config& cfg) :
* @* all alias for "left, right, top,
* bottom"
*
* scrollbar_mode How to show the scrollbar of a widget.
* Possible values:
* @* always The scrollbar is always shown,
* regardless whether it's required or not.
* @* never The scrollbar is never shown,
* even not when needed. (Note when setting
* this mode dialogs might not properly fit
* anymore).
* @* auto Shows the scrollbar when
* needed. The widget will reserve space for
* the scrollbar, but only show when needed.
* @end_table
*/

View file

@ -61,9 +61,7 @@ tlistbox::tlistbox() :
row_select_(true),
must_select_(true),
multi_select_(false),
list_rect_(),
list_background_(),
best_spacer_size_(0, 0),
rows_()
{
}
@ -127,7 +125,6 @@ void tlistbox::add_row(const std::map<std::string /* widget id */, std::map<
foreach(trow& row, rows_) {
height += row.get_height();
}
std::cerr << "Items " << height << ".\n";
find_scrollbar()->set_item_count(height);
}
@ -169,219 +166,6 @@ void tlistbox::mouse_left_button_down(tevent_handler& event)
event.keyboard_capture(this);
}
tpoint tlistbox::get_best_size(const tpoint& maximum_size) const
{
log_scope2(gui, "Listbox: Get best size");
tpoint best_size = get_best_size();
// We can only reduce our height so we ignore the x value.
// NOTE we might later be able to reduce our width as well, but that's
// something for later, also we don't ask our children for a better value.
if(best_size.y <= maximum_size.y) {
return best_size;
}
/**
* @todo At this point we should check whether maximum_size is larger as
* the minimum size for the widget (the scrollbar) and adjust accordingly.
*/
tpoint max = maximum_size;
if(assume_fixed_row_size_) {
// Only adjust the sizes if we have some rows
if(rows_.size() > 0) {
// The row might not have a size, since it might never been set
// so ask the best size.
const unsigned row_height = (*rows_[0].grid()).get_best_size().y;
best_size.y = (max.y / row_height) * row_height;
}
} else {
best_size.y = max.y;
}
DBG_G << "Grid : maximum size "
<< maximum_size << " returning " << best_size << ".\n";
return best_size;
}
tpoint tlistbox::get_best_size() const
{
// Set the size of the spacer to the wanted size for the list.
unsigned width = 0;
unsigned height = 0;
if(best_spacer_size_ != tpoint(0, 0)) {
// We got a best size set use that instead of calculating it.
height = best_spacer_size_.y;
width = best_spacer_size_.x;
} else {
// NOTE we should look at the number of visible items etc
foreach(const trow& row, rows_) {
assert(row.grid());
const tpoint best_size = row.grid()->get_best_size();
width = (static_cast<int>(width) >= best_size.x) ?
width : static_cast<int>(best_size.x);
height += static_cast<int>(best_size.y);
}
}
// Kind of a hack, we edit the spacer in a const function.
// Of course we could cache the list and make it mutable instead.
// But since the spacer is a kind of cache the const_cast doesn't
// look too ugly.
const_cast<tspacer*>(list())->set_best_size(tpoint(width, height));
// Now the container will return the wanted result.
return tcontainer_::get_best_size();
}
void tlistbox::draw(surface& surface, const bool force,
const bool invalidate_background)
{
// Inherited.
tcontainer_::draw(surface, force, invalidate_background);
if(invalidate_background) {
list_background_.assign(NULL);
}
// Handle our full redraw for the spacer area.
if(!list_background_) {
list_background_.assign(gui2::save_background(surface, list_rect_));
} else {
gui2::restore_background(list_background_, surface, list_rect_);
}
// Now paint the list over the spacer.
if(assume_fixed_row_size_) {
draw_list_area_fixed_row_height(surface, force, invalidate_background);
} else {
draw_list_area_variable_row_height(
surface, force, invalidate_background);
}
}
twidget* tlistbox::find_widget(const tpoint& coordinate, const bool must_be_active)
{
// Inherited
twidget* result = tcontainer_::find_widget(coordinate, must_be_active);
// if on the panel we need to do special things.
if(result && result->id() == "_list") {
int offset = 0;
const size_t row = row_at_offset(coordinate.y - list_rect_.y, offset);
if(row == static_cast<size_t>(-1)) {
return NULL;
}
assert(row < rows_.size());
assert(rows_[row].grid());
return rows_[row].grid()->find_widget(
tpoint(coordinate.x - list_rect_.x, offset), must_be_active);
}
return result;
}
const twidget* tlistbox::find_widget(
const tpoint& coordinate, const bool must_be_active) const
{
// Inherited
const twidget* result = tcontainer_::find_widget(coordinate, must_be_active);
// if on the panel we need to do special things.
if(result && result->id() == "_list") {
int offset = 0;
const size_t row = row_at_offset(coordinate.y - list_rect_.y, offset);
if(row == static_cast<size_t>(-1)) {
return NULL;
}
assert(row < rows_.size());
assert(rows_[row].grid());
return rows_[row].grid()->find_widget(
tpoint(coordinate.x - list_rect_.x, offset), must_be_active);
}
return result;
}
void tlistbox::set_size(const SDL_Rect& rect)
{
// Since we allow to be resized we need to determine the real size we get.
assert(best_spacer_size_ == tpoint(0, 0));
SDL_Rect best_rect = rect;
if(rows_.size()) {
const tpoint best_size = get_best_size();
if(best_size.y > rect.h) {
best_spacer_size_ = list()->get_best_size();
best_spacer_size_.y -= (best_size.y - rect.h);
if(assume_fixed_row_size_) {
const unsigned row_height = rows_[0].grid()->get_best_size().y;
const unsigned orig_height = best_spacer_size_.y;
best_spacer_size_.y =
(best_spacer_size_.y / row_height) * row_height;
best_rect.h -= (orig_height - best_spacer_size_.y);
}
// This call is required to update the best size.
get_best_size();
}
}
// Inherit.
tcontainer_::set_size(best_rect);
// Now set the items in the spacer.
tspacer* spacer = list();
list_rect_ = spacer->get_rect();
unsigned total_height = 0;
foreach(trow& row, rows_) {
assert(row.grid());
const unsigned height = row.grid()->get_best_size().y;
row.set_height(height);
row.grid()->set_size(::create_rect(0, 0, list_rect_.w, height));
row.canvas().assign(create_neutral_surface(list_rect_.w, height));
total_height += height;
}
if(!assume_fixed_row_size_) {
find_scrollbar()->set_item_count(total_height);
}
if(rows_.size() > 0) {
if(assume_fixed_row_size_) {
const unsigned row_height = rows_[0].get_height();
if(row_height) {
const unsigned rows = list()->get_best_size().y / row_height;
find_scrollbar()->set_visible_items(rows);
} else {
WRN_G << "Listbox row 0 has no height, making all rows visible.\n";
find_scrollbar()->set_visible_items(rows_.size());
}
} else {
find_scrollbar()->set_visible_items(best_spacer_size_.y);
}
} else {
find_scrollbar()->set_visible_items(1);
}
set_scrollbar_button_status();
// Clear for next run.
best_spacer_size_ = tpoint(0, 0);
}
bool tlistbox::select_row(const unsigned row, const bool select)
{
if(!select && must_select_ && selection_count_ < 2) {
@ -412,22 +196,6 @@ bool tlistbox::select_row(const unsigned row, const bool select)
return true;
}
tspacer* tlistbox::list()
{
tspacer* list =
dynamic_cast<tspacer*>(tcontainer_::find_widget("_list", false));
assert(list);
return list;
}
const tspacer* tlistbox::list() const
{
const tspacer* list =
dynamic_cast<const tspacer*>(tcontainer_::find_widget("_list", false));
assert(list);
return list;
}
bool tlistbox::list_row_selected(const size_t row, twidget* caller)
{
assert(rows_[row].grid());
@ -448,10 +216,22 @@ bool tlistbox::list_row_selected(const size_t row, twidget* caller)
return false;
}
tgrid* tlistbox::find_list(const bool must_exist)
{
tgrid* result = find_widget<tgrid>("_list", false, false);
return result ? result : find_content_grid(must_exist);
}
const tgrid* tlistbox::find_list(const bool must_exist) const
{
const tgrid* result = find_widget<const tgrid>("_list", false, false);
return result ? result : find_content_grid(must_exist);
}
void tlistbox::draw_list_area_fixed_row_height(surface& surface,
const bool force, const bool invalidate_background)
{
unsigned offset = list_rect_.y;
unsigned offset = find_list()->get_rect().y;
for(unsigned i = 0; i < find_scrollbar()->get_visible_items(); ++i) {
// make sure we stay inside the valid range.
@ -466,7 +246,8 @@ void tlistbox::draw_list_area_fixed_row_height(surface& surface,
// draw background
const SDL_Rect rect =
{list_rect_.x, offset, list_rect_.w, row.get_height() };
{find_list()->get_rect().x, offset,
find_list()->get_rect().w, row.get_height() };
// draw widget
blit_surface(row.canvas(), 0, surface, &rect);
@ -483,7 +264,7 @@ void tlistbox::draw_list_area_variable_row_height(surface& surface,
const unsigned start = find_scrollbar()->get_item_position();
const unsigned end = start + find_scrollbar()->get_visible_items();
unsigned offset = 0;
unsigned offset = find_list()->get_rect().y;
foreach(trow& row, rows_) {
const unsigned height = row.get_height();
@ -510,8 +291,8 @@ void tlistbox::draw_list_area_variable_row_height(surface& surface,
// Draw on the surface.
SDL_Rect rect = {
list_rect_.x,
offset + top_cut - start + list_rect_.y,
find_list()->get_rect().x,
offset + top_cut - start + find_list()->get_rect().y,
0,
0};
@ -645,5 +426,165 @@ void tlistbox::trow::select_in_grid(tgrid* grid, const bool selected)
}
}
} // namespace gui2
tpoint tlistbox::get_content_best_size(const tpoint& maximum_size) const
{
log_scope2(gui, "Listbox: Get best content size with max");
tpoint best_size = get_content_best_size();
// We can only reduce our height so we ignore the x value.
// NOTE we might later be able to reduce our width as well, but that's
// something for later, also we don't ask our children for a better value.
if(best_size.y <= maximum_size.y) {
return best_size;
}
tpoint max = maximum_size;
// Adjust for the size of the header and footer.
const tpoint base = find_content_grid()->get_best_size();
max.y -= base.y;
if(base.x > max.x) {
max.x = base.x;
}
if(assume_fixed_row_size_) {
// Only adjust the sizes if we have some rows
if(rows_.size() > 0) {
// The row might not have a size, since it might never been set
// so ask the best size.
const unsigned row_height = (*rows_[0].grid()).get_best_size().y;
best_size.y = (max.y / row_height) * row_height;
}
} else {
best_size.y = max.y;
}
DBG_G << "Grid : maximum size "
<< maximum_size << " returning " << best_size << ".\n";
return best_size;
}
tpoint tlistbox::get_content_best_size() const
{
log_scope2(gui, "Listbox: Get best content size");
// First determine the size wanted for the grid, which is used when header
// or footer are used.
const tpoint base = find_content_grid()->get_best_size();
unsigned width = base.x;
unsigned height = base.y;
// NOTE we should look at the number of visible items etc
foreach(const trow& row, rows_) {
assert(row.grid());
const tpoint best_size = row.grid()->get_best_size();
width = (static_cast<int>(width) >= best_size.x) ?
width : static_cast<int>(best_size.x);
height += static_cast<int>(best_size.y);
}
return tpoint(width, height);
}
void tlistbox::set_content_size(const SDL_Rect& rect)
{
unsigned total_height = 0;
foreach(trow& row, rows_) {
assert(row.grid());
const unsigned height = row.grid()->get_best_size().y;
row.set_height(height);
row.grid()->set_size(::create_rect(0, 0, rect.w, height));
row.canvas().assign(create_neutral_surface(rect.w, height));
total_height += height;
}
if(!assume_fixed_row_size_) {
find_scrollbar()->set_item_count(total_height);
}
if(rows_.size() > 0) {
if(assume_fixed_row_size_) {
const unsigned row_height = rows_[0].get_height();
if(row_height) {
const unsigned rows = rect.h / row_height;
find_scrollbar()->set_visible_items(rows);
} else {
WRN_G << "Listbox row 0 has no height, making all rows visible.\n";
find_scrollbar()->set_visible_items(rows_.size());
}
} else {
find_scrollbar()->set_visible_items(rect.h);
}
} else {
find_scrollbar()->set_visible_items(1);
}
}
void tlistbox::draw_content(surface& surface, const bool force,
const bool invalidate_background)
{
// Handle our full redraw for the spacer area.
if(!list_background_) {
list_background_.assign(
gui2::save_background(surface, find_content_grid()->get_rect()));
} else {
gui2::restore_background(
list_background_, surface, find_content_grid()->get_rect());
}
// draw header and footer.
find_content_grid()->draw(surface, force, invalidate_background);
// Now paint the list
if(assume_fixed_row_size_) {
draw_list_area_fixed_row_height(surface, force, invalidate_background);
} else {
draw_list_area_variable_row_height(
surface, force, invalidate_background);
}
}
twidget* tlistbox::find_content_widget(
const tpoint& coordinate, const bool must_be_active)
{
int offset = 0;
tpoint coord = coordinate;
coord.y -= find_list()->get_rect().y - find_content_grid()->get_rect().y;
const size_t row = row_at_offset(coord.y, offset);
if(row == static_cast<size_t>(-1)) {
return NULL;
}
assert(row < rows_.size());
assert(rows_[row].grid());
return rows_[row].grid()->find_widget(
tpoint(coordinate.x, offset), must_be_active);
}
const twidget* tlistbox::find_content_widget(
const tpoint& coordinate, const bool must_be_active) const
{
int offset = 0;
tpoint coord = coordinate;
coord.y -= find_list()->get_rect().y - find_content_grid()->get_rect().y;
const size_t row = row_at_offset(coord.y, offset);
if(row == static_cast<size_t>(-1)) {
return NULL;
}
assert(row < rows_.size());
assert(rows_[row].grid());
return rows_[row].grid()->find_widget(
tpoint(coordinate.x, offset), must_be_active);
}
} // namespace gui2

View file

@ -15,7 +15,7 @@
#ifndef GUI_WIDGETS_LISTBOX_HPP_INCLUDED
#define GUI_WIDGETS_LISTBOX_HPP_INCLUDED
#include "tstring.hpp" //NEEDED?
#include "tstring.hpp"
#include "gui/widgets/vertical_scrollbar_container.hpp"
namespace gui2 {
@ -27,6 +27,9 @@ class tspacer;
* - Header row + footer row same width as client data.
* - Cell or row select.
* - Sort at some way.
* - Test whether footers work properly.
* - More testing with listboxes with their own background.
* - client rect is also untested.
*
* Maybe create two types 1 fixed size and one with a builder to add new rows.
*/
@ -122,29 +125,6 @@ public:
/** Inherited from tevent_executor. */
void mouse_left_button_down(tevent_handler& event);
/** Inherited from twidget. */
tpoint get_best_size(const tpoint& maximum_size) const;
/** Inherited from tcontainer_. */
tpoint get_best_size() const;
/** Inherited from tcontainer_. */
void draw(surface& surface, const bool force = false,
const bool invalidate_background = false);
/** Inherited from tcontainer_. */
twidget* find_widget(const tpoint& coordinate, const bool must_be_active);
/** Inherited from tcontainer_. */
const twidget* find_widget(const tpoint& coordinate,
const bool must_be_active) const;
/** Import overloaded versions. */
using tvertical_scrollbar_container_::find_widget;
/** Inherited from tcontainer_. */
void set_size(const SDL_Rect& rect);
/** Inherited from tcontainer_. */
void set_self_active(const bool active)
{ state_ = active ? ENABLED : DISABLED; }
@ -192,12 +172,6 @@ private:
*/
tbuilder_grid_ptr list_builder_;
/** Returns the spacer widget which is used to reserve space of the real list. */
tspacer* list();
/** Returns the spacer widget which is used to reserve space of the real list. */
const tspacer* list() const;
/**
* Does every row in the listbox have the same height?
*
@ -237,16 +211,38 @@ private:
/** Multiple items can be selected. */
bool multi_select_;
/** The sizes of the spacer. */
SDL_Rect list_rect_;
/** The background of the list, needed for redrawing. */
surface list_background_;
/** The best size for the spacer, if not set it's calculated. */
tpoint best_spacer_size_;
/**
* The content grid of a list might contain another grid name _list. This
* grid must exist if there is a header or footer. This grid marks the
* space for the real scrollable area.
*/
/**
* @todo evaluate whether the value of the grid needs to be cached as well
* as it's size. It would be save since we get notified about a resize.
*/
/**
* Returns the list area.
*
* If the listbox has no _list grid the _content_grid grid will be returned
* instead.
*
* @param must_exist If true the grid must exist and the
* function will fail if that's not the case. If
* true the pointer returned is always valid.
*
* @returns A pointer to the grid or NULL.
*/
tgrid* find_list(const bool must_exist = true);
/** The const version. */
const tgrid* find_list(const bool must_exist = true) const;
/**
* Draws the list area if assume_fixed_row_size_ is true.
*
@ -345,9 +341,32 @@ private:
/** The rows in the listbox. */
std::vector<trow> rows_;
/***** ***** ***** inherited ****** *****/
/** Inherited from tcontrol. */
const std::string& get_control_type() const
{ static const std::string type = "listbox"; return type; }
/** Inherited from tvertical_scrollbar_container_. */
tpoint get_content_best_size(const tpoint& maximum_size) const;
/** Inherited from tvertical_scrollbar_container_. */
tpoint get_content_best_size() const;
/** Inherited from tvertical_scrollbar_container_. */
void set_content_size(const SDL_Rect& rect);
/** Inherited from tvertical_scrollbar_container_. */
void draw_content(surface& surface, const bool force = false,
const bool invalidate_background = false);
/** Inherited from tvertical_scrollbar_container_. */
twidget* find_content_widget(
const tpoint& coordinate, const bool must_be_active);
/** Inherited from tvertical_scrollbar_container_. */
const twidget* find_content_widget(const tpoint& coordinate,
const bool must_be_active) const;
};
} // namespace gui2

View file

@ -17,6 +17,7 @@
#include "foreach.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/scrollbar.hpp"
#include "gui/widgets/widget.hpp"
#include "gui/widgets/window.hpp"
#include "log.hpp"
@ -85,6 +86,29 @@ const std::map<std::string, tscrollbar_::tscroll>& scroll_lookup()
} // namespace
tvertical_scrollbar_container_::
tvertical_scrollbar_container_(const unsigned canvas_count)
: tcontainer_(canvas_count)
, scrollbar_mode_(SHOW_WHEN_NEEDED)
, scrollbar_grid_(NULL)
, callback_value_change_(NULL)
{
}
tvertical_scrollbar_container_::~tvertical_scrollbar_container_()
{
delete scrollbar_grid_;
}
void tvertical_scrollbar_container_::
set_scrollbar_mode(const tscrollbar_mode scrollbar_mode)
{
if(scrollbar_mode_ != scrollbar_mode) {
scrollbar_mode_ = scrollbar_mode;
show_scrollbar(scrollbar_mode_ != HIDE);
}
}
void tvertical_scrollbar_container_::key_press(tevent_handler& /*event*/,
bool& handled, SDLKey key, SDLMod /*modifier*/, Uint16 /*unicode*/)
{
@ -129,13 +153,16 @@ void tvertical_scrollbar_container_::key_press(tevent_handler& /*event*/,
case SDLK_DOWN :
++row;
while(static_cast<size_t>(row) < sb->get_item_count() && !get_item_active(row)) {
while(static_cast<size_t>(row) < sb->get_item_count()
&& !get_item_active(row)) {
++row;
}
if(static_cast<size_t>(row) < sb->get_item_count()) {
select_row(row);
handled = true;
if(static_cast<size_t>(row) >= sb->get_item_position() + sb->get_visible_items()) {
if(static_cast<size_t>(row) >= sb->get_item_position()
+ sb->get_visible_items()) {
sb->set_item_position(row + 1 - sb->get_visible_items());
set_scrollbar_button_status();
@ -150,6 +177,129 @@ void tvertical_scrollbar_container_::key_press(tevent_handler& /*event*/,
}
}
tpoint tvertical_scrollbar_container_::get_best_size() const
{
const tpoint content = get_content_best_size();
if(scrollbar_mode_ == HIDE) {
return content;
}
const tpoint scrollbar = find_scrollbar_grid()->get_best_size();
if(scrollbar_mode_ == SHOW) {
// We need to show the scrollbar so the biggest height of scrollbar and
// content is needed. The width is the sum of them.
return tpoint(
content.x + scrollbar.x,
std::max(content.y, scrollbar.y));
}
// When auto show the height of the content is leading. (Width again the sum.)
return tpoint(content.x + scrollbar.x, content.y);
}
tpoint tvertical_scrollbar_container_::get_best_size(const tpoint& maximum_size) const
{
if(scrollbar_mode_ == HIDE) {
// No scrollbar hope the normal size is small enough. Don't send the
// maximum_size parameter since then the content 'thinks' there will be
// a scrollbar.
return get_content_best_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));
// Width and height same rules as above.
if(scrollbar_mode_ == SHOW) {
return tpoint(
content.x + scrollbar.x,
std::max(content.y, scrollbar.y));
}
return tpoint(content.x + scrollbar.x, content.y);
}
}
void tvertical_scrollbar_container_::set_size(const SDL_Rect& rect)
{
// Inherited. -- note we don't use client size, might change
tcontrol::set_size(rect);
// Test whether we need a scrollbar.
bool scrollbar_needed = (scrollbar_mode_ == SHOW);
if(scrollbar_mode_ == SHOW_WHEN_NEEDED) {
tpoint size = get_content_best_size();
scrollbar_needed = size.x > rect.w || size.y > rect.h;
}
if(scrollbar_needed) {
show_scrollbar(true);
const tpoint scrollbar = find_scrollbar_grid()->get_best_size();
SDL_Rect tmp = rect;
tmp.x += tmp.w - scrollbar.x;
tmp.w = scrollbar.x;
find_scrollbar_grid()->set_size(tmp);
tmp = rect;
tmp.w -= scrollbar.x;
find_content_grid()->set_size(tmp);
set_content_size(tmp);
} else {
show_scrollbar(false);
find_content_grid()->set_size(rect);
set_content_size(rect);
}
set_scrollbar_button_status();
}
void tvertical_scrollbar_container_::draw(
surface& surface, const bool force, const bool invalidate_background)
{
// Inherited.
tcontainer_::draw(surface, force, invalidate_background);
if(scrollbar_mode_ != HIDE) {
draw_content(surface, force, invalidate_background);
}
draw_content(surface, force, invalidate_background);
}
twidget* tvertical_scrollbar_container_::find_widget(
const tpoint& coordinate, const bool must_be_active)
{
SDL_Rect content = find_content_grid()->get_rect();
if(point_in_rect(coordinate.x, coordinate.y, content)) {
return find_content_widget(tpoint(
coordinate.x - get_x(), coordinate.y - get_y())
, must_be_active);
}
// Inherited
return tcontainer_::find_widget(coordinate, must_be_active);
}
const twidget* tvertical_scrollbar_container_::find_widget(
const tpoint& coordinate, const bool must_be_active) const
{
SDL_Rect content = find_content_grid()->get_rect();
if(point_in_rect(coordinate.x, coordinate.y, content)) {
return find_content_widget(tpoint(
coordinate.x - get_x(), coordinate.y - get_y())
, must_be_active);
}
// Inherited
return tcontainer_::find_widget(coordinate, must_be_active);
}
void tvertical_scrollbar_container_::value_changed()
{
if(callback_value_change_) {
@ -157,15 +307,41 @@ void tvertical_scrollbar_container_::value_changed()
}
}
tgrid* tvertical_scrollbar_container_::find_scrollbar_grid(const bool must_exist)
{
return scrollbar_grid_ ? scrollbar_grid_
: find_widget<tgrid>("_scrollbar_grid", false, must_exist);
}
const tgrid* tvertical_scrollbar_container_::
find_scrollbar_grid(const bool must_exist) const
{
return scrollbar_grid_ ? scrollbar_grid_
: find_widget<tgrid>("_scrollbar_grid", false, must_exist);
}
tscrollbar_* tvertical_scrollbar_container_::find_scrollbar(const bool must_exist)
{
return find_widget<tscrollbar_>("_scrollbar", false, must_exist);
return static_cast<twidget*>(find_scrollbar_grid())
->find_widget<tscrollbar_>("_scrollbar", false, must_exist);
}
const tscrollbar_* tvertical_scrollbar_container_::find_scrollbar(
const bool must_exist) const
{
return find_widget<const tscrollbar_>("_scrollbar", false, must_exist);
return static_cast<const twidget*>(find_scrollbar_grid())
->find_widget<const tscrollbar_>("_scrollbar", false, must_exist);
}
tgrid* tvertical_scrollbar_container_::find_content_grid(const bool must_exist)
{
return find_widget<tgrid>("_content_grid", false, must_exist);
}
const tgrid* tvertical_scrollbar_container_::
find_content_grid(const bool must_exist) const
{
return find_widget<tgrid>("_content_grid", false, must_exist);
}
void tvertical_scrollbar_container_::set_scrollbar_button_status()
@ -195,6 +371,24 @@ void tvertical_scrollbar_container_::set_scrollbar_button_status()
!(find_scrollbar()->at_begin() && find_scrollbar()->at_end()));
}
void tvertical_scrollbar_container_::show_scrollbar(const bool show)
{
if(show && scrollbar_grid_) {
tgrid* tmp = dynamic_cast<tgrid*>
(grid().swap_child("_scrollbar_grid", scrollbar_grid_, true));
delete tmp;
scrollbar_grid_ = NULL;
} else if(!show && !scrollbar_grid_) {
tgrid* tmp = new tgrid();
scrollbar_grid_ = dynamic_cast<tgrid*>
(grid().swap_child("_scrollbar_grid", tmp, true));
}
}
void tvertical_scrollbar_container_::finalize_setup()
{
find_scrollbar()->set_callback_positioner_move(callback_scrollbar);
@ -210,6 +404,10 @@ void tvertical_scrollbar_container_::finalize_setup()
button->set_callback_mouse_left_click(callback_scrollbar_button);
}
}
// Make sure all mandatory widgets are tested
find_scrollbar_grid();
find_content_grid();
}
void tvertical_scrollbar_container_::scrollbar_click(twidget* caller)
@ -230,5 +428,6 @@ unsigned tvertical_scrollbar_container_::get_selected_row() const
{
return find_scrollbar()->get_item_position();
}
} // namespace gui2

View file

@ -21,8 +21,6 @@ namespace gui2 {
class tscrollbar_;
/** Base class for creating containers with a vertical scrollbar. */
class tvertical_scrollbar_container_ : public tcontainer_
{
@ -30,26 +28,35 @@ class tvertical_scrollbar_container_ : public tcontainer_
friend class tbuilder_listbox;
// Callbacks can call update rountines. Note these are not further declared
// here only need extrnal linkage to be friends.
// Callbacks can call update routines. Note these are not further declared
// here only need external linkage to be friends.
friend void callback_scrollbar_button(twidget*);
friend void callback_scrollbar(twidget*);
public:
tvertical_scrollbar_container_(const unsigned canvas_count)
: tcontainer_(canvas_count),
callback_value_change_(NULL)
{
}
tvertical_scrollbar_container_(const unsigned canvas_count);
/***** ***** ***** inherited ****** *****/
~tvertical_scrollbar_container_();
/** Inherited from tevent_executor. */
void key_press(tevent_handler& event, bool& handled,
SDLKey key, SDLMod modifier, Uint16 unicode);
/** Inherited from twidget. */
bool has_vertical_scrollbar() const { return true; }
/** The way to handle the showing or hiding of the scrollbar. */
enum tscrollbar_mode {
SHOW, /**<
* The scrollbar is always shown, whether
* needed or not.
*/
HIDE, /**<
* The scrollbar is never shown even not when
* needed. There's also no space reserved for
* the scrollbar.
*/
SHOW_WHEN_NEEDED /**<
* The scrollbar is shown when the number of
* items is larger as the visible items. The
* space for the scrollbar is always
* reserved, just in case it's needed after
* the initial sizing (due to adding items).
*/
};
/**
* Selects an entire row.
@ -62,10 +69,46 @@ public:
*/
virtual bool select_row(const unsigned row, const bool select = true) = 0;
/***** ***** ***** inherited ****** *****/
/** Inherited from tevent_executor. */
void key_press(tevent_handler& event, bool& handled,
SDLKey key, SDLMod modifier, Uint16 unicode);
/** Inherited from twidget. */
bool has_vertical_scrollbar() const { return true; }
/** Inherited from tcontainer. */
tpoint get_best_size() const;
/** Inherited from tcontainer. */
tpoint get_best_size(const tpoint& maximum_size) const;
/** Inherited from tcontainer. */
void draw(surface& surface, const bool force = false,
const bool invalidate_background = false);
/** Inherited from tcontainer. */
void set_size(const SDL_Rect& rect);
/** Inherited from tcontainer_. */
twidget* find_widget(const tpoint& coordinate, const bool must_be_active);
/** Inherited from tcontainer_. */
const twidget* find_widget(const tpoint& coordinate,
const bool must_be_active) const;
/** Import overloaded versions. */
using tcontainer_::find_widget;
/***** ***** ***** setters / getters for members ***** ****** *****/
void set_callback_value_change(void (*callback) (twidget* caller))
{ callback_value_change_ = callback; }
void set_scrollbar_mode(const tscrollbar_mode scrollbar_mode);
tscrollbar_mode get_scrollbar_mode() const { return scrollbar_mode_; }
protected:
/**
@ -74,22 +117,42 @@ protected:
*/
void value_changed();
/**
* The widget contains named widgets:
* - _scrollbar_grid a grid containing all items regarding the scrollbar and
* associated buttons etc.
* - _scrollbar a scrollbar itself.
* - _content_grid a grid containing the widgets in the container.
* Subclasses may define extra named widgets in this container for their
* own purposes.
*/
/**
* Returns the scroll widget.
* Returns the scrollbar grid.
*
* This always returns the wdiget, regardless of the mode.
* This always returns the grid, regardless of the mode (active or not).
*
* @param must_exist If true the widget must exist and the
* @param must_exist If true the grid must exist and the
* function will fail if that's not the case. If
* true the pointer returned is always valid.
*
* @returns A pointer to the widget or NULL.
* @returns A pointer to the grid or NULL.
*/
tscrollbar_* find_scrollbar(const bool must_exist = true);
tgrid* find_scrollbar_grid(const bool must_exist = true);
/** The const version. */
const tscrollbar_* find_scrollbar(const bool must_exist = true) const;
const tgrid* find_scrollbar_grid(const bool must_exist = true) const;
/** See find_scrollbar_grid, but returns the scrollbar instead. */
tscrollbar_* find_scrollbar(const bool must_exist = true);
/** The const version. */
const tscrollbar_* find_scrollbar(const bool must_exist = true) const;
/** See find_scrollbar_grid, but returns the content grid instead. */
tgrid* find_content_grid(const bool must_exist = true);
const tgrid* find_content_grid(const bool must_exist = true) const;
/**
* Sets the status of the scrollbar buttons.
@ -101,9 +164,21 @@ protected:
private:
/**
* The mode of how to show the scrollbar.
*
* This value should only be modified before showing, doing it while
* showing results in UB.
*/
tscrollbar_mode scrollbar_mode_;
tgrid* scrollbar_grid_;
void show_scrollbar(const bool show);
/**
* This callback is used when the selection is changed due to a user event.
* The name is not fully appropriate for the event but it's choosen to be
* The name is not fully appropriate for the event but it's chosen to be
* generic.
*/
void (*callback_value_change_) (twidget* caller);
@ -132,10 +207,54 @@ private:
*/
virtual bool get_item_active(const unsigned /*item*/) const { return true; }
/** Returns the selected row. */
virtual unsigned get_selected_row() const;
/***** ***** pure virtuals for the subclasses ****** *****/
/**
* Returns the best size for the content part.
*
* See get_best_size() for more info.
*/
virtual tpoint get_content_best_size() const = 0;
/**
* Returns the best size for the content part.
*
* See get_best_size(cont tpoint&) for more info.
*/
virtual tpoint get_content_best_size(const tpoint& maximum_size) const = 0;
/**
* Sets the size for the content.
*
* This is a notification after the size of the content grid has been set
* so the function only needs to update its state if applicable.
*
* @param rect The new size of the content grid.
*/
virtual void set_content_size(const SDL_Rect& rect) = 0;
/**
* Draws the content part of the widget.
*
* See draw_content for more info.
*/
virtual void draw_content(surface& surface, const bool force,
const bool invalidate_background) = 0;
/**
* Finds a widget in the content area.
*
* See find_content_widget for more info.
*/
virtual twidget* find_content_widget(
const tpoint& coordinate, const bool must_be_active) = 0;
/** The const version. */
virtual const twidget* find_content_widget(
const tpoint& coordinate, const bool must_be_active) const = 0;
};
} // namespace gui2

View file

@ -60,6 +60,8 @@ static unsigned get_v_align(const std::string& v_align);
static unsigned get_h_align(const std::string& h_align);
static unsigned get_border(const std::vector<std::string>& border);
static unsigned read_flags(const config& cfg);
static tvertical_scrollbar_container_::tscrollbar_mode
get_scrollbar_mode(const std::string& scrollbar_mode);
twindow build(CVideo& video, const std::string& type)
{
@ -221,7 +223,6 @@ twindow_builder::tresolution::tresolution(const config& cfg) :
static unsigned get_v_align(const std::string& v_align)
{
if(v_align == "top") {
return tgrid::VERTICAL_ALIGN_TOP;
} else if(v_align == "bottom") {
@ -616,8 +617,25 @@ twidget* tbuilder_label::build() const
return tmp_label;
}
static tvertical_scrollbar_container_::tscrollbar_mode
get_scrollbar_mode(const std::string& scrollbar_mode)
{
if(scrollbar_mode == "always") {
return tvertical_scrollbar_container_::SHOW;
} else if(scrollbar_mode == "never") {
return tvertical_scrollbar_container_::HIDE;
} else {
if(!scrollbar_mode.empty() && scrollbar_mode != "auto") {
ERR_G_E << "Invalid scrollbar mode '"
<< scrollbar_mode << "' falling back to 'auto'.\n";
}
return tvertical_scrollbar_container_::SHOW_WHEN_NEEDED;
}
}
tbuilder_listbox::tbuilder_listbox(const config& cfg) :
tbuilder_control(cfg),
scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"])),
header(cfg.child("header") ? new tbuilder_grid(*(cfg.child("header"))) : 0),
footer(cfg.child("footer") ? new tbuilder_grid(*(cfg.child("footer"))) : 0),
list_builder(0),
@ -634,16 +652,10 @@ tbuilder_listbox::tbuilder_listbox(const config& cfg) :
*
* List with the listbox specific variables:
* @start_table = config
* scrollbar_mode (foo) The mode for the scrollbar
* @* always_show the srollbar is always
* shown even if all items can be shown. The
* scrollbar will be disabled in this case.
* @* auto_show the scrollbar is shown if
* more items are in the listbox as can be
* shown.
* @* never_show the scrollbar is
* never shown even not if more items are
* available as visible.
* vertical_scrollbar_mode (scrollbar_mode = auto)
* Determines whether or not to show the
* scrollbar.
*
* header (section = []) Defines the grid for the optional header.
* footer (section = []) Defines the grid for the optional footer.
*
@ -713,6 +725,7 @@ twidget* tbuilder_listbox::build() const
listbox->set_list_builder(list_builder);
listbox->set_assume_fixed_row_size(assume_fixed_row_size);
listbox->set_scrollbar_mode(scrollbar_mode);
DBG_G << "Window builder: placed listbox '" << id << "' with defintion '"
<< definition << "'.\n";
@ -721,21 +734,29 @@ twidget* tbuilder_listbox::build() const
boost::dynamic_pointer_cast<const tlistbox_definition::tresolution>(listbox->config());
assert(conf);
/*
* We generate the following items to put in the listbox grid
* - _scrollbar_grid the grid containing the scrollbar.
* - _content_grid the grid containing the content of the listbox.
* - _list if the content has a header of footer they're an extra
* grid needed to find the scrolling content, the item
* with the id _list holds this, so the listbox needs to
* test for this item as well.
*/
tgrid* scrollbar = dynamic_cast<tgrid*>(conf->scrollbar->build());
assert(scrollbar);
scrollbar->set_id("_scroll");
scrollbar->set_id("_scrollbar_grid");
twidget* list_area = new tspacer();
assert(list_area);
list_area->set_definition("default");
list_area->set_id("_list");
tgrid* content_grid = new tgrid();
content_grid->set_definition("default");
content_grid->set_id("_content_grid");
assert(content_grid);
if(header || footer) {
// Create a grid to hold header (if needed), list and footer (if needed).
tgrid* grid = new tgrid();
grid->set_rows_cols(header && footer ? 3 : 2, 1);
content_grid->set_rows_cols(header && footer ? 3 : 2, 1);
// Create and add the header.
if(header) {
@ -748,7 +769,7 @@ twidget* tbuilder_listbox::build() const
* We need sort indicators, which are tristat_buttons;
* none, acending, decending. Once we have them we can write them in.
*/
grid->set_child(widget, 0, 0, tgrid::HORIZONTAL_GROW_SEND_TO_CLIENT
content_grid->set_child(widget, 0, 0, tgrid::HORIZONTAL_GROW_SEND_TO_CLIENT
| tgrid::VERTICAL_ALIGN_TOP, 0);
}
@ -757,24 +778,26 @@ twidget* tbuilder_listbox::build() const
twidget* widget = footer->build();
assert(widget);
grid->set_child(widget, header && footer ? 2 : 1, 0,
content_grid->set_child(widget, header && footer ? 2 : 1, 0,
tgrid::HORIZONTAL_GROW_SEND_TO_CLIENT
| tgrid::VERTICAL_ALIGN_BOTTOM, 0);
}
// Add the list itself.
grid->set_child(list_area, header ? 1 : 0, 0,
// Add the list itself which needs a new grid as described above.
tgrid* list = new tgrid();
assert(list);
list->set_definition("default");
list->set_id("_list");
content_grid->set_child(list, header ? 1 : 0, 0,
tgrid::HORIZONTAL_GROW_SEND_TO_CLIENT
| tgrid::VERTICAL_GROW_SEND_TO_CLIENT
, 0);
// Now make the list_area the grid so the code with and without a header
// or footer is the same.
list_area = grid;
content_grid->set_row_grow_factor( header ? 1 : 0, 1);
}
listbox->grid().set_rows_cols(1, 2);
listbox->grid().set_child(list_area, 0, 0,
listbox->grid().set_child(content_grid, 0, 0,
tgrid::VERTICAL_GROW_SEND_TO_CLIENT
| tgrid::HORIZONTAL_GROW_SEND_TO_CLIENT
, 0);

View file

@ -25,6 +25,7 @@
#include "config.hpp"
#include "gui/widgets/menubar.hpp"
#include "gui/widgets/vertical_scrollbar_container.hpp"
namespace gui2 {
@ -116,6 +117,8 @@ public:
twidget* build () const;
tvertical_scrollbar_container_::tscrollbar_mode scrollbar_mode;
tbuilder_grid_ptr header;
tbuilder_grid_ptr footer;