Вот окончательный вариант, в котором выправлены некоторые баги исходного.
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.gui.QAbstractTableModel;
/**
* Прокси-модель данных, позволяющая создавать строки путем редактирования последний
* специальный строки. Обратите внимание, что для древовидной модели сделать аналогичную
* прокси-модель будет сложнее, т.к. в таком случае нетривиально преобразование индексов.
* (Тем не менее, изменения нужны только в методах mapToSource и mapFromSource).
* Для табличной модели это преобразование можно вообще не делать, т.к. там имеет значение
* только числовые параметры, а на внутренние данные, асоциированные с каждым индексом,
* внимание не обращается. Но тем не менее лучшене рисковать - это не то место, где стоит
* оптимизировать :)
* @author Mingun
*/
public class AutoAddProxyTableModel extends QAbstractTableModel {
private QAbstractTableModel sourceModel;
private final Object specialValue = tr("<enter value to add>");
public AutoAddProxyTableModel() {
}
public AutoAddProxyTableModel(QAbstractTableModel sourceModel) {
setSourceModel(sourceModel);
}
//<editor-fold defaultstate="collapsed" desc="QAbstractItemModel">
@Override
public int rowCount(QModelIndex parent) {
if (sourceModel == null)
return 0;
// Дополнительная строка, при редактировании которой добавляется новая
// строка в исходную модель.
return sourceModel.rowCount(mapToSource(parent))+1;
}
@Override
public int columnCount(QModelIndex parent) {
if (sourceModel == null)
return 0;
return sourceModel.columnCount(mapToSource(parent));
}
@Override
public Object data(QModelIndex index, int role) {
if (sourceModel == null)
return null;
if (isExtraRow(index)) {
if (role == Qt.ItemDataRole.DisplayRole) {
return specialValue;
}
return null;
}
return sourceModel.data(mapToSource(index), role);
}
@Override
public Object headerData(int section, Qt.Orientation orientation, int role) {
if (sourceModel == null)
return super.headerData(section, orientation, role);
return sourceModel.headerData(section, orientation, role);
}
/**
* Если устанавливаются данные в последную ячейку, то в исходную таблицу
* вставляется новая строка и эти данные заносятся в нее.
* @param index
* @param value
* @param role
* @return
*/
@Override
public boolean setData(QModelIndex index, Object value, int role) {
if (sourceModel == null)
return super.setData(index, value, role);
if (isExtraRow(index)) {
// Если вставить строку не удалось, сообщаем, что задать данные не
// получилось.
if (!sourceModel.insertRow(index.row())) {
return false;
}
}
return sourceModel.setData(mapToSource(index), value, role);
}
@Override
public Qt.ItemFlags flags(QModelIndex index) {
if (sourceModel == null)
return Qt.ItemFlag.createQFlags();
if (isExtraRow(index)) {
return Qt.ItemFlag.createQFlags(
Qt.ItemFlag.ItemIsEnabled,
//Qt.ItemFlag.ItemIsSelectable,
Qt.ItemFlag.ItemIsEditable
);
}
return sourceModel.flags(mapToSource(index));
}
@Override
public boolean removeRows(int row, int count, QModelIndex parent) {
if (sourceModel == null)
return false;
// Убеждаемся, что наша строка не будет удалена.
count = Math.min(row+count, sourceModel.rowCount()) - row;
// Предыдущей операцией количество могло стать отрицательным
if (count <= 0)
return false;
return sourceModel.removeRows(row, count, mapToSource(parent));
}
@Override
public boolean removeColumns(int row, int count, QModelIndex parent) {
if (sourceModel == null)
return false;
return sourceModel.removeColumns(row, count, mapToSource(parent));
}
//</editor-fold>
public final void setSourceModel(QAbstractTableModel sourceModel) {
// Эта функция появилась только в Qt 4.6 и вы должны использовать ее.
// Если же у вас Qt 4,5, то используйте beginRemoveRows/endRemoveRows/beginInsertRows/endInsertRows,
// как в коде ниже.
//beginResetModel();
if (this.sourceModel != null) {
beginRemoveRows(null, 0, this.sourceModel.rowCount());
this.sourceModel = null;
endRemoveRows();
disconnectSlots();
}
if (sourceModel != null) {
beginInsertRows(null, 0, sourceModel.rowCount());
this.sourceModel = sourceModel;
endInsertRows();
connectSlots();
}
// Эта функция появилась только в Qt 4.6 и вы должны использовать ее.
// Если же у вас Qt 4,5, то используйте beginRemoveRows/endRemoveRows/beginInsertRows/endInsertRows,
// как в коде выше.
//endResetModel();
}
public final QAbstractTableModel sourceModel() {
return this.sourceModel;
}
private boolean isExtraRow(QModelIndex index) {
return index != null && index.row() == rowCount()-1;
}
private void connectSlots() {
sourceModel.dataChanged.connect(this, "onSourceDataChanged(com.trolltech.qt.core.QModelIndex,com.trolltech.qt.core.QModelIndex)");
sourceModel.headerDataChanged.connect(this, "onSourceHeaderDataChanged(com.trolltech.qt.core.Qt.Orientation,int,int)");
sourceModel.rowsAboutToBeInserted.connect(this, "onSourceRowsAboutToBeInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsAboutToBeRemoved.connect(this, "onSourceRowsAboutToBeRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsInserted.connect(this, "onSourceRowsInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsRemoved.connect(this, "onSourceRowsRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsAboutToBeInserted.connect(this, "onSourceColumnsAboutToBeInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsAboutToBeRemoved.connect(this, "onSourceColumnsAboutToBeRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsInserted.connect(this, "onSourceColumnsInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsRemoved.connect(this, "onSourceColumnsRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.layoutAboutToBeChanged.connect(this, "onSourceLayoutAboutToBeChanged()");
sourceModel.layoutChanged.connect(this, "onSourceLayoutChanged()");
}
private void disconnectSlots() {
sourceModel.dataChanged.disconnect(this, "onSourceDataChanged(com.trolltech.qt.core.QModelIndex,com.trolltech.qt.core.QModelIndex)");
sourceModel.headerDataChanged.disconnect(this, "onSourceHeaderDataChanged(com.trolltech.qt.core.Qt.Orientation,int,int)");
sourceModel.rowsAboutToBeInserted.disconnect(this, "onSourceRowsAboutToBeInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsAboutToBeRemoved.disconnect(this, "onSourceRowsAboutToBeRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsInserted.disconnect(this, "onSourceRowsInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.rowsRemoved.disconnect(this, "onSourceRowsRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsAboutToBeInserted.disconnect(this, "onSourceColumnsAboutToBeInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsAboutToBeRemoved.disconnect(this, "onSourceColumnsAboutToBeRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsInserted.disconnect(this, "onSourceColumnsInserted(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.columnsRemoved.disconnect(this, "onSourceColumnsRemoved(com.trolltech.qt.core.QModelIndex,int,int)");
sourceModel.layoutAboutToBeChanged.disconnect(this, "onSourceLayoutAboutToBeChanged()");
sourceModel.layoutChanged.disconnect(this, "onSourceLayoutChanged()");
}
private QModelIndex mapFromSource(QModelIndex sourceIndex) {
if (sourceIndex == null) {
return null;
}
return index(sourceIndex.row(), sourceIndex.column());
}
private QModelIndex mapToSource(QModelIndex proxyIndex) {
if (proxyIndex == null) {
return null;
}
return sourceModel.index(proxyIndex.row(), proxyIndex.column());
}
//<editor-fold defaultstate="collapsed" desc="Слоты">
private void onSourceDataChanged(QModelIndex topLeft, QModelIndex bottomRight) {
topLeft = mapFromSource(topLeft);
bottomRight = mapFromSource(bottomRight);
dataChanged.emit(topLeft, bottomRight);
}
private void onSourceHeaderDataChanged(Qt.Orientation orientation, int first, int last) {
headerDataChanged.emit(orientation, first, last);
}
private void onSourceRowsAboutToBeInserted(QModelIndex index, int start, int end) {
index = mapFromSource(index);
beginInsertRows(index, start, end);
}
private void onSourceRowsInserted(QModelIndex index, int start, int end) {
endInsertRows();
}
private void onSourceRowsAboutToBeRemoved(QModelIndex index, int start, int end) {
index = mapFromSource(index);
beginRemoveRows(index, start, end);
}
private void onSourceRowsRemoved(QModelIndex index, int start, int end) {
endRemoveRows();
}
private void onSourceColumnsAboutToBeInserted(QModelIndex index, int start, int end) {
index = mapFromSource(index);
beginInsertColumns(index, start, end);
}
private void onSourceColumnsInserted(QModelIndex index, int start, int end) {
endInsertColumns();
}
private void onSourceColumnsAboutToBeRemoved(QModelIndex index, int start, int end) {
index = mapFromSource(index);
beginRemoveColumns(index, start, end);
}
private void onSourceColumnsRemoved(QModelIndex index, int start, int end) {
endRemoveRows();
}
private void onSourceLayoutAboutToBeChanged() {
layoutAboutToBeChanged.emit();
}
private void onSourceLayoutChanged() {
layoutChanged.emit();
}
//</editor-fold>
}