diff --git a/include/wx/qt/listctrl.h b/include/wx/qt/listctrl.h index dc5414cd87..7bb46d23e8 100644 --- a/include/wx/qt/listctrl.h +++ b/include/wx/qt/listctrl.h @@ -209,6 +209,8 @@ public: // Ensures this item is visible bool EnsureVisible(long item); + bool IsVisible(long item) const override; + // Find an item whose label matches this string, starting from the item after 'start' // or the beginning if 'start' is -1. long FindItem(long start, const wxString& str, bool partial = false); @@ -262,6 +264,11 @@ public: // data is arbitrary data to be passed to the sort function. bool SortItems(wxListCtrlCompare fn, wxIntPtr data); + // Sort indicator in header. + virtual void ShowSortIndicator(int col, bool ascending = true) override; + virtual int GetSortIndicator() const override; + virtual bool IsAscendingSortIndicator() const override; + virtual QWidget *GetHandle() const override; protected: diff --git a/interface/wx/listctrl.h b/interface/wx/listctrl.h index 730b9ce3bc..4d8e88ceae 100644 --- a/interface/wx/listctrl.h +++ b/interface/wx/listctrl.h @@ -632,6 +632,10 @@ public: Gets the number of items that can fit vertically in the visible area of the list control (list or report view) or the total number of items in the list control (icon or small icon view). + + @note The caller must ensure that there is at least one item in the control + to be able to calculate the count per page under wxQt, otherwise 0 will + be returned. */ int GetCountPerPage() const; @@ -1073,7 +1077,7 @@ public: @c wxLIST_AUTOSIZE will resize the column to the length of its longest item. @c wxLIST_AUTOSIZE_USEHEADER will resize the column to the length of the - header (Win32) or 80 pixels (other platforms). + header (wxMSW and wxQt) or 80 pixels (other platforms). In small or normal icon view, @a col must be -1, and the column width is set for all columns. diff --git a/samples/listctrl/listtest.cpp b/samples/listctrl/listtest.cpp index 86376f59fe..963c0168cd 100644 --- a/samples/listctrl/listtest.cpp +++ b/samples/listctrl/listtest.cpp @@ -151,6 +151,8 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(LIST_TOGGLE_HEADER, MyFrame::OnToggleHeader) EVT_MENU(LIST_TOGGLE_BELL, MyFrame::OnToggleBell) EVT_MENU(LIST_CHECKVISIBILITY, MyFrame::OnCheckVisibility) + EVT_MENU(LIST_AUTOSIZE, MyFrame::OnAutoResize) + EVT_MENU(LIST_AUTOSIZE_USEHEADER, MyFrame::OnAutoResize) EVT_MENU(LIST_FIND, MyFrame::OnFind) EVT_MENU(LIST_TOGGLE_CHECKBOX, MyFrame::OnToggleItemCheckBox) EVT_MENU(LIST_GET_CHECKBOX, MyFrame::OnGetItemCheckBox) @@ -260,6 +262,9 @@ MyFrame::MyFrame(const wxString& title) menuList->AppendCheckItem(LIST_TOGGLE_BELL, "Toggle &bell on no match"); menuList->Append( LIST_CHECKVISIBILITY, "Check if lines 2 and 9 are visible" ); menuList->AppendSeparator(); + menuList->Append( LIST_AUTOSIZE, "Auto resize column 2\tCtrl-R" ); + menuList->Append( LIST_AUTOSIZE_USEHEADER, "Auto resize column 2 (use header)\tCtrl-Shift-R" ); + menuList->AppendSeparator(); menuList->AppendCheckItem(LIST_TOGGLE_CHECKBOXES, "&Enable Checkboxes"); menuList->Check(LIST_TOGGLE_CHECKBOXES, true); @@ -379,6 +384,20 @@ void MyFrame::OnCheckVisibility(wxCommandEvent& WXUNUSED(event)) wxLogMessage( "Line 9 is not visible" ); } +void MyFrame::OnAutoResize(wxCommandEvent& event) +{ + if ( event.GetId() == LIST_AUTOSIZE ) + { + wxLogMessage( "Column 2 resized to content" ); + m_listCtrl->SetColumnWidth(1, wxLIST_AUTOSIZE); + } + else + { + wxLogMessage( "Column 2 resized to header" ); + m_listCtrl->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); + } +} + void MyFrame::OnGoTo(wxCommandEvent& WXUNUSED(event)) { if ( m_listCtrl->IsEmpty() ) diff --git a/samples/listctrl/listtest.h b/samples/listctrl/listtest.h index 9b53636415..1a7c32b9af 100644 --- a/samples/listctrl/listtest.h +++ b/samples/listctrl/listtest.h @@ -117,6 +117,7 @@ protected: void OnVirtualView(wxCommandEvent& event); void OnSmallVirtualView(wxCommandEvent& event); void OnCheckVisibility(wxCommandEvent& event); + void OnAutoResize(wxCommandEvent& event); void OnSetItemsCount(wxCommandEvent& event); @@ -243,5 +244,7 @@ enum LIST_THAW, LIST_TOGGLE_LINES, LIST_CHECKVISIBILITY, + LIST_AUTOSIZE, + LIST_AUTOSIZE_USEHEADER, LIST_CTRL = 1000 }; diff --git a/src/qt/listctrl.cpp b/src/qt/listctrl.cpp index 12cc8056fb..19b32d3228 100644 --- a/src/qt/listctrl.cpp +++ b/src/qt/listctrl.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifndef WX_PRECOMP #include "wx/bitmap.h" @@ -152,12 +153,18 @@ public: { } - int rowCount(const QModelIndex& WXUNUSED(parent)) const override + int rowCount(const QModelIndex& parent = QModelIndex()) const override { + if ( parent.isValid() ) + return 0; + return static_cast(m_rows.size()); } - int columnCount(const QModelIndex& WXUNUSED(parent)) const override + int columnCount(const QModelIndex& parent = QModelIndex()) const override { + if ( parent.isValid() ) + return 0; + return static_cast(m_headers.size()); } @@ -166,7 +173,7 @@ public: const int row = index.row(); const int col = index.column(); - wxCHECK_MSG(row >= 0 && static_cast(row) < m_rows.size(), + wxCHECK_MSG(row >= 0 && row < rowCount(), QVariant(), "Invalid row index" ); @@ -226,7 +233,7 @@ public: case Qt::CheckStateRole: return col == 0 && m_listCtrl->HasCheckBoxes() - ? rowItem.m_checked + ? rowItem.CheckState() : QVariant(); default: @@ -299,6 +306,25 @@ public: case Qt::TextAlignmentRole: return header.m_align; + + case Qt::DecorationRole: + { + wxImageList *imageList = GetImageList(); + if ( imageList == nullptr ) + return QVariant(); + + int imageIndex = -1; + + if ( imageIndex == -1 ) + imageIndex = header.m_image; + + if ( imageIndex == -1 ) + return QVariant(); + + wxBitmap image = imageList->GetBitmap(imageIndex); + wxCHECK_MSG(image.IsOk(), QVariant(), "Invalid image"); + return QVariant::fromValue(*image.GetHandle()); + } } return QVariant(); } @@ -367,8 +393,13 @@ public: false, "Invalid column"); ColumnItem &column = m_headers[index]; - column.m_label = wxQtConvertString(info.GetText()); - column.m_align = wxQtConvertTextAlign(info.GetAlign()); + + if ( info.m_mask & wxLIST_MASK_TEXT ) + column.m_label = wxQtConvertString(info.GetText()); + if ( info.m_mask & wxLIST_MASK_FORMAT ) + column.m_align = wxQtConvertTextAlign(info.GetAlign()); + if ( info.m_mask & wxLIST_MASK_IMAGE ) + column.m_image = info.m_image; headerDataChanged(Qt::Horizontal, index, index); return true; @@ -417,12 +448,33 @@ public: const int row = static_cast(info.GetId()); const int col = info.m_col; - wxCHECK_MSG( static_cast(row) < m_rows.size(), - false, "Invalid row"); - wxCHECK_MSG( static_cast(col) < m_headers.size(), - false, "Invalid col"); + wxCHECK_MSG( row < rowCount(), false, "Invalid row"); + wxCHECK_MSG( col < columnCount(), false, "Invalid col"); const QModelIndex modelIndex = index(row, col); + + if ( info.m_mask & wxLIST_MASK_STATE ) + { + if ( (info.m_stateMask & wxLIST_STATE_FOCUSED) && + (info.m_state & wxLIST_STATE_FOCUSED) ) + m_view->setCurrentIndex(modelIndex); + if ( info.m_stateMask & wxLIST_STATE_SELECTED ) + { + QItemSelectionModel *selection = m_view->selectionModel(); + const QItemSelectionModel::SelectionFlag flag = + info.m_state & wxLIST_STATE_SELECTED + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect; + selection->select(modelIndex, flag|QItemSelectionModel::Rows); + } + } + + if ( IsVirtual() ) + { + // wxLIST_MASK_STATE is the only mask supported by virtual wxListCtrl + return true; + } + RowItem &rowItem = m_rows[row]; ColumnItem &columnItem = rowItem[col]; @@ -446,22 +498,6 @@ public: roles.push_back(Qt::UserRole); } - if ( info.m_mask & wxLIST_MASK_STATE ) - { - if ( (info.m_stateMask & wxLIST_STATE_FOCUSED) && - (info.m_state & wxLIST_STATE_FOCUSED) ) - m_view->setCurrentIndex(modelIndex); - if ( info.m_stateMask & wxLIST_STATE_SELECTED ) - { - QItemSelectionModel *selection = m_view->selectionModel(); - const QItemSelectionModel::SelectionFlag flag = - info.m_state & wxLIST_STATE_SELECTED - ? QItemSelectionModel::Select - : QItemSelectionModel::Deselect; - selection->select(modelIndex, flag|QItemSelectionModel::Rows); - } - } - if ( info.m_mask & wxLIST_MASK_IMAGE ) { columnItem.m_image = info.m_image; @@ -669,13 +705,17 @@ public: endInsertColumns(); + m_listCtrl->SetColumnWidth(newColumnIndex, info.m_width); + return true; } void SortItems(wxListCtrlCompare fn, wxIntPtr data) { CompareAdapter compare(fn, data); + beginResetModel(); std::sort(m_rows.begin(), m_rows.end(), compare); + endResetModel(); } bool IsItemChecked(long item) const @@ -712,7 +752,7 @@ public: protected: wxImageList *GetImageList() const { - const int requiredList = m_listCtrl->HasFlag(wxLC_SMALL_ICON) + const int requiredList = m_listCtrl->HasFlag(wxLC_SMALL_ICON | wxLC_LIST | wxLC_REPORT) ? wxIMAGE_LIST_SMALL : wxIMAGE_LIST_NORMAL; return m_listCtrl->GetImageList(requiredList); @@ -804,6 +844,11 @@ private: return m_columns[index]; } + Qt::CheckState CheckState() const + { + return m_checked ? Qt::Checked : Qt::Unchecked; + } + std::vector m_columns; void *m_data; bool m_checked; @@ -845,8 +890,11 @@ public: { } - int rowCount(const QModelIndex& WXUNUSED(parent)) const override + int rowCount(const QModelIndex& parent = QModelIndex()) const override { + if ( parent.isValid() ) + return 0; + return m_rowCount; } @@ -911,10 +959,21 @@ private: class wxQtListTreeWidget : public wxQtEventSignalHandler< QTreeView, wxListCtrl > { + using BaseClass = wxQtEventSignalHandler< QTreeView, wxListCtrl >; + + // Data type passed to EmitListEvent() as extra data + struct ListEventData + { + int m_colOrFirstRow; // column index or first (de)selected row + int m_colWidthOrLastRow; // column width or last (de)selected row + }; + public: wxQtListTreeWidget( wxWindow *parent, wxListCtrl *handler ); - void EmitListEvent(wxEventType typ, const QModelIndex &index) const; + bool EmitListEvent(wxEventType type, + const QModelIndex &index, + const ListEventData* data = nullptr) const; void closeEditor( QWidget *editor, @@ -970,6 +1029,17 @@ public: return m_itemDelegate.GetEditControl(); } + void EnableEditLabel(bool enable) + { + if ( enable ) + { + setItemDelegate(&m_itemDelegate); + setEditTriggers(SelectedClicked | EditKeyPressed); + } + else + setEditTriggers(NoEditTriggers); + } + virtual void paintEvent(QPaintEvent *event) override { QTreeView::paintEvent(event); @@ -980,11 +1050,134 @@ public: return header() != nullptr ? header()->height() : 0; } + int GetRowCount() const + { + if ( model() ) + return model()->rowCount(); + + return 0; + } + + int GetCountPerPage() const + { + // this may not be exact but should be a good approximation: + const int h = rowHeight(model()->index(0, 0)); + if ( h ) + return viewport()->height() / h; + else + return 0; + } + +protected: + virtual void currentChanged(const QModelIndex& current, + const QModelIndex& previous) override + { + EmitListEvent(wxEVT_LIST_ITEM_FOCUSED, current); + QTreeView::currentChanged(current, previous); + } + + virtual void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) override; + + // wxQtHeaderView is for wxEVT_LIST_COL_XXX events generation only, + // i.e. it doesn't try to add anything fancy or new functionality + // not supported by QHeaderView + class wxQtHeaderView : public QHeaderView + { + public: + wxQtHeaderView(wxQtListTreeWidget* parent) + : QHeaderView(Qt::Horizontal, parent) + , m_parent(parent) + { + setSectionsClickable(true); + setContextMenuPolicy(Qt::CustomContextMenu); + setSortIndicatorShown(true); + + connect(this, &QHeaderView::sectionClicked, + this, &wxQtHeaderView::sectionClicked); + connect(this, &QHeaderView::customContextMenuRequested, + this, &wxQtHeaderView::sectionRightClicked); + connect(this, &QHeaderView::sectionResized, + this, &wxQtHeaderView::sectionResized); + } + + protected: + virtual void mouseReleaseEvent(QMouseEvent* event) override + { + if ( m_isDragging ) + { + m_isDragging = false; + + const ListEventData data { m_parent->columnAt(event->x()), -1 }; + + m_parent->EmitListEvent(wxEVT_LIST_COL_END_DRAG, QModelIndex(), &data); + } + + QHeaderView::mouseReleaseEvent(event); + } + + private: + void sectionClicked(int logicalIndex) + { + const ListEventData data { logicalIndex, -1 }; + + m_parent->EmitListEvent(wxEVT_LIST_COL_CLICK, QModelIndex(), &data); + } + + void sectionRightClicked(const QPoint& pos) + { + const ListEventData data { m_parent->columnAt(pos.x()), -1 }; + + m_parent->EmitListEvent(wxEVT_LIST_COL_RIGHT_CLICK, QModelIndex(), &data); + } + + void sectionResized(int logicalIndex, int oldSize, int newSize) + { + ListEventData data; + + if ( m_isDragging ) + { + data = { logicalIndex, newSize }; + m_parent->EmitListEvent(wxEVT_LIST_COL_DRAGGING, QModelIndex(), &data); + return; + } + + if ( !underMouse() ) + { + // sectionResized() also called if the control is being resized and + // the last section in the header is strechable, so just return and + // do nothing in this case. + return; + } + + data = { logicalIndex, oldSize }; + + if ( sectionResizeMode(logicalIndex) == QHeaderView::Fixed || + !m_parent->EmitListEvent(wxEVT_LIST_COL_BEGIN_DRAG, QModelIndex(), &data) ) + { + wxQtEnsureSignalsBlocked blocker(this); + resizeSection(logicalIndex, oldSize); + // This only takes effect after wxEVT_LIST_COL_END_DRAG is generated, + // after which the user can no longer drag the column. + setSectionResizeMode(logicalIndex, QHeaderView::Fixed); + return; + } + + m_isDragging = true; + } + + wxQtListTreeWidget* const m_parent; + + bool m_isDragging = false; + }; + private: - void itemClicked(const QModelIndex &index); - void itemActivated(const QModelIndex &index); + void itemActivated(const QModelIndex &index) + { EmitListEvent(wxEVT_LIST_ITEM_ACTIVATED, index); } void itemPressed(const QModelIndex &index); + void OnKeyDown(wxKeyEvent& event); // to generate wxEVT_LIST_KEY_DOWN event + wxQtStyledItemDelegate m_itemDelegate; wxRecursionGuardFlag m_closingEditor; }; @@ -994,16 +1187,19 @@ wxQtListTreeWidget::wxQtListTreeWidget( wxWindow *parent, wxListCtrl *handler ) m_itemDelegate(handler), m_closingEditor(0) { - connect(this, &QTreeView::clicked, this, &wxQtListTreeWidget::itemClicked); + setHeader(new wxQtHeaderView(this)); + + setSortingEnabled(true); + connect(this, &QTreeView::pressed, this, &wxQtListTreeWidget::itemPressed); connect(this, &QTreeView::activated, this, &wxQtListTreeWidget::itemActivated); - setItemDelegate(&m_itemDelegate); - setEditTriggers(NoEditTriggers); + handler->Bind(wxEVT_KEY_DOWN, &wxQtListTreeWidget::OnKeyDown, this); } -void wxQtListTreeWidget::EmitListEvent(wxEventType typ, - const QModelIndex &index) const +bool wxQtListTreeWidget::EmitListEvent(wxEventType type, + const QModelIndex &index, + const ListEventData* data) const { wxListCtrl *handler = GetHandler(); if ( handler ) @@ -1011,24 +1207,104 @@ void wxQtListTreeWidget::EmitListEvent(wxEventType typ, // prepare the event // ----------------- wxListEvent event; - InitListEvent(event, handler, typ, index); - EmitEvent(event); - } -} + InitListEvent(event, handler, type, index); -void wxQtListTreeWidget::itemClicked(const QModelIndex &index) -{ - EmitListEvent(wxEVT_LIST_ITEM_SELECTED, index); + if ( !index.isValid() && data ) + { + if ( type == wxEVT_LIST_ITEM_SELECTED || + type == wxEVT_LIST_ITEM_DESELECTED ) + { + // Instead of sending hundreds of (de)selection messages, send only + // one for each range which is more efficient (see issue #4541) + // data->m_colOrFirstRow is the first row in the (de)selection + // data->m_colWidthOrLastRow is the last row in the (de)selection + wxFAIL_MSG("No implementation yet"); + } + else if ( data->m_colOrFirstRow >= 0 && + data->m_colOrFirstRow < handler->GetColumnCount() ) + { + event.m_col = data->m_colOrFirstRow; + event.m_item.m_width = data->m_colWidthOrLastRow; + event.m_pointDrag = wxQtConvertPoint( QCursor::pos() ); + + if ( type == wxEVT_LIST_COL_RIGHT_CLICK ) + { + // handlers of this event expect m_pointDrag in client coordinates + event.m_pointDrag = handler->ScreenToClient(event.m_pointDrag); + } + } + } + + return !EmitEvent(event) || event.IsAllowed(); + } + + return false; } void wxQtListTreeWidget::itemPressed(const QModelIndex &index) { - EmitListEvent(wxEVT_LIST_ITEM_SELECTED, index); + wxEventType eventType; + Qt::MouseButtons mouseButton = QGuiApplication::mouseButtons(); + + switch( mouseButton ) + { + case Qt::RightButton: + eventType = wxEVT_LIST_ITEM_RIGHT_CLICK; + break; + case Qt::MiddleButton: + eventType = wxEVT_LIST_ITEM_MIDDLE_CLICK; + break; + default: + return; + } + + EmitListEvent(eventType, index); } -void wxQtListTreeWidget::itemActivated(const QModelIndex &index) +void wxQtListTreeWidget::selectionChanged(const QItemSelection& selected, + const QItemSelection& deselected) { - EmitListEvent(wxEVT_LIST_ITEM_ACTIVATED, index); + // A QItemSelection is basically a list of selection ranges, i.e. QItemSelectionRange. + + for ( const auto& range : deselected ) + { + for ( int row = range.top(); row <= range.bottom(); ++row ) + { + EmitListEvent(wxEVT_LIST_ITEM_DESELECTED, model()->index(row, 0)); + } + } + + for ( const auto& range : selected ) + { + for ( int row = range.top(); row <= range.bottom(); ++row ) + { + EmitListEvent(wxEVT_LIST_ITEM_SELECTED, model()->index(row, 0)); + } + } + + QTreeView::selectionChanged(selected, deselected); +} + +void wxQtListTreeWidget::OnKeyDown(wxKeyEvent& event) +{ + // send a list event + wxListEvent le; + InitListEvent(le, GetHandler(), wxEVT_LIST_KEY_DOWN, currentIndex()); + + const long itemId = le.m_item.m_itemId; + + if ( itemId != -1 ) + { + // fill the other fields too + le.m_item.m_text = GetHandler()->GetItemText(itemId, 0); + le.m_item.m_data = GetHandler()->GetItemData(itemId); + } + + le.m_code = event.GetKeyCode(); + + EmitEvent( le ); + + event.Skip(); } // Specialization: to safely remove and delete the model associated with QTreeView @@ -1075,11 +1351,9 @@ bool wxListCtrl::Create(wxWindow *parent, m_qtTreeWidget->setModel(m_model); m_model->SetView(m_qtTreeWidget); - if (style & wxLC_NO_HEADER) - m_qtTreeWidget->setHeaderHidden(true); - m_qtTreeWidget->setRootIsDecorated(false); m_qtTreeWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + m_qtTreeWidget->setTabKeyNavigation(true); if ( !QtCreateControl(parent, id, pos, size, style, validator, name) ) return false; @@ -1133,13 +1407,44 @@ int wxListCtrl::GetColumnWidth(int col) const bool wxListCtrl::SetColumnWidth(int col, int width) { - if ( width < 0 ) + const auto header = m_qtTreeWidget->header(); + + if ( header && + col == GetColumnIndexFromOrder(col) && + col == GetColumnCount() - 1 ) { - m_qtTreeWidget->resizeColumnToContents(width); - return true; + // Always stretch the last section if _width_ is either + // wxLIST_AUTOSIZE or wxLIST_AUTOSIZE_USEHEADER + header->setStretchLastSection( width < 0 ); } - m_qtTreeWidget->setColumnWidth(col, width); + if ( width >= 0 ) + { + m_qtTreeWidget->setColumnWidth(col, width); + } + else + { + if ( width == wxLIST_AUTOSIZE_USEHEADER ) + { + const auto header = m_qtTreeWidget->header(); + const QHeaderView::ResizeMode oldResizeMode = header->sectionResizeMode(col); + + header->setSectionResizeMode(col, QHeaderView::ResizeToContents); + header->resizeSection(col, header->defaultSectionSize()); // passing any value > 0 is ok + header->setSectionResizeMode(col, oldResizeMode); + } + else // wxLIST_AUTOSIZE + { + // Temporarily hide the header if it's shown as we don't want the header section + // to be considered by resizeColumnToContents() because it's size will be honored + // if it's larger than the column content. + const bool wasHidden = m_qtTreeWidget->isHeaderHidden(); + m_qtTreeWidget->setHeaderHidden(true); + m_qtTreeWidget->resizeColumnToContents(col); + m_qtTreeWidget->setHeaderHidden(wasHidden); + } + } + return true; } @@ -1165,12 +1470,9 @@ bool wxListCtrl::SetColumnsOrder(const wxArrayInt& WXUNUSED(orders)) int wxListCtrl::GetCountPerPage() const { - // this may not be exact but should be a good approximation: - const int h = m_qtTreeWidget->visualRect(m_model->index(0, 0)).height(); - if ( h ) - return m_qtTreeWidget->height() / h; - else - return 0; + wxCHECK_MSG(m_qtTreeWidget->GetRowCount() > 0, 0, + "wxListCtrl needs at least one item to calculate the count per page"); + return m_qtTreeWidget->GetCountPerPage(); } wxRect wxListCtrl::GetViewRect() const @@ -1301,7 +1603,7 @@ bool wxListCtrl::SetItemData(long item, long data) bool wxListCtrl::GetItemRect(long item, wxRect& rect, int WXUNUSED(code)) const { wxCHECK_MSG(item >= 0 && (item < GetItemCount()), false, - "invalid item in GetSubItemRect"); + "invalid item in GetItemRect"); const int columnCount = m_model->columnCount(QModelIndex()); if ( columnCount == 0 ) @@ -1320,7 +1622,7 @@ bool wxListCtrl::GetItemRect(long item, wxRect& rect, int WXUNUSED(code)) const bool wxListCtrl::GetSubItemRect(long item, long subItem, wxRect& rect, - int WXUNUSED(code)) const + int code) const { wxCHECK_MSG(item >= 0 && item < GetItemCount(), false, "invalid row index in GetSubItemRect"); @@ -1331,6 +1633,45 @@ bool wxListCtrl::GetSubItemRect(long item, const QModelIndex index = m_qtTreeWidget->model()->index(item, subItem); rect = wxQtConvertRect(m_qtTreeWidget->visualRect(index)); rect.Offset(0, m_qtTreeWidget->GetHeaderHeight()); + + switch ( code ) + { + case wxLIST_RECT_BOUNDS: + // Nothing to do. + break; + + case wxLIST_RECT_ICON: + case wxLIST_RECT_LABEL: + { + QVariant var = index.data(Qt::DecorationRole); + if ( var.isValid() ) + { + const int iconWidth = m_qtTreeWidget->iconSize().width(); + + if ( code == wxLIST_RECT_ICON ) + { + rect.width = iconWidth; + } + else // wxLIST_RECT_LABEL + { + rect.x += iconWidth; + rect.width -= iconWidth; + } + } + else // No icon + { + if ( code == wxLIST_RECT_ICON ) + rect = wxRect(); + //else: label rect is the same as the full one + } + } + break; + + default: + wxFAIL_MSG(wxS("Unknown rectangle requested")); + return false; + } + return true; } @@ -1359,7 +1700,11 @@ int wxListCtrl::GetItemCount() const int wxListCtrl::GetColumnCount() const { - return m_model->columnCount(QModelIndex()); + // wxLC_LIST is special as we want to return 1 for it, for compatibility + // with the native wxMSW version and not the real number of columns, which + // is 0. For the other non-wxLC_REPORT modes returning 0 is fine, however, + // as wxMSW does it too. + return HasFlag(wxLC_LIST) ? 1 : m_model->columnCount(QModelIndex()); } wxSize wxListCtrl::GetItemSpacing() const @@ -1495,6 +1840,7 @@ void wxListCtrl::SetWindowStyleFlag(long style) { m_windowStyle = style; m_qtTreeWidget->setHeaderHidden((style & wxLC_NO_HEADER) != 0); + m_qtTreeWidget->EnableEditLabel((style & wxLC_EDIT_LABELS) != 0); m_qtTreeWidget->setSelectionMode((style & wxLC_SINGLE_SEL) != 0 ? QAbstractItemView::SingleSelection : QAbstractItemView::ExtendedSelection @@ -1542,9 +1888,17 @@ long wxListCtrl::GetNextItem(long item, int WXUNUSED(geometry), int state) const return -1; } -void wxListCtrl::DoUpdateImages(int WXUNUSED(which)) +void wxListCtrl::DoUpdateImages(int which) { - // TODO: Ensure the icons are actually updated. + wxImageList* const imageList = GetUpdatedImageList(which); + + if ( imageList ) + { + int width, height; + imageList->GetSize(0, width, height); + m_qtTreeWidget->setIconSize(QSize(width, height)); + m_qtTreeWidget->update(); + } } void wxListCtrl::RefreshItem(long item) @@ -1631,6 +1985,7 @@ wxTextCtrl* wxListCtrl::EditLabel(long item, // Open the editor first so that it's available when handling events as per // wx standard. const QModelIndex index = m_model->index(item, 0); + m_qtTreeWidget->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); m_qtTreeWidget->openPersistentEditor(index); wxListEvent event; @@ -1662,6 +2017,17 @@ bool wxListCtrl::EnsureVisible(long item) return true; } +bool wxListCtrl::IsVisible(long item) const +{ + wxRect itemRect; + if ( !GetItemRect(item, itemRect) ) + return false; + + wxRect viewportRect = wxQtConvertRect( m_qtTreeWidget->viewport()->rect() ); + viewportRect.y += m_qtTreeWidget->GetHeaderHeight(); + return !viewportRect.Intersect(itemRect).IsEmpty(); +} + long wxListCtrl::FindItem(long start, const wxString& str, bool partial) { return m_model->FindItem(start, wxQtConvertString(str), partial); @@ -1771,6 +2137,39 @@ bool wxListCtrl::SortItems(wxListCtrlCompare fn, wxIntPtr data) return true; } +void wxListCtrl::ShowSortIndicator(int col, bool ascending) +{ + // It seems that wx and Qt are not using the same meaning for the + // "ascending" order, for that we pass to setSortIndicator() the + // inverted logic to make things work correctly. + const auto header = m_qtTreeWidget->header(); + if ( header ) + header->setSortIndicator(col, ascending ? Qt::DescendingOrder + : Qt::AscendingOrder); +} + +int wxListCtrl::GetSortIndicator() const +{ + const auto header = m_qtTreeWidget->header(); + if ( header && header->isSortIndicatorShown() ) + { + // If no section has a sort indicator, sortIndicatorSection() + // returns section 0 by default. + return header->sortIndicatorSection(); + } + + return -1; +} + +bool wxListCtrl::IsAscendingSortIndicator() const +{ + const auto header = m_qtTreeWidget->header(); + if ( header ) + return header->sortIndicatorOrder() == Qt::AscendingOrder; + + return true; +} + QWidget *wxListCtrl::GetHandle() const { return m_qtTreeWidget;