/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic * Copyright (C) 2018 Serge Bazanski * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "treemodel.h" #include "log.h" NEXTPNR_NAMESPACE_BEGIN namespace TreeModel { // converts 'aa123bb432' -> ['aa', '123', 'bb', '432'] std::vector IdList::alphaNumSplit(const QString &str) { std::vector res; QString current_part; bool number = true; for (const auto c : str) { if (current_part.size() == 0 && res.size() == 0) { current_part.push_back(c); number = c.isNumber(); continue; } if (number != c.isNumber()) { number = c.isNumber(); res.push_back(current_part); current_part.clear(); } current_part.push_back(c); } res.push_back(current_part); return res; } void IdList::updateElements(Context *ctx, std::vector elements) { bool changed = false; // For any elements that are not yet in managed_, created them. pool element_set; for (auto elem : elements) { element_set.insert(elem); auto existing = managed_.find(elem); if (existing == managed_.end()) { auto item = new IdStringItem(ctx, elem, this, child_type_); managed_.emplace(elem, std::unique_ptr(item)); changed = true; } } // For any elements that are in managed_ but not in new, delete them. auto it = managed_.begin(); while (it != managed_.end()) { if (element_set.count(it->first) != 0) { ++it; } else { it = managed_.erase(it); changed = true; } } // Return early if there are no changes. if (!changed) return; // Rebuild children list. children_.clear(); for (auto &pair : managed_) { if (element_set.count(pair.first) != 0) { children_.push_back(pair.second.get()); } } // Sort new children std::sort(children_.begin(), children_.end(), [&](const Item *a, const Item *b) { auto parts_a = alphaNumSplit(a->name()); auto parts_b = alphaNumSplit(b->name()); for (size_t i = 0; i < parts_a.size() && i < parts_b.size(); i++) { auto &part_a = parts_a.at(i); auto &part_b = parts_b.at(i); bool a_is_number, b_is_number; int a_number = part_a.toInt(&a_is_number); int b_number = part_b.toInt(&b_is_number); // If both parts are numbers, compare numerically. // If they're equal, continue to next part. if (a_is_number && b_is_number) { if (a_number != b_number) { return a_number < b_number; } else { continue; } } // For different alpha/nonalpha types, make numeric parts appear // first. if (a_is_number != b_is_number) { return a_is_number; } // If both parts are numbers, compare lexically. // If they're equal, continue to next part. if (part_a == part_b) { continue; } return part_a < part_b; } // One string is equal to or a subset of the other, compare length. return parts_a.size() < parts_b.size(); }); } void IdList::search(QList &results, QString text, int limit) { for (const auto &child : children_) { if (limit != -1 && results.size() > limit) return; if (child->name().contains(text)) results.push_back(child); } } Model::Model(QObject *parent) : QAbstractItemModel(parent), root_(new Item("Elements", nullptr)) {} Model::~Model() {} void Model::loadData(Context *ctx, std::unique_ptr data) { beginResetModel(); ctx_ = ctx; root_ = std::move(data); endResetModel(); } void Model::updateElements(std::vector elements) { if (!ctx_) return; beginResetModel(); root_->updateElements(ctx_, elements); endResetModel(); } int Model::rowCount(const QModelIndex &parent) const { return nodeFromIndex(parent)->count(); } int Model::columnCount(const QModelIndex &parent) const { return 1; } QModelIndex Model::index(int row, int column, const QModelIndex &parent) const { Item *node = nodeFromIndex(parent); if (row >= node->count()) return QModelIndex(); return createIndex(row, column, node->child(row)); } QModelIndex Model::parent(const QModelIndex &child) const { Item *parent = nodeFromIndex(child)->parent(); if (parent == root_.get()) return QModelIndex(); Item *node = parent->parent(); return createIndex(node->indexOf(parent), 0, parent); } QVariant Model::data(const QModelIndex &index, int role) const { if (index.column() != 0) return QVariant(); if (role != Qt::DisplayRole) return QVariant(); Item *node = nodeFromIndex(index); return node->name(); } QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return QString("Items"); return QVariant(); } Item *Model::nodeFromIndex(const QModelIndex &idx) const { if (idx.isValid()) return (Item *)idx.internalPointer(); return root_.get(); } Qt::ItemFlags Model::flags(const QModelIndex &index) const { Item *node = nodeFromIndex(index); return Qt::ItemIsEnabled | (node->type() != ElementType::NONE ? Qt::ItemIsSelectable : Qt::NoItemFlags); } void Model::fetchMore(const QModelIndex &parent) { if (ctx_ == nullptr) return; std::lock_guard lock_ui(ctx_->ui_mutex); std::lock_guard lock(ctx_->mutex); nodeFromIndex(parent)->fetchMore(); } bool Model::canFetchMore(const QModelIndex &parent) const { return nodeFromIndex(parent)->canFetchMore(); } QList Model::search(QString text) { const int limit = 500; QList list; root_->search(list, text, limit); QList res; for (auto i : list) { res.push_back(indexFromNode(i)); } return res; } }; // namespace TreeModel NEXTPNR_NAMESPACE_END