Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue 1199 sitrep list lacks scroll bar for last item. #1356

33 changes: 30 additions & 3 deletions GG/GG/ListBox.h
Expand Up @@ -544,16 +544,19 @@ class GG_API ListBox : public Control
Row& ColHeaders(); ///< returns the row containing the headings for the columns, if any. If undefined, the returned heading Row will have size() 0. non-const for derivers
//@}

void AdjustScrolls(bool adjust_for_resize); ///< creates, destroys, or resizes scrolls to reflect size of data in listbox
/** creates, destroys, or resizes scrolls to reflect size of data in listbox. \p force_scroll
forces the scroll bar to be added.*/
void AdjustScrolls(bool adjust_for_resize, const std::pair<bool, bool>& force_scrolls = {false, false});

void DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
const Pt& pt, Flags<ModKey> mod_keys) const override;
void HandleRowRightClicked(const Pt& pt, Flags<ModKey> mod);

private:
/** Show only rows that are within the visible list box area and hide all others. If
\p do_prerender is true then prerender the visible rows.*/
void ShowVisibleRows(bool do_prerender);
\p do_prerender is true then prerender the visible rows. Return true if prerender
resulted in any visible row changing size. */
bool ShowVisibleRows(bool do_prerender);
void ConnectSignals();
void ValidateStyle(); ///< reconciles inconsistencies in the style flags
void VScrolled(int tab_low, int tab_high, int low, int high);///< signals from the vertical scroll bar are caught here
Expand All @@ -569,6 +572,30 @@ class GG_API ListBox : public Control
/** Restore cached selected, clicked and last browsed rows.*/
void RestoreCachedSelections(const SelectionCache& cache);

/** Return the client size excluding the scroll bar sizes, in order to determine if scroll bars
are needed. This is a private function that is a component of AdjustScrolls.*/
Pt ClientSizeExcludingScrolls() const;

/** Return a pair of optional X and/or Y dimensions of the scollable area iff vscroll and/or
hscroll are required. If scrollbars are needed, the scrollable extent will be larger than the
client size. If a scrollbar is not required in some dimension return boost::none
for that dimension. \p maybe_client_size might contain a precalculated client size.

This is a private function that is a component of AdjustScrolls. */
std::pair<boost::optional<X>, boost::optional<Y>>
CheckIfScrollsRequired(const std::pair<bool, bool>& force_scrolls = {false, false},
const boost::optional<Pt>& maybe_client_size = boost::none) const;

/** Add vscroll and/or hscroll if \p required_total_extents the x andor y dimension exists. The
value of \p required_total_extents is the full x and y dimensions of the underlying ListBox
requiring the scrollbar as calculated in CheckIfScrollsRequired. Return a pair of bools
indicating if vscroll and/or hscroll was added and/or removed. \p maybe_client_size might
contain a precalculated client size as calculated in ClientSizeExcludingScrolls.

This is a private function that is a component of AdjustScrolls. */
std::pair<bool, bool> AddOrRemoveScrolls(const std::pair<boost::optional<X>, boost::optional<Y>>& required_total_extents,
const boost::optional<Pt>& maybe_client_size = boost::none);

std::list<Row*> m_rows; ///< line item data

Scroll* m_vscroll; ///< vertical scroll bar on right
Expand Down
157 changes: 116 additions & 41 deletions GG/src/ListBox.cpp
Expand Up @@ -853,20 +853,40 @@ void ListBox::PreRender()
NormalizeRow(row);
}

AdjustScrolls(false);
// Adding/removing scrolls and prerendering rows may change the row sizes and require a change
// in added/removed scrolls. This may not be stable. Perform two cycles and if it is not
// stable then force the scrollbar to be added if either cycle had a scroll bar.

// Reset require prerender after call to adjust scrolls
Control::PreRender();
// Perform a cycle of adjust scrolls and prerendering rows and return if sizes changed.
auto check_adjust_scroll_size_change = [this](std::pair<bool, bool> force_scrolls = {false, false}) {
// This adjust scrolls may add or remove scrolls
AdjustScrolls(true);

// Resize rows to fit client area.
X row_width(std::max(ClientWidth(), X(1)));
for (Row* row : m_rows)
row->Resize(Pt(row_width, row->Height()));
bool visible_row_size_change = ShowVisibleRows(true);

ShowVisibleRows(true);
bool header_size_change = false;
if (!m_header_row->empty()) {
auto old_size = m_header_row->Size();
GUI::PreRenderWindow(m_header_row);
header_size_change |= (old_size != m_header_row->Size());
}
return visible_row_size_change | header_size_change;
};

// Try adjusting scroll twice and then force the scrolls on.
if (check_adjust_scroll_size_change()) {
bool any_vscroll = (m_vscroll != nullptr);
bool any_hscroll = (m_hscroll != nullptr);

if (check_adjust_scroll_size_change()) {
any_vscroll |= (m_vscroll != nullptr);
any_hscroll |= (m_hscroll != nullptr);
check_adjust_scroll_size_change({any_hscroll, any_vscroll});
}
}

