Ticket #7461: MythUI-DynamicLayout-v1.1.diff

File MythUI-DynamicLayout-v1.1.diff, 62.5 KB (added by John Patrick Poet <jppoet@…>, 10 years ago)

Updated for #22683

  • libs/libmythui/mythuibuttonlist.h

     
    99#include "mythuitype.h"
    1010#include "mythuiimage.h"
    1111#include "mythuitext.h"
     12#include "mythuigroup.h"
    1213
    1314class MythUIButtonList;
    1415class MythFontProperties;
     
    143144    uint GetVisibleCount() const { return m_itemsVisible; }
    144145    bool IsEmpty() const;
    145146
    146     enum ScrollStyle  { ScrollFree, ScrollCenter };
     147    enum ScrollStyle  { ScrollFree, ScrollCenter, ScrollGroupCenter };
    147148    enum LayoutType   { LayoutVertical, LayoutHorizontal, LayoutGrid };
     149    enum ArrangeType  { ArrangeFixed, ArrangeFill, ArrangeSpread, ArrangeStack };
    148150    enum MovementUnit { MoveItem, MoveColumn, MoveRow, MovePage, MoveMax,
    149151                        MoveMid, MoveByAmount };
    150152    enum WrapStyle    { WrapCaptive = -1, WrapNone = 0, WrapSelect, WrapItems };
    151153
     154    void InitButton(int itemIdx, MythUIStateType* & realButton,
     155                    MythUIButtonListItem* & buttonItem);
     156    int PageUp(void);
     157    int PageDown(void);
    152158    virtual bool MoveDown(MovementUnit unit = MoveItem, uint amount = 0);
    153159    virtual bool MoveUp(MovementUnit unit = MoveItem, uint amount = 0);
    154160    bool MoveToNamedPosition(const QString &position_name);
     
    173179
    174180    void InsertItem(MythUIButtonListItem *item);
    175181
     182    int minButtonWidth(const MythRect & area);
     183    int minButtonHeight(const MythRect & area);
     184    MythUIGroup *PrepareButton(int buttonIdx, int itemIdx,
     185                               int & selectedIdx, int & button_shift);
     186    bool DistributeRow(int & first_button, int & last_button,
     187                       int & first_item, int & last_item,
     188                       int & selected_column,
     189                       bool grow_left, bool grow_right,
     190                       int ** col_widths, int & row_height,
     191                       int total_height, int split_height,
     192                       int & col_cnt, bool & wrapped);
     193    bool DistributeCols(int & first_button, int & last_button,
     194                        int & first_item, int & last_item,
     195                        int & selected_column, int & selected_row,
     196                        int ** col_widths, QList<int> & row_heights,
     197                        int & top_height, int & bottom_height,
     198                        bool & wrapped);
     199    bool DistributeButtons(void);
     200    void SetPosition(void);
    176201    void SetPositionArrowStates(void);
    177202
    178203    void updateLCD(void);
     
    192217
    193218    /**/
    194219
    195     LayoutType m_layout;
     220    LayoutType  m_layout;
     221    ArrangeType m_arrange;
    196222    ScrollStyle m_scrollStyle;
    197     WrapStyle m_wrapStyle;
     223    WrapStyle   m_wrapStyle;
     224    int         m_alignment;
    198225
    199226    MythRect m_contentsRect;
    200227
     
    203230    int m_itemHorizSpacing;
    204231    int m_itemVertSpacing;
    205232    uint m_itemsVisible;
     233    int m_maxVisible;
    206234    int m_rows;
    207235    int m_columns;
     236    int m_leftColumns, m_rightColumns;
     237    int m_topRows, m_bottomRows;
    208238
    209239    bool m_active;
    210240    bool m_showArrow;
     
    212242    MythUIStateType *m_upArrow;
    213243    MythUIStateType *m_downArrow;
    214244
     245    MythUIStateType *m_buttontemplate;
     246
    215247    QVector<MythUIStateType *> m_ButtonList;
    216248    QMap<int, MythUIButtonListItem *> m_ButtonToItem;
    217249
  • libs/libmythui/mythuibuttonlist.cpp

     
    1010#include "mythverbose.h"
    1111
    1212// mythui headers
    13 #include "mythuigroup.h"
    1413#include "mythmainwindow.h"
    1514#include "mythuistatetype.h"
    1615#include "lcddevice.h"
     
    4241    m_contentsRect = MythRect(0,0,0,0);
    4342
    4443    m_layout      = LayoutVertical;
     44    m_arrange     = ArrangeFixed;
     45    m_alignment   = Qt::AlignLeft | Qt::AlignTop;
    4546    m_scrollStyle = ScrollFree;
    4647    m_wrapStyle   = WrapNone;
    4748
     
    6162    m_itemHeight       = 0;
    6263    m_itemWidth        = 0;
    6364    m_itemsVisible     = 0;
     65    m_maxVisible       = 0;
    6466    m_columns          = 0;
    6567    m_rows             = 0;
     68    m_leftColumns      = 0;
     69    m_rightColumns     = 0;
     70    m_topRows          = 0;
     71    m_bottomRows       = 0;
    6672    m_lcdTitle         = "";
    6773
    6874    m_upArrow = m_downArrow = NULL;
    6975
     76    m_buttontemplate = NULL;
     77
    7078    SetCanTakeFocus(true);
    7179
    7280    connect(this, SIGNAL(TakingFocus()), this, SLOT(Select()));
     
    137145    SetRedraw();
    138146}
    139147
    140 void MythUIButtonList::SanitizePosition(void)
     148/*
     149 * The "width" of a button determines it relative position when using
     150 * Dynamic-Layout.
     151 *
     152 * If a button has a negative offset, that offset needs accounted for
     153 * to position the button in proper releation to the surrounding buttons.
     154 */
     155int MythUIButtonList::minButtonWidth(const MythRect & area)
    141156{
    142     if (m_selPosition < 0)
    143         m_selPosition = (m_wrapStyle > WrapNone) ? m_itemList.size() - 1 : 0;
    144     else if (m_selPosition >= m_itemList.size())
    145         m_selPosition = (m_wrapStyle > WrapNone) ? 0 : m_itemList.size() - 1;
     157    int width = area.width();
     158
     159    /*
     160     * Assume if an overlap is allowed on the left, the same overlap
     161     * is on the right
     162     */
     163    if (area.x() < 0)
     164        width += (area.x() * 2 - 1); // x is negative
     165    else
     166        width += area.x();
     167
     168    while (width < 0)
     169        width -= area.x(); // Oops
     170
     171    return width;
    146172}
    147173
    148 void MythUIButtonList::SetPositionArrowStates(void)
     174/*
     175 * The "height" of a button determines it relative position when using
     176 * Dynamic-Layout.
     177 *
     178 * If a button has a negative offset, that offset needs accounted for
     179 * to position the button in proper releation to the surrounding buttons.
     180 */
     181int MythUIButtonList::minButtonHeight(const MythRect & area)
    149182{
    150     if (!m_initialized)
    151         Init();
     183    int height = area.height();
    152184
    153     if (m_clearing)
    154         return;
     185    /*
     186     * Assume if an overlap is allowed on the top, the same overlap
     187     * is on the bottom
     188     */
     189    if (area.y() < 0)
     190        height += (area.y() * 2 - 1);
     191    else
     192        height += area.y();
    155193
    156     m_needsUpdate = false;
     194    while (height < 0)
     195        height -= area.y(); // Oops
    157196
    158     m_ButtonToItem.clear();
     197    return height;
     198}
    159199
    160     if (m_ButtonList.size() > 0)
     200/*
     201 * For Dynamic-Layout, buttons are allocated as needed.  If the list is
     202 * being redrawn, re-use any previously allocated buttons.
     203 */
     204MythUIGroup *MythUIButtonList::PrepareButton(int buttonIdx, int itemIdx,
     205                                             int & selectedIdx,
     206                                             int & button_shift)
     207{
     208    MythUIStateType *realButton;
     209    MythUIGroup *buttonstate;
     210    MythUIButtonListItem* buttonItem = m_itemList[itemIdx];
     211
     212    buttonIdx += button_shift;
     213    if (buttonIdx < 0 || buttonIdx + 1 > m_maxVisible)
    161214    {
    162         int button = 0;
     215        QString name = QString("buttonlist button %1").arg(m_maxVisible);
     216        MythUIStateType *button = new MythUIStateType(this, name);
     217        button->CopyFrom(m_buttontemplate);
     218        if (buttonIdx < 0)
     219        {
     220            /*
     221             * If a new button is needed in the front of the list, previously
     222             * existing buttons need shifted to the right.
     223             */
     224            m_ButtonList.prepend(button);
     225            buttonIdx = 0;
     226            ++button_shift;
     227            if (selectedIdx >= 0)
     228                ++selectedIdx;
     229        }
     230        else
     231            m_ButtonList.append(button);
     232        ++m_maxVisible;
     233    }
    163234
    164         // set topitem, top position
    165         SanitizePosition();
     235    realButton = m_ButtonList[buttonIdx];
     236    m_ButtonToItem[buttonIdx] = buttonItem;
     237    buttonItem->SetToRealButton(realButton, itemIdx == m_selPosition);
     238    buttonstate = dynamic_cast<MythUIGroup *>(realButton->GetCurrentState());
    166239
    167         switch (m_scrollStyle)
     240    if (itemIdx == m_selPosition)
     241        selectedIdx = buttonIdx;
     242
     243    return buttonstate;
     244}
     245
     246/*
     247 * Dynamically layout the buttons on a row.
     248 */
     249bool MythUIButtonList::DistributeRow(int & first_button, int & last_button,
     250                                     int & first_item, int & last_item,
     251                                     int & selected_column,
     252                                     bool grow_left, bool grow_right,
     253                                     int ** col_widths, int & row_height,
     254                                     int total_height, int split_height,
     255                                     int & col_cnt, bool & wrapped)
     256{
     257    MythUIGroup *buttonstate;
     258    int  initial_first_button, initial_last_button;
     259    int  initial_first_item,   initial_last_item;
     260    int  col_idx, left_cnt = 0;
     261    int  width, height;
     262    int  max_width, max_height;
     263    int  left_width, right_width;
     264    int  begin, end;
     265    bool underflow;
     266    bool added;
     267    bool hsplit, vsplit;
     268    int  selectedIdx;
     269    int  button_shift;
     270
     271    selectedIdx = -1;
     272    button_shift = 0;
     273    col_cnt = 1;
     274
     275    if (last_item + 1 > m_itemCount || last_item < 0 || first_item < 0)
     276        return false;
     277
     278    /*
     279     * Allocate a button on the row.  With a vertical layout, there is
     280     * only one button per row, and this would be it.
     281     */
     282    if (grow_right)
     283        buttonstate = PrepareButton(last_button, last_item,
     284                                    selectedIdx, button_shift);
     285    else
     286        buttonstate = PrepareButton(first_button, first_item,
     287                                    selectedIdx, button_shift);
     288
     289    if (buttonstate == NULL)
     290    {
     291        VERBOSE(VB_IMPORTANT, QString("Failed to query buttonlist state: %1")
     292                .arg(last_button));
     293        return false;
     294    }
     295
     296    // Note size of initial button.
     297    max_width  = m_contentsRect.width();
     298    max_height = m_contentsRect.height();
     299    row_height = minButtonHeight(buttonstate->GetArea());
     300    width      = minButtonWidth(buttonstate->GetArea());
     301
     302    /*
     303     * If the selected button should be centered, don't allow new buttons
     304     * to take up more than half the allowed area.
     305     */
     306    vsplit = (m_scrollStyle == ScrollCenter);
     307    hsplit = vsplit && grow_left && grow_right;
     308    if (hsplit)
     309    {
     310        max_width /= 2;
     311        left_width = right_width = (width / 2);
     312    }
     313    else
     314    {
     315        if (grow_right)
    168316        {
    169             case ScrollCenter:
    170                 m_topPosition = qMax(m_selPosition - (int)((float)m_itemsVisible / 2), 0);
    171                 break;
    172             case ScrollFree:
     317            left_width  = 0;
     318            right_width = width;
     319        }
     320        else
     321        {
     322            left_width  = width;
     323            right_width = 0;
     324        }
     325    }
     326    if (vsplit)
     327        max_height /= 2;
     328
     329    /*
     330     * If total_height == 0, then this is the first row, so allow any height.
     331     * Otherwide, If adding a button to a column would exceed the
     332     * parent height, abort
     333    */
     334    if (total_height > 0 &&
     335        ((vsplit ? split_height : total_height) +
     336         m_itemVertSpacing + row_height > max_height))
     337    {
     338        VERBOSE(VB_GENERAL|VB_EXTRA,
     339                QString("%1 Height exceeded %2 + (%3) + %4 = %5 "
     340                        "which is > %6")
     341                .arg(vsplit ? "Centering" : "Total")
     342                .arg(split_height).arg(m_itemVertSpacing).arg(row_height)
     343                .arg(split_height + m_itemVertSpacing + row_height)
     344                .arg(max_height));
     345        first_button += button_shift;
     346        last_button += button_shift;
     347        return false;
     348    }
     349
     350    VERBOSE(VB_GENERAL|VB_EXTRA, QString("Added button item %1 "
     351                                         "width %2 height %3")
     352            .arg(grow_right ? last_item : first_item)
     353            .arg(width).arg(row_height));
     354
     355    initial_first_button = first_button;
     356    initial_last_button  = last_button;
     357    initial_first_item   = first_item;
     358    initial_last_item    = last_item;
     359
     360    /*
     361     * if col_widths is not NULL, then grow_left & grow_right
     362     * are mutually exclusive.  So, col_idx can be anchored from
     363     * the left or right.
     364    */
     365    if (grow_right)
     366        col_idx = 0;
     367    else
     368        col_idx = m_columns - 1;
     369
     370    // Add butons until no more fit.
     371    added = (m_layout != LayoutVertical);
     372    while (added)
     373    {
     374        added = false;
     375
     376        // If a grid, maintain same number of columns on each row.
     377        if (grow_right && col_cnt < m_columns)
     378        {
     379            if (wrapped)
     380                end = first_item;
     381            else
    173382            {
    174                 int adjust = 0;
    175                 if (m_topPosition == -1 || m_keepSelAtBottom)
     383                // Are we allowed to wrap when we run out of items?
     384                if (m_wrapStyle == WrapItems &&
     385                    (hsplit || m_scrollStyle != ScrollFree) &&
     386                    last_item + 1 == m_itemCount)
    176387                {
    177                     if (m_topPosition == -1)
    178                         m_topPosition = 0;
    179                     if (m_layout == LayoutHorizontal)
    180                         adjust = 1 - m_itemsVisible;
    181                     else
    182                         adjust = m_columns - m_itemsVisible;
    183                     m_keepSelAtBottom = false;
     388                    last_item = -1;
     389                    wrapped = true;
     390                    end = first_item;
    184391                }
     392                else
     393                    end = m_itemCount;
     394            }
    185395
    186                 if (m_selPosition < m_topPosition ||
    187                     m_topPosition + (int)m_itemsVisible <= m_selPosition)
     396            if (last_item + 1 < end)
     397            {
     398                // Allocate next button to the right.
     399                buttonstate = PrepareButton(last_button + 1, last_item + 1,
     400                                            selectedIdx, button_shift);
     401                if (buttonstate == NULL)
     402                    continue;
     403                width = minButtonWidth(buttonstate->GetArea());
     404
     405                // For grids, use the widest button in a column
     406                if (*col_widths && width < (*col_widths)[col_idx])
     407                    width = (*col_widths)[col_idx];
     408
     409                // Does the button fit?
     410                if ((hsplit ? right_width : left_width + right_width) +
     411                    m_itemHorizSpacing + width > max_width)
    188412                {
    189                     if (m_layout == LayoutHorizontal)
    190                         m_topPosition = m_selPosition + adjust;
    191                     else
    192                         m_topPosition = (m_selPosition + adjust) /
    193                                         m_columns * m_columns;
     413                    int total = hsplit ? right_width : left_width + right_width;
     414                    VERBOSE(VB_GENERAL|VB_EXTRA,
     415                            QString("button on right would exceed width: "
     416                                    "%1+(%2)+%3 == %4 which is > %5")
     417                            .arg(total).arg(m_itemHorizSpacing).arg(width)
     418                            .arg(total + m_itemHorizSpacing + width)
     419                            .arg(max_width));
    194420                }
     421                else
     422                {
     423                    added = true;
     424                    ++col_cnt;
     425                    ++last_button;
     426                    ++last_item;
     427                    ++col_idx;
     428                    right_width += m_itemHorizSpacing + width;
     429                    height = minButtonHeight(buttonstate->GetArea());
     430                    if (row_height < height)
     431                        row_height = height;
     432                    VERBOSE(VB_GENERAL|VB_EXTRA,
     433                            QString("Added button item %1 "
     434                                    "r.width %2 height %3 total width %4+%5")
     435                            .arg(last_item).arg(width).arg(height)
     436                            .arg(left_width).arg(right_width));
     437                }
     438            }
     439            else
     440                underflow = true;
     441        }
    195442
    196                 m_topPosition = qMax(m_topPosition, 0);
    197                 break;
     443        // If a grid, maintain same number of columns on each row.
     444        if (grow_left && col_cnt < m_columns)
     445        {
     446            if (wrapped)
     447                end = last_item + 1;
     448            else
     449            {
     450                // Are we allowed to wrap when we run out of items?
     451                if (m_wrapStyle == WrapItems &&
     452                    (hsplit || m_scrollStyle != ScrollFree) &&
     453                    first_item == 0)
     454                {
     455                    first_item = m_itemCount;
     456                    wrapped = true;
     457                    end = last_item + 1;
     458                }
     459                else
     460                    end = 0;
    198461            }
     462
     463            if (first_item > end)
     464            {
     465                buttonstate = PrepareButton(first_button - 1, first_item - 1,
     466                                            selectedIdx, button_shift);
     467                if (buttonstate == NULL)
     468                    continue;
     469                width = minButtonWidth(buttonstate->GetArea());
     470
     471                // For grids, use the widest button in a column
     472                if (*col_widths && width < (*col_widths)[col_idx])
     473                    width = (*col_widths)[col_idx];
     474
     475                // Does the button fit?
     476                if ((hsplit ? left_width : left_width + right_width) +
     477                    m_itemHorizSpacing + width > max_width)
     478                {
     479                    int total = hsplit ? left_width : left_width + right_width;
     480                    VERBOSE(VB_GENERAL|VB_EXTRA,
     481                            QString("button on left would exceed width: "
     482                                    "%1+(%2)+%3 == %4 which is > %5")
     483                            .arg(total).arg(m_itemHorizSpacing).arg(width)
     484                            .arg(total + m_itemHorizSpacing + width)
     485                            .arg(max_width));
     486                }
     487                else
     488                {
     489                    added = true;
     490                    --first_button;
     491                    --first_item;
     492                    --col_idx;
     493                    ++col_cnt;
     494                    ++left_cnt;
     495                    left_width += m_itemHorizSpacing + width;
     496                    height = minButtonHeight(buttonstate->GetArea());
     497                    if (row_height < height)
     498                        row_height = height;
     499                    VERBOSE(VB_GENERAL|VB_EXTRA,
     500                            QString("Added button item %1 "
     501                                    "l.width %2 height %3 total width %4+%5")
     502                            .arg(first_item).arg(width).arg(height)
     503                            .arg(left_width).arg(right_width));
     504                }
     505            }
     506            else
     507                underflow = true;
    199508        }
     509    }
    200510
    201         QList<MythUIButtonListItem*>::iterator it = m_itemList.begin() + m_topPosition;
     511    /*
     512     * If total_height == 0, then this is the first row, so allow any height.
     513     * Otherwide, If adding a button to a column would exceed the
     514     * parent height, abort
     515    */
     516    if (total_height > 0 &&
     517        ((vsplit ? split_height : total_height) +
     518         m_itemVertSpacing + row_height > max_height))
     519    {
     520        VERBOSE(VB_GENERAL|VB_EXTRA,
     521                QString("%1 Height exceeded %2 + (%3) + %4 = %5 "
     522                        "which is > %6")
     523                .arg(vsplit ? "Centering" : "Total")
     524                .arg(split_height).arg(m_itemVertSpacing).arg(row_height)
     525                .arg(split_height + m_itemVertSpacing + row_height)
     526                .arg(max_height));
     527        first_button = initial_first_button + button_shift;
     528        last_button  = initial_last_button + button_shift;
     529        first_item   = initial_first_item;
     530        last_item    = initial_last_item;
     531        return false;
     532    }
    202533
     534    if (*col_widths == 0)
     535    {
     536        /*
     537         * Allocate array to hold columns widths, now that we know
     538         * how many columns there are.
     539         */
     540        *col_widths = new int[col_cnt];
     541        for (col_idx = 0; col_idx < col_cnt; ++col_idx)
     542            (*col_widths)[col_idx] = 0;
     543
     544    }
     545
     546    // Adjust for insertions on the front.
     547    first_button += button_shift;
     548    last_button += button_shift;
     549
     550    // It fits, so so note max column widths
     551    MythUIStateType *realButton;
     552    int buttonIdx;
     553
     554    if (grow_left)
     555    {
     556        begin = first_button;
     557        end = first_button + col_cnt;
     558    }
     559    else
     560    {
     561        end = last_button + 1;
     562        begin = end - col_cnt;
     563    }
     564
     565    for (buttonIdx = begin, col_idx = 0;
     566         buttonIdx < end; ++buttonIdx, ++col_idx)
     567    {
     568        realButton = m_ButtonList[buttonIdx];
     569        buttonstate = dynamic_cast<MythUIGroup *>
     570                      (realButton->GetCurrentState());
     571        width = minButtonWidth(buttonstate->GetArea());
     572        if ((*col_widths)[col_idx] < width)
     573            (*col_widths)[col_idx] = width;
     574
     575        // Make note of which column as the selected button
     576        if (selectedIdx == buttonIdx)
     577            selected_column = col_idx;
     578    }
     579
     580    /*
     581      An underflow indicates we ran out of items, not that the
     582      buttons did not fit on the row.
     583    */
     584    if (total_height && underflow && col_cnt < m_columns)
     585        col_cnt = m_columns;
     586
     587    return true;
     588}
     589
     590/*
     591 * Dynamically layout columns
     592 */
     593bool MythUIButtonList::DistributeCols(int & first_button, int & last_button,
     594                                      int & first_item, int & last_item,
     595                                      int & selected_column, int & selected_row,
     596                                      int ** col_widths,
     597                                      QList<int> & row_heights,
     598                                      int & top_height, int & bottom_height,
     599                                      bool & wrapped)
     600{
     601    int  col_cnt;
     602    int  height;
     603    int  row_cnt = 1;
     604    int  end;
     605    bool added;
     606
     607    do
     608    {
     609        added = false;
     610
     611        if (wrapped)
     612            end = first_item;
     613        else
     614        {
     615            // Are we allowed to wrap when we run out of items?
     616            if (m_wrapStyle == WrapItems &&
     617                (m_scrollStyle == ScrollCenter ||
     618                 m_scrollStyle == ScrollGroupCenter) &&
     619                last_item + 1 == m_itemCount)
     620            {
     621                last_item = -1;
     622                wrapped = true;
     623                end = first_item;
     624            }
     625            else
     626                end = m_itemCount;
     627        }
     628
     629        if (last_item + 1 < end)
     630        {
     631            // Does another row fit?
     632            if (DistributeRow(first_button, ++last_button,
     633                              first_item, ++last_item, selected_column,
     634                              false, true, col_widths, height,
     635                              top_height + bottom_height, bottom_height,
     636                              col_cnt, wrapped))
     637            {
     638                if (col_cnt < m_columns)
     639                    return false;  // Need to try again with fewer cols
     640
     641                if (selected_row == -1 && selected_column != -1)
     642                    selected_row = row_heights.size();
     643                ++row_cnt;
     644                row_heights.push_back(height);
     645                bottom_height += (height + m_itemVertSpacing);
     646                added = true;
     647            }
     648            else
     649            {
     650                --last_button;
     651                --last_item;
     652            }
     653        }
     654
     655        if (wrapped)
     656            end = last_item + 1;
     657        else
     658        {
     659            // Are we allowed to wrap when we run out of items?
     660            if (m_wrapStyle == WrapItems &&
     661                (m_scrollStyle == ScrollCenter ||
     662                 m_scrollStyle == ScrollGroupCenter) &&
     663                first_item == 0)
     664            {
     665                first_item = m_itemCount;
     666                wrapped = true;
     667                end = last_item + 1;
     668            }
     669            else
     670                end = 0;
     671        }
     672
     673        if (first_item > end)
     674        {
     675            // Can we insert another row?
     676            if (DistributeRow(--first_button, last_button,
     677                              --first_item, last_item, selected_column,
     678                              true, false, col_widths, height,
     679                              top_height + bottom_height, top_height,
     680                              col_cnt, wrapped))
     681            {
     682                if (col_cnt < m_columns)
     683                    return false;  // Need to try again with fewer cols
     684
     685                if (selected_row == -1 && selected_column != -1)
     686                    selected_row = row_heights.size();
     687                else if (selected_row != -1)
     688                    ++selected_row;
     689                ++row_cnt;
     690                row_heights.push_front(height);
     691                top_height += (height + m_itemVertSpacing);
     692                added = true;
     693            }
     694            else
     695            {
     696                ++first_button;
     697                ++first_item;
     698            }
     699        }
     700    } while (added);
     701
     702    return true;
     703}
     704
     705/*
     706 * Dynamically layout as many buttons as will fit in the area.
     707 */
     708bool MythUIButtonList::DistributeButtons(void)
     709{
     710    int  first_button, last_button, start_button, start_item;
     711    int  first_item, last_item;
     712    int *col_widths;
     713    int  col_cnt;
     714    int  selected_column, selected_row;
     715    bool wrapped = false;
     716    bool grow_left = true;
     717
     718    QList<int> row_heights;
     719    QList<int>::iterator Iheight;
     720    int height, top_height, bottom_height;
     721
     722    start_item = m_selPosition;
     723    selected_column = selected_row = -1;
     724    top_height = bottom_height = 0;
     725    col_widths = 0;
     726
     727    VERBOSE(VB_GENERAL|VB_EXTRA, QString("DistributeButtons: "
     728                                         "selected item %1 total items %2")
     729            .arg(start_item).arg(m_itemCount));
     730
     731    /*
     732     * Try fewer and fewer columns until each row can fit the same
     733     * number of columns.
     734     */
     735    for (m_columns = m_itemCount; m_columns > 0; )
     736    {
     737        first_item = last_item = start_item;
     738
     739        /*
     740         * Drawing starts at start_button, and radiates from there.
     741         * Attempt to pick a start_button which will minimize the need for new
     742         * button allocations.
     743         */
     744        switch (m_scrollStyle)
     745        {
     746          case ScrollCenter:
     747          case ScrollGroupCenter:
     748            start_button = qMax((m_maxVisible / 2) - 1, 0);
     749            break;
     750          case ScrollFree:
     751            if (m_layout == LayoutGrid)
     752            {
     753                start_button = 0;
     754                first_item = last_item = 0;
     755                grow_left = false;
     756            }
     757            else if (!m_ButtonList.empty())
     758            {
     759                if (m_itemCount - m_selPosition - 1 <
     760                    static_cast<int>(m_ButtonList.size()) / 2)
     761                    start_button = m_ButtonList.size() -
     762                                   (m_itemCount - m_selPosition) + 1;
     763                else if (m_selPosition >
     764                         static_cast<int>(m_ButtonList.size()) / 2)
     765                    start_button = (m_ButtonList.size() / 2);
     766                else
     767                    start_button = m_selPosition;
     768            }
     769            else
     770                start_button = 0;
     771            break;
     772        }
     773
     774        first_button = last_button = start_button;
     775        row_heights.clear();
     776
     777        // Process row with selected button, and set starting val for m_columns.
     778        if (!DistributeRow(first_button, last_button,
     779                           first_item, last_item, selected_column,
     780                           grow_left, true, &col_widths,
     781                           height, 0, 0, col_cnt, wrapped))
     782            return false;
     783        m_columns = col_cnt;
     784
     785        if (m_layout == LayoutGrid && m_scrollStyle == ScrollFree)
     786        {
     787            /*
     788             * Now that we know how many columns there are, we can start
     789             * the grid layout for real.
     790             */
     791            start_item = (m_selPosition / m_columns) * m_columns;
     792            first_item = last_item = start_item;
     793
     794            /*
     795             * Attempt to pick a start_button which will minimize the need
     796             * for new button allocations.
     797             */
     798            start_button = qMax((int)m_ButtonList.size() / 2, 0);
     799            start_button = (start_button / qMax(m_columns, 1)) * m_columns;
     800            if (start_button < m_itemCount / 2 &&
     801                m_itemCount - m_selPosition - 1 < (int)m_ButtonList.size() / 2)
     802                start_button += m_columns;
     803            first_button = last_button = start_button;
     804
     805            // Now do initial row layout again with our new knowledge
     806            selected_column = selected_row = -1;
     807            if (!DistributeRow(first_button, last_button,
     808                               first_item, last_item, selected_column,
     809                               grow_left, true, &col_widths,
     810                               height, 0, 0, col_cnt, wrapped))
     811                return false;
     812        }
     813
     814        if (selected_column != -1)
     815            selected_row = 0;
     816        row_heights.push_back(height);
    203817        if (m_scrollStyle == ScrollCenter)
     818            top_height = bottom_height = (height / 2);
     819        else
     820            bottom_height = height;
     821
     822        if (m_layout == LayoutHorizontal)
     823            break;
     824
     825        // As as many columns as will fit.
     826        if (DistributeCols(first_button, last_button,
     827                           first_item, last_item,
     828                           selected_column, selected_row,
     829                           &col_widths, row_heights,
     830                           top_height, bottom_height, wrapped))
     831            break; // Buttons fit on each row, so done
     832
     833        delete[] col_widths;
     834        col_widths = 0;
     835
     836        --m_columns;
     837        start_item = m_selPosition;
     838    }
     839
     840    m_rows = row_heights.size();
     841
     842    VERBOSE(VB_GENERAL|VB_EXTRA,
     843            QString("%1 rows, %2 columns fit inside parent area %3x%4")
     844            .arg(m_rows).arg(m_columns).arg(m_contentsRect.width())
     845            .arg(m_contentsRect.height()));
     846
     847    if (col_widths == 0)
     848        return false;
     849
     850    int total, row, col;
     851    int left_spacing, right_spacing, top_spacing, bottom_spacing;
     852    int x, y, x_init, x_adj, y_adj;
     853    QString status_msg;
     854
     855    /*
     856     * Calculate heights of buttons on each side of selected button
     857     */
     858    top_height = bottom_height = m_topRows = m_bottomRows = 0;
     859
     860    status_msg = "Row heights: ";
     861    for (row = 0; row < m_rows; ++row)
     862    {
     863        if ( row != 0)
     864            status_msg += ", ";
     865
     866        if (row == selected_row)
    204867        {
    205             if (m_selPosition <= (int)(m_itemsVisible/2))
     868            status_msg += '[';
     869            top_height += (row_heights[row] / 2);
     870            bottom_height += ((row_heights[row] / 2) + (row_heights[row] % 2));
     871        }
     872        else
     873        {
     874            if (bottom_height)
    206875            {
    207                 button = (m_itemsVisible / 2) - m_selPosition;
    208                 if (m_wrapStyle == WrapItems && button > 0 &&
    209                     m_itemCount >= (int)m_itemsVisible)
     876                bottom_height += m_itemVertSpacing + row_heights[row];
     877                ++m_bottomRows;
     878            }
     879            else
     880            {
     881                top_height += row_heights[row] + m_itemVertSpacing;
     882                ++m_topRows;
     883            }
     884        }
     885
     886        status_msg += QString("%1").arg(row_heights[row]);
     887        if (row == selected_row)
     888            status_msg += ']';
     889    }
     890
     891    /*
     892     * How much extra space should there be between buttons?
     893     */
     894    if (m_arrange == ArrangeStack || m_layout == LayoutHorizontal)
     895    {
     896        // None
     897        top_spacing = bottom_spacing = 0;
     898    }
     899    else
     900    {
     901        if (m_rows < 2)
     902        {
     903            // Equal space on both sides of single row
     904            top_spacing = bottom_spacing =
     905                          (m_contentsRect.height() - top_height) / 2;
     906        }
     907        else
     908        {
     909            if (m_scrollStyle == ScrollCenter)
     910            {
     911                // Selected button needs to end up in the middle of area
     912                top_spacing = m_topRows ? (m_contentsRect.height() / 2 -
     913                                 top_height) / m_topRows : 0;
     914                bottom_spacing = m_bottomRows ? (m_contentsRect.height() / 2 -
     915                                 bottom_height) / m_bottomRows : 0;
     916                if (m_arrange == ArrangeSpread)
    210917                {
    211                     it = m_itemList.end() - button;
    212                     button = 0;
     918                    // Use same spacing on both sides of selected button
     919                    if (!m_topRows || top_spacing > bottom_spacing)
     920                        top_spacing = bottom_spacing;
     921                    else
     922                        bottom_spacing = top_spacing;
    213923                }
    214924            }
    215             else if ((m_itemCount - m_selPosition) < (int)(m_itemsVisible/2))
     925            else
    216926            {
    217                 it = m_itemList.begin() + m_selPosition - (m_itemsVisible/2);
     927                // Buttons will be evenly spread out to fill entire area
     928                top_spacing = bottom_spacing = (m_contentsRect.height() -
     929                                (top_height + bottom_height)) /
     930                               (m_topRows + m_bottomRows);
    218931            }
    219932        }
    220         else if (m_drawFromBottom && m_itemCount < (int)m_itemsVisible)
    221             button = m_itemsVisible - m_itemCount;
     933        // Add in intra-button space size
     934        top_height += (top_spacing * m_topRows);
     935        bottom_height += (bottom_spacing * m_bottomRows);
     936    }
    222937
    223         for (int i = 0; i < button; i++)
    224              m_ButtonList[i]->SetVisible(false);
     938    /*
     939     * Calculate top margin
     940     */
     941    y = m_contentsRect.y();
     942    if (m_alignment & Qt::AlignVCenter && m_arrange != ArrangeFill)
     943    {
     944        if (m_scrollStyle == ScrollCenter)
     945        {
     946            // Adjust to compensate for top height less than bottom height
     947            y += qMax(bottom_height - top_height, 0);
     948            total = qMax(top_height, bottom_height) * 2;
     949        }
     950        else
     951            total = top_height + bottom_height;
    225952
    226         bool seenSelected = false;
     953        // Adjust top margin so selected button ends up in the middle
     954        y += (qMax(m_contentsRect.height() - total, 2) / 2);
     955    }
     956    else if (m_alignment & Qt::AlignBottom && m_arrange == ArrangeStack)
     957    {
     958        // Adjust top margin so buttons are bottom justified
     959        y += qMax(m_contentsRect.height() -
     960                  (top_height + bottom_height), 0);
     961    }
    227962
    228         MythUIStateType *realButton = NULL;
    229         MythUIButtonListItem *buttonItem = NULL;
     963    status_msg += QString(" spacing top %1 bottom %2 fixed %3 offset %4")
     964                  .arg(top_spacing).arg(bottom_spacing)
     965                  .arg(m_itemVertSpacing).arg(y);
    230966
    231         if (it < m_itemList.begin())
    232             it = m_itemList.begin();
     967    VERBOSE(VB_GENERAL|VB_EXTRA, status_msg);
    233968
    234         int curItem = GetItemPos(*it);
    235         while (it < m_itemList.end() && button < (int)m_itemsVisible)
    236         {
    237             realButton = m_ButtonList[button];
    238             buttonItem = *it;
     969    /*
     970     * Calculate width of buttons on each side of selected button
     971     */
     972    int left_width, right_width;
    239973
    240             if (!realButton || !buttonItem)
    241                 break;
     974    left_width = right_width = m_leftColumns = m_rightColumns = 0;
    242975
    243             bool selected = false;
    244             if (!seenSelected && (curItem == m_selPosition))
     976    status_msg = "Col widths: ";
     977    for (col = 0; col < m_columns; ++col)
     978    {
     979        if (col != 0)
     980            status_msg += ", ";
     981
     982        if (col == selected_column)
     983        {
     984            status_msg += '[';
     985            left_width += (col_widths[col] / 2);
     986            right_width += ((col_widths[col] / 2) + (col_widths[col] % 2));
     987        }
     988        else
     989        {
     990            if (right_width)
    245991            {
    246                 seenSelected = true;
    247                 selected = true;
     992                right_width += m_itemHorizSpacing + col_widths[col];
     993                ++m_rightColumns;
    248994            }
     995            else
     996            {
     997                left_width  += col_widths[col] + m_itemHorizSpacing;
     998                ++m_leftColumns;
     999            }
     1000        }
    2491001
    250             m_ButtonToItem[button] = buttonItem;
    251             buttonItem->SetToRealButton(realButton, selected);
    252             realButton->SetVisible(true);
     1002        status_msg += QString("%1").arg(col_widths[col]);
     1003        if (col == selected_column)
     1004            status_msg += ']';
     1005    }
    2531006
    254             if (m_wrapStyle == WrapItems && it == (m_itemList.end()-1) &&
    255                 m_itemCount >= (int)m_itemsVisible)
     1007    /*
     1008     * How much extra space should there be between buttons?
     1009     */
     1010    if (m_arrange == ArrangeStack || m_layout == LayoutVertical)
     1011    {
     1012        // None
     1013        left_spacing = right_spacing = 0;
     1014    }
     1015    else
     1016    {
     1017        if (m_columns < 2)
     1018        {
     1019            // Equal space on both sides of single column
     1020            left_spacing = right_spacing =
     1021                           (m_contentsRect.width() - left_width) / 2;
     1022        }
     1023        else
     1024        {
     1025            if (m_scrollStyle == ScrollCenter)
    2561026            {
    257                 it = m_itemList.begin();
    258                 curItem = 0;
     1027                // Selected button needs to end up in the middle
     1028                left_spacing = m_leftColumns ? (m_contentsRect.width() / 2 -
     1029                                             left_width) / m_leftColumns : 0;
     1030                right_spacing = m_rightColumns ? (m_contentsRect.width() / 2 -
     1031                                             right_width) / m_rightColumns : 0;
     1032
     1033                if (m_arrange == ArrangeSpread)
     1034                {
     1035                    // Use same spacing on both sides of selected button
     1036                    if (!m_leftColumns || left_spacing > right_spacing)
     1037                        left_spacing = right_spacing;
     1038                    else
     1039                        right_spacing = left_spacing;
     1040                }
    2591041            }
    2601042            else
    2611043            {
    262                 ++it;
    263                 ++curItem;
     1044                // Buttons will be evenly spread out to fill entire area
     1045                left_spacing = right_spacing = (m_contentsRect.width() -
     1046                                      (left_width + right_width)) /
     1047                                     (m_leftColumns + m_rightColumns);
    2641048            }
     1049        }
     1050        // Add in intra-button space size
     1051        left_width += (left_spacing * m_leftColumns);
     1052        right_width += (right_spacing * m_rightColumns);
     1053    }
    2651054
    266             button++;
     1055    /*
     1056     * Calculate left margin
     1057     */
     1058    x_init = m_contentsRect.x();
     1059    if (m_alignment & Qt::AlignHCenter && m_arrange != ArrangeFill)
     1060    {
     1061        if (m_scrollStyle == ScrollCenter)
     1062        {
     1063            // Compensate for left being smaller than right
     1064            x_init += qMax(right_width - left_width, 0);
     1065            total = qMax(left_width, right_width) * 2;
    2671066        }
     1067        else
     1068            total = left_width + right_width;
    2681069
    269         for (; button < (int)m_itemsVisible; button++)
    270             m_ButtonList[button]->SetVisible(false);
     1070        // Adjust left margin so selected button ends up in the middle
     1071        x_init += (qMax(m_contentsRect.width() - total, 2) / 2);
     1072    }
     1073    else if (m_alignment & Qt::AlignRight && m_arrange == ArrangeStack)
     1074    {
     1075        // Adjust left margin, so buttons are right justified
     1076        x_init += qMax(m_contentsRect.width() -
     1077                       (left_width + right_width), 0);
     1078    }
    2711079
     1080    status_msg += QString(" spacing left %1 right %2 fixed %3 offset %4")
     1081                  .arg(left_spacing).arg(right_spacing)
     1082                  .arg(m_itemHorizSpacing).arg(x_init);
     1083    VERBOSE(VB_GENERAL|VB_EXTRA, status_msg);
     1084
     1085    top_spacing    += m_itemVertSpacing;
     1086    bottom_spacing += m_itemVertSpacing;
     1087    left_spacing   += m_itemHorizSpacing;
     1088    right_spacing  += m_itemHorizSpacing;
     1089
     1090    MythUIStateType *realButton = NULL;
     1091    MythUIGroup *buttonstate;
     1092
     1093    // Calculate position of each button
     1094    int vertical_spacing, horizontal_spacing;
     1095    int buttonIdx = first_button;
     1096
     1097    vertical_spacing = top_spacing;
     1098    for (row = 0; row < m_rows; ++row)
     1099    {
     1100        x = x_init;
     1101        horizontal_spacing = left_spacing;
     1102        for (col = 0; col < m_columns && buttonIdx <= last_button; ++col)
     1103        {
     1104            realButton = m_ButtonList[buttonIdx];
     1105            buttonstate = dynamic_cast<MythUIGroup *>
     1106                          (realButton->GetCurrentState());
     1107
     1108            // Center button within width of column
     1109            if (m_alignment & Qt::AlignHCenter)
     1110                x_adj = (col_widths[col] -
     1111                         minButtonWidth(buttonstate->GetArea())) / 2;
     1112            else if (m_alignment & Qt::AlignRight)
     1113                x_adj = (col_widths[col] -
     1114                         minButtonWidth(buttonstate->GetArea()));
     1115            else
     1116                x_adj = 0;
     1117
     1118            // Center button within height of row.
     1119            if (m_alignment & Qt::AlignVCenter)
     1120                y_adj = (row_heights[row] -
     1121                         minButtonHeight(buttonstate->GetArea())) / 2;
     1122            else if (m_alignment & Qt::AlignBottom)
     1123                y_adj = (row_heights[row] -
     1124                         minButtonHeight(buttonstate->GetArea()));
     1125            else
     1126                y_adj = 0;
     1127
     1128            // Set position of button
     1129            realButton->SetPosition(x + x_adj, y + y_adj);
     1130            realButton->SetVisible(true);
     1131
     1132            if (col == selected_column)
     1133                horizontal_spacing = right_spacing;
     1134
     1135            x += col_widths[col] + horizontal_spacing;
     1136            ++buttonIdx;
     1137        }
     1138        if (row == selected_row)
     1139            vertical_spacing = bottom_spacing;
     1140
     1141        y += row_heights[row] + vertical_spacing;
    2721142    }
    2731143
     1144    m_itemsVisible = m_columns * m_rows;
     1145
     1146    // Hide buttons before first active button
     1147    for (buttonIdx = 0; buttonIdx < first_button; ++buttonIdx)
     1148        m_ButtonList[buttonIdx]->SetVisible(false);
     1149    // Hide buttons after last active buttons.
     1150    for (buttonIdx = m_maxVisible - 1; buttonIdx > last_button; --buttonIdx)
     1151        m_ButtonList[buttonIdx]->SetVisible(false);
     1152
     1153    // Set m_topPosition so arrows are displayed correctly.
     1154    if (m_scrollStyle == ScrollCenter || m_scrollStyle == ScrollGroupCenter)
     1155        m_topPosition = static_cast<int>(m_itemsVisible) < m_itemCount;
     1156    else
     1157        m_topPosition = first_item;
     1158
     1159    delete[] col_widths;
     1160    return true;
     1161}
     1162
     1163void MythUIButtonList::SetPosition(void)
     1164{
     1165    if (m_ButtonList.size() == 0)
     1166        return;
     1167
     1168    int button = 0;
     1169
     1170    switch (m_scrollStyle)
     1171    {
     1172      case ScrollCenter:
     1173      case ScrollGroupCenter:
     1174        m_topPosition = qMax(m_selPosition - (int)((float)m_itemsVisible / 2), 0);
     1175        break;
     1176      case ScrollFree:
     1177      {
     1178          int adjust = 0;
     1179          if (m_topPosition == -1 || m_keepSelAtBottom)
     1180          {
     1181              if (m_topPosition == -1)
     1182                  m_topPosition = 0;
     1183              if (m_layout == LayoutHorizontal)
     1184                  adjust = 1 - m_itemsVisible;
     1185              else
     1186                  adjust = m_columns - m_itemsVisible;
     1187              m_keepSelAtBottom = false;
     1188          }
     1189
     1190          if (m_selPosition < m_topPosition ||
     1191              m_topPosition + (int)m_itemsVisible <= m_selPosition)
     1192          {
     1193              if (m_layout == LayoutHorizontal)
     1194                  m_topPosition = m_selPosition + adjust;
     1195              else
     1196                  m_topPosition = (m_selPosition + adjust) /
     1197                                  m_columns * m_columns;
     1198          }
     1199
     1200          m_topPosition = qMax(m_topPosition, 0);
     1201          break;
     1202      }
     1203    }
     1204
     1205    QList<MythUIButtonListItem*>::iterator it = m_itemList.begin() + m_topPosition;
     1206
     1207    if (m_scrollStyle == ScrollCenter || m_scrollStyle == ScrollGroupCenter)
     1208    {
     1209        if (m_selPosition <= (int)(m_itemsVisible/2))
     1210        {
     1211            button = (m_itemsVisible / 2) - m_selPosition;
     1212            if (m_wrapStyle == WrapItems && button > 0 &&
     1213                m_itemCount >= (int)m_itemsVisible)
     1214            {
     1215                it = m_itemList.end() - button;
     1216                button = 0;
     1217            }
     1218        }
     1219        else if ((m_itemCount - m_selPosition) < (int)(m_itemsVisible/2))
     1220        {
     1221            it = m_itemList.begin() + m_selPosition - (m_itemsVisible/2);
     1222        }
     1223    }
     1224    else if (m_drawFromBottom && m_itemCount < (int)m_itemsVisible)
     1225        button = m_itemsVisible - m_itemCount;
     1226
     1227    for (int i = 0; i < button; i++)
     1228        m_ButtonList[i]->SetVisible(false);
     1229
     1230    bool seenSelected = false;
     1231
     1232    MythUIStateType *realButton = NULL;
     1233    MythUIButtonListItem *buttonItem = NULL;
     1234
     1235    if (it < m_itemList.begin())
     1236        it = m_itemList.begin();
     1237
     1238    int curItem = GetItemPos(*it);
     1239    while (it < m_itemList.end() && button < (int)m_itemsVisible)
     1240    {
     1241        realButton = m_ButtonList[button];
     1242        buttonItem = *it;
     1243
     1244        if (!realButton || !buttonItem)
     1245            break;
     1246
     1247        bool selected = false;
     1248        if (!seenSelected && (curItem == m_selPosition))
     1249        {
     1250            seenSelected = true;
     1251            selected = true;
     1252        }
     1253
     1254        m_ButtonToItem[button] = buttonItem;
     1255        buttonItem->SetToRealButton(realButton, selected);
     1256        realButton->SetVisible(true);
     1257
     1258        if (m_wrapStyle == WrapItems && it == (m_itemList.end()-1) &&
     1259            m_itemCount >= (int)m_itemsVisible)
     1260        {
     1261            it = m_itemList.begin();
     1262            curItem = 0;
     1263        }
     1264        else
     1265        {
     1266            ++it;
     1267            ++curItem;
     1268        }
     1269
     1270        button++;
     1271    }
     1272
     1273    for (; button < (int)m_itemsVisible; button++)
     1274        m_ButtonList[button]->SetVisible(false);
     1275
     1276}
     1277
     1278void MythUIButtonList::SanitizePosition(void)
     1279{
     1280    if (m_selPosition < 0)
     1281        m_selPosition = (m_wrapStyle > WrapNone) ? m_itemList.size() - 1 : 0;
     1282    else if (m_selPosition >= m_itemList.size())
     1283        m_selPosition = (m_wrapStyle > WrapNone) ? 0 : m_itemList.size() - 1;
     1284}
     1285
     1286void MythUIButtonList::SetPositionArrowStates()
     1287{
     1288    if (!m_initialized)
     1289        Init();
     1290
     1291    if (!m_initialized)
     1292        return;
     1293
     1294    if (m_clearing)
     1295        return;
     1296
     1297    m_needsUpdate = false;
     1298
     1299    // set topitem, top position
     1300    SanitizePosition();
     1301    m_ButtonToItem.clear();
     1302
     1303    if (m_arrange == ArrangeFixed)
     1304        SetPosition();
     1305    else
     1306        DistributeButtons();
     1307
    2741308    updateLCD();
    2751309
    2761310    if (!m_downArrow || !m_upArrow)
     
    4851519    return m_itemList.indexOf(item);
    4861520}
    4871521
     1522void MythUIButtonList::InitButton(int itemIdx, MythUIStateType* & realButton,
     1523                                  MythUIButtonListItem* & buttonItem)
     1524{
     1525    buttonItem = m_itemList[itemIdx];
     1526
     1527    if (m_maxVisible == 0)
     1528    {
     1529        QString name("buttonlist button 0");
     1530        MythUIStateType *button = new MythUIStateType(this, name);
     1531        button->CopyFrom(m_buttontemplate);
     1532        m_ButtonList.append(button);
     1533        ++m_maxVisible;
     1534    }
     1535
     1536    realButton = m_ButtonList[0];
     1537    m_ButtonToItem[0] = buttonItem;
     1538}
     1539
     1540/*
     1541 * PageUp and PageDown are helpers when Dynamic layout is being used.
     1542 *
     1543 * When buttons are layed out dynamically, the number of buttons on the next
     1544 * page, may not equal the number of buttons on the current page.  Dynamic
     1545 * layout is always center-weighted, so attempt to figure out which button
     1546 * is near the middle on the next page.
     1547 */
     1548
     1549int MythUIButtonList::PageUp(void)
     1550{
     1551    int pos        = m_selPosition;
     1552    int num_items  = m_itemList.size();
     1553    int total      = 0;
     1554    MythUIGroup*     buttonstate;
     1555    MythUIStateType* realButton;
     1556    MythUIButtonListItem* buttonItem;
     1557
     1558    /*
     1559     * /On the new page/
     1560     * If the number of buttons before the selected button does not equal
     1561     * the number of buttons after the selected button, this logic can
     1562     * undershoot the new selected button.  That is better than overshooting
     1563     * though.
     1564     *
     1565     * To fix this would require laying out the new page and then figuring
     1566     * out which button should be selected, but this is already complex enough.
     1567     */
     1568
     1569    if (m_layout == LayoutHorizontal)
     1570    {
     1571        pos -= (m_leftColumns + 1);
     1572
     1573        int max_width = m_contentsRect.width() / 2;
     1574        for ( ; pos >= 0; --pos)
     1575        {
     1576            InitButton(pos, realButton, buttonItem);
     1577            buttonItem->SetToRealButton(realButton, true);
     1578            buttonstate = dynamic_cast<MythUIGroup *>
     1579                         (realButton->GetCurrentState());
     1580            if (buttonstate == NULL)
     1581            {
     1582                VERBOSE(VB_IMPORTANT,
     1583                        QString("PageUp: Failed to query buttonlist state"));
     1584                return pos;
     1585            }
     1586            if (total + m_itemHorizSpacing +
     1587                buttonstate->GetArea().width() / 2 >= max_width)
     1588                return pos + 1;
     1589
     1590            buttonItem->SetToRealButton(realButton, false);
     1591            buttonstate = dynamic_cast<MythUIGroup *>
     1592                         (realButton->GetCurrentState());
     1593            total += m_itemHorizSpacing + buttonstate->GetArea().width();
     1594        }
     1595        return 0;
     1596    }
     1597
     1598    // Grid or Vertical
     1599    int dec;
     1600
     1601    if (m_layout == LayoutGrid)
     1602    {
     1603        /*
     1604         * Adjusting using bottomRow:TopRow only works if new page
     1605         * has the same ratio as the previous page, but that is common
     1606         * with the grid layout, so go for it.  If themers start doing
     1607         * grids where this is not true, then this will need to be modified.
     1608         */
     1609        pos -= (m_columns * (m_topRows + 1 +
     1610                             qMax(m_bottomRows - m_topRows, 0)));
     1611        dec = m_columns;
     1612    }
     1613    else
     1614    {
     1615        pos -= (m_topRows + 1);
     1616        dec = 1;
     1617    }
     1618
     1619    int max_height = m_contentsRect.height() / 2;
     1620    for ( ; pos >= 0; pos -= dec)
     1621    {
     1622        InitButton(pos, realButton, buttonItem);
     1623        buttonItem->SetToRealButton(realButton, true);
     1624        buttonstate = dynamic_cast<MythUIGroup *>
     1625                      (realButton->GetCurrentState());
     1626        if (buttonstate == NULL)
     1627        {
     1628            VERBOSE(VB_IMPORTANT,
     1629                    QString("PageUp: Failed to query buttonlist state"));
     1630            return pos;
     1631        }
     1632        if (total + m_itemHorizSpacing +
     1633            buttonstate->GetArea().height() / 2 >= max_height)
     1634            return pos + dec;
     1635
     1636        buttonItem->SetToRealButton(realButton, false);
     1637        buttonstate = dynamic_cast<MythUIGroup *>
     1638                      (realButton->GetCurrentState());
     1639        total += m_itemHorizSpacing + buttonstate->GetArea().height();
     1640    }
     1641    return 0;
     1642}
     1643
     1644int MythUIButtonList::PageDown(void)
     1645{
     1646    int pos        = m_selPosition;
     1647    int num_items  = m_itemList.size();
     1648    int total      = 0;
     1649    MythUIGroup*     buttonstate;
     1650    MythUIStateType* realButton;
     1651    MythUIButtonListItem* buttonItem;
     1652
     1653    /*
     1654     * /On the new page/
     1655     * If the number of buttons before the selected button does not equal
     1656     * the number of buttons after the selected button, this logic can
     1657     * undershoot the new selected button.  That is better than overshooting
     1658     * though.
     1659     *
     1660     * To fix this would require laying out the new page and then figuring
     1661     * out which button should be selected, but this is already complex enough.
     1662     */
     1663
     1664    if (m_layout == LayoutHorizontal)
     1665    {
     1666        pos += (m_rightColumns + 1);
     1667
     1668        int max_width = m_contentsRect.width() / 2;
     1669        for ( ; pos < num_items; ++pos)
     1670        {
     1671            InitButton(pos, realButton, buttonItem);
     1672            buttonItem->SetToRealButton(realButton, true);
     1673            buttonstate = dynamic_cast<MythUIGroup *>
     1674                         (realButton->GetCurrentState());
     1675            if (buttonstate == NULL)
     1676            {
     1677                VERBOSE(VB_IMPORTANT,
     1678                        QString("PageDown: Failed to query buttonlist state"));
     1679                return pos;
     1680            }
     1681            if (total + m_itemHorizSpacing +
     1682                buttonstate->GetArea().width() / 2 >= max_width)
     1683                return pos - 1;
     1684
     1685            buttonItem->SetToRealButton(realButton, false);
     1686            buttonstate = dynamic_cast<MythUIGroup *>
     1687                         (realButton->GetCurrentState());
     1688            total += m_itemHorizSpacing + buttonstate->GetArea().width();
     1689        }
     1690        return num_items - 1;
     1691    }
     1692
     1693    // Grid or Vertical
     1694    int inc;
     1695
     1696    if (m_layout == LayoutGrid)
     1697    {
     1698        /*
     1699         * Adjusting using bottomRow:TopRow only works if new page
     1700         * has the same ratio as the previous page, but that is common
     1701         * with the grid layout, so go for it.  If themers start doing
     1702         * grids where this is not true, then this will need to be modified.
     1703         */
     1704        pos += (m_columns * (m_bottomRows + 1 +
     1705                             qMax(m_topRows - m_bottomRows, 0)));
     1706        inc = m_columns;
     1707    }
     1708    else
     1709    {
     1710        pos += (m_bottomRows + 1);
     1711        inc = 1;
     1712    }
     1713
     1714    int max_height = m_contentsRect.height() / 2;
     1715    for ( ; pos < num_items; pos += inc)
     1716    {
     1717        InitButton(pos, realButton, buttonItem);
     1718        buttonItem->SetToRealButton(realButton, true);
     1719        buttonstate = dynamic_cast<MythUIGroup *>
     1720                      (realButton->GetCurrentState());
     1721        if (buttonstate == NULL)
     1722        {
     1723            VERBOSE(VB_IMPORTANT,
     1724                    QString("PageDown: Failed to query buttonlist state"));
     1725            return pos;
     1726        }
     1727        if (total + m_itemHorizSpacing +
     1728            buttonstate->GetArea().height() / 2 >= max_height)
     1729            return pos - inc;
     1730
     1731        buttonItem->SetToRealButton(realButton, false);
     1732        buttonstate = dynamic_cast<MythUIGroup *>
     1733                      (realButton->GetCurrentState());
     1734        total += m_itemHorizSpacing + buttonstate->GetArea().height();
     1735    }
     1736    return num_items - 1;
     1737}
     1738
    4881739bool MythUIButtonList::MoveUp(MovementUnit unit, uint amount)
    4891740{
    4901741    int pos = m_selPosition;
     
    5321783                return true;
    5331784            break;
    5341785        case MovePage:
    535             m_selPosition = qMax(0, m_selPosition - (int)m_itemsVisible);
     1786            if (m_arrange == ArrangeFixed)
     1787                m_selPosition = qMax(0, m_selPosition - (int)m_itemsVisible);
     1788            else
     1789                m_selPosition = PageUp();
    5361790            break;
    5371791        case MoveMid:
    5381792            m_selPosition = (int)(m_itemList.size() / 2);
     
    5891843                return true;
    5901844            break;
    5911845        case MoveRow:
    592             if (((m_itemList.size()-1) / m_columns) > (pos / m_columns))
     1846            if (((m_itemList.size()-1) / qMax(m_columns, 0) ) > (pos / m_columns))
    5931847            {
    5941848                for (int i = 0; i < m_columns; ++i)
    5951849                {
     
    6041858                return true;
    6051859            break;
    6061860        case MovePage:
    607             m_selPosition = qMin(m_itemCount - 1,
     1861            if (m_arrange == ArrangeFixed)
     1862                m_selPosition = qMin(m_itemCount - 1,
    6081863                                     m_selPosition + (int)m_itemsVisible);
     1864            else
     1865                m_selPosition = PageDown();
    6091866            break;
    6101867        case MoveMax:
    6111868            m_selPosition = m_itemCount - 1;
     
    7271984    if (m_downArrow)
    7281985        m_downArrow->SetVisible(true);
    7291986
    730     MythUIStateType *buttontemplate = dynamic_cast<MythUIStateType *>
    731                                                 (GetChild("buttonitem"));
     1987    m_contentsRect.CalculateArea(m_Area);
    7321988
    733     if (!buttontemplate)
     1989    m_buttontemplate = dynamic_cast<MythUIStateType *>(GetChild("buttonitem"));
     1990
     1991    if (!m_buttontemplate)
    7341992    {
    7351993        VERBOSE(VB_IMPORTANT, QString("Statetype buttonitem is required in "
    7361994                                      "mythuibuttonlist: %1")
    737                                       .arg(objectName()));
     1995                .arg(objectName()));
    7381996        return;
    7391997    }
    7401998
    741     m_contentsRect.CalculateArea(m_Area);
     1999    m_buttontemplate->SetVisible(false);
    7422000
    743     buttontemplate->SetVisible(false);
     2001    if (m_arrange == ArrangeFixed)
     2002    {
    7442003
    745     MythRect buttonItemArea;
     2004        /*
     2005         * If fixed spacing is defined, then use the "active" state size
     2006         * to predictively determine the position of each button.
     2007         */
     2008        MythRect buttonItemArea;
    7462009
    747     MythUIGroup *buttonActiveState = dynamic_cast<MythUIGroup *>
    748                                         (buttontemplate->GetState("active"));
    749     if (buttonActiveState)
    750         buttonItemArea = buttonActiveState->GetArea();
    751     else
    752         buttonItemArea = buttontemplate->GetArea();
     2010        MythUIGroup *buttonActiveState = dynamic_cast<MythUIGroup *>
     2011                                         (m_buttontemplate->GetState("active"));
     2012        if (buttonActiveState)
     2013            buttonItemArea = buttonActiveState->GetArea();
     2014        else
     2015            buttonItemArea = m_buttontemplate->GetArea();
    7532016
    754     buttonItemArea.CalculateArea(m_contentsRect);
     2017        buttonItemArea.CalculateArea(m_contentsRect);
    7552018
    756     m_itemHeight = buttonItemArea.height();
    757     m_itemWidth = buttonItemArea.width();
     2019        m_itemHeight = buttonItemArea.height();
     2020        m_itemWidth = buttonItemArea.width();
    7582021
    759     CalculateVisibleItems();
     2022        CalculateVisibleItems();
    7602023
    761     int col = 1;
    762     int row = 1;
     2024        int col = 1;
     2025        int row = 1;
    7632026
    764     for (int i = 0; i < (int)m_itemsVisible; i++)
    765     {
    766         QString name = QString("buttonlist button %1").arg(i);
    767         MythUIStateType *button = new MythUIStateType(this, name);
    768         button->CopyFrom(buttontemplate);
    769 
    770         if (col > m_columns)
     2027        for (int i = 0; i < (int)m_itemsVisible; i++)
    7712028        {
    772             col = 1;
    773             row++;
    774         }
     2029            QString name = QString("buttonlist button %1").arg(i);
     2030            MythUIStateType *button = new MythUIStateType(this, name);
     2031            button->CopyFrom(m_buttontemplate);
    7752032
    776         button->SetPosition(GetButtonPosition(col, row));
     2033            if (col > m_columns)
     2034            {
     2035                col = 1;
     2036                row++;
     2037            }
    7772038
    778         col++;
     2039            button->SetPosition(GetButtonPosition(col, row));
     2040            col++;
    7792041
    780         m_ButtonList.push_back(button);
    781     }
     2042            m_ButtonList.push_back(button);
     2043        }
    7822044
    783     // The following is pretty much a hack for the benefit of MythGallery
    784     // it scales images based on the button size and we need to give it the
    785     // largest button state so that the images are not too small
    786     // This can be removed once the disk based image caching is added to mythui,
    787     // since the mythgallery thumbnail generator can be ditched.
    788     MythUIGroup *buttonSelectedState = dynamic_cast<MythUIGroup *>
    789                                         (buttontemplate->GetState("selected"));
     2045        // The following is pretty much a hack for the benefit of MythGallery
     2046        // it scales images based on the button size and we need to give it the
     2047        // largest button state so that the images are not too small
     2048        // This can be removed once the disk based image caching is added to
     2049        // mythui, since the mythgallery thumbnail generator can be ditched.
     2050        MythUIGroup *buttonSelectedState = dynamic_cast<MythUIGroup *>
     2051                                   (m_buttontemplate->GetState("selected"));
    7902052
    791     if (buttonSelectedState)
    792     {
    793         MythRect itemArea = buttonSelectedState->GetArea();
    794         itemArea.CalculateArea(m_contentsRect);
     2053        if (buttonSelectedState)
     2054        {
     2055            MythRect itemArea = buttonSelectedState->GetArea();
     2056            itemArea.CalculateArea(m_contentsRect);
    7952057
    796         if (m_itemHeight < itemArea.height())
    797             m_itemHeight = itemArea.height();
    798         if (m_itemWidth < itemArea.width())
    799             m_itemWidth = itemArea.width();
     2058            if (m_itemHeight < itemArea.height())
     2059                m_itemHeight = itemArea.height();
     2060            if (m_itemWidth < itemArea.width())
     2061                m_itemWidth = itemArea.width();
     2062        }
     2063        // End Hack
    8002064    }
    801     // End Hack
    8022065
    8032066    m_initialized = true;
    8042067}
     
    8472110            if (m_layout == LayoutHorizontal)
    8482111                handled = MoveDown(MoveItem);
    8492112            else if (m_layout == LayoutGrid)
    850                 handled = MoveDown(MoveColumn);
     2113            {
     2114                if (m_scrollStyle == ScrollFree)
     2115                    handled = MoveDown(MoveColumn);
     2116                else
     2117                    handled = MoveDown(MoveItem);
     2118            }
    8512119            else
    8522120                handled = false;
    8532121        }
     
    8562124            if (m_layout == LayoutHorizontal)
    8572125                handled = MoveUp(MoveItem);
    8582126            else if (m_layout == LayoutGrid)
    859                 handled = MoveUp(MoveColumn);
     2127            {
     2128                if (m_scrollStyle == ScrollFree)
     2129                    handled = MoveUp(MoveColumn);
     2130                else
     2131                    handled = MoveUp(MoveItem);
     2132            }
    8602133            else
    8612134                handled = false;
    8622135        }
     
    10072280        else
    10082281            m_layout = LayoutVertical;
    10092282    }
     2283    else if (element.tagName() == "arrange")
     2284    {
     2285        QString arrange = getFirstText(element).toLower();
     2286
     2287        if (arrange == "fill")
     2288            m_arrange = ArrangeFill;
     2289        else if (arrange == "spread")
     2290            m_arrange = ArrangeSpread;
     2291        else if (arrange == "stack")
     2292            m_arrange = ArrangeStack;
     2293        else
     2294            m_arrange = ArrangeFixed;
     2295
     2296    }
     2297    else if (element.tagName() == "align")
     2298    {
     2299        QString align = getFirstText(element).toLower();
     2300        m_alignment = parseAlignment(align);
     2301    }
    10102302    else if (element.tagName() == "scrollstyle")
    10112303    {
    10122304        QString layout = getFirstText(element).toLower();
    10132305
    10142306        if (layout == "center")
    10152307            m_scrollStyle = ScrollCenter;
     2308        else if (layout == "groupcenter")
     2309            m_scrollStyle = ScrollGroupCenter;
    10162310        else if (layout == "free")
    10172311            m_scrollStyle = ScrollFree;
    10182312    }
     
    10372331        m_itemVertSpacing = NormY(getFirstText(element).toInt());
    10382332    }
    10392333    else if (element.tagName() == "drawfrombottom")
     2334    {
    10402335        m_drawFromBottom = parseBool(element);
     2336        m_alignment |= Qt::AlignBottom;
     2337    }
    10412338    else
    10422339        return MythUIType::ParseElement(element);
    10432340
     
    10632360        return;
    10642361
    10652362    m_layout = lb->m_layout;
     2363    m_arrange = lb->m_arrange;
     2364    m_alignment = lb->m_alignment;
    10662365
    10672366    m_contentsRect = lb->m_contentsRect;
    10682367
     
    10712370    m_itemHorizSpacing = lb->m_itemHorizSpacing;
    10722371    m_itemVertSpacing = lb->m_itemVertSpacing;
    10732372    m_itemsVisible = lb->m_itemsVisible;
     2373    m_maxVisible = lb->m_maxVisible;
    10742374
    10752375    m_active = lb->m_active;
    10762376    m_showArrow = lb->m_showArrow;
     
    16082908        }
    16092909        ++state_it;
    16102910    }
     2911
     2912
    16112913}