5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "modelslist.h"
23 TreeItem::TreeItem(const QVector
<QVariant
> & itemData
):
32 TreeItem::TreeItem(TreeItem
* parent
, int categoryIndex
, int modelIndex
):
33 TreeItem(QVector
<QVariant
>(parent
->columnCount()))
36 setCategoryIndex(categoryIndex
);
37 setModelIndex(modelIndex
);
42 qDeleteAll(childItems
);
45 TreeItem
* TreeItem::child(int number
)
47 return childItems
.value(number
);
50 int TreeItem::childCount() const
52 return childItems
.count();
55 int TreeItem::childNumber() const
58 return parentItem
->childItems
.indexOf(const_cast<TreeItem
*>(this));
63 int TreeItem::columnCount() const
65 return itemData
.count();
68 QVariant
TreeItem::data(int column
) const
70 return itemData
.value(column
);
73 TreeItem
*TreeItem::insertChild(const int row
, int categoryIndex
, int modelIndex
)
75 TreeItem
* item
= new TreeItem(this, categoryIndex
, modelIndex
);
76 childItems
.insert(row
, item
);
80 TreeItem
* TreeItem::appendChild(int categoryIndex
, int modelIndex
)
82 return insertChild(childItems
.size(), categoryIndex
, modelIndex
);
85 bool TreeItem::removeChildren(int position
, int count
)
87 if (position
< 0 || position
+ count
> childItems
.size())
90 for (int row
= 0; row
< count
; ++row
)
91 delete childItems
.takeAt(position
);
96 bool TreeItem::insertChildren(int row
, int count
)
98 for (int i
=0; i
< count
; ++i
) {
99 insertChild(row
+ i
, -1, -1);
104 bool TreeItem::setData(int column
, const QVariant
& value
)
106 if (column
< 0 || column
>= itemData
.size())
109 itemData
[column
] = value
;
113 void TreeItem::setFlag(const quint16
& flag
, const bool on
)
121 bool TreeItem::isCategory() const
123 return (modelIndex
< 0 && categoryIndex
> -1);
126 bool TreeItem::isModel() const
128 return (modelIndex
> -1);
136 TreeModel::TreeModel(RadioData
* radioData
, QObject
* parent
):
137 QAbstractItemModel(parent
),
138 radioData(radioData
),
139 availableEEpromSize(-1)
141 Board::Type board
= getCurrentBoard();
142 QVector
<QVariant
> labels
;
143 if (!getCurrentFirmware()->getCapability(Capability::HasModelCategories
))
144 labels
<< tr("Index");
145 labels
<< tr("Name");
146 if (!(IS_HORUS(board
) || IS_SKY9X(board
))) {
147 labels
<< tr("Size");
149 rootItem
= new TreeItem(labels
);
150 // uniqueId and version for drag/drop operations (see encodeHeaderData())
151 mimeHeaderData
.instanceId
= QUuid::createUuid();
152 mimeHeaderData
.dataVersion
= 1;
155 //connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TreeModel::onRowsAboutToBeRemoved);
156 connect(this, &QAbstractItemModel::rowsRemoved
, this, &TreeModel::onRowsRemoved
);
159 TreeModel::~TreeModel()
164 int TreeModel::columnCount(const QModelIndex
& /* parent */) const
166 return rootItem
->columnCount();
169 QVariant
TreeModel::data(const QModelIndex
& index
, int role
) const
171 if (!index
.isValid())
174 TreeItem
* item
= getItem(index
);
176 if (role
== Qt::DisplayRole
|| role
== Qt::EditRole
) {
177 return item
->data(index
.column());
180 if (role
== Qt::FontRole
&& item
->isModel() && item
->getModelIndex() == (int)radioData
->generalSettings
.currModelIndex
) {
186 if (role
== Qt::ForegroundRole
&& (item
->getFlags() & TreeItem::MarkedForCut
)) {
187 return QPalette().brush(QPalette::Disabled
, QPalette::Text
);
193 Qt::ItemFlags
TreeModel::flags(const QModelIndex
&index
) const
195 Qt::ItemFlags f
= Qt::ItemIsSelectable
| Qt::ItemIsEnabled
;
197 if (index
.isValid()) {
198 if (getItem(index
)->isCategory())
199 f
|= Qt::ItemIsEditable
;
201 f
|= Qt::ItemIsDragEnabled
; // TODO drag/drop categories
203 f
|= Qt::ItemIsDropEnabled
;
209 QVariant
TreeModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
211 if (orientation
== Qt::Horizontal
&& role
== Qt::DisplayRole
)
212 return rootItem
->data(section
);
217 QModelIndex
TreeModel::index(int row
, int column
, const QModelIndex
& parent
) const
219 if (parent
.isValid() && parent
.column() != 0)
220 return QModelIndex();
222 TreeItem
* parentItem
= getItem(parent
);
223 TreeItem
* childItem
= parentItem
->child(row
);
225 return createIndex(row
, column
, childItem
);
227 return QModelIndex();
230 QModelIndex
TreeModel::parent(const QModelIndex
& index
) const
232 if (!index
.isValid())
233 return QModelIndex();
235 TreeItem
* childItem
= getItem(index
);
236 TreeItem
* parentItem
= childItem
->parent();
238 if (parentItem
== rootItem
)
239 return QModelIndex();
241 return createIndex(parentItem
->childNumber(), 0, parentItem
);
244 bool TreeModel::removeRows(int position
, int rows
, const QModelIndex
& parent
)
246 TreeItem
* parentItem
= getItem(parent
);
251 if (position
>= 0 && rows
> 0) {
252 beginRemoveRows(parent
, position
, position
+ rows
- 1);
253 success
= parentItem
->removeChildren(position
, rows
);
260 /* unused but possibly useful in future
261 bool TreeModel::insertRows(int row, int count, const QModelIndex & parent)
263 TreeItem * parentItem = getItem(parent);
268 if (row >= 0 && count > 0) {
269 beginInsertRows(parent, row, row + count - 1);
270 success = parentItem->insertChildren(row, count);
277 int TreeModel::rowCount(const QModelIndex
&parent
) const
279 TreeItem
* parentItem
= getItem(parent
);
280 return parentItem
->childCount();
283 bool TreeModel::setData(const QModelIndex
& index
, const QVariant
& value
, int role
)
285 if (role
!= Qt::EditRole
)
288 if (!index
.isValid())
291 TreeItem
* item
= getItem(index
);
292 bool result
= item
->setData(index
.column(), value
);
295 emit
dataChanged(index
, index
);
301 QStringList
TreeModel::mimeTypes() const
304 types
<< "application/x-companion-modeldata";
305 types
<< "application/x-companion-generaldata";
306 //type << "application/x-companion-radiodata-header"; // supported but not advertised, must be in conjunction with one of the above
310 Qt::DropActions
TreeModel::supportedDropActions() const
312 return Qt::CopyAction
| Qt::MoveAction
;
315 Qt::DropActions
TreeModel::supportedDragActions() const
317 return Qt::CopyAction
| Qt::MoveAction
;
320 // This method encodes all the data on default drag operation, including general radio settings. This is useful for eg. Compare dialog/model printer.
321 QMimeData
* TreeModel::mimeData(const QModelIndexList
& indexes
) const
323 QMimeData
* mimeData
= new QMimeData();
324 getModelsMimeData(indexes
, mimeData
);
325 getGeneralMimeData(mimeData
);
326 getHeaderMimeData(mimeData
);
330 bool TreeModel::canDropMimeData(const QMimeData
* data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
& parent
) const
333 //qDebug() << action << row << column << parent.row();
335 // we do not accept dropped general settings right now (user must copy/paste those)
336 if (hasHeaderMimeData(data
) && hasModelsMimeData(data
) && (row
> -1 || parent
.isValid()))
342 bool TreeModel::dropMimeData(const QMimeData
* data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
& parent
)
344 if (!canDropMimeData(data
, action
, row
, column
, parent
))
347 if (action
== Qt::IgnoreAction
)
351 bool isInsert
= false;
353 // dropped between rows (insert)
355 idx
= index(row
, column
, parent
);
357 else if (parent
.isValid()) {
358 // was dropped on a row (overwrite)
362 // dropped who knows where, (shouldn't be here though due check in canDropMimeData())
365 //qDebug() << action << row << column << parent.row() << idx << idx.row() << hasOwnMimeData(data);
367 // Force drops from other file windows to be copy actions because we don't want to delete our models.
368 if (action
== Qt::MoveAction
&& !hasOwnMimeData(data
))
369 action
= Qt::CopyAction
;
371 // canDropMimeData() only accepts models
372 emit
modelsDropped(data
, idx
, isInsert
, (action
== Qt::MoveAction
));
377 QMimeData
*TreeModel::getModelsMimeData(const QModelIndexList
& indexes
, QMimeData
* mimeData
) const
380 mimeData
= new QMimeData();
382 encodeModelsData(indexes
, &mData
);
383 mimeData
->setData("application/x-companion-modeldata", mData
);
387 QMimeData
*TreeModel::getGeneralMimeData(QMimeData
* mimeData
) const
390 mimeData
= new QMimeData();
392 encodeGeneralData(&mData
);
393 mimeData
->setData("application/x-companion-generaldata", mData
);
397 QMimeData
*TreeModel::getHeaderMimeData(QMimeData
* mimeData
) const
400 mimeData
= new QMimeData();
402 encodeHeaderData(&mData
);
403 mimeData
->setData("application/x-companion-radiodata-header", mData
);
407 QUuid
TreeModel::getMimeDataSourceId(const QMimeData
* mimeData
) const
409 MimeHeaderData header
;
410 decodeHeaderData(mimeData
, &header
);
411 return header
.instanceId
;
414 bool TreeModel::hasSupportedMimeData(const QMimeData
* mimeData
) const
416 foreach (const QString
& mtype
, mimeTypes()) {
417 if (mimeData
->hasFormat(mtype
))
423 bool TreeModel::hasModelsMimeData(const QMimeData
* mimeData
) const
425 return mimeData
->hasFormat("application/x-companion-modeldata");
428 bool TreeModel::hasGenralMimeData(const QMimeData
* mimeData
) const
430 return mimeData
->hasFormat("application/x-companion-generaldata");
433 bool TreeModel::hasHeaderMimeData(const QMimeData
* mimeData
) const
435 return mimeData
->hasFormat("application/x-companion-radiodata-header");
438 // returns true if mime data origin was this data model (vs. from another file window)
439 bool TreeModel::hasOwnMimeData(const QMimeData
* mimeData
) const
441 return (getMimeDataSourceId(mimeData
) == mimeHeaderData
.instanceId
);
444 void TreeModel::encodeModelsData(const QModelIndexList
& indexes
, QByteArray
* data
) const
446 foreach (const QModelIndex
&index
, indexes
) {
447 if (index
.isValid() && index
.column() == 0) {
448 if (!getItem(index
)->isCategory()) { // TODO: encode categoreis also
450 data
->append((char *)&radioData
->models
[getModelIndex(index
)], sizeof(ModelData
));
456 void TreeModel::encodeGeneralData(QByteArray
* data
) const
459 data
->append((char *)&radioData
->generalSettings
, sizeof(GeneralSettings
));
462 void TreeModel::encodeHeaderData(QByteArray
* data
) const
464 // We use a unique ID representing this TreeModel instance (a unique file).
465 // This can be used eg. to detect cross-file drop operations.
466 QDataStream
stream(data
, QIODevice::WriteOnly
);
467 stream
<< mimeHeaderData
.dataVersion
;
468 stream
<< mimeHeaderData
.instanceId
;
472 bool TreeModel::decodeHeaderData(const QMimeData
* mimeData
, MimeHeaderData
* header
)
474 if (header
&& mimeData
->hasFormat("application/x-companion-radiodata-header")) {
475 QByteArray data
= mimeData
->data("application/x-companion-radiodata-header");
476 QDataStream
stream(&data
, QIODevice::ReadOnly
);
477 stream
>> header
->dataVersion
>> header
->instanceId
;
484 bool TreeModel::decodeMimeData(const QMimeData
* mimeData
, QVector
<ModelData
> * models
, GeneralSettings
* gs
, bool * hasGenSet
)
492 if (models
&& mimeData
->hasFormat("application/x-companion-modeldata")) {
493 QByteArray mdlData
= mimeData
->data("application/x-companion-modeldata");
494 gData
= mdlData
.data();
496 while (size
< mdlData
.size()) {
500 ModelData
model(*((ModelData
*)gData
));
501 models
->append(model
);
502 gData
+= sizeof(ModelData
);
503 size
+= sizeof(ModelData
) + 1;
509 if (gs
&& mimeData
->hasFormat("application/x-companion-generaldata")) {
510 QByteArray genData
= mimeData
->data("application/x-companion-generaldata");
511 gData
= genData
.data();
514 *gs
= *((GeneralSettings
*)gData
);
525 int TreeModel::countModelsInMimeData(const QMimeData
* mimeData
)
528 if (mimeData
->hasFormat("application/x-companion-modeldata")) {
529 QByteArray mdlData
= mimeData
->data("application/x-companion-modeldata");
530 ret
= mdlData
.size() / (sizeof(ModelData
) + 1);
536 TreeItem
* TreeModel::getItem(const QModelIndex
& index
) const
538 if (index
.isValid()) {
539 TreeItem
* item
= static_cast<TreeItem
*>(index
.internalPointer());
548 QModelIndex
TreeModel::getIndexForModel(const int modelIndex
, QModelIndex parent
)
550 for (int i
=0; i
< rowCount(parent
); ++i
) {
551 QModelIndex idx
= index(i
, 0, parent
);
552 if (hasChildren(idx
) && (idx
= getIndexForModel(modelIndex
, idx
)).isValid())
554 if (getItem(idx
)->getModelIndex() == modelIndex
)
557 return QModelIndex();
560 QModelIndex
TreeModel::getIndexForCategory(const int categoryIndex
)
562 for (int i
=0; i
< rowCount(); ++i
) {
563 if (getItem(index(i
, 0))->getCategoryIndex() == categoryIndex
)
566 return QModelIndex();
569 int TreeModel::getAvailableEEpromSize()
571 return availableEEpromSize
;
574 int TreeModel::getModelIndex(const QModelIndex
& index
) const
576 return getItem(index
)->getModelIndex();
579 int TreeModel::getCategoryIndex(const QModelIndex
& index
) const
581 return getItem(index
)->getCategoryIndex();
584 int TreeModel::rowNumber(const QModelIndex
& index
) const
586 return getItem(index
)->childNumber();
589 bool TreeModel::isCategoryType(const QModelIndex
& index
) const
591 return index
.isValid() && getItem(index
)->isCategory();
594 bool TreeModel::isModelType(const QModelIndex
& index
) const
596 return index
.isValid() && getItem(index
)->isModel();
599 void TreeModel::markItemForCut(const QModelIndex
& index
, bool on
)
601 if (index
.isValid() && index
.column() == 0)
602 getItem(index
)->setFlag(TreeItem::MarkedForCut
, on
);
605 void TreeModel::markItemsForCut(const QModelIndexList
& indexes
, bool on
)
607 foreach (const QModelIndex
&index
, indexes
)
608 markItemForCut(index
, on
);
611 // onRowsAboutToBeRemoved could be a way to deal with models being drag-drop moved to another window/file.
612 // TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes
613 // but not the actual models from the RadioData::models array.
614 // BUT this also runs when moving rows within our own tree, and if there is an error during the move,
615 // or the user cancels the operation, removeRows() is still called automatically somewhere inside QAbstractItemModel().
616 // If a solution could be found to this problem then we could enable DnD-moving models between file windows.
618 void TreeModel::onRowsAboutToBeRemoved(const QModelIndex & parent, int first, int last)
620 qDebug() << parent << first << last;
621 QVector<int> modelIndices;
622 for (int i=first; i <= last; ++i) {
623 modelIndices << getItem(index(i, 0, parent))->getModelIndex();
625 if (modelIndices.size())
626 emit modelsRemoved(modelIndices);
630 void TreeModel::onRowsRemoved(const QModelIndex
& parent
, int first
, int last
)
632 // This is a workaround to deal with models being DnD moved to another window/file or if user cancels a DnD move within our own.
633 // TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes but not our actual models. See notes above.
634 //qDebug() << parent << first << last;
635 emit
refreshRequested(); // request refresh from View because it may have it's own ideas
638 void TreeModel::refresh()
640 EEPROMInterface
* eepromInterface
= getCurrentEEpromInterface();
641 Board::Type board
= eepromInterface
->getBoard();
642 TreeItem
* defaultCategoryItem
= NULL
;
643 bool hasCategories
= getCurrentFirmware()->getCapability(Capability::HasModelCategories
);
644 bool hasEepromSizeData
= (rootItem
->columnCount() > 2);
646 if (hasEepromSizeData
) {
647 availableEEpromSize
= Boards::getEEpromSize(board
) - 64; // let's consider fat
648 availableEEpromSize
-= 16 * ((eepromInterface
->getSize(radioData
->generalSettings
) + 14) / 15);
651 this->blockSignals(true); // make sure onRowsRemoved is not triggered
652 removeRows(0, rowCount());
653 this->blockSignals(false);
656 for (unsigned i
= 0; i
< radioData
->categories
.size(); i
++) {
657 TreeItem
* current
= rootItem
->appendChild(i
, -1);
658 current
->setData(0, QString(radioData
->categories
[i
].name
));
662 for (unsigned i
=0; i
<radioData
->models
.size(); i
++) {
663 ModelData
& model
= radioData
->models
[i
];
664 int currentColumn
= 0;
665 TreeItem
* current
= NULL
;
667 model
.modelIndex
= i
;
670 if (!model
.isEmpty()) {
671 TreeItem
* categoryItem
;
672 // TODO category should be set to -1 if not Horus
673 if (model
.category
>= 0 && model
.category
< rootItem
->childCount()) {
674 categoryItem
= rootItem
->child(model
.category
);
678 if (!defaultCategoryItem
) {
679 defaultCategoryItem
= rootItem
->appendChild(0, -1);
680 /*: Translators do NOT use accent for this, this is the default category name on Horus. */
681 defaultCategoryItem
->setData(0, tr("Models"));
683 categoryItem
= defaultCategoryItem
;
685 current
= categoryItem
->appendChild(model
.category
, i
);
689 current
= rootItem
->appendChild(0, i
);
690 current
->setData(currentColumn
++, QString().sprintf("%02d", i
+ 1));
693 if (!model
.isEmpty() && current
) {
695 if (strlen(model
.name
) > 0) {
696 modelName
= model
.name
;
699 /*: Translators: do NOT use accents here, this is a default model name. */
700 modelName
= tr("Model %1").arg(uint(i
+1), 2, 10, QChar('0'));
702 current
->setData(currentColumn
++, modelName
);
703 if (hasEepromSizeData
) {
704 int size
= eepromInterface
->getSize(model
);
705 current
->setData(currentColumn
++, QString().sprintf("%5d", size
));
706 size
= 16 * ((size
+ 14) / 15);
707 availableEEpromSize
-= size
;
708 if (i
== radioData
->generalSettings
.currModelIndex
) {
709 // Because we need this space for a TEMP model each time we have to write it again
710 availableEEpromSize
-= size
;
716 if (hasEepromSizeData
) {
717 availableEEpromSize
= (availableEEpromSize
/ 16) * 15;