Files
wxWidgets/src/qt/listctrl.cpp
Vadim Zeitlin 5941dea234 Move wxImageList-related code to wxListCtrlBase
Put the code common to all wxListCtrl implementations in the common base
class instead of duplicating it in wxMSW, wxQt and generic versions.

No real changes yet, this is a pure refactoring which prepares for the
upcoming changes.
2021-11-11 00:52:12 +00:00

1764 lines
45 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"
#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_qtTreeWidget = NULL;
}
wxListCtrl::~wxListCtrl()
{
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;
}
void wxListCtrl::DoSetImageList(wxImageList* WXUNUSED(imageList), int WXUNUSED(which))
{
// TODO: Ensure the icons are actually updated.
}
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;
}