if (!m_header_row->empty())
GUI::PreRenderWindow(m_header_row);
// Reset require prerender after call to adjust scrolls
Control::PreRender();

// Position rows
Pt pt(m_first_row_offset);
Expand Down Expand Up @@ -946,8 +966,9 @@ void ListBox::SizeMove(const Pt& ul, const Pt& lr)
RequirePreRender();
}

void ListBox::ShowVisibleRows(bool do_prerender)
bool ListBox::ShowVisibleRows(bool do_prerender)
{
bool a_row_size_changed = false;
// Ensure that data in occluded cells is not rendered
// and that any re-layout during prerender is immediate.
Y visible_height(BORDER_THICK);
Expand All @@ -961,15 +982,19 @@ void ListBox::ShowVisibleRows(bool do_prerender)
(*it)->Hide();
} else {
(*it)->Show();
if (do_prerender)
if (do_prerender) {
auto old_size = (*it)->Size();
GUI::PreRenderWindow(*it);
a_row_size_changed |= (old_size != (*it)->Size());
}

visible_height += (*it)->Height();
if (visible_height >= max_visible_height)
hide = true;
}
}

return a_row_size_changed;
}

void ListBox::Show(bool show_children /* = true*/)
Expand Down Expand Up @@ -2122,26 +2147,39 @@ void ListBox::ValidateStyle()
m_style &= ~(LIST_NOSEL | LIST_SINGLESEL | LIST_QUICKSEL);
}

void ListBox::AdjustScrolls(bool adjust_for_resize)
Pt ListBox::ClientSizeExcludingScrolls() const
{
// this client area calculation disregards the thickness of scrolls
Pt cl_sz = (LowerRight() - Pt(X(BORDER_THICK), Y(BORDER_THICK))) -
(UpperLeft() + Pt(X(BORDER_THICK), static_cast<int>(BORDER_THICK)
+ (m_header_row->empty()
? Y0
: m_header_row->Height())));
// This client area calculation is used to determine if scroll should/should not be added, so
// it does not include the thickness of scrolls.
Pt cl_sz = (LowerRight()
- Pt(X(BORDER_THICK), Y(BORDER_THICK))
- UpperLeft()
- Pt(X(BORDER_THICK),
static_cast<int>(BORDER_THICK)
+ (m_header_row->empty() ? Y0 : m_header_row->Height())));
return cl_sz;
}

