[Companion] Restore model drag/drop/compare, many UI improvements to main and sub-windows. (#4769)
* [Companion] Refactor MdiChild & TreeModel, restore drag/drop, and more:
* Add full drag-and-drop functions to the models list, including model copy, move, and insert actions (also between windows);
* Add general radio settings copy/paste across file windows;
* Full MIME support with distinct types for various data (eg. models vs. general settings), also add new type for metadata (header);
* Copied/cut models can now be inserted in any spot (similar to drop-insert);
* Deleted/cut models can now be removed entirely (including the empty slot) for all radio types (any models after the removed one move up, like for Horus) (may need to make this an option);
* Empty model slots can now also be removed (any models after the removed slot move up) (but total # of slots is still maintained);
* Default model can be deleted (new default is picked);
* Completely revamp the actions system (copy/paste/edit/etc) to make it fully functional:
* Actions exist in the window, not just context menu, therefore all keyboard shortcuts actually work, w/out any duplicated code;
* Provides access from MainWindow in a comprehensive manner: all actions are available and properly updated based on selections in current editor window, w/out awkward event monitoring;
* More actions available from context menu, and somewhat reorganized;
* Also show all actions on mini toolbars right in the MDI window (possibly make optional later);
* Updated English localization file to account for plurals usage in new action item names (other languages need to be updated);
* Keyboard navigation in the model list now works/practical;
* MDI window uses all available height by default;
* Newly added models/categories are selected and highlighted; Category names are immediately editable; Model editor is/can be launched if wizard is not (need to make option);
* MainWindow updates:
* Edit toolbar and menu now showing all available edit actions, with proper context;
* File menu properly updates with all "general" radio actions: edit/copy/paste settings and simulate radio;
* Added Windows menu showing list of open windows (eg. to switch when full screen) and tabbed window option (need to make persistent setting);
* Immediate update of icon size preference;
* Do not delete toolbars when re-translating UI (they don't lose assigned position);
* [Companion] Restore model compare functionality by using new MIME data exchange system; Add ability to compare any number of models at a time; Multi-model printer now shows custom hardware settings for each dropped model (even from different radios).
* [Companion] Add missing monoblue & monowhite theme sdsync icons.
* [Companion] Model wizard starts with default model name populated.
* [Companion] Properly re-translate UI instead of recreating all the actions/etc.; Tell Mac specifically where to put our actions (seems to make it happier too).
* [Companion] Further improve handling of Window menu items/detection of sub-window changes.
* [Companion] Add file-close action.
* [Companion] Add user options and save preferences:
* Option to use wizard, open editor, or do nothing on new model creation;
* Option to remove empty model slots when deleting/moving models (in non-Horus radios);
* Options to hide toolbars in document windows (r-click on TBs);
* Save/restore size of document windows, and maximized/tabbed state;
* Save/restore size/position of model compare window;
* Add shortcuts for model compare, FW download, log viewer;
* [Companion] Disable being able to "delete" empty model slots (more awkward than helpful).
This commit is contained in:
committed by
Bertrand Songis
parent
9c19889309
commit
d864a67c3b
+440
-224
@@ -24,16 +24,17 @@ TreeItem::TreeItem(const QVector<QVariant> & itemData):
|
||||
itemData(itemData),
|
||||
parentItem(NULL),
|
||||
categoryIndex(-1),
|
||||
modelIndex(-1)
|
||||
modelIndex(-1),
|
||||
flags(0)
|
||||
{
|
||||
}
|
||||
|
||||
TreeItem::TreeItem(TreeItem * parent, int categoryIndex, int modelIndex):
|
||||
itemData(parent->columnCount()),
|
||||
parentItem(parent),
|
||||
categoryIndex(categoryIndex),
|
||||
modelIndex(modelIndex)
|
||||
TreeItem(QVector<QVariant>(parent->columnCount()))
|
||||
{
|
||||
setParent(parent);
|
||||
setCategoryIndex(categoryIndex);
|
||||
setModelIndex(modelIndex);
|
||||
}
|
||||
|
||||
TreeItem::~TreeItem()
|
||||
@@ -69,16 +70,16 @@ QVariant TreeItem::data(int column) const
|
||||
return itemData.value(column);
|
||||
}
|
||||
|
||||
TreeItem * TreeItem::appendChild(int categoryIndex, int modelIndex)
|
||||
TreeItem *TreeItem::insertChild(const int row, int categoryIndex, int modelIndex)
|
||||
{
|
||||
TreeItem * item = new TreeItem(this, categoryIndex, modelIndex);
|
||||
childItems.insert(childItems.size(), item);
|
||||
childItems.insert(row, item);
|
||||
return item;
|
||||
}
|
||||
|
||||
TreeItem * TreeItem::parent()
|
||||
TreeItem * TreeItem::appendChild(int categoryIndex, int modelIndex)
|
||||
{
|
||||
return parentItem;
|
||||
return insertChild(childItems.size(), categoryIndex, modelIndex);
|
||||
}
|
||||
|
||||
bool TreeItem::removeChildren(int position, int count)
|
||||
@@ -92,6 +93,14 @@ bool TreeItem::removeChildren(int position, int count)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TreeItem::insertChildren(int row, int count)
|
||||
{
|
||||
for (int i=0; i < count; ++i) {
|
||||
insertChild(row + i, -1, -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TreeItem::setData(int column, const QVariant & value)
|
||||
{
|
||||
if (column < 0 || column >= itemData.size())
|
||||
@@ -101,6 +110,29 @@ bool TreeItem::setData(int column, const QVariant & value)
|
||||
return true;
|
||||
}
|
||||
|
||||
void TreeItem::setFlag(const quint16 & flag, const bool on)
|
||||
{
|
||||
if (on)
|
||||
flags |= flag;
|
||||
else
|
||||
flags &= ~flag;
|
||||
}
|
||||
|
||||
bool TreeItem::isCategory() const
|
||||
{
|
||||
return (modelIndex < 0 && categoryIndex > -1);
|
||||
}
|
||||
|
||||
bool TreeItem::isModel() const
|
||||
{
|
||||
return (modelIndex > -1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TreeModel
|
||||
*/
|
||||
|
||||
TreeModel::TreeModel(RadioData * radioData, QObject * parent):
|
||||
QAbstractItemModel(parent),
|
||||
radioData(radioData),
|
||||
@@ -108,14 +140,20 @@ TreeModel::TreeModel(RadioData * radioData, QObject * parent):
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
QVector<QVariant> labels;
|
||||
if (!IS_HORUS(board))
|
||||
if (!getCurrentFirmware()->getCapability(Capability::HasModelCategories))
|
||||
labels << tr("Index");
|
||||
labels << tr("Name");
|
||||
if (!(IS_HORUS(board) || IS_SKY9X(board))) {
|
||||
labels << tr("Size");
|
||||
}
|
||||
rootItem = new TreeItem(labels);
|
||||
// uniqueId and version for drag/drop operations (see encodeHeaderData())
|
||||
mimeHeaderData.instanceId = QUuid::createUuid();
|
||||
mimeHeaderData.dataVersion = 1;
|
||||
|
||||
refresh();
|
||||
//connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TreeModel::onRowsAboutToBeRemoved);
|
||||
connect(this, &QAbstractItemModel::rowsRemoved, this, &TreeModel::onRowsRemoved);
|
||||
}
|
||||
|
||||
TreeModel::~TreeModel()
|
||||
@@ -133,39 +171,38 @@ QVariant TreeModel::data(const QModelIndex & index, int role) const
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::FontRole) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
TreeItem * item = getItem(index);
|
||||
|
||||
if (role == Qt::FontRole) {
|
||||
if (item->getModelIndex() == (int)radioData->generalSettings.currModelIndex) {
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
return font;
|
||||
}
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
return item->data(index.column());
|
||||
}
|
||||
return item->data(index.column());
|
||||
|
||||
if (role == Qt::FontRole && item->getModelIndex() == (int)radioData->generalSettings.currModelIndex) {
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
return font;
|
||||
}
|
||||
|
||||
if (role == Qt::ForegroundRole && (item->getFlags() & TreeItem::MarkedForCut)) {
|
||||
return QPalette().brush(QPalette::Disabled, QPalette::Text);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
Qt::ItemFlags f = QAbstractItemModel::flags(index);
|
||||
|
||||
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
TreeItem * TreeModel::getItem(const QModelIndex & index) const
|
||||
{
|
||||
if (index.isValid()) {
|
||||
TreeItem * item = static_cast<TreeItem *>(index.internalPointer());
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
if (getItem(index)->isCategory())
|
||||
f |= Qt::ItemIsEditable;
|
||||
else
|
||||
f |= Qt::ItemIsDragEnabled; // TODO drag/drop categories
|
||||
}
|
||||
return rootItem;
|
||||
f |= Qt::ItemIsDropEnabled;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
@@ -206,8 +243,10 @@ QModelIndex TreeModel::parent(const QModelIndex & index) const
|
||||
bool TreeModel::removeRows(int position, int rows, const QModelIndex & parent)
|
||||
{
|
||||
TreeItem * parentItem = getItem(parent);
|
||||
bool success = true;
|
||||
if (!parentItem)
|
||||
return false;
|
||||
|
||||
bool success = true;
|
||||
if (position >= 0 && rows > 0) {
|
||||
beginRemoveRows(parent, position, position + rows - 1);
|
||||
success = parentItem->removeChildren(position, rows);
|
||||
@@ -217,6 +256,23 @@ bool TreeModel::removeRows(int position, int rows, const QModelIndex & parent)
|
||||
return success;
|
||||
}
|
||||
|
||||
/* unused but possibly useful in future
|
||||
bool TreeModel::insertRows(int row, int count, const QModelIndex & parent)
|
||||
{
|
||||
TreeItem * parentItem = getItem(parent);
|
||||
if (!parentItem)
|
||||
return false;
|
||||
|
||||
bool success = true;
|
||||
if (row >= 0 && count > 0) {
|
||||
beginInsertRows(parent, row, row + count - 1);
|
||||
success = parentItem->insertChildren(row, count);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
return success;
|
||||
} */
|
||||
|
||||
int TreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
TreeItem * parentItem = getItem(parent);
|
||||
@@ -241,21 +297,361 @@ bool TreeModel::setData(const QModelIndex & index, const QVariant & value, int r
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList TreeModel::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "application/x-companion-modeldata";
|
||||
types << "application/x-companion-generaldata";
|
||||
//type << "application/x-companion-radiodata-header"; // supported but not advertised, must be in conjunction with one of the above
|
||||
return types;
|
||||
}
|
||||
|
||||
Qt::DropActions TreeModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
Qt::DropActions TreeModel::supportedDragActions() const
|
||||
{
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
// This method encodes all the data on default drag operation, including general radio settings. This is useful for eg. Compare dialog/model printer.
|
||||
QMimeData * TreeModel::mimeData(const QModelIndexList & indexes) const
|
||||
{
|
||||
QMimeData * mimeData = new QMimeData();
|
||||
getModelsMimeData(indexes, mimeData);
|
||||
getGeneralMimeData(mimeData);
|
||||
getHeaderMimeData(mimeData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
bool TreeModel::canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const
|
||||
{
|
||||
Q_UNUSED(action);
|
||||
//qDebug() << action << row << column << parent.row();
|
||||
|
||||
// we do not accept dropped general settings right now (user must copy/paste those)
|
||||
if (hasHeaderMimeData(data) && hasModelsMimeData(data) && (row > -1 || parent.isValid()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TreeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
|
||||
{
|
||||
if (!canDropMimeData(data, action, row, column, parent))
|
||||
return false;
|
||||
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
|
||||
QModelIndex idx;
|
||||
bool isInsert = false;
|
||||
if (row > -1) {
|
||||
// dropped between rows (insert)
|
||||
isInsert = true;
|
||||
idx = index(row, column, parent);
|
||||
}
|
||||
else if (parent.isValid()) {
|
||||
// was dropped on a row (overwrite)
|
||||
idx = parent;
|
||||
}
|
||||
else {
|
||||
// dropped who knows where, (shouldn't be here though due check in canDropMimeData())
|
||||
return false;
|
||||
}
|
||||
//qDebug() << action << row << column << parent.row() << idx << idx.row() << hasOwnMimeData(data);
|
||||
|
||||
// Force drops from other file windows to be copy actions because we don't want to delete our models.
|
||||
if (action == Qt::MoveAction && !hasOwnMimeData(data))
|
||||
action = Qt::CopyAction;
|
||||
|
||||
// canDropMimeData() only accepts models
|
||||
emit modelsDropped(data, idx, isInsert, (action == Qt::MoveAction));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QMimeData *TreeModel::getModelsMimeData(const QModelIndexList & indexes, QMimeData * mimeData) const
|
||||
{
|
||||
if (!mimeData)
|
||||
mimeData = new QMimeData();
|
||||
QByteArray mData;
|
||||
encodeModelsData(indexes, &mData);
|
||||
mimeData->setData("application/x-companion-modeldata", mData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
QMimeData *TreeModel::getGeneralMimeData(QMimeData * mimeData) const
|
||||
{
|
||||
if (!mimeData)
|
||||
mimeData = new QMimeData();
|
||||
QByteArray mData;
|
||||
encodeGeneralData(&mData);
|
||||
mimeData->setData("application/x-companion-generaldata", mData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
QMimeData *TreeModel::getHeaderMimeData(QMimeData * mimeData) const
|
||||
{
|
||||
if (!mimeData)
|
||||
mimeData = new QMimeData();
|
||||
QByteArray mData;
|
||||
encodeHeaderData(&mData);
|
||||
mimeData->setData("application/x-companion-radiodata-header", mData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
QUuid TreeModel::getMimeDataSourceId(const QMimeData * mimeData) const
|
||||
{
|
||||
MimeHeaderData header;
|
||||
decodeHeaderData(mimeData, &header);
|
||||
return header.instanceId;
|
||||
}
|
||||
|
||||
bool TreeModel::hasSupportedMimeData(const QMimeData * mimeData) const
|
||||
{
|
||||
foreach (const QString & mtype, mimeTypes()) {
|
||||
if (mimeData->hasFormat(mtype))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TreeModel::hasModelsMimeData(const QMimeData * mimeData) const
|
||||
{
|
||||
return mimeData->hasFormat("application/x-companion-modeldata");
|
||||
}
|
||||
|
||||
bool TreeModel::hasGenralMimeData(const QMimeData * mimeData) const
|
||||
{
|
||||
return mimeData->hasFormat("application/x-companion-generaldata");
|
||||
}
|
||||
|
||||
bool TreeModel::hasHeaderMimeData(const QMimeData * mimeData) const
|
||||
{
|
||||
return mimeData->hasFormat("application/x-companion-radiodata-header");
|
||||
}
|
||||
|
||||
// returns true if mime data origin was this data model (vs. from another file window)
|
||||
bool TreeModel::hasOwnMimeData(const QMimeData * mimeData) const
|
||||
{
|
||||
return (getMimeDataSourceId(mimeData) == mimeHeaderData.instanceId);
|
||||
}
|
||||
|
||||
void TreeModel::encodeModelsData(const QModelIndexList & indexes, QByteArray * data) const
|
||||
{
|
||||
foreach (const QModelIndex &index, indexes) {
|
||||
if (index.isValid() && index.column() == 0) {
|
||||
if (!getItem(index)->isCategory()) { // TODO: encode categoreis also
|
||||
data->append('M');
|
||||
data->append((char *)&radioData->models[getModelIndex(index)], sizeof(ModelData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TreeModel::encodeGeneralData(QByteArray * data) const
|
||||
{
|
||||
data->append('G');
|
||||
data->append((char *)&radioData->generalSettings, sizeof(GeneralSettings));
|
||||
}
|
||||
|
||||
void TreeModel::encodeHeaderData(QByteArray * data) const
|
||||
{
|
||||
// We use a unique ID representing this TreeModel instance (a unique file).
|
||||
// This can be used eg. to detect cross-file drop operations.
|
||||
QDataStream stream(data, QIODevice::WriteOnly);
|
||||
stream << mimeHeaderData.dataVersion;
|
||||
stream << mimeHeaderData.instanceId;
|
||||
}
|
||||
|
||||
// static
|
||||
bool TreeModel::decodeHeaderData(const QMimeData * mimeData, MimeHeaderData * header)
|
||||
{
|
||||
if (header && mimeData->hasFormat("application/x-companion-radiodata-header")) {
|
||||
QByteArray data = mimeData->data("application/x-companion-radiodata-header");
|
||||
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||
stream >> header->dataVersion >> header->instanceId;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool TreeModel::decodeMimeData(const QMimeData * mimeData, QVector<ModelData> * models, GeneralSettings * gs, bool * hasGenSet)
|
||||
{
|
||||
bool ret = false;
|
||||
char * gData;
|
||||
|
||||
if (hasGenSet)
|
||||
*hasGenSet = false;
|
||||
|
||||
if (models && mimeData->hasFormat("application/x-companion-modeldata")) {
|
||||
QByteArray mdlData = mimeData->data("application/x-companion-modeldata");
|
||||
gData = mdlData.data();
|
||||
int size = 0;
|
||||
while (size < mdlData.size()) {
|
||||
char c = *gData++;
|
||||
if (c != 'M')
|
||||
break;
|
||||
ModelData model(*((ModelData *)gData));
|
||||
models->append(model);
|
||||
gData += sizeof(ModelData);
|
||||
size += sizeof(ModelData) + 1;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
// General settings
|
||||
if (gs && mimeData->hasFormat("application/x-companion-generaldata")) {
|
||||
QByteArray genData = mimeData->data("application/x-companion-generaldata");
|
||||
gData = genData.data();
|
||||
char c = *gData++;
|
||||
if (c == 'G') {
|
||||
*gs = *((GeneralSettings *)gData);
|
||||
ret = true;
|
||||
if (hasGenSet)
|
||||
*hasGenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// static
|
||||
int TreeModel::countModelsInMimeData(const QMimeData * mimeData)
|
||||
{
|
||||
int ret = 0;
|
||||
if (mimeData->hasFormat("application/x-companion-modeldata")) {
|
||||
QByteArray mdlData = mimeData->data("application/x-companion-modeldata");
|
||||
ret = mdlData.size() / (sizeof(ModelData) + 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
TreeItem * TreeModel::getItem(const QModelIndex & index) const
|
||||
{
|
||||
if (index.isValid()) {
|
||||
TreeItem * item = static_cast<TreeItem *>(index.internalPointer());
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return rootItem;
|
||||
}
|
||||
|
||||
// recursive
|
||||
QModelIndex TreeModel::getIndexForModel(const int modelIndex, QModelIndex parent)
|
||||
{
|
||||
for (int i=0; i < rowCount(parent); ++i) {
|
||||
QModelIndex idx = index(i, 0, parent);
|
||||
if (hasChildren(idx) && (idx = getIndexForModel(modelIndex, idx)).isValid())
|
||||
return idx;
|
||||
if (getItem(idx)->getModelIndex() == modelIndex)
|
||||
return idx;
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex TreeModel::getIndexForCategory(const int categoryIndex)
|
||||
{
|
||||
for (int i=0; i < rowCount(); ++i) {
|
||||
if (getItem(index(i, 0))->getCategoryIndex() == categoryIndex)
|
||||
return index(i, 0);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int TreeModel::getAvailableEEpromSize()
|
||||
{
|
||||
return availableEEpromSize;
|
||||
}
|
||||
|
||||
int TreeModel::getModelIndex(const QModelIndex & index) const
|
||||
{
|
||||
return getItem(index)->getModelIndex();
|
||||
}
|
||||
|
||||
int TreeModel::getCategoryIndex(const QModelIndex & index) const
|
||||
{
|
||||
return getItem(index)->getCategoryIndex();
|
||||
}
|
||||
|
||||
int TreeModel::rowNumber(const QModelIndex & index) const
|
||||
{
|
||||
return getItem(index)->childNumber();
|
||||
}
|
||||
|
||||
bool TreeModel::isCategoryType(const QModelIndex & index) const
|
||||
{
|
||||
return index.isValid() && getItem(index)->isCategory();
|
||||
}
|
||||
|
||||
bool TreeModel::isModelType(const QModelIndex & index) const
|
||||
{
|
||||
return index.isValid() && getItem(index)->isModel();
|
||||
}
|
||||
|
||||
void TreeModel::markItemForCut(const QModelIndex & index, bool on)
|
||||
{
|
||||
if (index.isValid() && index.column() == 0)
|
||||
getItem(index)->setFlag(TreeItem::MarkedForCut, on);
|
||||
}
|
||||
|
||||
void TreeModel::markItemsForCut(const QModelIndexList & indexes, bool on)
|
||||
{
|
||||
foreach (const QModelIndex &index, indexes)
|
||||
markItemForCut(index, on);
|
||||
}
|
||||
|
||||
// onRowsAboutToBeRemoved could be a way to deal with models being drag-drop moved to another window/file.
|
||||
// TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes
|
||||
// but not the actual models from the RadioData::models array.
|
||||
// BUT this also runs when moving rows within our own tree, and if there is an error during the move,
|
||||
// or the user cancels the operation, removeRows() is still called automatically somewhere inside QAbstractItemModel().
|
||||
// If a solution could be found to this problem then we could enable DnD-moving models between file windows.
|
||||
/*
|
||||
void TreeModel::onRowsAboutToBeRemoved(const QModelIndex & parent, int first, int last)
|
||||
{
|
||||
qDebug() << parent << first << last;
|
||||
QVector<int> modelIndices;
|
||||
for (int i=first; i <= last; ++i) {
|
||||
modelIndices << getItem(index(i, 0, parent))->getModelIndex();
|
||||
}
|
||||
if (modelIndices.size())
|
||||
emit modelsRemoved(modelIndices);
|
||||
}
|
||||
*/
|
||||
|
||||
void TreeModel::onRowsRemoved(const QModelIndex & parent, int first, int last)
|
||||
{
|
||||
// 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.
|
||||
// TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes but not our actual models. See notes above.
|
||||
//qDebug() << parent << first << last;
|
||||
emit refreshRequested(); // request refresh from View because it may have it's own ideas
|
||||
}
|
||||
|
||||
void TreeModel::refresh()
|
||||
{
|
||||
EEPROMInterface * eepromInterface = getCurrentEEpromInterface();
|
||||
Board::Type board = eepromInterface->getBoard();
|
||||
TreeItem * defaultCategoryItem = NULL;
|
||||
bool hasCategories = getCurrentFirmware()->getCapability(Capability::HasModelCategories);
|
||||
bool hasEepromSizeData = (rootItem->columnCount() > 2);
|
||||
|
||||
if (!IS_SKY9X(board) && !IS_HORUS(board)) {
|
||||
if (hasEepromSizeData) {
|
||||
availableEEpromSize = Boards::getEEpromSize(board) - 64; // let's consider fat
|
||||
availableEEpromSize -= 16 * ((eepromInterface->getSize(radioData->generalSettings) + 14) / 15);
|
||||
}
|
||||
|
||||
this->blockSignals(true); // make sure onRowsRemoved is not triggered
|
||||
removeRows(0, rowCount());
|
||||
this->blockSignals(false);
|
||||
|
||||
TreeItem * defaultCategoryItem = NULL;
|
||||
|
||||
if (IS_HORUS(board)) {
|
||||
if (hasCategories) {
|
||||
for (unsigned i = 0; i < radioData->categories.size(); i++) {
|
||||
TreeItem * current = rootItem->appendChild(i, -1);
|
||||
current->setData(0, QString(radioData->categories[i].name));
|
||||
@@ -266,7 +662,10 @@ void TreeModel::refresh()
|
||||
ModelData & model = radioData->models[i];
|
||||
int currentColumn = 0;
|
||||
TreeItem * current = NULL;
|
||||
if (IS_HORUS(board)) {
|
||||
|
||||
model.modelIndex = i;
|
||||
|
||||
if (hasCategories) {
|
||||
if (!model.isEmpty()) {
|
||||
TreeItem * categoryItem;
|
||||
// TODO category should be set to -1 if not Horus
|
||||
@@ -297,7 +696,7 @@ void TreeModel::refresh()
|
||||
else
|
||||
modelName = QString().sprintf("Model%02d", i+1);
|
||||
current->setData(currentColumn++, modelName);
|
||||
if (!IS_SKY9X(board) && !IS_HORUS(board)) {
|
||||
if (hasEepromSizeData) {
|
||||
int size = eepromInterface->getSize(model);
|
||||
current->setData(currentColumn++, QString().sprintf("%5d", size));
|
||||
size = 16 * ((size + 14) / 15);
|
||||
@@ -310,190 +709,7 @@ void TreeModel::refresh()
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_SKY9X(board) && !IS_HORUS(board)) {
|
||||
if (hasEepromSizeData) {
|
||||
availableEEpromSize = (availableEEpromSize / 16) * 15;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
ModelsListWidget::ModelsListWidget(QWidget * parent):
|
||||
QTreeView(parent),
|
||||
radioData(NULL)
|
||||
{
|
||||
setColumnWidth(0, 50);
|
||||
setColumnWidth(2, 100);
|
||||
|
||||
active_highlight_color = palette().color(QPalette::Active, QPalette::Highlight);
|
||||
}
|
||||
|
||||
void ModelsListWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
dragStartPosition = event->pos();
|
||||
|
||||
QTreeView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ModelsListWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!(event->buttons() & Qt::LeftButton))
|
||||
return;
|
||||
|
||||
if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance())
|
||||
return;
|
||||
|
||||
QDrag * drag = new QDrag(this);
|
||||
|
||||
QByteArray gmData;
|
||||
doCopy(&gmData);
|
||||
|
||||
QMimeData * mimeData = new QMimeData;
|
||||
mimeData->setData("application/x-companion", gmData);
|
||||
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
//Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
|
||||
|
||||
//if(dropAction==Qt::MoveAction)
|
||||
|
||||
// QTreeWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ModelsListWidget::saveSelection()
|
||||
{
|
||||
/*currentSelection.current_item = currentItem();
|
||||
for (int i=0; i<getCurrentFirmware()->getCapability(Models)+1; ++i) {
|
||||
currentSelection.selected[i] = selectionModel()->isSelected(model()->index(i, 0));
|
||||
}*/
|
||||
}
|
||||
|
||||
void ModelsListWidget::restoreSelection()
|
||||
{
|
||||
/*setCurrentItem(currentSelection.current_item);
|
||||
for (int i=0; i<getCurrentFirmware()->getCapability(Models)+1; ++i) {
|
||||
selectionModel()->select(model()->index(i, 0), currentSelection.selected[i] ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
|
||||
}*/
|
||||
}
|
||||
|
||||
void ModelsListWidget::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasFormat("application/x-companion")) {
|
||||
event->acceptProposedAction();
|
||||
saveSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsListWidget::dragLeaveEvent(QDragLeaveEvent *)
|
||||
{
|
||||
restoreSelection();
|
||||
}
|
||||
|
||||
void ModelsListWidget::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
int row = indexAt(event->pos()).row();
|
||||
const QMimeData * mimeData = event->mimeData();
|
||||
if (mimeData->hasFormat("application/x-companion")) {
|
||||
QByteArray gmData = mimeData->data("application/x-companion");
|
||||
event->acceptProposedAction();
|
||||
clearSelection();
|
||||
DragDropHeader * header = (DragDropHeader *)gmData.data();
|
||||
if (row >= 0) {
|
||||
if (header->general_settings)
|
||||
selectionModel()->select(model()->index(0, 0), QItemSelectionModel::Select);
|
||||
for (int i=row, end=std::min(getCurrentFirmware()->getCapability(Models)+1, row+header->models_count); i<end; i++)
|
||||
selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsListWidget::dropEvent(QDropEvent *event)
|
||||
{
|
||||
int row = this->indexAt(event->pos()).row();
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
// QMessageBox::warning(this, tr("Companion"), tr("Index :%1").arg(row));
|
||||
const QMimeData * mimeData = event->mimeData();
|
||||
|
||||
if (mimeData->hasFormat("application/x-companion")) {
|
||||
QByteArray gmData = mimeData->data("application/x-companion");
|
||||
if (event->source() && event->dropAction() == Qt::MoveAction)
|
||||
((ModelsListWidget*)event->source())->doCut(&gmData);
|
||||
doPaste(&gmData, row);
|
||||
clearSelection();
|
||||
// setCurrentItem(topLevelItem(row));
|
||||
DragDropHeader * header = (DragDropHeader *)gmData.data();
|
||||
if (header->general_settings)
|
||||
selectionModel()->select(model()->index(0, 0), QItemSelectionModel::Select);
|
||||
for (int i=row, end=std::min(getCurrentFirmware()->getCapability(Models)+1, row+header->models_count); i<end; i++)
|
||||
selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
|
||||
}
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
void ModelsListWidget::focusInEvent ( QFocusEvent * event )
|
||||
{
|
||||
QTreeView::focusInEvent(event);
|
||||
QPalette palette = this->palette();
|
||||
palette.setColor(QPalette::Active, QPalette::Highlight, active_highlight_color);
|
||||
palette.setColor(QPalette::Inactive, QPalette::Highlight, active_highlight_color);
|
||||
setPalette(palette);
|
||||
}
|
||||
|
||||
void ModelsListWidget::focusOutEvent ( QFocusEvent * event )
|
||||
{
|
||||
QTreeView::focusOutEvent(event);
|
||||
QPalette palette = this->palette();
|
||||
palette.setColor(QPalette::Active, QPalette::Highlight, palette.color(QPalette::Active, QPalette::Midlight));
|
||||
palette.setColor(QPalette::Inactive, QPalette::Highlight, palette.color(QPalette::Active, QPalette::Midlight));
|
||||
setPalette(palette);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ModelsListWidget::doCut(QByteArray * gmData)
|
||||
{
|
||||
bool modified = false;
|
||||
DragDropHeader * header = (DragDropHeader *)gmData->data();
|
||||
for (int i=0; i<header->models_count; i++) {
|
||||
if (radioData->generalSettings.currModelIndex != (unsigned int)header->models[i]-1) {
|
||||
radioData->models[header->models[i]-1].clear();
|
||||
modified=true;
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
((MdiChild *)parent())->setModified();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsListWidget::duplicate()
|
||||
{
|
||||
int i = this->currentRow();
|
||||
if (i && i<getCurrentFirmware()->getCapability(Models)) {
|
||||
ModelData * model = &radioData->models[i-1];
|
||||
while (i<getCurrentFirmware()->getCapability(Models)) {
|
||||
if (radioData->models[i].isEmpty()) {
|
||||
radioData->models[i] = *model;
|
||||
strcpy(radioData->models[i].filename, qPrintable(radioData->getNextModelFilename()));
|
||||
((MdiChild *)parent())->setModified();
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i==getCurrentFirmware()->getCapability(Models)) {
|
||||
QMessageBox::warning(this, "Companion", tr("No free slot available, cannot duplicate"), QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsListWidget::onCurrentItemChanged(QTreeWidgetItem * current, QTreeWidgetItem *)
|
||||
{
|
||||
/*int index = indexOfTopLevelItem(current);
|
||||
if (!isVisible())
|
||||
((MdiChild*)parent())->viableModelSelected(false);
|
||||
else if (index<1)
|
||||
((MdiChild*)parent())->viableModelSelected(false);
|
||||
else
|
||||
((MdiChild*)parent())->viableModelSelected(!radioData->models[currentRow()-1].isEmpty()); */
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user