Files
wxWidgets/src/qt/listctrl.cpp
Vadim Zeitlin cfbe47560e Merge branch 'qt_list_ctrl_hit_test_offset' of https://github.com/GeoTeric/wxWidgets into qt-listctrl
Fix wxListCtrl::HitTest() which was off roughly by 1 due to not taking
into account the header height.

Closes https://github.com/wxWidgets/wxWidgets/pull/1350
2019-09-22 01:20:42 +02:00

1833 lines
46 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/qt/listctrl.cpp
// Author: Mariano Reingart, Peter Most
// Copyright: (c) 2010 wxWidgets dev team
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QTreeView>
#include <QtWidgets/QItemDelegate>
#include <QtWidgets/QStyledItemDelegate>
#ifndef WX_PRECOMP
#include "wx/bitmap.h"
#endif // WX_PRECOMP
#include "wx/app.h"
#include "wx/listctrl.h"
#include "wx/imaglist.h"
#include "wx/recguard.h"
#include "wx/qt/private/winevent.h"
#include "wx/qt/private/treeitemfactory.h"
namespace
{
Qt::AlignmentFlag wxQtConvertTextAlign(wxListColumnFormat align)
{
switch (align)
{
case wxLIST_FORMAT_LEFT:
return Qt::AlignLeft;
case wxLIST_FORMAT_RIGHT:
return Qt::AlignRight;
case wxLIST_FORMAT_CENTRE:
return Qt::AlignCenter;
}
return Qt::AlignLeft;
}
wxListColumnFormat wxQtConvertAlignFlag(int align)
{
switch (align)
{
case Qt::AlignLeft:
return wxLIST_FORMAT_LEFT;
case Qt::AlignRight:
return wxLIST_FORMAT_RIGHT;
case Qt::AlignCenter:
return wxLIST_FORMAT_CENTRE;
}
return wxLIST_FORMAT_LEFT;
}
void InitListEvent(wxListEvent& event,
wxListCtrl* listctrl,
wxEventType eventType,
const QModelIndex& index = QModelIndex())
{
event.SetEventObject(listctrl);
event.SetEventType(eventType);
event.SetId(listctrl->GetId());
if ( index.isValid() )
{
event.m_itemIndex = index.row();
event.m_col = index.column();
event.m_item.SetId(event.m_itemIndex);
event.m_item.SetMask(wxLIST_MASK_TEXT |
wxLIST_MASK_IMAGE |
wxLIST_MASK_DATA);
listctrl->GetItem(event.m_item);
}
}
} // anonymous namespace
class wxQtStyledItemDelegate : public QStyledItemDelegate
{
public:
explicit wxQtStyledItemDelegate(wxWindow* parent)
: m_parent(parent),
m_textCtrl(NULL)
{
}
QWidget* createEditor(QWidget *parent,
const QStyleOptionViewItem &WXUNUSED(option),
const QModelIndex &index) const wxOVERRIDE
{
if (m_textCtrl != NULL)
destroyEditor(m_textCtrl->GetHandle(), m_currentModelIndex);
m_currentModelIndex = index;
m_textCtrl = new wxQtListTextCtrl(m_parent, parent);
m_textCtrl->SetFocus();
return m_textCtrl->GetHandle();
}
void destroyEditor(QWidget *WXUNUSED(editor),
const QModelIndex &WXUNUSED(index)) const wxOVERRIDE
{
if (m_textCtrl != NULL)
{
m_currentModelIndex = QModelIndex(); // invalidate the index
wxTheApp->ScheduleForDestruction(m_textCtrl);
m_textCtrl = NULL;
}
}
void setModelData(QWidget *WXUNUSED(editor),
QAbstractItemModel *WXUNUSED(model),
const QModelIndex &WXUNUSED(index)) const wxOVERRIDE
{
// Don't set model data until wx has had a chance to send out events
}
wxTextCtrl* GetEditControl() const
{
return m_textCtrl;
}
QModelIndex GetCurrentModelIndex() const
{
return m_currentModelIndex;
}
void AcceptModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
QStyledItemDelegate::setModelData(editor, model, index);
}
private:
wxWindow* m_parent;
mutable wxTextCtrl* m_textCtrl;
mutable QModelIndex m_currentModelIndex;
};
class wxQtListModel : public QAbstractTableModel
{
public:
explicit wxQtListModel(wxListCtrl *listCtrl) :
m_view(NULL),
m_listCtrl(listCtrl)
{
}
int rowCount(const QModelIndex& WXUNUSED(parent)) const wxOVERRIDE
{
return static_cast<int>(m_rows.size());
}
int columnCount(const QModelIndex& WXUNUSED(parent)) const wxOVERRIDE
{
return static_cast<int>(m_headers.size());
}
QVariant data(const QModelIndex &index, int role) const wxOVERRIDE
{
const int row = index.row();
const int col = index.column();
wxCHECK_MSG(row >= 0 && static_cast<size_t>(row) < m_rows.size(),
QVariant(),
"Invalid row index"
);
const RowItem &rowItem = m_rows[row];
const ColumnItem &columnItem = rowItem[col];
const bool isSelected = IsSelected(index);
switch ( role )
{
case Qt::DisplayRole:
return QVariant::fromValue(columnItem.m_label);
case Qt::EditRole:
return QVariant::fromValue(columnItem.m_label);
case Qt::DecorationRole:
{
wxImageList *imageList = GetImageList();
if ( imageList == NULL )
return QVariant();
int imageIndex = -1;
if ( columnItem.m_selectedImage != -1 )
{
if ( isSelected )
imageIndex = columnItem.m_selectedImage;
}
if ( imageIndex == -1 )
imageIndex = columnItem.m_image;
if ( imageIndex == -1 )
return QVariant();
wxBitmap image = imageList->GetBitmap(imageIndex);
wxCHECK_MSG(image.IsOk(), QVariant(), "Invalid image");
return QVariant::fromValue(*image.GetHandle());
}
case Qt::FontRole:
return QVariant::fromValue(columnItem.m_font);
case Qt::BackgroundRole:
return columnItem.m_backgroundColour.isValid() && !isSelected
? QVariant::fromValue(columnItem.m_backgroundColour)
: QVariant();
case Qt::ForegroundRole:
return columnItem.m_textColour.isValid() && !isSelected
? QVariant::fromValue(columnItem.m_textColour)
: QVariant();
case Qt::TextAlignmentRole:
return columnItem.m_align;
case Qt::CheckStateRole:
return col == 0 && m_listCtrl->HasCheckBoxes()
? rowItem.m_checked
: QVariant();
default:
return QVariant();
}
}
bool setData(
const QModelIndex &index,
const QVariant &value,
int role
) wxOVERRIDE
{
const int row = index.row();
const int col = index.column();
wxCHECK_MSG(
row >= 0 && static_cast<size_t>(row) < m_rows.size(),
false,
"Invalid row index"
);
wxCHECK_MSG(
col >= 0 && static_cast<size_t>(col) < m_rows[row].m_columns.size(),
false,
"Invalid column index"
);
if ( role == Qt::DisplayRole || role == Qt::EditRole )
{
m_rows[row][col].m_label = value.toString();
return true;
}
if ( role == Qt::CheckStateRole && col == 0 )
{
m_rows[row].m_checked =
static_cast<Qt::CheckState>(value.toUInt()) == Qt::Checked;
wxListEvent event;
InitListEvent(event,
m_listCtrl,
m_rows[row].m_checked ? wxEVT_LIST_ITEM_CHECKED
: wxEVT_LIST_ITEM_UNCHECKED,
index);
m_listCtrl->HandleWindowEvent(event);
return true;
}
return false;
}
QVariant headerData(
int section,
Qt::Orientation orientation,
int role
) const wxOVERRIDE
{
if ( orientation == Qt::Vertical )
return QVariant();
wxCHECK_MSG(static_cast<size_t>(section) < m_headers.size(),
QVariant(), "Invalid header index");
const ColumnItem& header = m_headers[section];
switch ( role )
{
case Qt::DisplayRole:
return header.m_label;
case Qt::TextAlignmentRole:
return header.m_align;
}
return QVariant();
}
Qt::ItemFlags flags(const QModelIndex &index) const wxOVERRIDE
{
Qt::ItemFlags
itemFlags = Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
if ( m_listCtrl->HasFlag(wxLC_EDIT_LABELS) )
itemFlags |= Qt::ItemIsEditable;
if ( index.column() == 0 && m_listCtrl->HasCheckBoxes() )
itemFlags |= Qt::ItemIsUserCheckable;
return itemFlags | QAbstractTableModel::flags(index);
}
bool removeRows(int row, int count, const QModelIndex &parent) wxOVERRIDE
{
if ( count == 0 )
return true;
beginRemoveRows(parent, row, row + count - 1);
eraseFromContainer(m_rows, row, count);
endRemoveRows();
return true;
}
bool removeColumns(int column,
int count,
const QModelIndex &parent) wxOVERRIDE
{
if ( count == 0 )
return true;
beginRemoveColumns(parent, column, column + count - 1);
eraseFromContainer(m_headers, column, count);
const int nRows = this->m_rows.size();
for ( int i = 0; i < nRows; ++i )
{
eraseFromContainer(m_rows[i].m_columns, column, count);
}
endRemoveColumns();
return true;
}
bool GetColumn(int index, wxListItem &info) const
{
wxCHECK_MSG(static_cast<size_t>(index) < m_headers.size(),
false, "Invalid column");
const ColumnItem &column = m_headers[index];
info.SetText(wxQtConvertString(column.m_label));
info.SetAlign(wxQtConvertAlignFlag(column.m_align));
info.SetWidth(m_view->columnWidth(index));
return true;
}
bool SetColumn(int index, const wxListItem& info)
{
wxCHECK_MSG(static_cast<size_t>(index) < m_headers.size(),
false, "Invalid column");
ColumnItem &column = m_headers[index];
column.m_label = wxQtConvertString(info.GetText());
column.m_align = wxQtConvertTextAlign(info.GetAlign());
headerDataChanged(Qt::Horizontal, index, index);
return true;
}
virtual bool GetItem(wxListItem& info)
{
const int row = static_cast<int>(info.GetId());
const int col = info.m_col;
wxCHECK_MSG(
row >= 0 && static_cast<size_t>(row) < m_rows.size(),
false,
"Invalid row"
);
wxCHECK_MSG(
col >= 0 && static_cast<size_t>(col) < m_rows[row].m_columns.size(),
false,
"Invalid col"
);
const RowItem &rowItem = m_rows[row];
const ColumnItem &columnItem = rowItem[col];
if ( !info.m_mask )
// by default, get everything for backwards compatibility
info.m_mask = -1;
if ( info.m_mask & wxLIST_MASK_TEXT )
info.SetText(wxQtConvertString(columnItem.m_label));
if ( info.m_mask & wxLIST_MASK_DATA )
{
info.SetData(rowItem.m_data);
}
CopySelectStatusToItem(info, row, col);
return true;
}
bool SetItem(wxListItem &info)
{
const int row = static_cast<int>(info.GetId());
const int col = info.m_col;
wxCHECK_MSG( static_cast<size_t>(row) < m_rows.size(),
false, "Invalid row");
wxCHECK_MSG( static_cast<size_t>(col) < m_headers.size(),
false, "Invalid col");
const QModelIndex modelIndex = index(row, col);
RowItem &rowItem = m_rows[row];
ColumnItem &columnItem = rowItem[col];
QVector<int> roles;
if ( (info.m_mask & wxLIST_MASK_TEXT) && !info.GetText().IsNull() )
{
columnItem.m_label = wxQtConvertString(info.GetText());
roles.push_back(Qt::DisplayRole);
}
if ( info.m_mask & wxLIST_MASK_FORMAT )
{
columnItem.m_align = wxQtConvertTextAlign(info.GetAlign());
roles.push_back(Qt::TextAlignmentRole);
}
if ( info.m_mask & wxLIST_MASK_DATA )
{
rowItem.m_data = (void*)(info.GetData());
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;
roles.push_back(Qt::DecorationRole);
}
if ( info.GetFont().IsOk() )
{
columnItem.m_font = info.GetFont().GetHandle();
roles.push_back(Qt::FontRole);
}
if ( info.GetTextColour().IsOk() )
{
columnItem.m_textColour = info.GetTextColour().GetQColor();
roles.push_back(Qt::BackgroundRole);
}
if ( info.GetBackgroundColour().IsOk() )
{
columnItem.m_backgroundColour = info.GetBackgroundColour().GetQColor();
roles.push_back(Qt::ForegroundRole);
}
dataChanged(modelIndex, modelIndex, roles);
return true;
}
void SetView(QTreeView *view)
{
m_view = view;
}
wxColour GetItemTextColour(long item)
{
wxCHECK_MSG(item >= 0 && static_cast<size_t>(item) < m_rows.size(),
wxNullColour, "Invalid row");
wxCHECK_MSG(!m_rows[item].m_columns.empty(),
wxNullColour, "No columns in model");
return wxColour(m_rows[item][0].m_textColour);
}
wxColour GetItemBackgroundColour(long item)
{
wxCHECK_MSG(item >= 0 && static_cast<size_t>(item) < m_rows.size(),
wxNullColour, "Invalid row");
wxCHECK_MSG(!m_headers.empty(),
wxNullColour, "No columns in model");
return wxColour(m_rows[item][0].m_backgroundColour);
}
wxFont GetItemFont(long item)
{
wxCHECK_MSG(item >= 0 && static_cast<size_t>(item) < m_rows.size(),
wxNullFont, "Invalid row");
wxCHECK_MSG(!m_headers.empty(),
wxNullFont, "No columns in model");
return wxFont(m_rows[item][0].m_font);
}
long FindItem(long start, const QString& str, bool partial) const
{
if ( start < 0 )
start = 0;
const QString strUpper = str.toUpper();
const long numberOfRows = m_rows.size();
const long numberOfColumns = m_headers.size();
if ( partial )
{
for ( long i = start; i < numberOfRows; ++i )
{
for ( long j = 0; j < numberOfColumns; ++j )
{
const QString current = m_rows[i][j].m_label.toUpper();
if ( current.contains(strUpper) )
return i;
}
}
return -1;
}
for ( long i = start; i < numberOfRows; ++i )
{
for ( long j = 0; j < numberOfColumns; ++j )
{
if ( m_rows[i][j].m_label.toUpper() == strUpper )
return i;
}
}
return -1;
}
long FindItem(long start, wxUIntPtr data) const
{
if ( start < 0 )
start = 0;
const long count = m_rows.size();
for ( long i = start; i < count; ++i )
{
if ( m_rows[i].m_data == reinterpret_cast<void*>(data) )
return i;
}
return -1;
}
long InsertItem(const wxListItem& info)
{
const int column = info.GetColumn();
wxCHECK_MSG(column >= 0, -1, "Invalid column index");
if ( static_cast<size_t>(column) >= m_headers.size() )
{
beginInsertColumns(QModelIndex(), m_headers.size(), column);
const ColumnItem emptyColumn;
for ( int c = m_headers.size(); c <= column; ++c )
{
m_headers.push_back(emptyColumn);
for ( size_t r = 0; r < m_rows.size(); ++r )
{
m_rows[r].m_columns.push_back(emptyColumn);
}
}
endInsertColumns();
}
const long row = info.GetId();
long newRowIndex;
if ( row == -1 || static_cast<size_t>(row) >= m_rows.size() )
{
m_rows.push_back(RowItem(m_headers.size()));
newRowIndex = m_rows.size() - 1;
}
else
{
std::vector<RowItem>::iterator i = m_rows.begin();
std::advance(i, row);
m_rows.insert(i, RowItem(m_headers.size()));
newRowIndex = row;
}
beginInsertRows(QModelIndex(), newRowIndex, newRowIndex);
wxListItem newItem = info;
newItem.SetId(newRowIndex);
SetItem(newItem);
endInsertRows();
return newRowIndex;
}
long InsertColumn(long col, const wxListItem& info)
{
long newColumnIndex;
ColumnItem newColumn;
if ( info.m_mask & wxLIST_MASK_FORMAT )
{
newColumn.m_align = wxQtConvertTextAlign(info.GetAlign());
}
newColumn.m_label = wxQtConvertString(info.GetText());
if ( col == -1 || static_cast<size_t>(col) >= m_headers.size() )
{
newColumnIndex = m_headers.empty() ? 0 : m_headers.size();
}
else
{
newColumnIndex = col;
}
beginInsertColumns(QModelIndex(), newColumnIndex, newColumnIndex);
std::vector<ColumnItem>::iterator i = m_headers.begin();
std::advance(i, newColumnIndex);
m_headers.insert(i, newColumn);
const int numberOfRows = m_rows.size();
for (int i = 0; i < numberOfRows; ++i )
{
std::vector<ColumnItem>::iterator it = m_rows[i].m_columns.begin();
std::advance(it, newColumnIndex);
m_rows[i].m_columns.insert(it, newColumn);
}
endInsertColumns();
return true;
}
void SortItems(wxListCtrlCompare fn, wxIntPtr data)
{
CompareAdapter compare(fn, data);
std::sort(m_rows.begin(), m_rows.end(), compare);
}
bool IsItemChecked(long item) const
{
wxCHECK_MSG(item >= 0 && static_cast<size_t>(item) <= m_rows.size(),
false, "Invalid row");
return m_rows[item].m_checked;
}
void CheckItem(long item, bool check)
{
wxCHECK_RET(item >= 0 && static_cast<size_t>(item) <= m_rows.size(),
"Invalid row");
m_rows[item].m_checked = check;
QVector<int> roles;
roles.push_back(Qt::CheckStateRole);
const QModelIndex modelIndex = index(item, 0);
dataChanged(modelIndex, modelIndex, roles);
}
virtual bool IsVirtual() const
{
return false;
}
virtual void SetVirtualItemCount(long WXUNUSED(count))
{
wxFAIL_MSG("Shouldn't be called if wxLC_VIRTUAL is not used");
}
protected:
wxImageList *GetImageList() const
{
const int requiredList = m_listCtrl->HasFlag(wxLC_SMALL_ICON)
? wxIMAGE_LIST_SMALL
: wxIMAGE_LIST_NORMAL;
return m_listCtrl->GetImageList(requiredList);
}
wxListCtrl *GetListCtrl() const
{
return m_listCtrl;
}
bool IsSelected(const QModelIndex& index) const
{
QModelIndexList indices = m_view->selectionModel()->selectedIndexes();
return indices.contains(index);
}
void CopySelectStatusToItem(wxListItem& info, int row, int col)
{
if ( info.m_mask & wxLIST_MASK_STATE )
{
info.m_state = wxLIST_STATE_DONTCARE;
if (info.m_stateMask & wxLIST_STATE_FOCUSED)
{
if (m_view->currentIndex().row() == row)
info.m_state |= wxLIST_STATE_FOCUSED;
}
if (info.m_stateMask & wxLIST_STATE_SELECTED)
{
if (IsSelected(index(row, col)))
info.m_state |= wxLIST_STATE_SELECTED;
}
}
}
private:
template <typename T>
static void eraseFromContainer(T &container, int start_index, int count)
{
typename T::iterator first = container.begin();
std::advance(first, start_index);
typename T::iterator last = first;
std::advance(last, count);
container.erase(first, last);
}
struct ColumnItem
{
ColumnItem() :
m_align(Qt::AlignLeft),
m_image(-1),
m_selectedImage(-1)
{
}
QString m_label;
QColor m_backgroundColour;
QColor m_textColour;
QFont m_font;
Qt::AlignmentFlag m_align;
int m_image;
int m_selectedImage;
};
struct RowItem
{
RowItem() :
m_data(NULL),
m_checked(false)
{
}
RowItem(int columnCount ) :
m_columns(columnCount),
m_data(NULL),
m_checked(false)
{
}
const ColumnItem &operator[](int index) const
{
return m_columns[index];
}
ColumnItem &operator[](int index)
{
return m_columns[index];
}
std::vector<ColumnItem> m_columns;
void *m_data;
bool m_checked;
};
struct CompareAdapter
{
CompareAdapter(wxListCtrlCompare fn, wxIntPtr data) :
m_fn(fn),
m_data(data)
{
}
bool operator()(const RowItem &lhs, const RowItem &rhs) const
{
return m_fn(
reinterpret_cast<wxIntPtr>(lhs.m_data),
reinterpret_cast<wxIntPtr>(rhs.m_data),
m_data) < 0;
}
wxListCtrlCompare m_fn;
wxIntPtr m_data;
};
std::vector<ColumnItem> m_headers;
std::vector<RowItem> m_rows;
QTreeView *m_view;
wxListCtrl *m_listCtrl;
};
class wxQtVirtualListModel : public wxQtListModel
{
public:
wxQtVirtualListModel(wxListCtrl *listCtrl) :
wxQtListModel(listCtrl),
m_rowCount(0)
{
}
int rowCount(const QModelIndex& WXUNUSED(parent)) const wxOVERRIDE
{
return m_rowCount;
}
QVariant data(const QModelIndex &index, int role) const wxOVERRIDE
{
wxListCtrl *listCtrl = GetListCtrl();
const int row = index.row();
const int col = index.column();
if ( role == Qt::DisplayRole || role == Qt::EditRole )
{
const wxString text = listCtrl->OnGetItemText(row, col);
return QVariant::fromValue(wxQtConvertString(text));
}
if ( role == Qt::DecorationRole )
{
wxImageList *imageList = GetImageList();
if ( imageList == NULL )
return QVariant();
const int imageIndex = listCtrl->OnGetItemColumnImage(row, col);
if ( imageIndex == -1 )
return QVariant();
wxBitmap image = imageList->GetBitmap(imageIndex);
wxCHECK_MSG(image.IsOk(), QVariant(), "Invalid Bitmap");
return QVariant::fromValue(*image.GetHandle());
}
return QVariant();
}
bool GetItem(wxListItem& info) wxOVERRIDE
{
const int row = static_cast<int>(info.GetId());
const int col = info.m_col;
if ( info.m_mask & wxLIST_MASK_TEXT )
info.SetText(GetListCtrl()->OnGetItemText(row, col));
CopySelectStatusToItem(info, row, col);
return true;
}
bool IsVirtual() const wxOVERRIDE
{
return true;
}
void SetVirtualItemCount(long count) wxOVERRIDE
{
beginResetModel();
m_rowCount = static_cast<int>(count);
endResetModel();
}
private:
int m_rowCount;
};
class wxQtListTreeWidget : public wxQtEventSignalHandler< QTreeView, wxListCtrl >
{
public:
wxQtListTreeWidget( wxWindow *parent, wxListCtrl *handler );
void EmitListEvent(wxEventType typ, const QModelIndex &index) const;
void closeEditor(
QWidget *editor,
QAbstractItemDelegate::EndEditHint hint
) wxOVERRIDE
{
// Close process can re-signal closeEditor so we need to guard against
// reentrant calls.
wxRecursionGuard guard(m_closingEditor);
if (guard.IsInside())
return;
// There can be multiple calls to close editor when the item loses focus
const QModelIndex current_index = m_itemDelegate.GetCurrentModelIndex();
if (!current_index.isValid())
return;
const wxString editedText = m_itemDelegate.GetEditControl()->GetLineText(0);
wxListEvent event;
InitListEvent(event,
GetHandler(),
wxEVT_LIST_END_LABEL_EDIT,
current_index);
event.m_item.SetText(editedText);
if (hint == QAbstractItemDelegate::RevertModelCache)
{
event.SetEditCanceled(true);
EmitEvent(event);
}
else
{
// Allow event handlers to decide whether to accept edited text
if (!GetHandler()->HandleWindowEvent(event) || event.IsAllowed())
m_itemDelegate.AcceptModelData(editor, model(), current_index);
}
// wx doesn't have hints to edit next/previous item
if ( hint == QAbstractItemDelegate::EditNextItem ||
hint == QAbstractItemDelegate::EditPreviousItem )
{
hint = QAbstractItemDelegate::SubmitModelCache;
}
QTreeView::closeEditor(editor, hint);
closePersistentEditor(current_index);
}
wxTextCtrl *GetEditControl()
{
return m_itemDelegate.GetEditControl();
}
virtual void paintEvent(QPaintEvent *event) wxOVERRIDE
{
QTreeView::paintEvent(event);
}
int GetHeaderHeight() const
{
return header() != NULL ? header()->height() : 0;
}
private:
void itemClicked(const QModelIndex &index);
void itemActivated(const QModelIndex &index);
void itemPressed(const QModelIndex &index);
wxQtStyledItemDelegate m_itemDelegate;
wxRecursionGuardFlag m_closingEditor;
};
wxQtListTreeWidget::wxQtListTreeWidget( wxWindow *parent, wxListCtrl *handler )
: wxQtEventSignalHandler< QTreeView, wxListCtrl >( parent, handler ),
m_itemDelegate(handler),
m_closingEditor(0)
{
connect(this, &QTreeView::clicked, this, &wxQtListTreeWidget::itemClicked);
connect(this, &QTreeView::pressed, this, &wxQtListTreeWidget::itemPressed);
connect(this, &QTreeView::activated, this, &wxQtListTreeWidget::itemActivated);
setItemDelegate(&m_itemDelegate);
setEditTriggers(NoEditTriggers);
}
void wxQtListTreeWidget::EmitListEvent(wxEventType typ,
const QModelIndex &index) const
{
wxListCtrl *handler = GetHandler();
if ( handler )
{
// prepare the event
// -----------------
wxListEvent event;
InitListEvent(event, handler, typ, index);
EmitEvent(event);
}
}
void wxQtListTreeWidget::itemClicked(const QModelIndex &index)
{
EmitListEvent(wxEVT_LIST_ITEM_SELECTED, index);
}
void wxQtListTreeWidget::itemPressed(const QModelIndex &index)
{
EmitListEvent(wxEVT_LIST_ITEM_SELECTED, index);
}
void wxQtListTreeWidget::itemActivated(const QModelIndex &index)
{
EmitListEvent(wxEVT_LIST_ITEM_ACTIVATED, index);
}
wxListCtrl::wxListCtrl()
{
Init();
}
wxListCtrl::wxListCtrl(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
{
Init();
Create( parent, id, pos, size, style, validator, name );
}
bool wxListCtrl::Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
{
m_model = style & wxLC_VIRTUAL
? new wxQtVirtualListModel(this)
: new wxQtListModel(this);
m_qtTreeWidget = new wxQtListTreeWidget(parent, this);
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);
if ( !QtCreateControl(parent, id, pos, size, style, validator, name) )
return false;
SetWindowStyleFlag(style);
return true;
}
void wxListCtrl::Init()
{
m_hasCheckBoxes = false;
m_model = NULL;
m_imageListNormal = NULL;
m_ownsImageListNormal = false;
m_imageListSmall = NULL;
m_ownsImageListSmall = false;
m_imageListState = NULL;
m_ownsImageListState = false;
m_qtTreeWidget = NULL;
}
wxListCtrl::~wxListCtrl()
{
if ( m_ownsImageListNormal )
delete m_imageListNormal;
if ( m_ownsImageListSmall )
delete m_imageListSmall;
if ( m_ownsImageListState )
delete m_imageListState;
m_imageListNormal = NULL;
m_imageListSmall = NULL;
m_imageListState = NULL;
m_qtTreeWidget->setModel(NULL);
m_model->deleteLater();
}
bool wxListCtrl::SetForegroundColour(const wxColour& col)
{
return wxListCtrlBase::SetForegroundColour(col);
}
bool wxListCtrl::SetBackgroundColour(const wxColour& col)
{
return wxListCtrlBase::SetBackgroundColour(col);
}
bool wxListCtrl::GetColumn(int col, wxListItem& info) const
{
return m_model->GetColumn(col, info);
}
bool wxListCtrl::SetColumn(int col, const wxListItem& info)
{
if ( !m_model->SetColumn(col, info) )
return false;
if ( info.GetMask() & wxLIST_MASK_WIDTH )
SetColumnWidth(col, info.GetWidth());
return true;
}
int wxListCtrl::GetColumnWidth(int col) const
{
return m_qtTreeWidget->columnWidth(col);
}
bool wxListCtrl::SetColumnWidth(int col, int width)
{
if ( width < 0 )
{
m_qtTreeWidget->resizeColumnToContents(width);
return true;
}
m_qtTreeWidget->setColumnWidth(col, width);
return true;
}
int wxListCtrl::GetColumnOrder(int col) const
{
return col;
}
int wxListCtrl::GetColumnIndexFromOrder(int order) const
{
return order;
}
wxArrayInt wxListCtrl::GetColumnsOrder() const
{
return wxArrayInt();
}
bool wxListCtrl::SetColumnsOrder(const wxArrayInt& WXUNUSED(orders))
{
return false;
}
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;
}
wxRect wxListCtrl::GetViewRect() const
{
// this may not be exact but should be a good approximation:
wxRect rect = wxQtConvertRect(m_qtTreeWidget->rect());
const int h = m_qtTreeWidget->header()->defaultSectionSize();
rect.SetTop(h);
rect.SetHeight(rect.GetHeight() - h);
return rect;
}
wxTextCtrl* wxListCtrl::GetEditControl() const
{
return m_qtTreeWidget->GetEditControl();
}
bool wxListCtrl::GetItem(wxListItem& info) const
{
return m_model->GetItem(info);
}
bool wxListCtrl::SetItem(wxListItem& info)
{
return m_model->SetItem(info);
}
long wxListCtrl::SetItem(long index, int col, const wxString& label, int imageId)
{
wxListItem info;
info.m_text = label;
info.m_mask = wxLIST_MASK_TEXT;
info.m_itemId = index;
info.m_col = col;
if ( imageId > -1 )
{
info.m_image = imageId;
info.m_mask |= wxLIST_MASK_IMAGE;
}
return SetItem(info);
}
int wxListCtrl::GetItemState(long item, long stateMask) const
{
wxListItem info;
info.m_mask = wxLIST_MASK_STATE;
info.m_stateMask = stateMask;
info.m_itemId = item;
if ( !GetItem(info) )
return 0;
return info.m_state;
}
bool wxListCtrl::SetItemState(long item, long state, long stateMask)
{
wxListItem info;
info.m_mask = wxLIST_MASK_STATE;
info.m_stateMask = stateMask;
info.m_state = state;
info.m_itemId = item;
return SetItem(info);
}
bool wxListCtrl::SetItemImage(long item, int image, int WXUNUSED(selImage))
{
return SetItemColumnImage(item, 0, image);
}
bool wxListCtrl::SetItemColumnImage(long item, long column, int image)
{
wxListItem info;
info.m_mask = wxLIST_MASK_IMAGE;
info.m_image = image;
info.m_itemId = item;
info.m_col = column;
return SetItem(info);
}
wxString wxListCtrl::GetItemText(long item, int col) const
{
wxListItem info;
info.SetId(item);
info.m_col = col;
info.m_mask = wxLIST_MASK_TEXT;
GetItem(info);
return info.GetText();
}
void wxListCtrl::SetItemText(long item, const wxString& str)
{
wxListItem info;
info.SetId(item);
info.m_mask = wxLIST_MASK_TEXT;
info.SetText(str);
SetItem(info);
}
wxUIntPtr wxListCtrl::GetItemData(long item) const
{
wxListItem info;
info.SetId(item);
info.m_mask = wxLIST_MASK_DATA;
GetItem(info);
return info.GetData();
}
bool wxListCtrl::SetItemPtrData(long item, wxUIntPtr data)
{
wxListItem info;
info.SetId(item);
info.m_mask = wxLIST_MASK_DATA;
info.SetData(data);
return SetItem(info);
}
bool wxListCtrl::SetItemData(long item, long data)
{
return SetItemPtrData(item, static_cast<wxUIntPtr>(data));
}
bool wxListCtrl::GetItemRect(long item, wxRect& rect, int WXUNUSED(code)) const
{
wxCHECK_MSG(item >= 0 && (item < GetItemCount()), false,
"invalid item in GetSubItemRect");
const int columnCount = m_model->columnCount(QModelIndex());
if ( columnCount == 0 )
return false;
// Calculate the union of the bounds of the items in the first and last
// column for the given row.
QRect first = m_qtTreeWidget->visualRect(m_model->index(item, 0));
QRect last = m_qtTreeWidget->visualRect(m_model->index(item, columnCount-1));
rect = wxQtConvertRect(first.united(last));
rect.Offset(0, m_qtTreeWidget->GetHeaderHeight());
return true;
}
bool wxListCtrl::GetSubItemRect(long item,
long subItem,
wxRect& rect,
int WXUNUSED(code)) const
{
wxCHECK_MSG(item >= 0 && item < GetItemCount(),
false, "invalid row index in GetSubItemRect");
wxCHECK_MSG(subItem >= 0 && subItem < GetColumnCount(),
false, "invalid column index in GetSubItemRect");
const QModelIndex index = m_qtTreeWidget->model()->index(item, subItem);
rect = wxQtConvertRect(m_qtTreeWidget->visualRect(index));
rect.Offset(0, m_qtTreeWidget->GetHeaderHeight());
return true;
}
bool wxListCtrl::GetItemPosition(long item, wxPoint& pos) const
{
wxRect rect;
if ( !GetItemRect(item, rect) )
return false;
pos.x = rect.x;
pos.y = rect.y;
return true;
}
bool wxListCtrl::SetItemPosition(long WXUNUSED(item),
const wxPoint& WXUNUSED(pos))
{
return false;
}
int wxListCtrl::GetItemCount() const
{
return m_model->rowCount(QModelIndex());
}
int wxListCtrl::GetColumnCount() const
{
return m_model->columnCount(QModelIndex());
}
wxSize wxListCtrl::GetItemSpacing() const
{
return wxSize();
}
void wxListCtrl::SetItemTextColour(long item, const wxColour& col)
{
const int columnCount = m_model->columnCount(QModelIndex());
wxListItem listItem;
listItem.SetId(item);
listItem.SetTextColour(col);
for ( int i = 0; i < columnCount; ++i )
{
listItem.m_col = i;
SetItem(listItem);
}
}
wxColour wxListCtrl::GetItemTextColour( long item ) const
{
return m_model->GetItemTextColour(item);
}
void wxListCtrl::SetItemBackgroundColour( long item, const wxColour &col)
{
wxListItem listItem;
listItem.SetId(item);
listItem.SetBackgroundColour(col);
const int columnCount = m_model->columnCount(QModelIndex());
for ( int i = 0; i < columnCount; ++i )
{
listItem.m_col = i;
SetItem(listItem);
}
}
wxColour wxListCtrl::GetItemBackgroundColour( long item ) const
{
return m_model->GetItemBackgroundColour(item);
}
void wxListCtrl::SetItemFont( long item, const wxFont &f)
{
const int columnCount = m_model->columnCount(QModelIndex());
wxListItem listItem;
listItem.SetId(item);
listItem.SetFont(f);
for ( int i = 0; i < columnCount; ++i )
{
listItem.m_col = i;
SetItem(listItem);
}
}
wxFont wxListCtrl::GetItemFont( long item ) const
{
return m_model->GetItemFont(item);
}
int wxListCtrl::GetSelectedItemCount() const
{
QItemSelectionModel *selectionModel = m_qtTreeWidget->selectionModel();
QModelIndexList selectedRows = selectionModel->selectedRows();
return selectedRows.length();
}
wxColour wxListCtrl::GetTextColour() const
{
const QPalette palette = m_qtTreeWidget->palette();
const QColor color = palette.color(QPalette::WindowText);
return wxColour(color);
}
void wxListCtrl::SetTextColour(const wxColour& col)
{
QPalette palette = m_qtTreeWidget->palette();
palette.setColor(QPalette::WindowText, col.GetQColor());
m_qtTreeWidget->setPalette(palette);
}
long wxListCtrl::GetTopItem() const
{
const long itemCount = GetItemCount();
for ( long i = 0; i < itemCount; ++i )
{
wxRect itemRect;
GetItemRect(i, itemRect);
if ( itemRect.GetY() >= 0 )
return i;
}
return 0;
}
bool wxListCtrl::HasCheckBoxes() const
{
return m_hasCheckBoxes;
}
bool wxListCtrl::EnableCheckBoxes(bool enable /*= true*/)
{
m_hasCheckBoxes = enable;
QVector<int> roles;
roles.push_back(Qt::CheckStateRole);
m_model->dataChanged(m_model->index(0, 0),
m_model->index(GetItemCount() - 1, GetColumnCount() - 1),
roles);
return true;
}
bool wxListCtrl::IsItemChecked(long item) const
{
return m_model->IsItemChecked(item);
}
void wxListCtrl::CheckItem(long item, bool check)
{
m_model->CheckItem(item, check);
}
void wxListCtrl::SetSingleStyle(long WXUNUSED(style), bool WXUNUSED(add))
{
}
void wxListCtrl::SetWindowStyleFlag(long style)
{
m_windowStyle = style;
m_qtTreeWidget->setHeaderHidden((style & wxLC_NO_HEADER) != 0);
m_qtTreeWidget->setSelectionMode((style & wxLC_SINGLE_SEL) != 0
? QAbstractItemView::SingleSelection
: QAbstractItemView::ExtendedSelection
);
const bool needVirtual = (style & wxLC_VIRTUAL) != 0;
if ( needVirtual != m_model->IsVirtual() )
{
wxQtListModel *oldModel = m_model;
m_model = needVirtual
? new wxQtVirtualListModel(this)
: new wxQtListModel(this);
m_model->SetView(m_qtTreeWidget);
m_qtTreeWidget->setModel(m_model);
delete oldModel;
}
}
long wxListCtrl::GetNextItem(long item, int WXUNUSED(geometry), int state) const
{
wxListItem info;
long ret = item;
const long max = GetItemCount();
wxCHECK_MSG((ret >= -1) || (ret < max), -1,
"invalid listctrl index in GetNextItem()");
// notice that we start with the next item (or the first one if item == -1)
// and this is intentional to allow writing a simple loop to iterate over
// all selected items
ret++;
if ( ret == max )
// this is not an error because the index was OK initially,
// just no such item
return -1;
if ( state == wxLIST_STATE_DONTCARE )
return ret;
for ( long line = ret; line < max; line++ )
{
if ( GetItemState(line, state) )
return line;
}
return -1;
}
wxImageList *wxListCtrl::GetImageList(int which) const
{
if ( which == wxIMAGE_LIST_NORMAL )
{
return m_imageListNormal;
}
else if ( which == wxIMAGE_LIST_SMALL )
{
return m_imageListSmall;
}
else if ( which == wxIMAGE_LIST_STATE )
{
return m_imageListState;
}
return NULL;
}
void wxListCtrl::SetImageList(wxImageList *imageList, int which)
{
if ( which == wxIMAGE_LIST_NORMAL )
{
if ( m_ownsImageListNormal )
delete m_imageListNormal;
m_imageListNormal = imageList;
m_ownsImageListNormal = false;
}
else if ( which == wxIMAGE_LIST_SMALL )
{
if ( m_ownsImageListSmall )
delete m_imageListSmall;
m_imageListSmall = imageList;
m_ownsImageListSmall = false;
}
else if ( which == wxIMAGE_LIST_STATE )
{
if ( m_ownsImageListState )
delete m_imageListState;
m_imageListState = imageList;
m_ownsImageListState = false;
}
}
void wxListCtrl::AssignImageList(wxImageList *imageList, int which)
{
SetImageList(imageList, which);
if ( which == wxIMAGE_LIST_NORMAL )
m_ownsImageListNormal = true;
else if ( which == wxIMAGE_LIST_SMALL )
m_ownsImageListSmall = true;
else if ( which == wxIMAGE_LIST_STATE )
m_ownsImageListState = true;
}
void wxListCtrl::RefreshItem(long item)
{
RefreshItems(item, item);
}
void wxListCtrl::RefreshItems(long itemFrom, long itemTo)
{
const int columnCount = GetColumnCount();
const QModelIndex start = m_model->index(itemFrom, 0);
const QModelIndex end = m_model->index(itemTo, columnCount - 1);
m_model->dataChanged(start, end);
}
bool wxListCtrl::Arrange(int WXUNUSED(flag))
{
return false;
}
bool wxListCtrl::DeleteItem(long item)
{
if ( item < 0 || item >= GetItemCount() )
return false;
m_model->removeRow(item, QModelIndex());
wxListEvent event;
InitListEvent(event, this, wxEVT_LIST_DELETE_ITEM);
event.m_item.SetId(item);
HandleWindowEvent(event);
return true;
}
bool wxListCtrl::DeleteAllItems()
{
if ( GetItemCount() == 0 )
return true;
m_model->removeRows(0, GetItemCount(), QModelIndex());
wxListEvent event;
InitListEvent(event, this, wxEVT_LIST_DELETE_ALL_ITEMS);
HandleWindowEvent(event);
return true;
}
bool wxListCtrl::DeleteColumn(int col)
{
if ( col < 0 || col >= m_model->columnCount(QModelIndex()) )
return false;
m_model->removeColumn(0, QModelIndex());
return true;
}
bool wxListCtrl::DeleteAllColumns()
{
m_model->removeColumns(0,
m_model->columnCount(QModelIndex()),
QModelIndex());
return true;
}
void wxListCtrl::ClearAll()
{
DeleteAllColumns();
DeleteAllItems();
}
wxTextCtrl* wxListCtrl::EditLabel(long item,
wxClassInfo* WXUNUSED(textControlClass))
{
// 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->openPersistentEditor(index);
wxListEvent event;
InitListEvent(event, this, wxEVT_LIST_BEGIN_LABEL_EDIT, index);
// close the editor again if event is vetoed
if (HandleWindowEvent(event) && !event.IsAllowed())
m_qtTreeWidget->closePersistentEditor(index);
return m_qtTreeWidget->GetEditControl();
}
bool wxListCtrl::EndEditLabel(bool WXUNUSED(cancel))
{
const int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
if ( item < 0 )
return false;
m_qtTreeWidget->closePersistentEditor(m_model->index(item, 0));
return true;
}
bool wxListCtrl::EnsureVisible(long item)
{
if ( item < 0 || item >= GetItemCount() )
return false;
m_qtTreeWidget->scrollTo(m_model->index(item, 0));
return true;
}
long wxListCtrl::FindItem(long start, const wxString& str, bool partial)
{
return m_model->FindItem(start, wxQtConvertString(str), partial);
}
long wxListCtrl::FindItem(long start, wxUIntPtr data)
{
return m_model->FindItem(start, data);
}
long wxListCtrl::FindItem(
long WXUNUSED(start),
const wxPoint& WXUNUSED(pt),
int WXUNUSED(direction)
)
{
return -1;
}
long wxListCtrl::HitTest(
const wxPoint& point,
int &flags,
long* ptrSubItem
) const
{
// Remove the header height as qt expects point relative to the table sub widget
QPoint qPoint = wxQtConvertPoint(point);
qPoint.setY(qPoint.y() - m_qtTreeWidget->GetHeaderHeight());
QModelIndex index = m_qtTreeWidget->indexAt(qPoint);
if ( index.isValid() )
{
flags = wxLIST_HITTEST_ONITEM;
if (ptrSubItem)
*ptrSubItem = index.column();
}
else
{
flags = wxLIST_HITTEST_NOWHERE;
if (ptrSubItem)
*ptrSubItem = 0;
}
return index.row();
}
long wxListCtrl::InsertItem(const wxListItem& info)
{
const long index = m_model->InsertItem(info);
wxListEvent event;
InitListEvent(event, this, wxEVT_LIST_INSERT_ITEM);
HandleWindowEvent(event);
return index;
}
long wxListCtrl::InsertItem(long index, const wxString& label)
{
wxListItem info;
info.m_text = label;
info.m_mask = wxLIST_MASK_TEXT;
info.m_itemId = index;
return InsertItem(info);
}
long wxListCtrl::InsertItem(long index, int imageIndex)
{
wxListItem info;
info.m_image = imageIndex;
info.m_mask = wxLIST_MASK_IMAGE;
info.m_itemId = index;
return InsertItem(info);
}
long wxListCtrl::InsertItem(long index, const wxString& label, int imageIndex)
{
wxListItem info;
info.m_text = label;
info.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_IMAGE;
info.m_image = imageIndex;
info.m_itemId = index;
return InsertItem(info);
}
long wxListCtrl::DoInsertColumn(long col, const wxListItem& info)
{
return m_model->InsertColumn(col, info);
}
void wxListCtrl::SetItemCount(long count)
{
m_model->SetVirtualItemCount(count);
}
bool wxListCtrl::ScrollList(int dx, int dy)
{
// approximate, as scrollContentsBy is protected
m_qtTreeWidget->scroll(dx, dy);
return true;
}
bool wxListCtrl::SortItems(wxListCtrlCompare fn, wxIntPtr data)
{
m_model->SortItems(fn, data);
return true;
}
QWidget *wxListCtrl::GetHandle() const
{
return m_qtTreeWidget;
}