std::pair<boost::optional<X>, boost::optional<Y>> ListBox::CheckIfScrollsRequired(
const std::pair<bool, bool>& force_scrolls,
const boost::optional<Pt>& maybe_client_size) const
{
// Use the precalculated client size if possible.
auto cl_sz = maybe_client_size ? *maybe_client_size : ClientSizeExcludingScrolls();

X total_x_extent = std::accumulate(m_col_widths.begin(), m_col_widths.end(), X0);
Y total_y_extent(0);
for (Row* row : m_rows)
total_y_extent += row->Height();

bool vertical_needed =
force_scrolls.second ||
m_first_row_shown != m_rows.begin() ||
(m_rows.size() && (cl_sz.y < total_y_extent ||
(cl_sz.y < total_y_extent - SCROLL_WIDTH &&
cl_sz.x < total_x_extent - SCROLL_WIDTH)));
bool horizontal_needed =
force_scrolls.first ||
m_first_col_shown ||
(m_rows.size() && (cl_sz.x < total_x_extent ||
(cl_sz.x < total_x_extent - SCROLL_WIDTH &&
Expand All @@ -2161,15 +2199,31 @@ void ListBox::AdjustScrolls(bool adjust_for_resize)
total_y_extent += cl_sz.y - m_rows.back()->Height();
}

std::shared_ptr<StyleFactory> style = GetStyleFactory();
boost::optional<X> x_retval = horizontal_needed ? boost::optional<X>(total_x_extent) : boost::none;
boost::optional<Y> y_retval = vertical_needed ? boost::optional<Y>(total_y_extent) : boost::none;

return {x_retval, y_retval};
}

std::pair<bool, bool> ListBox::AddOrRemoveScrolls(
const std::pair<boost::optional<X>, boost::optional<Y>>& required_total_extents,
const boost::optional<Pt>& maybe_client_size)
{
// Use the precalculated client size if possible.
auto cl_sz = maybe_client_size ? *maybe_client_size : ClientSizeExcludingScrolls();

bool vscroll_added_or_removed(false);
const std::shared_ptr<const StyleFactory> style = GetStyleFactory();

bool horizontal_needed = (required_total_extents.first ? true : false);
bool vertical_needed = (required_total_extents.second ? true : false);

bool vscroll_added_or_removed = false;

// Remove unecessary vscroll
if (m_vscroll && !vertical_needed) {
vscroll_added_or_removed = true;
DeleteChild(m_vscroll);
m_vscroll = nullptr;
vscroll_added_or_removed = true;
}

// Add necessary vscroll
Expand All @@ -2185,14 +2239,6 @@ void ListBox::AdjustScrolls(bool adjust_for_resize)
}

if (vertical_needed) {
if (adjust_for_resize) {
X scroll_x = cl_sz.x - SCROLL_WIDTH;
Y scroll_y(0);
m_vscroll->SizeMove(Pt(scroll_x, scroll_y),
Pt(scroll_x + SCROLL_WIDTH,
scroll_y + cl_sz.y - (horizontal_needed ? SCROLL_WIDTH : 0)));
}

unsigned int line_size = m_vscroll_wheel_scroll_increment;
if (line_size == 0 && !this->Empty()) {
const Row* row = *begin();
Expand All @@ -2201,20 +2247,24 @@ void ListBox::AdjustScrolls(bool adjust_for_resize)

unsigned int page_size = std::abs(Value(cl_sz.y - (horizontal_needed ? SCROLL_WIDTH : 0)));

m_vscroll->SizeScroll(0, Value(total_y_extent - 1),
m_vscroll->SizeScroll(0, Value(*required_total_extents.second - 1),
line_size, std::max(line_size, page_size));

MoveChildUp(m_vscroll);
}

bool hscroll_added_or_removed = false;

// Remove unecessary hscroll
if (m_hscroll && !horizontal_needed) {
hscroll_added_or_removed = true;
DeleteChild(m_hscroll);
m_hscroll = nullptr;
}

// Add necessary hscroll
if (!m_hscroll && horizontal_needed) {
hscroll_added_or_removed = true;
m_hscroll = style->NewListBoxHScroll(m_color, CLR_SHADOW);
m_hscroll->NonClientChild(true);
m_hscroll->MoveTo(Pt(X0, cl_sz.y - SCROLL_WIDTH));
Expand All @@ -2225,14 +2275,6 @@ void ListBox::AdjustScrolls(bool adjust_for_resize)
}

if (horizontal_needed) {
if (adjust_for_resize) {
X scroll_x(0);
Y scroll_y = cl_sz.y - SCROLL_WIDTH;
m_hscroll->SizeMove(Pt(scroll_x, scroll_y),
Pt(scroll_x + cl_sz.x - (vertical_needed ? SCROLL_WIDTH : 0),
scroll_y + SCROLL_WIDTH));
}

unsigned int line_size = m_hscroll_wheel_scroll_increment;
if (line_size == 0 && !this->Empty()) {
const Row* row = *begin();
Expand All @@ -2241,13 +2283,46 @@ void ListBox::AdjustScrolls(bool adjust_for_resize)

unsigned int page_size = std::abs(Value(cl_sz.x - (vertical_needed ? SCROLL_WIDTH : 0)));

m_hscroll->SizeScroll(0, Value(total_x_extent - 1),
m_hscroll->SizeScroll(0, Value(*required_total_extents.first - 1),
line_size, std::max(line_size, page_size));
MoveChildUp(m_hscroll);
}

return {hscroll_added_or_removed, vscroll_added_or_removed};
}

void ListBox::AdjustScrolls(bool adjust_for_resize, const std::pair<bool, bool>& force_scrolls)
{
// The client size before scrolls are/are not added.
const Pt cl_sz = ClientSizeExcludingScrolls();

// The size of the underlying list box, indicating if scrolls are required.
const auto required_total_extents = CheckIfScrollsRequired(force_scrolls, cl_sz);

bool vscroll_added_or_removed;
std::tie(std::ignore, vscroll_added_or_removed) = AddOrRemoveScrolls(required_total_extents, cl_sz);

if (!adjust_for_resize)
return;

if (m_vscroll) {
X scroll_x = cl_sz.x - SCROLL_WIDTH;
Y scroll_y(0);
m_vscroll->SizeMove(Pt(scroll_x, scroll_y),
Pt(scroll_x + SCROLL_WIDTH,
scroll_y + cl_sz.y - (m_hscroll ? SCROLL_WIDTH : 0)));
}

if (m_hscroll) {
X scroll_x(0);
Y scroll_y = cl_sz.y - SCROLL_WIDTH;
m_hscroll->SizeMove(Pt(scroll_x, scroll_y),
Pt(scroll_x + cl_sz.x - (m_vscroll ? SCROLL_WIDTH : 0),
scroll_y + SCROLL_WIDTH));
}

// Resize rows to fit client area.
if (vscroll_added_or_removed || adjust_for_resize) {
if (vscroll_added_or_removed) {
RequirePreRender();
X row_width(std::max(ClientWidth(), X(1)));
for (Row* row : m_rows) {
Expand Down