From ccbf341dc8d8e515d9cf918bff7ff9435c477847 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Tue, 24 Dec 2013 11:47:30 +0100 Subject: Initial commit. Basics work. Next: Drag and Drop --- .gitignore | 2 + CMakeLists.txt | 35 +++ CategorizedProxyModel.cpp | 12 + CategorizedProxyModel.h | 18 ++ CategorizedView.cpp | 587 ++++++++++++++++++++++++++++++++++++++++++++++ CategorizedView.h | 96 ++++++++ main.cpp | 53 +++++ 7 files changed, 803 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CategorizedProxyModel.cpp create mode 100644 CategorizedProxyModel.h create mode 100644 CategorizedView.cpp create mode 100644 CategorizedView.h create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a5d18fa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +*.user* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..8a246bcf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 2.8.9) + +project(GroupView) + +set(CMAKE_AUTOMOC ON) + +IF(APPLE) + message(STATUS "Using APPLE CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") +ELSEIF(UNIX) + # assume GCC, add C++0x/C++11 stuff + MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") +ELSEIF(MINGW) + MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall") +ENDIF() + +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Widgets REQUIRED) + +include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + +set(SOURCES + main.cpp + + CategorizedView.h + CategorizedView.cpp + CategorizedProxyModel.h + CategorizedProxyModel.cpp +) + +add_executable(GroupView ${SOURCES}) +qt5_use_modules(GroupView Core Gui Widgets) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp new file mode 100644 index 00000000..2b54ce1b --- /dev/null +++ b/CategorizedProxyModel.cpp @@ -0,0 +1,12 @@ +#include "CategorizedProxyModel.h" + +#include "CategorizedView.h" + +CategorizedProxyModel::CategorizedProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} +bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return left.data(CategorizedView::CategoryRole).toString() < right.data(CategorizedView::CategoryRole).toString(); +} diff --git a/CategorizedProxyModel.h b/CategorizedProxyModel.h new file mode 100644 index 00000000..6e4f3fdc --- /dev/null +++ b/CategorizedProxyModel.h @@ -0,0 +1,18 @@ +#ifndef CATEGORIZEDPROXYMODEL_H +#define CATEGORIZEDPROXYMODEL_H + +#include + +class CategorizedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + CategorizedProxyModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; + + +#endif // CATEGORIZEDPROXYMODEL_H diff --git a/CategorizedView.cpp b/CategorizedView.cpp new file mode 100644 index 00000000..46b1e072 --- /dev/null +++ b/CategorizedView.cpp @@ -0,0 +1,587 @@ +#include "CategorizedView.h" + +#include +#include +#include +#include +#include + +CategorizedView::Category::Category(const QString &text, CategorizedView *view) + : view(view), text(text), collapsed(false) +{ +} +CategorizedView::Category::Category(const CategorizedView::Category *other) : + view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void CategorizedView::Category::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); + painter->setPen(QPen(Qt::black, 1)); + painter->drawRect(iconRect); + static const int margin = 2; + QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + int midX = iconSubrect.center().x(); + int midY = iconSubrect.center().y(); + if (collapsed) + { + painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + } + painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); + + // the text + int textWidth = painter->fontMetrics().width(text); + textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->drawText(textRect, text, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int CategorizedView::Category::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); +} +int CategorizedView::Category::headerHeight() const +{ + return qApp->fontMetrics().height() + 4; +} +int CategorizedView::Category::contentHeight() const +{ + if (collapsed) + { + return 0; + } + const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); + return view->itemSize().height() * rows; +} +QSize CategorizedView::Category::categoryTotalSize() const +{ + return QSize(view->contentWidth(), contentHeight()); +} + +CategorizedView::CategorizedView(QWidget *parent) + : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) +{ + setViewMode(IconMode); + setMovement(Snap); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setWordWrap(true); +} + +CategorizedView::~CategorizedView() +{ + qDeleteAll(m_categories); + m_categories.clear(); +} + +void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::dataChanged(topLeft, bottomRight, roles); + + if (roles.contains(CategoryRole)) + { + updateGeometries(); + update(); + } +} +void CategorizedView::rowsInserted(const QModelIndex &parent, int start, int end) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::rowsInserted(parent, start, end); + + updateGeometries(); + update(); +} +void CategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::rowsAboutToBeRemoved(parent, start, end); + + updateGeometries(); + update(); +} + +void CategorizedView::updateGeometries() +{ + QListView::updateGeometries(); + + m_cachedItemSize = QSize(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString category = model()->index(i, 0).data(CategoryRole).toString(); + if (!cats.contains(category)) + { + Category *old = this->category(category); + if (old) + { + cats.insert(category, new Category(old)); + } + else + { + cats.insert(category, new Category(category, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_categories); + m_categories = cats.values(); + + update(); +} + +bool CategorizedView::isIndexHidden(const QModelIndex &index) const +{ + Category *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } +} + +CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const +{ + return category(index.data(CategoryRole).toString()); +} +CategorizedView::Category *CategorizedView::category(const QString &cat) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->text == cat) + { + return m_categories.at(i); + } + } + return 0; +} + +int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const +{ + return itemsForCategory(category).size(); +} +QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const +{ + QList indices; + for (int i = 0; i < model()->rowCount(); ++i) + { + if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + { + indices += model()->index(i, 0); + } + } + return indices; +} + +int CategorizedView::categoryTop(const CategorizedView::Category *category) const +{ + int res = 0; + const QList cats = sortedCategories(); + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == category) + { + break; + } + res += cats.at(i)->totalHeight() + m_categoryMargin; + } + return res; +} + +int CategorizedView::itemsPerRow() const +{ + return qFloor((qreal)contentWidth() / (qreal)itemSize().width()); +} +int CategorizedView::contentWidth() const +{ + return width() - m_leftMargin - m_rightMargin; +} + +bool CategorizedView::lessThanCategoryPointer(const CategorizedView::Category *c1, const CategorizedView::Category *c2) +{ + return c1->text < c2->text; +} +QList CategorizedView::sortedCategories() const +{ + QList out = m_categories; + qSort(out.begin(), out.end(), &CategorizedView::lessThanCategoryPointer); + return out; +} + +QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const +{ + if (!m_cachedItemSize.isValid()) + { + QModelIndex sample = model()->index(model()->rowCount() -1, 0); + const QAbstractItemDelegate *delegate = itemDelegate(); + if (delegate) + { + m_cachedItemSize = delegate->sizeHint(option, sample); + m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20); + m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20); + } + else + { + m_cachedItemSize = QSize(); + } + } + return m_cachedItemSize; +} + +void CategorizedView::mousePressEvent(QMouseEvent *event) +{ + //endCategoryEditor(); + + if (event->buttons() & Qt::LeftButton) + { + foreach (Category *category, m_categories) + { + if (category->iconRect.contains(event->pos())) + { + category->collapsed = !category->collapsed; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mousePressEvent(event); +} +void CategorizedView::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mouseMoveEvent(event); +} +void CategorizedView::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mouseReleaseEvent(event); +} +void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) +{ + /*endCategoryEditor(); + + foreach (Category *category, m_categories) + { + if (category->textRect.contains(event->pos()) && m_categoryEditor == 0) + { + startCategoryEditor(category); + event->accept(); + return; + } + }*/ + + QListView::mouseDoubleClickEvent(event); +} +void CategorizedView::paintEvent(QPaintEvent *event) +{ + QPainter painter(this->viewport()); + + int y = 0; + for (int i = 0; i < m_categories.size(); ++i) + { + Category *category = m_categories.at(i); + category->drawHeader(&painter, y); + y += category->totalHeight() + m_categoryMargin; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + QStyleOptionViewItemV4 option(viewOptions()); + option.rect = visualRect(index); + option.widget = this; + option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; + if (flags & Qt::ItemIsSelectable) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } +} +void CategorizedView::resizeEvent(QResizeEvent *event) +{ + QListView::resizeEvent(event); + +// if (m_categoryEditor) +// { +// m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), m_categoryEditor->height()); +// } + + updateGeometries(); +} + +void CategorizedView::dragEnterEvent(QDragEnterEvent *event) +{ + // TODO +} +void CategorizedView::dragMoveEvent(QDragMoveEvent *event) +{ + // TODO +} +void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) +{ + // TODO +} +void CategorizedView::dropEvent(QDropEvent *event) +{ + stopAutoScroll(); + setState(NoState); + + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + { + return; + } + + // check that we aren't on a category header and calculate which category we're in + Category *category = 0; + { + int y = 0; + foreach (Category *cat, m_categories) + { + if (event->pos().y() > y && event->pos().y() < (y + cat->headerHeight())) + { + viewport()->update(); + return; + } + y += cat->totalHeight() + m_categoryMargin; + if (event->pos().y() < y) + { + category = cat; + break; + } + } + } + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = itemSize().width(); + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth, ++c) + { + if (event->pos().x() > (i - itemWidth / 2) && + event->pos().x() < (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + if (internalColumn == -1) + { + viewport()->update(); + return; + } + } + + // calculate the internal row + int internalRow = -1; + { + const int itemHeight = itemSize().height(); + const int top = categoryTop(category); + for (int i = top + category->headerHeight(), r = 0; + i < top + category->totalHeight(); + i += itemHeight, ++r) + { + if (event->pos().y() > i && event->pos().y() < (i + itemHeight)) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + viewport()->update(); + return; + } + } + + QList indices = itemsForCategory(category); + + // flaten the internalColumn/internalRow to one row + int categoryRow; + { + for (int i = 0; i < internalRow; ++i) + { + if (i == internalRow) + { + break; + } + categoryRow += itemsPerRow(); + } + categoryRow += internalColumn; + } + + int row = indices.at(categoryRow).row(); + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); +} + +bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) +{ + return i1.data() < i2.data(); +} +QRect CategorizedView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Category *cat = category(index); + QList indices = itemsForCategory(cat); + qSort(indices.begin(), indices.end(), &lessThanQModelIndex); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) + { + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } + } + + QSize size = itemSize(); + + QRect out; + out.setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); + out.setLeft(x * size.width()); + out.setSize(size); + + return out; +} +/* +void CategorizedView::startCategoryEditor(Category *category) +{ + if (m_categoryEditor != 0) + { + return; + } + m_editedCategory = category; + m_categoryEditor = new QLineEdit(m_editedCategory->text, this); + QRect rect = m_editedCategory->textRect; + rect.setWidth(qMax(contentWidth() / 2, rect.width())); + m_categoryEditor->setGeometry(rect); + m_categoryEditor->show(); + m_categoryEditor->setFocus(); + connect(m_categoryEditor, &QLineEdit::returnPressed, this, &CategorizedView::endCategoryEditor); +} + +void CategorizedView::endCategoryEditor() +{ + if (m_categoryEditor == 0) + { + return; + } + m_editedCategory->text = m_categoryEditor->text(); + m_updatesDisabled = true; + foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) + { + const_cast(index.model())->setData(index, m_categoryEditor->text(), CategoryRole); + } + m_updatesDisabled = false; + delete m_categoryEditor; + m_categoryEditor = 0; + m_editedCategory = 0; + updateGeometries(); +} +*/ diff --git a/CategorizedView.h b/CategorizedView.h new file mode 100644 index 00000000..1e918496 --- /dev/null +++ b/CategorizedView.h @@ -0,0 +1,96 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include +#include + +class CategorizedView : public QListView +{ + Q_OBJECT + +public: + CategorizedView(QWidget *parent = 0); + ~CategorizedView(); + + enum + { + CategoryRole = Qt::UserRole + }; + + virtual QRect visualRect(const QModelIndex &index) const; + +protected slots: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void updateGeometries(); + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + +private: + struct Category + { + Category(const QString &text, CategorizedView *view); + Category(const Category *other); + CategorizedView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + QSize categoryTotalSize() const; + }; + friend struct Category; + + QList m_categories; + + int m_leftMargin; + int m_rightMargin; + int m_categoryMargin; + int m_itemSpacing; + + //bool m_updatesDisabled; + + Category *category(const QModelIndex &index) const; + Category *category(const QString &cat) const; + int numItemsForCategory(const Category *category) const; + QList itemsForCategory(const Category *category) const; + + int categoryTop(const Category *category) const; + + int itemsPerRow() const; + int contentWidth() const; + + static bool lessThanCategoryPointer(const Category *c1, const Category *c2); + QList sortedCategories() const; + +private: + mutable QSize m_cachedItemSize; + QSize itemSize(const QStyleOptionViewItem &option) const; + QSize itemSize() const { return itemSize(viewOptions()); } + + /*QLineEdit *m_categoryEditor; + Category *m_editedCategory; + void startCategoryEditor(Category *category); + +private slots: + void endCategoryEditor();*/ +}; + +#endif // WIDGET_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..24d7075e --- /dev/null +++ b/main.cpp @@ -0,0 +1,53 @@ +#include "CategorizedView.h" +#include +#include + +#include "CategorizedProxyModel.h" + +QPixmap icon(const Qt::GlobalColor color) +{ + QPixmap p = QPixmap(32, 32); + p.fill(QColor(color)); + return p; +} +QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, const QString &category) +{ + QStandardItem *item = new QStandardItem; + item->setText(text); + item->setData(icon(color), Qt::DecorationRole); + item->setData(category, CategorizedView::CategoryRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; +} + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QStandardItemModel model; + model.setRowCount(10); + model.setColumnCount(1); + + model.setItem(0, createItem(Qt::red, "Red", "Colorful")); + model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); + model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); + + model.setItem(3, createItem(Qt::black, "Black", "Not Colorful")); + model.setItem(4, createItem(Qt::darkGray, "Dark Gray", "Not Colorful")); + model.setItem(5, createItem(Qt::gray, "Gray", "Not Colorful")); + model.setItem(6, createItem(Qt::lightGray, "Light Gray", "Not Colorful")); + model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); + + model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); + model.setItem(9, createItem(Qt::green, "Green", "")); + + CategorizedProxyModel pModel; + pModel.setSourceModel(&model); + + CategorizedView w; + w.setModel(&pModel); + w.resize(640, 480); + w.show(); + + return a.exec(); +} -- cgit From 525f508d94120feae89ee1d1bd960625ab14ed37 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:16:03 +0100 Subject: Loads of stuff, amongst others d&d and many bug fixes --- CategorizedProxyModel.cpp | 11 +- CategorizedView.cpp | 681 +++++++++++++++++++++++++++++++++++----------- CategorizedView.h | 25 ++ 3 files changed, 555 insertions(+), 162 deletions(-) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp index 2b54ce1b..e4a7563a 100644 --- a/CategorizedProxyModel.cpp +++ b/CategorizedProxyModel.cpp @@ -8,5 +8,14 @@ CategorizedProxyModel::CategorizedProxyModel(QObject *parent) } bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - return left.data(CategorizedView::CategoryRole).toString() < right.data(CategorizedView::CategoryRole).toString(); + const QString leftCategory = left.data(CategorizedView::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedView::CategoryRole).toString(); + if (leftCategory == rightCategory) + { + return left.row() < right.row(); + } + else + { + return leftCategory < rightCategory; + } } diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 46b1e072..95505fd7 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -5,6 +5,23 @@ #include #include #include +#include +#include +#include +#include + +template +bool listsIntersect(const QList &l1, const QList t2) +{ + foreach (const T &item, l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} CategorizedView::Category::Category(const QString &text, CategorizedView *view) : view(view), text(text), collapsed(false) @@ -39,7 +56,7 @@ void CategorizedView::Category::drawHeader(QPainter *painter, const int y) // the text int textWidth = painter->fontMetrics().width(text); textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - painter->drawText(textRect, text, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->palette(), true, text); // the line painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); @@ -77,6 +94,11 @@ CategorizedView::CategorizedView(QWidget *parent) setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setWordWrap(true); + setDragDropMode(QListView::InternalMove); + setAcceptDrops(true); + + m_cachedCategoryToIndexMapping.setMaxCost(50); + m_cachedVisualRects.setMaxCost(50); } CategorizedView::~CategorizedView() @@ -130,6 +152,8 @@ void CategorizedView::updateGeometries() QListView::updateGeometries(); m_cachedItemSize = QSize(); + m_cachedCategoryToIndexMapping.clear(); + m_cachedVisualRects.clear(); QMap cats; @@ -189,6 +213,17 @@ CategorizedView::Category *CategorizedView::category(const QString &cat) const } return 0; } +CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->iconRect.contains(pos)) + { + return m_categories.at(i); + } + } + return 0; +} int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const { @@ -196,15 +231,47 @@ int CategorizedView::numItemsForCategory(const CategorizedView::Category *catego } QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const { - QList indices; - for (int i = 0; i < model()->rowCount(); ++i) + if (!m_cachedCategoryToIndexMapping.contains(category)) + { + QList *indices = new QList(); + for (int i = 0; i < model()->rowCount(); ++i) + { + if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + { + indices->append(model()->index(i, 0)); + } + } + m_cachedCategoryToIndexMapping.insert(category, indices, indices->size()); + } + return *m_cachedCategoryToIndexMapping.object(category); +} +QModelIndex CategorizedView::firstItemForCategory(const CategorizedView::Category *category) const +{ + QList indices = itemsForCategory(category); + QModelIndex first; + foreach (const QModelIndex &index, indices) { - if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + if (index.row() < first.row() || !first.isValid()) { - indices += model()->index(i, 0); + first = index; } } - return indices; + + return first; +} +QModelIndex CategorizedView::lastItemForCategory(const CategorizedView::Category *category) const +{ + QList indices = itemsForCategory(category); + QModelIndex last; + foreach (const QModelIndex &index, indices) + { + if (index.row() > last.row() || !last.isValid()) + { + last = index; + } + } + + return last; } int CategorizedView::categoryTop(const CategorizedView::Category *category) const @@ -231,14 +298,10 @@ int CategorizedView::contentWidth() const return width() - m_leftMargin - m_rightMargin; } -bool CategorizedView::lessThanCategoryPointer(const CategorizedView::Category *c1, const CategorizedView::Category *c2) -{ - return c1->text < c2->text; -} QList CategorizedView::sortedCategories() const { QList out = m_categories; - qSort(out.begin(), out.end(), &CategorizedView::lessThanCategoryPointer); + qSort(out.begin(), out.end(), [](const Category *c1, const Category *c2) { return c1->text < c2->text; }); return out; } @@ -266,85 +329,186 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) { //endCategoryEditor(); - if (event->buttons() & Qt::LeftButton) + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + QPoint offset = QPoint(horizontalOffset(), verticalOffset()); + if (!(command & QItemSelectionModel::Current)) { - foreach (Category *category, m_categories) - { - if (category->iconRect.contains(event->pos())) - { - category->collapsed = !category->collapsed; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } + m_pressedPosition = pos + offset; + } + else if (!indexAt(m_pressedPosition - offset).isValid()) + { + m_pressedPosition = visualRect(currentIndex()).center() + offset; + } - for (int i = 0; i < model()->rowCount(); ++i) + m_pressedCategory = categoryAt(m_pressedPosition); + if (m_pressedCategory) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + setAutoScroll(autoScroll); + QRect rect(m_pressedPosition - offset, pos); + if (command.testFlag(QItemSelectionModel::Toggle)) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + command &= ~QItemSelectionModel::Toggle; + m_ctrlDragSelectionFlag = selectionModel()->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; + command |= m_ctrlDragSelectionFlag; } - } + setSelection(rect, command); + + // signal handlers may change the model + emit pressed(index); - QListView::mousePressEvent(event); + } else { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } } void CategorizedView::mouseMoveEvent(QMouseEvent *event) { - if (event->buttons() & Qt::LeftButton) + QPoint topLeft; + QPoint bottomRight = event->pos(); + + if (state() == ExpandingState || state() == CollapsingState) { - for (int i = 0; i < model()->rowCount(); ++i) + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); } + return; } - QListView::mouseMoveEvent(event); -} -void CategorizedView::mouseReleaseEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::LeftButton) + QPersistentModelIndex index = indexAt(bottomRight); + + if (selectionMode() != SingleSelection) { - for (int i = 0; i < model()->rowCount(); ++i) + topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + } + else + { + topLeft = bottomRight; + } + + if (m_pressedIndex.isValid() + && (state() != DragSelectingState) + && (event->buttons() != Qt::NoButton) + && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(QItemSelectionModel::Toggle)) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + command &= ~QItemSelectionModel::Toggle; + command |= m_ctrlDragSelectionFlag; } - } - QListView::mouseReleaseEvent(event); + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + setSelection(selectionRect, command); + + // set at the end because it might scroll the view + if (index.isValid() + && (index != selectionModel()->currentIndex()) + && (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } } -void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) +void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - /*endCategoryEditor(); + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); - foreach (Category *category, m_categories) + bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); + + if (click && m_pressedCategory) { - if (category->textRect.contains(event->pos()) && m_categoryEditor == 0) + if (state() == ExpandingState) { - startCategoryEditor(category); + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); event->accept(); return; } - }*/ + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); - QListView::mouseDoubleClickEvent(event); + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) + && style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } +} +void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() + || !(index.flags() & Qt::ItemIsEnabled) + || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, + event->localPos(), event->windowPos(), event->screenPos(), + event->button(), event->buttons(), event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); } void CategorizedView::paintEvent(QPaintEvent *event) { @@ -385,6 +549,33 @@ void CategorizedView::paintEvent(QPaintEvent *event) } itemDelegate()->paint(&painter, option, index); } + + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Category *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - firstItemForCategory(category).row(); + qDebug() << internalRow << numItemsForCategory(category) << model()->index(row, 0).data().toString(); + QLine line; + if (internalRow >= numItemsForCategory(category)) + { + QRect toTheRightOfRect = visualRect(lastItemForCategory(category)); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } } void CategorizedView::resizeEvent(QResizeEvent *event) { @@ -400,18 +591,33 @@ void CategorizedView::resizeEvent(QResizeEvent *event) void CategorizedView::dragEnterEvent(QDragEnterEvent *event) { - // TODO + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos(); + viewport()->update(); + event->accept(); } void CategorizedView::dragMoveEvent(QDragMoveEvent *event) { - // TODO + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos(); + viewport()->update(); + event->accept(); } void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) { - // TODO + m_lastDragPosition = QPoint(); + viewport()->update(); } void CategorizedView::dropEvent(QDropEvent *event) { + m_lastDragPosition = QPoint(); + stopAutoScroll(); setState(NoState); @@ -420,93 +626,68 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - // check that we aren't on a category header and calculate which category we're in - Category *category = 0; + QPair dropPos = rowDropPos(event->pos()); + const Category *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) { - int y = 0; - foreach (Category *cat, m_categories) - { - if (event->pos().y() > y && event->pos().y() < (y + cat->headerHeight())) - { - viewport()->update(); - return; - } - y += cat->totalHeight() + m_categoryMargin; - if (event->pos().y() < y) - { - category = cat; - break; - } - } + viewport()->update(); + return; } - // calculate the internal column - int internalColumn = -1; + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { - const int itemWidth = itemSize().width(); - for (int i = 0, c = 0; - i < contentWidth(); - i += itemWidth, ++c) - { - if (event->pos().x() > (i - itemWidth / 2) && - event->pos().x() < (i + itemWidth / 2)) - { - internalColumn = c; - break; - } - } - if (internalColumn == -1) - { - viewport()->update(); - return; - } + model()->setData(model()->index(row, 0), categoryText, CategoryRole); + event->setDropAction(Qt::MoveAction); + event->accept(); } + updateGeometries(); + viewport()->update(); +} - // calculate the internal row - int internalRow = -1; +void CategorizedView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.count() > 0) { - const int itemHeight = itemSize().height(); - const int top = categoryTop(category); - for (int i = top + category->headerHeight(), r = 0; - i < top + category->totalHeight(); - i += itemHeight, ++r) + QMimeData *data = model()->mimeData(indexes); + if (!data) { - if (event->pos().y() > i && event->pos().y() < (i + itemHeight)) - { - internalRow = r; - break; - } + return; } - if (internalRow == -1) + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(m_pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && (supportedActions & this->defaultDropAction())) { - viewport()->update(); - return; + defaultDropAction = this->defaultDropAction(); } - } - - QList indices = itemsForCategory(category); - - // flaten the internalColumn/internalRow to one row - int categoryRow; - { - for (int i = 0; i < internalRow; ++i) + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) { - if (i == internalRow) - { - break; + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); } - categoryRow += itemsPerRow(); } - categoryRow += internalColumn; - } - - int row = indices.at(categoryRow).row(); - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - event->setDropAction(Qt::MoveAction); - event->accept(); } - updateGeometries(); } bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) @@ -520,34 +701,39 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const return QRect(); } - const Category *cat = category(index); - QList indices = itemsForCategory(cat); - qSort(indices.begin(), indices.end(), &lessThanQModelIndex); - int x = 0; - int y = 0; - const int perRow = itemsPerRow(); - for (int i = 0; i < indices.size(); ++i) + if (!m_cachedVisualRects.contains(index)) { - if (indices.at(i) == index) - { - break; - } - ++x; - if (x == perRow) + const Category *cat = category(index); + QList indices = itemsForCategory(cat); + qSort(indices.begin(), indices.end(), &lessThanQModelIndex); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) { - x = 0; - ++y; + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } } - } - QSize size = itemSize(); + QSize size = itemSize(); - QRect out; - out.setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out.setLeft(x * size.width()); - out.setSize(size); + QRect *out = new QRect; + out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); + out->setLeft(x * size.width()); + out->setSize(size); - return out; + m_cachedVisualRects.insert(index, out); + } + + return *m_cachedVisualRects.object(index); } /* void CategorizedView::startCategoryEditor(Category *category) @@ -585,3 +771,176 @@ void CategorizedView::endCategoryEditor() updateGeometries(); } */ + +QModelIndex CategorizedView::indexAt(const QPoint &point) const +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); +} +void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) +{ + QItemSelection selection; + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).intersects(rect)) + { + selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + } + } + selectionModel()->select(selection, commands); +} + +QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QList > paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} +QList > CategorizedView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + const QRect viewportRect = viewport()->rect(); + QList > ret; + for (int i = 0; i < indices.count(); ++i) { + const QModelIndex &index = indices.at(i); + const QRect current = visualRect(index); + if (current.intersects(viewportRect)) { + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + +bool CategorizedView::isDragEventAccepted(QDropEvent *event) +{ + if (event->source() != this) + { + return false; + } + if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) + { + return false; + } + if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), rowDropPos(event->pos()).second, 0, QModelIndex())) + { + return false; + } + return true; +} +QPair CategorizedView::rowDropPos(const QPoint &pos) +{ + // check that we aren't on a category header and calculate which category we're in + Category *category = 0; + { + int y = 0; + foreach (Category *cat, m_categories) + { + if (pos.y() > y && pos.y() < (y + cat->headerHeight())) + { + return qMakePair(nullptr, -1); + } + y += cat->totalHeight() + m_categoryMargin; + if (pos.y() < y) + { + category = cat; + break; + } + } + if (category == 0) + { + return qMakePair(nullptr, -1); + } + } + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = itemSize().width(); + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth, ++c) + { + if (pos.x() > (i - itemWidth / 2) && + pos.x() < (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + if (internalColumn == -1) + { + return qMakePair(nullptr, -1); + } + } + + // calculate the internal row + int internalRow = -1; + { + const int itemHeight = itemSize().height(); + const int top = categoryTop(category); + for (int i = top + category->headerHeight(), r = 0; + i < top + category->totalHeight(); + i += itemHeight, ++r) + { + if (pos.y() > i && pos.y() < (i + itemHeight)) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + return qMakePair(nullptr, -1); + } + } + + QList indices = itemsForCategory(category); + + // flaten the internalColumn/internalRow to one row + int categoryRow = 0; + { + for (int i = 0; i < internalRow; ++i) + { + if ((i + 1) >= internalRow) + { + break; + } + categoryRow += itemsPerRow(); + } + categoryRow += internalColumn; + } + + // this is used if we're past the last item + if (internalColumn >= qMin(itemsPerRow(), indices.size())) + { + return qMakePair(category, indices.last().row() + 1); + } + + return qMakePair(category, indices.at(categoryRow).row()); +} diff --git a/CategorizedView.h b/CategorizedView.h index 1e918496..0756629a 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -3,6 +3,7 @@ #include #include +#include class CategorizedView : public QListView { @@ -18,6 +19,8 @@ public: }; virtual QRect visualRect(const QModelIndex &index) const; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; protected slots: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); @@ -39,6 +42,8 @@ protected: void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + private: struct Category { @@ -59,6 +64,8 @@ private: friend struct Category; QList m_categories; + mutable QCache > m_cachedCategoryToIndexMapping; + mutable QCache m_cachedVisualRects; int m_leftMargin; int m_rightMargin; @@ -69,8 +76,11 @@ private: Category *category(const QModelIndex &index) const; Category *category(const QString &cat) const; + Category *categoryAt(const QPoint &pos) const; int numItemsForCategory(const Category *category) const; QList itemsForCategory(const Category *category) const; + QModelIndex firstItemForCategory(const Category *category) const; + QModelIndex lastItemForCategory(const Category *category) const; int categoryTop(const Category *category) const; @@ -91,6 +101,21 @@ private: private slots: void endCategoryEditor();*/ + +private: + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Category *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); }; #endif // WIDGET_H -- cgit From c71808446b3e95e4fefb91b69c2cc51e4c4918cc Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:23:21 +0100 Subject: Fix a bug --- CategorizedView.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 95505fd7..4d9a4a62 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -690,10 +690,6 @@ void CategorizedView::startDrag(Qt::DropActions supportedActions) } } -bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) -{ - return i1.data() < i2.data(); -} QRect CategorizedView::visualRect(const QModelIndex &index) const { if (!index.isValid() || isIndexHidden(index) || index.column() > 0) @@ -705,7 +701,6 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const { const Category *cat = category(index); QList indices = itemsForCategory(cat); - qSort(indices.begin(), indices.end(), &lessThanQModelIndex); int x = 0; int y = 0; const int perRow = itemsPerRow(); -- cgit From acbbdf319a7378a4029965a52222e7a84c33253f Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:24:42 +0100 Subject: Remove a debug message --- CategorizedView.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 4d9a4a62..780674eb 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -558,7 +558,6 @@ void CategorizedView::paintEvent(QPaintEvent *event) if (category) { int internalRow = row - firstItemForCategory(category).row(); - qDebug() << internalRow << numItemsForCategory(category) << model()->index(row, 0).data().toString(); QLine line; if (internalRow >= numItemsForCategory(category)) { -- cgit From 53db8edb851917809209e4473eef2a66651d6047 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 22:02:25 +0100 Subject: Fixing several d&d bugs --- CategorizedView.cpp | 26 +++++++++++--------------- main.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 780674eb..1860f095 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -872,6 +872,8 @@ QPair CategorizedView::rowDropPos(const QPoint } } + QList indices = itemsForCategory(category); + // calculate the internal column int internalColumn = -1; { @@ -912,26 +914,20 @@ QPair CategorizedView::rowDropPos(const QPoint { return qMakePair(nullptr, -1); } - } - - QList indices = itemsForCategory(category); - - // flaten the internalColumn/internalRow to one row - int categoryRow = 0; - { - for (int i = 0; i < internalRow; ++i) + // this happens if we're in the margin between a one category and another + // categories header + if (internalRow > (indices.size() / itemsPerRow())) { - if ((i + 1) >= internalRow) - { - break; - } - categoryRow += itemsPerRow(); + return qMakePair(nullptr, -1); } - categoryRow += internalColumn; } + // flaten the internalColumn/internalRow to one row + int categoryRow = internalRow * itemsPerRow() + internalColumn; + // this is used if we're past the last item - if (internalColumn >= qMin(itemsPerRow(), indices.size())) + int numItemsInLastRow = indices.size() % itemsPerRow(); + if (internalColumn >= numItemsInLastRow) { return qMakePair(category, indices.last().row() + 1); } diff --git a/main.cpp b/main.cpp index 24d7075e..427ae17a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ #include "CategorizedView.h" #include #include +#include #include "CategorizedProxyModel.h" @@ -10,6 +11,18 @@ QPixmap icon(const Qt::GlobalColor color) p.fill(QColor(color)); return p; } +QPixmap icon(const int number) +{ + QPixmap p = icon(Qt::white); + QPainter painter(&p); + QFont font = painter.font(); + font.setBold(true); + font.setPixelSize(28); + painter.setFont(font); + painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, QString::number(number)); + painter.end(); + return p; +} QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, const QString &category) { QStandardItem *item = new QStandardItem; @@ -19,6 +32,15 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, cons item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); return item; } +QStandardItem *createItem(const int index, const QString &category) +{ + QStandardItem *item = new QStandardItem; + item->setText(QString("Item #%1").arg(index)); + item->setData(icon(index), Qt::DecorationRole); + item->setData(category, CategorizedView::CategoryRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; +} int main(int argc, char *argv[]) { @@ -41,6 +63,11 @@ int main(int argc, char *argv[]) model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); model.setItem(9, createItem(Qt::green, "Green", "")); + for (int i = 0; i < 21; ++i) + { + model.setItem(i + 10, createItem(i+1, "Items 1-20")); + } + CategorizedProxyModel pModel; pModel.setSourceModel(&model); -- cgit From f8d835cd22de89bc130ff0413228cfea0ebfd8ac Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 22:40:26 +0100 Subject: Fix scrolling --- CategorizedView.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 1860f095..6164cde6 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -9,6 +9,7 @@ #include #include #include +#include template bool listsIntersect(const QList &l1, const QList t2) @@ -182,6 +183,22 @@ void CategorizedView::updateGeometries() qDeleteAll(m_categories); m_categories = cats.values(); + if (m_categories.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + foreach (const Category *category, m_categories) + { + totalHeight += category->totalHeight() + m_categoryMargin; + } + // remove the last margin (we don't want it) + totalHeight -= m_categoryMargin; + verticalScrollBar()->setRange(0, totalHeight- height()); + } + update(); } @@ -513,6 +530,8 @@ void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) void CategorizedView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); + QPoint offset(horizontalOffset(), verticalOffset()); + painter.translate(-offset); int y = 0; for (int i = 0; i < m_categories.size(); ++i) -- cgit From 01092206783f74ce14f31d328cdac025fd90fe16 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Fri, 27 Dec 2013 00:03:24 +0100 Subject: Take the spacing into account --- CategorizedView.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 6164cde6..c134220f 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -97,6 +97,7 @@ CategorizedView::CategorizedView(QWidget *parent) setWordWrap(true); setDragDropMode(QListView::InternalMove); setAcceptDrops(true); + setSpacing(10); m_cachedCategoryToIndexMapping.setMaxCost(50); m_cachedVisualRects.setMaxCost(50); @@ -308,7 +309,7 @@ int CategorizedView::categoryTop(const CategorizedView::Category *category) cons int CategorizedView::itemsPerRow() const { - return qFloor((qreal)contentWidth() / (qreal)itemSize().width()); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); } int CategorizedView::contentWidth() const { @@ -740,7 +741,7 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const QRect *out = new QRect; out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out->setLeft(x * size.width()); + out->setLeft(spacing() + x * itemWidth() + x * spacing()); out->setSize(size); m_cachedVisualRects.insert(index, out); -- cgit From 4662fbd29891ccb9120df82d17a34a7619242827 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 18:45:40 +0100 Subject: Make the MultiMC delegate fully usable. Dynamic row heights. --- CMakeLists.txt | 2 + CategorizedView.cpp | 104 ++++++++++++++------- CategorizedView.h | 9 +- InstanceDelegate.cpp | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++ InstanceDelegate.h | 27 ++++++ main.cpp | 4 +- 6 files changed, 363 insertions(+), 37 deletions(-) create mode 100644 InstanceDelegate.cpp create mode 100644 InstanceDelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a246bcf..b94cf53e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ set(SOURCES CategorizedView.cpp CategorizedProxyModel.h CategorizedProxyModel.cpp + InstanceDelegate.h + InstanceDelegate.cpp ) add_executable(GroupView ${SOURCES}) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index c134220f..bdb0b222 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -80,7 +80,22 @@ int CategorizedView::Category::contentHeight() const return 0; } const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); - return view->itemSize().height() * rows; + QMap rowToHeightMapping; + foreach (const QModelIndex &index, view->itemsForCategory(this)) + { + int row = view->categoryInternalPosition(index).second; + if (!rowToHeightMapping.contains(row)) + { + rowToHeightMapping.insert(row, view->itemSize(index).height()); + } + } + int result = 0; + for (int i = 0; i < rows; ++i) + { + Q_ASSERT(rowToHeightMapping.contains(i)); + result += rowToHeightMapping[i]; + } + return result; } QSize CategorizedView::Category::categoryTotalSize() const { @@ -153,9 +168,10 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); - m_cachedItemSize = QSize(); + m_cachedItemWidth = -1; m_cachedCategoryToIndexMapping.clear(); m_cachedVisualRects.clear(); + m_cachedItemSizes.clear(); QMap cats; @@ -323,24 +339,59 @@ QList CategorizedView::sortedCategories() const return out; } -QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const +int CategorizedView::itemWidth() const { - if (!m_cachedItemSize.isValid()) + if (m_cachedItemWidth == -1) { - QModelIndex sample = model()->index(model()->rowCount() -1, 0); - const QAbstractItemDelegate *delegate = itemDelegate(); - if (delegate) + m_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); + } + return m_cachedItemWidth; +} + +QSize CategorizedView::itemSize(const QModelIndex &index) const +{ + if (!m_cachedItemSizes.contains(index)) + { + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + foreach (const QModelIndex &i, itemsForCategory(category(index))) + { + if (categoryInternalPosition(i).second == internalRow) + { + indices.append(i); + } + } + + int largestHeight = 0; + foreach (const QModelIndex &i, indices) { - m_cachedItemSize = delegate->sizeHint(option, sample); - m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20); - m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20); + largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - else + m_cachedItemSizes.insert(index, new QSize(itemWidth(), largestHeight)); + } + return *m_cachedItemSizes.object(index); +} + +QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const +{ + QList indices = itemsForCategory(category(index)); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) + { + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) { - m_cachedItemSize = QSize(); + x = 0; + ++y; } } - return m_cachedItemSize; + return qMakePair(x, y); } void CategorizedView::mousePressEvent(QMouseEvent *event) @@ -719,25 +770,11 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const if (!m_cachedVisualRects.contains(index)) { const Category *cat = category(index); - QList indices = itemsForCategory(cat); - int x = 0; - int y = 0; - const int perRow = itemsPerRow(); - for (int i = 0; i < indices.size(); ++i) - { - if (indices.at(i) == index) - { - break; - } - ++x; - if (x == perRow) - { - x = 0; - ++y; - } - } + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; - QSize size = itemSize(); + QSize size = itemSize(index); QRect *out = new QRect; out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); @@ -897,7 +934,7 @@ QPair CategorizedView::rowDropPos(const QPoint // calculate the internal column int internalColumn = -1; { - const int itemWidth = itemSize().width(); + const int itemWidth = this->itemWidth(); for (int i = 0, c = 0; i < contentWidth(); i += itemWidth, ++c) @@ -918,7 +955,8 @@ QPair CategorizedView::rowDropPos(const QPoint // calculate the internal row int internalRow = -1; { - const int itemHeight = itemSize().height(); + // FIXME rework the drag and drop code + const int itemHeight = 0; //itemSize().height(); const int top = categoryTop(category); for (int i = top + category->headerHeight(), r = 0; i < top + category->totalHeight(); diff --git a/CategorizedView.h b/CategorizedView.h index 0756629a..e98e7c5e 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -91,9 +91,10 @@ private: QList sortedCategories() const; private: - mutable QSize m_cachedItemSize; - QSize itemSize(const QStyleOptionViewItem &option) const; - QSize itemSize() const { return itemSize(viewOptions()); } + mutable int m_cachedItemWidth; + mutable QCache m_cachedItemSizes; + int itemWidth() const; + QSize itemSize(const QModelIndex &index) const; /*QLineEdit *m_categoryEditor; Category *m_editedCategory; @@ -110,6 +111,8 @@ private: QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; + QPair categoryInternalPosition(const QModelIndex &index) const; + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp new file mode 100644 index 00000000..5020b8b6 --- /dev/null +++ b/InstanceDelegate.cpp @@ -0,0 +1,254 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InstanceDelegate.h" +#include +#include +#include +#include +#include + +// Origin: Qt +static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, + qreal &widthUsed) +{ + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); +} + +#define QFIXED_MAX (INT_MAX / 256) + +ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, + const QRect &rect) +{ + if ((option.state & QStyle::State_Selected)) + painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); + else + { + QColor backgroundColor = option.palette.color(QPalette::Background); + backgroundColor.setAlpha(160); + painter->fillRect(rect, QBrush(backgroundColor)); + } +} + +void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) +{ + if (!(option.state & QStyle::State_HasFocus)) + return; + QStyleOptionFocusRect opt; + opt.direction = option.direction; + opt.fontMetrics = option.fontMetrics; + opt.palette = option.palette; + opt.rect = rect; + // opt.state = option.state | QStyle::State_KeyboardFocusChange | + // QStyle::State_Item; + auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; + opt.backgroundColor = option.palette.color(col); + // Apparently some widget styles expect this hint to not be set + painter->setRenderHint(QPainter::Antialiasing, false); + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); + + painter->setRenderHint(QPainter::Antialiasing); +} + +static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) +{ + QStyle *style = option->widget ? option->widget->style() : QApplication::style(); + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + QRect bounds(0, 0, 100 - 2 * textMargin, 600); + qreal height = 0, widthUsed = 0; + viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); +} + +void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + painter->save(); + painter->setClipRect(opt.rect); + + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + + // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); + const int iconSize = 48; + QRect iconbox = opt.rect; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; + QRect textRect = opt.rect; + QRect textHighlightRect = textRect; + // clip the decoration on top, remove width padding + textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); + + textHighlightRect.adjust(0, iconSize + 5, 0, 0); + + // draw background + { + // FIXME: unused + // QSize textSize = viewItemTextSize ( &opt ); + QPalette::ColorGroup cg; + QStyleOptionViewItemV4 opt2(opt); + + if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) + { + if (!(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else + cg = QPalette::Normal; + } + else + { + cg = QPalette::Disabled; + } + opt2.palette.setCurrentColorGroup(cg); + + // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, opt.backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + if (opt.showDecorationSelected) + { + drawSelectionRect(painter, opt2, opt.rect); + drawFocusRect(painter, opt2, opt.rect); + // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); + } + else + { + + // if ( opt.state & QStyle::State_Selected ) + { + // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, + // opt.widget ); + // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, + // QPalette::Highlight ) ); + drawSelectionRect(painter, opt2, textHighlightRect); + drawFocusRect(painter, opt2, textHighlightRect); + } + } + } + + // draw the icon + { + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + iconbox.setHeight(iconSize); + opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + } + // set the text colors + QPalette::ColorGroup cg = + opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (opt.state & QStyle::State_Selected) + { + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else + { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + // draw the text + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + textOption.setTextDirection(opt.direction); + textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(opt.font); + textLayout.setText(opt.text); + + qreal width, height; + viewItemTextLayout(textLayout, textRect.width(), height, width); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect( + opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) + { + const QTextLine line = textLayout.lineAt(i); + line.draw(painter, position); + } + + painter->restore(); +} + +QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables + QSize szz = viewItemTextSize(&opt); + height += szz.height(); + // FIXME: maybe the icon items could scale and keep proportions? + QSize sz(100, height); + return sz; +} diff --git a/InstanceDelegate.h b/InstanceDelegate.h new file mode 100644 index 00000000..6f924405 --- /dev/null +++ b/InstanceDelegate.h @@ -0,0 +1,27 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class ListViewDelegate : public QStyledItemDelegate +{ +public: + explicit ListViewDelegate ( QObject* parent = 0 ); +protected: + void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; +}; diff --git a/main.cpp b/main.cpp index 427ae17a..b164b85d 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,7 @@ #include #include "CategorizedProxyModel.h" +#include "InstanceDelegate.h" QPixmap icon(const Qt::GlobalColor color) { @@ -50,7 +51,7 @@ int main(int argc, char *argv[]) model.setRowCount(10); model.setColumnCount(1); - model.setItem(0, createItem(Qt::red, "Red", "Colorful")); + model.setItem(0, createItem(Qt::red, "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", "Colorful")); model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); @@ -72,6 +73,7 @@ int main(int argc, char *argv[]) pModel.setSourceModel(&model); CategorizedView w; + w.setItemDelegate(new ListViewDelegate); w.setModel(&pModel); w.resize(640, 480); w.show(); -- cgit From 1e1b2342f48a3dd1eff90229ac4005fb0d45d2ba Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 18:46:12 +0100 Subject: Loads of fixes --- CategorizedView.cpp | 115 +++++++++++++++++++++++++++++++--------------------- CategorizedView.h | 8 +++- main.cpp | 2 +- 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index bdb0b222..f660e8f3 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -79,7 +79,6 @@ int CategorizedView::Category::contentHeight() const { return 0; } - const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); QMap rowToHeightMapping; foreach (const QModelIndex &index, view->itemsForCategory(this)) { @@ -90,20 +89,23 @@ int CategorizedView::Category::contentHeight() const } } int result = 0; - for (int i = 0; i < rows; ++i) + if (!rowToHeightMapping.isEmpty()) { - Q_ASSERT(rowToHeightMapping.contains(i)); - result += rowToHeightMapping[i]; + for (int i = 0; i < numRows(); ++i) + { + Q_ASSERT(rowToHeightMapping.contains(i)); + result += rowToHeightMapping[i]; + } } return result; } -QSize CategorizedView::Category::categoryTotalSize() const +int CategorizedView::Category::numRows() const { - return QSize(view->contentWidth(), contentHeight()); + return qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); } CategorizedView::CategorizedView(QWidget *parent) - : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) + : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { setViewMode(IconMode); setMovement(Snap); @@ -168,10 +170,7 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); - m_cachedItemWidth = -1; - m_cachedCategoryToIndexMapping.clear(); - m_cachedVisualRects.clear(); - m_cachedItemSizes.clear(); + invalidateCaches(); QMap cats; @@ -213,6 +212,7 @@ void CategorizedView::updateGeometries() } // remove the last margin (we don't want it) totalHeight -= m_categoryMargin; + totalHeight += m_bottomMargin; verticalScrollBar()->setRange(0, totalHeight- height()); } @@ -265,7 +265,7 @@ int CategorizedView::numItemsForCategory(const CategorizedView::Category *catego } QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const { - if (!m_cachedCategoryToIndexMapping.contains(category)) + if (!m_cachedCategoryToIndexMapping.contains(category) || true) { QList *indices = new QList(); for (int i = 0; i < model()->rowCount(); ++i) @@ -393,26 +393,30 @@ QPair CategorizedView::categoryInternalPosition(const QModelIndex &ind } return qMakePair(x, y); } +int CategorizedView::itemHeightForCategoryRow(const CategorizedView::Category *category, const int internalRow) const +{ + foreach (const QModelIndex &i, itemsForCategory(category)) + { + QPair pos = categoryInternalPosition(i); + if (pos.second == internalRow) + { + return itemSize(i).height(); + } + } + return -1; +} void CategorizedView::mousePressEvent(QMouseEvent *event) { //endCategoryEditor(); - QPoint pos = event->pos(); + QPoint pos = event->pos() + offset(); QPersistentModelIndex index = indexAt(pos); m_pressedIndex = index; m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - QPoint offset = QPoint(horizontalOffset(), verticalOffset()); - if (!(command & QItemSelectionModel::Current)) - { - m_pressedPosition = pos + offset; - } - else if (!indexAt(m_pressedPosition - offset).isValid()) - { - m_pressedPosition = visualRect(currentIndex()).center() + offset; - } + m_pressedPosition = pos; m_pressedCategory = categoryAt(m_pressedPosition); if (m_pressedCategory) @@ -430,7 +434,7 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) setAutoScroll(false); selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); setAutoScroll(autoScroll); - QRect rect(m_pressedPosition - offset, pos); + QRect rect(m_pressedPosition, pos); if (command.testFlag(QItemSelectionModel::Toggle)) { command &= ~QItemSelectionModel::Toggle; @@ -459,7 +463,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) if (state() == DraggingState) { - topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + topLeft = m_pressedPosition - offset(); if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) { m_pressedIndex = QModelIndex(); @@ -474,7 +478,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) if (selectionMode() != SingleSelection) { - topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + topLeft = m_pressedPosition - offset(); } else { @@ -515,7 +519,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) } void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - QPoint pos = event->pos(); + QPoint pos = event->pos() - offset(); QPersistentModelIndex index = indexAt(pos); bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); @@ -582,8 +586,10 @@ void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) void CategorizedView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); - QPoint offset(horizontalOffset(), verticalOffset()); - painter.translate(-offset); + painter.translate(-offset()); + + // FIXME we shouldn't need to do this + invalidateCaches(); int y = 0; for (int i = 0; i < m_categories.size(); ++i) @@ -665,7 +671,7 @@ void CategorizedView::dragEnterEvent(QDragEnterEvent *event) { return; } - m_lastDragPosition = event->pos(); + m_lastDragPosition = event->pos() + offset(); viewport()->update(); event->accept(); } @@ -675,7 +681,7 @@ void CategorizedView::dragMoveEvent(QDragMoveEvent *event) { return; } - m_lastDragPosition = event->pos(); + m_lastDragPosition = event->pos() + offset(); viewport()->update(); event->accept(); } @@ -696,7 +702,7 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - QPair dropPos = rowDropPos(event->pos()); + QPair dropPos = rowDropPos(event->pos() + offset()); const Category *category = dropPos.first; const int row = dropPos.second; @@ -729,7 +735,8 @@ void CategorizedView::startDrag(Qt::DropActions supportedActions) } QRect rect; QPixmap pixmap = renderToPixmap(indexes, &rect); - rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + rect.translate(offset()); + //rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); @@ -935,15 +942,22 @@ QPair CategorizedView::rowDropPos(const QPoint int internalColumn = -1; { const int itemWidth = this->itemWidth(); - for (int i = 0, c = 0; - i < contentWidth(); - i += itemWidth, ++c) + if (pos.x() >= (itemWidth * itemsPerRow())) { - if (pos.x() > (i - itemWidth / 2) && - pos.x() < (i + itemWidth / 2)) + internalColumn = itemsPerRow(); + } + else + { + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth + spacing(), ++c) { - internalColumn = c; - break; + if (pos.x() > (i - itemWidth / 2) && + pos.x() <= (i + itemWidth / 2)) + { + internalColumn = c; + break; + } } } if (internalColumn == -1) @@ -956,13 +970,10 @@ QPair CategorizedView::rowDropPos(const QPoint int internalRow = -1; { // FIXME rework the drag and drop code - const int itemHeight = 0; //itemSize().height(); const int top = categoryTop(category); - for (int i = top + category->headerHeight(), r = 0; - i < top + category->totalHeight(); - i += itemHeight, ++r) + for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { - if (pos.y() > i && pos.y() < (i + itemHeight)) + if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) { internalRow = r; break; @@ -984,11 +995,23 @@ QPair CategorizedView::rowDropPos(const QPoint int categoryRow = internalRow * itemsPerRow() + internalColumn; // this is used if we're past the last item - int numItemsInLastRow = indices.size() % itemsPerRow(); - if (internalColumn >= numItemsInLastRow) + if (categoryRow >= indices.size()) { return qMakePair(category, indices.last().row() + 1); } return qMakePair(category, indices.at(categoryRow).row()); } + +void CategorizedView::invalidateCaches() +{ + m_cachedItemWidth = -1; + m_cachedCategoryToIndexMapping.clear(); + m_cachedVisualRects.clear(); + m_cachedItemSizes.clear(); +} + +QPoint CategorizedView::offset() const +{ + return QPoint(horizontalOffset(), verticalOffset()); +} diff --git a/CategorizedView.h b/CategorizedView.h index e98e7c5e..8ab9ce87 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -59,7 +59,7 @@ private: int totalHeight() const; int headerHeight() const; int contentHeight() const; - QSize categoryTotalSize() const; + int numRows() const; }; friend struct Category; @@ -69,6 +69,7 @@ private: int m_leftMargin; int m_rightMargin; + int m_bottomMargin; int m_categoryMargin; int m_itemSpacing; @@ -112,6 +113,7 @@ private: QPoint m_lastDragPosition; QPair categoryInternalPosition(const QModelIndex &index) const; + int itemHeightForCategoryRow(const Category *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; @@ -119,6 +121,10 @@ private: bool isDragEventAccepted(QDropEvent *event); QPair rowDropPos(const QPoint &pos); + + void invalidateCaches(); + + QPoint offset() const; }; #endif // WIDGET_H diff --git a/main.cpp b/main.cpp index b164b85d..58d1c9ba 100644 --- a/main.cpp +++ b/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); model.setItem(9, createItem(Qt::green, "Green", "")); - for (int i = 0; i < 21; ++i) + for (int i = 0; i < 20; ++i) { model.setItem(i + 10, createItem(i+1, "Items 1-20")); } -- cgit From e6be883d14a4147ffd58a1c8066bb70879d775fb Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 23:10:53 +0100 Subject: Fixing more bugs --- CategorizedView.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index f660e8f3..27575b59 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -170,6 +170,8 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); + int previousScroll = verticalScrollBar()->value(); + invalidateCaches(); QMap cats; @@ -216,6 +218,8 @@ void CategorizedView::updateGeometries() verticalScrollBar()->setRange(0, totalHeight- height()); } + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + update(); } @@ -435,13 +439,7 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); setAutoScroll(autoScroll); QRect rect(m_pressedPosition, pos); - if (command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - m_ctrlDragSelectionFlag = selectionModel()->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; - command |= m_ctrlDragSelectionFlag; - } - setSelection(rect, command); + setSelection(rect, QItemSelectionModel::ClearAndSelect); // signal handlers may change the model emit pressed(index); @@ -519,7 +517,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) } void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - QPoint pos = event->pos() - offset(); + QPoint pos = event->pos() + offset(); QPersistentModelIndex index = indexAt(pos); bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); @@ -611,7 +609,7 @@ void CategorizedView::paintEvent(QPaintEvent *event) option.rect = visualRect(index); option.widget = this; option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; - if (flags & Qt::ItemIsSelectable) + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) { option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None; } @@ -844,16 +842,15 @@ QModelIndex CategorizedView::indexAt(const QPoint &point) const } void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) { - QItemSelection selection; for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); if (visualRect(index).intersects(rect)) { - selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + selectionModel()->select(index, commands); } } - selectionModel()->select(selection, commands); + update(); } QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const -- cgit From 8cfd0881ac3fcbb45fd42dedcb1caf0be38eacaf Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 23:39:10 +0100 Subject: Progress indicators --- CMakeLists.txt | 1 + CategorizedProxyModel.cpp | 4 ++-- CategorizedView.cpp | 10 ++++----- CategorizedView.h | 15 +++++++++----- InstanceDelegate.cpp | 25 ++++++++++++++++++++++ main.cpp | 19 +++++++++++++---- main.h | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 main.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b94cf53e..029c90b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_ set(SOURCES main.cpp + main.h CategorizedView.h CategorizedView.cpp diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp index e4a7563a..efcf13c8 100644 --- a/CategorizedProxyModel.cpp +++ b/CategorizedProxyModel.cpp @@ -8,8 +8,8 @@ CategorizedProxyModel::CategorizedProxyModel(QObject *parent) } bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(CategorizedView::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedView::CategoryRole).toString(); + const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); if (leftCategory == rightCategory) { return left.row() < right.row(); diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 27575b59..6bf18cbc 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -135,7 +135,7 @@ void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex QListView::dataChanged(topLeft, bottomRight, roles); - if (roles.contains(CategoryRole)) + if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); update(); @@ -178,7 +178,7 @@ void CategorizedView::updateGeometries() for (int i = 0; i < model()->rowCount(); ++i) { - const QString category = model()->index(i, 0).data(CategoryRole).toString(); + const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); if (!cats.contains(category)) { Category *old = this->category(category); @@ -238,7 +238,7 @@ bool CategorizedView::isIndexHidden(const QModelIndex &index) const CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const { - return category(index.data(CategoryRole).toString()); + return category(index.data(CategorizedViewRoles::CategoryRole).toString()); } CategorizedView::Category *CategorizedView::category(const QString &cat) const { @@ -274,7 +274,7 @@ QList CategorizedView::itemsForCategory(const CategorizedView::Cate QList *indices = new QList(); for (int i = 0; i < model()->rowCount(); ++i) { - if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + if (model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString() == category->text) { indices->append(model()->index(i, 0)); } @@ -713,7 +713,7 @@ void CategorizedView::dropEvent(QDropEvent *event) const QString categoryText = category->text; if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { - model()->setData(model()->index(row, 0), categoryText, CategoryRole); + model()->setData(model()->index(row, 0), categoryText, CategorizedViewRoles::CategoryRole); event->setDropAction(Qt::MoveAction); event->accept(); } diff --git a/CategorizedView.h b/CategorizedView.h index 8ab9ce87..08d43be8 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -5,6 +5,16 @@ #include #include +struct CategorizedViewRoles +{ + enum + { + CategoryRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + class CategorizedView : public QListView { Q_OBJECT @@ -13,11 +23,6 @@ public: CategorizedView(QWidget *parent = 0); ~CategorizedView(); - enum - { - CategoryRole = Qt::UserRole - }; - virtual QRect visualRect(const QModelIndex &index) const; QModelIndex indexAt(const QPoint &point) const; void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 5020b8b6..50cead55 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -20,6 +20,8 @@ #include #include +#include "CategorizedView.h" + // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, qreal &widthUsed) @@ -85,6 +87,26 @@ void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, cons painter->setRenderHint(QPainter::Antialiasing); } +// TODO this can be made a lot prettier +void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, const int value, const int maximum) +{ + if (maximum == 0 || value == maximum) + { + return; + } + + painter->save(); + + qreal percent = (qreal)value / (qreal)maximum; + QColor color = option.palette.color(QPalette::Dark); + color.setAlphaF(0.70f); + painter->setBrush(color); + painter->setPen(QPen(QBrush(), 0)); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 60); + + painter->restore(); +} + static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) { QStyle *style = option->widget ? option->widget->style() : QApplication::style(); @@ -229,6 +251,9 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti line.draw(painter, position); } + drawProgressOverlay(painter, opt, index.data(CategorizedViewRoles::ProgressValueRole).toInt(), + index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); + painter->restore(); } diff --git a/main.cpp b/main.cpp index 58d1c9ba..bd6a44f9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,16 @@ -#include "CategorizedView.h" +#include "main.h" + #include #include #include +#include +#include "CategorizedView.h" #include "CategorizedProxyModel.h" #include "InstanceDelegate.h" +Progresser *progresser; + QPixmap icon(const Qt::GlobalColor color) { QPixmap p = QPixmap(32, 32); @@ -29,8 +34,9 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, cons QStandardItem *item = new QStandardItem; item->setText(text); item->setData(icon(color), Qt::DecorationRole); - item->setData(category, CategorizedView::CategoryRole); + item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + //progresser->addTrackedIndex(item); return item; } QStandardItem *createItem(const int index, const QString &category) @@ -38,8 +44,9 @@ QStandardItem *createItem(const int index, const QString &category) QStandardItem *item = new QStandardItem; item->setText(QString("Item #%1").arg(index)); item->setData(icon(index), Qt::DecorationRole); - item->setData(category, CategorizedView::CategoryRole); + item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + //progresser->addTrackedIndex(item); return item; } @@ -47,6 +54,10 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); + qsrand(QTime::currentTime().msec()); + + progresser = new Progresser(); + QStandardItemModel model; model.setRowCount(10); model.setColumnCount(1); @@ -62,7 +73,7 @@ int main(int argc, char *argv[]) model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); - model.setItem(9, createItem(Qt::green, "Green", "")); + model.setItem(9, progresser->addTrackedIndex(createItem(Qt::green, "Green", ""))); for (int i = 0; i < 20; ++i) { diff --git a/main.h b/main.h new file mode 100644 index 00000000..f4c7a3f8 --- /dev/null +++ b/main.h @@ -0,0 +1,53 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include +#include +#include +#include + +#include "CategorizedView.h" + +class Progresser : public QObject +{ + Q_OBJECT +public: + explicit Progresser(QObject *parent = 0) : QObject(parent) + { + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer->start(50); + } + + QStandardItem *addTrackedIndex(QStandardItem *item) + { + item->setData(1000, CategorizedViewRoles::ProgressMaximumRole); + m_items.append(item); + return item; + } + +public slots: + void timeout() + { + foreach (QStandardItem *item, m_items) + { + int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); + value += qrand() % 3; + if (value >= item->data(CategorizedViewRoles::ProgressMaximumRole).toInt()) + { + item->setData(item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(), + CategorizedViewRoles::ProgressValueRole); + } + else + { + item->setData(value, CategorizedViewRoles::ProgressValueRole); + } + } + } + +private: + QList m_items; +}; + +#endif // MAIN_H -- cgit From c47933d95cae407a99acfbbb941655f7cd52e1ae Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Tue, 31 Dec 2013 17:26:36 +0100 Subject: Loads of changes and some refactorings --- CMakeLists.txt | 2 + CategorizedView.cpp | 296 ++++++++++---------------------------------- CategorizedView.h | 56 ++------- CategorizedViewCategory.cpp | 128 +++++++++++++++++++ CategorizedViewCategory.h | 39 ++++++ 5 files changed, 248 insertions(+), 273 deletions(-) create mode 100644 CategorizedViewCategory.cpp create mode 100644 CategorizedViewCategory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 029c90b3..44a28c57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,8 @@ set(SOURCES CategorizedView.h CategorizedView.cpp + CategorizedViewCategory.h + CategorizedViewCategory.cpp CategorizedProxyModel.h CategorizedProxyModel.cpp InstanceDelegate.h diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 6bf18cbc..60230661 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -11,6 +11,8 @@ #include #include +#include "CategorizedViewCategory.h" + template bool listsIntersect(const QList &l1, const QList t2) { @@ -24,86 +26,6 @@ bool listsIntersect(const QList &l1, const QList t2) return false; } -CategorizedView::Category::Category(const QString &text, CategorizedView *view) - : view(view), text(text), collapsed(false) -{ -} -CategorizedView::Category::Category(const CategorizedView::Category *other) : - view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) -{ -} - -void CategorizedView::Category::drawHeader(QPainter *painter, const int y) -{ - painter->save(); - - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); - painter->setPen(QPen(Qt::black, 1)); - painter->drawRect(iconRect); - static const int margin = 2; - QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); - int midX = iconSubrect.center().x(); - int midY = iconSubrect.center().y(); - if (collapsed) - { - painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); - } - painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); - - // the text - int textWidth = painter->fontMetrics().width(text); - textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->palette(), true, text); - - // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); - - painter->restore(); -} - -int CategorizedView::Category::totalHeight() const -{ - return headerHeight() + 5 + contentHeight(); -} -int CategorizedView::Category::headerHeight() const -{ - return qApp->fontMetrics().height() + 4; -} -int CategorizedView::Category::contentHeight() const -{ - if (collapsed) - { - return 0; - } - QMap rowToHeightMapping; - foreach (const QModelIndex &index, view->itemsForCategory(this)) - { - int row = view->categoryInternalPosition(index).second; - if (!rowToHeightMapping.contains(row)) - { - rowToHeightMapping.insert(row, view->itemSize(index).height()); - } - } - int result = 0; - if (!rowToHeightMapping.isEmpty()) - { - for (int i = 0; i < numRows(); ++i) - { - Q_ASSERT(rowToHeightMapping.contains(i)); - result += rowToHeightMapping[i]; - } - } - return result; -} -int CategorizedView::Category::numRows() const -{ - return qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); -} - CategorizedView::CategorizedView(QWidget *parent) : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { @@ -115,9 +37,6 @@ CategorizedView::CategorizedView(QWidget *parent) setDragDropMode(QListView::InternalMove); setAcceptDrops(true); setSpacing(10); - - m_cachedCategoryToIndexMapping.setMaxCost(50); - m_cachedVisualRects.setMaxCost(50); } CategorizedView::~CategorizedView() @@ -172,23 +91,21 @@ void CategorizedView::updateGeometries() int previousScroll = verticalScrollBar()->value(); - invalidateCaches(); - - QMap cats; + QMap cats; for (int i = 0; i < model()->rowCount(); ++i) { const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); if (!cats.contains(category)) { - Category *old = this->category(category); + CategorizedViewCategory *old = this->category(category); if (old) { - cats.insert(category, new Category(old)); + cats.insert(category, new CategorizedViewCategory(old)); } else { - cats.insert(category, new Category(category, this)); + cats.insert(category, new CategorizedViewCategory(category, this)); } } } @@ -201,6 +118,11 @@ void CategorizedView::updateGeometries() qDeleteAll(m_categories); m_categories = cats.values(); + for (auto cat : m_categories) + { + cat->update(); + } + if (m_categories.isEmpty()) { verticalScrollBar()->setRange(0, 0); @@ -208,7 +130,7 @@ void CategorizedView::updateGeometries() else { int totalHeight = 0; - foreach (const Category *category, m_categories) + foreach (const CategorizedViewCategory *category, m_categories) { totalHeight += category->totalHeight() + m_categoryMargin; } @@ -225,7 +147,7 @@ void CategorizedView::updateGeometries() bool CategorizedView::isIndexHidden(const QModelIndex &index) const { - Category *cat = category(index); + CategorizedViewCategory *cat = category(index); if (cat) { return cat->collapsed; @@ -236,11 +158,11 @@ bool CategorizedView::isIndexHidden(const QModelIndex &index) const } } -CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const +CategorizedViewCategory *CategorizedView::category(const QModelIndex &index) const { return category(index.data(CategorizedViewRoles::CategoryRole).toString()); } -CategorizedView::Category *CategorizedView::category(const QString &cat) const +CategorizedViewCategory *CategorizedView::category(const QString &cat) const { for (int i = 0; i < m_categories.size(); ++i) { @@ -251,7 +173,7 @@ CategorizedView::Category *CategorizedView::category(const QString &cat) const } return 0; } -CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const +CategorizedViewCategory *CategorizedView::categoryAt(const QPoint &pos) const { for (int i = 0; i < m_categories.size(); ++i) { @@ -263,70 +185,6 @@ CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const return 0; } -int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const -{ - return itemsForCategory(category).size(); -} -QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const -{ - if (!m_cachedCategoryToIndexMapping.contains(category) || true) - { - QList *indices = new QList(); - for (int i = 0; i < model()->rowCount(); ++i) - { - if (model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString() == category->text) - { - indices->append(model()->index(i, 0)); - } - } - m_cachedCategoryToIndexMapping.insert(category, indices, indices->size()); - } - return *m_cachedCategoryToIndexMapping.object(category); -} -QModelIndex CategorizedView::firstItemForCategory(const CategorizedView::Category *category) const -{ - QList indices = itemsForCategory(category); - QModelIndex first; - foreach (const QModelIndex &index, indices) - { - if (index.row() < first.row() || !first.isValid()) - { - first = index; - } - } - - return first; -} -QModelIndex CategorizedView::lastItemForCategory(const CategorizedView::Category *category) const -{ - QList indices = itemsForCategory(category); - QModelIndex last; - foreach (const QModelIndex &index, indices) - { - if (index.row() > last.row() || !last.isValid()) - { - last = index; - } - } - - return last; -} - -int CategorizedView::categoryTop(const CategorizedView::Category *category) const -{ - int res = 0; - const QList cats = sortedCategories(); - for (int i = 0; i < cats.size(); ++i) - { - if (cats.at(i) == category) - { - break; - } - res += cats.at(i)->totalHeight() + m_categoryMargin; - } - return res; -} - int CategorizedView::itemsPerRow() const { return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); @@ -336,49 +194,34 @@ int CategorizedView::contentWidth() const return width() - m_leftMargin - m_rightMargin; } -QList CategorizedView::sortedCategories() const -{ - QList out = m_categories; - qSort(out.begin(), out.end(), [](const Category *c1, const Category *c2) { return c1->text < c2->text; }); - return out; -} - int CategorizedView::itemWidth() const { - if (m_cachedItemWidth == -1) - { - m_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); - } - return m_cachedItemWidth; + return itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); } -QSize CategorizedView::itemSize(const QModelIndex &index) const +int CategorizedView::categoryRowHeight(const QModelIndex &index) const { - if (!m_cachedItemSizes.contains(index)) + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + foreach (const QModelIndex &i, category(index)->items()) { - QModelIndexList indices; - int internalRow = categoryInternalPosition(index).second; - foreach (const QModelIndex &i, itemsForCategory(category(index))) + if (categoryInternalPosition(i).second == internalRow) { - if (categoryInternalPosition(i).second == internalRow) - { - indices.append(i); - } + indices.append(i); } + } - int largestHeight = 0; - foreach (const QModelIndex &i, indices) - { - largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); - } - m_cachedItemSizes.insert(index, new QSize(itemWidth(), largestHeight)); + int largestHeight = 0; + foreach (const QModelIndex &i, indices) + { + largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - return *m_cachedItemSizes.object(index); + return largestHeight; } QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const { - QList indices = itemsForCategory(category(index)); + QList indices = category(index)->items(); int x = 0; int y = 0; const int perRow = itemsPerRow(); @@ -397,14 +240,25 @@ QPair CategorizedView::categoryInternalPosition(const QModelIndex &ind } return qMakePair(x, y); } -int CategorizedView::itemHeightForCategoryRow(const CategorizedView::Category *category, const int internalRow) const +int CategorizedView::categoryInternalRowTop(const QModelIndex &index) const { - foreach (const QModelIndex &i, itemsForCategory(category)) + CategorizedViewCategory *cat = category(index); + int categoryInternalRow = categoryInternalPosition(index).second; + int result = 0; + for (int i = 0; i < categoryInternalRow; ++i) + { + result += cat->rowHeights.at(i); + } + return result; +} +int CategorizedView::itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const +{ + foreach (const QModelIndex &i, category->items()) { QPair pos = categoryInternalPosition(i); if (pos.second == internalRow) { - return itemSize(i).height(); + return categoryRowHeight(i); } } return -1; @@ -586,13 +440,10 @@ void CategorizedView::paintEvent(QPaintEvent *event) QPainter painter(this->viewport()); painter.translate(-offset()); - // FIXME we shouldn't need to do this - invalidateCaches(); - int y = 0; for (int i = 0; i < m_categories.size(); ++i) { - Category *category = m_categories.at(i); + CategorizedViewCategory *category = m_categories.at(i); category->drawHeader(&painter, y); y += category->totalHeight() + m_categoryMargin; } @@ -627,16 +478,16 @@ void CategorizedView::paintEvent(QPaintEvent *event) if (!m_lastDragPosition.isNull()) { - QPair pair = rowDropPos(m_lastDragPosition); - Category *category = pair.first; + QPair pair = rowDropPos(m_lastDragPosition); + CategorizedViewCategory *category = pair.first; int row = pair.second; if (category) { - int internalRow = row - firstItemForCategory(category).row(); + int internalRow = row - category->firstRow; QLine line; - if (internalRow >= numItemsForCategory(category)) + if (internalRow >= category->numItems()) { - QRect toTheRightOfRect = visualRect(lastItemForCategory(category)); + QRect toTheRightOfRect = visualRect(category->lastItem()); line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); } else @@ -700,8 +551,8 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - QPair dropPos = rowDropPos(event->pos() + offset()); - const Category *category = dropPos.first; + QPair dropPos = rowDropPos(event->pos() + offset()); + const CategorizedViewCategory *category = dropPos.first; const int row = dropPos.second; if (row == -1) @@ -772,24 +623,17 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const return QRect(); } - if (!m_cachedVisualRects.contains(index)) - { - const Category *cat = category(index); - QPair pos = categoryInternalPosition(index); - int x = pos.first; - int y = pos.second; - - QSize size = itemSize(index); + const CategorizedViewCategory *cat = category(index); + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; - QRect *out = new QRect; - out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out->setLeft(spacing() + x * itemWidth() + x * spacing()); - out->setSize(size); + QRect out; + out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(spacing() + x * itemWidth() + x * spacing()); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - m_cachedVisualRects.insert(index, out); - } - - return *m_cachedVisualRects.object(index); + return out; } /* void CategorizedView::startCategoryEditor(Category *category) @@ -908,13 +752,13 @@ bool CategorizedView::isDragEventAccepted(QDropEvent *event) } return true; } -QPair CategorizedView::rowDropPos(const QPoint &pos) +QPair CategorizedView::rowDropPos(const QPoint &pos) { // check that we aren't on a category header and calculate which category we're in - Category *category = 0; + CategorizedViewCategory *category = 0; { int y = 0; - foreach (Category *cat, m_categories) + foreach (CategorizedViewCategory *cat, m_categories) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { @@ -933,7 +777,7 @@ QPair CategorizedView::rowDropPos(const QPoint } } - QList indices = itemsForCategory(category); + QList indices = category->items(); // calculate the internal column int internalColumn = -1; @@ -967,7 +811,7 @@ QPair CategorizedView::rowDropPos(const QPoint int internalRow = -1; { // FIXME rework the drag and drop code - const int top = categoryTop(category); + const int top = category->top(); for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) @@ -1000,14 +844,6 @@ QPair CategorizedView::rowDropPos(const QPoint return qMakePair(category, indices.at(categoryRow).row()); } -void CategorizedView::invalidateCaches() -{ - m_cachedItemWidth = -1; - m_cachedCategoryToIndexMapping.clear(); - m_cachedVisualRects.clear(); - m_cachedItemSizes.clear(); -} - QPoint CategorizedView::offset() const { return QPoint(horizontalOffset(), verticalOffset()); diff --git a/CategorizedView.h b/CategorizedView.h index 08d43be8..0550c7f8 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -3,7 +3,6 @@ #include #include -#include struct CategorizedViewRoles { @@ -15,6 +14,8 @@ struct CategorizedViewRoles }; }; +struct CategorizedViewCategory; + class CategorizedView : public QListView { Q_OBJECT @@ -28,7 +29,7 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); virtual void rowsInserted(const QModelIndex &parent, int start, int end); virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); virtual void updateGeometries(); @@ -50,57 +51,27 @@ protected: void startDrag(Qt::DropActions supportedActions) override; private: - struct Category - { - Category(const QString &text, CategorizedView *view); - Category(const Category *other); - CategorizedView *view; - QString text; - bool collapsed; - QRect iconRect; - QRect textRect; - - void drawHeader(QPainter *painter, const int y); - int totalHeight() const; - int headerHeight() const; - int contentHeight() const; - int numRows() const; - }; - friend struct Category; + friend struct CategorizedViewCategory; - QList m_categories; - mutable QCache > m_cachedCategoryToIndexMapping; - mutable QCache m_cachedVisualRects; + QList m_categories; int m_leftMargin; int m_rightMargin; int m_bottomMargin; int m_categoryMargin; - int m_itemSpacing; //bool m_updatesDisabled; - Category *category(const QModelIndex &index) const; - Category *category(const QString &cat) const; - Category *categoryAt(const QPoint &pos) const; - int numItemsForCategory(const Category *category) const; - QList itemsForCategory(const Category *category) const; - QModelIndex firstItemForCategory(const Category *category) const; - QModelIndex lastItemForCategory(const Category *category) const; - - int categoryTop(const Category *category) const; + CategorizedViewCategory *category(const QModelIndex &index) const; + CategorizedViewCategory *category(const QString &cat) const; + CategorizedViewCategory *categoryAt(const QPoint &pos) const; int itemsPerRow() const; int contentWidth() const; - static bool lessThanCategoryPointer(const Category *c1, const Category *c2); - QList sortedCategories() const; - private: - mutable int m_cachedItemWidth; - mutable QCache m_cachedItemSizes; int itemWidth() const; - QSize itemSize(const QModelIndex &index) const; + int categoryRowHeight(const QModelIndex &index) const; /*QLineEdit *m_categoryEditor; Category *m_editedCategory; @@ -113,21 +84,20 @@ private: QPoint m_pressedPosition; QPersistentModelIndex m_pressedIndex; bool m_pressedAlreadySelected; - Category *m_pressedCategory; + CategorizedViewCategory *m_pressedCategory; QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; QPair categoryInternalPosition(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Category *category, const int internalRow) const; + int categoryInternalRowTop(const QModelIndex &index) const; + int itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; bool isDragEventAccepted(QDropEvent *event); - QPair rowDropPos(const QPoint &pos); - - void invalidateCaches(); + QPair rowDropPos(const QPoint &pos); QPoint offset() const; }; diff --git a/CategorizedViewCategory.cpp b/CategorizedViewCategory.cpp new file mode 100644 index 00000000..b82ffc96 --- /dev/null +++ b/CategorizedViewCategory.cpp @@ -0,0 +1,128 @@ +#include "CategorizedViewCategory.h" + +#include +#include +#include + +#include "CategorizedView.h" + +CategorizedViewCategory::CategorizedViewCategory(const QString &text, CategorizedView *view) + : view(view), text(text), collapsed(false) +{ +} +CategorizedViewCategory::CategorizedViewCategory(const CategorizedViewCategory *other) : + view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void CategorizedViewCategory::update() +{ + firstRow = firstItem().row(); + + rowHeights = QVector(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight(view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + } +} + +void CategorizedViewCategory::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); + painter->setPen(QPen(Qt::black, 1)); + painter->drawRect(iconRect); + static const int margin = 2; + QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + int midX = iconSubrect.center().x(); + int midY = iconSubrect.center().y(); + if (collapsed) + { + painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + } + painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); + + // the text + int textWidth = painter->fontMetrics().width(text); + textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->setBrush(view->viewOptions().palette.text()); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->viewport()->palette(), true, text); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int CategorizedViewCategory::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); +} +int CategorizedViewCategory::headerHeight() const +{ + return view->viewport()->fontMetrics().height() + 4; +} +int CategorizedViewCategory::contentHeight() const +{ + if (collapsed) + { + return 0; + } + int result = 0; + for (int i = 0; i < rowHeights.size(); ++i) + { + result += rowHeights[i]; + } + return result; +} +int CategorizedViewCategory::numRows() const +{ + return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); +} +int CategorizedViewCategory::top() const +{ + int res = 0; + const QList cats = view->m_categories; + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == this) + { + break; + } + res += cats.at(i)->totalHeight() + view->m_categoryMargin; + } + return res; +} + +QList CategorizedViewCategory::items() const +{ + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} +int CategorizedViewCategory::numItems() const +{ + return items().size(); +} +QModelIndex CategorizedViewCategory::firstItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} +QModelIndex CategorizedViewCategory::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/CategorizedViewCategory.h b/CategorizedViewCategory.h new file mode 100644 index 00000000..cb6ef8c5 --- /dev/null +++ b/CategorizedViewCategory.h @@ -0,0 +1,39 @@ +#ifndef CATEGORIZEDVIEWROW_H +#define CATEGORIZEDVIEWROW_H + +#include +#include +#include + +class CategorizedView; +class QPainter; +class QModelIndex; + +struct CategorizedViewCategory +{ + CategorizedViewCategory(const QString &text, CategorizedView *view); + CategorizedViewCategory(const CategorizedViewCategory *other); + CategorizedView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + QVector rowHeights; + int firstRow; + + void update(); + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + int numRows() const; + int top() const; + + QList items() const; + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; + +#endif // CATEGORIZEDVIEWROW_H -- cgit From a1a06cc89f7f1d904a1b71d330d6129b866ff29b Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Wed, 22 Jan 2014 07:33:32 +0100 Subject: Derpstances. Everything renamed. Launching does not yet work. --- CMakeLists.txt | 36 +-- gui/MainWindow.cpp | 4 +- gui/dialogs/DerpModEditDialog.cpp | 300 +++++++++++++++++++++++++ gui/dialogs/DerpModEditDialog.h | 67 ++++++ gui/dialogs/DerpModEditDialog.ui | 317 ++++++++++++++++++++++++++ gui/dialogs/OneSixModEditDialog.cpp | 364 ------------------------------ gui/dialogs/OneSixModEditDialog.h | 69 ------ gui/dialogs/OneSixModEditDialog.ui | 340 ---------------------------- logic/BaseInstance.h | 2 +- logic/DerpFTBInstance.cpp | 123 +++++++++++ logic/DerpFTBInstance.h | 22 ++ logic/DerpInstance.cpp | 373 +++++++++++++++++++++++++++++++ logic/DerpInstance.h | 73 ++++++ logic/DerpInstance_p.h | 27 +++ logic/DerpLibrary.cpp | 268 ++++++++++++++++++++++ logic/DerpLibrary.h | 132 +++++++++++ logic/DerpRule.cpp | 89 ++++++++ logic/DerpRule.h | 98 +++++++++ logic/DerpUpdate.cpp | 377 +++++++++++++++++++++++++++++++ logic/DerpUpdate.h | 63 ++++++ logic/DerpVersion.cpp | 164 ++++++++++++++ logic/DerpVersion.h | 110 +++++++++ logic/DerpVersionBuilder.cpp | 279 +++++++++++++++++++++++ logic/DerpVersionBuilder.h | 43 ++++ logic/ForgeInstaller.cpp | 22 +- logic/ForgeInstaller.h | 6 +- logic/InstanceFactory.cpp | 34 +-- logic/LegacyInstance.h | 2 +- logic/LiteLoaderInstaller.cpp | 25 +-- logic/LiteLoaderInstaller.h | 8 +- logic/MinecraftVersion.h | 6 +- logic/NostalgiaInstance.cpp | 2 +- logic/NostalgiaInstance.h | 4 +- logic/OneSixFTBInstance.cpp | 125 ----------- logic/OneSixFTBInstance.h | 22 -- logic/OneSixInstance.cpp | 416 ----------------------------------- logic/OneSixInstance.h | 78 ------- logic/OneSixInstance_p.h | 30 --- logic/OneSixLibrary.cpp | 268 ---------------------- logic/OneSixLibrary.h | 132 ----------- logic/OneSixRule.cpp | 89 -------- logic/OneSixRule.h | 98 --------- logic/OneSixUpdate.cpp | 377 ------------------------------- logic/OneSixUpdate.h | 63 ------ logic/OneSixVersion.cpp | 50 ++--- logic/OneSixVersion.h | 14 +- logic/lists/MinecraftVersionList.cpp | 6 +- 47 files changed, 3039 insertions(+), 2578 deletions(-) create mode 100644 gui/dialogs/DerpModEditDialog.cpp create mode 100644 gui/dialogs/DerpModEditDialog.h create mode 100644 gui/dialogs/DerpModEditDialog.ui delete mode 100644 gui/dialogs/OneSixModEditDialog.cpp delete mode 100644 gui/dialogs/OneSixModEditDialog.h delete mode 100644 gui/dialogs/OneSixModEditDialog.ui create mode 100644 logic/DerpFTBInstance.cpp create mode 100644 logic/DerpFTBInstance.h create mode 100644 logic/DerpInstance.cpp create mode 100644 logic/DerpInstance.h create mode 100644 logic/DerpInstance_p.h create mode 100644 logic/DerpLibrary.cpp create mode 100644 logic/DerpLibrary.h create mode 100644 logic/DerpRule.cpp create mode 100644 logic/DerpRule.h create mode 100644 logic/DerpUpdate.cpp create mode 100644 logic/DerpUpdate.h create mode 100644 logic/DerpVersion.cpp create mode 100644 logic/DerpVersion.h create mode 100644 logic/DerpVersionBuilder.cpp create mode 100644 logic/DerpVersionBuilder.h delete mode 100644 logic/OneSixFTBInstance.cpp delete mode 100644 logic/OneSixFTBInstance.h delete mode 100644 logic/OneSixInstance.cpp delete mode 100644 logic/OneSixInstance.h delete mode 100644 logic/OneSixInstance_p.h delete mode 100644 logic/OneSixLibrary.cpp delete mode 100644 logic/OneSixLibrary.h delete mode 100644 logic/OneSixRule.cpp delete mode 100644 logic/OneSixRule.h delete mode 100644 logic/OneSixUpdate.cpp delete mode 100644 logic/OneSixUpdate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b276cf8..43de626b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,8 +297,8 @@ gui/dialogs/IconPickerDialog.h gui/dialogs/IconPickerDialog.cpp gui/dialogs/LegacyModEditDialog.h gui/dialogs/LegacyModEditDialog.cpp -gui/dialogs/OneSixModEditDialog.h -gui/dialogs/OneSixModEditDialog.cpp +gui/dialogs/DerpModEditDialog.h +gui/dialogs/DerpModEditDialog.cpp gui/dialogs/ModEditDialogCommon.h gui/dialogs/ModEditDialogCommon.cpp gui/dialogs/EditNotesDialog.h @@ -405,32 +405,34 @@ logic/LegacyUpdate.cpp logic/LegacyForge.h logic/LegacyForge.cpp -# 1.6 instances -logic/OneSixInstance.h -logic/OneSixInstance.cpp -logic/OneSixInstance_p.h -logic/OneSixUpdate.h -logic/OneSixUpdate.cpp -logic/OneSixVersion.h -logic/OneSixVersion.cpp -logic/OneSixLibrary.h -logic/OneSixLibrary.cpp -logic/OneSixRule.h -logic/OneSixRule.cpp +# Derp instances +logic/DerpUpdate.h +logic/DerpUpdate.cpp +logic/DerpVersion.h +logic/DerpVersion.cpp +logic/DerpLibrary.h +logic/DerpLibrary.cpp +logic/DerpRule.h +logic/DerpRule.cpp logic/OpSys.h logic/OpSys.cpp logic/ForgeInstaller.h logic/ForgeInstaller.cpp logic/LiteLoaderInstaller.h logic/LiteLoaderInstaller.cpp +logic/DerpInstance.h +logic/DerpInstance.cpp +logic/DerpInstance_p.h +logic/DerpVersionBuilder.h +logic/DerpVersionBuilder.cpp # Nostalgia logic/NostalgiaInstance.h logic/NostalgiaInstance.cpp # FTB -logic/OneSixFTBInstance.h -logic/OneSixFTBInstance.cpp +logic/DerpFTBInstance.h +logic/DerpFTBInstance.cpp logic/LegacyFTBInstance.h logic/LegacyFTBInstance.cpp @@ -506,7 +508,7 @@ gui/dialogs/InstanceSettings.ui gui/dialogs/ProgressDialog.ui gui/dialogs/IconPickerDialog.ui gui/dialogs/LegacyModEditDialog.ui -gui/dialogs/OneSixModEditDialog.ui +gui/dialogs/DerpModEditDialog.ui gui/dialogs/EditNotesDialog.ui gui/dialogs/AccountListDialog.ui gui/dialogs/AccountSelectDialog.ui diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index ee9c3fad..ba394a94 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -84,7 +84,7 @@ #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" -#include "logic/OneSixUpdate.h" +#include "logic/DerpUpdate.h" #include "logic/JavaUtils.h" #include "logic/NagUtils.h" #include "logic/SkinUtils.h" @@ -1257,7 +1257,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() VersionSelectDialog vselect(m_selectedInstance->versionList().get(), tr("Change Minecraft version"), this); - vselect.setFilter(1, "OneSix"); + vselect.setFilter(1, "Derp"); if(!vselect.exec() || !vselect.selectedVersion()) return; diff --git a/gui/dialogs/DerpModEditDialog.cpp b/gui/dialogs/DerpModEditDialog.cpp new file mode 100644 index 00000000..be17404c --- /dev/null +++ b/gui/dialogs/DerpModEditDialog.cpp @@ -0,0 +1,300 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiMC.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "DerpModEditDialog.h" +#include "ModEditDialogCommon.h" +#include "ui_DerpModEditDialog.h" + +#include "gui/Platform.h" +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/VersionSelectDialog.h" + +#include "gui/dialogs/ProgressDialog.h" + +#include "logic/ModList.h" +#include "logic/DerpVersion.h" +#include "logic/EnabledItemFilter.h" +#include "logic/lists/ForgeVersionList.h" +#include "logic/ForgeInstaller.h" +#include "logic/LiteLoaderInstaller.h" + +DerpModEditDialog::DerpModEditDialog(DerpInstance *inst, QWidget *parent) + : QDialog(parent), ui(new Ui::DerpModEditDialog), m_inst(inst) +{ + MultiMCPlatform::fixWM_CLASS(this); + ui->setupUi(this); + // libraries! + + m_version = m_inst->getFullVersion(); + if (m_version) + { + main_model = new EnabledItemFilter(this); + main_model->setActive(true); + main_model->setSourceModel(m_version.get()); + ui->libraryTreeView->setModel(main_model); + ui->libraryTreeView->installEventFilter(this); + ui->mainClassEdit->setText(m_version->mainClass); + updateVersionControls(); + } + else + { + disableVersionControls(); + } + // Loader mods + { + ensureFolderPathExists(m_inst->loaderModsDir()); + m_mods = m_inst->loaderModList(); + ui->loaderModTreeView->setModel(m_mods.get()); + ui->loaderModTreeView->installEventFilter(this); + m_mods->startWatching(); + auto smodel = ui->loaderModTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(loaderCurrent(QModelIndex, QModelIndex))); + } + // resource packs + { + ensureFolderPathExists(m_inst->resourcePacksDir()); + m_resourcepacks = m_inst->resourcePackList(); + ui->resPackTreeView->setModel(m_resourcepacks.get()); + ui->resPackTreeView->installEventFilter(this); + m_resourcepacks->startWatching(); + } + + connect(m_inst, &DerpInstance::versionReloaded, this, &DerpModEditDialog::updateVersionControls); +} + +DerpModEditDialog::~DerpModEditDialog() +{ + m_mods->stopWatching(); + m_resourcepacks->stopWatching(); + delete ui; +} + +void DerpModEditDialog::updateVersionControls() +{ + bool customVersion = m_inst->versionIsCustom(); + ui->forgeBtn->setEnabled(true); + ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply()); + ui->customEditorBtn->setEnabled(customVersion); +} + +void DerpModEditDialog::disableVersionControls() +{ + ui->forgeBtn->setEnabled(false); + ui->liteloaderBtn->setEnabled(false); + ui->customEditorBtn->setEnabled(false); +} + +void DerpModEditDialog::on_customEditorBtn_clicked() +{ + if (QDir(m_inst->instanceRoot()).exists("custom.json")) + { + if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json")) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings")); + } + } +} + +void DerpModEditDialog::on_forgeBtn_clicked() +{ + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setFilter(1, m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ForgeVersionPtr forgeVersion = + std::dynamic_pointer_cast(vselect.selectedVersion()); + if (!forgeVersion) + return; + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + NetJob *fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); + ProgressDialog dlg(this); + dlg.exec(fjob); + if (dlg.result() == QDialog::Accepted) + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.apply(m_version)) + { + // failure notice + } + } + else + { + // failed to download forge :/ + } + } + else + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.apply(m_version)) + { + // failure notice + } + } + } +} + +void DerpModEditDialog::on_liteloaderBtn_clicked() +{ + LiteLoaderInstaller liteloader(m_inst->intendedVersionId()); + if (!liteloader.canApply()) + { + QMessageBox::critical( + this, tr("LiteLoader"), + tr("There is no information available on how to install LiteLoader " + "into this version of Minecraft")); + return; + } + if (!liteloader.apply(m_version)) + { + QMessageBox::critical( + this, tr("LiteLoader"), + tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); + } +} + +bool DerpModEditDialog::loaderListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); +} + +bool DerpModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmResPackBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addResPackBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->resPackTreeView, keyEvent); +} + +bool DerpModEditDialog::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() != QEvent::KeyPress) + { + return QDialog::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast(ev); + if (obj == ui->loaderModTreeView) + return loaderListFilter(keyEvent); + if (obj == ui->resPackTreeView) + return resourcePackListFilter(keyEvent); + return QDialog::eventFilter(obj, ev); +} + +void DerpModEditDialog::on_buttonBox_rejected() +{ + close(); +} + +void DerpModEditDialog::on_addModBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); + for (auto filename : fileNames) + { + m_mods->stopWatching(); + m_mods->installMod(QFileInfo(filename)); + m_mods->startWatching(); + } +} +void DerpModEditDialog::on_rmModBtn_clicked() +{ + int first, last; + auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_mods->stopWatching(); + m_mods->deleteMods(first, last); + m_mods->startWatching(); +} +void DerpModEditDialog::on_viewModBtn_clicked() +{ + openDirInDefaultProgram(m_inst->loaderModsDir(), true); +} + +void DerpModEditDialog::on_addResPackBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); + for (auto filename : fileNames) + { + m_resourcepacks->stopWatching(); + m_resourcepacks->installMod(QFileInfo(filename)); + m_resourcepacks->startWatching(); + } +} +void DerpModEditDialog::on_rmResPackBtn_clicked() +{ + int first, last; + auto list = ui->resPackTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_resourcepacks->stopWatching(); + m_resourcepacks->deleteMods(first, last); + m_resourcepacks->startWatching(); +} +void DerpModEditDialog::on_viewResPackBtn_clicked() +{ + openDirInDefaultProgram(m_inst->resourcePacksDir(), true); +} + +void DerpModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) +{ + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/gui/dialogs/DerpModEditDialog.h b/gui/dialogs/DerpModEditDialog.h new file mode 100644 index 00000000..e9e61c65 --- /dev/null +++ b/gui/dialogs/DerpModEditDialog.h @@ -0,0 +1,67 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#include + +class EnabledItemFilter; +namespace Ui +{ +class DerpModEditDialog; +} + +class DerpModEditDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DerpModEditDialog(DerpInstance *inst, QWidget *parent = 0); + virtual ~DerpModEditDialog(); + +private +slots: + void on_addModBtn_clicked(); + void on_rmModBtn_clicked(); + void on_viewModBtn_clicked(); + + void on_addResPackBtn_clicked(); + void on_rmResPackBtn_clicked(); + void on_viewResPackBtn_clicked(); + // Questionable: SettingsDialog doesn't need this for some reason? + void on_buttonBox_rejected(); + void on_forgeBtn_clicked(); + void on_liteloaderBtn_clicked(); + void on_customEditorBtn_clicked(); + void updateVersionControls(); + void disableVersionControls(); + +protected: + bool eventFilter(QObject *obj, QEvent *ev); + bool loaderListFilter(QKeyEvent *ev); + bool resourcePackListFilter(QKeyEvent *ev); + +private: + Ui::DerpModEditDialog *ui; + std::shared_ptr m_version; + std::shared_ptr m_mods; + std::shared_ptr m_resourcepacks; + EnabledItemFilter *main_model; + DerpInstance *m_inst; +public +slots: + void loaderCurrent(QModelIndex current, QModelIndex previous); +}; diff --git a/gui/dialogs/DerpModEditDialog.ui b/gui/dialogs/DerpModEditDialog.ui new file mode 100644 index 00000000..7aaf1564 --- /dev/null +++ b/gui/dialogs/DerpModEditDialog.ui @@ -0,0 +1,317 @@ + + + DerpModEditDialog + + + + 0 + 0 + 555 + 463 + + + + Manage Mods + + + + + + true + + + + 0 + 0 + + + + 1 + + + + Version + + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + + + + + + + Main Class: + + + + + + + false + + + + + + + + + + + + + Replace any current custom version with Minecraft Forge + + + Install Forge + + + + + + + Install LiteLoader + + + + + + + QFrame::Sunken + + + Qt::Horizontal + + + + + + + false + + + Add new libraries + + + &Add + + + + + + + false + + + Remove selected libraries + + + &Remove + + + + + + + Qt::Horizontal + + + + + + + Open custom.json + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Loader Mods + + + + + + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DropOnly + + + + + + + + + + + &Add + + + + + + + &Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + &View Folder + + + + + + + + + + + + 0 + 0 + + + + + + + + + Resource Packs + + + + + + true + + + QAbstractItemView::DropOnly + + + + + + + + + &Add + + + + + + + &Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + &View Folder + + + + + + + + + + + + + false + + + QDialogButtonBox::Close + + + + + + + + ModListView + QTreeView +
gui/widgets/ModListView.h
+
+ + MCModInfoFrame + QFrame +
gui/widgets/MCModInfoFrame.h
+ 1 +
+
+ + +
diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp deleted file mode 100644 index 3982f17d..00000000 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MultiMC.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "OneSixModEditDialog.h" -#include "ModEditDialogCommon.h" -#include "ui_OneSixModEditDialog.h" - -#include "gui/Platform.h" -#include "gui/dialogs/CustomMessageBox.h" -#include "gui/dialogs/VersionSelectDialog.h" - -#include "gui/dialogs/ProgressDialog.h" - -#include "logic/ModList.h" -#include "logic/OneSixVersion.h" -#include "logic/EnabledItemFilter.h" -#include "logic/lists/ForgeVersionList.h" -#include "logic/ForgeInstaller.h" -#include "logic/LiteLoaderInstaller.h" - -OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) - : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - // libraries! - - m_version = m_inst->getFullVersion(); - if (m_version) - { - main_model = new EnabledItemFilter(this); - main_model->setActive(true); - main_model->setSourceModel(m_version.get()); - ui->libraryTreeView->setModel(main_model); - ui->libraryTreeView->installEventFilter(this); - ui->mainClassEdit->setText(m_version->mainClass); - updateVersionControls(); - } - else - { - disableVersionControls(); - } - // Loader mods - { - ensureFolderPathExists(m_inst->loaderModsDir()); - m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.get()); - ui->loaderModTreeView->installEventFilter(this); - m_mods->startWatching(); - auto smodel = ui->loaderModTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(loaderCurrent(QModelIndex, QModelIndex))); - } - // resource packs - { - ensureFolderPathExists(m_inst->resourcePacksDir()); - m_resourcepacks = m_inst->resourcePackList(); - ui->resPackTreeView->setModel(m_resourcepacks.get()); - ui->resPackTreeView->installEventFilter(this); - m_resourcepacks->startWatching(); - } -} - -OneSixModEditDialog::~OneSixModEditDialog() -{ - m_mods->stopWatching(); - m_resourcepacks->stopWatching(); - delete ui; -} - -void OneSixModEditDialog::updateVersionControls() -{ - bool customVersion = m_inst->versionIsCustom(); - ui->customizeBtn->setEnabled(!customVersion); - ui->revertBtn->setEnabled(customVersion); - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply()); - ui->customEditorBtn->setEnabled(customVersion); -} - -void OneSixModEditDialog::disableVersionControls() -{ - ui->customizeBtn->setEnabled(false); - ui->revertBtn->setEnabled(false); - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->customEditorBtn->setEnabled(false); -} - -void OneSixModEditDialog::on_customizeBtn_clicked() -{ - if (m_inst->customizeVersion()) - { - m_version = m_inst->getFullVersion(); - main_model->setSourceModel(m_version.get()); - updateVersionControls(); - } -} - -void OneSixModEditDialog::on_revertBtn_clicked() -{ - auto response = CustomMessageBox::selectable( - this, tr("Revert?"), tr("Do you want to revert the " - "version of this instance to its original configuration?"), - QMessageBox::Question, QMessageBox::Yes | QMessageBox::No)->exec(); - if (response == QMessageBox::Yes) - { - if (m_inst->revertCustomVersion()) - { - m_version = m_inst->getFullVersion(); - main_model->setSourceModel(m_version.get()); - updateVersionControls(); - } - } -} - -void OneSixModEditDialog::on_customEditorBtn_clicked() -{ - if (m_inst->versionIsCustom()) - { - if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json")) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings")); - } - } -} - -void OneSixModEditDialog::on_forgeBtn_clicked() -{ - VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); - vselect.setFilter(1, m_inst->currentVersionId()); - if (vselect.exec() && vselect.selectedVersion()) - { - if (m_inst->versionIsCustom()) - { - auto reply = QMessageBox::question( - this, tr("Revert?"), - tr("This will revert any " - "changes you did to the version up to this point. Is that " - "OK?"), - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) - { - m_inst->revertCustomVersion(); - m_inst->customizeVersion(); - { - m_version = m_inst->getFullVersion(); - main_model->setSourceModel(m_version.get()); - updateVersionControls(); - } - } - else - return; - } - else - { - m_inst->customizeVersion(); - m_version = m_inst->getFullVersion(); - main_model->setSourceModel(m_version.get()); - updateVersionControls(); - } - ForgeVersionPtr forgeVersion = - std::dynamic_pointer_cast(vselect.selectedVersion()); - if (!forgeVersion) - return; - auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) - { - NetJob *fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - ProgressDialog dlg(this); - dlg.exec(fjob); - if (dlg.result() == QDialog::Accepted) - { - // install - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.apply(m_version)) - { - // failure notice - } - } - else - { - // failed to download forge :/ - } - } - else - { - // install - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.apply(m_version)) - { - // failure notice - } - } - } -} - -void OneSixModEditDialog::on_liteloaderBtn_clicked() -{ - LiteLoaderInstaller liteloader(m_inst->intendedVersionId()); - if (!liteloader.canApply()) - { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("There is no information available on how to install LiteLoader " - "into this version of Minecraft")); - return; - } - if (!m_inst->versionIsCustom()) - { - m_inst->customizeVersion(); - m_version = m_inst->getFullVersion(); - main_model->setSourceModel(m_version.get()); - updateVersionControls(); - } - if (!liteloader.apply(m_version)) - { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); - } -} - -bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); -} - -bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmResPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addResPackBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->resPackTreeView, keyEvent); -} - -bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast(ev); - if (obj == ui->loaderModTreeView) - return loaderListFilter(keyEvent); - if (obj == ui->resPackTreeView) - return resourcePackListFilter(keyEvent); - return QDialog::eventFilter(obj, ev); -} - -void OneSixModEditDialog::on_buttonBox_rejected() -{ - close(); -} - -void OneSixModEditDialog::on_addModBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); - for (auto filename : fileNames) - { - m_mods->stopWatching(); - m_mods->installMod(QFileInfo(filename)); - m_mods->startWatching(); - } -} -void OneSixModEditDialog::on_rmModBtn_clicked() -{ - int first, last; - auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_mods->stopWatching(); - m_mods->deleteMods(first, last); - m_mods->startWatching(); -} -void OneSixModEditDialog::on_viewModBtn_clicked() -{ - openDirInDefaultProgram(m_inst->loaderModsDir(), true); -} - -void OneSixModEditDialog::on_addResPackBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); - for (auto filename : fileNames) - { - m_resourcepacks->stopWatching(); - m_resourcepacks->installMod(QFileInfo(filename)); - m_resourcepacks->startWatching(); - } -} -void OneSixModEditDialog::on_rmResPackBtn_clicked() -{ - int first, last; - auto list = ui->resPackTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_resourcepacks->stopWatching(); - m_resourcepacks->deleteMods(first, last); - m_resourcepacks->startWatching(); -} -void OneSixModEditDialog::on_viewResPackBtn_clicked() -{ - openDirInDefaultProgram(m_inst->resourcePacksDir(), true); -} - -void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - int row = current.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); -} diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h deleted file mode 100644 index 2510c59c..00000000 --- a/gui/dialogs/OneSixModEditDialog.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include - -#include - -class EnabledItemFilter; -namespace Ui -{ -class OneSixModEditDialog; -} - -class OneSixModEditDialog : public QDialog -{ - Q_OBJECT - -public: - explicit OneSixModEditDialog(OneSixInstance *inst, QWidget *parent = 0); - virtual ~OneSixModEditDialog(); - -private -slots: - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - - void on_addResPackBtn_clicked(); - void on_rmResPackBtn_clicked(); - void on_viewResPackBtn_clicked(); - // Questionable: SettingsDialog doesn't need this for some reason? - void on_buttonBox_rejected(); - void on_forgeBtn_clicked(); - void on_liteloaderBtn_clicked(); - void on_customizeBtn_clicked(); - void on_revertBtn_clicked(); - void on_customEditorBtn_clicked(); - void updateVersionControls(); - void disableVersionControls(); - -protected: - bool eventFilter(QObject *obj, QEvent *ev); - bool loaderListFilter(QKeyEvent *ev); - bool resourcePackListFilter(QKeyEvent *ev); - -private: - Ui::OneSixModEditDialog *ui; - std::shared_ptr m_version; - std::shared_ptr m_mods; - std::shared_ptr m_resourcepacks; - EnabledItemFilter *main_model; - OneSixInstance *m_inst; -public -slots: - void loaderCurrent(QModelIndex current, QModelIndex previous); -}; diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui deleted file mode 100644 index 899e0cbf..00000000 --- a/gui/dialogs/OneSixModEditDialog.ui +++ /dev/null @@ -1,340 +0,0 @@ - - - OneSixModEditDialog - - - - 0 - 0 - 555 - 463 - - - - Manage Mods - - - - - - true - - - - 0 - 0 - - - - 1 - - - - Version - - - - - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - - - - - - - Main Class: - - - - - - - false - - - - - - - - - - - - - Replace any current custom version with Minecraft Forge - - - Install Forge - - - - - - - Install LiteLoader - - - - - - - Create an customized copy of the base version - - - Customize - - - - - - - false - - - Revert to original base version - - - Revert - - - - - - - QFrame::Sunken - - - Qt::Horizontal - - - - - - - false - - - Add new libraries - - - &Add - - - - - - - false - - - Remove selected libraries - - - &Remove - - - - - - - Qt::Horizontal - - - - - - - Open custom.json - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Loader Mods - - - - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DropOnly - - - - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - - - 0 - 0 - - - - - - - - - Resource Packs - - - - - - true - - - QAbstractItemView::DropOnly - - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - - - - false - - - QDialogButtonBox::Close - - - - - - - - ModListView - QTreeView -
gui/widgets/ModListView.h
-
- - MCModInfoFrame - QFrame -
gui/widgets/MCModInfoFrame.h
- 1 -
-
- - -
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index a861e9b2..79640c84 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -27,7 +27,7 @@ class QDialog; class Task; class MinecraftProcess; -class OneSixUpdate; +class DerpUpdate; class InstanceList; class BaseInstancePrivate; diff --git a/logic/DerpFTBInstance.cpp b/logic/DerpFTBInstance.cpp new file mode 100644 index 00000000..f9aeeca0 --- /dev/null +++ b/logic/DerpFTBInstance.cpp @@ -0,0 +1,123 @@ +#include "DerpFTBInstance.h" + +#include "DerpVersion.h" +#include "DerpLibrary.h" +#include "tasks/SequentialTask.h" +#include "ForgeInstaller.h" +#include "lists/ForgeVersionList.h" +#include "MultiMC.h" + +class DerpFTBInstanceForge : public Task +{ + Q_OBJECT +public: + explicit DerpFTBInstanceForge(const QString &version, DerpFTBInstance *inst, QObject *parent = 0) : + Task(parent), instance(inst), version("Forge " + version) + { + } + + void executeTask() + { + for (int i = 0; i < MMC->forgelist()->count(); ++i) + { + if (MMC->forgelist()->at(i)->name() == version) + { + forgeVersion = std::dynamic_pointer_cast(MMC->forgelist()->at(i)); + break; + } + } + if (!forgeVersion) + { + emitFailed(QString("Couldn't find forge version ") + version ); + return; + } + entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + setStatus(tr("Downloading Forge...")); + fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); + connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);}); + connect(fjob, &NetJob::succeeded, this, &DerpFTBInstanceForge::installForge); + connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); }); + fjob->start(); + } + else + { + installForge(); + } + } + +private +slots: + void installForge() + { + setStatus(tr("Installing Forge...")); + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!instance->reloadFullVersion()) + { + emitFailed(tr("Couldn't load the version config")); + return; + } + auto version = instance->getFullVersion(); + if (!forge.apply(version)) + { + emitFailed(tr("Couldn't install Forge")); + return; + } + emitSucceeded(); + } + +private: + DerpFTBInstance *instance; + QString version; + ForgeVersionPtr forgeVersion; + MetaEntryPtr entry; + NetJob *fjob; +}; + +DerpFTBInstance::DerpFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : + DerpInstance(rootDir, settings, parent) +{ + QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); + if (f.open(QFile::ReadOnly)) + { + QString data = QString::fromUtf8(f.readAll()); + QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); + m_forge.reset(new DerpLibrary(match.captured())); + m_forge->finalize(); + } +} + +QString DerpFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} + +QString DerpFTBInstance::getStatusbarDescription() +{ + return "Derp FTB: " + intendedVersionId(); +} +bool DerpFTBInstance::menuActionEnabled(QString action_name) const +{ + return false; +} + +std::shared_ptr DerpFTBInstance::doUpdate(bool only_prepare) +{ + std::shared_ptr task; + task.reset(new SequentialTask(this)); + if (!MMC->forgelist()->isLoaded()) + { + task->addTask(std::shared_ptr(MMC->forgelist()->getLoadTask())); + } + task->addTask(DerpInstance::doUpdate(only_prepare)); + task->addTask(std::shared_ptr(new DerpFTBInstanceForge(m_forge->version(), this, this))); + //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. + //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. + task->addTask(DerpInstance::doUpdate(only_prepare)); + return task; +} + +#include "DerpFTBInstance.moc" diff --git a/logic/DerpFTBInstance.h b/logic/DerpFTBInstance.h new file mode 100644 index 00000000..c16998bf --- /dev/null +++ b/logic/DerpFTBInstance.h @@ -0,0 +1,22 @@ +#pragma once + +#include "DerpInstance.h" + +class DerpLibrary; + +class DerpFTBInstance : public DerpInstance +{ + Q_OBJECT +public: + explicit DerpFTBInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + virtual QString getStatusbarDescription(); + virtual bool menuActionEnabled(QString action_name) const; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + + virtual QString id() const; + +private: + std::shared_ptr m_forge; +}; diff --git a/logic/DerpInstance.cpp b/logic/DerpInstance.cpp new file mode 100644 index 00000000..31ed7c95 --- /dev/null +++ b/logic/DerpInstance.cpp @@ -0,0 +1,373 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpInstance.h" + +#include + +#include "DerpInstance_p.h" +#include "DerpUpdate.h" +#include "DerpVersion.h" +#include "pathutils.h" +#include "logger/QsLog.h" +#include "assets/AssetsUtils.h" +#include "MultiMC.h" +#include "icons/IconList.h" +#include "MinecraftProcess.h" +#include "gui/dialogs/DerpModEditDialog.h" + +DerpInstance::DerpInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) + : BaseInstance(new DerpInstancePrivate(), rootDir, settings, parent) +{ + I_D(DerpInstance); + d->m_settings->registerSetting("IntendedVersion", ""); + d->m_settings->registerSetting("ShouldUpdate", false); + d->version.reset(new DerpVersion(this, this)); + reloadFullVersion(); +} + + +std::shared_ptr DerpInstance::doUpdate(bool only_prepare) +{ + return std::shared_ptr(new DerpUpdate(this, only_prepare)); +} + +QString replaceTokensIn(QString text, QMap with) +{ + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) + { + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) + { + result.append(*iter); + } + head += token_regexp.matchedLength(); + tail = head; + } + result.append(text.mid(tail)); + return result; +} + +QDir DerpInstance::reconstructAssets(std::shared_ptr version) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); + + if (!indexFile.exists()) + { + QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; + return virtualRoot; + } + + QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); + + if (loadAssetsIndex && index.isVirtual) + { + QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = PathCombine(virtualRoot.path(), map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = + PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); + QFile original(original_path); + if(!original.exists()) + continue; + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // QLOG_DEBUG() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + QLOG_DEBUG() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + } + + return virtualRoot; +} + +QStringList DerpInstance::processMinecraftArgs(MojangAccountPtr account) +{ + I_D(DerpInstance); + auto version = d->version; + QString args_pattern = version->minecraftArguments; + + QMap token_mapping; + // yggdrasil! + token_mapping["auth_username"] = account->username(); + token_mapping["auth_session"] = account->sessionId(); + token_mapping["auth_access_token"] = account->accessToken(); + token_mapping["auth_player_name"] = account->currentProfile()->name; + token_mapping["auth_uuid"] = account->currentProfile()->id; + + // this is for offline?: + /* + map["auth_player_name"] = "Player"; + map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; + */ + + // these do nothing and are stupid. + token_mapping["profile_name"] = name(); + token_mapping["version_name"] = version->id; + + QString absRootDir = QDir(minecraftRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); + + auto user = account->user(); + QJsonObject userAttrs; + for (auto key : user.properties.keys()) + { + auto array = QJsonArray::fromStringList(user.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + + token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); + token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = version->assets; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) + { + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; +} + +MinecraftProcess *DerpInstance::prepareForLaunch(MojangAccountPtr account) +{ + I_D(DerpInstance); + + QIcon icon = MMC->icons()->getIcon(iconKey()); + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); + + auto version = d->version; + if (!version) + return nullptr; + QString launchScript; + { + auto libs = version->getActiveNormalLibs(); + for (auto lib : libs) + { + QFileInfo fi(QString("libraries/") + lib->storagePath()); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; + QFileInfo fi(targetstr); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + launchScript += "mainClass " + version->mainClass + "\n"; + + for (auto param : processMinecraftArgs(account)) + { + launchScript += "param " + param + "\n"; + } + + // Set the width and height for 1.6 instances + bool maximize = settings().get("LaunchMaximized").toBool(); + if (maximize) + { + // this is probably a BAD idea + // launchScript += "param --fullscreen\n"; + } + else + { + launchScript += + "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; + launchScript += + "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; + } + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "natives " + natives_dir.absolutePath() + "\n"; + launchScript += "launch onesix"; + + qDebug() << launchScript; + + // create the process and set its parameters + MinecraftProcess *proc = new MinecraftProcess(this); + proc->setWorkdir(minecraftRoot()); + proc->setLaunchScript(launchScript); + // proc->setNativeFolder(natives_dir.absolutePath()); + return proc; +} + +void DerpInstance::cleanupAfterRun() +{ + QString target_dir = PathCombine(instanceRoot(), "natives/"); + QDir dir(target_dir); + dir.removeRecursively(); +} + +std::shared_ptr DerpInstance::loaderModList() +{ + I_D(DerpInstance); + if (!d->loader_mod_list) + { + d->loader_mod_list.reset(new ModList(loaderModsDir())); + } + d->loader_mod_list->update(); + return d->loader_mod_list; +} + +std::shared_ptr DerpInstance::resourcePackList() +{ + I_D(DerpInstance); + if (!d->resource_pack_list) + { + d->resource_pack_list.reset(new ModList(resourcePacksDir())); + } + d->resource_pack_list->update(); + return d->resource_pack_list; +} + +QDialog *DerpInstance::createModEditDialog(QWidget *parent) +{ + return new DerpModEditDialog(this, parent); +} + +bool DerpInstance::setIntendedVersionId(QString version) +{ + settings().set("IntendedVersion", version); + setShouldUpdate(true); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::remove(pathOrig); + reloadFullVersion(); + return true; +} + +QString DerpInstance::intendedVersionId() const +{ + return settings().get("IntendedVersion").toString(); +} + +void DerpInstance::setShouldUpdate(bool val) +{ + settings().set("ShouldUpdate", val); +} + +bool DerpInstance::shouldUpdate() const +{ + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) + { + return intendedVersionId() != currentVersionId(); + } + return true; +} + +bool DerpInstance::versionIsCustom() +{ + QDir patches(PathCombine(instanceRoot(), "patches/")); + return QFile::exists(PathCombine(instanceRoot(), "custom.json")) + || (patches.exists() && patches.count() >= 0); +} + +QString DerpInstance::currentVersionId() const +{ + return intendedVersionId(); +} + +bool DerpInstance::reloadFullVersion(QWidget *widgetParent) +{ + I_D(DerpInstance); + + bool ret = d->version->reload(widgetParent); + emit versionReloaded(); + return ret; +} + +std::shared_ptr DerpInstance::getFullVersion() +{ + I_D(DerpInstance); + return d->version; +} + +QString DerpInstance::defaultBaseJar() const +{ + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; +} + +QString DerpInstance::defaultCustomBaseJar() const +{ + return PathCombine(instanceRoot(), "custom.jar"); +} + +bool DerpInstance::menuActionEnabled(QString action_name) const +{ + if (action_name == "actionChangeInstLWJGLVersion") + return false; + return true; +} + +QString DerpInstance::getStatusbarDescription() +{ + QString descr = "Derp : " + intendedVersionId(); + if (versionIsCustom()) + { + descr + " (custom)"; + } + return descr; +} + +QString DerpInstance::loaderModsDir() const +{ + return PathCombine(minecraftRoot(), "mods"); +} + +QString DerpInstance::resourcePacksDir() const +{ + return PathCombine(minecraftRoot(), "resourcepacks"); +} + +QString DerpInstance::instanceConfigFolder() const +{ + return PathCombine(minecraftRoot(), "config"); +} diff --git a/logic/DerpInstance.h b/logic/DerpInstance.h new file mode 100644 index 00000000..37d3df52 --- /dev/null +++ b/logic/DerpInstance.h @@ -0,0 +1,73 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance.h" + +#include "DerpVersion.h" +#include "ModList.h" + +class DerpInstance : public BaseInstance +{ + Q_OBJECT +public: + explicit DerpInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + + ////// Mod Lists ////// + std::shared_ptr loaderModList(); + std::shared_ptr resourcePackList(); + + ////// Directories ////// + QString resourcePacksDir() const; + QString loaderModsDir() const; + virtual QString instanceConfigFolder() const override; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; + + virtual void cleanupAfterRun() override; + + virtual QString intendedVersionId() const override; + virtual bool setIntendedVersionId(QString version) override; + + virtual QString currentVersionId() const override; + + virtual bool shouldUpdate() const override; + virtual void setShouldUpdate(bool val) override; + + virtual QDialog *createModEditDialog(QWidget *parent) override; + + /// reload the full version json files. return true on success! + bool reloadFullVersion(QWidget *widgetParent = 0); + /// get the current full version info + std::shared_ptr getFullVersion(); + /// is the current version original, or custom? + virtual bool versionIsCustom() override; + + virtual QString defaultBaseJar() const override; + virtual QString defaultCustomBaseJar() const override; + + virtual bool menuActionEnabled(QString action_name) const override; + virtual QString getStatusbarDescription() override; + +signals: + void versionReloaded(); + +private: + QStringList processMinecraftArgs(MojangAccountPtr account); + QDir reconstructAssets(std::shared_ptr version); +}; diff --git a/logic/DerpInstance_p.h b/logic/DerpInstance_p.h new file mode 100644 index 00000000..41f7b62d --- /dev/null +++ b/logic/DerpInstance_p.h @@ -0,0 +1,27 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance_p.h" +#include "DerpVersion.h" +#include "ModList.h" + +struct DerpInstancePrivate : public BaseInstancePrivate +{ + std::shared_ptr version; + std::shared_ptr loader_mod_list; + std::shared_ptr resource_pack_list; +}; diff --git a/logic/DerpLibrary.cpp b/logic/DerpLibrary.cpp new file mode 100644 index 00000000..ba4d516b --- /dev/null +++ b/logic/DerpLibrary.cpp @@ -0,0 +1,268 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "DerpLibrary.h" +#include "DerpRule.h" +#include "OpSys.h" +#include "logic/net/URLConstants.h" +#include +#include +#include "logger/QsLog.h" + +void DerpLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void DerpLibrary::setName(const QString &name) +{ + m_name = name; +} +void DerpLibrary::setBaseUrl(const QString &base_url) +{ + m_base_url = base_url; +} +void DerpLibrary::setIsNative() +{ + m_is_native = true; +} +void DerpLibrary::addNative(OpSys os, const QString &suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void DerpLibrary::setRules(QList> rules) +{ + m_rules = rules; +} +bool DerpLibrary::isActive() const +{ + return m_is_active; +} +bool DerpLibrary::isNative() const +{ + return m_is_native; +} +QString DerpLibrary::downloadUrl() const +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString DerpLibrary::storagePath() const +{ + return m_storage_path; +} + +void DerpLibrary::setAbsoluteUrl(const QString &absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString DerpLibrary::absoluteUrl() const +{ + return m_absolute_url; +} + +void DerpLibrary::setHint(const QString &hint) +{ + m_hint = hint; +} + +QString DerpLibrary::hint() const +{ + return m_hint; +} + +bool DerpLibrary::filesExist() +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QFileInfo info32(PathCombine("libraries", cooked_storage)); + if (!info32.exists()) + { + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + QFileInfo info64(PathCombine("libraries", cooked_storage)); + if (!info64.exists()) + { + return false; + } + } + else + { + QFileInfo info(PathCombine("libraries", storage)); + if (!info.exists()) + { + return false; + } + } + return true; +} + +bool DerpLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "64"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if(!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + +QJsonObject DerpLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE) + libRoot.insert("url", m_base_url); + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/DerpLibrary.h b/logic/DerpLibrary.h new file mode 100644 index 00000000..d1cee843 --- /dev/null +++ b/logic/DerpLibrary.h @@ -0,0 +1,132 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "logic/net/URLConstants.h" +#include "OpSys.h" + +class Rule; + +class DerpLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + QList> m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// download hint - how to actually get the library + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap m_native_suffixes; + +public: + QStringList extract_excludes; + +public: + /// Constructor + DerpLibrary(const QString &name) + { + m_name = name; + } + + /// Returns the raw name field + QString rawName() const + { + return m_name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(const QString &name); + /// get a decent-looking name + QString name() const + { + return m_decentname; + } + /// get a decent-looking version + QString version() const + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() const + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(const QString &base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, const QString &suffix); + /// Set the load rules + void setRules(QList> rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + /// Returns true if the library is native + bool isNative() const; + /// Get the URL to download the library from + QString downloadUrl() const; + /// Get the relative path where the library should be saved + QString storagePath() const; + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(const QString &hint); + QString hint() const; + + bool extractTo(QString target_dir); + bool filesExist(); +}; diff --git a/logic/DerpRule.cpp b/logic/DerpRule.cpp new file mode 100644 index 00000000..d4cf1ba3 --- /dev/null +++ b/logic/DerpRule.cpp @@ -0,0 +1,89 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "DerpRule.h" + +QList> rulesFromJsonV4(QJsonObject &objectWithRules) +{ + QList> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } + return rules; +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} diff --git a/logic/DerpRule.h b/logic/DerpRule.h new file mode 100644 index 00000000..7895ea98 --- /dev/null +++ b/logic/DerpRule.h @@ -0,0 +1,98 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "logic/DerpLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList> rulesFromJsonV4(QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(DerpLibrary *parent) = 0; + +public: + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(DerpLibrary *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } + ; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; + +protected: + virtual bool applies(DerpLibrary *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr(new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies(DerpLibrary *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } +}; diff --git a/logic/DerpUpdate.cpp b/logic/DerpUpdate.cpp new file mode 100644 index 00000000..e1600d28 --- /dev/null +++ b/logic/DerpUpdate.cpp @@ -0,0 +1,377 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiMC.h" +#include "DerpUpdate.h" + +#include + +#include +#include +#include +#include + +#include "BaseInstance.h" +#include "lists/MinecraftVersionList.h" +#include "DerpVersion.h" +#include "DerpLibrary.h" +#include "DerpInstance.h" +#include "net/ForgeMirrors.h" +#include "net/URLConstants.h" +#include "assets/AssetsUtils.h" + +#include "pathutils.h" +#include + +DerpUpdate::DerpUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) + : Task(parent), m_inst(inst), m_only_prepare(only_prepare) +{ +} + +void DerpUpdate::executeTask() +{ + QString intendedVersion = m_inst->intendedVersionId(); + + // Make directories + QDir mcDir(m_inst->minecraftRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed("Failed to create bin folder."); + return; + } + + if (m_only_prepare) + { + prepareForLaunch(); + return; + } + + if (m_inst->shouldUpdate()) + { + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast( + MMC->minecraftlist()->findVersion(intendedVersion)); + if (targetVersion == nullptr) + { + // don't do anything if it was invalid + emitFailed("The specified Minecraft version is invalid. Choose a different one."); + return; + } + versionFileStart(); + } + else + { + jarlibStart(); + } +} + +void DerpUpdate::versionFileStart() +{ + QLOG_INFO() << m_inst->name() << ": getting version file."; + setStatus(tr("Getting the version files from Mojang...")); + + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void DerpUpdate::versionFileFinished() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + DerpInstance *inst = (DerpInstance *)m_inst; + + QString version_id = targetVersion->descriptor(); + QString inst_dir = m_inst->instanceRoot(); + // save the version file in $instanceId/version.json + { + QString version1 = PathCombine(inst_dir, "/version.json"); + ensureFilePathExists(version1); + // FIXME: detect errors here, download to a temp file, swap + QSaveFile vfile1(version1); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed("Can't open " + version1 + " for writing."); + return; + } + auto data = std::dynamic_pointer_cast(DlJob)->m_data; + qint64 actual = 0; + if ((actual = vfile1.write(data)) != data.size()) + { + emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + + data.size() + '.'); + return; + } + if (!vfile1.commit()) + { + emitFailed("Can't commit changes to " + version1); + return; + } + } + + // the version is downloaded safely. update is 'done' at this point + m_inst->setShouldUpdate(false); + + // delete any custom version inside the instance (it's no longer relevant, we did an update) + QString custom = PathCombine(inst_dir, "/custom.json"); + QFile finfo(custom); + if (finfo.exists()) + { + finfo.remove(); + } + inst->reloadFullVersion(); + + jarlibStart(); +} + +void DerpUpdate::versionFileFailed() +{ + emitFailed("Failed to download the version description. Try again."); +} + +void DerpUpdate::assetIndexStart() +{ + setStatus(tr("Updating assets index...")); + DerpInstance *inst = (DerpInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; + QString localPath = assetName + ".json"; + auto job = new NetJob("Asset index for " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + job->addNetAction(CacheDownload::make(indexUrl, entry)); + jarlibDownloadJob.reset(job); + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void DerpUpdate::assetIndexFinished() +{ + AssetsIndex index; + + DerpInstance *inst = (DerpInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + + QString asset_fname = "assets/indexes/" + assetName + ".json"; + if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index)) + { + emitFailed("Failed to read the assets index!"); + } + + QList dls; + for (auto object : index.objects.values()) + { + QString objectName = object.hash.left(2) + "/" + object.hash; + QFileInfo objectFile("assets/objects/" + objectName); + if ((!objectFile.isFile()) || (objectFile.size() != object.size)) + { + auto objectDL = MD5EtagDownload::make( + QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), + objectFile.filePath()); + objectDL->m_total_progress = object.size; + dls.append(objectDL); + } + } + if (dls.size()) + { + setStatus(tr("Getting the assets files from Mojang...")); + auto job = new NetJob("Assets for " + inst->name()); + for (auto dl : dls) + job->addNetAction(dl); + jarlibDownloadJob.reset(job); + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + jarlibDownloadJob->start(); + return; + } + assetsFinished(); +} + +void DerpUpdate::assetIndexFailed() +{ + emitFailed("Failed to download the assets index!"); +} + +void DerpUpdate::assetsFinished() +{ + prepareForLaunch(); +} + +void DerpUpdate::assetsFailed() +{ + emitFailed("Failed to download assets!"); +} + +void DerpUpdate::jarlibStart() +{ + setStatus(tr("Getting the library files from Mojang...")); + QLOG_INFO() << m_inst->name() << ": downloading libraries"; + DerpInstance *inst = (DerpInstance *)m_inst; + bool successful = inst->reloadFullVersion(); + if (!successful) + { + emitFailed("Failed to load the version description file. It might be " + "corrupted, missing or simply too new."); + return; + } + + // Build a list of URLs that will need to be downloaded. + std::shared_ptr version = inst->getFullVersion(); + // minecraft.jar for this version + { + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath; + + auto job = new NetJob("Libraries for instance " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); + + jarlibDownloadJob.reset(job); + } + + auto libs = version->getActiveNativeLibs(); + libs.append(version->getActiveNormalLibs()); + + auto metacache = MMC->metacache(); + QList ForgeLibs; + for (auto lib : libs) + { + if (lib->hint() == "local") + continue; + + QString raw_storage = lib->storagePath(); + QString raw_dl = lib->downloadUrl(); + + auto f = [&](QString storage, QString dl) + { + auto entry = metacache->resolveEntry("libraries", storage); + if (entry->stale) + { + if (lib->hint() == "forge-pack-xz") + { + ForgeLibs.append(ForgeXzDownload::make(storage, entry)); + } + else + { + jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); + } + } + }; + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); + } + else + { + f(raw_storage, raw_dl); + } + } + // TODO: think about how to propagate this from the original json file... or IF AT ALL + QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list"; + if (!ForgeLibs.empty()) + { + jarlibDownloadJob->addNetAction( + ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList)); + } + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void DerpUpdate::jarlibFinished() +{ + assetIndexStart(); +} + +void DerpUpdate::jarlibFailed() +{ + QStringList failed = jarlibDownloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed("Failed to download the following files:\n" + failed_all + + "\n\nPlease try again."); +} + +void DerpUpdate::prepareForLaunch() +{ + setStatus(tr("Preparing for launch...")); + QLOG_INFO() << m_inst->name() << ": preparing for launch"; + auto derp_inst = (DerpInstance *)m_inst; + + // delete any leftovers, if they are present. + derp_inst->cleanupAfterRun(); + + QString natives_dir_raw = PathCombine(derp_inst->instanceRoot(), "natives/"); + auto version = derp_inst->getFullVersion(); + if (!version) + { + emitFailed("The version information for this instance is not complete. Try re-creating " + "it or changing the version."); + return; + } + /* + * emitFailed("Could not create the native library folder:\n" + natives_dir_raw + + "\nMake sure MultiMC has appropriate permissions and there is enough + space " + "on the storage device."); + */ + for (auto lib : version->getActiveNativeLibs()) + { + if (!lib->filesExist()) + { + emitFailed("Native library is missing some files:\n" + lib->storagePath() + + "\n\nRun the instance at least once in online mode to get all the " + "required files."); + return; + } + if (!lib->extractTo(natives_dir_raw)) + { + emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + + natives_dir_raw + + "\n\nMake sure MultiMC has appropriate permissions and there is enough " + "space on the storage device."); + return; + } + } + + emitSucceeded(); +} diff --git a/logic/DerpUpdate.h b/logic/DerpUpdate.h new file mode 100644 index 00000000..475f6c35 --- /dev/null +++ b/logic/DerpUpdate.h @@ -0,0 +1,63 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "logic/net/NetJob.h" +#include "logic/tasks/Task.h" + +class MinecraftVersion; +class BaseInstance; + +class DerpUpdate : public Task +{ + Q_OBJECT +public: + explicit DerpUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); + virtual void executeTask(); + +private +slots: + void versionFileStart(); + void versionFileFinished(); + void versionFileFailed(); + + void jarlibStart(); + void jarlibFinished(); + void jarlibFailed(); + + void assetIndexStart(); + void assetIndexFinished(); + void assetIndexFailed(); + + void assetsFinished(); + void assetsFailed(); + + // extract the appropriate libraries + void prepareForLaunch(); + +private: + NetJobPtr specificVersionDownloadJob; + NetJobPtr jarlibDownloadJob; + + // target version, determined during this task + std::shared_ptr targetVersion; + BaseInstance *m_inst = nullptr; + bool m_only_prepare = false; +}; diff --git a/logic/DerpVersion.cpp b/logic/DerpVersion.cpp new file mode 100644 index 00000000..f13ac620 --- /dev/null +++ b/logic/DerpVersion.cpp @@ -0,0 +1,164 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpVersion.h" + +#include + +#include "DerpVersionBuilder.h" + +DerpVersion::DerpVersion(DerpInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ +} + +bool DerpVersion::reload(QWidget *widgetParent) +{ + return DerpVersionBuilder::build(this, m_instance, widgetParent); +} + +QList > DerpVersion::getActiveNormalLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +QList > DerpVersion::getActiveNativeLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +QVariant DerpVersion::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= libraries.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return libraries[row]->name(); + case 1: + return libraries[row]->type(); + case 2: + return libraries[row]->version(); + default: + return QVariant(); + } + } + return QVariant(); +} + +Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + int row = index.row(); + if (libraries[row]->isActive()) + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } + else + { + return Qt::ItemNeverHasChildren; + } + // return QAbstractListModel::flags(index); +} + +QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + switch (section) + { + case 0: + return QString("Name"); + case 1: + return QString("Type"); + case 2: + return QString("Version"); + default: + return QString(); + } +} + +int DerpVersion::rowCount(const QModelIndex &parent) const +{ + return libraries.size(); +} + +int DerpVersion::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QDebug operator<<(QDebug &dbg, const DerpVersion *version) +{ + dbg.nospace() << "DerpVersion(" + << "\n\tid=" << version->id + << "\n\ttime=" << version->time + << "\n\treleaseTime=" << version->releaseTime + << "\n\ttype=" << version->type + << "\n\tassets=" << version->assets + << "\n\tprocessArguments=" << version->processArguments + << "\n\tminecraftArguments=" << version->minecraftArguments + << "\n\tminimumLauncherVersion=" << version->minimumLauncherVersion + << "\n\tmainClass=" << version->mainClass + << "\n\tlibraries="; + for (auto lib : version->libraries) + { + dbg.nospace() << "\n\t\t" << lib.get(); + } + dbg.nospace() << "\n)"; + return dbg.maybeSpace(); +} +QDebug operator<<(QDebug &dbg, const DerpLibrary *library) +{ + dbg.nospace() << "DerpLibrary(" + << "\n\t\t\trawName=" << library->rawName() + << "\n\t\t\tname=" << library->name() + << "\n\t\t\tversion=" << library->version() + << "\n\t\t\ttype=" << library->type() + << "\n\t\t\tisActive=" << library->isActive() + << "\n\t\t\tisNative=" << library->isNative() + << "\n\t\t\tdownloadUrl=" << library->downloadUrl() + << "\n\t\t\tstoragePath=" << library->storagePath() + << "\n\t\t\tabsolutePath=" << library->absoluteUrl() + << "\n\t\t\thint=" << library->hint(); + dbg.nospace() << "\n\t\t)"; + return dbg.maybeSpace(); +} diff --git a/logic/DerpVersion.h b/logic/DerpVersion.h new file mode 100644 index 00000000..cadfa850 --- /dev/null +++ b/logic/DerpVersion.h @@ -0,0 +1,110 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "DerpLibrary.h" + +class DerpInstance; + +class DerpVersion : public QAbstractListModel +{ + Q_OBJECT +public: + explicit DerpVersion(DerpInstance *instance, QObject *parent = 0); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + bool reload(QWidget *widgetParent); + +public: + QList> getActiveNormalLibs(); + QList> getActiveNativeLibs(); + + // data members +public: + /// the ID - determines which jar to use! ACTUALLY IMPORTANT! + QString id; + /// Last updated time - as a string + QString time; + /// Release time - as a string + QString releaseTime; + /// Release type - "release" or "snapshot" + QString type; + /// Assets type - "legacy" or a version ID + QString assets; + /** + * DEPRECATED: Old versions of the new vanilla launcher used this + * ex: "username_session_version" + */ + QString processArguments; + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString minecraftArguments; + /** + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) + */ + int minimumLauncherVersion = 0xDEADBEEF; + /** + * The main class to load first + */ + QString mainClass; + + /// the list of libs - both active and inactive, native and java + QList> libraries; + + /* + FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. + + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ], + "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX + 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" + } + */ + // QList rules; + +private: + DerpInstance *m_instance; +}; + +QDebug operator<<(QDebug &dbg, const DerpVersion *version); +QDebug operator<<(QDebug &dbg, const DerpLibrary *library); diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp new file mode 100644 index 00000000..d8091f32 --- /dev/null +++ b/logic/DerpVersionBuilder.cpp @@ -0,0 +1,279 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpVersionBuilder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DerpVersion.h" +#include "DerpInstance.h" +#include "DerpRule.h" + +DerpVersionBuilder::DerpVersionBuilder() +{ + +} + +bool DerpVersionBuilder::build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent) +{ + DerpVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.m_widgetParent = widgetParent; + return builder.build(); +} + +bool DerpVersionBuilder::build() +{ + clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // version.json -> patches/*.json -> custom.json + + // version.json + { + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + + // patches/ + { + // load all, put into map for ordering, apply in the right order + + QMap objects; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QJsonObject obj; + if (!read(info, &obj)) + { + return false; + } + if (!obj.contains("order") || !obj.value("order").isDouble()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Missing or invalid 'order' in %1").arg(info.absoluteFilePath())); + return false; + } + objects.insert(obj.value("order").toDouble(), obj); + } + for (auto object : objects.values()) + { + if (!apply(object)) + { + return false; + } + } + } + + // custom.json + { + if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + } + + return true; +} + +void DerpVersionBuilder::clear() +{ + m_version->id.clear(); + m_version->time.clear(); + m_version->releaseTime.clear(); + m_version->type.clear(); + m_version->assets.clear(); + m_version->processArguments.clear(); + m_version->minecraftArguments.clear(); + m_version->minimumLauncherVersion = 0xDEADBEAF; + m_version->mainClass.clear(); + m_version->libraries.clear(); +} + +void applyString(const QJsonObject &obj, const QString &key, QString &out) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + out = obj.value(key).toString(); + } +} +void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(DerpLibrary::*func)(const QString &val)) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + (lib.get()->*func)(obj.value(key).toString()); + } +} +bool DerpVersionBuilder::apply(const QJsonObject &object) +{ + applyString(object, "id", m_version->id); + applyString(object, "mainClass", m_version->mainClass); + applyString(object, "processArguments", m_version->processArguments); + { + const QString toCompare = m_version->processArguments.toLower(); + if (toCompare == "legacy") + { + m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + } + } + applyString(object, "minecraftArguments", m_version->minecraftArguments); + applyString(object, "type", m_version->type); + applyString(object, "releaseTime", m_version->releaseTime); + applyString(object, "time", m_version->time); + applyString(object, "assets", m_version->assets); + { + if (m_version->assets.isEmpty()) + { + m_version->assets = "legacy"; + } + } + if (object.contains("minimumLauncherVersion")) + { + auto minLauncherVersionVal = object.value("minimumLauncherVersion"); + if (minLauncherVersionVal.isDouble()) + { + m_version->minimumLauncherVersion = minLauncherVersionVal.toDouble(); + } + } + + // libraries + { + auto librariesValue = object.value("libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + for (auto libVal : librariesValue.toArray()) + { + if (!libVal.isObject()) + { + continue; + } + + QJsonObject libObj = libVal.toObject(); + + // Library name + auto nameVal = libObj.value("name"); + if (!nameVal.isString()) + { + continue; + } + std::shared_ptr library(new DerpLibrary(nameVal.toString())); + + applyString(libObj, "url", library, &DerpLibrary::setBaseUrl); + applyString(libObj, "MMC-hint", library, &DerpLibrary::setHint); + applyString(libObj, "MMC-absulute_url", library, &DerpLibrary::setAbsoluteUrl); + applyString(libObj, "MMC-absoluteUrl", library, &DerpLibrary::setAbsoluteUrl); + + auto extractVal = libObj.value("extract"); + if (extractVal.isObject()) + { + QStringList excludes; + auto extractObj = extractVal.toObject(); + auto excludesVal = extractObj.value("exclude"); + if (excludesVal.isArray()) + { + auto excludesList = excludesVal.toArray(); + for (auto excludeVal : excludesList) + { + if (excludeVal.isString()) + { + excludes.append(excludeVal.toString()); + } + } + library->extract_excludes = excludes; + } + } + + auto nativesVal = libObj.value("natives"); + if (nativesVal.isObject()) + { + library->setIsNative(); + auto nativesObj = nativesVal.toObject(); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + auto osType = OpSys_fromString(it.key()); + if (osType == Os_Other) + { + continue; + } + if (!it.value().isString()) + { + continue; + } + library->addNative(osType, it.value().toString()); + } + } + + library->setRules(rulesFromJsonV4(libObj)); + library->finalize(); + m_version->libraries.append(library); + } + } + + return true; +} + +bool DerpVersionBuilder::read(const QFileInfo &fileInfo, QJsonObject *out) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); + return false; + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to parse %1: %2 at %3").arg(file.fileName(), error.errorString()).arg(error.offset)); + return false; + } + *out = doc.object(); + return true; +} diff --git a/logic/DerpVersionBuilder.h b/logic/DerpVersionBuilder.h new file mode 100644 index 00000000..739065db --- /dev/null +++ b/logic/DerpVersionBuilder.h @@ -0,0 +1,43 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class DerpVersion; +class DerpInstance; +class QWidget; +class QJsonObject; +class QFileInfo; + +class DerpVersionBuilder +{ + DerpVersionBuilder(); +public: + static bool build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent); + +private: + DerpVersion *m_version; + DerpInstance *m_instance; + QWidget *m_widgetParent; + + bool build(); + + void clear(); + bool apply(const QJsonObject &object); + + bool read(const QFileInfo &fileInfo, QJsonObject *out); +}; diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 8d4c5b41..29735a7d 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -14,8 +14,8 @@ */ #include "ForgeInstaller.h" -#include "OneSixVersion.h" -#include "OneSixLibrary.h" +#include "DerpVersion.h" +#include "DerpLibrary.h" #include "net/HttpMetaCache.h" #include #include @@ -23,9 +23,15 @@ #include #include "MultiMC.h" +#include +#include +#include + +// DERPFIX + ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) { - std::shared_ptr newVersion; + std::shared_ptr newVersion; m_universal_url = universal_url; QuaZip zip(filename); @@ -58,7 +64,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) // read the forge version info { - newVersion = OneSixVersion::fromJson(versionInfoVal.toObject()); + // DERPFIX newVersion = DerpVersion::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -68,7 +74,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) internalPath = installObj.value("filePath").toString(); // where do we put the library? decode the mojang path - OneSixLibrary lib(libraryName); + DerpLibrary lib(libraryName); lib.finalize(); auto cacheentry = MMC->metacache()->resolveEntry("libraries", lib.storagePath()); @@ -103,11 +109,10 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); } -bool ForgeInstaller::apply(std::shared_ptr to) +bool ForgeInstaller::apply(std::shared_ptr to) { if (!m_forge_version) return false; - to->externalUpdateStart(); int sliding_insert_window = 0; { // for each library in the version we are adding (except for the blacklisted) @@ -150,6 +155,5 @@ bool ForgeInstaller::apply(std::shared_ptr to) to->minecraftArguments = m_forge_version->minecraftArguments; to->processArguments = m_forge_version->processArguments; } - to->externalUpdateFinish(); - return to->toOriginalFile(); + return true; } diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h index 0b9f9c77..65cad87c 100644 --- a/logic/ForgeInstaller.h +++ b/logic/ForgeInstaller.h @@ -17,18 +17,18 @@ #include #include -class OneSixVersion; +class DerpVersion; class ForgeInstaller { public: ForgeInstaller(QString filename, QString universal_url); - bool apply(std::shared_ptr to); + bool apply(std::shared_ptr to); private: // the version, read from the installer - std::shared_ptr m_forge_version; + std::shared_ptr m_forge_version; QString internalPath; QString finalPath; QString realVersionId; diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 1f1a5879..c50ec4e4 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -21,9 +21,10 @@ #include "BaseInstance.h" #include "LegacyInstance.h" #include "LegacyFTBInstance.h" -#include "OneSixInstance.h" -#include "OneSixFTBInstance.h" +#include "DerpInstance.h" +#include "DerpFTBInstance.h" #include "NostalgiaInstance.h" +#include "DerpInstance.h" #include "BaseVersion.h" #include "MinecraftVersion.h" @@ -50,13 +51,13 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "Legacy") + if (inst_type == "Derp" || inst_type == "OneSix") { - inst = new LegacyInstance(instDir, m_settings, this); + inst = new DerpInstance(instDir, m_settings, this); } - else if (inst_type == "OneSix") + else if (inst_type == "Legacy") { - inst = new OneSixInstance(instDir, m_settings, this); + inst = new LegacyInstance(instDir, m_settings, this); } else if (inst_type == "Nostalgia") { @@ -66,9 +67,9 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst { inst = new LegacyFTBInstance(instDir, m_settings, this); } - else if (inst_type == "OneSixFTB") + else if (inst_type == "OneSixFTB" || inst_type == "DerpFTB") { - inst = new OneSixFTBInstance(instDir, m_settings, this); + inst = new DerpFTBInstance(instDir, m_settings, this); } else { @@ -101,14 +102,15 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& switch (mcVer->type) { case MinecraftVersion::Legacy: + // TODO derp m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; - case MinecraftVersion::OneSix: - m_settings->set("InstanceType", "OneSix"); - inst = new OneSixInstance(instDir, m_settings, this); + case MinecraftVersion::Derp: + m_settings->set("InstanceType", "Derp"); + inst = new DerpInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; @@ -135,9 +137,9 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; - case MinecraftVersion::OneSix: - m_settings->set("InstanceType", "OneSixFTB"); - inst = new OneSixFTBInstance(instDir, m_settings, this); + case MinecraftVersion::Derp: + m_settings->set("InstanceType", "DerpFTB"); + inst = new DerpFTBInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; @@ -174,8 +176,8 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne m_settings->registerSetting("InstanceType", "Legacy"); QString inst_type = m_settings->get("InstanceType").toString(); - if(inst_type == "OneSixFTB") - m_settings->set("InstanceType", "OneSix"); + if(inst_type == "OneSixFTB" || inst_type == "DerpFTB") + m_settings->set("InstanceType", "Derp"); if(inst_type == "LegacyFTB") m_settings->set("InstanceType", "Legacy"); diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index 1e7d9eb6..1ff17cbd 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -68,7 +68,7 @@ public: virtual QString intendedVersionId() const override; virtual bool setIntendedVersionId(QString version) override; // the `version' of Legacy instances is defined by the launcher code. - // in contrast with OneSix, where `version' is described in a json file + // in contrast with Derp, where `version' is described in a json file virtual bool versionIsCustom() override { return false; diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 07fffff3..8a120eab 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -15,8 +15,8 @@ #include "LiteLoaderInstaller.h" -#include "OneSixVersion.h" -#include "OneSixLibrary.h" +#include "DerpVersion.h" +#include "DerpLibrary.h" QMap LiteLoaderInstaller::m_launcherWrapperVersionMapping; @@ -36,9 +36,9 @@ bool LiteLoaderInstaller::canApply() const return m_launcherWrapperVersionMapping.contains(m_mcVersion); } -bool LiteLoaderInstaller::apply(std::shared_ptr to) +bool LiteLoaderInstaller::apply(std::shared_ptr to) { - to->externalUpdateStart(); + // DERPFIX applyLaunchwrapper(to); applyLiteLoader(to); @@ -51,15 +51,14 @@ bool LiteLoaderInstaller::apply(std::shared_ptr to) " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"); } - to->externalUpdateFinish(); - return to->toOriginalFile(); + return true; } -void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr to) +void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr to) { const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion]; - QMutableListIterator> it(to->libraries); + QMutableListIterator> it(to->libraries); while (it.hasNext()) { it.next(); @@ -76,15 +75,15 @@ void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr to) } } - std::shared_ptr lib(new OneSixLibrary( + std::shared_ptr lib(new DerpLibrary( "net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion])); lib->finalize(); to->libraries.prepend(lib); } -void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr to) +void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr to) { - QMutableListIterator> it(to->libraries); + QMutableListIterator> it(to->libraries); while (it.hasNext()) { it.next(); @@ -94,8 +93,8 @@ void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr to) } } - std::shared_ptr lib( - new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion)); + std::shared_ptr lib( + new DerpLibrary("com.mumfrey:liteloader:" + m_mcVersion)); lib->setBaseUrl("http://dl.liteloader.com/versions/"); lib->finalize(); to->libraries.prepend(lib); diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index 44b306d6..d7b8ce77 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -18,7 +18,7 @@ #include #include -class OneSixVersion; +class DerpVersion; class LiteLoaderInstaller { @@ -27,13 +27,13 @@ public: bool canApply() const; - bool apply(std::shared_ptr to); + bool apply(std::shared_ptr to); private: QString m_mcVersion; - void applyLaunchwrapper(std::shared_ptr to); - void applyLiteLoader(std::shared_ptr to); + void applyLaunchwrapper(std::shared_ptr to); + void applyLiteLoader(std::shared_ptr to); static QMap m_launcherWrapperVersionMapping; }; diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h index 504381a8..dd849ce9 100644 --- a/logic/MinecraftVersion.h +++ b/logic/MinecraftVersion.h @@ -32,7 +32,7 @@ struct MinecraftVersion : public BaseVersion /// This version's type. Used internally to identify what kind of version this is. enum VersionType { - OneSix, + Derp, Legacy, Nostalgia } type; @@ -66,8 +66,8 @@ struct MinecraftVersion : public BaseVersion } switch (type) { - case OneSix: - pre_final.append("OneSix"); + case Derp: + pre_final.append("Derp"); break; case Legacy: pre_final.append("Legacy"); diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp index 2e23ee71..f27954a7 100644 --- a/logic/NostalgiaInstance.cpp +++ b/logic/NostalgiaInstance.cpp @@ -17,7 +17,7 @@ NostalgiaInstance::NostalgiaInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) - : OneSixInstance(rootDir, settings, parent) + : DerpInstance(rootDir, settings, parent) { } diff --git a/logic/NostalgiaInstance.h b/logic/NostalgiaInstance.h index a26f7f0a..6a6b91a1 100644 --- a/logic/NostalgiaInstance.h +++ b/logic/NostalgiaInstance.h @@ -15,9 +15,9 @@ #pragma once -#include "OneSixInstance.h" +#include "DerpInstance.h" -class NostalgiaInstance : public OneSixInstance +class NostalgiaInstance : public DerpInstance { Q_OBJECT public: diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp deleted file mode 100644 index e50a5b53..00000000 --- a/logic/OneSixFTBInstance.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "OneSixFTBInstance.h" - -#include "OneSixVersion.h" -#include "OneSixLibrary.h" -#include "tasks/SequentialTask.h" -#include "ForgeInstaller.h" -#include "lists/ForgeVersionList.h" -#include "MultiMC.h" - -class OneSixFTBInstanceForge : public Task -{ - Q_OBJECT -public: - explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) : - Task(parent), instance(inst), version("Forge " + version) - { - } - - void executeTask() - { - for (int i = 0; i < MMC->forgelist()->count(); ++i) - { - if (MMC->forgelist()->at(i)->name() == version) - { - forgeVersion = std::dynamic_pointer_cast(MMC->forgelist()->at(i)); - break; - } - } - if (!forgeVersion) - { - emitFailed(QString("Couldn't find forge version ") + version ); - return; - } - entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) - { - setStatus(tr("Downloading Forge...")); - fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);}); - connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge); - connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); }); - fjob->start(); - } - else - { - installForge(); - } - } - -private -slots: - void installForge() - { - setStatus(tr("Installing Forge...")); - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!instance->reloadFullVersion()) - { - emitFailed(tr("Couldn't load the version config")); - return; - } - instance->revertCustomVersion(); - instance->customizeVersion(); - auto version = instance->getFullVersion(); - if (!forge.apply(version)) - { - emitFailed(tr("Couldn't install Forge")); - return; - } - emitSucceeded(); - } - -private: - OneSixFTBInstance *instance; - QString version; - ForgeVersionPtr forgeVersion; - MetaEntryPtr entry; - NetJob *fjob; -}; - -OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : - OneSixInstance(rootDir, settings, parent) -{ - QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); - if (f.open(QFile::ReadOnly)) - { - QString data = QString::fromUtf8(f.readAll()); - QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); - m_forge.reset(new OneSixLibrary(match.captured())); - m_forge->finalize(); - } -} - -QString OneSixFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -QString OneSixFTBInstance::getStatusbarDescription() -{ - return "OneSix FTB: " + intendedVersionId(); -} -bool OneSixFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} - -std::shared_ptr OneSixFTBInstance::doUpdate(bool only_prepare) -{ - std::shared_ptr task; - task.reset(new SequentialTask(this)); - if (!MMC->forgelist()->isLoaded()) - { - task->addTask(std::shared_ptr(MMC->forgelist()->getLoadTask())); - } - task->addTask(OneSixInstance::doUpdate(only_prepare)); - task->addTask(std::shared_ptr(new OneSixFTBInstanceForge(m_forge->version(), this, this))); - //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. - //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. - task->addTask(OneSixInstance::doUpdate(only_prepare)); - return task; -} - -#include "OneSixFTBInstance.moc" diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h deleted file mode 100644 index dc028819..00000000 --- a/logic/OneSixFTBInstance.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "OneSixInstance.h" - -class OneSixLibrary; - -class OneSixFTBInstance : public OneSixInstance -{ - Q_OBJECT -public: - explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; - - virtual std::shared_ptr doUpdate(bool only_prepare) override; - - virtual QString id() const; - -private: - std::shared_ptr m_forge; -}; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp deleted file mode 100644 index 3cfc1c76..00000000 --- a/logic/OneSixInstance.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MultiMC.h" -#include "OneSixInstance.h" -#include "OneSixInstance_p.h" -#include "OneSixUpdate.h" -#include "MinecraftProcess.h" -#include "OneSixVersion.h" -#include "JavaChecker.h" -#include "logic/icons/IconList.h" - -#include -#include -#include -#include -#include "gui/dialogs/OneSixModEditDialog.h" -#include "logger/QsLog.h" -#include "logic/assets/AssetsUtils.h" -#include - -OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj, - QObject *parent) - : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent) -{ - I_D(OneSixInstance); - d->m_settings->registerSetting("IntendedVersion", ""); - d->m_settings->registerSetting("ShouldUpdate", false); - reloadFullVersion(); -} - -std::shared_ptr OneSixInstance::doUpdate(bool only_prepare) -{ - return std::shared_ptr(new OneSixUpdate(this, only_prepare)); -} - -QString replaceTokensIn(QString text, QMap with) -{ - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; -} - -QDir OneSixInstance::reconstructAssets(std::shared_ptr version) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); - - if (!indexFile.exists()) - { - QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; - return virtualRoot; - } - - QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) - { - QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = - PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); - QFile original(original_path); - if(!original.exists()) - continue; - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - // QLOG_DEBUG() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - QLOG_DEBUG() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - } - - return virtualRoot; -} - -QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) -{ - I_D(OneSixInstance); - auto version = d->version; - QString args_pattern = version->minecraftArguments; - - QMap token_mapping; - // yggdrasil! - token_mapping["auth_username"] = account->username(); - token_mapping["auth_session"] = account->sessionId(); - token_mapping["auth_access_token"] = account->accessToken(); - token_mapping["auth_player_name"] = account->currentProfile()->name; - token_mapping["auth_uuid"] = account->currentProfile()->id; - - // this is for offline?: - /* - map["auth_player_name"] = "Player"; - map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; - */ - - // these do nothing and are stupid. - token_mapping["profile_name"] = name(); - token_mapping["version_name"] = version->id; - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); - - auto user = account->user(); - QJsonObject userAttrs; - for (auto key : user.properties.keys()) - { - auto array = QJsonArray::fromStringList(user.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - - token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); - token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = version->assets; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; -} - -MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) -{ - I_D(OneSixInstance); - - QIcon icon = MMC->icons()->getIcon(iconKey()); - auto pixmap = icon.pixmap(128, 128); - pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); - - auto version = d->version; - if (!version) - return nullptr; - QString launchScript; - { - auto libs = version->getActiveNormalLibs(); - for (auto lib : libs) - { - QFileInfo fi(QString("libraries/") + lib->storagePath()); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; - } - QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; - QFileInfo fi(targetstr); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; - } - launchScript += "mainClass " + version->mainClass + "\n"; - - for (auto param : processMinecraftArgs(account)) - { - launchScript += "param " + param + "\n"; - } - - // Set the width and height for 1.6 instances - bool maximize = settings().get("LaunchMaximized").toBool(); - if (maximize) - { - // this is probably a BAD idea - // launchScript += "param --fullscreen\n"; - } - else - { - launchScript += - "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; - launchScript += - "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; - } - QDir natives_dir(PathCombine(instanceRoot(), "natives/")); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "natives " + natives_dir.absolutePath() + "\n"; - launchScript += "launch onesix\n"; - - // create the process and set its parameters - MinecraftProcess *proc = new MinecraftProcess(this); - proc->setWorkdir(minecraftRoot()); - proc->setLaunchScript(launchScript); - // proc->setNativeFolder(natives_dir.absolutePath()); - return proc; -} - -void OneSixInstance::cleanupAfterRun() -{ - QString target_dir = PathCombine(instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); -} - -std::shared_ptr OneSixInstance::loaderModList() -{ - I_D(OneSixInstance); - if (!d->loader_mod_list) - { - d->loader_mod_list.reset(new ModList(loaderModsDir())); - } - d->loader_mod_list->update(); - return d->loader_mod_list; -} - -std::shared_ptr OneSixInstance::resourcePackList() -{ - I_D(OneSixInstance); - if (!d->resource_pack_list) - { - d->resource_pack_list.reset(new ModList(resourcePacksDir())); - } - d->resource_pack_list->update(); - return d->resource_pack_list; -} - -QDialog *OneSixInstance::createModEditDialog(QWidget *parent) -{ - return new OneSixModEditDialog(this, parent); -} - -bool OneSixInstance::setIntendedVersionId(QString version) -{ - settings().set("IntendedVersion", version); - setShouldUpdate(true); - auto pathCustom = PathCombine(instanceRoot(), "custom.json"); - auto pathOrig = PathCombine(instanceRoot(), "version.json"); - QFile::remove(pathCustom); - QFile::remove(pathOrig); - reloadFullVersion(); - return true; -} - -QString OneSixInstance::intendedVersionId() const -{ - return settings().get("IntendedVersion").toString(); -} - -void OneSixInstance::setShouldUpdate(bool val) -{ - settings().set("ShouldUpdate", val); -} - -bool OneSixInstance::shouldUpdate() const -{ - QVariant var = settings().get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; -} - -bool OneSixInstance::versionIsCustom() -{ - QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); - QFile versionfile(verpath_custom); - return versionfile.exists(); -} - -QString OneSixInstance::currentVersionId() const -{ - return intendedVersionId(); -} - -bool OneSixInstance::customizeVersion() -{ - if (!versionIsCustom()) - { - auto pathCustom = PathCombine(instanceRoot(), "custom.json"); - auto pathOrig = PathCombine(instanceRoot(), "version.json"); - QFile::copy(pathOrig, pathCustom); - return reloadFullVersion(); - } - else - return true; -} - -bool OneSixInstance::revertCustomVersion() -{ - if (versionIsCustom()) - { - auto path = PathCombine(instanceRoot(), "custom.json"); - QFile::remove(path); - return reloadFullVersion(); - } - else - return true; -} - -bool OneSixInstance::reloadFullVersion() -{ - I_D(OneSixInstance); - - QString verpath = PathCombine(instanceRoot(), "version.json"); - { - QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); - QFile versionfile(verpath_custom); - if (versionfile.exists()) - verpath = verpath_custom; - } - - auto version = OneSixVersion::fromFile(verpath); - if (version) - { - d->version = version; - return true; - } - else - { - d->version.reset(); - return false; - } -} - -std::shared_ptr OneSixInstance::getFullVersion() -{ - I_D(OneSixInstance); - return d->version; -} - -QString OneSixInstance::defaultBaseJar() const -{ - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; -} - -QString OneSixInstance::defaultCustomBaseJar() const -{ - return PathCombine(instanceRoot(), "custom.jar"); -} - -bool OneSixInstance::menuActionEnabled(QString action_name) const -{ - if (action_name == "actionChangeInstLWJGLVersion") - return false; - return true; -} - -QString OneSixInstance::getStatusbarDescription() -{ - QString descr = "One Six : " + intendedVersionId(); - if (versionIsCustom()) - { - descr + " (custom)"; - } - return descr; -} - -QString OneSixInstance::loaderModsDir() const -{ - return PathCombine(minecraftRoot(), "mods"); -} - -QString OneSixInstance::resourcePacksDir() const -{ - return PathCombine(minecraftRoot(), "resourcepacks"); -} - -QString OneSixInstance::instanceConfigFolder() const -{ - return PathCombine(minecraftRoot(), "config"); -} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h deleted file mode 100644 index f869e345..00000000 --- a/logic/OneSixInstance.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "BaseInstance.h" - -class OneSixVersion; -class Task; -class ModList; - -class OneSixInstance : public BaseInstance -{ - Q_OBJECT -public: - explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - - ////// Mod Lists ////// - std::shared_ptr loaderModList(); - std::shared_ptr resourcePackList(); - - ////// Directories ////// - QString resourcePacksDir() const; - QString loaderModsDir() const; - virtual QString instanceConfigFolder() const override; - - virtual std::shared_ptr doUpdate(bool only_prepare) override; - virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; - - virtual void cleanupAfterRun() override; - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - - virtual QString currentVersionId() const override; - - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - - virtual QDialog *createModEditDialog(QWidget *parent) override; - - /// reload the full version json file. return true on success! - bool reloadFullVersion(); - /// get the current full version info - std::shared_ptr getFullVersion(); - /// revert the current custom version back to base - bool revertCustomVersion(); - /// customize the current base version - bool customizeVersion(); - /// is the current version original, or custom? - virtual bool versionIsCustom() override; - - virtual QString defaultBaseJar() const override; - virtual QString defaultCustomBaseJar() const override; - - virtual bool menuActionEnabled(QString action_name) const override; - virtual QString getStatusbarDescription() override; - -private: - QStringList processMinecraftArgs(MojangAccountPtr account); - QDir reconstructAssets(std::shared_ptr version); -}; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h deleted file mode 100644 index 6b7ea431..00000000 --- a/logic/OneSixInstance_p.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include "logic/BaseInstance_p.h" -#include "logic/OneSixVersion.h" -#include "logic/OneSixLibrary.h" -#include "logic/ModList.h" - -struct OneSixInstancePrivate : public BaseInstancePrivate -{ - std::shared_ptr version; - std::shared_ptr loader_mod_list; - std::shared_ptr resource_pack_list; -}; \ No newline at end of file diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp deleted file mode 100644 index 7b80d5e7..00000000 --- a/logic/OneSixLibrary.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "OneSixLibrary.h" -#include "OneSixRule.h" -#include "OpSys.h" -#include "logic/net/URLConstants.h" -#include -#include -#include "logger/QsLog.h" - -void OneSixLibrary::finalize() -{ - QStringList parts = m_name.split(':'); - QString relative = parts[0]; - relative.replace('.', '/'); - relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - - if (!m_is_native) - relative += ".jar"; - else - { - if (m_native_suffixes.contains(currentSystem)) - { - relative += "-" + m_native_suffixes[currentSystem] + ".jar"; - } - else - { - // really, bad. - relative += ".jar"; - } - } - - m_decentname = parts[1]; - m_decentversion = parts[2]; - m_storage_path = relative; - m_download_url = m_base_url + relative; - - if (m_rules.empty()) - { - m_is_active = true; - } - else - { - RuleAction result = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - result = temp; - } - m_is_active = (result == Allow); - } - if (m_is_native) - { - m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); - m_decenttype = "Native"; - } - else - { - m_decenttype = "Java"; - } -} - -void OneSixLibrary::setName(QString name) -{ - m_name = name; -} -void OneSixLibrary::setBaseUrl(QString base_url) -{ - m_base_url = base_url; -} -void OneSixLibrary::setIsNative() -{ - m_is_native = true; -} -void OneSixLibrary::addNative(OpSys os, QString suffix) -{ - m_is_native = true; - m_native_suffixes[os] = suffix; -} -void OneSixLibrary::setRules(QList> rules) -{ - m_rules = rules; -} -bool OneSixLibrary::isActive() -{ - return m_is_active; -} -bool OneSixLibrary::isNative() -{ - return m_is_native; -} -QString OneSixLibrary::downloadUrl() -{ - if (m_absolute_url.size()) - return m_absolute_url; - return m_download_url; -} -QString OneSixLibrary::storagePath() -{ - return m_storage_path; -} - -void OneSixLibrary::setAbsoluteUrl(QString absolute_url) -{ - m_absolute_url = absolute_url; -} - -QString OneSixLibrary::absoluteUrl() -{ - return m_absolute_url; -} - -void OneSixLibrary::setHint(QString hint) -{ - m_hint = hint; -} - -QString OneSixLibrary::hint() -{ - return m_hint; -} - -bool OneSixLibrary::filesExist() -{ - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - QFileInfo info32(PathCombine("libraries", cooked_storage)); - if (!info32.exists()) - { - return false; - } - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - QFileInfo info64(PathCombine("libraries", cooked_storage)); - if (!info64.exists()) - { - return false; - } - } - else - { - QFileInfo info(PathCombine("libraries", storage)); - if (!info.exists()) - { - return false; - } - } - return true; -} - -bool OneSixLibrary::extractTo(QString target_dir) -{ - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - QString origin = PathCombine("libraries", cooked_storage); - QString target_dir_cooked = PathCombine(target_dir, "32"); - if(!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - origin = PathCombine("libraries", cooked_storage); - target_dir_cooked = PathCombine(target_dir, "64"); - if(!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - } - else - { - if(!ensureFolderPathExists(target_dir)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir; - return false; - } - QString path = PathCombine("libraries", storage); - if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + path; - return false; - } - } - return true; -} - -QJsonObject OneSixLibrary::toJson() -{ - QJsonObject libRoot; - libRoot.insert("name", m_name); - if (m_absolute_url.size()) - libRoot.insert("MMC-absoluteUrl", m_absolute_url); - if (m_hint.size()) - libRoot.insert("MMC-hint", m_hint); - if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE) - libRoot.insert("url", m_base_url); - if (isNative() && m_native_suffixes.size()) - { - QJsonObject nativeList; - auto iter = m_native_suffixes.begin(); - while (iter != m_native_suffixes.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - } - if (isNative() && extract_excludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : extract_excludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - if (m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - return libRoot; -} diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h deleted file mode 100644 index 227cdbef..00000000 --- a/logic/OneSixLibrary.h +++ /dev/null @@ -1,132 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "logic/net/URLConstants.h" -#include "OpSys.h" - -class Rule; - -class OneSixLibrary -{ -private: - // basic values used internally (so far) - QString m_name; - QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; - QList> m_rules; - - // custom values - /// absolute URL. takes precedence over m_download_path, if defined - QString m_absolute_url; - /// download hint - how to actually get the library - QString m_hint; - - // derived values used for real things - /// a decent name fit for display - QString m_decentname; - /// a decent version fit for display - QString m_decentversion; - /// a decent type fit for display - QString m_decenttype; - /// where to store the lib locally - QString m_storage_path; - /// where to download the lib from - QString m_download_url; - /// is this lib actually active on the current OS? - bool m_is_active = false; - /// is the library a native? - bool m_is_native = false; - /// native suffixes per OS - QMap m_native_suffixes; - -public: - QStringList extract_excludes; - -public: - /// Constructor - OneSixLibrary(QString name) - { - m_name = name; - } - - /// Returns the raw name field - QString rawName() const - { - return m_name; - } - - QJsonObject toJson(); - - /** - * finalize the library, processing the input values into derived values and state - * - * This SHALL be called after all the values are parsed or after any further change. - */ - void finalize(); - - /// Set the library composite name - void setName(QString name); - /// get a decent-looking name - QString name() - { - return m_decentname; - } - /// get a decent-looking version - QString version() - { - return m_decentversion; - } - /// what kind of library is it? (for display) - QString type() - { - return m_decenttype; - } - /// Set the url base for downloads - void setBaseUrl(QString base_url); - - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); - /// Attach a name suffix to the specified OS native - void addNative(OpSys os, QString suffix); - /// Set the load rules - void setRules(QList> rules); - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive(); - /// Returns true if the library is native - bool isNative(); - /// Get the URL to download the library from - QString downloadUrl(); - /// Get the relative path where the library should be saved - QString storagePath(); - - /// set an absolute URL for the library. This is an MMC extension. - void setAbsoluteUrl(QString absolute_url); - QString absoluteUrl(); - - /// set a hint about how to treat the library. This is an MMC extension. - void setHint(QString hint); - QString hint(); - - bool extractTo(QString target_dir); - bool filesExist(); -}; diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp deleted file mode 100644 index 392b1dd1..00000000 --- a/logic/OneSixRule.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include "OneSixRule.h" - -QList> rulesFromJsonV4(QJsonObject &objectWithRules) -{ - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; - - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } - - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - return rules; -} - -QJsonObject ImplicitRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; -} - -QJsonObject OsRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", OpSys_toString(m_system)); - osObj.insert("version", m_version_regexp); - } - ruleObj.insert("os", osObj); - return ruleObj; -} - -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} \ No newline at end of file diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h deleted file mode 100644 index 5a13cbd9..00000000 --- a/logic/OneSixRule.h +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include "logic/OneSixLibrary.h" - -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -RuleAction RuleAction_fromString(QString); -QList> rulesFromJsonV4(QJsonObject &objectWithRules); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(OneSixLibrary *parent) = 0; - -public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(OneSixLibrary *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } - ; -}; - -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; - -protected: - virtual bool applies(OneSixLibrary *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule -{ -protected: - virtual bool applies(OneSixLibrary *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result) - { - return std::shared_ptr(new ImplicitRule(result)); - } -}; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp deleted file mode 100644 index 0119ab07..00000000 --- a/logic/OneSixUpdate.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MultiMC.h" -#include "OneSixUpdate.h" - -#include - -#include -#include -#include -#include - -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "OneSixVersion.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" -#include "net/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" -#include - -OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) - : Task(parent), m_inst(inst), m_only_prepare(only_prepare) -{ -} - -void OneSixUpdate::executeTask() -{ - QString intendedVersion = m_inst->intendedVersionId(); - - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed("Failed to create bin folder."); - return; - } - - if (m_only_prepare) - { - prepareForLaunch(); - return; - } - - if (m_inst->shouldUpdate()) - { - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast( - MMC->minecraftlist()->findVersion(intendedVersion)); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed("The specified Minecraft version is invalid. Choose a different one."); - return; - } - versionFileStart(); - } - else - { - jarlibStart(); - } -} - -void OneSixUpdate::versionFileStart() -{ - QLOG_INFO() << m_inst->name() << ": getting version file."; - setStatus(tr("Getting the version files from Mojang...")); - - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + - targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; - auto job = new NetJob("Version index"); - job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); - specificVersionDownloadJob.reset(job); - connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - specificVersionDownloadJob->start(); -} - -void OneSixUpdate::versionFileFinished() -{ - NetActionPtr DlJob = specificVersionDownloadJob->first(); - OneSixInstance *inst = (OneSixInstance *)m_inst; - - QString version_id = targetVersion->descriptor(); - QString inst_dir = m_inst->instanceRoot(); - // save the version file in $instanceId/version.json - { - QString version1 = PathCombine(inst_dir, "/version.json"); - ensureFilePathExists(version1); - // FIXME: detect errors here, download to a temp file, swap - QSaveFile vfile1(version1); - if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) - { - emitFailed("Can't open " + version1 + " for writing."); - return; - } - auto data = std::dynamic_pointer_cast(DlJob)->m_data; - qint64 actual = 0; - if ((actual = vfile1.write(data)) != data.size()) - { - emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + - data.size() + '.'); - return; - } - if (!vfile1.commit()) - { - emitFailed("Can't commit changes to " + version1); - return; - } - } - - // the version is downloaded safely. update is 'done' at this point - m_inst->setShouldUpdate(false); - - // delete any custom version inside the instance (it's no longer relevant, we did an update) - QString custom = PathCombine(inst_dir, "/custom.json"); - QFile finfo(custom); - if (finfo.exists()) - { - finfo.remove(); - } - inst->reloadFullVersion(); - - jarlibStart(); -} - -void OneSixUpdate::versionFileFailed() -{ - emitFailed("Failed to download the version description. Try again."); -} - -void OneSixUpdate::assetIndexStart() -{ - setStatus(tr("Updating assets index...")); - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); - QString assetName = version->assets; - QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; - QString localPath = assetName + ".json"; - auto job = new NetJob("Asset index for " + inst->name()); - - auto metacache = MMC->metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - job->addNetAction(CacheDownload::make(indexUrl, entry)); - jarlibDownloadJob.reset(job); - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - - jarlibDownloadJob->start(); -} - -void OneSixUpdate::assetIndexFinished() -{ - AssetsIndex index; - - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); - QString assetName = version->assets; - - QString asset_fname = "assets/indexes/" + assetName + ".json"; - if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index)) - { - emitFailed("Failed to read the assets index!"); - } - - QList dls; - for (auto object : index.objects.values()) - { - QString objectName = object.hash.left(2) + "/" + object.hash; - QFileInfo objectFile("assets/objects/" + objectName); - if ((!objectFile.isFile()) || (objectFile.size() != object.size)) - { - auto objectDL = MD5EtagDownload::make( - QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), - objectFile.filePath()); - objectDL->m_total_progress = object.size; - dls.append(objectDL); - } - } - if (dls.size()) - { - setStatus(tr("Getting the assets files from Mojang...")); - auto job = new NetJob("Assets for " + inst->name()); - for (auto dl : dls) - job->addNetAction(dl); - jarlibDownloadJob.reset(job); - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - jarlibDownloadJob->start(); - return; - } - assetsFinished(); -} - -void OneSixUpdate::assetIndexFailed() -{ - emitFailed("Failed to download the assets index!"); -} - -void OneSixUpdate::assetsFinished() -{ - prepareForLaunch(); -} - -void OneSixUpdate::assetsFailed() -{ - emitFailed("Failed to download assets!"); -} - -void OneSixUpdate::jarlibStart() -{ - setStatus(tr("Getting the library files from Mojang...")); - QLOG_INFO() << m_inst->name() << ": downloading libraries"; - OneSixInstance *inst = (OneSixInstance *)m_inst; - bool successful = inst->reloadFullVersion(); - if (!successful) - { - emitFailed("Failed to load the version description file. It might be " - "corrupted, missing or simply too new."); - return; - } - - // Build a list of URLs that will need to be downloaded. - std::shared_ptr version = inst->getFullVersion(); - // minecraft.jar for this version - { - QString version_id = version->id; - QString localPath = version_id + "/" + version_id + ".jar"; - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath; - - auto job = new NetJob("Libraries for instance " + inst->name()); - - auto metacache = MMC->metacache(); - auto entry = metacache->resolveEntry("versions", localPath); - job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); - - jarlibDownloadJob.reset(job); - } - - auto libs = version->getActiveNativeLibs(); - libs.append(version->getActiveNormalLibs()); - - auto metacache = MMC->metacache(); - QList ForgeLibs; - for (auto lib : libs) - { - if (lib->hint() == "local") - continue; - - QString raw_storage = lib->storagePath(); - QString raw_dl = lib->downloadUrl(); - - auto f = [&](QString storage, QString dl) - { - auto entry = metacache->resolveEntry("libraries", storage); - if (entry->stale) - { - if (lib->hint() == "forge-pack-xz") - { - ForgeLibs.append(ForgeXzDownload::make(storage, entry)); - } - else - { - jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); - } - } - }; - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); - } - else - { - f(raw_storage, raw_dl); - } - } - // TODO: think about how to propagate this from the original json file... or IF AT ALL - QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list"; - if (!ForgeLibs.empty()) - { - jarlibDownloadJob->addNetAction( - ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList)); - } - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - - jarlibDownloadJob->start(); -} - -void OneSixUpdate::jarlibFinished() -{ - assetIndexStart(); -} - -void OneSixUpdate::jarlibFailed() -{ - QStringList failed = jarlibDownloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed("Failed to download the following files:\n" + failed_all + - "\n\nPlease try again."); -} - -void OneSixUpdate::prepareForLaunch() -{ - setStatus(tr("Preparing for launch...")); - QLOG_INFO() << m_inst->name() << ": preparing for launch"; - auto onesix_inst = (OneSixInstance *)m_inst; - - // delete any leftovers, if they are present. - onesix_inst->cleanupAfterRun(); - - QString natives_dir_raw = PathCombine(onesix_inst->instanceRoot(), "natives/"); - auto version = onesix_inst->getFullVersion(); - if (!version) - { - emitFailed("The version information for this instance is not complete. Try re-creating " - "it or changing the version."); - return; - } - /* - * emitFailed("Could not create the native library folder:\n" + natives_dir_raw + - "\nMake sure MultiMC has appropriate permissions and there is enough - space " - "on the storage device."); - */ - for (auto lib : version->getActiveNativeLibs()) - { - if (!lib->filesExist()) - { - emitFailed("Native library is missing some files:\n" + lib->storagePath() + - "\n\nRun the instance at least once in online mode to get all the " - "required files."); - return; - } - if (!lib->extractTo(natives_dir_raw)) - { - emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + - natives_dir_raw + - "\n\nMake sure MultiMC has appropriate permissions and there is enough " - "space on the storage device."); - return; - } - } - - emitSucceeded(); -} diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h deleted file mode 100644 index bc717a94..00000000 --- a/logic/OneSixUpdate.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "logic/net/NetJob.h" -#include "logic/tasks/Task.h" - -class MinecraftVersion; -class BaseInstance; - -class OneSixUpdate : public Task -{ - Q_OBJECT -public: - explicit OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); - virtual void executeTask(); - -private -slots: - void versionFileStart(); - void versionFileFinished(); - void versionFileFailed(); - - void jarlibStart(); - void jarlibFinished(); - void jarlibFailed(); - - void assetIndexStart(); - void assetIndexFinished(); - void assetIndexFailed(); - - void assetsFinished(); - void assetsFailed(); - - // extract the appropriate libraries - void prepareForLaunch(); - -private: - NetJobPtr specificVersionDownloadJob; - NetJobPtr jarlibDownloadJob; - - // target version, determined during this task - std::shared_ptr targetVersion; - BaseInstance *m_inst = nullptr; - bool m_only_prepare = false; -}; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 8ae685f0..3571be9b 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -13,14 +13,14 @@ * limitations under the License. */ -#include "logic/OneSixVersion.h" -#include "logic/OneSixLibrary.h" -#include "logic/OneSixRule.h" +#include "logic/DerpVersion.h" +#include "logic/DerpLibrary.h" +#include "logic/DerpRule.h" #include "logger/QsLog.h" -std::shared_ptr fromJsonV4(QJsonObject root, - std::shared_ptr fullVersion) +std::shared_ptr fromJsonV4(QJsonObject root, + std::shared_ptr fullVersion) { fullVersion->id = root.value("id").toString(); @@ -93,7 +93,7 @@ std::shared_ptr fromJsonV4(QJsonObject root, auto nameVal = libObj.value("name"); if (!nameVal.isString()) continue; - std::shared_ptr library(new OneSixLibrary(nameVal.toString())); + std::shared_ptr library(new DerpLibrary(nameVal.toString())); auto urlVal = libObj.value("url"); if (urlVal.isString()) @@ -158,9 +158,9 @@ std::shared_ptr fromJsonV4(QJsonObject root, return fullVersion; } -std::shared_ptr OneSixVersion::fromJson(QJsonObject root) +std::shared_ptr DerpVersion::fromJson(QJsonObject root) { - std::shared_ptr readVersion(new OneSixVersion()); + std::shared_ptr readVersion(new DerpVersion()); int launcher_ver = readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble(); @@ -169,16 +169,16 @@ std::shared_ptr OneSixVersion::fromJson(QJsonObject root) return fromJsonV4(root, readVersion); else { - return std::shared_ptr(); + return std::shared_ptr(); } } -std::shared_ptr OneSixVersion::fromFile(QString filepath) +std::shared_ptr DerpVersion::fromFile(QString filepath) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { - return std::shared_ptr(); + return std::shared_ptr(); } auto data = file.readAll(); @@ -187,12 +187,12 @@ std::shared_ptr OneSixVersion::fromFile(QString filepath) if (jsonError.error != QJsonParseError::NoError) { - return std::shared_ptr(); + return std::shared_ptr(); } if (!jsonDoc.isObject()) { - return std::shared_ptr(); + return std::shared_ptr(); } QJsonObject root = jsonDoc.object(); auto version = fromJson(root); @@ -201,7 +201,7 @@ std::shared_ptr OneSixVersion::fromFile(QString filepath) return version; } -bool OneSixVersion::toOriginalFile() +bool DerpVersion::toOriginalFile() { if (original_file.isEmpty()) return false; @@ -232,9 +232,9 @@ bool OneSixVersion::toOriginalFile() return file.commit(); } -QList> OneSixVersion::getActiveNormalLibs() +QList> DerpVersion::getActiveNormalLibs() { - QList> output; + QList> output; for (auto lib : libraries) { if (lib->isActive() && !lib->isNative()) @@ -245,9 +245,9 @@ QList> OneSixVersion::getActiveNormalLibs() return output; } -QList> OneSixVersion::getActiveNativeLibs() +QList> DerpVersion::getActiveNativeLibs() { - QList> output; + QList> output; for (auto lib : libraries) { if (lib->isActive() && lib->isNative()) @@ -258,17 +258,17 @@ QList> OneSixVersion::getActiveNativeLibs() return output; } -void OneSixVersion::externalUpdateStart() +void DerpVersion::externalUpdateStart() { beginResetModel(); } -void OneSixVersion::externalUpdateFinish() +void DerpVersion::externalUpdateFinish() { endResetModel(); } -QVariant OneSixVersion::data(const QModelIndex &index, int role) const +QVariant DerpVersion::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -296,7 +296,7 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const return QVariant(); } -Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const +Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; @@ -312,7 +312,7 @@ Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const // return QAbstractListModel::flags(index); } -QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const +QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); @@ -329,12 +329,12 @@ QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int } } -int OneSixVersion::rowCount(const QModelIndex &parent) const +int DerpVersion::rowCount(const QModelIndex &parent) const { return libraries.size(); } -int OneSixVersion::columnCount(const QModelIndex &parent) const +int DerpVersion::columnCount(const QModelIndex &parent) const { return 3; } diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 036f3d53..e8f744aa 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -17,9 +17,9 @@ #include #include -class OneSixLibrary; +class DerpLibrary; -class OneSixVersion : public QAbstractListModel +class DerpVersion : public QAbstractListModel { // Things required to implement the Qt list model public: @@ -33,12 +33,12 @@ public: // serialization/deserialization public: bool toOriginalFile(); - static std::shared_ptr fromJson(QJsonObject root); - static std::shared_ptr fromFile(QString filepath); + static std::shared_ptr fromJson(QJsonObject root); + static std::shared_ptr fromFile(QString filepath); public: - QList> getActiveNormalLibs(); - QList> getActiveNativeLibs(); + QList> getActiveNormalLibs(); + QList> getActiveNativeLibs(); // called when something starts/stops messing with the object // FIXME: these are ugly in every possible way. void externalUpdateStart(); @@ -81,7 +81,7 @@ public: QString mainClass; /// the list of libs - both active and inactive, native and java - QList> libraries; + QList> libraries; /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 91f86df0..29894b5a 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -234,18 +234,18 @@ void MCVListLoadTask::list_downloaded() } // Parse the type. MinecraftVersion::VersionType versionType; - // OneSix or Legacy. use filter to determine type + // Derp or Legacy. use filter to determine type if (versionTypeStr == "release") { versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; + : MinecraftVersion::Derp; is_latest = (versionID == latestReleaseID); is_snapshot = false; } else if (versionTypeStr == "snapshot") // It's a snapshot... yay { versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; + : MinecraftVersion::Derp; is_latest = (versionID == latestSnapshotID); is_snapshot = true; } -- cgit From 13ac46bc185e861f9772f85adf757890dbb80a57 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Wed, 22 Jan 2014 14:06:32 +0100 Subject: Fix launching --- logic/DerpInstance.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/logic/DerpInstance.cpp b/logic/DerpInstance.cpp index 31ed7c95..440c90a4 100644 --- a/logic/DerpInstance.cpp +++ b/logic/DerpInstance.cpp @@ -227,9 +227,7 @@ MinecraftProcess *DerpInstance::prepareForLaunch(MojangAccountPtr account) QDir natives_dir(PathCombine(instanceRoot(), "natives/")); launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "natives " + natives_dir.absolutePath() + "\n"; - launchScript += "launch onesix"; - - qDebug() << launchScript; + launchScript += "launch onesix\n"; // create the process and set its parameters MinecraftProcess *proc = new MinecraftProcess(this); -- cgit From 0a592ab99bc68ad918f74206edf2a77cc257683c Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Wed, 22 Jan 2014 15:20:48 +0100 Subject: Work towards liteloader support. Fix creating new instance --- gui/dialogs/DerpModEditDialog.cpp | 8 +-- logic/DerpInstance.cpp | 22 +++++-- logic/DerpInstance.h | 2 + logic/DerpVersion.cpp | 15 +++++ logic/DerpVersion.h | 1 + logic/DerpVersionBuilder.cpp | 18 +----- logic/LiteLoaderInstaller.cpp | 117 ++++++++++++++++++++++---------------- logic/LiteLoaderInstaller.h | 18 +++--- 8 files changed, 120 insertions(+), 81 deletions(-) diff --git a/gui/dialogs/DerpModEditDialog.cpp b/gui/dialogs/DerpModEditDialog.cpp index be17404c..aafaa2e2 100644 --- a/gui/dialogs/DerpModEditDialog.cpp +++ b/gui/dialogs/DerpModEditDialog.cpp @@ -96,7 +96,7 @@ void DerpModEditDialog::updateVersionControls() { bool customVersion = m_inst->versionIsCustom(); ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply()); + ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); ui->customEditorBtn->setEnabled(customVersion); } @@ -165,8 +165,8 @@ void DerpModEditDialog::on_forgeBtn_clicked() void DerpModEditDialog::on_liteloaderBtn_clicked() { - LiteLoaderInstaller liteloader(m_inst->intendedVersionId()); - if (!liteloader.canApply()) + LiteLoaderInstaller liteloader; + if (!liteloader.canApply(m_inst)) { QMessageBox::critical( this, tr("LiteLoader"), @@ -174,7 +174,7 @@ void DerpModEditDialog::on_liteloaderBtn_clicked() "into this version of Minecraft")); return; } - if (!liteloader.apply(m_version)) + if (!liteloader.add(m_inst)) { QMessageBox::critical( this, tr("LiteLoader"), diff --git a/logic/DerpInstance.cpp b/logic/DerpInstance.cpp index 672ba6c4..e6b92b1f 100644 --- a/logic/DerpInstance.cpp +++ b/logic/DerpInstance.cpp @@ -35,10 +35,16 @@ DerpInstance::DerpInstance(const QString &rootDir, SettingsObject *settings, QOb d->m_settings->registerSetting("IntendedVersion", ""); d->m_settings->registerSetting("ShouldUpdate", false); d->version.reset(new DerpVersion(this, this)); - reloadFullVersion(); + if (QDir(instanceRoot()).exists("version.json")) + { + reloadFullVersion(); + } + else + { + clearFullVersion(); + } } - std::shared_ptr DerpInstance::doUpdate(bool only_prepare) { return std::shared_ptr(new DerpUpdate(this, only_prepare)); @@ -280,9 +286,8 @@ bool DerpInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); setShouldUpdate(true); - auto pathOrig = PathCombine(instanceRoot(), "version.json"); - QFile::remove(pathOrig); - reloadFullVersion(); + QFile::remove(PathCombine(instanceRoot(), "version.json")); + clearFullVersion(); return true; } @@ -327,6 +332,13 @@ bool DerpInstance::reloadFullVersion(QWidget *widgetParent) return ret; } +void DerpInstance::clearFullVersion() +{ + I_D(DerpInstance); + d->version->clear(); + emit versionReloaded(); +} + std::shared_ptr DerpInstance::getFullVersion() { I_D(DerpInstance); diff --git a/logic/DerpInstance.h b/logic/DerpInstance.h index 37d3df52..46f953c0 100644 --- a/logic/DerpInstance.h +++ b/logic/DerpInstance.h @@ -53,6 +53,8 @@ public: /// reload the full version json files. return true on success! bool reloadFullVersion(QWidget *widgetParent = 0); + /// clears all version information in preparation for an update + void clearFullVersion(); /// get the current full version info std::shared_ptr getFullVersion(); /// is the current version original, or custom? diff --git a/logic/DerpVersion.cpp b/logic/DerpVersion.cpp index f13ac620..cabb2b24 100644 --- a/logic/DerpVersion.cpp +++ b/logic/DerpVersion.cpp @@ -22,6 +22,7 @@ DerpVersion::DerpVersion(DerpInstance *instance, QObject *parent) : QAbstractListModel(parent), m_instance(instance) { + clear(); } bool DerpVersion::reload(QWidget *widgetParent) @@ -29,6 +30,20 @@ bool DerpVersion::reload(QWidget *widgetParent) return DerpVersionBuilder::build(this, m_instance, widgetParent); } +void DerpVersion::clear() +{ + id.clear(); + time.clear(); + releaseTime.clear(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + libraries.clear(); +} + QList > DerpVersion::getActiveNormalLibs() { QList > output; diff --git a/logic/DerpVersion.h b/logic/DerpVersion.h index cadfa850..f2132ad8 100644 --- a/logic/DerpVersion.h +++ b/logic/DerpVersion.h @@ -39,6 +39,7 @@ public: virtual Qt::ItemFlags flags(const QModelIndex &index) const; bool reload(QWidget *widgetParent); + void clear(); public: QList> getActiveNormalLibs(); diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp index d8091f32..b0da8205 100644 --- a/logic/DerpVersionBuilder.cpp +++ b/logic/DerpVersionBuilder.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "DerpVersion.h" #include "DerpInstance.h" @@ -45,7 +46,7 @@ bool DerpVersionBuilder::build(DerpVersion *version, DerpInstance *instance, QWi bool DerpVersionBuilder::build() { - clear(); + m_version->clear(); QDir root(m_instance->instanceRoot()); QDir patches(root.absoluteFilePath("patches/")); @@ -112,20 +113,6 @@ bool DerpVersionBuilder::build() return true; } -void DerpVersionBuilder::clear() -{ - m_version->id.clear(); - m_version->time.clear(); - m_version->releaseTime.clear(); - m_version->type.clear(); - m_version->assets.clear(); - m_version->processArguments.clear(); - m_version->minecraftArguments.clear(); - m_version->minimumLauncherVersion = 0xDEADBEAF; - m_version->mainClass.clear(); - m_version->libraries.clear(); -} - void applyString(const QJsonObject &obj, const QString &key, QString &out) { if (obj.contains(key) && obj.value(key).isString()) @@ -181,6 +168,7 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) } // libraries + if (object.contains("libraries")) { auto librariesValue = object.value("libraries"); if (!librariesValue.isArray()) diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 8a120eab..8aed47e7 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -15,12 +15,18 @@ #include "LiteLoaderInstaller.h" +#include +#include + +#include "logger/QsLog.h" + #include "DerpVersion.h" #include "DerpLibrary.h" +#include "DerpInstance.h" QMap LiteLoaderInstaller::m_launcherWrapperVersionMapping; -LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion) +LiteLoaderInstaller::LiteLoaderInstaller() { if (m_launcherWrapperVersionMapping.isEmpty()) { @@ -31,71 +37,82 @@ LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion } } -bool LiteLoaderInstaller::canApply() const +bool LiteLoaderInstaller::canApply(DerpInstance *instance) const { - return m_launcherWrapperVersionMapping.contains(m_mcVersion); + return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId()); } -bool LiteLoaderInstaller::apply(std::shared_ptr to) +bool LiteLoaderInstaller::isApplied(DerpInstance *on) { - // DERPFIX + return QFile::exists(filename(on->instanceRoot())); +} - applyLaunchwrapper(to); - applyLiteLoader(to); +bool LiteLoaderInstaller::add(DerpInstance *to) +{ + if (!patchesDir(to->instanceRoot()).exists()) + { + QDir(to->instanceRoot()).mkdir("patches"); + } - to->mainClass = "net.minecraft.launchwrapper.Launch"; - if (!to->minecraftArguments.contains( - " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker")) + if (isApplied(to)) { - to->minecraftArguments.append( - " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"); + if (!remove(to)) + { + return false; + } } - return true; -} + QJsonObject obj; -void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr to) -{ - const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion]; + obj.insert("mainClass", QString("net.minecraft.launchwrapper.Launch")); + obj.insert("+minecraftArguments", QString(" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker")); + obj.insert("order", 10); + + QJsonArray libraries; - QMutableListIterator> it(to->libraries); - while (it.hasNext()) + // launchwrapper { - it.next(); - if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:")) - { - if (it.value()->version() >= intendedVersion) - { - return; - } - else - { - it.remove(); - } - } + DerpLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); + launchwrapperLib.finalize(); + QJsonObject lwLibObj = launchwrapperLib.toJson(); + lwLibObj.insert("insert", QString("beginning")); + libraries.append(lwLibObj); } - std::shared_ptr lib(new DerpLibrary( - "net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion])); - lib->finalize(); - to->libraries.prepend(lib); -} + // liteloader + { + DerpLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId()); + liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/"); + liteloaderLib.finalize(); + QJsonObject llLibObj = liteloaderLib.toJson(); + llLibObj.insert("insert", QString("beginning")); + libraries.append(llLibObj); + } -void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr to) -{ - QMutableListIterator> it(to->libraries); - while (it.hasNext()) + obj.insert("+libraries", libraries); + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) { - it.next(); - if (it.value()->rawName().startsWith("com.mumfrey:liteloader:")) - { - it.remove(); - } + QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + return false; } + file.write(QJsonDocument(obj).toJson()); + file.close(); - std::shared_ptr lib( - new DerpLibrary("com.mumfrey:liteloader:" + m_mcVersion)); - lib->setBaseUrl("http://dl.liteloader.com/versions/"); - lib->finalize(); - to->libraries.prepend(lib); + return true; +} + +bool LiteLoaderInstaller::remove(DerpInstance *from) +{ + return QFile::remove(filename(from->instanceRoot())); +} + +QString LiteLoaderInstaller::filename(const QString &root) const +{ + return patchesDir(root).absoluteFilePath(id() + ".json"); +} +QDir LiteLoaderInstaller::patchesDir(const QString &root) const +{ + return QDir(root + "/patches/"); } diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index d7b8ce77..48ef4baf 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -19,21 +19,25 @@ #include class DerpVersion; +class DerpInstance; +class QDir; +// TODO base class class LiteLoaderInstaller { public: - LiteLoaderInstaller(const QString &mcVersion); + LiteLoaderInstaller(); - bool canApply() const; + bool canApply(DerpInstance *instance) const; + bool isApplied(DerpInstance *on); - bool apply(std::shared_ptr to); + bool add(DerpInstance *to); + bool remove(DerpInstance *from); private: - QString m_mcVersion; - - void applyLaunchwrapper(std::shared_ptr to); - void applyLiteLoader(std::shared_ptr to); + virtual QString id() const { return "com.mumfrey.liteloader"; } + QString filename(const QString &root) const; + QDir patchesDir(const QString &root) const; static QMap m_launcherWrapperVersionMapping; }; -- cgit From c39d26f4453166603749826eee1a732c815047d0 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Wed, 22 Jan 2014 22:15:50 +0100 Subject: Got liteloader working. Patching more or less works --- logic/DerpRule.cpp | 2 +- logic/DerpRule.h | 2 +- logic/DerpVersionBuilder.cpp | 246 +++++++++++++++++++++++++++++++++--------- logic/DerpVersionBuilder.h | 8 ++ logic/LiteLoaderInstaller.cpp | 4 +- 5 files changed, 208 insertions(+), 54 deletions(-) diff --git a/logic/DerpRule.cpp b/logic/DerpRule.cpp index d4cf1ba3..5ed200e3 100644 --- a/logic/DerpRule.cpp +++ b/logic/DerpRule.cpp @@ -18,7 +18,7 @@ #include "DerpRule.h" -QList> rulesFromJsonV4(QJsonObject &objectWithRules) +QList> rulesFromJsonV4(const QJsonObject &objectWithRules) { QList> rules; auto rulesVal = objectWithRules.value("rules"); diff --git a/logic/DerpRule.h b/logic/DerpRule.h index 7895ea98..0254028c 100644 --- a/logic/DerpRule.h +++ b/logic/DerpRule.h @@ -27,7 +27,7 @@ enum RuleAction }; RuleAction RuleAction_fromString(QString); -QList> rulesFromJsonV4(QJsonObject &objectWithRules); +QList> rulesFromJsonV4(const QJsonObject &objectWithRules); class Rule { diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp index b0da8205..eb215cca 100644 --- a/logic/DerpVersionBuilder.cpp +++ b/logic/DerpVersionBuilder.cpp @@ -113,12 +113,23 @@ bool DerpVersionBuilder::build() return true; } -void applyString(const QJsonObject &obj, const QString &key, QString &out) +void applyString(const QJsonObject &obj, const QString &key, QString &out, const bool onlyOverride = true) { if (obj.contains(key) && obj.value(key).isString()) { out = obj.value(key).toString(); } + else if (!onlyOverride) + { + if (obj.contains("+" + key) && obj.value("+" + key).isString()) + { + out += obj.value("+" + key).toString(); + } + else if (obj.contains("-" + key) && obj.value("-" + key).isString()) + { + out.remove(obj.value("-" + key).toString()); + } + } } void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(DerpLibrary::*func)(const QString &val)) { @@ -131,7 +142,7 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) { applyString(object, "id", m_version->id); applyString(object, "mainClass", m_version->mainClass); - applyString(object, "processArguments", m_version->processArguments); + applyString(object, "processArguments", m_version->processArguments, false); { const QString toCompare = m_version->processArguments.toLower(); if (toCompare == "legacy") @@ -147,7 +158,7 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; } } - applyString(object, "minecraftArguments", m_version->minecraftArguments); + applyString(object, "minecraftArguments", m_version->minecraftArguments, false); applyString(object, "type", m_version->type); applyString(object, "releaseTime", m_version->releaseTime); applyString(object, "time", m_version->time); @@ -170,80 +181,215 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) // libraries if (object.contains("libraries")) { + m_version->libraries.clear(); auto librariesValue = object.value("libraries"); if (!librariesValue.isArray()) { QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); return false; } + auto array = librariesValue.toArray(); + for (auto libVal : array) + { + if (libVal.isObject()) + { + if (!applyLibrary(libVal.toObject(), Override)) + { + return false; + } + } + + } + } + + // +libraries + if (object.contains("+libraries")) + { + auto librariesValue = object.value("+libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } for (auto libVal : librariesValue.toArray()) { - if (!libVal.isObject()) + if (libVal.isObject()) { - continue; + applyLibrary(libVal.toObject(), Add); } - QJsonObject libObj = libVal.toObject(); + } + } - // Library name - auto nameVal = libObj.value("name"); - if (!nameVal.isString()) + // -libraries + if (object.contains("-libraries")) + { + auto librariesValue = object.value("-libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + for (auto libVal : librariesValue.toArray()) + { + if (libVal.isObject()) { - continue; + applyLibrary(libVal.toObject(), Remove); } - std::shared_ptr library(new DerpLibrary(nameVal.toString())); - applyString(libObj, "url", library, &DerpLibrary::setBaseUrl); - applyString(libObj, "MMC-hint", library, &DerpLibrary::setHint); - applyString(libObj, "MMC-absulute_url", library, &DerpLibrary::setAbsoluteUrl); - applyString(libObj, "MMC-absoluteUrl", library, &DerpLibrary::setAbsoluteUrl); + } + } + + return true; +} + +int findLibrary(QList > haystack, const QString &needle) +{ + for (int i = 0; i < haystack.size(); ++i) + { + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(haystack.at(i)->rawName()) != -1) + { + return i; + } + } + return -1; +} + +bool DerpVersionBuilder::applyLibrary(const QJsonObject &lib, const DerpVersionBuilder::Type type) +{ + // Library name + auto nameVal = lib.value("name"); + if (!nameVal.isString()) + { + return false; + } + auto name = nameVal.toString(); - auto extractVal = libObj.value("extract"); - if (extractVal.isObject()) + if (type == Remove) + { + int index = findLibrary(m_version->libraries, name); + if (index >= 0) + { + m_version->libraries.removeAt(index); + } + return true; + } + + if (type == Add && !lib.contains("insert")) + { + return false; + } + + std::shared_ptr library; + + if (lib.value("insert").toString() == "apply" && type == Add) + { + library = m_version->libraries[findLibrary(m_version->libraries, name)]; + } + else + { + library.reset(new DerpLibrary(nameVal.toString())); + } + + applyString(lib, "url", library, &DerpLibrary::setBaseUrl); + applyString(lib, "MMC-hint", library, &DerpLibrary::setHint); + applyString(lib, "MMC-absulute_url", library, &DerpLibrary::setAbsoluteUrl); + applyString(lib, "MMC-absoluteUrl", library, &DerpLibrary::setAbsoluteUrl); + + auto extractVal = lib.value("extract"); + if (extractVal.isObject()) + { + QStringList excludes; + auto extractObj = extractVal.toObject(); + auto excludesVal = extractObj.value("exclude"); + if (excludesVal.isArray()) + { + auto excludesList = excludesVal.toArray(); + for (auto excludeVal : excludesList) { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if (excludesVal.isArray()) + if (excludeVal.isString()) { - auto excludesList = excludesVal.toArray(); - for (auto excludeVal : excludesList) - { - if (excludeVal.isString()) - { - excludes.append(excludeVal.toString()); - } - } - library->extract_excludes = excludes; + excludes.append(excludeVal.toString()); } } + library->extract_excludes = excludes; + } + } - auto nativesVal = libObj.value("natives"); - if (nativesVal.isObject()) + auto nativesVal = lib.value("natives"); + if (nativesVal.isObject()) + { + library->setIsNative(); + auto nativesObj = nativesVal.toObject(); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + auto osType = OpSys_fromString(it.key()); + if (osType == Os_Other) { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - auto osType = OpSys_fromString(it.key()); - if (osType == Os_Other) - { - continue; - } - if (!it.value().isString()) - { - continue; - } - library->addNative(osType, it.value().toString()); - } + continue; + } + if (!it.value().isString()) + { + continue; } + library->addNative(osType, it.value().toString()); + } + } - library->setRules(rulesFromJsonV4(libObj)); - library->finalize(); + if (lib.contains("rules")) + { + library->setRules(rulesFromJsonV4(lib)); + } + library->finalize(); + if (type == Override) + { + qDebug() << "appending" << library->rawName(); + m_version->libraries.append(library); + } + else if (lib.value("insert").toString() != "apply") + { + if (lib.value("insert").toString() == "append") + { m_version->libraries.append(library); } + else if (lib.value("insert").toString() == "prepend") + { + m_version->libraries.prepend(library); + } + else if (lib.value("insert").isObject()) + { + QJsonObject insertObj = lib.value("insert").toObject(); + if (insertObj.isEmpty()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("'insert' object empty")); + return false; + } + const QString key = insertObj.keys().first(); + const QString value = insertObj.value(key).toString(); + const int index = findLibrary(m_version->libraries, value); + if (index >= 0) + { + if (key == "before") + { + m_version->libraries.insert(index, library); + } + else if (key == "after") + { + m_version->libraries.insert(index + 1, library); + } + else + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); + return false; + } + } + } + else + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); + return false; + } } - return true; } diff --git a/logic/DerpVersionBuilder.h b/logic/DerpVersionBuilder.h index 739065db..5354cb0e 100644 --- a/logic/DerpVersionBuilder.h +++ b/logic/DerpVersionBuilder.h @@ -34,10 +34,18 @@ private: DerpInstance *m_instance; QWidget *m_widgetParent; + enum Type + { + Override, + Add, + Remove + }; + bool build(); void clear(); bool apply(const QJsonObject &object); + bool applyLibrary(const QJsonObject &lib, const Type type); bool read(const QFileInfo &fileInfo, QJsonObject *out); }; diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 8aed47e7..d582f03a 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -75,7 +75,7 @@ bool LiteLoaderInstaller::add(DerpInstance *to) DerpLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); launchwrapperLib.finalize(); QJsonObject lwLibObj = launchwrapperLib.toJson(); - lwLibObj.insert("insert", QString("beginning")); + lwLibObj.insert("insert", QString("prepend")); libraries.append(lwLibObj); } @@ -85,7 +85,7 @@ bool LiteLoaderInstaller::add(DerpInstance *to) liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/"); liteloaderLib.finalize(); QJsonObject llLibObj = liteloaderLib.toJson(); - llLibObj.insert("insert", QString("beginning")); + llLibObj.insert("insert", QString("prepend")); libraries.append(llLibObj); } -- cgit From 156bc8f27656c115bf1b023cd4ebc5f629df3887 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 23 Jan 2014 21:31:41 +0100 Subject: Forge works now too, and so does forge+liteloader --- CMakeLists.txt | 2 ++ gui/dialogs/DerpModEditDialog.cpp | 16 +++++++--- logic/BaseInstaller.cpp | 66 +++++++++++++++++++++++++++++++++++++++ logic/BaseInstaller.h | 39 +++++++++++++++++++++++ logic/DerpFTBInstance.cpp | 2 +- logic/DerpVersion.cpp | 47 ++++++++++++++++++---------- logic/DerpVersion.h | 4 +++ logic/DerpVersionBuilder.cpp | 37 ++++++++++++++++++++-- logic/DerpVersionBuilder.h | 2 ++ logic/ForgeInstaller.cpp | 48 +++++++++++++++++++++------- logic/ForgeInstaller.h | 9 ++++-- logic/LiteLoaderInstaller.cpp | 32 ++----------------- logic/LiteLoaderInstaller.h | 22 +++++-------- 13 files changed, 245 insertions(+), 81 deletions(-) create mode 100644 logic/BaseInstaller.cpp create mode 100644 logic/BaseInstaller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 43de626b..c5339a30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -416,6 +416,8 @@ logic/DerpRule.h logic/DerpRule.cpp logic/OpSys.h logic/OpSys.cpp +logic/BaseInstaller.h +logic/BaseInstaller.cpp logic/ForgeInstaller.h logic/ForgeInstaller.cpp logic/LiteLoaderInstaller.h diff --git a/gui/dialogs/DerpModEditDialog.cpp b/gui/dialogs/DerpModEditDialog.cpp index aafaa2e2..216aa59d 100644 --- a/gui/dialogs/DerpModEditDialog.cpp +++ b/gui/dialogs/DerpModEditDialog.cpp @@ -55,7 +55,6 @@ DerpModEditDialog::DerpModEditDialog(DerpInstance *inst, QWidget *parent) main_model->setSourceModel(m_version.get()); ui->libraryTreeView->setModel(main_model); ui->libraryTreeView->installEventFilter(this); - ui->mainClassEdit->setText(m_version->mainClass); updateVersionControls(); } else @@ -98,6 +97,7 @@ void DerpModEditDialog::updateVersionControls() ui->forgeBtn->setEnabled(true); ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); ui->customEditorBtn->setEnabled(customVersion); + ui->mainClassEdit->setText(m_version->mainClass); } void DerpModEditDialog::disableVersionControls() @@ -105,6 +105,7 @@ void DerpModEditDialog::disableVersionControls() ui->forgeBtn->setEnabled(false); ui->liteloaderBtn->setEnabled(false); ui->customEditorBtn->setEnabled(false); + ui->mainClassEdit->setText(""); } void DerpModEditDialog::on_customEditorBtn_clicked() @@ -140,9 +141,9 @@ void DerpModEditDialog::on_forgeBtn_clicked() // install QString forgePath = entry->getFullPath(); ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.apply(m_version)) + if (!forge.add(m_inst)) { - // failure notice + QLOG_ERROR() << "Failure installing forge"; } } else @@ -155,12 +156,13 @@ void DerpModEditDialog::on_forgeBtn_clicked() // install QString forgePath = entry->getFullPath(); ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.apply(m_version)) + if (!forge.add(m_inst)) { - // failure notice + QLOG_ERROR() << "Failure installing forge"; } } } + m_inst->reloadFullVersion(this); } void DerpModEditDialog::on_liteloaderBtn_clicked() @@ -180,6 +182,10 @@ void DerpModEditDialog::on_liteloaderBtn_clicked() this, tr("LiteLoader"), tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); } + else + { + m_inst->reloadFullVersion(this); + } } bool DerpModEditDialog::loaderListFilter(QKeyEvent *keyEvent) diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp new file mode 100644 index 00000000..74a8e909 --- /dev/null +++ b/logic/BaseInstaller.cpp @@ -0,0 +1,66 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BaseInstaller.h" + +#include + +#include "DerpVersion.h" +#include "DerpLibrary.h" +#include "DerpInstance.h" + +#include "cmdutils.h" + +BaseInstaller::BaseInstaller() +{ + +} + +bool BaseInstaller::isApplied(DerpInstance *on) +{ + return QFile::exists(filename(on->instanceRoot())); +} + +bool BaseInstaller::add(DerpInstance *to) +{ + if (!patchesDir(to->instanceRoot()).exists()) + { + QDir(to->instanceRoot()).mkdir("patches"); + } + + if (isApplied(to)) + { + if (!remove(to)) + { + return false; + } + } + + return true; +} + +bool BaseInstaller::remove(DerpInstance *from) +{ + return QFile::remove(filename(from->instanceRoot())); +} + +QString BaseInstaller::filename(const QString &root) const +{ + return patchesDir(root).absoluteFilePath(id() + ".json"); +} +QDir BaseInstaller::patchesDir(const QString &root) const +{ + return QDir(root + "/patches/"); +} diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h new file mode 100644 index 00000000..08f78bfc --- /dev/null +++ b/logic/BaseInstaller.h @@ -0,0 +1,39 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class DerpInstance; +class QDir; +class QString; + +class BaseInstaller +{ +public: + BaseInstaller(); + + virtual bool canApply(DerpInstance *instance) const { return true; } + bool isApplied(DerpInstance *on); + + virtual bool add(DerpInstance *to); + virtual bool remove(DerpInstance *from); + +protected: + virtual QString id() const = 0; + QString filename(const QString &root) const; + QDir patchesDir(const QString &root) const; +}; diff --git a/logic/DerpFTBInstance.cpp b/logic/DerpFTBInstance.cpp index f9aeeca0..9b687461 100644 --- a/logic/DerpFTBInstance.cpp +++ b/logic/DerpFTBInstance.cpp @@ -61,7 +61,7 @@ slots: return; } auto version = instance->getFullVersion(); - if (!forge.apply(version)) + if (!forge.add(instance)) { emitFailed(tr("Couldn't install Forge")); return; diff --git a/logic/DerpVersion.cpp b/logic/DerpVersion.cpp index cabb2b24..f4a8dd97 100644 --- a/logic/DerpVersion.cpp +++ b/logic/DerpVersion.cpp @@ -44,6 +44,26 @@ void DerpVersion::clear() libraries.clear(); } +void DerpVersion::dump() const +{ + qDebug().nospace() << "DerpVersion(" + << "\n\tid=" << id + << "\n\ttime=" << time + << "\n\treleaseTime=" << releaseTime + << "\n\ttype=" << type + << "\n\tassets=" << assets + << "\n\tprocessArguments=" << processArguments + << "\n\tminecraftArguments=" << minecraftArguments + << "\n\tminimumLauncherVersion=" << minimumLauncherVersion + << "\n\tmainClass=" << mainClass + << "\n\tlibraries="; + for (auto lib : libraries) + { + qDebug().nospace() << "\n\t\t" << lib.get(); + } + qDebug().nospace() << "\n)"; +} + QList > DerpVersion::getActiveNormalLibs() { QList > output; @@ -70,6 +90,16 @@ QList > DerpVersion::getActiveNativeLibs() return output; } +std::shared_ptr DerpVersion::fromJson(const QJsonObject &obj) +{ + std::shared_ptr version(new DerpVersion(0)); + if (DerpVersionBuilder::read(version.get(), obj)) + { + return version; + } + return 0; +} + QVariant DerpVersion::data(const QModelIndex &index, int role) const { if (!index.isValid()) @@ -143,22 +173,7 @@ int DerpVersion::columnCount(const QModelIndex &parent) const QDebug operator<<(QDebug &dbg, const DerpVersion *version) { - dbg.nospace() << "DerpVersion(" - << "\n\tid=" << version->id - << "\n\ttime=" << version->time - << "\n\treleaseTime=" << version->releaseTime - << "\n\ttype=" << version->type - << "\n\tassets=" << version->assets - << "\n\tprocessArguments=" << version->processArguments - << "\n\tminecraftArguments=" << version->minecraftArguments - << "\n\tminimumLauncherVersion=" << version->minimumLauncherVersion - << "\n\tmainClass=" << version->mainClass - << "\n\tlibraries="; - for (auto lib : version->libraries) - { - dbg.nospace() << "\n\t\t" << lib.get(); - } - dbg.nospace() << "\n)"; + version->dump(); return dbg.maybeSpace(); } QDebug operator<<(QDebug &dbg, const DerpLibrary *library) diff --git a/logic/DerpVersion.h b/logic/DerpVersion.h index f2132ad8..4481d146 100644 --- a/logic/DerpVersion.h +++ b/logic/DerpVersion.h @@ -41,10 +41,14 @@ public: bool reload(QWidget *widgetParent); void clear(); + void dump() const; + public: QList> getActiveNormalLibs(); QList> getActiveNativeLibs(); + static std::shared_ptr fromJson(const QJsonObject &obj); + // data members public: /// the ID - determines which jar to use! ACTUALLY IMPORTANT! diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp index eb215cca..20f43404 100644 --- a/logic/DerpVersionBuilder.cpp +++ b/logic/DerpVersionBuilder.cpp @@ -29,6 +29,7 @@ #include "DerpVersion.h" #include "DerpInstance.h" #include "DerpRule.h" +#include "logger/QsLog.h" DerpVersionBuilder::DerpVersionBuilder() { @@ -44,6 +45,15 @@ bool DerpVersionBuilder::build(DerpVersion *version, DerpInstance *instance, QWi return builder.build(); } +bool DerpVersionBuilder::read(DerpVersion *version, const QJsonObject &obj) +{ + DerpVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.m_widgetParent = 0; + return builder.read(obj); +} + bool DerpVersionBuilder::build() { m_version->clear(); @@ -55,6 +65,7 @@ bool DerpVersionBuilder::build() // version.json { + QLOG_INFO() << "Reading version.json"; QJsonObject obj; if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) { @@ -73,6 +84,7 @@ bool DerpVersionBuilder::build() QMap objects; for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { + QLOG_INFO() << "Reading" << info.fileName(); QJsonObject obj; if (!read(info, &obj)) { @@ -87,6 +99,7 @@ bool DerpVersionBuilder::build() } for (auto object : objects.values()) { + qDebug() << "Applying object with order" << objects.key(object); if (!apply(object)) { return false; @@ -98,6 +111,7 @@ bool DerpVersionBuilder::build() { if (QFile::exists(root.absoluteFilePath("custom.json"))) { + QLOG_INFO() << "Reading custom.json"; QJsonObject obj; if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) { @@ -113,6 +127,13 @@ bool DerpVersionBuilder::build() return true; } +bool DerpVersionBuilder::read(const QJsonObject &obj) +{ + m_version->clear(); + + return apply(obj); +} + void applyString(const QJsonObject &obj, const QString &key, QString &out, const bool onlyOverride = true) { if (obj.contains(key) && obj.value(key).isString()) @@ -142,7 +163,9 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) { applyString(object, "id", m_version->id); applyString(object, "mainClass", m_version->mainClass); + applyString(object, "minecraftArguments", m_version->minecraftArguments, false); applyString(object, "processArguments", m_version->processArguments, false); + if (m_version->minecraftArguments.isEmpty()) { const QString toCompare = m_version->processArguments.toLower(); if (toCompare == "legacy") @@ -158,7 +181,6 @@ bool DerpVersionBuilder::apply(const QJsonObject &object) m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; } } - applyString(object, "minecraftArguments", m_version->minecraftArguments, false); applyString(object, "type", m_version->type); applyString(object, "releaseTime", m_version->releaseTime); applyString(object, "time", m_version->time); @@ -282,6 +304,18 @@ bool DerpVersionBuilder::applyLibrary(const QJsonObject &lib, const DerpVersionB std::shared_ptr library; + if (lib.value("insert").toString() != "apply" && type == Add) + { + QMutableListIterator > it(m_version->libraries); + while (it.hasNext()) + { + if (it.next()->rawName() == name) + { + it.remove(); + } + } + } + if (lib.value("insert").toString() == "apply" && type == Add) { library = m_version->libraries[findLibrary(m_version->libraries, name)]; @@ -343,7 +377,6 @@ bool DerpVersionBuilder::applyLibrary(const QJsonObject &lib, const DerpVersionB library->finalize(); if (type == Override) { - qDebug() << "appending" << library->rawName(); m_version->libraries.append(library); } else if (lib.value("insert").toString() != "apply") diff --git a/logic/DerpVersionBuilder.h b/logic/DerpVersionBuilder.h index 5354cb0e..b94cd8e9 100644 --- a/logic/DerpVersionBuilder.h +++ b/logic/DerpVersionBuilder.h @@ -28,6 +28,7 @@ class DerpVersionBuilder DerpVersionBuilder(); public: static bool build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent); + static bool read(DerpVersion *version, const QJsonObject &obj); private: DerpVersion *m_version; @@ -42,6 +43,7 @@ private: }; bool build(); + bool read(const QJsonObject &obj); void clear(); bool apply(const QJsonObject &object); diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 29735a7d..35663ddd 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -22,13 +22,13 @@ #include #include #include "MultiMC.h" +#include "DerpInstance.h" #include +#include #include #include -// DERPFIX - ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) { std::shared_ptr newVersion; @@ -64,7 +64,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) // read the forge version info { - // DERPFIX newVersion = DerpVersion::fromJson(versionInfoVal.toObject()); + newVersion = DerpVersion::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -109,12 +109,22 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); } -bool ForgeInstaller::apply(std::shared_ptr to) +bool ForgeInstaller::add(DerpInstance *to) { + if (!BaseInstaller::add(to)) + { + return false; + } + + QJsonObject obj; + obj.insert("order", 5); + if (!m_forge_version) return false; int sliding_insert_window = 0; { + QJsonArray librariesPlus; + // for each library in the version we are adding (except for the blacklisted) QSet blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; for (auto lib : m_forge_version->libraries) @@ -133,27 +143,43 @@ bool ForgeInstaller::apply(std::shared_ptr to) if (blacklist.contains(libName)) continue; - // find an entry that matches this one + QJsonObject libObj = lib->toJson(); + bool found = false; - for (auto tolib : to->libraries) + // find an entry that matches this one + for (auto tolib : to->getFullVersion()->libraries) { if (tolib->name() != libName) continue; found = true; // replace lib - tolib = lib; + libObj.insert("insert", QString("apply")); break; } if (!found) { // add lib - to->libraries.insert(sliding_insert_window, lib); + QJsonObject insertObj; + insertObj.insert("before", to->getFullVersion()->libraries.at(sliding_insert_window)->rawName()); + libObj.insert("insert", insertObj); sliding_insert_window++; } + librariesPlus.append(libObj); } - to->mainClass = m_forge_version->mainClass; - to->minecraftArguments = m_forge_version->minecraftArguments; - to->processArguments = m_forge_version->processArguments; + obj.insert("+libraries", librariesPlus); + obj.insert("mainClass", m_forge_version->mainClass); + obj.insert("minecraftArguments", m_forge_version->minecraftArguments); + obj.insert("processArguments", m_forge_version->processArguments); + } + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + return false; } + file.write(QJsonDocument(obj).toJson()); + file.close(); + return true; } diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h index 65cad87c..6490b5bf 100644 --- a/logic/ForgeInstaller.h +++ b/logic/ForgeInstaller.h @@ -14,17 +14,22 @@ */ #pragma once + +#include "BaseInstaller.h" + #include #include class DerpVersion; -class ForgeInstaller +class ForgeInstaller : public BaseInstaller { public: ForgeInstaller(QString filename, QString universal_url); - bool apply(std::shared_ptr to); + bool add(DerpInstance *to) override; + + QString id() const override { return "net.minecraftforge"; } private: // the version, read from the installer diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index d582f03a..71c7011f 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -27,6 +27,7 @@ QMap LiteLoaderInstaller::m_launcherWrapperVersionMapping; LiteLoaderInstaller::LiteLoaderInstaller() + : BaseInstaller() { if (m_launcherWrapperVersionMapping.isEmpty()) { @@ -42,24 +43,11 @@ bool LiteLoaderInstaller::canApply(DerpInstance *instance) const return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId()); } -bool LiteLoaderInstaller::isApplied(DerpInstance *on) -{ - return QFile::exists(filename(on->instanceRoot())); -} - bool LiteLoaderInstaller::add(DerpInstance *to) { - if (!patchesDir(to->instanceRoot()).exists()) - { - QDir(to->instanceRoot()).mkdir("patches"); - } - - if (isApplied(to)) + if (!BaseInstaller::add(to)) { - if (!remove(to)) - { - return false; - } + return false; } QJsonObject obj; @@ -102,17 +90,3 @@ bool LiteLoaderInstaller::add(DerpInstance *to) return true; } - -bool LiteLoaderInstaller::remove(DerpInstance *from) -{ - return QFile::remove(filename(from->instanceRoot())); -} - -QString LiteLoaderInstaller::filename(const QString &root) const -{ - return patchesDir(root).absoluteFilePath(id() + ".json"); -} -QDir LiteLoaderInstaller::patchesDir(const QString &root) const -{ - return QDir(root + "/patches/"); -} diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index 48ef4baf..c128ee22 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -14,30 +14,22 @@ */ #pragma once + +#include "BaseInstaller.h" + #include #include -#include -class DerpVersion; -class DerpInstance; -class QDir; - -// TODO base class -class LiteLoaderInstaller +class LiteLoaderInstaller : public BaseInstaller { public: LiteLoaderInstaller(); - bool canApply(DerpInstance *instance) const; - bool isApplied(DerpInstance *on); - - bool add(DerpInstance *to); - bool remove(DerpInstance *from); + bool canApply(DerpInstance *instance) const override; + bool add(DerpInstance *to) override; private: - virtual QString id() const { return "com.mumfrey.liteloader"; } - QString filename(const QString &root) const; - QDir patchesDir(const QString &root) const; + virtual QString id() const override { return "com.mumfrey.liteloader"; } static QMap m_launcherWrapperVersionMapping; }; -- cgit From 7d5787025aa5cebf6778f71e79f248f10b8541c9 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Fri, 24 Jan 2014 18:12:02 +0100 Subject: Change naming from Derp -> OneSix until the new instance type supports legacy --- CMakeLists.txt | 38 +-- gui/MainWindow.cpp | 4 +- gui/dialogs/DerpModEditDialog.cpp | 306 ------------------------ gui/dialogs/DerpModEditDialog.h | 67 ------ gui/dialogs/DerpModEditDialog.ui | 317 ------------------------- gui/dialogs/OneSixModEditDialog.cpp | 306 ++++++++++++++++++++++++ gui/dialogs/OneSixModEditDialog.h | 67 ++++++ gui/dialogs/OneSixModEditDialog.ui | 317 +++++++++++++++++++++++++ logic/BaseInstaller.cpp | 12 +- logic/BaseInstaller.h | 10 +- logic/BaseInstance.h | 2 +- logic/DerpFTBInstance.cpp | 123 ---------- logic/DerpFTBInstance.h | 22 -- logic/DerpInstance.cpp | 388 ------------------------------ logic/DerpInstance.h | 75 ------ logic/DerpInstance_p.h | 27 --- logic/DerpLibrary.cpp | 268 --------------------- logic/DerpLibrary.h | 132 ----------- logic/DerpRule.cpp | 89 ------- logic/DerpRule.h | 98 -------- logic/DerpUpdate.cpp | 372 ----------------------------- logic/DerpUpdate.h | 63 ----- logic/DerpVersion.cpp | 194 --------------- logic/DerpVersion.h | 115 --------- logic/DerpVersionBuilder.cpp | 446 ----------------------------------- logic/DerpVersionBuilder.h | 53 ----- logic/ForgeInstaller.cpp | 14 +- logic/ForgeInstaller.h | 6 +- logic/InstanceFactory.cpp | 32 +-- logic/LegacyInstance.h | 2 +- logic/LiteLoaderInstaller.cpp | 14 +- logic/LiteLoaderInstaller.h | 4 +- logic/MinecraftVersion.h | 6 +- logic/NostalgiaInstance.cpp | 2 +- logic/NostalgiaInstance.h | 4 +- logic/OneSixFTBInstance.cpp | 123 ++++++++++ logic/OneSixFTBInstance.h | 22 ++ logic/OneSixInstance.cpp | 388 ++++++++++++++++++++++++++++++ logic/OneSixInstance.h | 75 ++++++ logic/OneSixInstance_p.h | 27 +++ logic/OneSixLibrary.cpp | 268 +++++++++++++++++++++ logic/OneSixLibrary.h | 132 +++++++++++ logic/OneSixRule.cpp | 89 +++++++ logic/OneSixRule.h | 98 ++++++++ logic/OneSixUpdate.cpp | 372 +++++++++++++++++++++++++++++ logic/OneSixUpdate.h | 63 +++++ logic/OneSixVersion.cpp | 292 ++++++----------------- logic/OneSixVersion.h | 45 ++-- logic/OneSixVersionBuilder.cpp | 446 +++++++++++++++++++++++++++++++++++ logic/OneSixVersionBuilder.h | 53 +++++ logic/lists/MinecraftVersionList.cpp | 6 +- 51 files changed, 3024 insertions(+), 3470 deletions(-) delete mode 100644 gui/dialogs/DerpModEditDialog.cpp delete mode 100644 gui/dialogs/DerpModEditDialog.h delete mode 100644 gui/dialogs/DerpModEditDialog.ui create mode 100644 gui/dialogs/OneSixModEditDialog.cpp create mode 100644 gui/dialogs/OneSixModEditDialog.h create mode 100644 gui/dialogs/OneSixModEditDialog.ui delete mode 100644 logic/DerpFTBInstance.cpp delete mode 100644 logic/DerpFTBInstance.h delete mode 100644 logic/DerpInstance.cpp delete mode 100644 logic/DerpInstance.h delete mode 100644 logic/DerpInstance_p.h delete mode 100644 logic/DerpLibrary.cpp delete mode 100644 logic/DerpLibrary.h delete mode 100644 logic/DerpRule.cpp delete mode 100644 logic/DerpRule.h delete mode 100644 logic/DerpUpdate.cpp delete mode 100644 logic/DerpUpdate.h delete mode 100644 logic/DerpVersion.cpp delete mode 100644 logic/DerpVersion.h delete mode 100644 logic/DerpVersionBuilder.cpp delete mode 100644 logic/DerpVersionBuilder.h create mode 100644 logic/OneSixFTBInstance.cpp create mode 100644 logic/OneSixFTBInstance.h create mode 100644 logic/OneSixInstance.cpp create mode 100644 logic/OneSixInstance.h create mode 100644 logic/OneSixInstance_p.h create mode 100644 logic/OneSixLibrary.cpp create mode 100644 logic/OneSixLibrary.h create mode 100644 logic/OneSixRule.cpp create mode 100644 logic/OneSixRule.h create mode 100644 logic/OneSixUpdate.cpp create mode 100644 logic/OneSixUpdate.h create mode 100644 logic/OneSixVersionBuilder.cpp create mode 100644 logic/OneSixVersionBuilder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c5339a30..6218ee7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,8 +297,8 @@ gui/dialogs/IconPickerDialog.h gui/dialogs/IconPickerDialog.cpp gui/dialogs/LegacyModEditDialog.h gui/dialogs/LegacyModEditDialog.cpp -gui/dialogs/DerpModEditDialog.h -gui/dialogs/DerpModEditDialog.cpp +gui/dialogs/OneSixModEditDialog.h +gui/dialogs/OneSixModEditDialog.cpp gui/dialogs/ModEditDialogCommon.h gui/dialogs/ModEditDialogCommon.cpp gui/dialogs/EditNotesDialog.h @@ -405,15 +405,15 @@ logic/LegacyUpdate.cpp logic/LegacyForge.h logic/LegacyForge.cpp -# Derp instances -logic/DerpUpdate.h -logic/DerpUpdate.cpp -logic/DerpVersion.h -logic/DerpVersion.cpp -logic/DerpLibrary.h -logic/DerpLibrary.cpp -logic/DerpRule.h -logic/DerpRule.cpp +# OneSix instances +logic/OneSixUpdate.h +logic/OneSixUpdate.cpp +logic/OneSixVersion.h +logic/OneSixVersion.cpp +logic/OneSixLibrary.h +logic/OneSixLibrary.cpp +logic/OneSixRule.h +logic/OneSixRule.cpp logic/OpSys.h logic/OpSys.cpp logic/BaseInstaller.h @@ -422,19 +422,19 @@ logic/ForgeInstaller.h logic/ForgeInstaller.cpp logic/LiteLoaderInstaller.h logic/LiteLoaderInstaller.cpp -logic/DerpInstance.h -logic/DerpInstance.cpp -logic/DerpInstance_p.h -logic/DerpVersionBuilder.h -logic/DerpVersionBuilder.cpp +logic/OneSixInstance.h +logic/OneSixInstance.cpp +logic/OneSixInstance_p.h +logic/OneSixVersionBuilder.h +logic/OneSixVersionBuilder.cpp # Nostalgia logic/NostalgiaInstance.h logic/NostalgiaInstance.cpp # FTB -logic/DerpFTBInstance.h -logic/DerpFTBInstance.cpp +logic/OneSixFTBInstance.h +logic/OneSixFTBInstance.cpp logic/LegacyFTBInstance.h logic/LegacyFTBInstance.cpp @@ -510,7 +510,7 @@ gui/dialogs/InstanceSettings.ui gui/dialogs/ProgressDialog.ui gui/dialogs/IconPickerDialog.ui gui/dialogs/LegacyModEditDialog.ui -gui/dialogs/DerpModEditDialog.ui +gui/dialogs/OneSixModEditDialog.ui gui/dialogs/EditNotesDialog.ui gui/dialogs/AccountListDialog.ui gui/dialogs/AccountSelectDialog.ui diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index ba394a94..ee9c3fad 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -84,7 +84,7 @@ #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" -#include "logic/DerpUpdate.h" +#include "logic/OneSixUpdate.h" #include "logic/JavaUtils.h" #include "logic/NagUtils.h" #include "logic/SkinUtils.h" @@ -1257,7 +1257,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() VersionSelectDialog vselect(m_selectedInstance->versionList().get(), tr("Change Minecraft version"), this); - vselect.setFilter(1, "Derp"); + vselect.setFilter(1, "OneSix"); if(!vselect.exec() || !vselect.selectedVersion()) return; diff --git a/gui/dialogs/DerpModEditDialog.cpp b/gui/dialogs/DerpModEditDialog.cpp deleted file mode 100644 index 216aa59d..00000000 --- a/gui/dialogs/DerpModEditDialog.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MultiMC.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "DerpModEditDialog.h" -#include "ModEditDialogCommon.h" -#include "ui_DerpModEditDialog.h" - -#include "gui/Platform.h" -#include "gui/dialogs/CustomMessageBox.h" -#include "gui/dialogs/VersionSelectDialog.h" - -#include "gui/dialogs/ProgressDialog.h" - -#include "logic/ModList.h" -#include "logic/DerpVersion.h" -#include "logic/EnabledItemFilter.h" -#include "logic/lists/ForgeVersionList.h" -#include "logic/ForgeInstaller.h" -#include "logic/LiteLoaderInstaller.h" - -DerpModEditDialog::DerpModEditDialog(DerpInstance *inst, QWidget *parent) - : QDialog(parent), ui(new Ui::DerpModEditDialog), m_inst(inst) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - // libraries! - - m_version = m_inst->getFullVersion(); - if (m_version) - { - main_model = new EnabledItemFilter(this); - main_model->setActive(true); - main_model->setSourceModel(m_version.get()); - ui->libraryTreeView->setModel(main_model); - ui->libraryTreeView->installEventFilter(this); - updateVersionControls(); - } - else - { - disableVersionControls(); - } - // Loader mods - { - ensureFolderPathExists(m_inst->loaderModsDir()); - m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.get()); - ui->loaderModTreeView->installEventFilter(this); - m_mods->startWatching(); - auto smodel = ui->loaderModTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(loaderCurrent(QModelIndex, QModelIndex))); - } - // resource packs - { - ensureFolderPathExists(m_inst->resourcePacksDir()); - m_resourcepacks = m_inst->resourcePackList(); - ui->resPackTreeView->setModel(m_resourcepacks.get()); - ui->resPackTreeView->installEventFilter(this); - m_resourcepacks->startWatching(); - } - - connect(m_inst, &DerpInstance::versionReloaded, this, &DerpModEditDialog::updateVersionControls); -} - -DerpModEditDialog::~DerpModEditDialog() -{ - m_mods->stopWatching(); - m_resourcepacks->stopWatching(); - delete ui; -} - -void DerpModEditDialog::updateVersionControls() -{ - bool customVersion = m_inst->versionIsCustom(); - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); - ui->customEditorBtn->setEnabled(customVersion); - ui->mainClassEdit->setText(m_version->mainClass); -} - -void DerpModEditDialog::disableVersionControls() -{ - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->customEditorBtn->setEnabled(false); - ui->mainClassEdit->setText(""); -} - -void DerpModEditDialog::on_customEditorBtn_clicked() -{ - if (QDir(m_inst->instanceRoot()).exists("custom.json")) - { - if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json")) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings")); - } - } -} - -void DerpModEditDialog::on_forgeBtn_clicked() -{ - VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); - vselect.setFilter(1, m_inst->currentVersionId()); - if (vselect.exec() && vselect.selectedVersion()) - { - ForgeVersionPtr forgeVersion = - std::dynamic_pointer_cast(vselect.selectedVersion()); - if (!forgeVersion) - return; - auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) - { - NetJob *fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - ProgressDialog dlg(this); - dlg.exec(fjob); - if (dlg.result() == QDialog::Accepted) - { - // install - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.add(m_inst)) - { - QLOG_ERROR() << "Failure installing forge"; - } - } - else - { - // failed to download forge :/ - } - } - else - { - // install - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!forge.add(m_inst)) - { - QLOG_ERROR() << "Failure installing forge"; - } - } - } - m_inst->reloadFullVersion(this); -} - -void DerpModEditDialog::on_liteloaderBtn_clicked() -{ - LiteLoaderInstaller liteloader; - if (!liteloader.canApply(m_inst)) - { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("There is no information available on how to install LiteLoader " - "into this version of Minecraft")); - return; - } - if (!liteloader.add(m_inst)) - { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); - } - else - { - m_inst->reloadFullVersion(this); - } -} - -bool DerpModEditDialog::loaderListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); -} - -bool DerpModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmResPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addResPackBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->resPackTreeView, keyEvent); -} - -bool DerpModEditDialog::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast(ev); - if (obj == ui->loaderModTreeView) - return loaderListFilter(keyEvent); - if (obj == ui->resPackTreeView) - return resourcePackListFilter(keyEvent); - return QDialog::eventFilter(obj, ev); -} - -void DerpModEditDialog::on_buttonBox_rejected() -{ - close(); -} - -void DerpModEditDialog::on_addModBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); - for (auto filename : fileNames) - { - m_mods->stopWatching(); - m_mods->installMod(QFileInfo(filename)); - m_mods->startWatching(); - } -} -void DerpModEditDialog::on_rmModBtn_clicked() -{ - int first, last; - auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_mods->stopWatching(); - m_mods->deleteMods(first, last); - m_mods->startWatching(); -} -void DerpModEditDialog::on_viewModBtn_clicked() -{ - openDirInDefaultProgram(m_inst->loaderModsDir(), true); -} - -void DerpModEditDialog::on_addResPackBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); - for (auto filename : fileNames) - { - m_resourcepacks->stopWatching(); - m_resourcepacks->installMod(QFileInfo(filename)); - m_resourcepacks->startWatching(); - } -} -void DerpModEditDialog::on_rmResPackBtn_clicked() -{ - int first, last; - auto list = ui->resPackTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_resourcepacks->stopWatching(); - m_resourcepacks->deleteMods(first, last); - m_resourcepacks->startWatching(); -} -void DerpModEditDialog::on_viewResPackBtn_clicked() -{ - openDirInDefaultProgram(m_inst->resourcePacksDir(), true); -} - -void DerpModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - int row = current.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); -} diff --git a/gui/dialogs/DerpModEditDialog.h b/gui/dialogs/DerpModEditDialog.h deleted file mode 100644 index e9e61c65..00000000 --- a/gui/dialogs/DerpModEditDialog.h +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include - -#include - -class EnabledItemFilter; -namespace Ui -{ -class DerpModEditDialog; -} - -class DerpModEditDialog : public QDialog -{ - Q_OBJECT - -public: - explicit DerpModEditDialog(DerpInstance *inst, QWidget *parent = 0); - virtual ~DerpModEditDialog(); - -private -slots: - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - - void on_addResPackBtn_clicked(); - void on_rmResPackBtn_clicked(); - void on_viewResPackBtn_clicked(); - // Questionable: SettingsDialog doesn't need this for some reason? - void on_buttonBox_rejected(); - void on_forgeBtn_clicked(); - void on_liteloaderBtn_clicked(); - void on_customEditorBtn_clicked(); - void updateVersionControls(); - void disableVersionControls(); - -protected: - bool eventFilter(QObject *obj, QEvent *ev); - bool loaderListFilter(QKeyEvent *ev); - bool resourcePackListFilter(QKeyEvent *ev); - -private: - Ui::DerpModEditDialog *ui; - std::shared_ptr m_version; - std::shared_ptr m_mods; - std::shared_ptr m_resourcepacks; - EnabledItemFilter *main_model; - DerpInstance *m_inst; -public -slots: - void loaderCurrent(QModelIndex current, QModelIndex previous); -}; diff --git a/gui/dialogs/DerpModEditDialog.ui b/gui/dialogs/DerpModEditDialog.ui deleted file mode 100644 index 7aaf1564..00000000 --- a/gui/dialogs/DerpModEditDialog.ui +++ /dev/null @@ -1,317 +0,0 @@ - - - DerpModEditDialog - - - - 0 - 0 - 555 - 463 - - - - Manage Mods - - - - - - true - - - - 0 - 0 - - - - 1 - - - - Version - - - - - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - - - - - - - Main Class: - - - - - - - false - - - - - - - - - - - - - Replace any current custom version with Minecraft Forge - - - Install Forge - - - - - - - Install LiteLoader - - - - - - - QFrame::Sunken - - - Qt::Horizontal - - - - - - - false - - - Add new libraries - - - &Add - - - - - - - false - - - Remove selected libraries - - - &Remove - - - - - - - Qt::Horizontal - - - - - - - Open custom.json - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Loader Mods - - - - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DropOnly - - - - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - - - 0 - 0 - - - - - - - - - Resource Packs - - - - - - true - - - QAbstractItemView::DropOnly - - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - - - - false - - - QDialogButtonBox::Close - - - - - - - - ModListView - QTreeView -
gui/widgets/ModListView.h
-
- - MCModInfoFrame - QFrame -
gui/widgets/MCModInfoFrame.h
- 1 -
-
- - -
diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp new file mode 100644 index 00000000..625a7c91 --- /dev/null +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -0,0 +1,306 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiMC.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "OneSixModEditDialog.h" +#include "ModEditDialogCommon.h" +#include "ui_OneSixModEditDialog.h" + +#include "gui/Platform.h" +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/VersionSelectDialog.h" + +#include "gui/dialogs/ProgressDialog.h" + +#include "logic/ModList.h" +#include "logic/OneSixVersion.h" +#include "logic/EnabledItemFilter.h" +#include "logic/lists/ForgeVersionList.h" +#include "logic/ForgeInstaller.h" +#include "logic/LiteLoaderInstaller.h" + +OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) + : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst) +{ + MultiMCPlatform::fixWM_CLASS(this); + ui->setupUi(this); + // libraries! + + m_version = m_inst->getFullVersion(); + if (m_version) + { + main_model = new EnabledItemFilter(this); + main_model->setActive(true); + main_model->setSourceModel(m_version.get()); + ui->libraryTreeView->setModel(main_model); + ui->libraryTreeView->installEventFilter(this); + updateVersionControls(); + } + else + { + disableVersionControls(); + } + // Loader mods + { + ensureFolderPathExists(m_inst->loaderModsDir()); + m_mods = m_inst->loaderModList(); + ui->loaderModTreeView->setModel(m_mods.get()); + ui->loaderModTreeView->installEventFilter(this); + m_mods->startWatching(); + auto smodel = ui->loaderModTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(loaderCurrent(QModelIndex, QModelIndex))); + } + // resource packs + { + ensureFolderPathExists(m_inst->resourcePacksDir()); + m_resourcepacks = m_inst->resourcePackList(); + ui->resPackTreeView->setModel(m_resourcepacks.get()); + ui->resPackTreeView->installEventFilter(this); + m_resourcepacks->startWatching(); + } + + connect(m_inst, &OneSixInstance::versionReloaded, this, &OneSixModEditDialog::updateVersionControls); +} + +OneSixModEditDialog::~OneSixModEditDialog() +{ + m_mods->stopWatching(); + m_resourcepacks->stopWatching(); + delete ui; +} + +void OneSixModEditDialog::updateVersionControls() +{ + bool customVersion = m_inst->versionIsCustom(); + ui->forgeBtn->setEnabled(true); + ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); + ui->customEditorBtn->setEnabled(customVersion); + ui->mainClassEdit->setText(m_version->mainClass); +} + +void OneSixModEditDialog::disableVersionControls() +{ + ui->forgeBtn->setEnabled(false); + ui->liteloaderBtn->setEnabled(false); + ui->customEditorBtn->setEnabled(false); + ui->mainClassEdit->setText(""); +} + +void OneSixModEditDialog::on_customEditorBtn_clicked() +{ + if (QDir(m_inst->instanceRoot()).exists("custom.json")) + { + if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json")) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings")); + } + } +} + +void OneSixModEditDialog::on_forgeBtn_clicked() +{ + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setFilter(1, m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ForgeVersionPtr forgeVersion = + std::dynamic_pointer_cast(vselect.selectedVersion()); + if (!forgeVersion) + return; + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + NetJob *fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); + ProgressDialog dlg(this); + dlg.exec(fjob); + if (dlg.result() == QDialog::Accepted) + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.add(m_inst)) + { + QLOG_ERROR() << "Failure installing forge"; + } + } + else + { + // failed to download forge :/ + } + } + else + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.add(m_inst)) + { + QLOG_ERROR() << "Failure installing forge"; + } + } + } + m_inst->reloadFullVersion(this); +} + +void OneSixModEditDialog::on_liteloaderBtn_clicked() +{ + LiteLoaderInstaller liteloader; + if (!liteloader.canApply(m_inst)) + { + QMessageBox::critical( + this, tr("LiteLoader"), + tr("There is no information available on how to install LiteLoader " + "into this version of Minecraft")); + return; + } + if (!liteloader.add(m_inst)) + { + QMessageBox::critical( + this, tr("LiteLoader"), + tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); + } + else + { + m_inst->reloadFullVersion(this); + } +} + +bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); +} + +bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmResPackBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addResPackBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->resPackTreeView, keyEvent); +} + +bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() != QEvent::KeyPress) + { + return QDialog::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast(ev); + if (obj == ui->loaderModTreeView) + return loaderListFilter(keyEvent); + if (obj == ui->resPackTreeView) + return resourcePackListFilter(keyEvent); + return QDialog::eventFilter(obj, ev); +} + +void OneSixModEditDialog::on_buttonBox_rejected() +{ + close(); +} + +void OneSixModEditDialog::on_addModBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); + for (auto filename : fileNames) + { + m_mods->stopWatching(); + m_mods->installMod(QFileInfo(filename)); + m_mods->startWatching(); + } +} +void OneSixModEditDialog::on_rmModBtn_clicked() +{ + int first, last; + auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_mods->stopWatching(); + m_mods->deleteMods(first, last); + m_mods->startWatching(); +} +void OneSixModEditDialog::on_viewModBtn_clicked() +{ + openDirInDefaultProgram(m_inst->loaderModsDir(), true); +} + +void OneSixModEditDialog::on_addResPackBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); + for (auto filename : fileNames) + { + m_resourcepacks->stopWatching(); + m_resourcepacks->installMod(QFileInfo(filename)); + m_resourcepacks->startWatching(); + } +} +void OneSixModEditDialog::on_rmResPackBtn_clicked() +{ + int first, last; + auto list = ui->resPackTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_resourcepacks->stopWatching(); + m_resourcepacks->deleteMods(first, last); + m_resourcepacks->startWatching(); +} +void OneSixModEditDialog::on_viewResPackBtn_clicked() +{ + openDirInDefaultProgram(m_inst->resourcePacksDir(), true); +} + +void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) +{ + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h new file mode 100644 index 00000000..161604c9 --- /dev/null +++ b/gui/dialogs/OneSixModEditDialog.h @@ -0,0 +1,67 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#include + +class EnabledItemFilter; +namespace Ui +{ +class OneSixModEditDialog; +} + +class OneSixModEditDialog : public QDialog +{ + Q_OBJECT + +public: + explicit OneSixModEditDialog(OneSixInstance *inst, QWidget *parent = 0); + virtual ~OneSixModEditDialog(); + +private +slots: + void on_addModBtn_clicked(); + void on_rmModBtn_clicked(); + void on_viewModBtn_clicked(); + + void on_addResPackBtn_clicked(); + void on_rmResPackBtn_clicked(); + void on_viewResPackBtn_clicked(); + // Questionable: SettingsDialog doesn't need this for some reason? + void on_buttonBox_rejected(); + void on_forgeBtn_clicked(); + void on_liteloaderBtn_clicked(); + void on_customEditorBtn_clicked(); + void updateVersionControls(); + void disableVersionControls(); + +protected: + bool eventFilter(QObject *obj, QEvent *ev); + bool loaderListFilter(QKeyEvent *ev); + bool resourcePackListFilter(QKeyEvent *ev); + +private: + Ui::OneSixModEditDialog *ui; + std::shared_ptr m_version; + std::shared_ptr m_mods; + std::shared_ptr m_resourcepacks; + EnabledItemFilter *main_model; + OneSixInstance *m_inst; +public +slots: + void loaderCurrent(QModelIndex current, QModelIndex previous); +}; diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui new file mode 100644 index 00000000..d57d0f85 --- /dev/null +++ b/gui/dialogs/OneSixModEditDialog.ui @@ -0,0 +1,317 @@ + + + OneSixModEditDialog + + + + 0 + 0 + 555 + 463 + + + + Manage Mods + + + + + + true + + + + 0 + 0 + + + + 1 + + + + Version + + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + + + + + + + Main Class: + + + + + + + false + + + + + + + + + + + + + Replace any current custom version with Minecraft Forge + + + Install Forge + + + + + + + Install LiteLoader + + + + + + + QFrame::Sunken + + + Qt::Horizontal + + + + + + + false + + + Add new libraries + + + &Add + + + + + + + false + + + Remove selected libraries + + + &Remove + + + + + + + Qt::Horizontal + + + + + + + Open custom.json + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Loader Mods + + + + + + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DropOnly + + + + + + + + + + + &Add + + + + + + + &Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + &View Folder + + + + + + + + + + + + 0 + 0 + + + + + + + + + Resource Packs + + + + + + true + + + QAbstractItemView::DropOnly + + + + + + + + + &Add + + + + + + + &Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + &View Folder + + + + + + + + + + + + + false + + + QDialogButtonBox::Close + + + + + + + + ModListView + QTreeView +
gui/widgets/ModListView.h
+
+ + MCModInfoFrame + QFrame +
gui/widgets/MCModInfoFrame.h
+ 1 +
+
+ + +
diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp index 74a8e909..92aa0c92 100644 --- a/logic/BaseInstaller.cpp +++ b/logic/BaseInstaller.cpp @@ -17,9 +17,9 @@ #include -#include "DerpVersion.h" -#include "DerpLibrary.h" -#include "DerpInstance.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixInstance.h" #include "cmdutils.h" @@ -28,12 +28,12 @@ BaseInstaller::BaseInstaller() } -bool BaseInstaller::isApplied(DerpInstance *on) +bool BaseInstaller::isApplied(OneSixInstance *on) { return QFile::exists(filename(on->instanceRoot())); } -bool BaseInstaller::add(DerpInstance *to) +bool BaseInstaller::add(OneSixInstance *to) { if (!patchesDir(to->instanceRoot()).exists()) { @@ -51,7 +51,7 @@ bool BaseInstaller::add(DerpInstance *to) return true; } -bool BaseInstaller::remove(DerpInstance *from) +bool BaseInstaller::remove(OneSixInstance *from) { return QFile::remove(filename(from->instanceRoot())); } diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h index 08f78bfc..df7eab89 100644 --- a/logic/BaseInstaller.h +++ b/logic/BaseInstaller.h @@ -17,7 +17,7 @@ #include -class DerpInstance; +class OneSixInstance; class QDir; class QString; @@ -26,11 +26,11 @@ class BaseInstaller public: BaseInstaller(); - virtual bool canApply(DerpInstance *instance) const { return true; } - bool isApplied(DerpInstance *on); + virtual bool canApply(OneSixInstance *instance) const { return true; } + bool isApplied(OneSixInstance *on); - virtual bool add(DerpInstance *to); - virtual bool remove(DerpInstance *from); + virtual bool add(OneSixInstance *to); + virtual bool remove(OneSixInstance *from); protected: virtual QString id() const = 0; diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 79640c84..a861e9b2 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -27,7 +27,7 @@ class QDialog; class Task; class MinecraftProcess; -class DerpUpdate; +class OneSixUpdate; class InstanceList; class BaseInstancePrivate; diff --git a/logic/DerpFTBInstance.cpp b/logic/DerpFTBInstance.cpp deleted file mode 100644 index 9b687461..00000000 --- a/logic/DerpFTBInstance.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "DerpFTBInstance.h" - -#include "DerpVersion.h" -#include "DerpLibrary.h" -#include "tasks/SequentialTask.h" -#include "ForgeInstaller.h" -#include "lists/ForgeVersionList.h" -#include "MultiMC.h" - -class DerpFTBInstanceForge : public Task -{ - Q_OBJECT -public: - explicit DerpFTBInstanceForge(const QString &version, DerpFTBInstance *inst, QObject *parent = 0) : - Task(parent), instance(inst), version("Forge " + version) - { - } - - void executeTask() - { - for (int i = 0; i < MMC->forgelist()->count(); ++i) - { - if (MMC->forgelist()->at(i)->name() == version) - { - forgeVersion = std::dynamic_pointer_cast(MMC->forgelist()->at(i)); - break; - } - } - if (!forgeVersion) - { - emitFailed(QString("Couldn't find forge version ") + version ); - return; - } - entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) - { - setStatus(tr("Downloading Forge...")); - fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);}); - connect(fjob, &NetJob::succeeded, this, &DerpFTBInstanceForge::installForge); - connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); }); - fjob->start(); - } - else - { - installForge(); - } - } - -private -slots: - void installForge() - { - setStatus(tr("Installing Forge...")); - QString forgePath = entry->getFullPath(); - ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!instance->reloadFullVersion()) - { - emitFailed(tr("Couldn't load the version config")); - return; - } - auto version = instance->getFullVersion(); - if (!forge.add(instance)) - { - emitFailed(tr("Couldn't install Forge")); - return; - } - emitSucceeded(); - } - -private: - DerpFTBInstance *instance; - QString version; - ForgeVersionPtr forgeVersion; - MetaEntryPtr entry; - NetJob *fjob; -}; - -DerpFTBInstance::DerpFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : - DerpInstance(rootDir, settings, parent) -{ - QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); - if (f.open(QFile::ReadOnly)) - { - QString data = QString::fromUtf8(f.readAll()); - QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); - m_forge.reset(new DerpLibrary(match.captured())); - m_forge->finalize(); - } -} - -QString DerpFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -QString DerpFTBInstance::getStatusbarDescription() -{ - return "Derp FTB: " + intendedVersionId(); -} -bool DerpFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} - -std::shared_ptr DerpFTBInstance::doUpdate(bool only_prepare) -{ - std::shared_ptr task; - task.reset(new SequentialTask(this)); - if (!MMC->forgelist()->isLoaded()) - { - task->addTask(std::shared_ptr(MMC->forgelist()->getLoadTask())); - } - task->addTask(DerpInstance::doUpdate(only_prepare)); - task->addTask(std::shared_ptr(new DerpFTBInstanceForge(m_forge->version(), this, this))); - //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. - //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. - task->addTask(DerpInstance::doUpdate(only_prepare)); - return task; -} - -#include "DerpFTBInstance.moc" diff --git a/logic/DerpFTBInstance.h b/logic/DerpFTBInstance.h deleted file mode 100644 index c16998bf..00000000 --- a/logic/DerpFTBInstance.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "DerpInstance.h" - -class DerpLibrary; - -class DerpFTBInstance : public DerpInstance -{ - Q_OBJECT -public: - explicit DerpFTBInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; - - virtual std::shared_ptr doUpdate(bool only_prepare) override; - - virtual QString id() const; - -private: - std::shared_ptr m_forge; -}; diff --git a/logic/DerpInstance.cpp b/logic/DerpInstance.cpp deleted file mode 100644 index e6b92b1f..00000000 --- a/logic/DerpInstance.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "DerpInstance.h" - -#include - -#include "DerpInstance_p.h" -#include "DerpUpdate.h" -#include "DerpVersion.h" -#include "pathutils.h" -#include "logger/QsLog.h" -#include "assets/AssetsUtils.h" -#include "MultiMC.h" -#include "icons/IconList.h" -#include "MinecraftProcess.h" -#include "gui/dialogs/DerpModEditDialog.h" - -DerpInstance::DerpInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) - : BaseInstance(new DerpInstancePrivate(), rootDir, settings, parent) -{ - I_D(DerpInstance); - d->m_settings->registerSetting("IntendedVersion", ""); - d->m_settings->registerSetting("ShouldUpdate", false); - d->version.reset(new DerpVersion(this, this)); - if (QDir(instanceRoot()).exists("version.json")) - { - reloadFullVersion(); - } - else - { - clearFullVersion(); - } -} - -std::shared_ptr DerpInstance::doUpdate(bool only_prepare) -{ - return std::shared_ptr(new DerpUpdate(this, only_prepare)); -} - -QString replaceTokensIn(QString text, QMap with) -{ - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; -} - -QDir DerpInstance::reconstructAssets(std::shared_ptr version) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); - - if (!indexFile.exists()) - { - QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; - return virtualRoot; - } - - QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) - { - QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = - PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); - QFile original(original_path); - if(!original.exists()) - continue; - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - // QLOG_DEBUG() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - QLOG_DEBUG() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - } - - return virtualRoot; -} - -QStringList DerpInstance::processMinecraftArgs(MojangAccountPtr account) -{ - I_D(DerpInstance); - auto version = d->version; - QString args_pattern = version->minecraftArguments; - - QMap token_mapping; - // yggdrasil! - token_mapping["auth_username"] = account->username(); - token_mapping["auth_session"] = account->sessionId(); - token_mapping["auth_access_token"] = account->accessToken(); - token_mapping["auth_player_name"] = account->currentProfile()->name; - token_mapping["auth_uuid"] = account->currentProfile()->id; - - // this is for offline?: - /* - map["auth_player_name"] = "Player"; - map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; - */ - - // these do nothing and are stupid. - token_mapping["profile_name"] = name(); - token_mapping["version_name"] = version->id; - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); - - auto user = account->user(); - QJsonObject userAttrs; - for (auto key : user.properties.keys()) - { - auto array = QJsonArray::fromStringList(user.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - - token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); - token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = version->assets; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; -} - -MinecraftProcess *DerpInstance::prepareForLaunch(MojangAccountPtr account) -{ - I_D(DerpInstance); - - QIcon icon = MMC->icons()->getIcon(iconKey()); - auto pixmap = icon.pixmap(128, 128); - pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); - - auto version = d->version; - if (!version) - return nullptr; - QString launchScript; - { - auto libs = version->getActiveNormalLibs(); - for (auto lib : libs) - { - QFileInfo fi(QString("libraries/") + lib->storagePath()); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; - } - QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; - QFileInfo fi(targetstr); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; - } - launchScript += "mainClass " + version->mainClass + "\n"; - - for (auto param : processMinecraftArgs(account)) - { - launchScript += "param " + param + "\n"; - } - - // Set the width and height for 1.6 instances - bool maximize = settings().get("LaunchMaximized").toBool(); - if (maximize) - { - // this is probably a BAD idea - // launchScript += "param --fullscreen\n"; - } - else - { - launchScript += - "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; - launchScript += - "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; - } - QDir natives_dir(PathCombine(instanceRoot(), "natives/")); - launchScript += "windowTitle " + windowTitle() + "\n"; - for(auto native: version->getActiveNativeLibs()) - { - QFileInfo finfo(PathCombine("libraries", native->storagePath())); - launchScript += "ext " + finfo.absoluteFilePath() + "\n"; - } - launchScript += "natives " + natives_dir.absolutePath() + "\n"; - launchScript += "launch onesix\n"; - - // create the process and set its parameters - MinecraftProcess *proc = new MinecraftProcess(this); - proc->setWorkdir(minecraftRoot()); - proc->setLaunchScript(launchScript); - // proc->setNativeFolder(natives_dir.absolutePath()); - return proc; -} - -void DerpInstance::cleanupAfterRun() -{ - QString target_dir = PathCombine(instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); -} - -std::shared_ptr DerpInstance::loaderModList() -{ - I_D(DerpInstance); - if (!d->loader_mod_list) - { - d->loader_mod_list.reset(new ModList(loaderModsDir())); - } - d->loader_mod_list->update(); - return d->loader_mod_list; -} - -std::shared_ptr DerpInstance::resourcePackList() -{ - I_D(DerpInstance); - if (!d->resource_pack_list) - { - d->resource_pack_list.reset(new ModList(resourcePacksDir())); - } - d->resource_pack_list->update(); - return d->resource_pack_list; -} - -QDialog *DerpInstance::createModEditDialog(QWidget *parent) -{ - return new DerpModEditDialog(this, parent); -} - -bool DerpInstance::setIntendedVersionId(QString version) -{ - settings().set("IntendedVersion", version); - setShouldUpdate(true); - QFile::remove(PathCombine(instanceRoot(), "version.json")); - clearFullVersion(); - return true; -} - -QString DerpInstance::intendedVersionId() const -{ - return settings().get("IntendedVersion").toString(); -} - -void DerpInstance::setShouldUpdate(bool val) -{ - settings().set("ShouldUpdate", val); -} - -bool DerpInstance::shouldUpdate() const -{ - QVariant var = settings().get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; -} - -bool DerpInstance::versionIsCustom() -{ - QDir patches(PathCombine(instanceRoot(), "patches/")); - return QFile::exists(PathCombine(instanceRoot(), "custom.json")) - || (patches.exists() && patches.count() >= 0); -} - -QString DerpInstance::currentVersionId() const -{ - return intendedVersionId(); -} - -bool DerpInstance::reloadFullVersion(QWidget *widgetParent) -{ - I_D(DerpInstance); - - bool ret = d->version->reload(widgetParent); - emit versionReloaded(); - return ret; -} - -void DerpInstance::clearFullVersion() -{ - I_D(DerpInstance); - d->version->clear(); - emit versionReloaded(); -} - -std::shared_ptr DerpInstance::getFullVersion() -{ - I_D(DerpInstance); - return d->version; -} - -QString DerpInstance::defaultBaseJar() const -{ - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; -} - -QString DerpInstance::defaultCustomBaseJar() const -{ - return PathCombine(instanceRoot(), "custom.jar"); -} - -bool DerpInstance::menuActionEnabled(QString action_name) const -{ - if (action_name == "actionChangeInstLWJGLVersion") - return false; - return true; -} - -QString DerpInstance::getStatusbarDescription() -{ - QString descr = "Derp : " + intendedVersionId(); - if (versionIsCustom()) - { - descr + " (custom)"; - } - return descr; -} - -QString DerpInstance::loaderModsDir() const -{ - return PathCombine(minecraftRoot(), "mods"); -} - -QString DerpInstance::resourcePacksDir() const -{ - return PathCombine(minecraftRoot(), "resourcepacks"); -} - -QString DerpInstance::instanceConfigFolder() const -{ - return PathCombine(minecraftRoot(), "config"); -} diff --git a/logic/DerpInstance.h b/logic/DerpInstance.h deleted file mode 100644 index 46f953c0..00000000 --- a/logic/DerpInstance.h +++ /dev/null @@ -1,75 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "BaseInstance.h" - -#include "DerpVersion.h" -#include "ModList.h" - -class DerpInstance : public BaseInstance -{ - Q_OBJECT -public: - explicit DerpInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - - ////// Mod Lists ////// - std::shared_ptr loaderModList(); - std::shared_ptr resourcePackList(); - - ////// Directories ////// - QString resourcePacksDir() const; - QString loaderModsDir() const; - virtual QString instanceConfigFolder() const override; - - virtual std::shared_ptr doUpdate(bool only_prepare) override; - virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; - - virtual void cleanupAfterRun() override; - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - - virtual QString currentVersionId() const override; - - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - - virtual QDialog *createModEditDialog(QWidget *parent) override; - - /// reload the full version json files. return true on success! - bool reloadFullVersion(QWidget *widgetParent = 0); - /// clears all version information in preparation for an update - void clearFullVersion(); - /// get the current full version info - std::shared_ptr getFullVersion(); - /// is the current version original, or custom? - virtual bool versionIsCustom() override; - - virtual QString defaultBaseJar() const override; - virtual QString defaultCustomBaseJar() const override; - - virtual bool menuActionEnabled(QString action_name) const override; - virtual QString getStatusbarDescription() override; - -signals: - void versionReloaded(); - -private: - QStringList processMinecraftArgs(MojangAccountPtr account); - QDir reconstructAssets(std::shared_ptr version); -}; diff --git a/logic/DerpInstance_p.h b/logic/DerpInstance_p.h deleted file mode 100644 index 41f7b62d..00000000 --- a/logic/DerpInstance_p.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "BaseInstance_p.h" -#include "DerpVersion.h" -#include "ModList.h" - -struct DerpInstancePrivate : public BaseInstancePrivate -{ - std::shared_ptr version; - std::shared_ptr loader_mod_list; - std::shared_ptr resource_pack_list; -}; diff --git a/logic/DerpLibrary.cpp b/logic/DerpLibrary.cpp deleted file mode 100644 index ba4d516b..00000000 --- a/logic/DerpLibrary.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "DerpLibrary.h" -#include "DerpRule.h" -#include "OpSys.h" -#include "logic/net/URLConstants.h" -#include -#include -#include "logger/QsLog.h" - -void DerpLibrary::finalize() -{ - QStringList parts = m_name.split(':'); - QString relative = parts[0]; - relative.replace('.', '/'); - relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - - if (!m_is_native) - relative += ".jar"; - else - { - if (m_native_suffixes.contains(currentSystem)) - { - relative += "-" + m_native_suffixes[currentSystem] + ".jar"; - } - else - { - // really, bad. - relative += ".jar"; - } - } - - m_decentname = parts[1]; - m_decentversion = parts[2]; - m_storage_path = relative; - m_download_url = m_base_url + relative; - - if (m_rules.empty()) - { - m_is_active = true; - } - else - { - RuleAction result = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - result = temp; - } - m_is_active = (result == Allow); - } - if (m_is_native) - { - m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); - m_decenttype = "Native"; - } - else - { - m_decenttype = "Java"; - } -} - -void DerpLibrary::setName(const QString &name) -{ - m_name = name; -} -void DerpLibrary::setBaseUrl(const QString &base_url) -{ - m_base_url = base_url; -} -void DerpLibrary::setIsNative() -{ - m_is_native = true; -} -void DerpLibrary::addNative(OpSys os, const QString &suffix) -{ - m_is_native = true; - m_native_suffixes[os] = suffix; -} -void DerpLibrary::setRules(QList> rules) -{ - m_rules = rules; -} -bool DerpLibrary::isActive() const -{ - return m_is_active; -} -bool DerpLibrary::isNative() const -{ - return m_is_native; -} -QString DerpLibrary::downloadUrl() const -{ - if (m_absolute_url.size()) - return m_absolute_url; - return m_download_url; -} -QString DerpLibrary::storagePath() const -{ - return m_storage_path; -} - -void DerpLibrary::setAbsoluteUrl(const QString &absolute_url) -{ - m_absolute_url = absolute_url; -} - -QString DerpLibrary::absoluteUrl() const -{ - return m_absolute_url; -} - -void DerpLibrary::setHint(const QString &hint) -{ - m_hint = hint; -} - -QString DerpLibrary::hint() const -{ - return m_hint; -} - -bool DerpLibrary::filesExist() -{ - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - QFileInfo info32(PathCombine("libraries", cooked_storage)); - if (!info32.exists()) - { - return false; - } - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - QFileInfo info64(PathCombine("libraries", cooked_storage)); - if (!info64.exists()) - { - return false; - } - } - else - { - QFileInfo info(PathCombine("libraries", storage)); - if (!info.exists()) - { - return false; - } - } - return true; -} - -bool DerpLibrary::extractTo(QString target_dir) -{ - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - QString origin = PathCombine("libraries", cooked_storage); - QString target_dir_cooked = PathCombine(target_dir, "32"); - if(!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - origin = PathCombine("libraries", cooked_storage); - target_dir_cooked = PathCombine(target_dir, "64"); - if(!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - } - else - { - if(!ensureFolderPathExists(target_dir)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir; - return false; - } - QString path = PathCombine("libraries", storage); - if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + path; - return false; - } - } - return true; -} - -QJsonObject DerpLibrary::toJson() -{ - QJsonObject libRoot; - libRoot.insert("name", m_name); - if (m_absolute_url.size()) - libRoot.insert("MMC-absoluteUrl", m_absolute_url); - if (m_hint.size()) - libRoot.insert("MMC-hint", m_hint); - if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE) - libRoot.insert("url", m_base_url); - if (isNative() && m_native_suffixes.size()) - { - QJsonObject nativeList; - auto iter = m_native_suffixes.begin(); - while (iter != m_native_suffixes.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - } - if (isNative() && extract_excludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : extract_excludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - if (m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - return libRoot; -} diff --git a/logic/DerpLibrary.h b/logic/DerpLibrary.h deleted file mode 100644 index d1cee843..00000000 --- a/logic/DerpLibrary.h +++ /dev/null @@ -1,132 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "logic/net/URLConstants.h" -#include "OpSys.h" - -class Rule; - -class DerpLibrary -{ -private: - // basic values used internally (so far) - QString m_name; - QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; - QList> m_rules; - - // custom values - /// absolute URL. takes precedence over m_download_path, if defined - QString m_absolute_url; - /// download hint - how to actually get the library - QString m_hint; - - // derived values used for real things - /// a decent name fit for display - QString m_decentname; - /// a decent version fit for display - QString m_decentversion; - /// a decent type fit for display - QString m_decenttype; - /// where to store the lib locally - QString m_storage_path; - /// where to download the lib from - QString m_download_url; - /// is this lib actually active on the current OS? - bool m_is_active = false; - /// is the library a native? - bool m_is_native = false; - /// native suffixes per OS - QMap m_native_suffixes; - -public: - QStringList extract_excludes; - -public: - /// Constructor - DerpLibrary(const QString &name) - { - m_name = name; - } - - /// Returns the raw name field - QString rawName() const - { - return m_name; - } - - QJsonObject toJson(); - - /** - * finalize the library, processing the input values into derived values and state - * - * This SHALL be called after all the values are parsed or after any further change. - */ - void finalize(); - - /// Set the library composite name - void setName(const QString &name); - /// get a decent-looking name - QString name() const - { - return m_decentname; - } - /// get a decent-looking version - QString version() const - { - return m_decentversion; - } - /// what kind of library is it? (for display) - QString type() const - { - return m_decenttype; - } - /// Set the url base for downloads - void setBaseUrl(const QString &base_url); - - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); - /// Attach a name suffix to the specified OS native - void addNative(OpSys os, const QString &suffix); - /// Set the load rules - void setRules(QList> rules); - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - /// Returns true if the library is native - bool isNative() const; - /// Get the URL to download the library from - QString downloadUrl() const; - /// Get the relative path where the library should be saved - QString storagePath() const; - - /// set an absolute URL for the library. This is an MMC extension. - void setAbsoluteUrl(const QString &absolute_url); - QString absoluteUrl() const; - - /// set a hint about how to treat the library. This is an MMC extension. - void setHint(const QString &hint); - QString hint() const; - - bool extractTo(QString target_dir); - bool filesExist(); -}; diff --git a/logic/DerpRule.cpp b/logic/DerpRule.cpp deleted file mode 100644 index 5ed200e3..00000000 --- a/logic/DerpRule.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include "DerpRule.h" - -QList> rulesFromJsonV4(const QJsonObject &objectWithRules) -{ - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; - - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } - - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - return rules; -} - -QJsonObject ImplicitRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; -} - -QJsonObject OsRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", OpSys_toString(m_system)); - osObj.insert("version", m_version_regexp); - } - ruleObj.insert("os", osObj); - return ruleObj; -} - -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} diff --git a/logic/DerpRule.h b/logic/DerpRule.h deleted file mode 100644 index 0254028c..00000000 --- a/logic/DerpRule.h +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include "logic/DerpLibrary.h" - -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -RuleAction RuleAction_fromString(QString); -QList> rulesFromJsonV4(const QJsonObject &objectWithRules); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(DerpLibrary *parent) = 0; - -public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(DerpLibrary *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } - ; -}; - -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; - -protected: - virtual bool applies(DerpLibrary *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule -{ -protected: - virtual bool applies(DerpLibrary *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result) - { - return std::shared_ptr(new ImplicitRule(result)); - } -}; diff --git a/logic/DerpUpdate.cpp b/logic/DerpUpdate.cpp deleted file mode 100644 index 5686e4ac..00000000 --- a/logic/DerpUpdate.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MultiMC.h" -#include "DerpUpdate.h" - -#include - -#include -#include -#include -#include - -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "DerpVersion.h" -#include "DerpLibrary.h" -#include "DerpInstance.h" -#include "net/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" -#include - -DerpUpdate::DerpUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) - : Task(parent), m_inst(inst), m_only_prepare(only_prepare) -{ -} - -void DerpUpdate::executeTask() -{ - QString intendedVersion = m_inst->intendedVersionId(); - - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed("Failed to create bin folder."); - return; - } - - if (m_only_prepare) - { - prepareForLaunch(); - return; - } - - if (m_inst->shouldUpdate()) - { - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast( - MMC->minecraftlist()->findVersion(intendedVersion)); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed("The specified Minecraft version is invalid. Choose a different one."); - return; - } - versionFileStart(); - } - else - { - jarlibStart(); - } -} - -void DerpUpdate::versionFileStart() -{ - QLOG_INFO() << m_inst->name() << ": getting version file."; - setStatus(tr("Getting the version files from Mojang...")); - - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + - targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; - auto job = new NetJob("Version index"); - job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); - specificVersionDownloadJob.reset(job); - connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - specificVersionDownloadJob->start(); -} - -void DerpUpdate::versionFileFinished() -{ - NetActionPtr DlJob = specificVersionDownloadJob->first(); - DerpInstance *inst = (DerpInstance *)m_inst; - - QString version_id = targetVersion->descriptor(); - QString inst_dir = m_inst->instanceRoot(); - // save the version file in $instanceId/version.json - { - QString version1 = PathCombine(inst_dir, "/version.json"); - ensureFilePathExists(version1); - // FIXME: detect errors here, download to a temp file, swap - QSaveFile vfile1(version1); - if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) - { - emitFailed("Can't open " + version1 + " for writing."); - return; - } - auto data = std::dynamic_pointer_cast(DlJob)->m_data; - qint64 actual = 0; - if ((actual = vfile1.write(data)) != data.size()) - { - emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + - data.size() + '.'); - return; - } - if (!vfile1.commit()) - { - emitFailed("Can't commit changes to " + version1); - return; - } - } - - // the version is downloaded safely. update is 'done' at this point - m_inst->setShouldUpdate(false); - - // delete any custom version inside the instance (it's no longer relevant, we did an update) - QString custom = PathCombine(inst_dir, "/custom.json"); - QFile finfo(custom); - if (finfo.exists()) - { - finfo.remove(); - } - inst->reloadFullVersion(); - - jarlibStart(); -} - -void DerpUpdate::versionFileFailed() -{ - emitFailed("Failed to download the version description. Try again."); -} - -void DerpUpdate::assetIndexStart() -{ - setStatus(tr("Updating assets index...")); - DerpInstance *inst = (DerpInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); - QString assetName = version->assets; - QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; - QString localPath = assetName + ".json"; - auto job = new NetJob("Asset index for " + inst->name()); - - auto metacache = MMC->metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - job->addNetAction(CacheDownload::make(indexUrl, entry)); - jarlibDownloadJob.reset(job); - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - - jarlibDownloadJob->start(); -} - -void DerpUpdate::assetIndexFinished() -{ - AssetsIndex index; - - DerpInstance *inst = (DerpInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); - QString assetName = version->assets; - - QString asset_fname = "assets/indexes/" + assetName + ".json"; - if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index)) - { - emitFailed("Failed to read the assets index!"); - } - - QList dls; - for (auto object : index.objects.values()) - { - QString objectName = object.hash.left(2) + "/" + object.hash; - QFileInfo objectFile("assets/objects/" + objectName); - if ((!objectFile.isFile()) || (objectFile.size() != object.size)) - { - auto objectDL = MD5EtagDownload::make( - QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), - objectFile.filePath()); - objectDL->m_total_progress = object.size; - dls.append(objectDL); - } - } - if (dls.size()) - { - setStatus(tr("Getting the assets files from Mojang...")); - auto job = new NetJob("Assets for " + inst->name()); - for (auto dl : dls) - job->addNetAction(dl); - jarlibDownloadJob.reset(job); - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - jarlibDownloadJob->start(); - return; - } - assetsFinished(); -} - -void DerpUpdate::assetIndexFailed() -{ - emitFailed("Failed to download the assets index!"); -} - -void DerpUpdate::assetsFinished() -{ - prepareForLaunch(); -} - -void DerpUpdate::assetsFailed() -{ - emitFailed("Failed to download assets!"); -} - -void DerpUpdate::jarlibStart() -{ - setStatus(tr("Getting the library files from Mojang...")); - QLOG_INFO() << m_inst->name() << ": downloading libraries"; - DerpInstance *inst = (DerpInstance *)m_inst; - bool successful = inst->reloadFullVersion(); - if (!successful) - { - emitFailed("Failed to load the version description file. It might be " - "corrupted, missing or simply too new."); - return; - } - - // Build a list of URLs that will need to be downloaded. - std::shared_ptr version = inst->getFullVersion(); - // minecraft.jar for this version - { - QString version_id = version->id; - QString localPath = version_id + "/" + version_id + ".jar"; - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath; - - auto job = new NetJob("Libraries for instance " + inst->name()); - - auto metacache = MMC->metacache(); - auto entry = metacache->resolveEntry("versions", localPath); - job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); - - jarlibDownloadJob.reset(job); - } - - auto libs = version->getActiveNativeLibs(); - libs.append(version->getActiveNormalLibs()); - - auto metacache = MMC->metacache(); - QList ForgeLibs; - for (auto lib : libs) - { - if (lib->hint() == "local") - continue; - - QString raw_storage = lib->storagePath(); - QString raw_dl = lib->downloadUrl(); - - auto f = [&](QString storage, QString dl) - { - auto entry = metacache->resolveEntry("libraries", storage); - if (entry->stale) - { - if (lib->hint() == "forge-pack-xz") - { - ForgeLibs.append(ForgeXzDownload::make(storage, entry)); - } - else - { - jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); - } - } - }; - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); - } - else - { - f(raw_storage, raw_dl); - } - } - // TODO: think about how to propagate this from the original json file... or IF AT ALL - QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list"; - if (!ForgeLibs.empty()) - { - jarlibDownloadJob->addNetAction( - ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList)); - } - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); - connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - - jarlibDownloadJob->start(); -} - -void DerpUpdate::jarlibFinished() -{ - assetIndexStart(); -} - -void DerpUpdate::jarlibFailed() -{ - QStringList failed = jarlibDownloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed("Failed to download the following files:\n" + failed_all + - "\n\nPlease try again."); -} - -void DerpUpdate::prepareForLaunch() -{ - setStatus(tr("Preparing for launch...")); - QLOG_INFO() << m_inst->name() << ": preparing for launch"; - auto derp_inst = (DerpInstance *)m_inst; - - // delete any leftovers, if they are present. - derp_inst->cleanupAfterRun(); - - QString natives_dir_raw = PathCombine(derp_inst->instanceRoot(), "natives/"); - auto version = derp_inst->getFullVersion(); - if (!version) - { - emitFailed("The version information for this instance is not complete. Try re-creating " - "it or changing the version."); - return; - } - /* - for (auto lib : version->getActiveNativeLibs()) - { - if (!lib->filesExist()) - { - emitFailed("Native library is missing some files:\n" + lib->storagePath() + - "\n\nRun the instance at least once in online mode to get all the " - "required files."); - return; - } - if (!lib->extractTo(natives_dir_raw)) - { - emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + - natives_dir_raw + - "\n\nMake sure MultiMC has appropriate permissions and there is enough " - "space on the storage device."); - return; - } - } -*/ - emitSucceeded(); -} diff --git a/logic/DerpUpdate.h b/logic/DerpUpdate.h deleted file mode 100644 index 475f6c35..00000000 --- a/logic/DerpUpdate.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "logic/net/NetJob.h" -#include "logic/tasks/Task.h" - -class MinecraftVersion; -class BaseInstance; - -class DerpUpdate : public Task -{ - Q_OBJECT -public: - explicit DerpUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); - virtual void executeTask(); - -private -slots: - void versionFileStart(); - void versionFileFinished(); - void versionFileFailed(); - - void jarlibStart(); - void jarlibFinished(); - void jarlibFailed(); - - void assetIndexStart(); - void assetIndexFinished(); - void assetIndexFailed(); - - void assetsFinished(); - void assetsFailed(); - - // extract the appropriate libraries - void prepareForLaunch(); - -private: - NetJobPtr specificVersionDownloadJob; - NetJobPtr jarlibDownloadJob; - - // target version, determined during this task - std::shared_ptr targetVersion; - BaseInstance *m_inst = nullptr; - bool m_only_prepare = false; -}; diff --git a/logic/DerpVersion.cpp b/logic/DerpVersion.cpp deleted file mode 100644 index f4a8dd97..00000000 --- a/logic/DerpVersion.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "DerpVersion.h" - -#include - -#include "DerpVersionBuilder.h" - -DerpVersion::DerpVersion(DerpInstance *instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ - clear(); -} - -bool DerpVersion::reload(QWidget *widgetParent) -{ - return DerpVersionBuilder::build(this, m_instance, widgetParent); -} - -void DerpVersion::clear() -{ - id.clear(); - time.clear(); - releaseTime.clear(); - type.clear(); - assets.clear(); - processArguments.clear(); - minecraftArguments.clear(); - minimumLauncherVersion = 0xDEADBEAF; - mainClass.clear(); - libraries.clear(); -} - -void DerpVersion::dump() const -{ - qDebug().nospace() << "DerpVersion(" - << "\n\tid=" << id - << "\n\ttime=" << time - << "\n\treleaseTime=" << releaseTime - << "\n\ttype=" << type - << "\n\tassets=" << assets - << "\n\tprocessArguments=" << processArguments - << "\n\tminecraftArguments=" << minecraftArguments - << "\n\tminimumLauncherVersion=" << minimumLauncherVersion - << "\n\tmainClass=" << mainClass - << "\n\tlibraries="; - for (auto lib : libraries) - { - qDebug().nospace() << "\n\t\t" << lib.get(); - } - qDebug().nospace() << "\n)"; -} - -QList > DerpVersion::getActiveNormalLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && !lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -QList > DerpVersion::getActiveNativeLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -std::shared_ptr DerpVersion::fromJson(const QJsonObject &obj) -{ - std::shared_ptr version(new DerpVersion(0)); - if (DerpVersionBuilder::read(version.get(), obj)) - { - return version; - } - return 0; -} - -QVariant DerpVersion::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= libraries.size()) - return QVariant(); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return libraries[row]->name(); - case 1: - return libraries[row]->type(); - case 2: - return libraries[row]->version(); - default: - return QVariant(); - } - } - return QVariant(); -} - -Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - int row = index.row(); - if (libraries[row]->isActive()) - { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; - } - else - { - return Qt::ItemNeverHasChildren; - } - // return QAbstractListModel::flags(index); -} - -QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) - return QVariant(); - switch (section) - { - case 0: - return QString("Name"); - case 1: - return QString("Type"); - case 2: - return QString("Version"); - default: - return QString(); - } -} - -int DerpVersion::rowCount(const QModelIndex &parent) const -{ - return libraries.size(); -} - -int DerpVersion::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QDebug operator<<(QDebug &dbg, const DerpVersion *version) -{ - version->dump(); - return dbg.maybeSpace(); -} -QDebug operator<<(QDebug &dbg, const DerpLibrary *library) -{ - dbg.nospace() << "DerpLibrary(" - << "\n\t\t\trawName=" << library->rawName() - << "\n\t\t\tname=" << library->name() - << "\n\t\t\tversion=" << library->version() - << "\n\t\t\ttype=" << library->type() - << "\n\t\t\tisActive=" << library->isActive() - << "\n\t\t\tisNative=" << library->isNative() - << "\n\t\t\tdownloadUrl=" << library->downloadUrl() - << "\n\t\t\tstoragePath=" << library->storagePath() - << "\n\t\t\tabsolutePath=" << library->absoluteUrl() - << "\n\t\t\thint=" << library->hint(); - dbg.nospace() << "\n\t\t)"; - return dbg.maybeSpace(); -} diff --git a/logic/DerpVersion.h b/logic/DerpVersion.h deleted file mode 100644 index 4481d146..00000000 --- a/logic/DerpVersion.h +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include -#include - -#include "DerpLibrary.h" - -class DerpInstance; - -class DerpVersion : public QAbstractListModel -{ - Q_OBJECT -public: - explicit DerpVersion(DerpInstance *instance, QObject *parent = 0); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - - bool reload(QWidget *widgetParent); - void clear(); - - void dump() const; - -public: - QList> getActiveNormalLibs(); - QList> getActiveNativeLibs(); - - static std::shared_ptr fromJson(const QJsonObject &obj); - - // data members -public: - /// the ID - determines which jar to use! ACTUALLY IMPORTANT! - QString id; - /// Last updated time - as a string - QString time; - /// Release time - as a string - QString releaseTime; - /// Release type - "release" or "snapshot" - QString type; - /// Assets type - "legacy" or a version ID - QString assets; - /** - * DEPRECATED: Old versions of the new vanilla launcher used this - * ex: "username_session_version" - */ - QString processArguments; - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString minecraftArguments; - /** - * the minimum launcher version required by this version ... current is 4 (at point of - * writing) - */ - int minimumLauncherVersion = 0xDEADBEEF; - /** - * The main class to load first - */ - QString mainClass; - - /// the list of libs - both active and inactive, native and java - QList> libraries; - - /* - FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. - - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX - 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" - } - */ - // QList rules; - -private: - DerpInstance *m_instance; -}; - -QDebug operator<<(QDebug &dbg, const DerpVersion *version); -QDebug operator<<(QDebug &dbg, const DerpLibrary *library); diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp deleted file mode 100644 index 20f43404..00000000 --- a/logic/DerpVersionBuilder.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "DerpVersionBuilder.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DerpVersion.h" -#include "DerpInstance.h" -#include "DerpRule.h" -#include "logger/QsLog.h" - -DerpVersionBuilder::DerpVersionBuilder() -{ - -} - -bool DerpVersionBuilder::build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent) -{ - DerpVersionBuilder builder; - builder.m_version = version; - builder.m_instance = instance; - builder.m_widgetParent = widgetParent; - return builder.build(); -} - -bool DerpVersionBuilder::read(DerpVersion *version, const QJsonObject &obj) -{ - DerpVersionBuilder builder; - builder.m_version = version; - builder.m_instance = 0; - builder.m_widgetParent = 0; - return builder.read(obj); -} - -bool DerpVersionBuilder::build() -{ - m_version->clear(); - - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); - - // version.json -> patches/*.json -> custom.json - - // version.json - { - QLOG_INFO() << "Reading version.json"; - QJsonObject obj; - if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) - { - return false; - } - if (!apply(obj)) - { - return false; - } - } - - // patches/ - { - // load all, put into map for ordering, apply in the right order - - QMap objects; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - QLOG_INFO() << "Reading" << info.fileName(); - QJsonObject obj; - if (!read(info, &obj)) - { - return false; - } - if (!obj.contains("order") || !obj.value("order").isDouble()) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Missing or invalid 'order' in %1").arg(info.absoluteFilePath())); - return false; - } - objects.insert(obj.value("order").toDouble(), obj); - } - for (auto object : objects.values()) - { - qDebug() << "Applying object with order" << objects.key(object); - if (!apply(object)) - { - return false; - } - } - } - - // custom.json - { - if (QFile::exists(root.absoluteFilePath("custom.json"))) - { - QLOG_INFO() << "Reading custom.json"; - QJsonObject obj; - if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) - { - return false; - } - if (!apply(obj)) - { - return false; - } - } - } - - return true; -} - -bool DerpVersionBuilder::read(const QJsonObject &obj) -{ - m_version->clear(); - - return apply(obj); -} - -void applyString(const QJsonObject &obj, const QString &key, QString &out, const bool onlyOverride = true) -{ - if (obj.contains(key) && obj.value(key).isString()) - { - out = obj.value(key).toString(); - } - else if (!onlyOverride) - { - if (obj.contains("+" + key) && obj.value("+" + key).isString()) - { - out += obj.value("+" + key).toString(); - } - else if (obj.contains("-" + key) && obj.value("-" + key).isString()) - { - out.remove(obj.value("-" + key).toString()); - } - } -} -void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(DerpLibrary::*func)(const QString &val)) -{ - if (obj.contains(key) && obj.value(key).isString()) - { - (lib.get()->*func)(obj.value(key).toString()); - } -} -bool DerpVersionBuilder::apply(const QJsonObject &object) -{ - applyString(object, "id", m_version->id); - applyString(object, "mainClass", m_version->mainClass); - applyString(object, "minecraftArguments", m_version->minecraftArguments, false); - applyString(object, "processArguments", m_version->processArguments, false); - if (m_version->minecraftArguments.isEmpty()) - { - const QString toCompare = m_version->processArguments.toLower(); - if (toCompare == "legacy") - { - m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - } - applyString(object, "type", m_version->type); - applyString(object, "releaseTime", m_version->releaseTime); - applyString(object, "time", m_version->time); - applyString(object, "assets", m_version->assets); - { - if (m_version->assets.isEmpty()) - { - m_version->assets = "legacy"; - } - } - if (object.contains("minimumLauncherVersion")) - { - auto minLauncherVersionVal = object.value("minimumLauncherVersion"); - if (minLauncherVersionVal.isDouble()) - { - m_version->minimumLauncherVersion = minLauncherVersionVal.toDouble(); - } - } - - // libraries - if (object.contains("libraries")) - { - m_version->libraries.clear(); - auto librariesValue = object.value("libraries"); - if (!librariesValue.isArray()) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; - } - auto array = librariesValue.toArray(); - for (auto libVal : array) - { - if (libVal.isObject()) - { - if (!applyLibrary(libVal.toObject(), Override)) - { - return false; - } - } - - } - } - - // +libraries - if (object.contains("+libraries")) - { - auto librariesValue = object.value("+libraries"); - if (!librariesValue.isArray()) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; - } - for (auto libVal : librariesValue.toArray()) - { - if (libVal.isObject()) - { - applyLibrary(libVal.toObject(), Add); - } - - } - } - - // -libraries - if (object.contains("-libraries")) - { - auto librariesValue = object.value("-libraries"); - if (!librariesValue.isArray()) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; - } - for (auto libVal : librariesValue.toArray()) - { - if (libVal.isObject()) - { - applyLibrary(libVal.toObject(), Remove); - } - - } - } - - return true; -} - -int findLibrary(QList > haystack, const QString &needle) -{ - for (int i = 0; i < haystack.size(); ++i) - { - if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(haystack.at(i)->rawName()) != -1) - { - return i; - } - } - return -1; -} - -bool DerpVersionBuilder::applyLibrary(const QJsonObject &lib, const DerpVersionBuilder::Type type) -{ - // Library name - auto nameVal = lib.value("name"); - if (!nameVal.isString()) - { - return false; - } - auto name = nameVal.toString(); - - if (type == Remove) - { - int index = findLibrary(m_version->libraries, name); - if (index >= 0) - { - m_version->libraries.removeAt(index); - } - return true; - } - - if (type == Add && !lib.contains("insert")) - { - return false; - } - - std::shared_ptr library; - - if (lib.value("insert").toString() != "apply" && type == Add) - { - QMutableListIterator > it(m_version->libraries); - while (it.hasNext()) - { - if (it.next()->rawName() == name) - { - it.remove(); - } - } - } - - if (lib.value("insert").toString() == "apply" && type == Add) - { - library = m_version->libraries[findLibrary(m_version->libraries, name)]; - } - else - { - library.reset(new DerpLibrary(nameVal.toString())); - } - - applyString(lib, "url", library, &DerpLibrary::setBaseUrl); - applyString(lib, "MMC-hint", library, &DerpLibrary::setHint); - applyString(lib, "MMC-absulute_url", library, &DerpLibrary::setAbsoluteUrl); - applyString(lib, "MMC-absoluteUrl", library, &DerpLibrary::setAbsoluteUrl); - - auto extractVal = lib.value("extract"); - if (extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if (excludesVal.isArray()) - { - auto excludesList = excludesVal.toArray(); - for (auto excludeVal : excludesList) - { - if (excludeVal.isString()) - { - excludes.append(excludeVal.toString()); - } - } - library->extract_excludes = excludes; - } - } - - auto nativesVal = lib.value("natives"); - if (nativesVal.isObject()) - { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - auto osType = OpSys_fromString(it.key()); - if (osType == Os_Other) - { - continue; - } - if (!it.value().isString()) - { - continue; - } - library->addNative(osType, it.value().toString()); - } - } - - if (lib.contains("rules")) - { - library->setRules(rulesFromJsonV4(lib)); - } - library->finalize(); - if (type == Override) - { - m_version->libraries.append(library); - } - else if (lib.value("insert").toString() != "apply") - { - if (lib.value("insert").toString() == "append") - { - m_version->libraries.append(library); - } - else if (lib.value("insert").toString() == "prepend") - { - m_version->libraries.prepend(library); - } - else if (lib.value("insert").isObject()) - { - QJsonObject insertObj = lib.value("insert").toObject(); - if (insertObj.isEmpty()) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("'insert' object empty")); - return false; - } - const QString key = insertObj.keys().first(); - const QString value = insertObj.value(key).toString(); - const int index = findLibrary(m_version->libraries, value); - if (index >= 0) - { - if (key == "before") - { - m_version->libraries.insert(index, library); - } - else if (key == "after") - { - m_version->libraries.insert(index + 1, library); - } - else - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); - return false; - } - } - } - else - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); - return false; - } - } - return true; -} - -bool DerpVersionBuilder::read(const QFileInfo &fileInfo, QJsonObject *out) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); - return false; - } - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to parse %1: %2 at %3").arg(file.fileName(), error.errorString()).arg(error.offset)); - return false; - } - *out = doc.object(); - return true; -} diff --git a/logic/DerpVersionBuilder.h b/logic/DerpVersionBuilder.h deleted file mode 100644 index b94cd8e9..00000000 --- a/logic/DerpVersionBuilder.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -class DerpVersion; -class DerpInstance; -class QWidget; -class QJsonObject; -class QFileInfo; - -class DerpVersionBuilder -{ - DerpVersionBuilder(); -public: - static bool build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent); - static bool read(DerpVersion *version, const QJsonObject &obj); - -private: - DerpVersion *m_version; - DerpInstance *m_instance; - QWidget *m_widgetParent; - - enum Type - { - Override, - Add, - Remove - }; - - bool build(); - bool read(const QJsonObject &obj); - - void clear(); - bool apply(const QJsonObject &object); - bool applyLibrary(const QJsonObject &lib, const Type type); - - bool read(const QFileInfo &fileInfo, QJsonObject *out); -}; diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 35663ddd..863c3dfd 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -14,15 +14,15 @@ */ #include "ForgeInstaller.h" -#include "DerpVersion.h" -#include "DerpLibrary.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" #include "net/HttpMetaCache.h" #include #include #include #include #include "MultiMC.h" -#include "DerpInstance.h" +#include "OneSixInstance.h" #include #include @@ -31,7 +31,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) { - std::shared_ptr newVersion; + std::shared_ptr newVersion; m_universal_url = universal_url; QuaZip zip(filename); @@ -64,7 +64,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) // read the forge version info { - newVersion = DerpVersion::fromJson(versionInfoVal.toObject()); + newVersion = OneSixVersion::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -74,7 +74,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) internalPath = installObj.value("filePath").toString(); // where do we put the library? decode the mojang path - DerpLibrary lib(libraryName); + OneSixLibrary lib(libraryName); lib.finalize(); auto cacheentry = MMC->metacache()->resolveEntry("libraries", lib.storagePath()); @@ -109,7 +109,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); } -bool ForgeInstaller::add(DerpInstance *to) +bool ForgeInstaller::add(OneSixInstance *to) { if (!BaseInstaller::add(to)) { diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h index 6490b5bf..eb8ecd79 100644 --- a/logic/ForgeInstaller.h +++ b/logic/ForgeInstaller.h @@ -20,20 +20,20 @@ #include #include -class DerpVersion; +class OneSixVersion; class ForgeInstaller : public BaseInstaller { public: ForgeInstaller(QString filename, QString universal_url); - bool add(DerpInstance *to) override; + bool add(OneSixInstance *to) override; QString id() const override { return "net.minecraftforge"; } private: // the version, read from the installer - std::shared_ptr m_forge_version; + std::shared_ptr m_forge_version; QString internalPath; QString finalPath; QString realVersionId; diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index c50ec4e4..730bcd69 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -21,10 +21,10 @@ #include "BaseInstance.h" #include "LegacyInstance.h" #include "LegacyFTBInstance.h" -#include "DerpInstance.h" -#include "DerpFTBInstance.h" +#include "OneSixInstance.h" +#include "OneSixFTBInstance.h" #include "NostalgiaInstance.h" -#include "DerpInstance.h" +#include "OneSixInstance.h" #include "BaseVersion.h" #include "MinecraftVersion.h" @@ -51,9 +51,9 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "Derp" || inst_type == "OneSix") + if (inst_type == "OneSix" || inst_type == "OneSix") { - inst = new DerpInstance(instDir, m_settings, this); + inst = new OneSixInstance(instDir, m_settings, this); } else if (inst_type == "Legacy") { @@ -67,9 +67,9 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst { inst = new LegacyFTBInstance(instDir, m_settings, this); } - else if (inst_type == "OneSixFTB" || inst_type == "DerpFTB") + else if (inst_type == "OneSixFTB" || inst_type == "OneSixFTB") { - inst = new DerpFTBInstance(instDir, m_settings, this); + inst = new OneSixFTBInstance(instDir, m_settings, this); } else { @@ -102,15 +102,15 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& switch (mcVer->type) { case MinecraftVersion::Legacy: - // TODO derp + // TODO OneSix m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; - case MinecraftVersion::Derp: - m_settings->set("InstanceType", "Derp"); - inst = new DerpInstance(instDir, m_settings, this); + case MinecraftVersion::OneSix: + m_settings->set("InstanceType", "OneSix"); + inst = new OneSixInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; @@ -137,9 +137,9 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; - case MinecraftVersion::Derp: - m_settings->set("InstanceType", "DerpFTB"); - inst = new DerpFTBInstance(instDir, m_settings, this); + case MinecraftVersion::OneSix: + m_settings->set("InstanceType", "OneSixFTB"); + inst = new OneSixFTBInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; @@ -176,8 +176,8 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne m_settings->registerSetting("InstanceType", "Legacy"); QString inst_type = m_settings->get("InstanceType").toString(); - if(inst_type == "OneSixFTB" || inst_type == "DerpFTB") - m_settings->set("InstanceType", "Derp"); + if(inst_type == "OneSixFTB" || inst_type == "OneSixFTB") + m_settings->set("InstanceType", "OneSix"); if(inst_type == "LegacyFTB") m_settings->set("InstanceType", "Legacy"); diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index 1ff17cbd..1e7d9eb6 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -68,7 +68,7 @@ public: virtual QString intendedVersionId() const override; virtual bool setIntendedVersionId(QString version) override; // the `version' of Legacy instances is defined by the launcher code. - // in contrast with Derp, where `version' is described in a json file + // in contrast with OneSix, where `version' is described in a json file virtual bool versionIsCustom() override { return false; diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 71c7011f..1ce9d3f4 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -20,9 +20,9 @@ #include "logger/QsLog.h" -#include "DerpVersion.h" -#include "DerpLibrary.h" -#include "DerpInstance.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixInstance.h" QMap LiteLoaderInstaller::m_launcherWrapperVersionMapping; @@ -38,12 +38,12 @@ LiteLoaderInstaller::LiteLoaderInstaller() } } -bool LiteLoaderInstaller::canApply(DerpInstance *instance) const +bool LiteLoaderInstaller::canApply(OneSixInstance *instance) const { return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId()); } -bool LiteLoaderInstaller::add(DerpInstance *to) +bool LiteLoaderInstaller::add(OneSixInstance *to) { if (!BaseInstaller::add(to)) { @@ -60,7 +60,7 @@ bool LiteLoaderInstaller::add(DerpInstance *to) // launchwrapper { - DerpLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); + OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); launchwrapperLib.finalize(); QJsonObject lwLibObj = launchwrapperLib.toJson(); lwLibObj.insert("insert", QString("prepend")); @@ -69,7 +69,7 @@ bool LiteLoaderInstaller::add(DerpInstance *to) // liteloader { - DerpLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId()); + OneSixLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId()); liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/"); liteloaderLib.finalize(); QJsonObject llLibObj = liteloaderLib.toJson(); diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index c128ee22..5e01b16c 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -25,8 +25,8 @@ class LiteLoaderInstaller : public BaseInstaller public: LiteLoaderInstaller(); - bool canApply(DerpInstance *instance) const override; - bool add(DerpInstance *to) override; + bool canApply(OneSixInstance *instance) const override; + bool add(OneSixInstance *to) override; private: virtual QString id() const override { return "com.mumfrey.liteloader"; } diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h index dd849ce9..504381a8 100644 --- a/logic/MinecraftVersion.h +++ b/logic/MinecraftVersion.h @@ -32,7 +32,7 @@ struct MinecraftVersion : public BaseVersion /// This version's type. Used internally to identify what kind of version this is. enum VersionType { - Derp, + OneSix, Legacy, Nostalgia } type; @@ -66,8 +66,8 @@ struct MinecraftVersion : public BaseVersion } switch (type) { - case Derp: - pre_final.append("Derp"); + case OneSix: + pre_final.append("OneSix"); break; case Legacy: pre_final.append("Legacy"); diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp index f27954a7..2e23ee71 100644 --- a/logic/NostalgiaInstance.cpp +++ b/logic/NostalgiaInstance.cpp @@ -17,7 +17,7 @@ NostalgiaInstance::NostalgiaInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) - : DerpInstance(rootDir, settings, parent) + : OneSixInstance(rootDir, settings, parent) { } diff --git a/logic/NostalgiaInstance.h b/logic/NostalgiaInstance.h index 6a6b91a1..a26f7f0a 100644 --- a/logic/NostalgiaInstance.h +++ b/logic/NostalgiaInstance.h @@ -15,9 +15,9 @@ #pragma once -#include "DerpInstance.h" +#include "OneSixInstance.h" -class NostalgiaInstance : public DerpInstance +class NostalgiaInstance : public OneSixInstance { Q_OBJECT public: diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp new file mode 100644 index 00000000..71bc1470 --- /dev/null +++ b/logic/OneSixFTBInstance.cpp @@ -0,0 +1,123 @@ +#include "OneSixFTBInstance.h" + +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "tasks/SequentialTask.h" +#include "ForgeInstaller.h" +#include "lists/ForgeVersionList.h" +#include "MultiMC.h" + +class OneSixFTBInstanceForge : public Task +{ + Q_OBJECT +public: + explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) : + Task(parent), instance(inst), version("Forge " + version) + { + } + + void executeTask() + { + for (int i = 0; i < MMC->forgelist()->count(); ++i) + { + if (MMC->forgelist()->at(i)->name() == version) + { + forgeVersion = std::dynamic_pointer_cast(MMC->forgelist()->at(i)); + break; + } + } + if (!forgeVersion) + { + emitFailed(QString("Couldn't find forge version ") + version ); + return; + } + entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + setStatus(tr("Downloading Forge...")); + fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); + connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);}); + connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge); + connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); }); + fjob->start(); + } + else + { + installForge(); + } + } + +private +slots: + void installForge() + { + setStatus(tr("Installing Forge...")); + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!instance->reloadFullVersion()) + { + emitFailed(tr("Couldn't load the version config")); + return; + } + auto version = instance->getFullVersion(); + if (!forge.add(instance)) + { + emitFailed(tr("Couldn't install Forge")); + return; + } + emitSucceeded(); + } + +private: + OneSixFTBInstance *instance; + QString version; + ForgeVersionPtr forgeVersion; + MetaEntryPtr entry; + NetJob *fjob; +}; + +OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : + OneSixInstance(rootDir, settings, parent) +{ + QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); + if (f.open(QFile::ReadOnly)) + { + QString data = QString::fromUtf8(f.readAll()); + QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); + m_forge.reset(new OneSixLibrary(match.captured())); + m_forge->finalize(); + } +} + +QString OneSixFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} + +QString OneSixFTBInstance::getStatusbarDescription() +{ + return "OneSix FTB: " + intendedVersionId(); +} +bool OneSixFTBInstance::menuActionEnabled(QString action_name) const +{ + return false; +} + +std::shared_ptr OneSixFTBInstance::doUpdate(bool only_prepare) +{ + std::shared_ptr task; + task.reset(new SequentialTask(this)); + if (!MMC->forgelist()->isLoaded()) + { + task->addTask(std::shared_ptr(MMC->forgelist()->getLoadTask())); + } + task->addTask(OneSixInstance::doUpdate(only_prepare)); + task->addTask(std::shared_ptr(new OneSixFTBInstanceForge(m_forge->version(), this, this))); + //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. + //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. + task->addTask(OneSixInstance::doUpdate(only_prepare)); + return task; +} + +#include "OneSixFTBInstance.moc" diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h new file mode 100644 index 00000000..dc028819 --- /dev/null +++ b/logic/OneSixFTBInstance.h @@ -0,0 +1,22 @@ +#pragma once + +#include "OneSixInstance.h" + +class OneSixLibrary; + +class OneSixFTBInstance : public OneSixInstance +{ + Q_OBJECT +public: + explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + virtual QString getStatusbarDescription(); + virtual bool menuActionEnabled(QString action_name) const; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + + virtual QString id() const; + +private: + std::shared_ptr m_forge; +}; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp new file mode 100644 index 00000000..c43a5187 --- /dev/null +++ b/logic/OneSixInstance.cpp @@ -0,0 +1,388 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OneSixInstance.h" + +#include + +#include "OneSixInstance_p.h" +#include "OneSixUpdate.h" +#include "OneSixVersion.h" +#include "pathutils.h" +#include "logger/QsLog.h" +#include "assets/AssetsUtils.h" +#include "MultiMC.h" +#include "icons/IconList.h" +#include "MinecraftProcess.h" +#include "gui/dialogs/OneSixModEditDialog.h" + +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) + : BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent) +{ + I_D(OneSixInstance); + d->m_settings->registerSetting("IntendedVersion", ""); + d->m_settings->registerSetting("ShouldUpdate", false); + d->version.reset(new OneSixVersion(this, this)); + if (QDir(instanceRoot()).exists("version.json")) + { + reloadFullVersion(); + } + else + { + clearFullVersion(); + } +} + +std::shared_ptr OneSixInstance::doUpdate(bool only_prepare) +{ + return std::shared_ptr(new OneSixUpdate(this, only_prepare)); +} + +QString replaceTokensIn(QString text, QMap with) +{ + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) + { + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) + { + result.append(*iter); + } + head += token_regexp.matchedLength(); + tail = head; + } + result.append(text.mid(tail)); + return result; +} + +QDir OneSixInstance::reconstructAssets(std::shared_ptr version) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); + + if (!indexFile.exists()) + { + QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; + return virtualRoot; + } + + QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); + + if (loadAssetsIndex && index.isVirtual) + { + QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = PathCombine(virtualRoot.path(), map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = + PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); + QFile original(original_path); + if(!original.exists()) + continue; + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // QLOG_DEBUG() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + QLOG_DEBUG() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + } + + return virtualRoot; +} + +QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) +{ + I_D(OneSixInstance); + auto version = d->version; + QString args_pattern = version->minecraftArguments; + + QMap token_mapping; + // yggdrasil! + token_mapping["auth_username"] = account->username(); + token_mapping["auth_session"] = account->sessionId(); + token_mapping["auth_access_token"] = account->accessToken(); + token_mapping["auth_player_name"] = account->currentProfile()->name; + token_mapping["auth_uuid"] = account->currentProfile()->id; + + // this is for offline?: + /* + map["auth_player_name"] = "Player"; + map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; + */ + + // these do nothing and are stupid. + token_mapping["profile_name"] = name(); + token_mapping["version_name"] = version->id; + + QString absRootDir = QDir(minecraftRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); + + auto user = account->user(); + QJsonObject userAttrs; + for (auto key : user.properties.keys()) + { + auto array = QJsonArray::fromStringList(user.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + + token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); + token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = version->assets; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) + { + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; +} + +MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) +{ + I_D(OneSixInstance); + + QIcon icon = MMC->icons()->getIcon(iconKey()); + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); + + auto version = d->version; + if (!version) + return nullptr; + QString launchScript; + { + auto libs = version->getActiveNormalLibs(); + for (auto lib : libs) + { + QFileInfo fi(QString("libraries/") + lib->storagePath()); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; + QFileInfo fi(targetstr); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + launchScript += "mainClass " + version->mainClass + "\n"; + + for (auto param : processMinecraftArgs(account)) + { + launchScript += "param " + param + "\n"; + } + + // Set the width and height for 1.6 instances + bool maximize = settings().get("LaunchMaximized").toBool(); + if (maximize) + { + // this is probably a BAD idea + // launchScript += "param --fullscreen\n"; + } + else + { + launchScript += + "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; + launchScript += + "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; + } + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + launchScript += "windowTitle " + windowTitle() + "\n"; + for(auto native: version->getActiveNativeLibs()) + { + QFileInfo finfo(PathCombine("libraries", native->storagePath())); + launchScript += "ext " + finfo.absoluteFilePath() + "\n"; + } + launchScript += "natives " + natives_dir.absolutePath() + "\n"; + launchScript += "launch onesix\n"; + + // create the process and set its parameters + MinecraftProcess *proc = new MinecraftProcess(this); + proc->setWorkdir(minecraftRoot()); + proc->setLaunchScript(launchScript); + // proc->setNativeFolder(natives_dir.absolutePath()); + return proc; +} + +void OneSixInstance::cleanupAfterRun() +{ + QString target_dir = PathCombine(instanceRoot(), "natives/"); + QDir dir(target_dir); + dir.removeRecursively(); +} + +std::shared_ptr OneSixInstance::loaderModList() +{ + I_D(OneSixInstance); + if (!d->loader_mod_list) + { + d->loader_mod_list.reset(new ModList(loaderModsDir())); + } + d->loader_mod_list->update(); + return d->loader_mod_list; +} + +std::shared_ptr OneSixInstance::resourcePackList() +{ + I_D(OneSixInstance); + if (!d->resource_pack_list) + { + d->resource_pack_list.reset(new ModList(resourcePacksDir())); + } + d->resource_pack_list->update(); + return d->resource_pack_list; +} + +QDialog *OneSixInstance::createModEditDialog(QWidget *parent) +{ + return new OneSixModEditDialog(this, parent); +} + +bool OneSixInstance::setIntendedVersionId(QString version) +{ + settings().set("IntendedVersion", version); + setShouldUpdate(true); + QFile::remove(PathCombine(instanceRoot(), "version.json")); + clearFullVersion(); + return true; +} + +QString OneSixInstance::intendedVersionId() const +{ + return settings().get("IntendedVersion").toString(); +} + +void OneSixInstance::setShouldUpdate(bool val) +{ + settings().set("ShouldUpdate", val); +} + +bool OneSixInstance::shouldUpdate() const +{ + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) + { + return intendedVersionId() != currentVersionId(); + } + return true; +} + +bool OneSixInstance::versionIsCustom() +{ + QDir patches(PathCombine(instanceRoot(), "patches/")); + return QFile::exists(PathCombine(instanceRoot(), "custom.json")) + || (patches.exists() && patches.count() >= 0); +} + +QString OneSixInstance::currentVersionId() const +{ + return intendedVersionId(); +} + +bool OneSixInstance::reloadFullVersion(QWidget *widgetParent) +{ + I_D(OneSixInstance); + + bool ret = d->version->reload(widgetParent); + emit versionReloaded(); + return ret; +} + +void OneSixInstance::clearFullVersion() +{ + I_D(OneSixInstance); + d->version->clear(); + emit versionReloaded(); +} + +std::shared_ptr OneSixInstance::getFullVersion() +{ + I_D(OneSixInstance); + return d->version; +} + +QString OneSixInstance::defaultBaseJar() const +{ + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; +} + +QString OneSixInstance::defaultCustomBaseJar() const +{ + return PathCombine(instanceRoot(), "custom.jar"); +} + +bool OneSixInstance::menuActionEnabled(QString action_name) const +{ + if (action_name == "actionChangeInstLWJGLVersion") + return false; + return true; +} + +QString OneSixInstance::getStatusbarDescription() +{ + QString descr = "OneSix : " + intendedVersionId(); + if (versionIsCustom()) + { + descr + " (custom)"; + } + return descr; +} + +QString OneSixInstance::loaderModsDir() const +{ + return PathCombine(minecraftRoot(), "mods"); +} + +QString OneSixInstance::resourcePacksDir() const +{ + return PathCombine(minecraftRoot(), "resourcepacks"); +} + +QString OneSixInstance::instanceConfigFolder() const +{ + return PathCombine(minecraftRoot(), "config"); +} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h new file mode 100644 index 00000000..94577828 --- /dev/null +++ b/logic/OneSixInstance.h @@ -0,0 +1,75 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance.h" + +#include "OneSixVersion.h" +#include "ModList.h" + +class OneSixInstance : public BaseInstance +{ + Q_OBJECT +public: + explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + + ////// Mod Lists ////// + std::shared_ptr loaderModList(); + std::shared_ptr resourcePackList(); + + ////// Directories ////// + QString resourcePacksDir() const; + QString loaderModsDir() const; + virtual QString instanceConfigFolder() const override; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; + + virtual void cleanupAfterRun() override; + + virtual QString intendedVersionId() const override; + virtual bool setIntendedVersionId(QString version) override; + + virtual QString currentVersionId() const override; + + virtual bool shouldUpdate() const override; + virtual void setShouldUpdate(bool val) override; + + virtual QDialog *createModEditDialog(QWidget *parent) override; + + /// reload the full version json files. return true on success! + bool reloadFullVersion(QWidget *widgetParent = 0); + /// clears all version information in preparation for an update + void clearFullVersion(); + /// get the current full version info + std::shared_ptr getFullVersion(); + /// is the current version original, or custom? + virtual bool versionIsCustom() override; + + virtual QString defaultBaseJar() const override; + virtual QString defaultCustomBaseJar() const override; + + virtual bool menuActionEnabled(QString action_name) const override; + virtual QString getStatusbarDescription() override; + +signals: + void versionReloaded(); + +private: + QStringList processMinecraftArgs(MojangAccountPtr account); + QDir reconstructAssets(std::shared_ptr version); +}; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h new file mode 100644 index 00000000..a2b70ac9 --- /dev/null +++ b/logic/OneSixInstance_p.h @@ -0,0 +1,27 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance_p.h" +#include "OneSixVersion.h" +#include "ModList.h" + +struct OneSixInstancePrivate : public BaseInstancePrivate +{ + std::shared_ptr version; + std::shared_ptr loader_mod_list; + std::shared_ptr resource_pack_list; +}; diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp new file mode 100644 index 00000000..4d892d53 --- /dev/null +++ b/logic/OneSixLibrary.cpp @@ -0,0 +1,268 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "OneSixLibrary.h" +#include "OneSixRule.h" +#include "OpSys.h" +#include "logic/net/URLConstants.h" +#include +#include +#include "logger/QsLog.h" + +void OneSixLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void OneSixLibrary::setName(const QString &name) +{ + m_name = name; +} +void OneSixLibrary::setBaseUrl(const QString &base_url) +{ + m_base_url = base_url; +} +void OneSixLibrary::setIsNative() +{ + m_is_native = true; +} +void OneSixLibrary::addNative(OpSys os, const QString &suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void OneSixLibrary::setRules(QList> rules) +{ + m_rules = rules; +} +bool OneSixLibrary::isActive() const +{ + return m_is_active; +} +bool OneSixLibrary::isNative() const +{ + return m_is_native; +} +QString OneSixLibrary::downloadUrl() const +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString OneSixLibrary::storagePath() const +{ + return m_storage_path; +} + +void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString OneSixLibrary::absoluteUrl() const +{ + return m_absolute_url; +} + +void OneSixLibrary::setHint(const QString &hint) +{ + m_hint = hint; +} + +QString OneSixLibrary::hint() const +{ + return m_hint; +} + +bool OneSixLibrary::filesExist() +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QFileInfo info32(PathCombine("libraries", cooked_storage)); + if (!info32.exists()) + { + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + QFileInfo info64(PathCombine("libraries", cooked_storage)); + if (!info64.exists()) + { + return false; + } + } + else + { + QFileInfo info(PathCombine("libraries", storage)); + if (!info.exists()) + { + return false; + } + } + return true; +} + +bool OneSixLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "64"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if(!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + +QJsonObject OneSixLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE) + libRoot.insert("url", m_base_url); + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h new file mode 100644 index 00000000..52f89969 --- /dev/null +++ b/logic/OneSixLibrary.h @@ -0,0 +1,132 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "logic/net/URLConstants.h" +#include "OpSys.h" + +class Rule; + +class OneSixLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + QList> m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// download hint - how to actually get the library + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap m_native_suffixes; + +public: + QStringList extract_excludes; + +public: + /// Constructor + OneSixLibrary(const QString &name) + { + m_name = name; + } + + /// Returns the raw name field + QString rawName() const + { + return m_name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(const QString &name); + /// get a decent-looking name + QString name() const + { + return m_decentname; + } + /// get a decent-looking version + QString version() const + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() const + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(const QString &base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, const QString &suffix); + /// Set the load rules + void setRules(QList> rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + /// Returns true if the library is native + bool isNative() const; + /// Get the URL to download the library from + QString downloadUrl() const; + /// Get the relative path where the library should be saved + QString storagePath() const; + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(const QString &hint); + QString hint() const; + + bool extractTo(QString target_dir); + bool filesExist(); +}; diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp new file mode 100644 index 00000000..d8d13b50 --- /dev/null +++ b/logic/OneSixRule.cpp @@ -0,0 +1,89 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "OneSixRule.h" + +QList> rulesFromJsonV4(const QJsonObject &objectWithRules) +{ + QList> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } + return rules; +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h new file mode 100644 index 00000000..426e2886 --- /dev/null +++ b/logic/OneSixRule.h @@ -0,0 +1,98 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "logic/OneSixLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList> rulesFromJsonV4(const QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(OneSixLibrary *parent) = 0; + +public: + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(OneSixLibrary *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } + ; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; + +protected: + virtual bool applies(OneSixLibrary *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr(new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies(OneSixLibrary *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } +}; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp new file mode 100644 index 00000000..f51b6c23 --- /dev/null +++ b/logic/OneSixUpdate.cpp @@ -0,0 +1,372 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiMC.h" +#include "OneSixUpdate.h" + +#include + +#include +#include +#include +#include + +#include "BaseInstance.h" +#include "lists/MinecraftVersionList.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixInstance.h" +#include "net/ForgeMirrors.h" +#include "net/URLConstants.h" +#include "assets/AssetsUtils.h" + +#include "pathutils.h" +#include + +OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) + : Task(parent), m_inst(inst), m_only_prepare(only_prepare) +{ +} + +void OneSixUpdate::executeTask() +{ + QString intendedVersion = m_inst->intendedVersionId(); + + // Make directories + QDir mcDir(m_inst->minecraftRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed("Failed to create bin folder."); + return; + } + + if (m_only_prepare) + { + prepareForLaunch(); + return; + } + + if (m_inst->shouldUpdate()) + { + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast( + MMC->minecraftlist()->findVersion(intendedVersion)); + if (targetVersion == nullptr) + { + // don't do anything if it was invalid + emitFailed("The specified Minecraft version is invalid. Choose a different one."); + return; + } + versionFileStart(); + } + else + { + jarlibStart(); + } +} + +void OneSixUpdate::versionFileStart() +{ + QLOG_INFO() << m_inst->name() << ": getting version file."; + setStatus(tr("Getting the version files from Mojang...")); + + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void OneSixUpdate::versionFileFinished() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + OneSixInstance *inst = (OneSixInstance *)m_inst; + + QString version_id = targetVersion->descriptor(); + QString inst_dir = m_inst->instanceRoot(); + // save the version file in $instanceId/version.json + { + QString version1 = PathCombine(inst_dir, "/version.json"); + ensureFilePathExists(version1); + // FIXME: detect errors here, download to a temp file, swap + QSaveFile vfile1(version1); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed("Can't open " + version1 + " for writing."); + return; + } + auto data = std::dynamic_pointer_cast(DlJob)->m_data; + qint64 actual = 0; + if ((actual = vfile1.write(data)) != data.size()) + { + emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + + data.size() + '.'); + return; + } + if (!vfile1.commit()) + { + emitFailed("Can't commit changes to " + version1); + return; + } + } + + // the version is downloaded safely. update is 'done' at this point + m_inst->setShouldUpdate(false); + + // delete any custom version inside the instance (it's no longer relevant, we did an update) + QString custom = PathCombine(inst_dir, "/custom.json"); + QFile finfo(custom); + if (finfo.exists()) + { + finfo.remove(); + } + inst->reloadFullVersion(); + + jarlibStart(); +} + +void OneSixUpdate::versionFileFailed() +{ + emitFailed("Failed to download the version description. Try again."); +} + +void OneSixUpdate::assetIndexStart() +{ + setStatus(tr("Updating assets index...")); + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; + QString localPath = assetName + ".json"; + auto job = new NetJob("Asset index for " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + job->addNetAction(CacheDownload::make(indexUrl, entry)); + jarlibDownloadJob.reset(job); + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void OneSixUpdate::assetIndexFinished() +{ + AssetsIndex index; + + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + + QString asset_fname = "assets/indexes/" + assetName + ".json"; + if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index)) + { + emitFailed("Failed to read the assets index!"); + } + + QList dls; + for (auto object : index.objects.values()) + { + QString objectName = object.hash.left(2) + "/" + object.hash; + QFileInfo objectFile("assets/objects/" + objectName); + if ((!objectFile.isFile()) || (objectFile.size() != object.size)) + { + auto objectDL = MD5EtagDownload::make( + QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), + objectFile.filePath()); + objectDL->m_total_progress = object.size; + dls.append(objectDL); + } + } + if (dls.size()) + { + setStatus(tr("Getting the assets files from Mojang...")); + auto job = new NetJob("Assets for " + inst->name()); + for (auto dl : dls) + job->addNetAction(dl); + jarlibDownloadJob.reset(job); + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + jarlibDownloadJob->start(); + return; + } + assetsFinished(); +} + +void OneSixUpdate::assetIndexFailed() +{ + emitFailed("Failed to download the assets index!"); +} + +void OneSixUpdate::assetsFinished() +{ + prepareForLaunch(); +} + +void OneSixUpdate::assetsFailed() +{ + emitFailed("Failed to download assets!"); +} + +void OneSixUpdate::jarlibStart() +{ + setStatus(tr("Getting the library files from Mojang...")); + QLOG_INFO() << m_inst->name() << ": downloading libraries"; + OneSixInstance *inst = (OneSixInstance *)m_inst; + bool successful = inst->reloadFullVersion(); + if (!successful) + { + emitFailed("Failed to load the version description file. It might be " + "corrupted, missing or simply too new."); + return; + } + + // Build a list of URLs that will need to be downloaded. + std::shared_ptr version = inst->getFullVersion(); + // minecraft.jar for this version + { + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath; + + auto job = new NetJob("Libraries for instance " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); + + jarlibDownloadJob.reset(job); + } + + auto libs = version->getActiveNativeLibs(); + libs.append(version->getActiveNormalLibs()); + + auto metacache = MMC->metacache(); + QList ForgeLibs; + for (auto lib : libs) + { + if (lib->hint() == "local") + continue; + + QString raw_storage = lib->storagePath(); + QString raw_dl = lib->downloadUrl(); + + auto f = [&](QString storage, QString dl) + { + auto entry = metacache->resolveEntry("libraries", storage); + if (entry->stale) + { + if (lib->hint() == "forge-pack-xz") + { + ForgeLibs.append(ForgeXzDownload::make(storage, entry)); + } + else + { + jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); + } + } + }; + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); + } + else + { + f(raw_storage, raw_dl); + } + } + // TODO: think about how to propagate this from the original json file... or IF AT ALL + QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list"; + if (!ForgeLibs.empty()) + { + jarlibDownloadJob->addNetAction( + ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList)); + } + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void OneSixUpdate::jarlibFinished() +{ + assetIndexStart(); +} + +void OneSixUpdate::jarlibFailed() +{ + QStringList failed = jarlibDownloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed("Failed to download the following files:\n" + failed_all + + "\n\nPlease try again."); +} + +void OneSixUpdate::prepareForLaunch() +{ + setStatus(tr("Preparing for launch...")); + QLOG_INFO() << m_inst->name() << ": preparing for launch"; + auto OneSix_inst = (OneSixInstance *)m_inst; + + // delete any leftovers, if they are present. + OneSix_inst->cleanupAfterRun(); + + QString natives_dir_raw = PathCombine(OneSix_inst->instanceRoot(), "natives/"); + auto version = OneSix_inst->getFullVersion(); + if (!version) + { + emitFailed("The version information for this instance is not complete. Try re-creating " + "it or changing the version."); + return; + } + /* + for (auto lib : version->getActiveNativeLibs()) + { + if (!lib->filesExist()) + { + emitFailed("Native library is missing some files:\n" + lib->storagePath() + + "\n\nRun the instance at least once in online mode to get all the " + "required files."); + return; + } + if (!lib->extractTo(natives_dir_raw)) + { + emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + + natives_dir_raw + + "\n\nMake sure MultiMC has appropriate permissions and there is enough " + "space on the storage device."); + return; + } + } +*/ + emitSucceeded(); +} diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h new file mode 100644 index 00000000..bc717a94 --- /dev/null +++ b/logic/OneSixUpdate.h @@ -0,0 +1,63 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "logic/net/NetJob.h" +#include "logic/tasks/Task.h" + +class MinecraftVersion; +class BaseInstance; + +class OneSixUpdate : public Task +{ + Q_OBJECT +public: + explicit OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); + virtual void executeTask(); + +private +slots: + void versionFileStart(); + void versionFileFinished(); + void versionFileFailed(); + + void jarlibStart(); + void jarlibFinished(); + void jarlibFailed(); + + void assetIndexStart(); + void assetIndexFinished(); + void assetIndexFailed(); + + void assetsFinished(); + void assetsFailed(); + + // extract the appropriate libraries + void prepareForLaunch(); + +private: + NetJobPtr specificVersionDownloadJob; + NetJobPtr jarlibDownloadJob; + + // target version, determined during this task + std::shared_ptr targetVersion; + BaseInstance *m_inst = nullptr; + bool m_only_prepare = false; +}; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 3571be9b..3db3bd77 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -13,228 +13,60 @@ * limitations under the License. */ -#include "logic/DerpVersion.h" -#include "logic/DerpLibrary.h" -#include "logic/DerpRule.h" +#include "OneSixVersion.h" -#include "logger/QsLog.h" +#include -std::shared_ptr fromJsonV4(QJsonObject root, - std::shared_ptr fullVersion) -{ - fullVersion->id = root.value("id").toString(); - - fullVersion->mainClass = root.value("mainClass").toString(); - auto procArgsValue = root.value("processArguments"); - if (procArgsValue.isString()) - { - fullVersion->processArguments = procArgsValue.toString(); - QString toCompare = fullVersion->processArguments.toLower(); - if (toCompare == "legacy") - { - fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - fullVersion->minecraftArguments = - "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - } - - auto minecraftArgsValue = root.value("minecraftArguments"); - if (minecraftArgsValue.isString()) - { - fullVersion->minecraftArguments = minecraftArgsValue.toString(); - } - - auto minecraftTypeValue = root.value("type"); - if (minecraftTypeValue.isString()) - { - fullVersion->type = minecraftTypeValue.toString(); - } - - fullVersion->releaseTime = root.value("releaseTime").toString(); - fullVersion->time = root.value("time").toString(); - - auto assetsID = root.value("assets"); - if (assetsID.isString()) - { - fullVersion->assets = assetsID.toString(); - } - else - { - fullVersion->assets = "legacy"; - } - - QLOG_DEBUG() << "Assets version:" << fullVersion->assets; - - // Iterate through the list, if it's a list. - auto librariesValue = root.value("libraries"); - if (!librariesValue.isArray()) - return fullVersion; - - QJsonArray libList = root.value("libraries").toArray(); - for (auto libVal : libList) - { - if (!libVal.isObject()) - { - continue; - } - - QJsonObject libObj = libVal.toObject(); - - // Library name - auto nameVal = libObj.value("name"); - if (!nameVal.isString()) - continue; - std::shared_ptr library(new DerpLibrary(nameVal.toString())); - - auto urlVal = libObj.value("url"); - if (urlVal.isString()) - { - library->setBaseUrl(urlVal.toString()); - } - auto hintVal = libObj.value("MMC-hint"); - if (hintVal.isString()) - { - library->setHint(hintVal.toString()); - } - auto urlAbsVal = libObj.value("MMC-absoluteUrl"); - auto urlAbsuVal = libObj.value("MMC-absulute_url"); // compatibility - if (urlAbsVal.isString()) - { - library->setAbsoluteUrl(urlAbsVal.toString()); - } - else if (urlAbsuVal.isString()) - { - library->setAbsoluteUrl(urlAbsuVal.toString()); - } - // Extract excludes (if any) - auto extractVal = libObj.value("extract"); - if (extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if (excludesVal.isArray()) - { - auto excludesList = excludesVal.toArray(); - for (auto excludeVal : excludesList) - { - if (excludeVal.isString()) - excludes.append(excludeVal.toString()); - } - library->extract_excludes = excludes; - } - } +#include "OneSixVersionBuilder.h" - auto nativesVal = libObj.value("natives"); - if (nativesVal.isObject()) - { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - auto iter = nativesObj.begin(); - while (iter != nativesObj.end()) - { - auto osType = OpSys_fromString(iter.key()); - if (osType == Os_Other) - continue; - if (!iter.value().isString()) - continue; - library->addNative(osType, iter.value().toString()); - iter++; - } - } - library->setRules(rulesFromJsonV4(libObj)); - library->finalize(); - fullVersion->libraries.append(library); - } - return fullVersion; +OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); } -std::shared_ptr DerpVersion::fromJson(QJsonObject root) +bool OneSixVersion::reload(QWidget *widgetParent) { - std::shared_ptr readVersion(new DerpVersion()); - int launcher_ver = readVersion->minimumLauncherVersion = - root.value("minimumLauncherVersion").toDouble(); - - // ADD MORE HERE :D - if (launcher_ver > 0 && launcher_ver <= 13) - return fromJsonV4(root, readVersion); - else - { - return std::shared_ptr(); - } + return OneSixVersionBuilder::build(this, m_instance, widgetParent); } -std::shared_ptr DerpVersion::fromFile(QString filepath) +void OneSixVersion::clear() { - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) - { - return std::shared_ptr(); - } - - auto data = file.readAll(); - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - return std::shared_ptr(); - } - - if (!jsonDoc.isObject()) - { - return std::shared_ptr(); - } - QJsonObject root = jsonDoc.object(); - auto version = fromJson(root); - if (version) - version->original_file = filepath; - return version; + id.clear(); + time.clear(); + releaseTime.clear(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + libraries.clear(); } -bool DerpVersion::toOriginalFile() +void OneSixVersion::dump() const { - if (original_file.isEmpty()) - return false; - QSaveFile file(original_file); - if (!file.open(QIODevice::WriteOnly)) - { - return false; - } - // serialize base attributes (those we care about anyway) - QJsonObject root; - root.insert("minecraftArguments", minecraftArguments); - root.insert("mainClass", mainClass); - root.insert("minimumLauncherVersion", minimumLauncherVersion); - root.insert("time", time); - root.insert("id", id); - root.insert("type", type); - // screw processArguments - root.insert("releaseTime", releaseTime); - QJsonArray libarray; - for (const auto &lib : libraries) + qDebug().nospace() << "OneSixVersion(" + << "\n\tid=" << id + << "\n\ttime=" << time + << "\n\treleaseTime=" << releaseTime + << "\n\ttype=" << type + << "\n\tassets=" << assets + << "\n\tprocessArguments=" << processArguments + << "\n\tminecraftArguments=" << minecraftArguments + << "\n\tminimumLauncherVersion=" << minimumLauncherVersion + << "\n\tmainClass=" << mainClass + << "\n\tlibraries="; + for (auto lib : libraries) { - libarray.append(lib->toJson()); + qDebug().nospace() << "\n\t\t" << lib.get(); } - if (libarray.count()) - root.insert("libraries", libarray); - QJsonDocument doc(root); - file.write(doc.toJson()); - return file.commit(); + qDebug().nospace() << "\n)"; } -QList> DerpVersion::getActiveNormalLibs() +QList > OneSixVersion::getActiveNormalLibs() { - QList> output; + QList > output; for (auto lib : libraries) { if (lib->isActive() && !lib->isNative()) @@ -245,9 +77,9 @@ QList> DerpVersion::getActiveNormalLibs() return output; } -QList> DerpVersion::getActiveNativeLibs() +QList > OneSixVersion::getActiveNativeLibs() { - QList> output; + QList > output; for (auto lib : libraries) { if (lib->isActive() && lib->isNative()) @@ -258,17 +90,17 @@ QList> DerpVersion::getActiveNativeLibs() return output; } -void DerpVersion::externalUpdateStart() -{ - beginResetModel(); -} - -void DerpVersion::externalUpdateFinish() +std::shared_ptr OneSixVersion::fromJson(const QJsonObject &obj) { - endResetModel(); + std::shared_ptr version(new OneSixVersion(0)); + if (OneSixVersionBuilder::read(version.get(), obj)) + { + return version; + } + return 0; } -QVariant DerpVersion::data(const QModelIndex &index, int role) const +QVariant OneSixVersion::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -296,7 +128,7 @@ QVariant DerpVersion::data(const QModelIndex &index, int role) const return QVariant(); } -Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const +Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; @@ -312,7 +144,7 @@ Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const // return QAbstractListModel::flags(index); } -QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int role) const +QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); @@ -329,12 +161,34 @@ QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int r } } -int DerpVersion::rowCount(const QModelIndex &parent) const +int OneSixVersion::rowCount(const QModelIndex &parent) const { return libraries.size(); } -int DerpVersion::columnCount(const QModelIndex &parent) const +int OneSixVersion::columnCount(const QModelIndex &parent) const { return 3; } + +QDebug operator<<(QDebug &dbg, const OneSixVersion *version) +{ + version->dump(); + return dbg.maybeSpace(); +} +QDebug operator<<(QDebug &dbg, const OneSixLibrary *library) +{ + dbg.nospace() << "OneSixLibrary(" + << "\n\t\t\trawName=" << library->rawName() + << "\n\t\t\tname=" << library->name() + << "\n\t\t\tversion=" << library->version() + << "\n\t\t\ttype=" << library->type() + << "\n\t\t\tisActive=" << library->isActive() + << "\n\t\t\tisNative=" << library->isNative() + << "\n\t\t\tdownloadUrl=" << library->downloadUrl() + << "\n\t\t\tstoragePath=" << library->storagePath() + << "\n\t\t\tabsolutePath=" << library->absoluteUrl() + << "\n\t\t\thint=" << library->hint(); + dbg.nospace() << "\n\t\t)"; + return dbg.maybeSpace(); +} diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index e8f744aa..00afd010 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -14,15 +14,23 @@ */ #pragma once -#include + +#include + +#include +#include #include -class DerpLibrary; +#include "OneSixLibrary.h" + +class OneSixInstance; -class DerpVersion : public QAbstractListModel +class OneSixVersion : public QAbstractListModel { - // Things required to implement the Qt list model + Q_OBJECT public: + explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant headerData(int section, Qt::Orientation orientation, @@ -30,24 +38,19 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - // serialization/deserialization -public: - bool toOriginalFile(); - static std::shared_ptr fromJson(QJsonObject root); - static std::shared_ptr fromFile(QString filepath); + bool reload(QWidget *widgetParent); + void clear(); + + void dump() const; public: - QList> getActiveNormalLibs(); - QList> getActiveNativeLibs(); - // called when something starts/stops messing with the object - // FIXME: these are ugly in every possible way. - void externalUpdateStart(); - void externalUpdateFinish(); + QList> getActiveNormalLibs(); + QList> getActiveNativeLibs(); + + static std::shared_ptr fromJson(const QJsonObject &obj); // data members public: - /// file this was read from. blank, if none - QString original_file; /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; /// Last updated time - as a string @@ -81,7 +84,7 @@ public: QString mainClass; /// the list of libs - both active and inactive, native and java - QList> libraries; + QList> libraries; /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. @@ -103,4 +106,10 @@ public: } */ // QList rules; + +private: + OneSixInstance *m_instance; }; + +QDebug operator<<(QDebug &dbg, const OneSixVersion *version); +QDebug operator<<(QDebug &dbg, const OneSixLibrary *library); diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp new file mode 100644 index 00000000..85f55a52 --- /dev/null +++ b/logic/OneSixVersionBuilder.cpp @@ -0,0 +1,446 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OneSixVersionBuilder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OneSixVersion.h" +#include "OneSixInstance.h" +#include "OneSixRule.h" +#include "logger/QsLog.h" + +OneSixVersionBuilder::OneSixVersionBuilder() +{ + +} + +bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.m_widgetParent = widgetParent; + return builder.build(); +} + +bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.m_widgetParent = 0; + return builder.read(obj); +} + +bool OneSixVersionBuilder::build() +{ + m_version->clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // version.json -> patches/*.json -> custom.json + + // version.json + { + QLOG_INFO() << "Reading version.json"; + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + + // patches/ + { + // load all, put into map for ordering, apply in the right order + + QMap objects; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QLOG_INFO() << "Reading" << info.fileName(); + QJsonObject obj; + if (!read(info, &obj)) + { + return false; + } + if (!obj.contains("order") || !obj.value("order").isDouble()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Missing or invalid 'order' in %1").arg(info.absoluteFilePath())); + return false; + } + objects.insert(obj.value("order").toDouble(), obj); + } + for (auto object : objects.values()) + { + qDebug() << "Applying object with order" << objects.key(object); + if (!apply(object)) + { + return false; + } + } + } + + // custom.json + { + if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QLOG_INFO() << "Reading custom.json"; + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + } + + return true; +} + +bool OneSixVersionBuilder::read(const QJsonObject &obj) +{ + m_version->clear(); + + return apply(obj); +} + +void applyString(const QJsonObject &obj, const QString &key, QString &out, const bool onlyOverride = true) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + out = obj.value(key).toString(); + } + else if (!onlyOverride) + { + if (obj.contains("+" + key) && obj.value("+" + key).isString()) + { + out += obj.value("+" + key).toString(); + } + else if (obj.contains("-" + key) && obj.value("-" + key).isString()) + { + out.remove(obj.value("-" + key).toString()); + } + } +} +void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(OneSixLibrary::*func)(const QString &val)) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + (lib.get()->*func)(obj.value(key).toString()); + } +} +bool OneSixVersionBuilder::apply(const QJsonObject &object) +{ + applyString(object, "id", m_version->id); + applyString(object, "mainClass", m_version->mainClass); + applyString(object, "minecraftArguments", m_version->minecraftArguments, false); + applyString(object, "processArguments", m_version->processArguments, false); + if (m_version->minecraftArguments.isEmpty()) + { + const QString toCompare = m_version->processArguments.toLower(); + if (toCompare == "legacy") + { + m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + } + } + applyString(object, "type", m_version->type); + applyString(object, "releaseTime", m_version->releaseTime); + applyString(object, "time", m_version->time); + applyString(object, "assets", m_version->assets); + { + if (m_version->assets.isEmpty()) + { + m_version->assets = "legacy"; + } + } + if (object.contains("minimumLauncherVersion")) + { + auto minLauncherVersionVal = object.value("minimumLauncherVersion"); + if (minLauncherVersionVal.isDouble()) + { + m_version->minimumLauncherVersion = minLauncherVersionVal.toDouble(); + } + } + + // libraries + if (object.contains("libraries")) + { + m_version->libraries.clear(); + auto librariesValue = object.value("libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + auto array = librariesValue.toArray(); + for (auto libVal : array) + { + if (libVal.isObject()) + { + if (!applyLibrary(libVal.toObject(), Override)) + { + return false; + } + } + + } + } + + // +libraries + if (object.contains("+libraries")) + { + auto librariesValue = object.value("+libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + for (auto libVal : librariesValue.toArray()) + { + if (libVal.isObject()) + { + applyLibrary(libVal.toObject(), Add); + } + + } + } + + // -libraries + if (object.contains("-libraries")) + { + auto librariesValue = object.value("-libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + for (auto libVal : librariesValue.toArray()) + { + if (libVal.isObject()) + { + applyLibrary(libVal.toObject(), Remove); + } + + } + } + + return true; +} + +int findLibrary(QList > haystack, const QString &needle) +{ + for (int i = 0; i < haystack.size(); ++i) + { + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(haystack.at(i)->rawName()) != -1) + { + return i; + } + } + return -1; +} + +bool OneSixVersionBuilder::applyLibrary(const QJsonObject &lib, const OneSixVersionBuilder::Type type) +{ + // Library name + auto nameVal = lib.value("name"); + if (!nameVal.isString()) + { + return false; + } + auto name = nameVal.toString(); + + if (type == Remove) + { + int index = findLibrary(m_version->libraries, name); + if (index >= 0) + { + m_version->libraries.removeAt(index); + } + return true; + } + + if (type == Add && !lib.contains("insert")) + { + return false; + } + + std::shared_ptr library; + + if (lib.value("insert").toString() != "apply" && type == Add) + { + QMutableListIterator > it(m_version->libraries); + while (it.hasNext()) + { + if (it.next()->rawName() == name) + { + it.remove(); + } + } + } + + if (lib.value("insert").toString() == "apply" && type == Add) + { + library = m_version->libraries[findLibrary(m_version->libraries, name)]; + } + else + { + library.reset(new OneSixLibrary(nameVal.toString())); + } + + applyString(lib, "url", library, &OneSixLibrary::setBaseUrl); + applyString(lib, "MMC-hint", library, &OneSixLibrary::setHint); + applyString(lib, "MMC-absulute_url", library, &OneSixLibrary::setAbsoluteUrl); + applyString(lib, "MMC-absoluteUrl", library, &OneSixLibrary::setAbsoluteUrl); + + auto extractVal = lib.value("extract"); + if (extractVal.isObject()) + { + QStringList excludes; + auto extractObj = extractVal.toObject(); + auto excludesVal = extractObj.value("exclude"); + if (excludesVal.isArray()) + { + auto excludesList = excludesVal.toArray(); + for (auto excludeVal : excludesList) + { + if (excludeVal.isString()) + { + excludes.append(excludeVal.toString()); + } + } + library->extract_excludes = excludes; + } + } + + auto nativesVal = lib.value("natives"); + if (nativesVal.isObject()) + { + library->setIsNative(); + auto nativesObj = nativesVal.toObject(); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + auto osType = OpSys_fromString(it.key()); + if (osType == Os_Other) + { + continue; + } + if (!it.value().isString()) + { + continue; + } + library->addNative(osType, it.value().toString()); + } + } + + if (lib.contains("rules")) + { + library->setRules(rulesFromJsonV4(lib)); + } + library->finalize(); + if (type == Override) + { + m_version->libraries.append(library); + } + else if (lib.value("insert").toString() != "apply") + { + if (lib.value("insert").toString() == "append") + { + m_version->libraries.append(library); + } + else if (lib.value("insert").toString() == "prepend") + { + m_version->libraries.prepend(library); + } + else if (lib.value("insert").isObject()) + { + QJsonObject insertObj = lib.value("insert").toObject(); + if (insertObj.isEmpty()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("'insert' object empty")); + return false; + } + const QString key = insertObj.keys().first(); + const QString value = insertObj.value(key).toString(); + const int index = findLibrary(m_version->libraries, value); + if (index >= 0) + { + if (key == "before") + { + m_version->libraries.insert(index, library); + } + else if (key == "after") + { + m_version->libraries.insert(index + 1, library); + } + else + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); + return false; + } + } + } + else + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); + return false; + } + } + return true; +} + +bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, QJsonObject *out) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); + return false; + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to parse %1: %2 at %3").arg(file.fileName(), error.errorString()).arg(error.offset)); + return false; + } + *out = doc.object(); + return true; +} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h new file mode 100644 index 00000000..a3ba331c --- /dev/null +++ b/logic/OneSixVersionBuilder.h @@ -0,0 +1,53 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class OneSixVersion; +class OneSixInstance; +class QWidget; +class QJsonObject; +class QFileInfo; + +class OneSixVersionBuilder +{ + OneSixVersionBuilder(); +public: + static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent); + static bool read(OneSixVersion *version, const QJsonObject &obj); + +private: + OneSixVersion *m_version; + OneSixInstance *m_instance; + QWidget *m_widgetParent; + + enum Type + { + Override, + Add, + Remove + }; + + bool build(); + bool read(const QJsonObject &obj); + + void clear(); + bool apply(const QJsonObject &object); + bool applyLibrary(const QJsonObject &lib, const Type type); + + bool read(const QFileInfo &fileInfo, QJsonObject *out); +}; diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 29894b5a..91f86df0 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -234,18 +234,18 @@ void MCVListLoadTask::list_downloaded() } // Parse the type. MinecraftVersion::VersionType versionType; - // Derp or Legacy. use filter to determine type + // OneSix or Legacy. use filter to determine type if (versionTypeStr == "release") { versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::Derp; + : MinecraftVersion::OneSix; is_latest = (versionID == latestReleaseID); is_snapshot = false; } else if (versionTypeStr == "snapshot") // It's a snapshot... yay { versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::Derp; + : MinecraftVersion::OneSix; is_latest = (versionID == latestSnapshotID); is_snapshot = true; } -- cgit From 0f7b38c76bacc210beae21c4e23cc312f53fa5e4 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Fri, 24 Jan 2014 18:17:26 +0100 Subject: Fix some stuff --- logic/InstanceFactory.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 730bcd69..807bccd0 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -51,7 +51,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "OneSix" || inst_type == "OneSix") + if (inst_type == "OneSix") { inst = new OneSixInstance(instDir, m_settings, this); } @@ -67,7 +67,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst { inst = new LegacyFTBInstance(instDir, m_settings, this); } - else if (inst_type == "OneSixFTB" || inst_type == "OneSixFTB") + else if (inst_type == "OneSixFTB") { inst = new OneSixFTBInstance(instDir, m_settings, this); } @@ -102,7 +102,7 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& switch (mcVer->type) { case MinecraftVersion::Legacy: - // TODO OneSix + // TODO new instance type m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); @@ -176,7 +176,7 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne m_settings->registerSetting("InstanceType", "Legacy"); QString inst_type = m_settings->get("InstanceType").toString(); - if(inst_type == "OneSixFTB" || inst_type == "OneSixFTB") + if(inst_type == "OneSixFTB") m_settings->set("InstanceType", "OneSix"); if(inst_type == "LegacyFTB") m_settings->set("InstanceType", "Legacy"); -- cgit From f9ea3dbfdea0db5626a6afadcfd599a5f53d8abd Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 27 Jan 2014 19:20:07 +0100 Subject: Split parsing/applying. Better error logging. Fix crash. --- gui/dialogs/OneSixModEditDialog.cpp | 4 +- logic/ForgeInstaller.cpp | 49 +- logic/OneSixFTBInstance.cpp | 2 +- logic/OneSixInstance.cpp | 30 +- logic/OneSixInstance.h | 8 +- logic/OneSixInstance_p.h | 1 + logic/OneSixLibrary.cpp | 14 +- logic/OneSixLibrary.h | 2 + logic/OneSixUpdate.cpp | 4 +- logic/OneSixVersion.cpp | 5 +- logic/OneSixVersion.h | 6 +- logic/OneSixVersionBuilder.cpp | 1002 +++++++++++++++++++++++++---------- logic/OneSixVersionBuilder.h | 18 +- 13 files changed, 816 insertions(+), 329 deletions(-) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index 625a7c91..ad15311b 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -162,7 +162,7 @@ void OneSixModEditDialog::on_forgeBtn_clicked() } } } - m_inst->reloadFullVersion(this); + m_inst->reloadVersion(this); } void OneSixModEditDialog::on_liteloaderBtn_clicked() @@ -184,7 +184,7 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked() } else { - m_inst->reloadFullVersion(this); + m_inst->reloadVersion(this); } } diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 863c3dfd..c0340e95 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "MultiMC.h" #include "OneSixInstance.h" @@ -146,36 +148,71 @@ bool ForgeInstaller::add(OneSixInstance *to) QJsonObject libObj = lib->toJson(); bool found = false; + bool equals = false; // find an entry that matches this one - for (auto tolib : to->getFullVersion()->libraries) + for (auto tolib : to->getNonCustomVersion()->libraries) { if (tolib->name() != libName) continue; found = true; + if (tolib->toJson() == libObj) + { + equals = true; + } // replace lib libObj.insert("insert", QString("apply")); break; } + if (equals) + { + continue; + } if (!found) { // add lib QJsonObject insertObj; - insertObj.insert("before", to->getFullVersion()->libraries.at(sliding_insert_window)->rawName()); + insertObj.insert( + "before", + to->getFullVersion()->libraries.at(sliding_insert_window + 1)->rawName()); libObj.insert("insert", insertObj); sliding_insert_window++; } - librariesPlus.append(libObj); + librariesPlus.prepend(libObj); } obj.insert("+libraries", librariesPlus); obj.insert("mainClass", m_forge_version->mainClass); - obj.insert("minecraftArguments", m_forge_version->minecraftArguments); - obj.insert("processArguments", m_forge_version->processArguments); + QString args = m_forge_version->minecraftArguments; + QStringList tweakers; + { + QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)"); + QRegularExpressionMatch match = expression.match(args); + while (match.hasMatch()) + { + tweakers.append(match.captured(1)); + args.remove(match.capturedStart(), match.capturedLength()); + match = expression.match(args); + } + } + if (!args.isEmpty() && args != to->getNonCustomVersion()->minecraftArguments) + { + obj.insert("minecraftArguments", args); + } + if (!tweakers.isEmpty()) + { + obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); + } + if (!m_forge_version->processArguments.isEmpty() && + m_forge_version->processArguments != to->getNonCustomVersion()->processArguments) + { + obj.insert("processArguments", m_forge_version->processArguments); + } } QFile file(filename(to->instanceRoot())); if (!file.open(QFile::WriteOnly)) { - QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); return false; } file.write(QJsonDocument(obj).toJson()); diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 71bc1470..54bfbe2b 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -55,7 +55,7 @@ slots: setStatus(tr("Installing Forge...")); QString forgePath = entry->getFullPath(); ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!instance->reloadFullVersion()) + if (!instance->reloadVersion()) { emitFailed(tr("Couldn't load the version config")); return; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index c43a5187..19d5e112 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -35,13 +35,14 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, d->m_settings->registerSetting("IntendedVersion", ""); d->m_settings->registerSetting("ShouldUpdate", false); d->version.reset(new OneSixVersion(this, this)); + d->nonCustomVersion.reset(new OneSixVersion(this, this)); if (QDir(instanceRoot()).exists("version.json")) { - reloadFullVersion(); + reloadVersion(); } else { - clearFullVersion(); + clearVersion(); } } @@ -139,6 +140,10 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) I_D(OneSixInstance); auto version = d->version; QString args_pattern = version->minecraftArguments; + for (auto tweaker : version->tweakers) + { + args_pattern += " --tweakClass " + tweaker; + } QMap token_mapping; // yggdrasil! @@ -287,7 +292,7 @@ bool OneSixInstance::setIntendedVersionId(QString version) settings().set("IntendedVersion", version); setShouldUpdate(true); QFile::remove(PathCombine(instanceRoot(), "version.json")); - clearFullVersion(); + clearVersion(); return true; } @@ -323,28 +328,39 @@ QString OneSixInstance::currentVersionId() const return intendedVersionId(); } -bool OneSixInstance::reloadFullVersion(QWidget *widgetParent) +bool OneSixInstance::reloadVersion(QWidget *widgetParent) { I_D(OneSixInstance); bool ret = d->version->reload(widgetParent); + if (ret) + { + ret = d->nonCustomVersion->reload(widgetParent, true); + } emit versionReloaded(); return ret; } -void OneSixInstance::clearFullVersion() +void OneSixInstance::clearVersion() { I_D(OneSixInstance); d->version->clear(); + d->nonCustomVersion->clear(); emit versionReloaded(); } -std::shared_ptr OneSixInstance::getFullVersion() +std::shared_ptr OneSixInstance::getFullVersion() const { - I_D(OneSixInstance); + I_D(const OneSixInstance); return d->version; } +std::shared_ptr OneSixInstance::getNonCustomVersion() const +{ + I_D(const OneSixInstance); + return d->nonCustomVersion; +} + QString OneSixInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 94577828..ee64e886 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -52,11 +52,13 @@ public: virtual QDialog *createModEditDialog(QWidget *parent) override; /// reload the full version json files. return true on success! - bool reloadFullVersion(QWidget *widgetParent = 0); + bool reloadVersion(QWidget *widgetParent = 0); /// clears all version information in preparation for an update - void clearFullVersion(); + void clearVersion(); /// get the current full version info - std::shared_ptr getFullVersion(); + std::shared_ptr getFullVersion() const; + /// gets the current version info, excluding custom.json + std::shared_ptr getNonCustomVersion() const; /// is the current version original, or custom? virtual bool versionIsCustom() override; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index a2b70ac9..6dc74e50 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -22,6 +22,7 @@ struct OneSixInstancePrivate : public BaseInstancePrivate { std::shared_ptr version; + std::shared_ptr nonCustomVersion; std::shared_ptr loader_mod_list; std::shared_ptr resource_pack_list; }; diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp index 4d892d53..2b1f0600 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/OneSixLibrary.cpp @@ -93,6 +93,10 @@ void OneSixLibrary::addNative(OpSys os, const QString &suffix) m_is_native = true; m_native_suffixes[os] = suffix; } +void OneSixLibrary::clearSuffixes() +{ + m_native_suffixes.clear(); +} void OneSixLibrary::setRules(QList> rules) { m_rules = rules; @@ -176,7 +180,7 @@ bool OneSixLibrary::extractTo(QString target_dir) cooked_storage.replace("${arch}", "32"); QString origin = PathCombine("libraries", cooked_storage); QString target_dir_cooked = PathCombine(target_dir, "32"); - if(!ensureFolderPathExists(target_dir_cooked)) + if (!ensureFolderPathExists(target_dir_cooked)) { QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; return false; @@ -191,7 +195,7 @@ bool OneSixLibrary::extractTo(QString target_dir) cooked_storage.replace("${arch}", "64"); origin = PathCombine("libraries", cooked_storage); target_dir_cooked = PathCombine(target_dir, "64"); - if(!ensureFolderPathExists(target_dir_cooked)) + if (!ensureFolderPathExists(target_dir_cooked)) { QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; return false; @@ -205,7 +209,7 @@ bool OneSixLibrary::extractTo(QString target_dir) } else { - if(!ensureFolderPathExists(target_dir)) + if (!ensureFolderPathExists(target_dir)) { QLOG_ERROR() << "Couldn't create folder " + target_dir; return false; @@ -230,8 +234,10 @@ QJsonObject OneSixLibrary::toJson() libRoot.insert("MMC-hint", m_hint); if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE) + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { libRoot.insert("url", m_base_url); + } if (isNative() && m_native_suffixes.size()) { QJsonObject nativeList; diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h index 52f89969..5384015a 100644 --- a/logic/OneSixLibrary.h +++ b/logic/OneSixLibrary.h @@ -107,6 +107,8 @@ public: void setIsNative(); /// Attach a name suffix to the specified OS native void addNative(OpSys os, const QString &suffix); + /// Clears all suffixes + void clearSuffixes(); /// Set the load rules void setRules(QList> rules); diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index f51b6c23..c056bc4c 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -137,7 +137,7 @@ void OneSixUpdate::versionFileFinished() { finfo.remove(); } - inst->reloadFullVersion(); + inst->reloadVersion(); jarlibStart(); } @@ -235,7 +235,7 @@ void OneSixUpdate::jarlibStart() setStatus(tr("Getting the library files from Mojang...")); QLOG_INFO() << m_inst->name() << ": downloading libraries"; OneSixInstance *inst = (OneSixInstance *)m_inst; - bool successful = inst->reloadFullVersion(); + bool successful = inst->reloadVersion(); if (!successful) { emitFailed("Failed to load the version description file. It might be " diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 3db3bd77..1b6bc9cb 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -25,9 +25,9 @@ OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) clear(); } -bool OneSixVersion::reload(QWidget *widgetParent) +bool OneSixVersion::reload(QWidget *widgetParent, const bool excludeCustom) { - return OneSixVersionBuilder::build(this, m_instance, widgetParent); + return OneSixVersionBuilder::build(this, m_instance, widgetParent, excludeCustom); } void OneSixVersion::clear() @@ -42,6 +42,7 @@ void OneSixVersion::clear() minimumLauncherVersion = 0xDEADBEAF; mainClass.clear(); libraries.clear(); + tweakers.clear(); } void OneSixVersion::dump() const diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 00afd010..767e05db 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -38,7 +38,7 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - bool reload(QWidget *widgetParent); + bool reload(QWidget *widgetParent, const bool excludeCustom = false); void clear(); void dump() const; @@ -78,6 +78,10 @@ public: * writing) */ int minimumLauncherVersion = 0xDEADBEEF; + /** + * A list of all tweaker classes + */ + QStringList tweakers; /** * The main class to load first */ diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 85f55a52..c031a94a 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -31,416 +31,844 @@ #include "OneSixRule.h" #include "logger/QsLog.h" -OneSixVersionBuilder::OneSixVersionBuilder() -{ - -} - -bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent) +struct VersionFile { - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = instance; - builder.m_widgetParent = widgetParent; - return builder.build(); -} - -bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = 0; - builder.m_widgetParent = 0; - return builder.read(obj); -} - -bool OneSixVersionBuilder::build() -{ - m_version->clear(); - - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); - - // version.json -> patches/*.json -> custom.json + int order; + QString id; + QString mainClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + QString releaseTime; + QString time; + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + struct Library + { + QString name; + QString url; + QString hint; + QString absoluteUrl; + bool applyExcludes = false; + QStringList excludes; + bool applyNatives = false; + QList> natives; + bool applyRules = false; + QList> rules; - // version.json + // user for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + InsertBefore, + InsertAfter, + Replace + }; + InsertType insertType; + QString insertData; + }; + bool shouldOverwriteLibs = false; + QList overwriteLibs; + QList addLibs; + QList removeLibs; + + static Library fromLibraryJson(const QJsonObject &libObj, const QString &filename, + bool &isError) { - QLOG_INFO() << "Reading version.json"; - QJsonObject obj; - if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) + isError = true; + Library out; + if (!libObj.contains("name")) { - return false; + QLOG_ERROR() << filename << "contains a library that doesn't have a 'name' field"; + return out; } - if (!apply(obj)) - { - return false; - } - } + out.name = libObj.value("name").toString(); - // patches/ - { - // load all, put into map for ordering, apply in the right order + auto readString = [libObj, filename](const QString &key, QString &variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; - QMap objects; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + readString("url", out.url); + readString("MMC-hint", out.hint); + readString("MMC-absulute_url", out.absoluteUrl); + readString("MMC-absoluteUrl", out.absoluteUrl); + if (libObj.contains("extract")) { - QLOG_INFO() << "Reading" << info.fileName(); - QJsonObject obj; - if (!read(info, &obj)) + if (!libObj.value("extract").isObject()) { - return false; + QLOG_ERROR() + << filename + << "contains a library with an 'extract' field that's not an object"; + return out; } - if (!obj.contains("order") || !obj.value("order").isDouble()) + QJsonObject extractObj = libObj.value("extract").toObject(); + if (!extractObj.contains("exclude") || !extractObj.value("exclude").isArray()) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Missing or invalid 'order' in %1").arg(info.absoluteFilePath())); - return false; + QLOG_ERROR() << filename + << "contains a library with an invalid 'extract' field"; + return out; } - objects.insert(obj.value("order").toDouble(), obj); - } - for (auto object : objects.values()) - { - qDebug() << "Applying object with order" << objects.key(object); - if (!apply(object)) + out.applyExcludes = true; + QJsonArray excludeArray = extractObj.value("exclude").toArray(); + for (auto excludeVal : excludeArray) { - return false; + if (!excludeVal.isString()) + { + QLOG_WARN() << filename << "contains a library that contains an 'extract' " + "field that contains an invalid 'exclude' entry " + "(skipping)"; + } + else + { + out.excludes.append(excludeVal.toString()); + } } } - } - - // custom.json - { - if (QFile::exists(root.absoluteFilePath("custom.json"))) + if (libObj.contains("natives")) { - QLOG_INFO() << "Reading custom.json"; - QJsonObject obj; - if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) + if (!libObj.value("natives").isObject()) { - return false; + QLOG_ERROR() + << filename + << "contains a library with a 'natives' field that's not an object"; + return out; } - if (!apply(obj)) + out.applyNatives = true; + QJsonObject nativesObj = libObj.value("natives").toObject(); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) { - return false; + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out.natives.append(qMakePair(opSys, it.value().toString())); + } } } + if (libObj.contains("rules")) + { + out.applyRules = true; + out.rules = rulesFromJsonV4(libObj); + } + isError = false; + return out; } - - return true; -} - -bool OneSixVersionBuilder::read(const QJsonObject &obj) -{ - m_version->clear(); - - return apply(obj); -} - -void applyString(const QJsonObject &obj, const QString &key, QString &out, const bool onlyOverride = true) -{ - if (obj.contains(key) && obj.value(key).isString()) - { - out = obj.value(key).toString(); - } - else if (!onlyOverride) + static VersionFile fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, bool &isError) { - if (obj.contains("+" + key) && obj.value("+" + key).isString()) + VersionFile out; + isError = true; + if (doc.isEmpty() || doc.isNull()) { - out += obj.value("+" + key).toString(); + QLOG_ERROR() << filename << "is empty or null"; + return out; } - else if (obj.contains("-" + key) && obj.value("-" + key).isString()) + if (!doc.isObject()) { - out.remove(obj.value("-" + key).toString()); + QLOG_ERROR() << "The root of" << filename << "is not an object"; + return out; } - } -} -void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(OneSixLibrary::*func)(const QString &val)) -{ - if (obj.contains(key) && obj.value(key).isString()) - { - (lib.get()->*func)(obj.value(key).toString()); - } -} -bool OneSixVersionBuilder::apply(const QJsonObject &object) -{ - applyString(object, "id", m_version->id); - applyString(object, "mainClass", m_version->mainClass); - applyString(object, "minecraftArguments", m_version->minecraftArguments, false); - applyString(object, "processArguments", m_version->processArguments, false); - if (m_version->minecraftArguments.isEmpty()) - { - const QString toCompare = m_version->processArguments.toLower(); - if (toCompare == "legacy") + + QJsonObject root = doc.object(); + + if (requireOrder) { - m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; + if (root.contains("order")) + { + if (root.value("order").isDouble()) + { + out.order = root.value("order").toDouble(); + } + else + { + QLOG_ERROR() << "'order' field contains an invalid value in" << filename; + return out; + } + } + else + { + QLOG_ERROR() << filename << "doesn't contain an order field"; + } } - else if (toCompare == "username_session") + + auto readString = [root, filename](const QString &key, QString &variable) + { + if (root.contains(key)) + { + QJsonValue val = root.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("id", out.id); + readString("mainClass", out.mainClass); + readString("processArguments", out.processArguments); + readString("minecraftArguments", out.overwriteMinecraftArguments); + readString("+minecraftArguments", out.addMinecraftArguments); + readString("-minecraftArguments", out.removeMinecraftArguments); + readString("type", out.type); + readString("releaseTime", out.releaseTime); + readString("time", out.time); + readString("assets", out.assets); + if (root.contains("minimumLauncherVersion")) { - m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + QJsonValue val = root.value("minimumLauncherVersion"); + if (!val.isDouble()) + { + QLOG_WARN() << "minimumLauncherVersion is not an int in" << filename + << "(skipping)"; + } + else + { + out.minimumLauncherVersion = val.toDouble(); + } } - else if (toCompare == "username_session_version") + + if (root.contains("tweakers")) { - m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + QJsonValue tweakersVal = root.value("tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a 'tweakers' field, but it's not an array"; + return out; + } + out.shouldOverwriteTweakers = true; + QJsonArray tweakers = root.value("tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a 'tweakers' field entry that's not a string"; + return out; + } + out.overwriteTweakers.append(tweakerVal.toString()); + } } - } - applyString(object, "type", m_version->type); - applyString(object, "releaseTime", m_version->releaseTime); - applyString(object, "time", m_version->time); - applyString(object, "assets", m_version->assets); - { - if (m_version->assets.isEmpty()) + if (root.contains("+tweakers")) { - m_version->assets = "legacy"; + QJsonValue tweakersVal = root.value("+tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a '+tweakers' field, but it's not an array"; + return out; + } + QJsonArray tweakers = root.value("+tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a '+tweakers' field entry that's not a string"; + return out; + } + out.addTweakers.append(tweakerVal.toString()); + } } - } - if (object.contains("minimumLauncherVersion")) - { - auto minLauncherVersionVal = object.value("minimumLauncherVersion"); - if (minLauncherVersionVal.isDouble()) + if (root.contains("-tweakers")) { - m_version->minimumLauncherVersion = minLauncherVersionVal.toDouble(); + QJsonValue tweakersVal = root.value("-tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a '-tweakers' field, but it's not an array"; + return out; + } + out.shouldOverwriteTweakers = true; + QJsonArray tweakers = root.value("-tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a '-tweakers' field entry that's not a string"; + return out; + } + out.removeTweakers.append(tweakerVal.toString()); + } } - } - // libraries - if (object.contains("libraries")) - { - m_version->libraries.clear(); - auto librariesValue = object.value("libraries"); - if (!librariesValue.isArray()) + if (root.contains("libraries")) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; + out.shouldOverwriteLibs = true; + QJsonValue librariesVal = root.value("libraries"); + if (!librariesVal.isArray()) + { + QLOG_ERROR() << filename + << "contains a 'libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + bool error; + Library lib = fromLibraryJson(libObj, filename, error); + if (error) + { + QLOG_ERROR() << "Error while reading a library entry in" << filename; + return out; + } + out.overwriteLibs.append(lib); + } } - auto array = librariesValue.toArray(); - for (auto libVal : array) + if (root.contains("+libraries")) { - if (libVal.isObject()) + QJsonValue librariesVal = root.value("+libraries"); + if (!librariesVal.isArray()) { - if (!applyLibrary(libVal.toObject(), Override)) + QLOG_ERROR() << filename + << "contains a '+libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + bool error; + Library lib = fromLibraryJson(libObj, filename, error); + if (error) + { + QLOG_ERROR() << "Error while reading a library entry in" << filename; + return out; + } + if (!libObj.contains("insert")) { - return false; + QLOG_ERROR() << "Missing 'insert' field in '+libraries' field in" + << filename; + return out; } + QJsonValue insertVal = libObj.value("insert"); + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + QLOG_ERROR() << "One library has an empty insert object in" + << filename; + return out; + } + insertString = insertObj.keys().first(); + lib.insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib.insertType = Library::Apply; + } + else if (insertString == "append") + { + lib.insertType = Library::Append; + } + else if (insertString == "prepend") + { + lib.insertType = Library::Prepend; + } + else if (insertString == "before") + { + lib.insertType = Library::InsertBefore; + } + else if (insertString == "after") + { + lib.insertType = Library::InsertAfter; + } + else if (insertString == "replace") + { + lib.insertType = Library::Replace; + } + else + { + QLOG_ERROR() << "A '+' library in" << filename + << "contains an invalid insert type"; + return out; + } + out.addLibs.append(lib); } - } + if (root.contains("-libraries")) + { + QJsonValue librariesVal = root.value("-libraries"); + if (!librariesVal.isArray()) + { + QLOG_ERROR() << filename + << "contains a '-libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + if (!libObj.contains("name")) + { + QLOG_ERROR() << filename << "contains a library without a name"; + return out; + } + if (!libObj.value("name").isString()) + { + QLOG_ERROR() << filename + << "contains a library without a valid 'name' field"; + return out; + } + out.removeLibs.append(libObj.value("name").toString()); + } + } + + isError = false; + return out; } - // +libraries - if (object.contains("+libraries")) + static std::shared_ptr createLibrary(const Library &lib) { - auto librariesValue = object.value("+libraries"); - if (!librariesValue.isArray()) + std::shared_ptr out(new OneSixLibrary(lib.name)); + out->setBaseUrl(lib.url); + out->setHint(lib.hint); + out->setAbsoluteUrl(lib.absoluteUrl); + out->extract_excludes = lib.excludes; + for (auto native : lib.natives) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; + out->addNative(native.first, native.second); } - for (auto libVal : librariesValue.toArray()) + out->setRules(lib.rules); + out->finalize(); + return out; + } + int findLibrary(QList> haystack, const QString &needle) + { + for (int i = 0; i < haystack.size(); ++i) { - if (libVal.isObject()) + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix) + .indexIn(haystack.at(i)->rawName()) != -1) { - applyLibrary(libVal.toObject(), Add); + return i; } - } + return -1; } - - // -libraries - if (object.contains("-libraries")) + void applyTo(OneSixVersion *version, bool &isError) { - auto librariesValue = object.value("-libraries"); - if (!librariesValue.isArray()) + isError = true; + if (!id.isNull()) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); - return false; + version->id = id; + } + if (!mainClass.isNull()) + { + version->mainClass = mainClass; + } + if (!processArguments.isNull()) + { + version->processArguments = processArguments; + } + if (!type.isNull()) + { + version->type = type; + } + if (!releaseTime.isNull()) + { + version->releaseTime = releaseTime; + } + if (!time.isNull()) + { + version->time = time; + } + if (!assets.isNull()) + { + version->assets = assets; + } + if (minimumLauncherVersion >= 0) + { + version->minimumLauncherVersion = minimumLauncherVersion; + } + if (!overwriteMinecraftArguments.isNull()) + { + version->minecraftArguments = overwriteMinecraftArguments; + } + if (!addMinecraftArguments.isNull()) + { + version->minecraftArguments += addMinecraftArguments; } - for (auto libVal : librariesValue.toArray()) + if (!removeMinecraftArguments.isNull()) { - if (libVal.isObject()) + version->minecraftArguments.remove(removeMinecraftArguments); + } + if (shouldOverwriteTweakers) + { + version->tweakers = overwriteTweakers; + } + for (auto tweaker : addTweakers) + { + version->tweakers += tweaker; + } + for (auto tweaker : removeTweakers) + { + version->tweakers.removeAll(tweaker); + } + if (shouldOverwriteLibs) + { + version->libraries.clear(); + for (auto lib : overwriteLibs) + { + version->libraries.append(createLibrary(lib)); + } + } + for (auto lib : addLibs) + { + switch (lib.insertType) + { + case Library::Apply: + { + + int index = findLibrary(version->libraries, lib.name); + if (index >= 0) + { + auto library = version->libraries[index]; + if (!lib.url.isNull()) + { + library->setBaseUrl(lib.url); + } + if (!lib.hint.isNull()) + { + library->setHint(lib.hint); + } + if (!lib.absoluteUrl.isNull()) + { + library->setAbsoluteUrl(lib.absoluteUrl); + } + if (lib.applyExcludes) + { + library->extract_excludes = lib.excludes; + } + if (lib.applyNatives) + { + library->clearSuffixes(); + for (auto native : lib.natives) + { + library->addNative(native.first, native.second); + } + } + if (lib.applyRules) + { + library->setRules(lib.rules); + } + library->finalize(); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; + } + case Library::Append: + version->libraries.append(createLibrary(lib)); + break; + case Library::Prepend: + version->libraries.prepend(createLibrary(lib)); + break; + case Library::InsertBefore: { - applyLibrary(libVal.toObject(), Remove); + + int index = findLibrary(version->libraries, lib.insertData); + if (index >= 0) + { + version->libraries.insert(index, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; } + case Library::InsertAfter: + { + int index = findLibrary(version->libraries, lib.insertData); + if (index >= 0) + { + version->libraries.insert(index + 1, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; + } + case Library::Replace: + { + int index = findLibrary(version->libraries, lib.insertData); + if (index >= 0) + { + version->libraries.replace(index, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; + } + } + } + for (auto lib : removeLibs) + { + int index = findLibrary(version->libraries, lib); + if (index >= 0) + { + version->libraries.removeAt(index); + } + else + { + QLOG_WARN() << "Couldn't find" << lib << "(skipping)"; + } } + + isError = false; } +}; - return true; +OneSixVersionBuilder::OneSixVersionBuilder() +{ } -int findLibrary(QList > haystack, const QString &needle) +bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, + QWidget *widgetParent, const bool excludeCustom) { - for (int i = 0; i < haystack.size(); ++i) - { - if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(haystack.at(i)->rawName()) != -1) - { - return i; - } - } - return -1; + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.m_widgetParent = widgetParent; + return builder.build(excludeCustom); } -bool OneSixVersionBuilder::applyLibrary(const QJsonObject &lib, const OneSixVersionBuilder::Type type) +bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) { - // Library name - auto nameVal = lib.value("name"); - if (!nameVal.isString()) - { - return false; - } - auto name = nameVal.toString(); + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.m_widgetParent = 0; + return builder.read(obj); +} - if (type == Remove) +bool OneSixVersionBuilder::build(const bool excludeCustom) +{ + m_version->clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // version.json -> patches/*.json -> custom.json + + // version.json { - int index = findLibrary(m_version->libraries, name); - if (index >= 0) + QLOG_INFO() << "Reading version.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) + { + return false; + } + bool isError = false; + file.applyTo(m_version, isError); + if (isError) { - m_version->libraries.removeAt(index); + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("version.json"))); + return false; } - return true; } - if (type == Add && !lib.contains("insert")) + // patches/ { - return false; - } - - std::shared_ptr library; + // load all, put into map for ordering, apply in the right order - if (lib.value("insert").toString() != "apply" && type == Add) - { - QMutableListIterator > it(m_version->libraries); - while (it.hasNext()) + QMap> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { - if (it.next()->rawName() == name) + QLOG_INFO() << "Reading" << info.fileName(); + VersionFile file; + if (!read(info, true, &file)) { - it.remove(); + return false; } + files.insert(file.order, qMakePair(info.fileName(), file)); } - } - - if (lib.value("insert").toString() == "apply" && type == Add) - { - library = m_version->libraries[findLibrary(m_version->libraries, name)]; - } - else - { - library.reset(new OneSixLibrary(nameVal.toString())); - } - - applyString(lib, "url", library, &OneSixLibrary::setBaseUrl); - applyString(lib, "MMC-hint", library, &OneSixLibrary::setHint); - applyString(lib, "MMC-absulute_url", library, &OneSixLibrary::setAbsoluteUrl); - applyString(lib, "MMC-absoluteUrl", library, &OneSixLibrary::setAbsoluteUrl); - - auto extractVal = lib.value("extract"); - if (extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if (excludesVal.isArray()) + for (auto order : files.keys()) { - auto excludesList = excludesVal.toArray(); - for (auto excludeVal : excludesList) + QLOG_DEBUG() << "Applying file with order" << order; + auto filePair = files[order]; + bool isError = false; + filePair.second.applyTo(m_version, isError); + if (isError) { - if (excludeVal.isString()) - { - excludes.append(excludeVal.toString()); - } + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(filePair.first)); + return false; } - library->extract_excludes = excludes; } } - auto nativesVal = lib.value("natives"); - if (nativesVal.isObject()) + // custom.json + if (!excludeCustom) { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + if (QFile::exists(root.absoluteFilePath("custom.json"))) { - auto osType = OpSys_fromString(it.key()); - if (osType == Os_Other) + QLOG_INFO() << "Reading custom.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file)) { - continue; + return false; } - if (!it.value().isString()) + bool isError = false; + file.applyTo(m_version, isError); + if (isError) { - continue; + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("custom.json"))); + return false; } - library->addNative(osType, it.value().toString()); } } - if (lib.contains("rules")) + // some final touches { - library->setRules(rulesFromJsonV4(lib)); - } - library->finalize(); - if (type == Override) - { - m_version->libraries.append(library); - } - else if (lib.value("insert").toString() != "apply") - { - if (lib.value("insert").toString() == "append") - { - m_version->libraries.append(library); - } - else if (lib.value("insert").toString() == "prepend") + if (m_version->assets.isEmpty()) { - m_version->libraries.prepend(library); + m_version->assets = "legacy"; } - else if (lib.value("insert").isObject()) + if (m_version->minecraftArguments.isEmpty()) { - QJsonObject insertObj = lib.value("insert").toObject(); - if (insertObj.isEmpty()) + QString toCompare = m_version->processArguments.toLower(); + if (toCompare == "legacy") { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("'insert' object empty")); - return false; + m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; } - const QString key = insertObj.keys().first(); - const QString value = insertObj.value(key).toString(); - const int index = findLibrary(m_version->libraries, value); - if (index >= 0) + else if (toCompare == "username_session") { - if (key == "before") - { - m_version->libraries.insert(index, library); - } - else if (key == "after") - { - m_version->libraries.insert(index + 1, library); - } - else - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); - return false; - } + m_version->minecraftArguments = + "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + m_version->minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; } } - else - { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Invalid value for 'insert': %1").arg(lib.value("insert").toString())); - return false; - } } + + return true; +} + +bool OneSixVersionBuilder::read(const QJsonObject &obj) +{ + m_version->clear(); + + bool isError = false; + VersionFile file = VersionFile::fromJson(QJsonDocument(obj), QString(), false, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); + return false; + } + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); + return false; + } + return true; } -bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, QJsonObject *out) +bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrder, + VersionFile *out) { QFile file(fileInfo.absoluteFilePath()); if (!file.open(QFile::ReadOnly)) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); return false; } QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); if (error.error != QJsonParseError::NoError) { - QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Unable to parse %1: %2 at %3").arg(file.fileName(), error.errorString()).arg(error.offset)); + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), + QObject::tr("Unable to parse %1: %2 at %3") + .arg(file.fileName(), error.errorString()) + .arg(error.offset)); return false; } - *out = doc.object(); + bool isError = false; + *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while reading %1. Please check MultiMC-0.log for more info.") + .arg(file.fileName())); + ; + } return true; } diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h index a3ba331c..ac8c13bf 100644 --- a/logic/OneSixVersionBuilder.h +++ b/logic/OneSixVersionBuilder.h @@ -22,12 +22,13 @@ class OneSixInstance; class QWidget; class QJsonObject; class QFileInfo; +class VersionFile; class OneSixVersionBuilder { OneSixVersionBuilder(); public: - static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent); + static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool excludeCustom); static bool read(OneSixVersion *version, const QJsonObject &obj); private: @@ -35,19 +36,8 @@ private: OneSixInstance *m_instance; QWidget *m_widgetParent; - enum Type - { - Override, - Add, - Remove - }; - - bool build(); + bool build(const bool excludeCustom); bool read(const QJsonObject &obj); - void clear(); - bool apply(const QJsonObject &object); - bool applyLibrary(const QJsonObject &lib, const Type type); - - bool read(const QFileInfo &fileInfo, QJsonObject *out); + bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out); }; -- cgit From 176783c8caacafd5d320c2fef7374335ee54796b Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 27 Jan 2014 20:17:29 +0100 Subject: Have the libraries tab show tweaker mods instead of libraries --- gui/dialogs/OneSixModEditDialog.ui | 10 ++++++++ logic/LiteLoaderInstaller.cpp | 2 +- logic/OneSixVersion.cpp | 52 +++++++++----------------------------- logic/OneSixVersion.h | 2 -- 4 files changed, 23 insertions(+), 43 deletions(-) diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui index d57d0f85..67642c23 100644 --- a/gui/dialogs/OneSixModEditDialog.ui +++ b/gui/dialogs/OneSixModEditDialog.ui @@ -35,6 +35,13 @@ + + + + Tweakers: + + + @@ -43,6 +50,9 @@ Qt::ScrollBarAlwaysOff + + false + diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 1ce9d3f4..1c38fd5e 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -53,7 +53,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) QJsonObject obj; obj.insert("mainClass", QString("net.minecraft.launchwrapper.Launch")); - obj.insert("+minecraftArguments", QString(" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker")); + obj.insert("+tweakers", QJsonArray::fromStringList(QStringList() << "com.mumfrey.liteloader.launch.LiteLoaderTweaker")); obj.insert("order", 10); QJsonArray libraries; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 1b6bc9cb..1abd5114 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -27,11 +27,15 @@ OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) bool OneSixVersion::reload(QWidget *widgetParent, const bool excludeCustom) { - return OneSixVersionBuilder::build(this, m_instance, widgetParent, excludeCustom); + beginResetModel(); + bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, excludeCustom); + endResetModel(); + return ret; } void OneSixVersion::clear() { + beginResetModel(); id.clear(); time.clear(); releaseTime.clear(); @@ -43,6 +47,7 @@ void OneSixVersion::clear() mainClass.clear(); libraries.clear(); tweakers.clear(); + endResetModel(); } void OneSixVersion::dump() const @@ -109,21 +114,14 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= libraries.size()) + if (row < 0 || row >= tweakers.size()) return QVariant(); if (role == Qt::DisplayRole) { - switch (column) + if (column == 0) { - case 0: - return libraries[row]->name(); - case 1: - return libraries[row]->type(); - case 2: - return libraries[row]->version(); - default: - return QVariant(); + return tweakers.at(row); } } return QVariant(); @@ -133,43 +131,17 @@ Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; - int row = index.row(); - if (libraries[row]->isActive()) - { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; - } - else - { - return Qt::ItemNeverHasChildren; - } - // return QAbstractListModel::flags(index); -} - -QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) - return QVariant(); - switch (section) - { - case 0: - return QString("Name"); - case 1: - return QString("Type"); - case 2: - return QString("Version"); - default: - return QString(); - } + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } int OneSixVersion::rowCount(const QModelIndex &parent) const { - return libraries.size(); + return tweakers.size(); } int OneSixVersion::columnCount(const QModelIndex &parent) const { - return 3; + return 1; } QDebug operator<<(QDebug &dbg, const OneSixVersion *version) diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 767e05db..98a4b418 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -33,8 +33,6 @@ public: virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; -- cgit From 986141b5035c99747c7e06cd447b8b4e27eb2b27 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 27 Jan 2014 22:23:07 +0100 Subject: Fix library ordering --- logic/ForgeInstaller.cpp | 8 ++------ logic/LiteLoaderInstaller.cpp | 2 +- logic/OneSixVersionBuilder.cpp | 36 ++++++++++++++---------------------- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index c0340e95..8d2445ed 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -160,7 +160,7 @@ bool ForgeInstaller::add(OneSixInstance *to) equals = true; } // replace lib - libObj.insert("insert", QString("apply")); + libObj.insert("insert", QString("replace")); break; } if (equals) @@ -170,11 +170,7 @@ bool ForgeInstaller::add(OneSixInstance *to) if (!found) { // add lib - QJsonObject insertObj; - insertObj.insert( - "before", - to->getFullVersion()->libraries.at(sliding_insert_window + 1)->rawName()); - libObj.insert("insert", insertObj); + libObj.insert("insert", QString("prepend-if-not-exists")); sliding_insert_window++; } librariesPlus.prepend(libObj); diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 1c38fd5e..f658f834 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -63,7 +63,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); launchwrapperLib.finalize(); QJsonObject lwLibObj = launchwrapperLib.toJson(); - lwLibObj.insert("insert", QString("prepend")); + lwLibObj.insert("insert", QString("prepend-if-not-exists")); libraries.append(lwLibObj); } diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index c031a94a..3b119b45 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -70,8 +70,8 @@ struct VersionFile Apply, Append, Prepend, - InsertBefore, - InsertAfter, + AppendIfNotExists, + PrependIfNotExists, Replace }; InsertType insertType; @@ -408,13 +408,13 @@ struct VersionFile { lib.insertType = Library::Prepend; } - else if (insertString == "before") + else if (insertString == "prepend-if-not-exists") { - lib.insertType = Library::InsertBefore; + lib.insertType = Library::PrependIfNotExists; } - else if (insertString == "after") + else if (insertString == "append-if-not-exists") { - lib.insertType = Library::InsertAfter; + lib.insertType = Library::PrependIfNotExists; } else if (insertString == "replace") { @@ -613,31 +613,23 @@ struct VersionFile case Library::Prepend: version->libraries.prepend(createLibrary(lib)); break; - case Library::InsertBefore: + case Library::AppendIfNotExists: { - int index = findLibrary(version->libraries, lib.insertData); - if (index >= 0) - { - version->libraries.insert(index, createLibrary(lib)); - } - else + int index = findLibrary(version->libraries, lib.name); + if (index < 0) { - QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + version->libraries.append(createLibrary(lib)); } break; } - case Library::InsertAfter: + case Library::PrependIfNotExists: { - int index = findLibrary(version->libraries, lib.insertData); - if (index >= 0) - { - version->libraries.insert(index + 1, createLibrary(lib)); - } - else + int index = findLibrary(version->libraries, lib.name); + if (index < 0) { - QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + version->libraries.prepend(createLibrary(lib)); } break; } -- cgit From 556d8f0ec16e5030cfbfac9dc0fc2c796e829dcb Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Tue, 28 Jan 2014 07:39:43 +0100 Subject: custom.json overrides all. For user patching there now is instance.json --- logic/OneSixVersionBuilder.cpp | 107 +++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 3b119b45..a8e7253b 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -695,13 +695,11 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) QDir root(m_instance->instanceRoot()); QDir patches(root.absoluteFilePath("patches/")); - // version.json -> patches/*.json -> custom.json - - // version.json + if (QFile::exists(root.absoluteFilePath("custom.json"))) { - QLOG_INFO() << "Reading version.json"; + QLOG_INFO() << "Reading custom.json"; VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file)) { return false; } @@ -713,65 +711,90 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) m_widgetParent, QObject::tr("Error"), QObject::tr( "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("version.json"))); + .arg(root.absoluteFilePath("custom.json"))); return false; } } - - // patches/ + else { - // load all, put into map for ordering, apply in the right order + // version.json -> patches/*.json -> instance.json - QMap> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + // version.json { - QLOG_INFO() << "Reading" << info.fileName(); + QLOG_INFO() << "Reading version.json"; VersionFile file; - if (!read(info, true, &file)) + if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) { return false; } - files.insert(file.order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - QLOG_DEBUG() << "Applying file with order" << order; - auto filePair = files[order]; bool isError = false; - filePair.second.applyTo(m_version, isError); + file.applyTo(m_version, isError); if (isError) { QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(filePair.first)); + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("version.json"))); return false; } } - } - // custom.json - if (!excludeCustom) - { - if (QFile::exists(root.absoluteFilePath("custom.json"))) + // patches/ { - QLOG_INFO() << "Reading custom.json"; - VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file)) + // load all, put into map for ordering, apply in the right order + + QMap> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { - return false; + QLOG_INFO() << "Reading" << info.fileName(); + VersionFile file; + if (!read(info, true, &file)) + { + return false; + } + files.insert(file.order, qMakePair(info.fileName(), file)); } - bool isError = false; - file.applyTo(m_version, isError); - if (isError) + for (auto order : files.keys()) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("custom.json"))); - return false; + QLOG_DEBUG() << "Applying file with order" << order; + auto filePair = files[order]; + bool isError = false; + filePair.second.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(filePair.first)); + return false; + } + } + } + + // instance.json + if (!excludeCustom) + { + if (QFile::exists(root.absoluteFilePath("instance.json"))) + { + QLOG_INFO() << "Reading instance.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("instance.json")), false, &file)) + { + return false; + } + bool isError = false; + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("instance.json"))); + return false; + } } } } -- cgit From 3fb7a0faf069fdc5a4f14a12dfd76c43819c3378 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Fri, 31 Jan 2014 22:51:45 +0100 Subject: Reformat, Rename, Redo --- .clang-format | 24 ++ CMakeLists.txt | 22 +- CategorizedProxyModel.cpp | 21 -- CategorizedProxyModel.h | 18 - CategorizedView.cpp | 850 ------------------------------------------ CategorizedView.h | 105 ------ CategorizedViewCategory.cpp | 128 ------- CategorizedViewCategory.h | 39 -- Group.cpp | 139 +++++++ Group.h | 36 ++ GroupView.cpp | 881 ++++++++++++++++++++++++++++++++++++++++++++ GroupView.h | 107 ++++++ GroupedProxyModel.cpp | 21 ++ GroupedProxyModel.h | 14 + InstanceDelegate.cpp | 8 +- InstanceDelegate.h | 8 +- main.cpp | 25 +- main.h | 12 +- 18 files changed, 1263 insertions(+), 1195 deletions(-) create mode 100644 .clang-format delete mode 100644 CategorizedProxyModel.cpp delete mode 100644 CategorizedProxyModel.h delete mode 100644 CategorizedView.cpp delete mode 100644 CategorizedView.h delete mode 100644 CategorizedViewCategory.cpp delete mode 100644 CategorizedViewCategory.h create mode 100644 Group.cpp create mode 100644 Group.h create mode 100644 GroupView.cpp create mode 100644 GroupView.h create mode 100644 GroupedProxyModel.cpp create mode 100644 GroupedProxyModel.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..167a8fa7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,24 @@ +UseTab: true +IndentWidth: 4 +TabWidth: 4 +ConstructorInitializerIndentWidth: 4 +AccessModifierOffset: -4 +IndentCaseLabels: false +IndentFunctionDeclarationAfterType: false +NamespaceIndentation: None + +BreakBeforeBraces: Allman +AllowShortIfStatementsOnASingleLine: false +ColumnLimit: 96 +MaxEmptyLinesToKeep: 1 + +Standard: Cpp11 +Cpp11BracedListStyle: true + +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true + +AlignTrailingComments: true +SpacesBeforeTrailingComments: 1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 44a28c57..e2a85950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,17 +23,17 @@ find_package(Qt5Widgets REQUIRED) include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) set(SOURCES - main.cpp - main.h - - CategorizedView.h - CategorizedView.cpp - CategorizedViewCategory.h - CategorizedViewCategory.cpp - CategorizedProxyModel.h - CategorizedProxyModel.cpp - InstanceDelegate.h - InstanceDelegate.cpp + main.cpp + main.h + + GroupView.h + GroupView.cpp + Group.h + Group.cpp + GroupedProxyModel.h + GroupedProxyModel.cpp + InstanceDelegate.h + InstanceDelegate.cpp ) add_executable(GroupView ${SOURCES}) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp deleted file mode 100644 index efcf13c8..00000000 --- a/CategorizedProxyModel.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "CategorizedProxyModel.h" - -#include "CategorizedView.h" - -CategorizedProxyModel::CategorizedProxyModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ -} -bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); - if (leftCategory == rightCategory) - { - return left.row() < right.row(); - } - else - { - return leftCategory < rightCategory; - } -} diff --git a/CategorizedProxyModel.h b/CategorizedProxyModel.h deleted file mode 100644 index 6e4f3fdc..00000000 --- a/CategorizedProxyModel.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CATEGORIZEDPROXYMODEL_H -#define CATEGORIZEDPROXYMODEL_H - -#include - -class CategorizedProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - CategorizedProxyModel(QObject *parent = 0); - -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; -}; - - -#endif // CATEGORIZEDPROXYMODEL_H diff --git a/CategorizedView.cpp b/CategorizedView.cpp deleted file mode 100644 index 60230661..00000000 --- a/CategorizedView.cpp +++ /dev/null @@ -1,850 +0,0 @@ -#include "CategorizedView.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CategorizedViewCategory.h" - -template -bool listsIntersect(const QList &l1, const QList t2) -{ - foreach (const T &item, l1) - { - if (t2.contains(item)) - { - return true; - } - } - return false; -} - -CategorizedView::CategorizedView(QWidget *parent) - : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) -{ - setViewMode(IconMode); - setMovement(Snap); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setWordWrap(true); - setDragDropMode(QListView::InternalMove); - setAcceptDrops(true); - setSpacing(10); -} - -CategorizedView::~CategorizedView() -{ - qDeleteAll(m_categories); - m_categories.clear(); -} - -void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::dataChanged(topLeft, bottomRight, roles); - - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) - { - updateGeometries(); - update(); - } -} -void CategorizedView::rowsInserted(const QModelIndex &parent, int start, int end) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::rowsInserted(parent, start, end); - - updateGeometries(); - update(); -} -void CategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::rowsAboutToBeRemoved(parent, start, end); - - updateGeometries(); - update(); -} - -void CategorizedView::updateGeometries() -{ - QListView::updateGeometries(); - - int previousScroll = verticalScrollBar()->value(); - - QMap cats; - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); - if (!cats.contains(category)) - { - CategorizedViewCategory *old = this->category(category); - if (old) - { - cats.insert(category, new CategorizedViewCategory(old)); - } - else - { - cats.insert(category, new CategorizedViewCategory(category, this)); - } - } - } - - /*if (m_editedCategory) - { - m_editedCategory = cats[m_editedCategory->text]; - }*/ - - qDeleteAll(m_categories); - m_categories = cats.values(); - - for (auto cat : m_categories) - { - cat->update(); - } - - if (m_categories.isEmpty()) - { - verticalScrollBar()->setRange(0, 0); - } - else - { - int totalHeight = 0; - foreach (const CategorizedViewCategory *category, m_categories) - { - totalHeight += category->totalHeight() + m_categoryMargin; - } - // remove the last margin (we don't want it) - totalHeight -= m_categoryMargin; - totalHeight += m_bottomMargin; - verticalScrollBar()->setRange(0, totalHeight- height()); - } - - verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); - - update(); -} - -bool CategorizedView::isIndexHidden(const QModelIndex &index) const -{ - CategorizedViewCategory *cat = category(index); - if (cat) - { - return cat->collapsed; - } - else - { - return false; - } -} - -CategorizedViewCategory *CategorizedView::category(const QModelIndex &index) const -{ - return category(index.data(CategorizedViewRoles::CategoryRole).toString()); -} -CategorizedViewCategory *CategorizedView::category(const QString &cat) const -{ - for (int i = 0; i < m_categories.size(); ++i) - { - if (m_categories.at(i)->text == cat) - { - return m_categories.at(i); - } - } - return 0; -} -CategorizedViewCategory *CategorizedView::categoryAt(const QPoint &pos) const -{ - for (int i = 0; i < m_categories.size(); ++i) - { - if (m_categories.at(i)->iconRect.contains(pos)) - { - return m_categories.at(i); - } - } - return 0; -} - -int CategorizedView::itemsPerRow() const -{ - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); -} -int CategorizedView::contentWidth() const -{ - return width() - m_leftMargin - m_rightMargin; -} - -int CategorizedView::itemWidth() const -{ - return itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); -} - -int CategorizedView::categoryRowHeight(const QModelIndex &index) const -{ - QModelIndexList indices; - int internalRow = categoryInternalPosition(index).second; - foreach (const QModelIndex &i, category(index)->items()) - { - if (categoryInternalPosition(i).second == internalRow) - { - indices.append(i); - } - } - - int largestHeight = 0; - foreach (const QModelIndex &i, indices) - { - largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); - } - return largestHeight; -} - -QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const -{ - QList indices = category(index)->items(); - int x = 0; - int y = 0; - const int perRow = itemsPerRow(); - for (int i = 0; i < indices.size(); ++i) - { - if (indices.at(i) == index) - { - break; - } - ++x; - if (x == perRow) - { - x = 0; - ++y; - } - } - return qMakePair(x, y); -} -int CategorizedView::categoryInternalRowTop(const QModelIndex &index) const -{ - CategorizedViewCategory *cat = category(index); - int categoryInternalRow = categoryInternalPosition(index).second; - int result = 0; - for (int i = 0; i < categoryInternalRow; ++i) - { - result += cat->rowHeights.at(i); - } - return result; -} -int CategorizedView::itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const -{ - foreach (const QModelIndex &i, category->items()) - { - QPair pos = categoryInternalPosition(i); - if (pos.second == internalRow) - { - return categoryRowHeight(i); - } - } - return -1; -} - -void CategorizedView::mousePressEvent(QMouseEvent *event) -{ - //endCategoryEditor(); - - QPoint pos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(pos); - - m_pressedIndex = index; - m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - m_pressedPosition = pos; - - m_pressedCategory = categoryAt(m_pressedPosition); - if (m_pressedCategory) - { - setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); - event->accept(); - return; - } - - if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) - { - // we disable scrollTo for mouse press so the item doesn't change position - // when the user is interacting with it (ie. clicking on it) - bool autoScroll = hasAutoScroll(); - setAutoScroll(false); - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - setAutoScroll(autoScroll); - QRect rect(m_pressedPosition, pos); - setSelection(rect, QItemSelectionModel::ClearAndSelect); - - // signal handlers may change the model - emit pressed(index); - - } else { - // Forces a finalize() even if mouse is pressed, but not on a item - selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); - } -} -void CategorizedView::mouseMoveEvent(QMouseEvent *event) -{ - QPoint topLeft; - QPoint bottomRight = event->pos(); - - if (state() == ExpandingState || state() == CollapsingState) - { - return; - } - - if (state() == DraggingState) - { - topLeft = m_pressedPosition - offset(); - if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) - { - m_pressedIndex = QModelIndex(); - startDrag(model()->supportedDragActions()); - setState(NoState); - stopAutoScroll(); - } - return; - } - - QPersistentModelIndex index = indexAt(bottomRight); - - if (selectionMode() != SingleSelection) - { - topLeft = m_pressedPosition - offset(); - } - else - { - topLeft = bottomRight; - } - - if (m_pressedIndex.isValid() - && (state() != DragSelectingState) - && (event->buttons() != Qt::NoButton) - && !selectedIndexes().isEmpty()) - { - setState(DraggingState); - return; - } - - if ((event->buttons() & Qt::LeftButton) && selectionModel()) - { - setState(DragSelectingState); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - command |= m_ctrlDragSelectionFlag; - } - - // Do the normalize ourselves, since QRect::normalized() is flawed - QRect selectionRect = QRect(topLeft, bottomRight); - setSelection(selectionRect, command); - - // set at the end because it might scroll the view - if (index.isValid() - && (index != selectionModel()->currentIndex()) - && (index.flags() & Qt::ItemIsEnabled)) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - } -} -void CategorizedView::mouseReleaseEvent(QMouseEvent *event) -{ - QPoint pos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(pos); - - bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); - - if (click && m_pressedCategory) - { - if (state() == ExpandingState) - { - m_pressedCategory->collapsed = false; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - else if (state() == CollapsingState) - { - m_pressedCategory->collapsed = true; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } - - m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; - - setState(NoState); - - if (click) - { - if (event->button() == Qt::LeftButton) - { - emit clicked(index); - } - QStyleOptionViewItem option = viewOptions(); - if (m_pressedAlreadySelected) - { - option.state |= QStyle::State_Selected; - } - if ((model()->flags(index) & Qt::ItemIsEnabled) - && style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) - { - emit activated(index); - } - } -} -void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) -{ - QModelIndex index = indexAt(event->pos()); - if (!index.isValid() - || !(index.flags() & Qt::ItemIsEnabled) - || (m_pressedIndex != index)) - { - QMouseEvent me(QEvent::MouseButtonPress, - event->localPos(), event->windowPos(), event->screenPos(), - event->button(), event->buttons(), event->modifiers()); - mousePressEvent(&me); - return; - } - // signal handlers may change the model - QPersistentModelIndex persistent = index; - emit doubleClicked(persistent); -} -void CategorizedView::paintEvent(QPaintEvent *event) -{ - QPainter painter(this->viewport()); - painter.translate(-offset()); - - int y = 0; - for (int i = 0; i < m_categories.size(); ++i) - { - CategorizedViewCategory *category = m_categories.at(i); - category->drawHeader(&painter, y); - y += category->totalHeight() + m_categoryMargin; - } - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QModelIndex index = model()->index(i, 0); - if (isIndexHidden(index)) - { - continue; - } - Qt::ItemFlags flags = index.flags(); - QStyleOptionViewItemV4 option(viewOptions()); - option.rect = visualRect(index); - option.widget = this; - option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; - if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) - { - option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; - if (!(flags & Qt::ItemIsEnabled)) - { - option.state &= ~QStyle::State_Enabled; - } - itemDelegate()->paint(&painter, option, index); - } - - if (!m_lastDragPosition.isNull()) - { - QPair pair = rowDropPos(m_lastDragPosition); - CategorizedViewCategory *category = pair.first; - int row = pair.second; - if (category) - { - int internalRow = row - category->firstRow; - QLine line; - if (internalRow >= category->numItems()) - { - QRect toTheRightOfRect = visualRect(category->lastItem()); - line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); - } - else - { - QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); - line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); - } - painter.save(); - painter.setPen(QPen(Qt::black, 3)); - painter.drawLine(line); - painter.restore(); - } - } -} -void CategorizedView::resizeEvent(QResizeEvent *event) -{ - QListView::resizeEvent(event); - -// if (m_categoryEditor) -// { -// m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), m_categoryEditor->height()); -// } - - updateGeometries(); -} - -void CategorizedView::dragEnterEvent(QDragEnterEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} -void CategorizedView::dragMoveEvent(QDragMoveEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} -void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) -{ - m_lastDragPosition = QPoint(); - viewport()->update(); -} -void CategorizedView::dropEvent(QDropEvent *event) -{ - m_lastDragPosition = QPoint(); - - stopAutoScroll(); - setState(NoState); - - if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) - { - return; - } - - QPair dropPos = rowDropPos(event->pos() + offset()); - const CategorizedViewCategory *category = dropPos.first; - const int row = dropPos.second; - - if (row == -1) - { - viewport()->update(); - return; - } - - const QString categoryText = category->text; - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - model()->setData(model()->index(row, 0), categoryText, CategorizedViewRoles::CategoryRole); - event->setDropAction(Qt::MoveAction); - event->accept(); - } - updateGeometries(); - viewport()->update(); -} - -void CategorizedView::startDrag(Qt::DropActions supportedActions) -{ - QModelIndexList indexes = selectionModel()->selectedIndexes(); - if (indexes.count() > 0) - { - QMimeData *data = model()->mimeData(indexes); - if (!data) - { - return; - } - QRect rect; - QPixmap pixmap = renderToPixmap(indexes, &rect); - rect.translate(offset()); - //rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); - QDrag *drag = new QDrag(this); - drag->setPixmap(pixmap); - drag->setMimeData(data); - drag->setHotSpot(m_pressedPosition - rect.topLeft()); - Qt::DropAction defaultDropAction = Qt::IgnoreAction; - if (this->defaultDropAction() != Qt::IgnoreAction && (supportedActions & this->defaultDropAction())) - { - defaultDropAction = this->defaultDropAction(); - } - if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) - { - const QItemSelection selection = selectionModel()->selection(); - - for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) { - QModelIndex parent = (*it).parent(); - if ((*it).left() != 0) - { - continue; - } - if ((*it).right() != (model()->columnCount(parent) - 1)) - { - continue; - } - int count = (*it).bottom() - (*it).top() + 1; - model()->removeRows((*it).top(), count, parent); - } - } - } -} - -QRect CategorizedView::visualRect(const QModelIndex &index) const -{ - if (!index.isValid() || isIndexHidden(index) || index.column() > 0) - { - return QRect(); - } - - const CategorizedViewCategory *cat = category(index); - QPair pos = categoryInternalPosition(index); - int x = pos.first; - int y = pos.second; - - QRect out; - out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); - out.setLeft(spacing() + x * itemWidth() + x * spacing()); - out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - - return out; -} -/* -void CategorizedView::startCategoryEditor(Category *category) -{ - if (m_categoryEditor != 0) - { - return; - } - m_editedCategory = category; - m_categoryEditor = new QLineEdit(m_editedCategory->text, this); - QRect rect = m_editedCategory->textRect; - rect.setWidth(qMax(contentWidth() / 2, rect.width())); - m_categoryEditor->setGeometry(rect); - m_categoryEditor->show(); - m_categoryEditor->setFocus(); - connect(m_categoryEditor, &QLineEdit::returnPressed, this, &CategorizedView::endCategoryEditor); -} - -void CategorizedView::endCategoryEditor() -{ - if (m_categoryEditor == 0) - { - return; - } - m_editedCategory->text = m_categoryEditor->text(); - m_updatesDisabled = true; - foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) - { - const_cast(index.model())->setData(index, m_categoryEditor->text(), CategoryRole); - } - m_updatesDisabled = false; - delete m_categoryEditor; - m_categoryEditor = 0; - m_editedCategory = 0; - updateGeometries(); -} -*/ - -QModelIndex CategorizedView::indexAt(const QPoint &point) const -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) - { - return index; - } - } - return QModelIndex(); -} -void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).intersects(rect)) - { - selectionModel()->select(index, commands); - } - } - update(); -} - -QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const -{ - Q_ASSERT(r); - QList > paintPairs = draggablePaintPairs(indices, r); - if (paintPairs.isEmpty()) - { - return QPixmap(); - } - QPixmap pixmap(r->size()); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - QStyleOptionViewItem option = viewOptions(); - option.state |= QStyle::State_Selected; - for (int j = 0; j < paintPairs.count(); ++j) - { - option.rect = paintPairs.at(j).first.translated(-r->topLeft()); - const QModelIndex ¤t = paintPairs.at(j).second; - itemDelegate()->paint(&painter, option, current); - } - return pixmap; -} -QList > CategorizedView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - const QRect viewportRect = viewport()->rect(); - QList > ret; - for (int i = 0; i < indices.count(); ++i) { - const QModelIndex &index = indices.at(i); - const QRect current = visualRect(index); - if (current.intersects(viewportRect)) { - ret += qMakePair(current, index); - rect |= current; - } - } - rect &= viewportRect; - return ret; -} - -bool CategorizedView::isDragEventAccepted(QDropEvent *event) -{ - if (event->source() != this) - { - return false; - } - if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) - { - return false; - } - if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), rowDropPos(event->pos()).second, 0, QModelIndex())) - { - return false; - } - return true; -} -QPair CategorizedView::rowDropPos(const QPoint &pos) -{ - // check that we aren't on a category header and calculate which category we're in - CategorizedViewCategory *category = 0; - { - int y = 0; - foreach (CategorizedViewCategory *cat, m_categories) - { - if (pos.y() > y && pos.y() < (y + cat->headerHeight())) - { - return qMakePair(nullptr, -1); - } - y += cat->totalHeight() + m_categoryMargin; - if (pos.y() < y) - { - category = cat; - break; - } - } - if (category == 0) - { - return qMakePair(nullptr, -1); - } - } - - QList indices = category->items(); - - // calculate the internal column - int internalColumn = -1; - { - const int itemWidth = this->itemWidth(); - if (pos.x() >= (itemWidth * itemsPerRow())) - { - internalColumn = itemsPerRow(); - } - else - { - for (int i = 0, c = 0; - i < contentWidth(); - i += itemWidth + spacing(), ++c) - { - if (pos.x() > (i - itemWidth / 2) && - pos.x() <= (i + itemWidth / 2)) - { - internalColumn = c; - break; - } - } - } - if (internalColumn == -1) - { - return qMakePair(nullptr, -1); - } - } - - // calculate the internal row - int internalRow = -1; - { - // FIXME rework the drag and drop code - const int top = category->top(); - for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) - { - if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) - { - internalRow = r; - break; - } - } - if (internalRow == -1) - { - return qMakePair(nullptr, -1); - } - // this happens if we're in the margin between a one category and another - // categories header - if (internalRow > (indices.size() / itemsPerRow())) - { - return qMakePair(nullptr, -1); - } - } - - // flaten the internalColumn/internalRow to one row - int categoryRow = internalRow * itemsPerRow() + internalColumn; - - // this is used if we're past the last item - if (categoryRow >= indices.size()) - { - return qMakePair(category, indices.last().row() + 1); - } - - return qMakePair(category, indices.at(categoryRow).row()); -} - -QPoint CategorizedView::offset() const -{ - return QPoint(horizontalOffset(), verticalOffset()); -} diff --git a/CategorizedView.h b/CategorizedView.h deleted file mode 100644 index 0550c7f8..00000000 --- a/CategorizedView.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef WIDGET_H -#define WIDGET_H - -#include -#include - -struct CategorizedViewRoles -{ - enum - { - CategoryRole = Qt::UserRole, - ProgressValueRole, - ProgressMaximumRole - }; -}; - -struct CategorizedViewCategory; - -class CategorizedView : public QListView -{ - Q_OBJECT - -public: - CategorizedView(QWidget *parent = 0); - ~CategorizedView(); - - virtual QRect visualRect(const QModelIndex &index) const; - QModelIndex indexAt(const QPoint &point) const; - void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; - -protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); - virtual void rowsInserted(const QModelIndex &parent, int start, int end); - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - virtual void updateGeometries(); - -protected: - virtual bool isIndexHidden(const QModelIndex &index) const; - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - - void dragEnterEvent(QDragEnterEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - void startDrag(Qt::DropActions supportedActions) override; - -private: - friend struct CategorizedViewCategory; - - QList m_categories; - - int m_leftMargin; - int m_rightMargin; - int m_bottomMargin; - int m_categoryMargin; - - //bool m_updatesDisabled; - - CategorizedViewCategory *category(const QModelIndex &index) const; - CategorizedViewCategory *category(const QString &cat) const; - CategorizedViewCategory *categoryAt(const QPoint &pos) const; - - int itemsPerRow() const; - int contentWidth() const; - -private: - int itemWidth() const; - int categoryRowHeight(const QModelIndex &index) const; - - /*QLineEdit *m_categoryEditor; - Category *m_editedCategory; - void startCategoryEditor(Category *category); - -private slots: - void endCategoryEditor();*/ - -private: - QPoint m_pressedPosition; - QPersistentModelIndex m_pressedIndex; - bool m_pressedAlreadySelected; - CategorizedViewCategory *m_pressedCategory; - QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; - QPoint m_lastDragPosition; - - QPair categoryInternalPosition(const QModelIndex &index) const; - int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const; - - QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; - - bool isDragEventAccepted(QDropEvent *event); - - QPair rowDropPos(const QPoint &pos); - - QPoint offset() const; -}; - -#endif // WIDGET_H diff --git a/CategorizedViewCategory.cpp b/CategorizedViewCategory.cpp deleted file mode 100644 index b82ffc96..00000000 --- a/CategorizedViewCategory.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "CategorizedViewCategory.h" - -#include -#include -#include - -#include "CategorizedView.h" - -CategorizedViewCategory::CategorizedViewCategory(const QString &text, CategorizedView *view) - : view(view), text(text), collapsed(false) -{ -} -CategorizedViewCategory::CategorizedViewCategory(const CategorizedViewCategory *other) : - view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) -{ -} - -void CategorizedViewCategory::update() -{ - firstRow = firstItem().row(); - - rowHeights = QVector(numRows()); - for (int i = 0; i < numRows(); ++i) - { - rowHeights[i] = view->categoryRowHeight(view->model()->index(i * view->itemsPerRow() + firstRow, 0)); - } -} - -void CategorizedViewCategory::drawHeader(QPainter *painter, const int y) -{ - painter->save(); - - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); - painter->setPen(QPen(Qt::black, 1)); - painter->drawRect(iconRect); - static const int margin = 2; - QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); - int midX = iconSubrect.center().x(); - int midY = iconSubrect.center().y(); - if (collapsed) - { - painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); - } - painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); - - // the text - int textWidth = painter->fontMetrics().width(text); - textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - painter->setBrush(view->viewOptions().palette.text()); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->viewport()->palette(), true, text); - - // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); - - painter->restore(); -} - -int CategorizedViewCategory::totalHeight() const -{ - return headerHeight() + 5 + contentHeight(); -} -int CategorizedViewCategory::headerHeight() const -{ - return view->viewport()->fontMetrics().height() + 4; -} -int CategorizedViewCategory::contentHeight() const -{ - if (collapsed) - { - return 0; - } - int result = 0; - for (int i = 0; i < rowHeights.size(); ++i) - { - result += rowHeights[i]; - } - return result; -} -int CategorizedViewCategory::numRows() const -{ - return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); -} -int CategorizedViewCategory::top() const -{ - int res = 0; - const QList cats = view->m_categories; - for (int i = 0; i < cats.size(); ++i) - { - if (cats.at(i) == this) - { - break; - } - res += cats.at(i)->totalHeight() + view->m_categoryMargin; - } - return res; -} - -QList CategorizedViewCategory::items() const -{ - QList indices; - for (int i = 0; i < view->model()->rowCount(); ++i) - { - const QModelIndex index = view->model()->index(i, 0); - if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) - { - indices.append(index); - } - } - return indices; -} -int CategorizedViewCategory::numItems() const -{ - return items().size(); -} -QModelIndex CategorizedViewCategory::firstItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.first(); -} -QModelIndex CategorizedViewCategory::lastItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.last(); -} diff --git a/CategorizedViewCategory.h b/CategorizedViewCategory.h deleted file mode 100644 index cb6ef8c5..00000000 --- a/CategorizedViewCategory.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CATEGORIZEDVIEWROW_H -#define CATEGORIZEDVIEWROW_H - -#include -#include -#include - -class CategorizedView; -class QPainter; -class QModelIndex; - -struct CategorizedViewCategory -{ - CategorizedViewCategory(const QString &text, CategorizedView *view); - CategorizedViewCategory(const CategorizedViewCategory *other); - CategorizedView *view; - QString text; - bool collapsed; - QRect iconRect; - QRect textRect; - QVector rowHeights; - int firstRow; - - void update(); - - void drawHeader(QPainter *painter, const int y); - int totalHeight() const; - int headerHeight() const; - int contentHeight() const; - int numRows() const; - int top() const; - - QList items() const; - int numItems() const; - QModelIndex firstItem() const; - QModelIndex lastItem() const; -}; - -#endif // CATEGORIZEDVIEWROW_H diff --git a/Group.cpp b/Group.cpp new file mode 100644 index 00000000..f23066c5 --- /dev/null +++ b/Group.cpp @@ -0,0 +1,139 @@ +#include "Group.h" + +#include +#include +#include + +#include "GroupView.h" + +Group::Group(const QString &text, GroupView *view) + : view(view), text(text), collapsed(false) +{ +} +Group::Group(const Group *other) + : view(other->view), text(other->text), collapsed(other->collapsed), + iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void Group::update() +{ + firstRow = firstItem().row(); + + rowHeights = QVector(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight( + view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + } +} + +void Group::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); + painter->setPen(QPen(Qt::black, 1)); + painter->drawRect(iconRect); + static const int margin = 2; + QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + int midX = iconSubrect.center().x(); + int midY = iconSubrect.center().y(); + if (collapsed) + { + painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + } + painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); + + // the text + int textWidth = painter->fontMetrics().width(text); + textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->setBrush(view->viewOptions().palette.text()); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, + view->viewport()->palette(), true, text); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, + view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int Group::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); +} + +int Group::headerHeight() const +{ + return view->viewport()->fontMetrics().height() + 4; +} + +int Group::contentHeight() const +{ + if (collapsed) + { + return 0; + } + int result = 0; + for (int i = 0; i < rowHeights.size(); ++i) + { + result += rowHeights[i]; + } + return result; +} + +int Group::numRows() const +{ + return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); +} + +int Group::top() const +{ + int res = 0; + const QList cats = view->m_categories; + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == this) + { + break; + } + res += cats.at(i)->totalHeight() + view->m_categoryMargin; + } + return res; +} + +QList Group::items() const +{ + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} + +int Group::numItems() const +{ + return items().size(); +} + +QModelIndex Group::firstItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} + +QModelIndex Group::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/Group.h b/Group.h new file mode 100644 index 00000000..6a8fadeb --- /dev/null +++ b/Group.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +class GroupView; +class QPainter; +class QModelIndex; + +struct Group +{ + Group(const QString &text, GroupView *view); + Group(const Group *other); + GroupView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + QVector rowHeights; + int firstRow; + + void update(); + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + int numRows() const; + int top() const; + + QList items() const; + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; diff --git a/GroupView.cpp b/GroupView.cpp new file mode 100644 index 00000000..e3bc1055 --- /dev/null +++ b/GroupView.cpp @@ -0,0 +1,881 @@ +#include "GroupView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Group.h" + +template bool listsIntersect(const QList &l1, const QList t2) +{ + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} + +GroupView::GroupView(QWidget *parent) + : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), + m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) +{ + setViewMode(IconMode); + //setMovement(Snap); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setWordWrap(true); + //setDragDropMode(QListView::InternalMove); + setAcceptDrops(true); + setSpacing(10); +} + +GroupView::~GroupView() +{ + qDeleteAll(m_categories); + m_categories.clear(); +} + +void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::dataChanged(topLeft, bottomRight, roles); + + if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) + { + updateGeometries(); + update(); + } +} +void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::rowsInserted(parent, start, end); + + updateGeometries(); + update(); +} + +void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::rowsAboutToBeRemoved(parent, start, end); + + updateGeometries(); + update(); +} + +void GroupView::updateGeometries() +{ + QListView::updateGeometries(); + + int previousScroll = verticalScrollBar()->value(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString category = + model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); + if (!cats.contains(category)) + { + Group *old = this->category(category); + if (old) + { + cats.insert(category, new Group(old)); + } + else + { + cats.insert(category, new Group(category, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_categories); + m_categories = cats.values(); + + for (auto cat : m_categories) + { + cat->update(); + } + + if (m_categories.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + for (auto category : m_categories) + { + totalHeight += category->totalHeight() + m_categoryMargin; + } + // remove the last margin (we don't want it) + totalHeight -= m_categoryMargin; + totalHeight += m_bottomMargin; + verticalScrollBar()->setRange(0, totalHeight - height()); + } + + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + + update(); +} + +bool GroupView::isIndexHidden(const QModelIndex &index) const +{ + Group *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } +} + +Group *GroupView::category(const QModelIndex &index) const +{ + return category(index.data(CategorizedViewRoles::CategoryRole).toString()); +} + +Group *GroupView::category(const QString &cat) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->text == cat) + { + return m_categories.at(i); + } + } + return 0; +} + +Group *GroupView::categoryAt(const QPoint &pos) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->iconRect.contains(pos)) + { + return m_categories.at(i); + } + } + return 0; +} + +int GroupView::itemsPerRow() const +{ + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + /* spacing */ 10)); +} + +int GroupView::contentWidth() const +{ + return width() - m_leftMargin - m_rightMargin; +} + +int GroupView::itemWidth() const +{ + return itemDelegate() + ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0)) + .width(); +} + +int GroupView::categoryRowHeight(const QModelIndex &index) const +{ + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + for (auto &i : category(index)->items()) + { + if (categoryInternalPosition(i).second == internalRow) + { + indices.append(i); + } + } + + int largestHeight = 0; + for (auto &i : indices) + { + largestHeight = + qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); + } + return largestHeight; +} + +QPair GroupView::categoryInternalPosition(const QModelIndex &index) const +{ + QList indices = category(index)->items(); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) + { + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } + } + return qMakePair(x, y); +} + +int GroupView::categoryInternalRowTop(const QModelIndex &index) const +{ + Group *cat = category(index); + int categoryInternalRow = categoryInternalPosition(index).second; + int result = 0; + for (int i = 0; i < categoryInternalRow; ++i) + { + result += cat->rowHeights.at(i); + } + return result; +} + +int GroupView::itemHeightForCategoryRow(const Group *category, + const int internalRow) const +{ + for (auto &i : category->items()) + { + QPair pos = categoryInternalPosition(i); + if (pos.second == internalRow) + { + return categoryRowHeight(i); + } + } + return -1; +} + +void GroupView::mousePressEvent(QMouseEvent *event) +{ + // endCategoryEditor(); + + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + m_pressedPosition = pos; + + m_pressedCategory = categoryAt(m_pressedPosition); + if (m_pressedCategory) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + setAutoScroll(autoScroll); + QRect rect(m_pressedPosition, pos); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + + // signal handlers may change the model + emit pressed(index); + } + else + { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } +} + +void GroupView::mouseMoveEvent(QMouseEvent *event) +{ + QPoint topLeft; + QPoint bottomRight = event->pos(); + + if (state() == ExpandingState || state() == CollapsingState) + { + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - offset(); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) + { + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); + } + return; + } + + QPersistentModelIndex index = indexAt(bottomRight); + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = bottomRight; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && + command.testFlag(QItemSelectionModel::Toggle)) + { + command &= ~QItemSelectionModel::Toggle; + command |= m_ctrlDragSelectionFlag; + } + + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + setSelection(selectionRect, command); + + // set at the end because it might scroll the view + if (index.isValid() && (index != selectionModel()->currentIndex()) && + (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } +} + +void GroupView::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + bool click = (index == m_pressedIndex && index.isValid()) || + (m_pressedCategory && m_pressedCategory == categoryAt(pos)); + + if (click && m_pressedCategory) + { + if (state() == ExpandingState) + { + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); + + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) && + style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } +} + +void GroupView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), + event->screenPos(), event->button(), event->buttons(), + event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); +} + +void GroupView::paintEvent(QPaintEvent *event) +{ + QPainter painter(this->viewport()); + painter.translate(-offset()); + + int y = 0; + for (int i = 0; i < m_categories.size(); ++i) + { + Group *category = m_categories.at(i); + category->drawHeader(&painter, y); + y += category->totalHeight() + m_categoryMargin; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + QStyleOptionViewItemV4 option(viewOptions()); + option.rect = visualRect(index); + option.widget = this; + option.features |= QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } + + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstRow; + QLine line; + if (internalRow >= category->numItems()) + { + QRect toTheRightOfRect = visualRect(category->lastItem()); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } +} + +void GroupView::resizeEvent(QResizeEvent *event) +{ + QListView::resizeEvent(event); + + // if (m_categoryEditor) + // { + // m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), + //m_categoryEditor->height()); + // } + + updateGeometries(); +} + +void GroupView::dragEnterEvent(QDragEnterEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragMoveEvent(QDragMoveEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragLeaveEvent(QDragLeaveEvent *event) +{ + m_lastDragPosition = QPoint(); + viewport()->update(); +} + +void GroupView::dropEvent(QDropEvent *event) +{ + m_lastDragPosition = QPoint(); + + stopAutoScroll(); + setState(NoState); + + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + { + return; + } + + QPair dropPos = rowDropPos(event->pos() + offset()); + const Group *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) + { + viewport()->update(); + return; + } + + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + model()->setData(model()->index(row, 0), categoryText, + CategorizedViewRoles::CategoryRole); + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); + viewport()->update(); +} + +void GroupView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.count() > 0) + { + QMimeData *data = model()->mimeData(indexes); + if (!data) + { + return; + } + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + rect.translate(offset()); + // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(m_pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && + (supportedActions & this->defaultDropAction())) + { + defaultDropAction = this->defaultDropAction(); + } + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + { + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); + } + } + } +} + +QRect GroupView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Group *cat = category(index); + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; + + QRect out; + out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(/*spacing*/ 10 + x * itemWidth() + x * /*spacing()*/ 10); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + + return out; +} +/* +void CategorizedView::startCategoryEditor(Category *category) +{ + if (m_categoryEditor != 0) + { + return; + } + m_editedCategory = category; + m_categoryEditor = new QLineEdit(m_editedCategory->text, this); + QRect rect = m_editedCategory->textRect; + rect.setWidth(qMax(contentWidth() / 2, rect.width())); + m_categoryEditor->setGeometry(rect); + m_categoryEditor->show(); + m_categoryEditor->setFocus(); + connect(m_categoryEditor, &QLineEdit::returnPressed, this, +&CategorizedView::endCategoryEditor); +} + +void CategorizedView::endCategoryEditor() +{ + if (m_categoryEditor == 0) + { + return; + } + m_editedCategory->text = m_categoryEditor->text(); + m_updatesDisabled = true; + foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) + { + const_cast(index.model())->setData(index, +m_categoryEditor->text(), CategoryRole); + } + m_updatesDisabled = false; + delete m_categoryEditor; + m_categoryEditor = 0; + m_editedCategory = 0; + updateGeometries(); +} +*/ + +QModelIndex GroupView::indexAt(const QPoint &point) const +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); +} + +void GroupView::setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).intersects(rect)) + { + selectionModel()->select(index, commands); + } + } + update(); +} + +QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + auto paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} + +QList> +GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + const QRect viewportRect = viewport()->rect(); + QList> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = visualRect(index); + if (current.intersects(viewportRect)) + { + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + +bool GroupView::isDragEventAccepted(QDropEvent *event) +{ + if (event->source() != this) + { + return false; + } + if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) + { + return false; + } + if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), + rowDropPos(event->pos()).second, 0, QModelIndex())) + { + return false; + } + return true; +} + +QPair GroupView::rowDropPos(const QPoint &pos) +{ + // check that we aren't on a category header and calculate which category we're in + Group *category = 0; + { + int y = 0; + foreach(Group * cat, m_categories) + { + if (pos.y() > y && pos.y() < (y + cat->headerHeight())) + { + return qMakePair(nullptr, -1); + } + y += cat->totalHeight() + m_categoryMargin; + if (pos.y() < y) + { + category = cat; + break; + } + } + if (category == 0) + { + return qMakePair(nullptr, -1); + } + } + + QList indices = category->items(); + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = this->itemWidth(); + if (pos.x() >= (itemWidth * itemsPerRow())) + { + internalColumn = itemsPerRow(); + } + else + { + for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) + { + if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + } + if (internalColumn == -1) + { + return qMakePair(nullptr, -1); + } + } + + // calculate the internal row + int internalRow = -1; + { + // FIXME rework the drag and drop code + const int top = category->top(); + for (int r = 0, h = top; r < category->numRows(); + h += itemHeightForCategoryRow(category, r), ++r) + { + if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + return qMakePair(nullptr, -1); + } + // this happens if we're in the margin between a one category and another + // categories header + if (internalRow > (indices.size() / itemsPerRow())) + { + return qMakePair(nullptr, -1); + } + } + + // flaten the internalColumn/internalRow to one row + int categoryRow = internalRow * itemsPerRow() + internalColumn; + + // this is used if we're past the last item + if (categoryRow >= indices.size()) + { + return qMakePair(category, indices.last().row() + 1); + } + + return qMakePair(category, indices.at(categoryRow).row()); +} + +QPoint GroupView::offset() const +{ + return QPoint(horizontalOffset(), verticalOffset()); +} diff --git a/GroupView.h b/GroupView.h new file mode 100644 index 00000000..e949d892 --- /dev/null +++ b/GroupView.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +struct CategorizedViewRoles +{ + enum + { + CategoryRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + +struct Group; + +class GroupView : public QListView +{ + Q_OBJECT + +public: + GroupView(QWidget *parent = 0); + ~GroupView(); + + virtual QRect visualRect(const QModelIndex &index) const; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; + +protected +slots: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void updateGeometries(); + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + + void startDrag(Qt::DropActions supportedActions) override; + +private: + friend struct Group; + + QList m_categories; + + int m_leftMargin; + int m_rightMargin; + int m_bottomMargin; + int m_categoryMargin; + + // bool m_updatesDisabled; + + Group *category(const QModelIndex &index) const; + Group *category(const QString &cat) const; + Group *categoryAt(const QPoint &pos) const; + + int itemsPerRow() const; + int contentWidth() const; + +private: + int itemWidth() const; + int categoryRowHeight(const QModelIndex &index) const; + + /*QLineEdit *m_categoryEditor; + Category *m_editedCategory; + void startCategoryEditor(Category *category); + +private slots: + void endCategoryEditor();*/ + +private: + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Group *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + QPair categoryInternalPosition(const QModelIndex &index) const; + int categoryInternalRowTop(const QModelIndex &index) const; + int itemHeightForCategoryRow(const Group *category, + const int internalRow) const; + + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); + + QPoint offset() const; +}; diff --git a/GroupedProxyModel.cpp b/GroupedProxyModel.cpp new file mode 100644 index 00000000..ab00a412 --- /dev/null +++ b/GroupedProxyModel.cpp @@ -0,0 +1,21 @@ +#include "GroupedProxyModel.h" + +#include "GroupView.h" + +GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ +} + +bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); + if (leftCategory == rightCategory) + { + return left.row() < right.row(); + } + else + { + return leftCategory < rightCategory; + } +} diff --git a/GroupedProxyModel.h b/GroupedProxyModel.h new file mode 100644 index 00000000..cae87ecd --- /dev/null +++ b/GroupedProxyModel.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class GroupedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GroupedProxyModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 50cead55..056db99d 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -20,7 +20,7 @@ #include #include -#include "CategorizedView.h" +#include "GroupView.h" // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, @@ -88,7 +88,8 @@ void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, cons } // TODO this can be made a lot prettier -void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, const int value, const int maximum) +void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, + const int value, const int maximum) { if (maximum == 0 || value == maximum) { @@ -251,7 +252,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti line.draw(painter, position); } - drawProgressOverlay(painter, opt, index.data(CategorizedViewRoles::ProgressValueRole).toInt(), + drawProgressOverlay(painter, opt, + index.data(CategorizedViewRoles::ProgressValueRole).toInt(), index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); painter->restore(); diff --git a/InstanceDelegate.h b/InstanceDelegate.h index 6f924405..de2f429b 100644 --- a/InstanceDelegate.h +++ b/InstanceDelegate.h @@ -20,8 +20,10 @@ class ListViewDelegate : public QStyledItemDelegate { public: - explicit ListViewDelegate ( QObject* parent = 0 ); + explicit ListViewDelegate(QObject *parent = 0); + protected: - void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; }; diff --git a/main.cpp b/main.cpp index bd6a44f9..b9ecde8f 100644 --- a/main.cpp +++ b/main.cpp @@ -5,8 +5,8 @@ #include #include -#include "CategorizedView.h" -#include "CategorizedProxyModel.h" +#include "GroupView.h" +#include "GroupedProxyModel.h" #include "InstanceDelegate.h" Progresser *progresser; @@ -25,18 +25,20 @@ QPixmap icon(const int number) font.setBold(true); font.setPixelSize(28); painter.setFont(font); - painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, QString::number(number)); + painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, + QString::number(number)); painter.end(); return p; } -QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, const QString &category) +QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, + const QString &category) { QStandardItem *item = new QStandardItem; item->setText(text); item->setData(icon(color), Qt::DecorationRole); item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - //progresser->addTrackedIndex(item); + // progresser->addTrackedIndex(item); return item; } QStandardItem *createItem(const int index, const QString &category) @@ -46,7 +48,7 @@ QStandardItem *createItem(const int index, const QString &category) item->setData(icon(index), Qt::DecorationRole); item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - //progresser->addTrackedIndex(item); + // progresser->addTrackedIndex(item); return item; } @@ -62,7 +64,10 @@ int main(int argc, char *argv[]) model.setRowCount(10); model.setColumnCount(1); - model.setItem(0, createItem(Qt::red, "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", "Colorful")); + model.setItem( + 0, createItem(Qt::red, + "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", + "Colorful")); model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); @@ -77,13 +82,13 @@ int main(int argc, char *argv[]) for (int i = 0; i < 20; ++i) { - model.setItem(i + 10, createItem(i+1, "Items 1-20")); + model.setItem(i + 10, createItem(i + 1, "Items 1-20")); } - CategorizedProxyModel pModel; + GroupedProxyModel pModel; pModel.setSourceModel(&model); - CategorizedView w; + GroupView w; w.setItemDelegate(new ListViewDelegate); w.setModel(&pModel); w.resize(640, 480); diff --git a/main.h b/main.h index f4c7a3f8..a1e7f432 100644 --- a/main.h +++ b/main.h @@ -1,5 +1,4 @@ -#ifndef MAIN_H -#define MAIN_H +#pragma once #include #include @@ -7,7 +6,7 @@ #include #include -#include "CategorizedView.h" +#include "GroupView.h" class Progresser : public QObject { @@ -27,10 +26,11 @@ public: return item; } -public slots: +public +slots: void timeout() { - foreach (QStandardItem *item, m_items) + foreach(QStandardItem * item, m_items) { int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); value += qrand() % 3; @@ -49,5 +49,3 @@ public slots: private: QList m_items; }; - -#endif // MAIN_H -- cgit From 179451d5911ccce569492717b1f0b0186e25cab9 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Fri, 31 Jan 2014 22:58:57 +0100 Subject: More reformat. --- Group.cpp | 3 +-- GroupView.cpp | 25 +++++++++++++------------ GroupView.h | 3 +-- main.h | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Group.cpp b/Group.cpp index f23066c5..a62f592b 100644 --- a/Group.cpp +++ b/Group.cpp @@ -6,8 +6,7 @@ #include "GroupView.h" -Group::Group(const QString &text, GroupView *view) - : view(view), text(text), collapsed(false) +Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) { } Group::Group(const Group *other) diff --git a/GroupView.cpp b/GroupView.cpp index e3bc1055..94682df8 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -30,11 +30,11 @@ GroupView::GroupView(QWidget *parent) m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { setViewMode(IconMode); - //setMovement(Snap); + // setMovement(Snap); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setWordWrap(true); - //setDragDropMode(QListView::InternalMove); + // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); setSpacing(10); } @@ -46,7 +46,7 @@ GroupView::~GroupView() } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) + const QVector &roles) { // if (m_updatesDisabled) // { @@ -262,8 +262,7 @@ int GroupView::categoryInternalRowTop(const QModelIndex &index) const return result; } -int GroupView::itemHeightForCategoryRow(const Group *category, - const int internalRow) const +int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const { for (auto &i : category->items()) { @@ -473,7 +472,8 @@ void GroupView::paintEvent(QPaintEvent *event) QStyleOptionViewItemV4 option(viewOptions()); option.rect = visualRect(index); option.widget = this; - option.features |= QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? + option.features |= + QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) { option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected @@ -524,8 +524,9 @@ void GroupView::resizeEvent(QResizeEvent *event) // if (m_categoryEditor) // { - // m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), - //m_categoryEditor->height()); + // m_categoryEditor->resize(qMax(contentWidth() / 2, + //m_editedCategory->textRect.width()), + // m_categoryEditor->height()); // } updateGeometries(); @@ -711,7 +712,7 @@ QModelIndex GroupView::indexAt(const QPoint &point) const } void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) + const QItemSelectionModel::SelectionFlags commands) { for (int i = 0; i < model()->rowCount(); ++i) { @@ -746,8 +747,8 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons return pixmap; } -QList> -GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const { Q_ASSERT(r); QRect &rect = *r; @@ -791,7 +792,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) Group *category = 0; { int y = 0; - foreach(Group * cat, m_categories) + for (auto cat : m_categories) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { diff --git a/GroupView.h b/GroupView.h index e949d892..6d55a462 100644 --- a/GroupView.h +++ b/GroupView.h @@ -92,8 +92,7 @@ private: QPair categoryInternalPosition(const QModelIndex &index) const; int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Group *category, - const int internalRow) const; + int itemHeightForCategoryRow(const Group *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList> draggablePaintPairs(const QModelIndexList &indices, diff --git a/main.h b/main.h index a1e7f432..47377f7a 100644 --- a/main.h +++ b/main.h @@ -30,7 +30,7 @@ public slots: void timeout() { - foreach(QStandardItem * item, m_items) + for (auto item : m_items) { int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); value += qrand() % 3; -- cgit From 4a9e213238234b03fdd994143726a3f75290fc26 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 1 Feb 2014 14:52:21 +0100 Subject: Change the OneSix library view. It now shows a list of patches. --- gui/dialogs/OneSixModEditDialog.cpp | 63 ++++++++++++++++++++++++++++++++----- gui/dialogs/OneSixModEditDialog.h | 5 ++- gui/dialogs/OneSixModEditDialog.ui | 36 +++++---------------- logic/ForgeInstaller.cpp | 6 ++++ logic/ForgeInstaller.h | 1 + logic/LiteLoaderInstaller.cpp | 4 +++ logic/OneSixInstance.cpp | 5 +-- logic/OneSixVersion.cpp | 55 +++++++++++++++++++++++++++++--- logic/OneSixVersion.h | 17 ++++++++++ logic/OneSixVersionBuilder.cpp | 40 ++++++++++++++++++++--- 10 files changed, 183 insertions(+), 49 deletions(-) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index ad15311b..a34409f5 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -55,6 +55,8 @@ OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) main_model->setSourceModel(m_version.get()); ui->libraryTreeView->setModel(main_model); ui->libraryTreeView->installEventFilter(this); + connect(ui->libraryTreeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &OneSixModEditDialog::versionCurrent); updateVersionControls(); } else @@ -93,10 +95,8 @@ OneSixModEditDialog::~OneSixModEditDialog() void OneSixModEditDialog::updateVersionControls() { - bool customVersion = m_inst->versionIsCustom(); ui->forgeBtn->setEnabled(true); ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); - ui->customEditorBtn->setEnabled(customVersion); ui->mainClassEdit->setText(m_version->mainClass); } @@ -104,23 +104,52 @@ void OneSixModEditDialog::disableVersionControls() { ui->forgeBtn->setEnabled(false); ui->liteloaderBtn->setEnabled(false); - ui->customEditorBtn->setEnabled(false); + ui->reloadLibrariesBtn->setEnabled(false); + ui->removeLibraryBtn->setEnabled(false); ui->mainClassEdit->setText(""); } -void OneSixModEditDialog::on_customEditorBtn_clicked() +void OneSixModEditDialog::on_userEditorBtn_clicked() { - if (QDir(m_inst->instanceRoot()).exists("custom.json")) + if (QDir(m_inst->instanceRoot()).exists("user.json")) + { + if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/user.json")) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to open user.json, check the settings")); + } + } +} + +void OneSixModEditDialog::on_reloadLibrariesBtn_clicked() +{ + m_inst->reloadVersion(this); +} + +void OneSixModEditDialog::on_removeLibraryBtn_clicked() +{ + if (ui->libraryTreeView->currentIndex().isValid()) { - if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json")) + if (!m_version->remove(ui->libraryTreeView->currentIndex().row())) { - QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings")); + QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); + } + else + { + m_inst->reloadVersion(this); } } } void OneSixModEditDialog::on_forgeBtn_clicked() { + if (QDir(m_inst->instanceRoot()).exists("custom.json")) + { + if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes) + { + return; + } + QDir(m_inst->instanceRoot()).remove("custom.json"); + } VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); vselect.setFilter(1, m_inst->currentVersionId()); if (vselect.exec() && vselect.selectedVersion()) @@ -167,6 +196,14 @@ void OneSixModEditDialog::on_forgeBtn_clicked() void OneSixModEditDialog::on_liteloaderBtn_clicked() { + if (QDir(m_inst->instanceRoot()).exists("custom.json")) + { + if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes) + { + return; + } + QDir(m_inst->instanceRoot()).remove("custom.json"); + } LiteLoaderInstaller liteloader; if (!liteloader.canApply(m_inst)) { @@ -304,3 +341,15 @@ void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previou Mod &m = m_mods->operator[](row); ui->frame->updateWithMod(m); } + +void OneSixModEditDialog::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->removeLibraryBtn->setDisabled(true); + } + else + { + ui->removeLibraryBtn->setEnabled(m_version->canRemove(current.row())); + } +} diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h index 161604c9..7e759f48 100644 --- a/gui/dialogs/OneSixModEditDialog.h +++ b/gui/dialogs/OneSixModEditDialog.h @@ -45,7 +45,9 @@ slots: void on_buttonBox_rejected(); void on_forgeBtn_clicked(); void on_liteloaderBtn_clicked(); - void on_customEditorBtn_clicked(); + void on_userEditorBtn_clicked(); + void on_reloadLibrariesBtn_clicked(); + void on_removeLibraryBtn_clicked(); void updateVersionControls(); void disableVersionControls(); @@ -64,4 +66,5 @@ private: public slots: void loaderCurrent(QModelIndex current, QModelIndex previous); + void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); }; diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui index 67642c23..d90c6d93 100644 --- a/gui/dialogs/OneSixModEditDialog.ui +++ b/gui/dialogs/OneSixModEditDialog.ui @@ -26,7 +26,7 @@ - 1 + 0 @@ -35,13 +35,6 @@ - - - - Tweakers: - - - @@ -51,7 +44,7 @@ Qt::ScrollBarAlwaysOff - false + true @@ -96,37 +89,22 @@ - - QFrame::Sunken - Qt::Horizontal - - - false - - - Add new libraries - + - &Add + Reload - - false - - - Remove selected libraries - - &Remove + Remove @@ -138,9 +116,9 @@ - + - Open custom.json + Open user.json diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 8d2445ed..c7160d6e 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -74,6 +74,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) QJsonObject installObj = installVal.toObject(); QString libraryName = installObj.value("path").toString(); internalPath = installObj.value("filePath").toString(); + m_forgeVersionString = installObj.value("version").toString().remove("Forge").trimmed(); // where do we put the library? decode the mojang path OneSixLibrary lib(libraryName); @@ -204,6 +205,11 @@ bool ForgeInstaller::add(OneSixInstance *to) } } + obj.insert("name", QString("Forge")); + obj.insert("id", id()); + obj.insert("version", m_forgeVersionString); + obj.insert("mcVersion", to->intendedVersionId()); + QFile file(filename(to->instanceRoot())); if (!file.open(QFile::WriteOnly)) { diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h index eb8ecd79..c5052092 100644 --- a/logic/ForgeInstaller.h +++ b/logic/ForgeInstaller.h @@ -37,5 +37,6 @@ private: QString internalPath; QString finalPath; QString realVersionId; + QString m_forgeVersionString; QString m_universal_url; }; diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index f658f834..48bbaeff 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -78,6 +78,10 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) } obj.insert("+libraries", libraries); + obj.insert("name", QString("LiteLoader")); + obj.insert("id", id()); + obj.insert("version", to->intendedVersionId()); + obj.insert("mcVersion", to->intendedVersionId()); QFile file(filename(to->instanceRoot())); if (!file.open(QFile::WriteOnly)) diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 19d5e112..88c40316 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -319,8 +319,9 @@ bool OneSixInstance::shouldUpdate() const bool OneSixInstance::versionIsCustom() { QDir patches(PathCombine(instanceRoot(), "patches/")); - return QFile::exists(PathCombine(instanceRoot(), "custom.json")) - || (patches.exists() && patches.count() >= 0); + return (patches.exists() && patches.count() >= 0) + || QFile::exists(PathCombine(instanceRoot(), "custom.json")) + || QFile::exists(PathCombine(instanceRoot(), "user.json")); } QString OneSixInstance::currentVersionId() const diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 1abd5114..0b6d1986 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -16,6 +16,7 @@ #include "OneSixVersion.h" #include +#include #include "OneSixVersionBuilder.h" @@ -47,6 +48,7 @@ void OneSixVersion::clear() mainClass.clear(); libraries.clear(); tweakers.clear(); + versionFiles.clear(); endResetModel(); } @@ -70,6 +72,24 @@ void OneSixVersion::dump() const qDebug().nospace() << "\n)"; } +bool OneSixVersion::canRemove(const int index) const +{ + if (index < versionFiles.size()) + { + return versionFiles.at(index).id != "org.multimc.version.json"; + } + return false; +} + +bool OneSixVersion::remove(const int index) +{ + if (canRemove(index)) + { + return QFile::remove(versionFiles.at(index).filename); + } + return false; +} + QList > OneSixVersion::getActiveNormalLibs() { QList > output; @@ -114,14 +134,39 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= tweakers.size()) + if (row < 0 || row >= versionFiles.size()) return QVariant(); if (role == Qt::DisplayRole) { - if (column == 0) + switch (column) + { + case 0: + return versionFiles.at(row).name; + case 1: + return versionFiles.at(row).version; + default: + return QVariant(); + } + } + return QVariant(); +} + +QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) { - return tweakers.at(row); + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } } } return QVariant(); @@ -136,12 +181,12 @@ Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const int OneSixVersion::rowCount(const QModelIndex &parent) const { - return tweakers.size(); + return versionFiles.size(); } int OneSixVersion::columnCount(const QModelIndex &parent) const { - return 1; + return 2; } QDebug operator<<(QDebug &dbg, const OneSixVersion *version) diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 98a4b418..d7a6684d 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -32,6 +32,7 @@ public: explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; @@ -41,6 +42,12 @@ public: void dump() const; + bool canRemove(const int index) const; + +public +slots: + bool remove(const int index); + public: QList> getActiveNormalLibs(); QList> getActiveNativeLibs(); @@ -109,6 +116,16 @@ public: */ // QList rules; + struct VersionFile + { + QString name; + QString id; + QString version; + QString mcVersion; + QString filename; + }; + QList versionFiles; + private: OneSixInstance *m_instance; }; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index a8e7253b..08506d4b 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -34,6 +34,14 @@ struct VersionFile { int order; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap requirements; QString id; QString mainClass; QString overwriteMinecraftArguments; @@ -216,6 +224,12 @@ struct VersionFile } } + out.name = root.value("name").toString(); + out.fileId = root.value("id").toString(); + out.version = root.value("version").toString(); + out.mcVersion = root.value("mcVersion").toString(); + out.filename = filename; + auto readString = [root, filename](const QString &key, QString &variable) { if (root.contains(key)) @@ -661,6 +675,14 @@ struct VersionFile } } + OneSixVersion::VersionFile versionFile; + versionFile.name = name; + versionFile.id = fileId; + versionFile.version = this->version; + versionFile.mcVersion = mcVersion; + versionFile.filename = filename; + version->versionFiles.append(versionFile); + isError = false; } }; @@ -727,6 +749,10 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) { return false; } + file.name = "version.json"; + file.fileId = "org.multimc.version.json"; + file.version = m_instance->intendedVersionId(); + file.mcVersion = m_instance->intendedVersionId(); bool isError = false; file.applyTo(m_version, isError); if (isError) @@ -773,17 +799,21 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) } } - // instance.json + // user.json if (!excludeCustom) { - if (QFile::exists(root.absoluteFilePath("instance.json"))) + if (QFile::exists(root.absoluteFilePath("user.json"))) { - QLOG_INFO() << "Reading instance.json"; + QLOG_INFO() << "Reading user.json"; VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("instance.json")), false, &file)) + if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file)) { return false; } + file.name = "user.json"; + file.fileId = "org.multimc.user.json"; + file.version = QString(); + file.mcVersion = QString(); bool isError = false; file.applyTo(m_version, isError); if (isError) @@ -792,7 +822,7 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) m_widgetParent, QObject::tr("Error"), QObject::tr( "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("instance.json"))); + .arg(root.absoluteFilePath("user.json"))); return false; } } -- cgit From 8637cce4333aaf56a231d5fab866b0e770436783 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 1 Feb 2014 16:26:38 +0100 Subject: Fix a bug --- gui/dialogs/OneSixModEditDialog.cpp | 15 ++++++++++++--- logic/ForgeInstaller.cpp | 2 +- logic/LiteLoaderInstaller.cpp | 2 +- logic/OneSixVersionBuilder.cpp | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index a34409f5..ebd685e8 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -111,12 +111,21 @@ void OneSixModEditDialog::disableVersionControls() void OneSixModEditDialog::on_userEditorBtn_clicked() { - if (QDir(m_inst->instanceRoot()).exists("user.json")) + QDir root(m_inst->instanceRoot()); + if (!root.exists("user.json")) { - if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/user.json")) + QFile file(root.absoluteFilePath("user.json")); + if (!file.open(QFile::WriteOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Unable to open user.json, check the settings")); + QMessageBox::critical(this, tr("Error"), tr("Couldn't write a skeletion user.json file: %1").arg(file.errorString())); + return; } + file.write("{\n}"); + file.close(); + } + if (!MMC->openJsonEditor(root.absoluteFilePath("user.json"))) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to open user.json, check the settings")); } } diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index c7160d6e..47c42694 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -206,7 +206,7 @@ bool ForgeInstaller::add(OneSixInstance *to) } obj.insert("name", QString("Forge")); - obj.insert("id", id()); + obj.insert("fileId", id()); obj.insert("version", m_forgeVersionString); obj.insert("mcVersion", to->intendedVersionId()); diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 48bbaeff..60a43d49 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -79,7 +79,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) obj.insert("+libraries", libraries); obj.insert("name", QString("LiteLoader")); - obj.insert("id", id()); + obj.insert("fileId", id()); obj.insert("version", to->intendedVersionId()); obj.insert("mcVersion", to->intendedVersionId()); diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 08506d4b..a541766b 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -225,7 +225,7 @@ struct VersionFile } out.name = root.value("name").toString(); - out.fileId = root.value("id").toString(); + out.fileId = root.value("fileId").toString(); out.version = root.value("version").toString(); out.mcVersion = root.value("mcVersion").toString(); out.filename = filename; -- cgit From 866d7029afa10293a57d71aecbe3629399c95d06 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 1 Feb 2014 19:42:47 +0100 Subject: Fix some bugs that got uncovered while trying to get liteloader 1.7 to work --- logic/OneSixVersionBuilder.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index a541766b..9832d4a0 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -483,8 +483,15 @@ struct VersionFile static std::shared_ptr createLibrary(const Library &lib) { std::shared_ptr out(new OneSixLibrary(lib.name)); - out->setBaseUrl(lib.url); + if (!lib.url.isEmpty()) + { + out->setBaseUrl(lib.url); + } out->setHint(lib.hint); + if (!lib.absoluteUrl.isEmpty()) + { + out->setAbsoluteUrl(lib.absoluteUrl); + } out->setAbsoluteUrl(lib.absoluteUrl); out->extract_excludes = lib.excludes; for (auto native : lib.natives) @@ -725,6 +732,10 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) { return false; } + file.name = "custom.json"; + file.filename = "custom.json"; + file.fileId = "org.multimc.custom.json"; + file.version = QString(); bool isError = false; file.applyTo(m_version, isError); if (isError) -- cgit From 790402bdce96a4ce67b32d228aa251fc4d184f5e Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 1 Feb 2014 22:32:48 +0100 Subject: Disable anything related to user.json for now. Will be re-enabled once we have a gui for it. --- gui/dialogs/OneSixModEditDialog.cpp | 20 -------------------- gui/dialogs/OneSixModEditDialog.h | 1 - gui/dialogs/OneSixModEditDialog.ui | 16 +--------------- logic/OneSixVersionBuilder.cpp | 4 +++- 4 files changed, 4 insertions(+), 37 deletions(-) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index 1742ff80..41ac5d8d 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -109,26 +109,6 @@ void OneSixModEditDialog::disableVersionControls() ui->mainClassEdit->setText(""); } -void OneSixModEditDialog::on_userEditorBtn_clicked() -{ - QDir root(m_inst->instanceRoot()); - if (!root.exists("user.json")) - { - QFile file(root.absoluteFilePath("user.json")); - if (!file.open(QFile::WriteOnly)) - { - QMessageBox::critical(this, tr("Error"), tr("Couldn't write a skeletion user.json file: %1").arg(file.errorString())); - return; - } - file.write("{\n}"); - file.close(); - } - if (!MMC->openJsonEditor(root.absoluteFilePath("user.json"))) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to open user.json, check the settings")); - } -} - void OneSixModEditDialog::on_reloadLibrariesBtn_clicked() { m_inst->reloadVersion(this); diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h index 7e759f48..a7def967 100644 --- a/gui/dialogs/OneSixModEditDialog.h +++ b/gui/dialogs/OneSixModEditDialog.h @@ -45,7 +45,6 @@ slots: void on_buttonBox_rejected(); void on_forgeBtn_clicked(); void on_liteloaderBtn_clicked(); - void on_userEditorBtn_clicked(); void on_reloadLibrariesBtn_clicked(); void on_removeLibraryBtn_clicked(); void updateVersionControls(); diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui index d90c6d93..b97fa323 100644 --- a/gui/dialogs/OneSixModEditDialog.ui +++ b/gui/dialogs/OneSixModEditDialog.ui @@ -44,7 +44,7 @@ Qt::ScrollBarAlwaysOff - true + false @@ -108,20 +108,6 @@ - - - - Qt::Horizontal - - - - - - - Open user.json - - - diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 9832d4a0..4d6fb05a 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -750,7 +750,7 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) } else { - // version.json -> patches/*.json -> instance.json + // version.json -> patches/*.json -> user.json // version.json { @@ -810,6 +810,7 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) } } +#if 0 // user.json if (!excludeCustom) { @@ -838,6 +839,7 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) } } } +#endif } // some final touches -- cgit From b2bf50a6d75d32ac483bb53d5c5948b353cd2d16 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 10:26:38 +0100 Subject: Small tweaks --- GroupView.cpp | 41 +++++++++-------------------------------- GroupView.h | 36 +++++++++++++++++++++++++++++++++++- InstanceDelegate.cpp | 2 +- main.h | 19 +++++++++++-------- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index 94682df8..50d19f52 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -26,17 +26,17 @@ template bool listsIntersect(const QList &l1, const QList t2) } GroupView::GroupView(QWidget *parent) - : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), + : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { - setViewMode(IconMode); + // setViewMode(IconMode); // setMovement(Snap); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setWordWrap(true); + // setWordWrap(true); // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); - setSpacing(10); + // setSpacing(10); } GroupView::~GroupView() @@ -48,49 +48,26 @@ GroupView::~GroupView() void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::dataChanged(topLeft, bottomRight, roles); - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); - update(); } + viewport()->update(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::rowsInserted(parent, start, end); - updateGeometries(); - update(); + viewport()->update(); } void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::rowsAboutToBeRemoved(parent, start, end); - updateGeometries(); - update(); + viewport()->update(); } void GroupView::updateGeometries() { - QListView::updateGeometries(); - int previousScroll = verticalScrollBar()->value(); QMap cats; @@ -145,7 +122,7 @@ void GroupView::updateGeometries() verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); - update(); + viewport()->update(); } bool GroupView::isIndexHidden(const QModelIndex &index) const @@ -520,7 +497,7 @@ void GroupView::paintEvent(QPaintEvent *event) void GroupView::resizeEvent(QResizeEvent *event) { - QListView::resizeEvent(event); + // QListView::resizeEvent(event); // if (m_categoryEditor) // { diff --git a/GroupView.h b/GroupView.h index 6d55a462..bf911794 100644 --- a/GroupView.h +++ b/GroupView.h @@ -15,7 +15,7 @@ struct CategorizedViewRoles struct Group; -class GroupView : public QListView +class GroupView : public QAbstractItemView { Q_OBJECT @@ -28,6 +28,40 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; + /* + * BS + */ + + virtual int horizontalOffset() const override + { + return 0; + } + + virtual int verticalOffset() const override + { + return 0; + } + + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override + { + return; + } + + virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) + override + { + return QModelIndex(); + } + + virtual QRegion visualRegionForSelection(const QItemSelection &) const override + { + return QRegion(); + } + + /* + * End of BS + */ + protected slots: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 056db99d..944cfd76 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -103,7 +103,7 @@ void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option color.setAlphaF(0.70f); painter->setBrush(color); painter->setPen(QPen(QBrush(), 0)); - painter->drawPie(option.rect, 90 * 16, -percent * 360 * 60); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); painter->restore(); } diff --git a/main.h b/main.h index 47377f7a..6883e98e 100644 --- a/main.h +++ b/main.h @@ -30,20 +30,23 @@ public slots: void timeout() { + QList toRemove; for (auto item : m_items) { + int maximum = item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(); int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); - value += qrand() % 3; - if (value >= item->data(CategorizedViewRoles::ProgressMaximumRole).toInt()) - { - item->setData(item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(), - CategorizedViewRoles::ProgressValueRole); - } - else + int newvalue = std::min(value + 3, maximum); + item->setData(newvalue, CategorizedViewRoles::ProgressValueRole); + + if(newvalue >= maximum) { - item->setData(value, CategorizedViewRoles::ProgressValueRole); + toRemove.append(item); } } + for(auto remove : toRemove) + { + m_items.removeAll(remove); + } } private: -- cgit From ece826bdbc5ca525e253cafcfef3d93e492949f5 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sun, 2 Feb 2014 14:05:07 +0100 Subject: Add a MMC-depend field (soft/hard) for version checking --- depends/util/CMakeLists.txt | 3 + depends/util/include/modutils.h | 32 ++++++ depends/util/src/modutils.cpp | 216 ++++++++++++++++++++++++++++++++++++++++ logic/ForgeInstaller.cpp | 6 +- logic/LiteLoaderInstaller.cpp | 3 +- logic/OneSixLibrary.cpp | 2 +- logic/OneSixLibrary.h | 11 +- logic/OneSixVersionBuilder.cpp | 110 ++++++++++++++------ 8 files changed, 351 insertions(+), 32 deletions(-) create mode 100644 depends/util/include/modutils.h create mode 100644 depends/util/src/modutils.cpp diff --git a/depends/util/CMakeLists.txt b/depends/util/CMakeLists.txt index db7d70e6..7f6573bd 100644 --- a/depends/util/CMakeLists.txt +++ b/depends/util/CMakeLists.txt @@ -35,6 +35,9 @@ src/userutils.cpp include/cmdutils.h src/cmdutils.cpp + +include/modutils.h +src/modutils.cpp ) # Set the include dir path. diff --git a/depends/util/include/modutils.h b/depends/util/include/modutils.h new file mode 100644 index 00000000..e04db66f --- /dev/null +++ b/depends/util/include/modutils.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "libutil_config.h" + +class QUrl; + +namespace Util +{ +struct Version +{ + Version(const QString &str); + + bool operator<(const Version &other) const; + bool operator<=(const Version &other) const; + bool operator>(const Version &other) const; + bool operator==(const Version &other) const; + bool operator!=(const Version &other) const; + + QString toString() const + { + return m_string; + } + +private: + QString m_string; +}; + +LIBUTIL_EXPORT QUrl expandQMURL(const QString &in); +LIBUTIL_EXPORT bool versionIsInInterval(const QString &version, const QString &interval); +} + diff --git a/depends/util/src/modutils.cpp b/depends/util/src/modutils.cpp new file mode 100644 index 00000000..44a04b72 --- /dev/null +++ b/depends/util/src/modutils.cpp @@ -0,0 +1,216 @@ +#include "include/modutils.h" + +#include +#include +#include +#include + +Util::Version::Version(const QString &str) : m_string(str) +{ +} + +bool Util::Version::operator<(const Version &other) const +{ + QStringList parts1 = m_string.split('.'); + QStringList parts2 = other.m_string.split('.'); + + while (!parts1.isEmpty() && !parts2.isEmpty()) + { + QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst(); + QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst(); + bool ok1 = false; + bool ok2 = false; + int int1 = part1.toInt(&ok1); + int int2 = part2.toInt(&ok2); + if (ok1 && ok2) + { + if (int1 == int2) + { + continue; + } + else + { + return int1 < int2; + } + } + else + { + if (part1 == part2) + { + continue; + } + else + { + return part1 < part2; + } + } + } + + return false; +} +bool Util::Version::operator<=(const Util::Version &other) const +{ + return *this < other || *this == other; +} +bool Util::Version::operator>(const Version &other) const +{ + QStringList parts1 = m_string.split('.'); + QStringList parts2 = other.m_string.split('.'); + + while (!parts1.isEmpty() && !parts2.isEmpty()) + { + QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst(); + QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst(); + bool ok1 = false; + bool ok2 = false; + int int1 = part1.toInt(&ok1); + int int2 = part2.toInt(&ok2); + if (ok1 && ok2) + { + if (int1 == int2) + { + continue; + } + else + { + return int1 > int2; + } + } + else + { + if (part1 == part2) + { + continue; + } + else + { + return part1 > part2; + } + } + } + + return false; +} +bool Util::Version::operator==(const Version &other) const +{ + QStringList parts1 = m_string.split('.'); + QStringList parts2 = other.m_string.split('.'); + + while (!parts1.isEmpty() && !parts2.isEmpty()) + { + QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst(); + QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst(); + bool ok1 = false; + bool ok2 = false; + int int1 = part1.toInt(&ok1); + int int2 = part2.toInt(&ok2); + if (ok1 && ok2) + { + if (int1 == int2) + { + continue; + } + else + { + return false; + } + } + else + { + if (part1 == part2) + { + continue; + } + else + { + return false; + } + } + } + + return true; +} +bool Util::Version::operator!=(const Version &other) const +{ + return !operator==(other); +} + +QUrl Util::expandQMURL(const QString &in) +{ + QUrl inUrl(in); + if (inUrl.scheme() == "github") + { + // needed because QUrl makes the host all lower cases + const QString repo = in.mid(in.indexOf(inUrl.host(), 0, Qt::CaseInsensitive), inUrl.host().size()); + QUrl out; + out.setScheme("https"); + out.setHost("raw.github.com"); + out.setPath(QString("/%1/%2/%3%4") + .arg(inUrl.userInfo(), repo, + inUrl.fragment().isEmpty() ? "master" : inUrl.fragment(), inUrl.path())); + return out; + } + else if (inUrl.scheme() == "mcf") + { + QUrl out; + out.setScheme("http"); + out.setHost("www.minecraftforum.net"); + out.setPath(QString("/topic/%1-").arg(inUrl.path())); + return out; + } + else + { + return in; + } +} + +bool Util::versionIsInInterval(const QString &version, const QString &interval) +{ + if (interval.isEmpty() || version == interval) + { + return true; + } + + // Interval notation is used + QRegularExpression exp( + "(?[\\[\\]\\(\\)])(?.*?)(,(?.*?))?(?[\\[\\]\\(\\)])"); + QRegularExpressionMatch match = exp.match(interval); + if (match.hasMatch()) + { + const QChar start = match.captured("start").at(0); + const QChar end = match.captured("end").at(0); + const QString bottom = match.captured("bottom"); + const QString top = match.captured("top"); + + // check if in range (bottom) + if (!bottom.isEmpty()) + { + if ((start == '[') && !(version >= bottom)) + { + return false; + } + else if ((start == '(') && !(version > bottom)) + { + return false; + } + } + + // check if in range (top) + if (!top.isEmpty()) + { + if ((end == ']') && !(version <= top)) + { + return false; + } + else if ((end == ')') && !(version < top)) + { + return false; + } + } + + return true; + } + + return false; +} + diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 47c42694..6b498d70 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -171,7 +171,11 @@ bool ForgeInstaller::add(OneSixInstance *to) if (!found) { // add lib - libObj.insert("insert", QString("prepend-if-not-exists")); + libObj.insert("insert", QString("prepend")); + if (lib->name() == "minecraftforge") + { + libObj.insert("MMC-depend", QString("hard")); + } sliding_insert_window++; } librariesPlus.prepend(libObj); diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 60a43d49..c363cad6 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -63,7 +63,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); launchwrapperLib.finalize(); QJsonObject lwLibObj = launchwrapperLib.toJson(); - lwLibObj.insert("insert", QString("prepend-if-not-exists")); + lwLibObj.insert("insert", QString("prepend")); libraries.append(lwLibObj); } @@ -74,6 +74,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) liteloaderLib.finalize(); QJsonObject llLibObj = liteloaderLib.toJson(); llLibObj.insert("insert", QString("prepend")); + llLibObj.insert("MMC-depend", QString("hard")); libraries.append(llLibObj); } diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp index 2b1f0600..c78679d1 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/OneSixLibrary.cpp @@ -46,7 +46,7 @@ void OneSixLibrary::finalize() } m_decentname = parts[1]; - m_decentversion = parts[2]; + m_decentversion = minVersion = parts[2]; m_storage_path = relative; m_download_url = m_base_url + relative; diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h index 5384015a..371ca6f4 100644 --- a/logic/OneSixLibrary.h +++ b/logic/OneSixLibrary.h @@ -60,12 +60,21 @@ private: public: QStringList extract_excludes; + QString minVersion; + + enum DependType + { + Soft, + Hard + }; + DependType dependType; public: /// Constructor - OneSixLibrary(const QString &name) + OneSixLibrary(const QString &name, const DependType type = Soft) { m_name = name; + dependType = type; } /// Returns the raw name field diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 4d6fb05a..55c7d48e 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -29,6 +29,7 @@ #include "OneSixVersion.h" #include "OneSixInstance.h" #include "OneSixRule.h" +#include "modutils.h" #include "logger/QsLog.h" struct VersionFile @@ -78,12 +79,16 @@ struct VersionFile Apply, Append, Prepend, - AppendIfNotExists, - PrependIfNotExists, Replace }; - InsertType insertType; + InsertType insertType = Append; QString insertData; + enum DependType + { + Soft, + Hard + }; + DependType dependType = Soft; }; bool shouldOverwriteLibs = false; QList overwriteLibs; @@ -414,21 +419,13 @@ struct VersionFile { lib.insertType = Library::Apply; } - else if (insertString == "append") - { - lib.insertType = Library::Append; - } else if (insertString == "prepend") { lib.insertType = Library::Prepend; } - else if (insertString == "prepend-if-not-exists") - { - lib.insertType = Library::PrependIfNotExists; - } - else if (insertString == "append-if-not-exists") + else if (insertString == "append") { - lib.insertType = Library::PrependIfNotExists; + lib.insertType = Library::Prepend; } else if (insertString == "replace") { @@ -440,6 +437,24 @@ struct VersionFile << "contains an invalid insert type"; return out; } + if (libObj.contains("MMC-depend") && libObj.value("MMC-depend").isString()) + { + const QString dependString = libObj.value("MMC-depend").toString(); + if (dependString == "hard") + { + lib.dependType = Library::Hard; + } + else if (dependString == "soft") + { + lib.dependType = Library::Soft; + } + else + { + QLOG_ERROR() << "A '+' library in" << filename + << "contains an invalid depend type"; + return out; + } + } out.addLibs.append(lib); } } @@ -629,28 +644,67 @@ struct VersionFile break; } case Library::Append: - version->libraries.append(createLibrary(lib)); - break; case Library::Prepend: - version->libraries.prepend(createLibrary(lib)); - break; - case Library::AppendIfNotExists: { - int index = findLibrary(version->libraries, lib.name); + const int startOfVersion = lib.name.lastIndexOf(':') + 1; + const int index = findLibrary(version->libraries, QString(lib.name).replace(startOfVersion, INT_MAX, '*')); if (index < 0) { - version->libraries.append(createLibrary(lib)); + if (lib.insertType == Library::Append) + { + version->libraries.append(createLibrary(lib)); + } + else + { + version->libraries.prepend(createLibrary(lib)); + } } - break; - } - case Library::PrependIfNotExists: - { - - int index = findLibrary(version->libraries, lib.name); - if (index < 0) + else { - version->libraries.prepend(createLibrary(lib)); + auto otherLib = version->libraries.at(index); + const Util::Version ourVersion = lib.name.mid(startOfVersion, INT_MAX); + const Util::Version otherVersion = otherLib->version(); + // if the existing version is a hard dependency we can either use it or fail, but we can't change it + if (otherLib->dependType == OneSixLibrary::Hard) + { + // we need a higher version, or we're hard to and the versions aren't equal + if (ourVersion > otherVersion || (lib.dependType == Library::Hard && ourVersion != otherVersion)) + { + QLOG_ERROR() << "Error resolving library dependencies between" + << otherLib->rawName() << "and" << lib.name << "in" + << filename; + return; + } + else + { + // the library is already existing, so we don't have to do anything + } + } + else if (otherLib->dependType == OneSixLibrary::Soft) + { + // if we are higher it means we should update + if (ourVersion > otherVersion) + { + auto library = createLibrary(lib); + if (Util::Version(otherLib->minVersion) < ourVersion) + { + library->minVersion = ourVersion.toString(); + } + version->libraries.replace(index, library); + } + else + { + // our version is smaller than the existing version, but we require it: fail + if (lib.dependType == Library::Hard) + { + QLOG_ERROR() << "Error resolving library dependencies between" + << otherLib->rawName() << "and" << lib.name << "in" + << filename; + return; + } + } + } } break; } -- cgit From cdd35910c3aeb4048e2e982db0b8227f76574704 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sun, 2 Feb 2014 14:17:44 +0100 Subject: Fix installing forge after liteloader and then removing liteloader. Also formatting. --- logic/ForgeInstaller.cpp | 6 +- logic/OneSixInstance.cpp | 10 +-- logic/OneSixInstance.h | 4 +- logic/OneSixInstance_p.h | 2 +- logic/OneSixVersion.cpp | 4 +- logic/OneSixVersion.h | 2 +- logic/OneSixVersionBuilder.cpp | 146 ++++++++++++++++++++++------------------- logic/OneSixVersionBuilder.h | 4 +- 8 files changed, 96 insertions(+), 82 deletions(-) diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 6b498d70..3e18d17f 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -151,7 +151,7 @@ bool ForgeInstaller::add(OneSixInstance *to) bool found = false; bool equals = false; // find an entry that matches this one - for (auto tolib : to->getNonCustomVersion()->libraries) + for (auto tolib : to->getVanillaVersion()->libraries) { if (tolib->name() != libName) continue; @@ -194,7 +194,7 @@ bool ForgeInstaller::add(OneSixInstance *to) match = expression.match(args); } } - if (!args.isEmpty() && args != to->getNonCustomVersion()->minecraftArguments) + if (!args.isEmpty() && args != to->getVanillaVersion()->minecraftArguments) { obj.insert("minecraftArguments", args); } @@ -203,7 +203,7 @@ bool ForgeInstaller::add(OneSixInstance *to) obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); } if (!m_forge_version->processArguments.isEmpty() && - m_forge_version->processArguments != to->getNonCustomVersion()->processArguments) + m_forge_version->processArguments != to->getVanillaVersion()->processArguments) { obj.insert("processArguments", m_forge_version->processArguments); } diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 8ecbe7a0..ae172f21 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -35,7 +35,7 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, d->m_settings->registerSetting("IntendedVersion", ""); d->m_settings->registerSetting("ShouldUpdate", false); d->version.reset(new OneSixVersion(this, this)); - d->nonCustomVersion.reset(new OneSixVersion(this, this)); + d->vanillaVersion.reset(new OneSixVersion(this, this)); if (QDir(instanceRoot()).exists("version.json")) { reloadVersion(); @@ -321,7 +321,7 @@ bool OneSixInstance::reloadVersion(QWidget *widgetParent) bool ret = d->version->reload(widgetParent); if (ret) { - ret = d->nonCustomVersion->reload(widgetParent, true); + ret = d->vanillaVersion->reload(widgetParent, true); } emit versionReloaded(); return ret; @@ -331,7 +331,7 @@ void OneSixInstance::clearVersion() { I_D(OneSixInstance); d->version->clear(); - d->nonCustomVersion->clear(); + d->vanillaVersion->clear(); emit versionReloaded(); } @@ -341,10 +341,10 @@ std::shared_ptr OneSixInstance::getFullVersion() const return d->version; } -std::shared_ptr OneSixInstance::getNonCustomVersion() const +std::shared_ptr OneSixInstance::getVanillaVersion() const { I_D(const OneSixInstance); - return d->nonCustomVersion; + return d->vanillaVersion; } QString OneSixInstance::defaultBaseJar() const diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 5aa12e44..ae95eab1 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -57,8 +57,8 @@ public: void clearVersion(); /// get the current full version info std::shared_ptr getFullVersion() const; - /// gets the current version info, excluding custom.json - std::shared_ptr getNonCustomVersion() const; + /// gets the current version info, but only for version.json + std::shared_ptr getVanillaVersion() const; /// is the current version original, or custom? virtual bool versionIsCustom() override; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index 6dc74e50..0cc46f33 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -22,7 +22,7 @@ struct OneSixInstancePrivate : public BaseInstancePrivate { std::shared_ptr version; - std::shared_ptr nonCustomVersion; + std::shared_ptr vanillaVersion; std::shared_ptr loader_mod_list; std::shared_ptr resource_pack_list; }; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 0b6d1986..e19683d8 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -26,10 +26,10 @@ OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) clear(); } -bool OneSixVersion::reload(QWidget *widgetParent, const bool excludeCustom) +bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla) { beginResetModel(); - bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, excludeCustom); + bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla); endResetModel(); return ret; } diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index d7a6684d..516f153c 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -37,7 +37,7 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - bool reload(QWidget *widgetParent, const bool excludeCustom = false); + bool reload(QWidget *widgetParent, const bool onlyVanilla = false); void clear(); void dump() const; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 55c7d48e..03555070 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -280,7 +280,8 @@ struct VersionFile QJsonValue tweakersVal = root.value("tweakers"); if (!tweakersVal.isArray()) { - QLOG_ERROR() << filename << "contains a 'tweakers' field, but it's not an array"; + QLOG_ERROR() << filename + << "contains a 'tweakers' field, but it's not an array"; return out; } out.shouldOverwriteTweakers = true; @@ -289,7 +290,8 @@ struct VersionFile { if (!tweakerVal.isString()) { - QLOG_ERROR() << filename << "contains a 'tweakers' field entry that's not a string"; + QLOG_ERROR() << filename + << "contains a 'tweakers' field entry that's not a string"; return out; } out.overwriteTweakers.append(tweakerVal.toString()); @@ -300,7 +302,8 @@ struct VersionFile QJsonValue tweakersVal = root.value("+tweakers"); if (!tweakersVal.isArray()) { - QLOG_ERROR() << filename << "contains a '+tweakers' field, but it's not an array"; + QLOG_ERROR() << filename + << "contains a '+tweakers' field, but it's not an array"; return out; } QJsonArray tweakers = root.value("+tweakers").toArray(); @@ -308,7 +311,8 @@ struct VersionFile { if (!tweakerVal.isString()) { - QLOG_ERROR() << filename << "contains a '+tweakers' field entry that's not a string"; + QLOG_ERROR() << filename + << "contains a '+tweakers' field entry that's not a string"; return out; } out.addTweakers.append(tweakerVal.toString()); @@ -319,7 +323,8 @@ struct VersionFile QJsonValue tweakersVal = root.value("-tweakers"); if (!tweakersVal.isArray()) { - QLOG_ERROR() << filename << "contains a '-tweakers' field, but it's not an array"; + QLOG_ERROR() << filename + << "contains a '-tweakers' field, but it's not an array"; return out; } out.shouldOverwriteTweakers = true; @@ -328,7 +333,8 @@ struct VersionFile { if (!tweakerVal.isString()) { - QLOG_ERROR() << filename << "contains a '-tweakers' field entry that's not a string"; + QLOG_ERROR() << filename + << "contains a '-tweakers' field entry that's not a string"; return out; } out.removeTweakers.append(tweakerVal.toString()); @@ -648,7 +654,9 @@ struct VersionFile { const int startOfVersion = lib.name.lastIndexOf(':') + 1; - const int index = findLibrary(version->libraries, QString(lib.name).replace(startOfVersion, INT_MAX, '*')); + const int index = + findLibrary(version->libraries, + QString(lib.name).replace(startOfVersion, INT_MAX, '*')); if (index < 0) { if (lib.insertType == Library::Append) @@ -665,11 +673,14 @@ struct VersionFile auto otherLib = version->libraries.at(index); const Util::Version ourVersion = lib.name.mid(startOfVersion, INT_MAX); const Util::Version otherVersion = otherLib->version(); - // if the existing version is a hard dependency we can either use it or fail, but we can't change it + // if the existing version is a hard dependency we can either use it or + // fail, but we can't change it if (otherLib->dependType == OneSixLibrary::Hard) { - // we need a higher version, or we're hard to and the versions aren't equal - if (ourVersion > otherVersion || (lib.dependType == Library::Hard && ourVersion != otherVersion)) + // we need a higher version, or we're hard to and the versions aren't + // equal + if (ourVersion > otherVersion || + (lib.dependType == Library::Hard && ourVersion != otherVersion)) { QLOG_ERROR() << "Error resolving library dependencies between" << otherLib->rawName() << "and" << lib.name << "in" @@ -695,7 +706,8 @@ struct VersionFile } else { - // our version is smaller than the existing version, but we require it: fail + // our version is smaller than the existing version, but we require + // it: fail if (lib.dependType == Library::Hard) { QLOG_ERROR() << "Error resolving library dependencies between" @@ -753,13 +765,13 @@ OneSixVersionBuilder::OneSixVersionBuilder() } bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, - QWidget *widgetParent, const bool excludeCustom) + QWidget *widgetParent, const bool onlyVanilla) { OneSixVersionBuilder builder; builder.m_version = version; builder.m_instance = instance; builder.m_widgetParent = widgetParent; - return builder.build(excludeCustom); + return builder.build(onlyVanilla); } bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) @@ -771,7 +783,7 @@ bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) return builder.read(obj); } -bool OneSixVersionBuilder::build(const bool excludeCustom) +bool OneSixVersionBuilder::build(const bool onlyVanilla) { m_version->clear(); @@ -823,77 +835,79 @@ bool OneSixVersionBuilder::build(const bool excludeCustom) if (isError) { QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("version.json"))); + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("version.json"))); return false; } } - // patches/ + if (!onlyVanilla) { - // load all, put into map for ordering, apply in the right order - QMap> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + // patches/ { - QLOG_INFO() << "Reading" << info.fileName(); - VersionFile file; - if (!read(info, true, &file)) + // load all, put into map for ordering, apply in the right order + + QMap> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { - return false; + QLOG_INFO() << "Reading" << info.fileName(); + VersionFile file; + if (!read(info, true, &file)) + { + return false; + } + files.insert(file.order, qMakePair(info.fileName(), file)); } - files.insert(file.order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - QLOG_DEBUG() << "Applying file with order" << order; - auto filePair = files[order]; - bool isError = false; - filePair.second.applyTo(m_version, isError); - if (isError) + for (auto order : files.keys()) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(filePair.first)); - return false; + QLOG_DEBUG() << "Applying file with order" << order; + auto filePair = files[order]; + bool isError = false; + filePair.second.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while applying %1. Please check MultiMC-0.log " + "for more info.").arg(filePair.first)); + return false; + } } } - } #if 0 - // user.json - if (!excludeCustom) - { - if (QFile::exists(root.absoluteFilePath("user.json"))) + // user.json { - QLOG_INFO() << "Reading user.json"; - VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file)) - { - return false; - } - file.name = "user.json"; - file.fileId = "org.multimc.user.json"; - file.version = QString(); - file.mcVersion = QString(); - bool isError = false; - file.applyTo(m_version, isError); - if (isError) + if (QFile::exists(root.absoluteFilePath("user.json"))) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("user.json"))); - return false; + QLOG_INFO() << "Reading user.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file)) + { + return false; + } + file.name = "user.json"; + file.fileId = "org.multimc.user.json"; + file.version = QString(); + file.mcVersion = QString(); + bool isError = false; + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("user.json"))); + return false; + } } } - } #endif + } } // some final touches diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h index ac8c13bf..f0d69bf6 100644 --- a/logic/OneSixVersionBuilder.h +++ b/logic/OneSixVersionBuilder.h @@ -28,7 +28,7 @@ class OneSixVersionBuilder { OneSixVersionBuilder(); public: - static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool excludeCustom); + static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla); static bool read(OneSixVersion *version, const QJsonObject &obj); private: @@ -36,7 +36,7 @@ private: OneSixInstance *m_instance; QWidget *m_widgetParent; - bool build(const bool excludeCustom); + bool build(const bool onlyVanilla); bool read(const QJsonObject &obj); bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out); -- cgit From eb0ed082d877156f543324736cbf4ab85a9ec3f8 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 14:27:43 +0100 Subject: Fix group mouse interaction issues --- Group.cpp | 39 ++++++++++++++++++++++++++++++++---- Group.h | 16 +++++++++++++-- GroupView.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------------------ GroupView.h | 23 +++++++++++++-------- 4 files changed, 109 insertions(+), 33 deletions(-) diff --git a/Group.cpp b/Group.cpp index a62f592b..c92132ca 100644 --- a/Group.cpp +++ b/Group.cpp @@ -9,9 +9,9 @@ Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) { } + Group::Group(const Group *other) - : view(other->view), text(other->text), collapsed(other->collapsed), - iconRect(other->iconRect), textRect(other->textRect) + : view(other->view), text(other->text), collapsed(other->collapsed) { } @@ -27,6 +27,37 @@ void Group::update() } } +Group::HitResults Group::pointIntersect(const QPoint &pos) const +{ + Group::HitResults results = Group::NoHit; + int y_start = top(); + int body_start = y_start + headerHeight(); + int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? + int y = pos.y(); + // int x = pos.x(); + if(y < y_start) + { + results = Group::NoHit; + } + else if(y < body_start) + { + results = Group::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if(iconRect.contains(pos)) + { + results |= Group::CheckboxHit; + } + } + else if (y < body_end) + { + results |= Group::BodyHit; + } + return results; +} + void Group::drawHeader(QPainter *painter, const int y) { painter->save(); @@ -35,7 +66,7 @@ void Group::drawHeader(QPainter *painter, const int y) int collapseSize = height; // the icon - iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); painter->setPen(QPen(Qt::black, 1)); painter->drawRect(iconRect); static const int margin = 2; @@ -50,7 +81,7 @@ void Group::drawHeader(QPainter *painter, const int y) // the text int textWidth = painter->fontMetrics().width(text); - textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); painter->setBrush(view->viewOptions().palette.text()); view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->viewport()->palette(), true, text); diff --git a/Group.h b/Group.h index 6a8fadeb..51e0470d 100644 --- a/Group.h +++ b/Group.h @@ -15,8 +15,6 @@ struct Group GroupView *view; QString text; bool collapsed; - QRect iconRect; - QRect textRect; QVector rowHeights; int firstRow; @@ -29,8 +27,22 @@ struct Group int numRows() const; int top() const; + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) + + HitResults pointIntersect (const QPoint &pos) const; + QList items() const; int numItems() const; QModelIndex firstItem() const; QModelIndex lastItem() const; }; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) diff --git a/GroupView.cpp b/GroupView.cpp index 50d19f52..1f0a51e7 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -36,7 +36,7 @@ GroupView::GroupView(QWidget *parent) // setWordWrap(true); // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); - // setSpacing(10); + m_spacing = 5; } GroupView::~GroupView() @@ -145,31 +145,31 @@ Group *GroupView::category(const QModelIndex &index) const Group *GroupView::category(const QString &cat) const { - for (int i = 0; i < m_categories.size(); ++i) + for (auto group : m_categories) { - if (m_categories.at(i)->text == cat) + if (group->text == cat) { - return m_categories.at(i); + return group; } } - return 0; + return nullptr; } Group *GroupView::categoryAt(const QPoint &pos) const { - for (int i = 0; i < m_categories.size(); ++i) + for (auto group : m_categories) { - if (m_categories.at(i)->iconRect.contains(pos)) + if(group->pointIntersect(pos) & Group::CheckboxHit) { - return m_categories.at(i); + return group; } } - return 0; + return nullptr; } int GroupView::itemsPerRow() const { - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + /* spacing */ 10)); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); } int GroupView::contentWidth() const @@ -261,7 +261,7 @@ void GroupView::mousePressEvent(QMouseEvent *event) m_pressedIndex = index; m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); m_pressedPosition = pos; m_pressedCategory = categoryAt(m_pressedPosition); @@ -428,9 +428,8 @@ void GroupView::mouseDoubleClickEvent(QMouseEvent *event) void GroupView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); - painter.translate(-offset()); - int y = 0; + int y = -verticalOffset(); for (int i = 0; i < m_categories.size(); ++i) { Group *category = m_categories.at(i); @@ -502,7 +501,7 @@ void GroupView::resizeEvent(QResizeEvent *event) // if (m_categoryEditor) // { // m_categoryEditor->resize(qMax(contentWidth() / 2, - //m_editedCategory->textRect.width()), + // m_editedCategory->textRect.width()), // m_categoryEditor->height()); // } @@ -618,6 +617,11 @@ void GroupView::startDrag(Qt::DropActions supportedActions) } QRect GroupView::visualRect(const QModelIndex &index) const +{ + return geometryRect(index).translated(-offset()); +} + +QRect GroupView::geometryRect(const QModelIndex &index) const { if (!index.isValid() || isIndexHidden(index) || index.column() > 0) { @@ -627,15 +631,16 @@ QRect GroupView::visualRect(const QModelIndex &index) const const Group *cat = category(index); QPair pos = categoryInternalPosition(index); int x = pos.first; - int y = pos.second; + // int y = pos.second; QRect out; out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); - out.setLeft(/*spacing*/ 10 + x * itemWidth() + x * /*spacing()*/ 10); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); return out; } + /* void CategorizedView::startCategoryEditor(Category *category) { @@ -680,7 +685,7 @@ QModelIndex GroupView::indexAt(const QPoint &point) const for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) + if (geometryRect(index).contains(point)) { return index; } @@ -694,7 +699,7 @@ void GroupView::setSelection(const QRect &rect, for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (visualRect(index).intersects(rect)) + if (geometryRect(index).intersects(rect)) { selectionModel()->select(index, commands); } @@ -734,7 +739,7 @@ QList> GroupView::draggablePaintPairs(const QModelInde for (int i = 0; i < indices.count(); ++i) { const QModelIndex &index = indices.at(i); - const QRect current = visualRect(index); + const QRect current = geometryRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); @@ -857,3 +862,24 @@ QPoint GroupView::offset() const { return QPoint(horizontalOffset(), verticalOffset()); } + +QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const +{ + QRegion region; + for (auto &range : selection) + { + int start_row = range.top(); + int end_row = range.bottom(); + for (int row = start_row; row <= end_row; ++row) + { + int start_column = range.left(); + int end_column = range.right(); + for (int column = start_column; column <= end_column; ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); // OK + } + } + } + return region; +} diff --git a/GroupView.h b/GroupView.h index bf911794..2c60f0e9 100644 --- a/GroupView.h +++ b/GroupView.h @@ -2,6 +2,7 @@ #include #include +#include struct CategorizedViewRoles { @@ -23,7 +24,8 @@ public: GroupView(QWidget *parent = 0); ~GroupView(); - virtual QRect visualRect(const QModelIndex &index) const; + virtual QRect geometryRect(const QModelIndex &index) const; + virtual QRect visualRect(const QModelIndex &index) const override; QModelIndex indexAt(const QPoint &point) const; void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; @@ -34,12 +36,18 @@ public: virtual int horizontalOffset() const override { - return 0; + return horizontalScrollBar()->value(); } virtual int verticalOffset() const override { - return 0; + return verticalScrollBar()->value(); + } + + virtual void scrollContentsBy(int dx, int dy) override + { + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); } virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override @@ -53,10 +61,7 @@ public: return QModelIndex(); } - virtual QRegion visualRegionForSelection(const QItemSelection &) const override - { - return QRegion(); - } + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; /* * End of BS @@ -116,14 +121,16 @@ private: private slots: void endCategoryEditor();*/ -private: +private: /* variables */ QPoint m_pressedPosition; QPersistentModelIndex m_pressedIndex; bool m_pressedAlreadySelected; Group *m_pressedCategory; QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; + int m_spacing = 5; +private: /* methods */ QPair categoryInternalPosition(const QModelIndex &index) const; int categoryInternalRowTop(const QModelIndex &index) const; int itemHeightForCategoryRow(const Group *category, const int internalRow) const; -- cgit From 2cd9b0647640ee595113ceca128db1131bebc19a Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 16:57:00 +0100 Subject: Fix drag&drop pixmap rendering --- GroupView.cpp | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index 1f0a51e7..b47cf93f 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -296,7 +296,7 @@ void GroupView::mousePressEvent(QMouseEvent *event) void GroupView::mouseMoveEvent(QMouseEvent *event) { QPoint topLeft; - QPoint bottomRight = event->pos(); + QPoint pos = event->pos() + offset(); if (state() == ExpandingState || state() == CollapsingState) { @@ -316,15 +316,13 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) return; } - QPersistentModelIndex index = indexAt(bottomRight); - if (selectionMode() != SingleSelection) { topLeft = m_pressedPosition - offset(); } else { - topLeft = bottomRight; + topLeft = pos; } if (m_pressedIndex.isValid() && (state() != DragSelectingState) && @@ -337,17 +335,9 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) if ((event->buttons() & Qt::LeftButton) && selectionModel()) { setState(DragSelectingState); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && - command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - command |= m_ctrlDragSelectionFlag; - } - // Do the normalize ourselves, since QRect::normalized() is flawed - QRect selectionRect = QRect(topLeft, bottomRight); - setSelection(selectionRect, command); + setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect); + QModelIndex index = indexAt(pos); // set at the end because it might scroll the view if (index.isValid() && (index != selectionModel()->currentIndex()) && @@ -582,7 +572,7 @@ void GroupView::startDrag(Qt::DropActions supportedActions) } QRect rect; QPixmap pixmap = renderToPixmap(indexes, &rect); - rect.translate(offset()); + //rect.translate(offset()); // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); @@ -699,12 +689,14 @@ void GroupView::setSelection(const QRect &rect, for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (geometryRect(index).intersects(rect)) + QRect itemRect = geometryRect(index); + if (itemRect.intersects(rect)) { selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); } } - update(); + } QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const @@ -725,6 +717,7 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; itemDelegate()->paint(&painter, option, current); + painter.drawLine(0,0, r->width(), r->height()); } return pixmap; } @@ -740,13 +733,13 @@ QList> GroupView::draggablePaintPairs(const QModelInde { const QModelIndex &index = indices.at(i); const QRect current = geometryRect(index); - if (current.intersects(viewportRect)) - { + //if (current.intersects(viewportRect)) + //{ ret += qMakePair(current, index); rect |= current; - } + //} } - rect &= viewportRect; + //rect &= viewportRect; return ret; } -- cgit From ddedd077b66f2bf45cd157d760eebdad15bcb289 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 18:30:52 +0100 Subject: Oh my. --- GroupView.cpp | 40 +++++++++++++++++++++++++++++++++------- GroupView.h | 15 ++++----------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index b47cf93f..3296ccfb 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -727,19 +727,14 @@ QList> GroupView::draggablePaintPairs(const QModelInde { Q_ASSERT(r); QRect &rect = *r; - const QRect viewportRect = viewport()->rect(); QList> ret; for (int i = 0; i < indices.count(); ++i) { const QModelIndex &index = indices.at(i); const QRect current = geometryRect(index); - //if (current.intersects(viewportRect)) - //{ - ret += qMakePair(current, index); - rect |= current; - //} + ret += qMakePair(current, index); + rect |= current; } - //rect &= viewportRect; return ret; } @@ -876,3 +871,34 @@ QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) con } return region; } +QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) +{ + auto current = currentIndex(); + if(!current.isValid()) + { + qDebug() << "model row: invalid"; + return current; + } + qDebug() << "model row: " << current.row(); + auto cat = category(current); + int i = m_categories.indexOf(cat); + if(i >= 0) + { + // this is a pile of something foul + auto real_cat = m_categories[i]; + int beginning_row = 0; + for(auto catt: m_categories) + { + if(catt == real_cat) + break; + beginning_row += catt->numRows(); + } + qDebug() << "category: " << real_cat->text; + QPair pos = categoryInternalPosition(current); + int row = beginning_row + pos.second; + qDebug() << "row: " << row; + qDebug() << "column: " << pos.first; + } + return current; +} diff --git a/GroupView.h b/GroupView.h index 2c60f0e9..a7ed8e9d 100644 --- a/GroupView.h +++ b/GroupView.h @@ -30,10 +30,6 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; - /* - * BS - */ - virtual int horizontalOffset() const override { return horizontalScrollBar()->value(); @@ -50,22 +46,19 @@ public: viewport()->scroll(dx, dy); } + /* + * TODO! + */ virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override { return; } virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) - override - { - return QModelIndex(); - } + override; virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - /* - * End of BS - */ protected slots: -- cgit From ac2f64f3373f37e4ba039d4395857f59ca7975af Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 3 Feb 2014 20:42:04 +0100 Subject: Reload version after removing custom.json --- gui/dialogs/OneSixModEditDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index 41ac5d8d..1b61771d 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -138,6 +138,7 @@ void OneSixModEditDialog::on_forgeBtn_clicked() return; } QDir(m_inst->instanceRoot()).remove("custom.json"); + m_inst->reloadVersion(this); } VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); vselect.setFilter(1, m_inst->currentVersionId()); @@ -194,6 +195,7 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked() return; } QDir(m_inst->instanceRoot()).remove("custom.json"); + m_inst->reloadVersion(this); } LiteLoaderInstaller liteloader; if (!liteloader.canApply(m_inst)) -- cgit From 946d49675cb4725c31ab49a51f3bcae302f89a19 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 00:40:10 +0100 Subject: Swap things around -- rename refactors, moving towards non-derpy design. Maybe. --- Group.cpp | 16 ++++++------ Group.h | 25 ++++++++++++++++--- GroupView.cpp | 68 +++++++++++++++++++++++++++------------------------ GroupView.h | 25 +++++++++---------- GroupedProxyModel.cpp | 4 +-- InstanceDelegate.cpp | 4 +-- main.cpp | 4 +-- main.h | 8 +++--- 8 files changed, 88 insertions(+), 66 deletions(-) diff --git a/Group.cpp b/Group.cpp index c92132ca..f216cc6e 100644 --- a/Group.cpp +++ b/Group.cpp @@ -17,20 +17,20 @@ Group::Group(const Group *other) void Group::update() { - firstRow = firstItem().row(); + firstItemIndex = firstItem().row(); rowHeights = QVector(numRows()); for (int i = 0; i < numRows(); ++i) { rowHeights[i] = view->categoryRowHeight( - view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); } } -Group::HitResults Group::pointIntersect(const QPoint &pos) const +Group::HitResults Group::hitScan(const QPoint &pos) const { Group::HitResults results = Group::NoHit; - int y_start = top(); + int y_start = verticalPosition(); int body_start = y_start + headerHeight(); int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? int y = pos.y(); @@ -95,7 +95,7 @@ void Group::drawHeader(QPainter *painter, const int y) int Group::totalHeight() const { - return headerHeight() + 5 + contentHeight(); + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? } int Group::headerHeight() const @@ -122,10 +122,10 @@ int Group::numRows() const return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); } -int Group::top() const +int Group::verticalPosition() const { int res = 0; - const QList cats = view->m_categories; + const QList cats = view->m_groups; for (int i = 0; i < cats.size(); ++i) { if (cats.at(i) == this) @@ -143,7 +143,7 @@ QList Group::items() const for (int i = 0; i < view->model()->rowCount(); ++i) { const QModelIndex index = view->model()->index(i, 0); - if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + if (index.data(GroupViewRoles::GroupRole).toString() == text) { indices.append(index); } diff --git a/Group.h b/Group.h index 51e0470d..455ee1a8 100644 --- a/Group.h +++ b/Group.h @@ -10,22 +10,38 @@ class QModelIndex; struct Group { +/* constructors */ Group(const QString &text, GroupView *view); Group(const Group *other); + +/* data */ GroupView *view; QString text; bool collapsed; QVector rowHeights; - int firstRow; + int firstItemIndex; +/* logic */ + /// do stuff. and things. TODO: redo. void update(); + /// draw the header at y-position. void drawHeader(QPainter *painter, const int y); + + /// height of the group, in total. includes a small bit of padding. int totalHeight() const; + + /// height of the group header, in pixels int headerHeight() const; + + /// height of the group content, in pixels int contentHeight() const; + + /// the number of visual rows this group has int numRows() const; - int top() const; + + /// the height at which this group starts, in pixels + int verticalPosition() const; enum HitResult { @@ -37,9 +53,12 @@ struct Group }; Q_DECLARE_FLAGS(HitResults, HitResult) - HitResults pointIntersect (const QPoint &pos) const; + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; + /// super derpy thing. QList items() const; + /// I don't even int numItems() const; QModelIndex firstItem() const; QModelIndex lastItem() const; diff --git a/GroupView.cpp b/GroupView.cpp index 3296ccfb..89e3e223 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -41,14 +41,14 @@ GroupView::GroupView(QWidget *parent) GroupView::~GroupView() { - qDeleteAll(m_categories); - m_categories.clear(); + qDeleteAll(m_groups); + m_groups.clear(); } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) + if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); } @@ -74,18 +74,18 @@ void GroupView::updateGeometries() for (int i = 0; i < model()->rowCount(); ++i) { - const QString category = - model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); - if (!cats.contains(category)) + const QString groupName = + model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) { - Group *old = this->category(category); + Group *old = this->category(groupName); if (old) { - cats.insert(category, new Group(old)); + cats.insert(groupName, new Group(old)); } else { - cats.insert(category, new Group(category, this)); + cats.insert(groupName, new Group(groupName, this)); } } } @@ -95,22 +95,22 @@ void GroupView::updateGeometries() m_editedCategory = cats[m_editedCategory->text]; }*/ - qDeleteAll(m_categories); - m_categories = cats.values(); + qDeleteAll(m_groups); + m_groups = cats.values(); - for (auto cat : m_categories) + for (auto cat : m_groups) { cat->update(); } - if (m_categories.isEmpty()) + if (m_groups.isEmpty()) { verticalScrollBar()->setRange(0, 0); } else { int totalHeight = 0; - for (auto category : m_categories) + for (auto category : m_groups) { totalHeight += category->totalHeight() + m_categoryMargin; } @@ -140,12 +140,12 @@ bool GroupView::isIndexHidden(const QModelIndex &index) const Group *GroupView::category(const QModelIndex &index) const { - return category(index.data(CategorizedViewRoles::CategoryRole).toString()); + return category(index.data(GroupViewRoles::GroupRole).toString()); } Group *GroupView::category(const QString &cat) const { - for (auto group : m_categories) + for (auto group : m_groups) { if (group->text == cat) { @@ -157,9 +157,9 @@ Group *GroupView::category(const QString &cat) const Group *GroupView::categoryAt(const QPoint &pos) const { - for (auto group : m_categories) + for (auto group : m_groups) { - if(group->pointIntersect(pos) & Group::CheckboxHit) + if(group->hitScan(pos) & Group::CheckboxHit) { return group; } @@ -420,9 +420,9 @@ void GroupView::paintEvent(QPaintEvent *event) QPainter painter(this->viewport()); int y = -verticalOffset(); - for (int i = 0; i < m_categories.size(); ++i) + for (int i = 0; i < m_groups.size(); ++i) { - Group *category = m_categories.at(i); + Group *category = m_groups.at(i); category->drawHeader(&painter, y); y += category->totalHeight() + m_categoryMargin; } @@ -457,6 +457,10 @@ void GroupView::paintEvent(QPaintEvent *event) itemDelegate()->paint(&painter, option, index); } + /* + * Drop indicators for manual reordering... + */ +#if 0 if (!m_lastDragPosition.isNull()) { QPair pair = rowDropPos(m_lastDragPosition); @@ -464,7 +468,7 @@ void GroupView::paintEvent(QPaintEvent *event) int row = pair.second; if (category) { - int internalRow = row - category->firstRow; + int internalRow = row - category->firstItemIndex; QLine line; if (internalRow >= category->numItems()) { @@ -482,6 +486,7 @@ void GroupView::paintEvent(QPaintEvent *event) painter.restore(); } } +#endif } void GroupView::resizeEvent(QResizeEvent *event) @@ -552,7 +557,7 @@ void GroupView::dropEvent(QDropEvent *event) if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { model()->setData(model()->index(row, 0), categoryText, - CategorizedViewRoles::CategoryRole); + GroupViewRoles::GroupRole); event->setDropAction(Qt::MoveAction); event->accept(); } @@ -624,7 +629,7 @@ QRect GroupView::geometryRect(const QModelIndex &index) const // int y = pos.second; QRect out; - out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); @@ -717,7 +722,6 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; itemDelegate()->paint(&painter, option, current); - painter.drawLine(0,0, r->width(), r->height()); } return pixmap; } @@ -762,7 +766,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) Group *category = 0; { int y = 0; - for (auto cat : m_categories) + for (auto cat : m_groups) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { @@ -812,7 +816,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) int internalRow = -1; { // FIXME rework the drag and drop code - const int top = category->top(); + const int top = category->verticalPosition(); for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { @@ -882,19 +886,19 @@ QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, } qDebug() << "model row: " << current.row(); auto cat = category(current); - int i = m_categories.indexOf(cat); + int i = m_groups.indexOf(cat); if(i >= 0) { // this is a pile of something foul - auto real_cat = m_categories[i]; + auto real_group = m_groups[i]; int beginning_row = 0; - for(auto catt: m_categories) + for(auto group: m_groups) { - if(catt == real_cat) + if(group == real_group) break; - beginning_row += catt->numRows(); + beginning_row += group->numRows(); } - qDebug() << "category: " << real_cat->text; + qDebug() << "category: " << real_group->text; QPair pos = categoryInternalPosition(current); int row = beginning_row + pos.second; qDebug() << "row: " << row; diff --git a/GroupView.h b/GroupView.h index a7ed8e9d..329a3503 100644 --- a/GroupView.h +++ b/GroupView.h @@ -4,11 +4,11 @@ #include #include -struct CategorizedViewRoles +struct GroupViewRoles { enum { - CategoryRole = Qt::UserRole, + GroupRole = Qt::UserRole, ProgressValueRole, ProgressMaximumRole }; @@ -24,7 +24,7 @@ public: GroupView(QWidget *parent = 0); ~GroupView(); - virtual QRect geometryRect(const QModelIndex &index) const; + QRect geometryRect(const QModelIndex &index) const; virtual QRect visualRect(const QModelIndex &index) const override; QModelIndex indexAt(const QPoint &point) const; void setSelection(const QRect &rect, @@ -54,22 +54,21 @@ public: return; } - virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) - override; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles); - virtual void rowsInserted(const QModelIndex &parent, int start, int end); - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - virtual void updateGeometries(); + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; + virtual void updateGeometries() override; protected: - virtual bool isIndexHidden(const QModelIndex &index) const; + virtual bool isIndexHidden(const QModelIndex &index) const override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; @@ -87,7 +86,7 @@ protected: private: friend struct Group; - QList m_categories; + QList m_groups; int m_leftMargin; int m_rightMargin; diff --git a/GroupedProxyModel.cpp b/GroupedProxyModel.cpp index ab00a412..57d7ff5c 100644 --- a/GroupedProxyModel.cpp +++ b/GroupedProxyModel.cpp @@ -8,8 +8,8 @@ GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(pa bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); if (leftCategory == rightCategory) { return left.row() < right.row(); diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 944cfd76..8527e2bc 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -253,8 +253,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti } drawProgressOverlay(painter, opt, - index.data(CategorizedViewRoles::ProgressValueRole).toInt(), - index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); + index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); painter->restore(); } diff --git a/main.cpp b/main.cpp index b9ecde8f..df14e3bd 100644 --- a/main.cpp +++ b/main.cpp @@ -36,7 +36,7 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, QStandardItem *item = new QStandardItem; item->setText(text); item->setData(icon(color), Qt::DecorationRole); - item->setData(category, CategorizedViewRoles::CategoryRole); + item->setData(category, GroupViewRoles::GroupRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // progresser->addTrackedIndex(item); return item; @@ -46,7 +46,7 @@ QStandardItem *createItem(const int index, const QString &category) QStandardItem *item = new QStandardItem; item->setText(QString("Item #%1").arg(index)); item->setData(icon(index), Qt::DecorationRole); - item->setData(category, CategorizedViewRoles::CategoryRole); + item->setData(category, GroupViewRoles::GroupRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // progresser->addTrackedIndex(item); return item; diff --git a/main.h b/main.h index 6883e98e..2a358329 100644 --- a/main.h +++ b/main.h @@ -21,7 +21,7 @@ public: QStandardItem *addTrackedIndex(QStandardItem *item) { - item->setData(1000, CategorizedViewRoles::ProgressMaximumRole); + item->setData(1000, GroupViewRoles::ProgressMaximumRole); m_items.append(item); return item; } @@ -33,10 +33,10 @@ slots: QList toRemove; for (auto item : m_items) { - int maximum = item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(); - int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); + int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt(); + int value = item->data(GroupViewRoles::ProgressValueRole).toInt(); int newvalue = std::min(value + 3, maximum); - item->setData(newvalue, CategorizedViewRoles::ProgressValueRole); + item->setData(newvalue, GroupViewRoles::ProgressValueRole); if(newvalue >= maximum) { -- cgit From a17caba2c9c2aa5960581db01e4b66472a9c019c Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 00:43:18 +0100 Subject: Nuke. --- depends/groupview/CMakeLists.txt | 46 - .../include/categorizedsortfilterproxymodel.h | 175 -- depends/groupview/include/categorizedview.h | 332 ---- depends/groupview/include/categorydrawer.h | 179 --- depends/groupview/include/groupview_config.h | 28 - .../src/categorizedsortfilterproxymodel.cpp | 168 -- .../src/categorizedsortfilterproxymodel_p.h | 48 - depends/groupview/src/categorizedview.cpp | 1692 -------------------- depends/groupview/src/categorizedview_p.h | 160 -- depends/groupview/src/categorydrawer.cpp | 231 --- 10 files changed, 3059 deletions(-) delete mode 100644 depends/groupview/CMakeLists.txt delete mode 100644 depends/groupview/include/categorizedsortfilterproxymodel.h delete mode 100644 depends/groupview/include/categorizedview.h delete mode 100644 depends/groupview/include/categorydrawer.h delete mode 100644 depends/groupview/include/groupview_config.h delete mode 100644 depends/groupview/src/categorizedsortfilterproxymodel.cpp delete mode 100644 depends/groupview/src/categorizedsortfilterproxymodel_p.h delete mode 100644 depends/groupview/src/categorizedview.cpp delete mode 100644 depends/groupview/src/categorizedview_p.h delete mode 100644 depends/groupview/src/categorydrawer.cpp diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt deleted file mode 100644 index 417b1d3a..00000000 --- a/depends/groupview/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -project(libGroupView) - -set(CMAKE_AUTOMOC ON) - -# Find Qt -find_package(Qt5Core REQUIRED) -find_package(Qt5Widgets REQUIRED) - -# Include Qt headers. -include_directories(${Qt5Base_INCLUDE_DIRS}) - -SET(LIBGROUPVIEW_HEADERS -include/groupview_config.h - -# Public headers -include/categorizedsortfilterproxymodel.h -include/categorizedview.h -include/categorydrawer.h - -# Private headers -src/categorizedsortfilterproxymodel_p.h -src/categorizedview_p.h -) - -SET(LIBGROUPVIEW_SOURCES -src/categorizedsortfilterproxymodel.cpp -src/categorizedview.cpp -src/categorydrawer.cpp -) - -# Set the include dir path. -SET(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) - -# Include self. -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -# Static link! -ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) - -add_definitions(-DLIBGROUPVIEW_LIBRARY) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -add_library(libGroupView STATIC ${LIBGROUPVIEW_SOURCES} ${LIBGROUPVIEW_HEADERS}) -qt5_use_modules(libGroupView Core Widgets) diff --git a/depends/groupview/include/categorizedsortfilterproxymodel.h b/depends/groupview/include/categorizedsortfilterproxymodel.h deleted file mode 100644 index d90fb254..00000000 --- a/depends/groupview/include/categorizedsortfilterproxymodel.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_H - -#include - -#include - -class QItemSelection; - - -/** - * This class lets you categorize a view. It is meant to be used along with - * KCategorizedView class. - * - * In general terms all you need to do is to reimplement subSortLessThan() and - * compareCategories() methods. In order to make categorization work, you need - * to also call setCategorizedModel() class to enable it, since the categorization - * is disabled by default. - * - * @see KCategorizedView - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedSortFilterProxyModel - : public QSortFilterProxyModel -{ -public: - enum AdditionalRoles - { - // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) - // to define additional roles. - CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index - - CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a - ///< string or a long long value. Strings will be sorted alphabetically - ///< while long long will be sorted by their value. Please note that this - ///< value won't be shown on the view, is only for sorting purposes. What will - ///< be shown as "Category" on the view will be asked with the role - ///< CategoryDisplayRole. - }; - - KCategorizedSortFilterProxyModel ( QObject *parent = 0 ); - virtual ~KCategorizedSortFilterProxyModel(); - - /** - * Overridden from QSortFilterProxyModel. Sorts the source model using - * @p column for the given @p order. - */ - virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - /** - * @return whether the model is categorized or not. Disabled by default. - */ - bool isCategorizedModel() const; - - /** - * Enables or disables the categorization feature. - * - * @param categorizedModel whether to enable or disable the categorization feature. - */ - void setCategorizedModel ( bool categorizedModel ); - - /** - * @return the column being used for sorting. - */ - int sortColumn() const; - - /** - * @return the sort order being used for sorting. - */ - Qt::SortOrder sortOrder() const; - - /** - * Set if the sorting using CategorySortRole will use a natural comparison - * in the case that strings were returned. If enabled, QString::localeAwareCompare - * will be used for sorting. - * - * @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not. - */ - void setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ); - - /** - * @return whether it is being used a natural comparison for sorting. Enabled by default. - */ - bool sortCategoriesByNaturalComparison() const; - -protected: - /** - * Overridden from QSortFilterProxyModel. If you are subclassing - * KCategorizedSortFilterProxyModel, you will probably not need to reimplement this - * method. - * - * It calls compareCategories() to sort by category. If the both items are in the - * same category (i.e. compareCategories returns 0), then subSortLessThan is called. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - * - * @warning You usually won't need to reimplement this method when subclassing - * from KCategorizedSortFilterProxyModel. - */ - virtual bool lessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method has a similar purpose as lessThan() has on QSortFilterProxyModel. - * It is used for sorting items that are in the same category. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - */ - virtual bool subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method compares the category of the @p left index with the category - * of the @p right index. - * - * Internally and if not reimplemented, this method will ask for @p left and - * @p right models for role CategorySortRole. In order to correctly sort - * categories, the data() metod of the model should return a qlonglong (or numeric) value, or - * a QString object. QString objects will be sorted with QString::localeAwareCompare if - * sortCategoriesByNaturalComparison() is true. - * - * @note Please have present that: - * QString(QChar(QChar::ObjectReplacementCharacter)) > - * QString(QChar(QChar::ReplacementCharacter)) > - * [ all possible strings ] > - * QString(); - * - * This means that QString() will be sorted the first one, while - * QString(QChar(QChar::ObjectReplacementCharacter)) and - * QString(QChar(QChar::ReplacementCharacter)) will be sorted in last - * position. - * - * @warning Please note that data() method of the model should return always - * information of the same type. If you return a QString for an index, - * you should return always QStrings for all indexes for role CategorySortRole - * in order to correctly sort categories. You can't mix by returning - * a QString for one index, and a qlonglong for other. - * - * @note If you need a more complex layout, you will have to reimplement this - * method. - * - * @return A negative value if the category of @p left should be placed before the - * category of @p right. 0 if @p left and @p right are on the same category, and - * a positive value if the category of @p left should be placed after the - * category of @p right. - */ - virtual int compareCategories ( const QModelIndex &left, const QModelIndex &right ) const; - -private: - class Private; - Private *const d; -}; - - -#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H diff --git a/depends/groupview/include/categorizedview.h b/depends/groupview/include/categorizedview.h deleted file mode 100644 index 81b1dbb1..00000000 --- a/depends/groupview/include/categorizedview.h +++ /dev/null @@ -1,332 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDVIEW_H -#define KCATEGORIZEDVIEW_H - -#include - -#include - -class KCategoryDrawer; - -/** - * @short Item view for listing items in a categorized fashion optionally - * - * KCategorizedView basically has the same functionality as QListView, only that it also lets you - * layout items in a way that they are categorized visually. - * - * For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer - * with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be - * flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true). - * - * The way it works (if categorization enabled): - * - * - When sorting, it does more things than QListView does. It will ask the model for the - * special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return - * a QString or an int in order to tell the view the order of categories. In this sense, for - * instance, if we are sorting by name ascending, "A" would be before than "B". If we are - * sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are - * also sorted. - * - * - When the view has to paint, it will ask the model with the role CategoryDisplayRole - * (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if - * we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example. - * - * For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own - * drawing. - * - * @note All examples cited before talk about filesystems and such, but have present that this - * is a completely generic class, and it can be used for whatever your purpose is. For - * instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In - * this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the - * same ("Mammal" and "Oviparous"). - * - * @note There is a really performance boost if CategorySortRole returns an int instead of a QString. - * Have present that this role is asked (n * log n) times when sorting and compared. Comparing - * ints is always faster than comparing strings, whithout mattering how fast the string - * comparison is. Consider thinking of a way of returning ints instead of QStrings if your - * model can contain a high number of items. - * - * @warning Note that for really drawing items in blocks you will need some things to be done: - * - The model set to this view has to be (or inherit if you want to do special stuff - * in it) KCategorizedSortFilterProxyModel. - * - This model needs to be set setCategorizedModel to true. - * - Set a category drawer by calling setCategoryDrawer. - * - * @see KCategorizedSortFilterProxyModel, KCategoryDrawer - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedView - : public QListView -{ - Q_OBJECT - Q_PROPERTY ( int categorySpacing READ categorySpacing WRITE setCategorySpacing ) - Q_PROPERTY ( bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors ) - Q_PROPERTY ( bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks ) - -public: - KCategorizedView ( QWidget *parent = 0 ); - - ~KCategorizedView(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setModel ( QAbstractItemModel *model ); - - /** - * Calls to setGridSizeOwn(). - */ - void setGridSize ( const QSize &size ); - - /** - * @warning note that setGridSize is not virtual in the base class (QListView), so if you are - * calling to this method, make sure you have a KCategorizedView pointer around. This - * means that something like: - * @code - * QListView *lv = new KCategorizedView(); - * lv->setGridSize(mySize); - * @endcode - * - * will not call to the expected setGridSize method. Instead do something like this: - * - * @code - * QListView *lv; - * ... - * KCategorizedView *cv = qobject_cast(lv); - * if (cv) { - * cv->setGridSizeOwn(mySize); - * } else { - * lv->setGridSize(mySize); - * } - * @endcode - * - * @note this method will call to QListView::setGridSize among other operations. - * - * @since 4.4 - */ - void setGridSizeOwn ( const QSize &size ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QRect visualRect ( const QModelIndex &index ) const; - - /** - * Returns the current category drawer. - */ - KCategoryDrawer *categoryDrawer() const; - - /** - * The category drawer that will be used for drawing categories. - */ - void setCategoryDrawer ( KCategoryDrawer *categoryDrawer ); - - /** - * @return Category spacing. The spacing between categories. - * - * @since 4.4 - */ - int categorySpacing() const; - - /** - * Stablishes the category spacing. This is the spacing between categories. - * - * @since 4.4 - */ - void setCategorySpacing ( int categorySpacing ); - - /** - * @return Whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - bool alternatingBlockColors() const; - - /** - * Sets whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - void setAlternatingBlockColors ( bool enable ); - - /** - * @return Whether blocks can be collapsed or not. - * - * @since 4.4 - */ - bool collapsibleBlocks() const; - - /** - * Sets whether blocks can be collapsed or not. - * - * @since 4.4 - */ - void setCollapsibleBlocks ( bool enable ); - - /** - * @return Block of indexes that are into @p category. - * - * @since 4.5 - */ - QModelIndexList block ( const QString &category ); - - /** - * @return Block of indexes that are represented by @p representative. - * - * @since 4.5 - */ - QModelIndexList block ( const QModelIndex &representative ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex indexAt ( const QPoint &point ) const; - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void reset(); - - /** - * Signify that all item delegates size hints return the same fixed size - */ - void setUniformItemWidths(bool enable); - - /** - * Do all item delegate size hints return the same fixed size? - */ - bool uniformItemWidths() const; - -protected: - /** - * Reimplemented from QWidget. - */ - virtual void paintEvent ( QPaintEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void resizeEvent ( QResizeEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseMoveEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mousePressEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseReleaseEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void leaveEvent ( QEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void startDrag ( Qt::DropActions supportedActions ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragMoveEvent ( QDragMoveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragEnterEvent ( QDragEnterEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragLeaveEvent ( QDragLeaveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dropEvent ( QDropEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void updateGeometries(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsInserted ( const QModelIndex &parent, - int start, - int end ); - -protected Q_SLOTS: - /** - * @internal - * Reposition items as needed. - */ - virtual void slotLayoutChanged(); - virtual void slotCollapseOrExpandClicked ( QModelIndex ); - -private: - class Private; - Private *const d; -}; - -#endif // KCATEGORIZEDVIEW_H diff --git a/depends/groupview/include/categorydrawer.h b/depends/groupview/include/categorydrawer.h deleted file mode 100644 index f37422ec..00000000 --- a/depends/groupview/include/categorydrawer.h +++ /dev/null @@ -1,179 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORYDRAWER_H -#define KCATEGORYDRAWER_H - -#include - -#include -#include - -class QPainter; -class QModelIndex; -class QStyleOption; -class KCategorizedView; - - -/** - * @since 4.5 - */ -class LIBGROUPVIEW_EXPORT KCategoryDrawer - : public QObject -{ - friend class KCategorizedView; - Q_OBJECT - - -public: - KCategoryDrawer ( KCategorizedView *view ); - virtual ~KCategoryDrawer(); - - /** - * @return The view this category drawer is associated with. - */ - KCategorizedView *view() const; - - /** - * This method purpose is to draw a category represented by the given - * @param index with the given @param sortRole sorting role - * - * @note This method will be called one time per category, always with the - * first element in that category - */ - virtual void drawCategory ( const QModelIndex &index, - int sortRole, - const QStyleOption &option, - QPainter *painter ) const; - - /** - * @return The category height for the category representated by index @p index with - * style options @p option. - */ - virtual int categoryHeight ( const QModelIndex &index, const QStyleOption &option ) const; - - //TODO KDE5: make virtual as leftMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int leftMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the left margin - * - * @since 4.4 - */ - void setLeftMargin ( int leftMargin ); - - //TODO KDE5: make virtual as rightMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int rightMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the right margin - * - * @since 4.4 - */ - void setRightMargin ( int rightMargin ); - - KCategoryDrawer &operator= ( const KCategoryDrawer &cd ); -protected: - /** - * Method called when the mouse button has been pressed. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonPressed ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been released. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonReleased ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse has been moved. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - */ - virtual void mouseMoved ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been double clicked. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonDoubleClicked ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has left this block. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - */ - virtual void mouseLeft ( const QModelIndex &index, const QRect &blockRect ); - -private: - class Private; - Private *const d; -Q_SIGNALS: - /** - * This signal becomes emitted when collapse or expand has been clicked. - */ - void collapseOrExpandClicked ( const QModelIndex &index ); - - /** - * Emit this signal on your subclass implementation to notify that something happened. Usually - * this will be triggered when you have received an event, and its position matched some "hot spot". - * - * You give this action the integer you want, and having connected this signal to your code, - * the connected slot can perform the needed changes (view, model, selection model, delegate...) - */ - void actionRequested ( int action, const QModelIndex &index ); -}; - -#endif // KCATEGORYDRAWER_H diff --git a/depends/groupview/include/groupview_config.h b/depends/groupview/include/groupview_config.h deleted file mode 100644 index c63acbde..00000000 --- a/depends/groupview/include/groupview_config.h +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#ifdef LIBGROUPVIEW_STATIC - #define LIBGROUPVIEW_EXPORT -#else - #ifdef LIBGROUPVIEW_LIBRARY - #define LIBGROUPVIEW_EXPORT Q_DECL_EXPORT - #else - #define LIBGROUPVIEW_EXPORT Q_DECL_IMPORT - #endif -#endif diff --git a/depends/groupview/src/categorizedsortfilterproxymodel.cpp b/depends/groupview/src/categorizedsortfilterproxymodel.cpp deleted file mode 100644 index 09da9dd3..00000000 --- a/depends/groupview/src/categorizedsortfilterproxymodel.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "categorizedsortfilterproxymodel.h" -#include "categorizedsortfilterproxymodel_p.h" - -#include - -#include -#include -#include - -KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent ) - : QSortFilterProxyModel ( parent ) - , d ( new Private() ) -{ -} - -KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel() -{ - delete d; -} - -void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order ) -{ - d->sortColumn = column; - d->sortOrder = order; - - QSortFilterProxyModel::sort ( column, order ); -} - -bool KCategorizedSortFilterProxyModel::isCategorizedModel() const -{ - return d->categorizedModel; -} - -void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel ) -{ - if ( categorizedModel == d->categorizedModel ) - { - return; - } - - d->categorizedModel = categorizedModel; - - invalidate(); -} - -int KCategorizedSortFilterProxyModel::sortColumn() const -{ - return d->sortColumn; -} - -Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const -{ - return d->sortOrder; -} - -void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ) -{ - if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison ) - { - return; - } - - d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison; - - invalidate(); -} - -bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const -{ - return d->sortCategoriesByNaturalComparison; -} - -bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - if ( d->categorizedModel ) - { - int compare = compareCategories ( left, right ); - - if ( compare > 0 ) // left is greater than right - { - return false; - } - else if ( compare < 0 ) // left is less than right - { - return true; - } - } - - return subSortLessThan ( left, right ); -} - -bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - return QSortFilterProxyModel::lessThan ( left, right ); -} - -int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const -{ - QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() ); - QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() ); - - Q_ASSERT ( l.isValid() ); - Q_ASSERT ( r.isValid() ); - Q_ASSERT ( l.type() == r.type() ); - - if ( l.type() == QVariant::String ) - { - QString lstr = l.toString(); - QString rstr = r.toString(); - - /* - if ( d->sortCategoriesByNaturalComparison ) - { - return KStringHandler::naturalCompare ( lstr, rstr ); - } - else - { - */ - if ( lstr < rstr ) - { - return -1; - } - - if ( lstr > rstr ) - { - return 1; - } - - return 0; - //} - } - - qlonglong lint = l.toLongLong(); - qlonglong rint = r.toLongLong(); - - if ( lint < rint ) - { - return -1; - } - - if ( lint > rint ) - { - return 1; - } - - return 0; -} diff --git a/depends/groupview/src/categorizedsortfilterproxymodel_p.h b/depends/groupview/src/categorizedsortfilterproxymodel_p.h deleted file mode 100644 index d7e7c9a0..00000000 --- a/depends/groupview/src/categorizedsortfilterproxymodel_p.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_P_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H - -class KCategorizedSortFilterProxyModel; - -class KCategorizedSortFilterProxyModel::Private -{ -public: - Private() - : sortColumn ( 0 ) - , sortOrder ( Qt::AscendingOrder ) - , categorizedModel ( false ) - , sortCategoriesByNaturalComparison ( true ) - { - } - - ~Private() - { - } - - int sortColumn; - Qt::SortOrder sortOrder; - bool categorizedModel; - bool sortCategoriesByNaturalComparison; -}; - -#endif diff --git a/depends/groupview/src/categorizedview.cpp b/depends/groupview/src/categorizedview.cpp deleted file mode 100644 index 1345205c..00000000 --- a/depends/groupview/src/categorizedview.cpp +++ /dev/null @@ -1,1692 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/** - * IMPLEMENTATION NOTES: - * - * QListView::setRowHidden() and QListView::isRowHidden() are not taken into - * account. This methods should actually not exist. This effect should be handled - * by an hypothetical QSortFilterProxyModel which filters out the desired rows. - * - * In case this needs to be implemented, contact me, but I consider this a faulty - * design. - */ - -#include "categorizedview.h" -#include "categorizedview_p.h" - -#include // trunc on C99 compliant systems -//#include // trunc for not C99 compliant systems - -#include -#include -#include - -#include "categorydrawer.h" -#include "categorizedsortfilterproxymodel.h" - -//BEGIN: Private part - -struct KCategorizedView::Private::Item -{ - Item() - : topLeft ( QPoint() ) - , size ( QSize() ) - { - } - - QPoint topLeft; - QSize size; -}; - -struct KCategorizedView::Private::Block -{ - bool operator!= ( const Block &rhs ) const - { - return firstIndex != rhs.firstIndex; - } - - static bool lessThan ( const Block &left, const Block &right ) - { - Q_ASSERT ( left.firstIndex.isValid() ); - Q_ASSERT ( right.firstIndex.isValid() ); - return left.firstIndex.row() < right.firstIndex.row(); - } - - QPoint topLeft; - int height = -1; - QPersistentModelIndex firstIndex; - // if we have n elements on this block, and we inserted an element at position i. The quarantine - // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the - // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine - // will only affect the current block, since the rest of blocks can be affected only in the way - // that the whole block will have different offset, but items will keep the same relative position - // in terms of their parent blocks. - QPersistentModelIndex quarantineStart; - QList items; - - // this affects the whole block, not items separately. items contain the topLeft point relative - // to the block. Because of insertions or removals a whole block can be moved, so the whole block - // will enter in quarantine, what is faster than moving all items in absolute terms. - bool outOfQuarantine = false; - - // should we alternate its color ? is just a hint, could not be used - bool alternate = false; - bool collapsed = false; -}; - -KCategorizedView::Private::Private ( KCategorizedView *q ) - : q ( q ) - , hoveredBlock ( new Block() ) -{ -} - -KCategorizedView::Private::~Private() -{ - delete hoveredBlock; -} - -bool KCategorizedView::Private::isCategorized() const -{ - return proxyModel && categoryDrawer && proxyModel->isCategorizedModel(); -} - -QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative ) -{ - QStyleOptionViewItemV4 option ( q->viewOptions() ); - const int height = categoryDrawer->categoryHeight ( representative, option ); - const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - QPoint pos = blockPosition ( categoryDisplay ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + blockHeight ( categoryDisplay ) ); - option.rect = mapToViewport ( option.rect ); - - return option; -} - -QPair KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const -{ - const int rowCount = proxyModel->rowCount(); - - const QRect rect = _rect.normalized(); - - // binary search to find out the top border - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.bottomRight().y() <= rect.topLeft().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() ); - - // binary search to find out the bottom border - bottom = 0; - top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.topLeft().y() <= rect.bottomRight().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() ); - - return qMakePair ( bottomIndex, topIndex ); -} - -QPoint KCategorizedView::Private::blockPosition ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.outOfQuarantine && !block.topLeft.isNull() ) - { - return block.topLeft; - } - - QPoint res ( categorySpacing, 0 ); - - const QModelIndex index = block.firstIndex; - - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - const QModelIndex categoryIndex = block.firstIndex; - if ( index.row() < categoryIndex.row() ) - { - continue; - } - res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing; - if ( index.row() == categoryIndex.row() ) - { - continue; - } - res.ry() += blockHeight ( it.key() ); - } - - block.outOfQuarantine = true; - block.topLeft = res; - - return res; -} - -int KCategorizedView::Private::blockHeight ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.collapsed ) - { - return 0; - } - - if ( block.height > -1 ) - { - return block.height; - } - - const QModelIndex firstIndex = block.firstIndex; - const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect topLeft = q->visualRect ( firstIndex ); - QRect bottomRight = q->visualRect ( lastIndex ); - - if ( hasGrid() ) - { - bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) ); - } - else - { - if ( !q->uniformItemSizes() ) - { - bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 ); - } - } - - const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1; - block.height = height; - - return height; -} - -int KCategorizedView::Private::viewportWidth() const -{ - return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin(); -} - -void KCategorizedView::Private::regenerateAllElements() -{ - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - block.outOfQuarantine = false; - block.quarantineStart = block.firstIndex; - block.height = -1; - } -} - -void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end ) -{ - if ( !isCategorized() ) - { - return; - } - - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = categoryForIndex ( index ); - - Block &block = blocks[category]; - - //BEGIN: update firstIndex - // save as firstIndex in block if - // - it forced the category creation (first element on this category) - // - it is before the first row on that category - const QModelIndex firstIndex = block.firstIndex; - if ( !firstIndex.isValid() || index.row() < firstIndex.row() ) - { - block.firstIndex = index; - } - //END: update firstIndex - - Q_ASSERT ( block.firstIndex.isValid() ); - - const int firstIndexRow = block.firstIndex.row(); - - block.items.insert ( index.row() - firstIndexRow, Private::Item() ); - block.height = -1; - - q->visualRect ( index ); - q->viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent ); - const QString category = categoryForIndex ( lastIndex ); - Private::Block &block = blocks[category]; - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent ); - const QString category = categoryForIndex ( firstIndex ); - const QModelIndex firstAffectedCategory = blocks[category].firstIndex; - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = blocks.values(); - qSort ( blockList.begin(), blockList.end(), Block::lessThan ); - QList firstIndexesRows; - foreach ( const Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > firstAffectedCategory.row() ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == firstAffectedCategory.row() ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones -} - -QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const -{ - const int dx = -q->horizontalOffset(); - const int dy = -q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const -{ - const int dx = q->horizontalOffset(); - const int dy = q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const -{ - //Find the highest element in the last row - const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect prevRect = q->visualRect ( lastIndex ); - int res = prevRect.height(); - QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - if ( !prevIndex.isValid() ) - { - return res; - } - Q_FOREVER - { - const QRect tempRect = q->visualRect ( prevIndex ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - res = qMax ( res, tempRect.height() ); - if ( prevIndex == block.firstIndex ) - { - break; - } - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - } - - return res; -} - -bool KCategorizedView::Private::hasGrid() const -{ - const QSize gridSize = q->gridSize(); - return gridSize.isValid() && !gridSize.isNull(); -} - -QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const -{ - const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() ); - return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); -} - -void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height(); - } - else - { - const QSize currSize = q->sizeHintForIndex ( index ); - if ( index != block.firstIndex ) - { - const int viewportW = viewportWidth() - q->spacing(); - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW ) - { - // we have to check the whole previous row, and see which one was the - // highest. - Q_FOREVER - { - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect tempRect = q->visualRect ( prevIndex ); - tempRect = mapFromViewport ( tempRect ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() ) - { - prevRect = tempRect; - } - if ( prevIndex == block.firstIndex ) - { - break; - } - } - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing(); - } - else - { - item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); - } - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); -} - -void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * itemSize.height(); - } - else - { - if ( index != block.firstIndex ) - { - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); - item.size.setWidth ( viewportWidth() ); -} - -void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex ) -{ -} - -//END: Private part - -//BEGIN: Public part - -KCategorizedView::KCategorizedView ( QWidget *parent ) - : QListView ( parent ) - , d ( new Private ( this ) ) -{ -} - -KCategorizedView::~KCategorizedView() -{ - delete d; -} - -void KCategorizedView::setModel ( QAbstractItemModel *model ) -{ - if ( d->proxyModel == model ) - { - return; - } - - d->blocks.clear(); - - if ( d->proxyModel ) - { - disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - d->proxyModel = dynamic_cast ( model ); - - if ( d->proxyModel ) - { - connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - QListView::setModel ( model ); - - // if the model already had information inserted, update our data structures to it - if ( model->rowCount() ) - { - slotLayoutChanged(); - } -} - - -void KCategorizedView::setUniformItemWidths(bool enable) -{ - d->constantItemWidth = enable; -} - - -bool KCategorizedView::uniformItemWidths() const -{ - return d->constantItemWidth; -} - -void KCategorizedView::setGridSize ( const QSize &size ) -{ - setGridSizeOwn ( size ); -} - -void KCategorizedView::setGridSizeOwn ( const QSize &size ) -{ - d->regenerateAllElements(); - QListView::setGridSize ( size ); -} - -QRect KCategorizedView::visualRect ( const QModelIndex &index ) const -{ - if ( !d->isCategorized() ) - { - return QListView::visualRect ( index ); - } - - if ( !index.isValid() ) - { - return QRect(); - } - - const QString category = d->categoryForIndex ( index ); - - if ( !d->blocks.contains ( category ) ) - { - return QRect(); - } - - Private::Block &block = d->blocks[category]; - const int firstIndexRow = block.firstIndex.row(); - - Q_ASSERT ( block.firstIndex.isValid() ); - - if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() ) - { - return QRect(); - } - - const QPoint blockPos = d->blockPosition ( category ); - - Private::Item &ritem = block.items[index.row() - firstIndexRow]; - - if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() && - index.row() >= block.quarantineStart.row() ) ) - { - if ( flow() == LeftToRight ) - { - d->leftToRightVisualRect ( index, ritem, block, blockPos ); - } - else - { - d->topToBottomVisualRect ( index, ritem, block, blockPos ); - } - - //BEGIN: update the quarantine start - const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) ); - if ( index.row() == block.quarantineStart.row() ) - { - if ( wasLastIndex ) - { - block.quarantineStart = QModelIndex(); - } - else - { - const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() ); - block.quarantineStart = nextIndex; - } - } - //END: update the quarantine start - } - - // we get now the absolute position through the relative position of the parent block. do not - // save this on ritem, since this would override the item relative position in block terms. - Private::Item item ( ritem ); - item.topLeft.ry() += blockPos.y(); - - const QSize sizeHint = item.size; - - if ( d->hasGrid() ) - { - const QSize sizeGrid = gridSize(); - const QSize resultingSize = sizeHint.boundedTo ( sizeGrid ); - QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ), - item.topLeft.y(), resultingSize.width(), resultingSize.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -resultingSize.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); - } - - QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -sizeHint.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); -} - -KCategoryDrawer *KCategorizedView::categoryDrawer() const -{ - return d->categoryDrawer; -} - -void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer ) -{ - disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); - d->categoryDrawer = categoryDrawer; - - connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); -} - -int KCategorizedView::categorySpacing() const -{ - return d->categorySpacing; -} - -void KCategorizedView::setCategorySpacing ( int categorySpacing ) -{ - if ( d->categorySpacing == categorySpacing ) - { - return; - } - - d->categorySpacing = categorySpacing; - - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - block.outOfQuarantine = false; - } -} - -bool KCategorizedView::alternatingBlockColors() const -{ - return d->alternatingBlockColors; -} - -void KCategorizedView::setAlternatingBlockColors ( bool enable ) -{ - d->alternatingBlockColors = enable; -} - -bool KCategorizedView::collapsibleBlocks() const -{ - return d->collapsibleBlocks; -} - -void KCategorizedView::setCollapsibleBlocks ( bool enable ) -{ - d->collapsibleBlocks = enable; -} - -QModelIndexList KCategorizedView::block ( const QString &category ) -{ - QModelIndexList res; - const Private::Block &block = d->blocks[category]; - if ( block.height == -1 ) - { - return res; - } - QModelIndex current = block.firstIndex; - const int first = current.row(); - for ( int i = 1; i <= block.items.count(); ++i ) - { - if ( current.isValid() ) - { - res << current; - } - current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() ); - } - return res; -} - -QModelIndexList KCategorizedView::block ( const QModelIndex &representative ) -{ - return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() ); -} - -QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const -{ - if ( !d->isCategorized() ) - { - return QListView::indexAt ( point ); - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - return QModelIndex(); - } - - // Binary search that will try to spot if there is an index under point - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() ); - QRect rect = visualRect ( index ); - const int verticalOff = verticalOffset(); - int horizontalOff = horizontalOffset(); - if ( layoutDirection() == Qt::RightToLeft ) - { - horizontalOff *= -1; - } - rect.topLeft().ry() += verticalOff; - rect.topLeft().rx() += horizontalOff; - rect.bottomRight().ry() += verticalOff; - rect.bottomRight().rx() += horizontalOff; - if ( rect.contains ( point ) ) - { - if ( index.model()->flags ( index ) & Qt::ItemIsEnabled ) - { - return index; - } - return QModelIndex(); - } - bool directionCondition; - if ( layoutDirection() == Qt::LeftToRight ) - { - directionCondition = point.x() > rect.bottomRight().x(); - } - else - { - directionCondition = point.x() < rect.bottomLeft().x(); - } - if ( point.y() > rect.bottomRight().y() || - ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - return QModelIndex(); -} - -void KCategorizedView::reset() -{ - d->blocks.clear(); - QListView::reset(); -} - -void KCategorizedView::paintEvent ( QPaintEvent *event ) -{ - if ( !d->isCategorized() ) - { - QListView::paintEvent ( event ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) ); - - QPainter p ( viewport() ); - p.save(); - - Q_ASSERT ( selectionModel()->model() == d->proxyModel ); - - //BEGIN: draw categories - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open - : QStyle::State_None; - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - if ( !option.rect.intersects ( viewport()->rect() ) ) - { - ++it; - continue; - } - d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p ); - ++it; - } - //END: draw categories - - if ( intersecting.first.isValid() && intersecting.second.isValid() ) - { - //BEGIN: draw items - int i = intersecting.first.row(); - int indexToCheckIfBlockCollapsed = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block = 0; - while ( i <= intersecting.second.row() ) - { - //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting - if ( i == indexToCheckIfBlockCollapsed ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); - if ( block->collapsed ) - { - i = indexToCheckIfBlockCollapsed; - continue; - } - } - //END: first check if the block is collapsed. if so, we have to skip the item painting - - Q_ASSERT ( block ); - - const bool alternateItem = ( i - block->firstIndex.row() ) % 2; - - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const Qt::ItemFlags flags = d->proxyModel->flags ( index ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.rect = visualRect ( index ); - option.widget = this; - option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText - : QStyleOptionViewItemV2::None; - option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - if ( flags & Qt::ItemIsSelectable ) - { - option.state |= selectionModel()->isSelected ( index ) ? QStyle::State_Selected - : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= ( index == currentIndex() ) ? QStyle::State_HasFocus - : QStyle::State_None; - if ( ! ( flags & Qt::ItemIsEnabled ) ) - { - option.state &= ~QStyle::State_Enabled; - } - else - { - option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver - : QStyle::State_None; - } - - itemDelegate ( index )->paint ( &p, option, index ); - ++i; - } - //END: draw items - } - - //BEGIN: draw selection rect - if ( isSelectionRectVisible() && d->rubberBandRect.isValid() ) - { - QStyleOptionRubberBand opt; - opt.initFrom ( this ); - opt.shape = QRubberBand::Rectangle; - opt.opaque = false; - opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) ); - p.save(); - style()->drawControl ( QStyle::CE_RubberBand, &opt, &p ); - p.restore(); - } - //END: draw selection rect - - p.restore(); -} - -void KCategorizedView::resizeEvent ( QResizeEvent *event ) -{ - d->regenerateAllElements(); - QListView::resizeEvent ( event ); -} - -void KCategorizedView::setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ) -{ - if ( !d->isCategorized() ) - { - QListView::setSelection ( rect, flags ); - return; - } - - if ( rect.topLeft() == rect.bottomRight() ) - { - const QModelIndex index = indexAt ( rect.topLeft() ); - selectionModel()->select ( index, flags ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( rect ); - - QItemSelection selection; - - //TODO: think of a faster implementation - QModelIndex firstIndex; - QModelIndex lastIndex; - for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const bool visualRectIntersects = visualRect ( index ).intersects ( rect ); - if ( firstIndex.isValid() ) - { - if ( visualRectIntersects ) - { - lastIndex = index; - } - else - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - firstIndex = QModelIndex(); - } - } - else if ( visualRectIntersects ) - { - firstIndex = index; - lastIndex = index; - } - } - - if ( firstIndex.isValid() ) - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - } - - selectionModel()->select ( selection, flags ); -} - -void KCategorizedView::mouseMoveEvent ( QMouseEvent *event ) -{ - QListView::mouseMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); - const SelectionMode itemViewSelectionMode = selectionMode(); - if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection - && itemViewSelectionMode != NoSelection ) - { - QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) ); - rect = rect.normalized(); - update ( rect.united ( d->rubberBandRect ) ); - d->rubberBandRect = rect; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - viewport()->update ( option.rect ); - } - else if ( d->hoveredBlock->height == -1 ) - { - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - } - else if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - return; - } - ++it; - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::mousePressEvent ( QMouseEvent *event ) -{ - if ( event->button() == Qt::LeftButton ) - { - d->pressedPosition = event->pos(); - d->pressedPosition.rx() += horizontalOffset(); - d->pressedPosition.ry() += verticalOffset(); - } - if ( !d->categoryDrawer ) - { - QListView::mousePressEvent ( event ); - return; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mousePressEvent ( event ); - } - return; - } - ++it; - } - QListView::mousePressEvent ( event ); -} - -void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event ) -{ - d->pressedPosition = QPoint(); - d->rubberBandRect = QRect(); - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mouseReleaseEvent ( event ); - } - return; - } - ++it; - } - QListView::mouseReleaseEvent ( event ); -} - -void KCategorizedView::leaveEvent ( QEvent *event ) -{ - QListView::leaveEvent ( event ); - if ( d->hoveredIndex.isValid() ) - { - viewport()->update ( visualRect ( d->hoveredIndex ) ); - d->hoveredIndex = QModelIndex(); - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::startDrag ( Qt::DropActions supportedActions ) -{ - QListView::startDrag ( supportedActions ); -} - -void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event ) -{ - QListView::dragMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); -} - -void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event ) -{ - QListView::dragEnterEvent ( event ); -} - -void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event ) -{ - QListView::dragLeaveEvent ( event ); -} - -void KCategorizedView::dropEvent ( QDropEvent *event ) -{ - QListView::dropEvent ( event ); -} - -//TODO: improve se we take into account collapsed blocks -//TODO: take into account when there is no grid and no uniformItemSizes -QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ) -{ - if ( !d->isCategorized() ) - { - return QListView::moveCursor ( cursorAction, modifiers ); - } - - const QModelIndex current = currentIndex(); - const QRect currentRect = visualRect ( current ); - if ( !current.isValid() ) - { - const int rowCount = d->proxyModel->rowCount ( rootIndex() ); - if ( !rowCount ) - { - return QModelIndex(); - } - return d->proxyModel->index ( 0, modelColumn(), rootIndex() ); - } - - switch ( cursorAction ) - { - case MoveLeft: - { - if ( !current.row() ) - { - return QModelIndex(); - } - const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() ); - const QRect previousRect = visualRect ( previous ); - if ( previousRect.top() == currentRect.top() ) - { - return previous; - } - - return QModelIndex(); - } - case MoveRight: - { - if ( current.row() == d->proxyModel->rowCount() - 1 ) - { - return QModelIndex(); - } - const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() ); - const QRect nextRect = visualRect ( next ); - if ( nextRect.top() == currentRect.top() ) - { - return next; - } - - return QModelIndex(); - } - case MoveDown: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + - block.items.count(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() ); - - if ( !nextIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )]; - - if ( nextBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) ) - { - return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - case MoveUp: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() ); - - if ( !prevIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )]; - - if ( prevBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - const int remainder = prevBlock.items.count() % maxItemsPerRow; - if ( currentRelativePos < remainder ) - { - return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - default: - break; - } - - return QModelIndex(); -} - -void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ) -{ - if ( !d->isCategorized() ) - { - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - if ( end - start + 1 == d->proxyModel->rowCount() ) - { - d->blocks.clear(); - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - // Removal feels a bit more complicated than insertion. Basically we can consider there are - // 3 different cases when going to remove items. (*) represents an item, Items between ([) and - // (]) are the ones which are marked for removal. - // - // - 1st case: - // ... * * * * * * [ * * * ... - // - // The items marked for removal are the last part of this category. No need to mark any item - // of this category as in quarantine, because no special offset will be pushed to items at - // the right because of any changes (since the removed items are those on the right most part - // of the category). - // - // - 2nd case: - // ... * * * * * * ] * * * ... - // - // The items marked for removal are the first part of this category. We have to mark as in - // quarantine all items in this category. Absolutely all. All items will have to be moved to - // the left (or moving up, because rows got a different offset). - // - // - 3rd case: - // ... * * [ * * * * ] * * ... - // - // The items marked for removal are in between of this category. We have to mark as in - // quarantine only those items that are at the right of the end of the removal interval, - // (starting on "]"). - // - // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are - // located under the top most affected category as in quarantine (the block itself, as a whole), - // because such a change can force it to have a different offset (note that items themselves - // contain relative positions to the block, so marking the block as in quarantine is enough). - // - // Also note that removal implicitly means that we have to update correctly firstIndex of each - // block, and in general keep updated the internal information of elements. - - QStringList listOfCategoriesMarkedForRemoval; - - QString lastCategory; - int alreadyRemoved = 0; - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = d->categoryForIndex ( index ); - - if ( lastCategory != category ) - { - lastCategory = category; - alreadyRemoved = 0; - } - - Private::Block &block = d->blocks[category]; - block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved ); - ++alreadyRemoved; - - if ( !block.items.count() ) - { - listOfCategoriesMarkedForRemoval << category; - } - - block.height = -1; - - viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent ); - const QString category = d->categoryForIndex ( lastIndex ); - Private::Block &block = d->blocks[category]; - if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() ) - { - block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent ); - } - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval ) - { - d->blocks.remove ( category ); - } - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = d->blocks.values(); - qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan ); - QList firstIndexesRows; - foreach ( const Private::Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > start ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == start ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones - - QListView::rowsAboutToBeRemoved ( parent, start, end ); -} - -void KCategorizedView::updateGeometries() -{ - const int oldVerticalOffset = verticalOffset(); - const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy(); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - /* - * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not - * and triggers a (sometimes additionally timered) resize through ::layoutChildren() - * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499 - * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy) - * - * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI - * So we prevent QListView from having an own opinion on the scrollbar visibility by - * fixing it before calling the baseclass QListView::updateGeometries() - * - * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded - * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself. - */ - if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized - { - setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - } - //END bugs 213068, 287847 -------------------------------------------------------------- - - QListView::updateGeometries(); - - if ( !d->isCategorized() ) - { - return; - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - verticalScrollBar()->setRange ( 0, 0 ); - // unconditional, see function end todo - horizontalScrollBar()->setRange ( 0, 0 ); - return; - } - - const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() ); - Q_ASSERT ( lastIndex.isValid() ); - QRect lastItemRect = visualRect ( lastIndex ); - - if ( d->hasGrid() ) - { - lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) ); - } - else - { - if ( uniformItemSizes() ) - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - itemSize.setHeight ( itemSize.height() + spacing() ); - lastItemRect.setSize ( itemSize ); - } - else - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - const QString category = d->categoryForIndex ( lastIndex ); - itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() ); - lastItemRect.setSize ( itemSize ); - } - } - - const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); - - if ( verticalScrollMode() == ScrollPerItem ) - { - verticalScrollBar()->setSingleStep ( lastItemRect.height() ); - const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 ); - verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() ); - } - - verticalScrollBar()->setRange ( 0, bottomRange ); - verticalScrollBar()->setValue ( oldVerticalOffset ); - - //TODO: also consider working with the horizontal scroll bar. since at this level I am not still - // supporting "top to bottom" flow, there is no real problem. If I support that someday - // (think how to draw categories), we would have to take care of the horizontal scroll bar too. - // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. - horizontalScrollBar()->setRange ( 0, 0 ); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - // restoring values from above ... - setVerticalScrollBarPolicy ( verticalP ); - setHorizontalScrollBarPolicy ( horizontalP ); - // ... and correct the visibility - bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum(); - if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) ) - verticalScrollBar()->setVisible ( validRange ); - validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum(); - if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) ) - horizontalScrollBar()->setVisible ( validRange ); - //END bugs 213068, 287847 -------------------------------------------------------------- -} - -void KCategorizedView::currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ) -{ - QListView::currentChanged ( current, previous ); -} - -void KCategorizedView::dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ) -{ - QListView::dataChanged ( topLeft, bottomRight ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - //BEGIN: since the model changed data, we need to reconsider item sizes - int i = topLeft.row(); - int indexToCheck = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block; - while ( i <= bottomRight.row() ) - { - const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - if ( i == indexToCheck ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - block->quarantineStart = currIndex; - indexToCheck = block->firstIndex.row() + block->items.count(); - } - visualRect ( currIndex ); - ++i; - } - //END: since the model changed data, we need to reconsider item sizes -} - -void KCategorizedView::rowsInserted ( const QModelIndex &parent, - int start, - int end ) -{ - QListView::rowsInserted ( parent, start, end ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - d->rowsInserted ( parent, start, end ); -} - -void KCategorizedView::slotLayoutChanged() -{ - if ( !d->isCategorized() ) - { - return; - } - - d->blocks.clear(); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - if ( d->proxyModel->rowCount() ) - { - d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 ); - } -} -//END: Public part - -void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx ) -{ - d->_k_slotCollapseOrExpandClicked ( idx ); -} - - -#include "categorizedview.moc" diff --git a/depends/groupview/src/categorizedview_p.h b/depends/groupview/src/categorizedview_p.h deleted file mode 100644 index 524bba3a..00000000 --- a/depends/groupview/src/categorizedview_p.h +++ /dev/null @@ -1,160 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDVIEW_P_H -#define KCATEGORIZEDVIEW_P_H - -class KCategorizedSortFilterProxyModel; -class KCategoryDrawer; -class KCategoryDrawerV2; -class KCategoryDrawerV3; - -/** - * @internal - */ -class KCategorizedView::Private -{ -public: - struct Block; - struct Item; - - Private(KCategorizedView *q); - ~Private(); - - /** - * @return whether this view has all required elements to be categorized. - */ - bool isCategorized() const; - - /** - * @return the block rect for the representative @p representative. - */ - QStyleOptionViewItemV4 blockRect(const QModelIndex &representative); - - /** - * Returns the first and last element that intersects with rect. - * - * @note see that here we cannot take out items between first and last (as we could - * do with the rubberband). - * - * Complexity: O(log(n)) where n is model()->rowCount(). - */ - QPair intersectingIndexesWithRect(const QRect &rect) const; - - /** - * Returns the position of the block of @p category. - * - * Complexity: O(n) where n is the number of different categories when the block has been - * marked as in quarantine. O(1) the rest of the times (the vast majority). - */ - QPoint blockPosition(const QString &category); - - /** - * Returns the height of the block determined by @p category. - */ - int blockHeight(const QString &category); - - /** - * Returns the actual viewport width. - */ - int viewportWidth() const; - - /** - * Marks all elements as in quarantine. - * - * Complexity: O(n) where n is model()->rowCount(). - * - * @warning this is an expensive operation - */ - void regenerateAllElements(); - - /** - * Update internal information, and keep sync with the real information that the model contains. - */ - void rowsInserted(const QModelIndex &parent, int start, int end); - - /** - * Returns @p rect in viewport terms, taking in count horizontal and vertical offsets. - */ - QRect mapToViewport(const QRect &rect) const; - - /** - * Returns @p rect in absolute terms, converted from viewport position. - */ - QRect mapFromViewport(const QRect &rect) const; - - /** - * Returns the height of the highest element in last row. This is only applicable if there is - * no grid set and uniformItemSizes is false. - * - * @param block in which block are we searching. Necessary to stop the search if we hit the - * first item in this block. - */ - int highestElementInLastRow(const Block &block) const; - - /** - * Returns whether the view has a valid grid size. - */ - bool hasGrid() const; - - /** - * Returns the category for the given index. - */ - QString categoryForIndex(const QModelIndex &index) const; - - /** - * Updates the visual rect for item when flow is LeftToRight. - */ - void leftToRightVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Updates the visual rect for item when flow is TopToBottom. - * @note we only support viewMode == ListMode in this case. - */ - void topToBottomVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Called when expand or collapse has been clicked on the category drawer. - */ - void _k_slotCollapseOrExpandClicked(QModelIndex); - - KCategorizedView *q = nullptr; - KCategorizedSortFilterProxyModel *proxyModel = nullptr; - KCategoryDrawer *categoryDrawer = nullptr; - int categorySpacing = 5; - bool alternatingBlockColors = false; - bool collapsibleBlocks = false; - bool constantItemWidth = false; - - // FIXME: this is some really weird logic. Investigate. - Block *hoveredBlock; - QString hoveredCategory; - QModelIndex hoveredIndex; - - QPoint pressedPosition; - QRect rubberBandRect; - - QHash blocks; -}; - -#endif // KCATEGORIZEDVIEW_P_H - diff --git a/depends/groupview/src/categorydrawer.cpp b/depends/groupview/src/categorydrawer.cpp deleted file mode 100644 index 214ce3b2..00000000 --- a/depends/groupview/src/categorydrawer.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "categorydrawer.h" - -#include -#include -#include - -#include -#include - -#define HORIZONTAL_HINT 3 - -class KCategoryDrawer::Private -{ -public: - Private(KCategorizedView *view) - : view(view) - , leftMargin(0) - , rightMargin(0) - { - } - - ~Private() - { - } - KCategorizedView *view; - int leftMargin; - int rightMargin; -}; - -KCategoryDrawer::KCategoryDrawer(KCategorizedView *view) - : QObject(view) - , d(new Private(view)) -{ - setLeftMargin(2); - setRightMargin(2); -} - -KCategoryDrawer::~KCategoryDrawer() -{ - delete d; -} - - -void KCategoryDrawer::drawCategory(const QModelIndex &index, - int /*sortRole*/, - const QStyleOption &option, - QPainter *painter) const -{ - painter->setRenderHint(QPainter::Antialiasing); - - const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); - painter->restore(); - } - //END: text -} - -int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const -{ - Q_UNUSED(index); - Q_UNUSED(option) - - QFont font(QApplication::font()); - font.setBold(true); - QFontMetrics fontMetrics(font); - - const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ - + 11 /* top and bottom separation */; - return height; -} - -int KCategoryDrawer::leftMargin() const -{ - return d->leftMargin; -} - -void KCategoryDrawer::setLeftMargin(int leftMargin) -{ - d->leftMargin = leftMargin; -} - -int KCategoryDrawer::rightMargin() const -{ - return d->rightMargin; -} - -void KCategoryDrawer::setRightMargin(int rightMargin) -{ - d->rightMargin = rightMargin; -} - -KCategoryDrawer &KCategoryDrawer::operator=(const KCategoryDrawer &cd) -{ - d->leftMargin = cd.d->leftMargin; - d->rightMargin = cd.d->rightMargin; - d->view = cd.d->view; - return *this; -} - -KCategorizedView *KCategoryDrawer::view() const -{ - return d->view; -} - -void KCategoryDrawer::mouseButtonPressed(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonReleased(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseMoved(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseLeft(const QModelIndex&, const QRect&) -{ -} - -#include "categorydrawer.moc" -- cgit From 7839c4ecc01aad4c5d34a6d06b2dfc4afdc58458 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 01:40:51 +0100 Subject: Pave. --- CMakeLists.txt | 19 +- depends/groupview/.clang-format | 24 - depends/groupview/.gitignore | 2 - depends/groupview/CMakeLists.txt | 40 -- depends/groupview/Group.cpp | 169 ------ depends/groupview/Group.h | 67 --- depends/groupview/GroupView.cpp | 908 -------------------------------- depends/groupview/GroupView.h | 139 ----- depends/groupview/GroupedProxyModel.cpp | 21 - depends/groupview/GroupedProxyModel.h | 14 - depends/groupview/InstanceDelegate.cpp | 281 ---------- depends/groupview/InstanceDelegate.h | 29 - depends/groupview/main.cpp | 98 ---- depends/groupview/main.h | 54 -- gui/MainWindow.cpp | 30 +- gui/MainWindow.h | 6 +- gui/dialogs/IconPickerDialog.cpp | 2 +- gui/groupview/Group.cpp | 169 ++++++ gui/groupview/Group.h | 67 +++ gui/groupview/GroupView.cpp | 908 ++++++++++++++++++++++++++++++++ gui/groupview/GroupView.h | 139 +++++ gui/groupview/GroupedProxyModel.cpp | 26 + gui/groupview/GroupedProxyModel.h | 15 + gui/groupview/InstanceDelegate.cpp | 281 ++++++++++ gui/groupview/InstanceDelegate.h | 29 + gui/widgets/InstanceDelegate.cpp | 231 -------- gui/widgets/InstanceDelegate.h | 27 - logic/lists/InstanceList.cpp | 8 +- logic/lists/InstanceList.h | 4 +- 29 files changed, 1667 insertions(+), 2140 deletions(-) delete mode 100644 depends/groupview/.clang-format delete mode 100644 depends/groupview/.gitignore delete mode 100644 depends/groupview/CMakeLists.txt delete mode 100644 depends/groupview/Group.cpp delete mode 100644 depends/groupview/Group.h delete mode 100644 depends/groupview/GroupView.cpp delete mode 100644 depends/groupview/GroupView.h delete mode 100644 depends/groupview/GroupedProxyModel.cpp delete mode 100644 depends/groupview/GroupedProxyModel.h delete mode 100644 depends/groupview/InstanceDelegate.cpp delete mode 100644 depends/groupview/InstanceDelegate.h delete mode 100644 depends/groupview/main.cpp delete mode 100644 depends/groupview/main.h create mode 100644 gui/groupview/Group.cpp create mode 100644 gui/groupview/Group.h create mode 100644 gui/groupview/GroupView.cpp create mode 100644 gui/groupview/GroupView.h create mode 100644 gui/groupview/GroupedProxyModel.cpp create mode 100644 gui/groupview/GroupedProxyModel.h create mode 100644 gui/groupview/InstanceDelegate.cpp create mode 100644 gui/groupview/InstanceDelegate.h delete mode 100644 gui/widgets/InstanceDelegate.cpp delete mode 100644 gui/widgets/InstanceDelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c34f363f..a8adfec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,6 @@ configure_file("${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/includ ADD_DEFINITIONS(-DQUAZIP_STATIC) ADD_DEFINITIONS(-DLIBSETTINGS_STATIC) ADD_DEFINITIONS(-DLIBUTIL_STATIC) -ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) ######## Packaging/install paths setup ######## @@ -247,10 +246,6 @@ include_directories(${LIBUTIL_INCLUDE_DIR}) add_subdirectory(depends/settings) include_directories(${LIBSETTINGS_INCLUDE_DIR}) -# Add the group view library. -add_subdirectory(depends/groupview) -include_directories(${LIBGROUPVIEW_INCLUDE_DIR}) - # Add the updater add_subdirectory(mmc_updater) @@ -317,8 +312,6 @@ gui/dialogs/UpdateDialog.cpp # GUI - widgets gui/widgets/Common.h gui/widgets/Common.cpp -gui/widgets/InstanceDelegate.h -gui/widgets/InstanceDelegate.cpp gui/widgets/ModListView.h gui/widgets/ModListView.cpp gui/widgets/VersionListView.h @@ -328,6 +321,16 @@ gui/widgets/LabeledToolButton.cpp gui/widgets/MCModInfoFrame.h gui/widgets/MCModInfoFrame.cpp +# GUI - instance group view +gui/groupview/Group.cpp +gui/groupview/Group.h +gui/groupview/GroupedProxyModel.cpp +gui/groupview/GroupedProxyModel.h +gui/groupview/GroupView.cpp +gui/groupview/GroupView.h +gui/groupview/InstanceDelegate.cpp +gui/groupview/InstanceDelegate.h + # Base classes and infrastructure logic/BaseVersion.h logic/MinecraftVersion.h @@ -584,7 +587,7 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS}) # Link TARGET_LINK_LIBRARIES(MultiMC MultiMC_common) -TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) +TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings ${MultiMC_LINK_ADDITIONAL_LIBS}) QT5_USE_MODULES(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES}) QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES}) diff --git a/depends/groupview/.clang-format b/depends/groupview/.clang-format deleted file mode 100644 index 167a8fa7..00000000 --- a/depends/groupview/.clang-format +++ /dev/null @@ -1,24 +0,0 @@ -UseTab: true -IndentWidth: 4 -TabWidth: 4 -ConstructorInitializerIndentWidth: 4 -AccessModifierOffset: -4 -IndentCaseLabels: false -IndentFunctionDeclarationAfterType: false -NamespaceIndentation: None - -BreakBeforeBraces: Allman -AllowShortIfStatementsOnASingleLine: false -ColumnLimit: 96 -MaxEmptyLinesToKeep: 1 - -Standard: Cpp11 -Cpp11BracedListStyle: true - -SpacesInParentheses: false -SpaceInEmptyParentheses: false -SpacesInCStyleCastParentheses: false -SpaceAfterControlStatementKeyword: true - -AlignTrailingComments: true -SpacesBeforeTrailingComments: 1 diff --git a/depends/groupview/.gitignore b/depends/groupview/.gitignore deleted file mode 100644 index a5d18fa3..00000000 --- a/depends/groupview/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -*.user* diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt deleted file mode 100644 index e2a85950..00000000 --- a/depends/groupview/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 2.8.9) - -project(GroupView) - -set(CMAKE_AUTOMOC ON) - -IF(APPLE) - message(STATUS "Using APPLE CMAKE_CXX_FLAGS") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") -ELSEIF(UNIX) - # assume GCC, add C++0x/C++11 stuff - MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") -ELSEIF(MINGW) - MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall") -ENDIF() - -find_package(Qt5Core REQUIRED) -find_package(Qt5Gui REQUIRED) -find_package(Qt5Widgets REQUIRED) - -include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) - -set(SOURCES - main.cpp - main.h - - GroupView.h - GroupView.cpp - Group.h - Group.cpp - GroupedProxyModel.h - GroupedProxyModel.cpp - InstanceDelegate.h - InstanceDelegate.cpp -) - -add_executable(GroupView ${SOURCES}) -qt5_use_modules(GroupView Core Gui Widgets) diff --git a/depends/groupview/Group.cpp b/depends/groupview/Group.cpp deleted file mode 100644 index f216cc6e..00000000 --- a/depends/groupview/Group.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "Group.h" - -#include -#include -#include - -#include "GroupView.h" - -Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) -{ -} - -Group::Group(const Group *other) - : view(other->view), text(other->text), collapsed(other->collapsed) -{ -} - -void Group::update() -{ - firstItemIndex = firstItem().row(); - - rowHeights = QVector(numRows()); - for (int i = 0; i < numRows(); ++i) - { - rowHeights[i] = view->categoryRowHeight( - view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); - } -} - -Group::HitResults Group::hitScan(const QPoint &pos) const -{ - Group::HitResults results = Group::NoHit; - int y_start = verticalPosition(); - int body_start = y_start + headerHeight(); - int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? - int y = pos.y(); - // int x = pos.x(); - if(y < y_start) - { - results = Group::NoHit; - } - else if(y < body_start) - { - results = Group::HeaderHit; - int collapseSize = headerHeight() - 4; - - // the icon - QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); - if(iconRect.contains(pos)) - { - results |= Group::CheckboxHit; - } - } - else if (y < body_end) - { - results |= Group::BodyHit; - } - return results; -} - -void Group::drawHeader(QPainter *painter, const int y) -{ - painter->save(); - - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); - painter->setPen(QPen(Qt::black, 1)); - painter->drawRect(iconRect); - static const int margin = 2; - QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); - int midX = iconSubrect.center().x(); - int midY = iconSubrect.center().y(); - if (collapsed) - { - painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); - } - painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); - - // the text - int textWidth = painter->fontMetrics().width(text); - QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - painter->setBrush(view->viewOptions().palette.text()); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, - view->viewport()->palette(), true, text); - - // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, - view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); - - painter->restore(); -} - -int Group::totalHeight() const -{ - return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? -} - -int Group::headerHeight() const -{ - return view->viewport()->fontMetrics().height() + 4; -} - -int Group::contentHeight() const -{ - if (collapsed) - { - return 0; - } - int result = 0; - for (int i = 0; i < rowHeights.size(); ++i) - { - result += rowHeights[i]; - } - return result; -} - -int Group::numRows() const -{ - return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); -} - -int Group::verticalPosition() const -{ - int res = 0; - const QList cats = view->m_groups; - for (int i = 0; i < cats.size(); ++i) - { - if (cats.at(i) == this) - { - break; - } - res += cats.at(i)->totalHeight() + view->m_categoryMargin; - } - return res; -} - -QList Group::items() const -{ - QList indices; - for (int i = 0; i < view->model()->rowCount(); ++i) - { - const QModelIndex index = view->model()->index(i, 0); - if (index.data(GroupViewRoles::GroupRole).toString() == text) - { - indices.append(index); - } - } - return indices; -} - -int Group::numItems() const -{ - return items().size(); -} - -QModelIndex Group::firstItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.first(); -} - -QModelIndex Group::lastItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.last(); -} diff --git a/depends/groupview/Group.h b/depends/groupview/Group.h deleted file mode 100644 index 455ee1a8..00000000 --- a/depends/groupview/Group.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include - -class GroupView; -class QPainter; -class QModelIndex; - -struct Group -{ -/* constructors */ - Group(const QString &text, GroupView *view); - Group(const Group *other); - -/* data */ - GroupView *view; - QString text; - bool collapsed; - QVector rowHeights; - int firstItemIndex; - -/* logic */ - /// do stuff. and things. TODO: redo. - void update(); - - /// draw the header at y-position. - void drawHeader(QPainter *painter, const int y); - - /// height of the group, in total. includes a small bit of padding. - int totalHeight() const; - - /// height of the group header, in pixels - int headerHeight() const; - - /// height of the group content, in pixels - int contentHeight() const; - - /// the number of visual rows this group has - int numRows() const; - - /// the height at which this group starts, in pixels - int verticalPosition() const; - - enum HitResult - { - NoHit = 0x0, - TextHit = 0x1, - CheckboxHit = 0x2, - HeaderHit = 0x4, - BodyHit = 0x8 - }; - Q_DECLARE_FLAGS(HitResults, HitResult) - - /// shoot! BANG! what did we hit? - HitResults hitScan (const QPoint &pos) const; - - /// super derpy thing. - QList items() const; - /// I don't even - int numItems() const; - QModelIndex firstItem() const; - QModelIndex lastItem() const; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) diff --git a/depends/groupview/GroupView.cpp b/depends/groupview/GroupView.cpp deleted file mode 100644 index 89e3e223..00000000 --- a/depends/groupview/GroupView.cpp +++ /dev/null @@ -1,908 +0,0 @@ -#include "GroupView.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Group.h" - -template bool listsIntersect(const QList &l1, const QList t2) -{ - for (auto &item : l1) - { - if (t2.contains(item)) - { - return true; - } - } - return false; -} - -GroupView::GroupView(QWidget *parent) - : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), - m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) -{ - // setViewMode(IconMode); - // setMovement(Snap); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - // setWordWrap(true); - // setDragDropMode(QListView::InternalMove); - setAcceptDrops(true); - m_spacing = 5; -} - -GroupView::~GroupView() -{ - qDeleteAll(m_groups); - m_groups.clear(); -} - -void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) -{ - if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) - { - updateGeometries(); - } - viewport()->update(); -} -void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) -{ - updateGeometries(); - viewport()->update(); -} - -void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) -{ - updateGeometries(); - viewport()->update(); -} - -void GroupView::updateGeometries() -{ - int previousScroll = verticalScrollBar()->value(); - - QMap cats; - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QString groupName = - model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); - if (!cats.contains(groupName)) - { - Group *old = this->category(groupName); - if (old) - { - cats.insert(groupName, new Group(old)); - } - else - { - cats.insert(groupName, new Group(groupName, this)); - } - } - } - - /*if (m_editedCategory) - { - m_editedCategory = cats[m_editedCategory->text]; - }*/ - - qDeleteAll(m_groups); - m_groups = cats.values(); - - for (auto cat : m_groups) - { - cat->update(); - } - - if (m_groups.isEmpty()) - { - verticalScrollBar()->setRange(0, 0); - } - else - { - int totalHeight = 0; - for (auto category : m_groups) - { - totalHeight += category->totalHeight() + m_categoryMargin; - } - // remove the last margin (we don't want it) - totalHeight -= m_categoryMargin; - totalHeight += m_bottomMargin; - verticalScrollBar()->setRange(0, totalHeight - height()); - } - - verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); - - viewport()->update(); -} - -bool GroupView::isIndexHidden(const QModelIndex &index) const -{ - Group *cat = category(index); - if (cat) - { - return cat->collapsed; - } - else - { - return false; - } -} - -Group *GroupView::category(const QModelIndex &index) const -{ - return category(index.data(GroupViewRoles::GroupRole).toString()); -} - -Group *GroupView::category(const QString &cat) const -{ - for (auto group : m_groups) - { - if (group->text == cat) - { - return group; - } - } - return nullptr; -} - -Group *GroupView::categoryAt(const QPoint &pos) const -{ - for (auto group : m_groups) - { - if(group->hitScan(pos) & Group::CheckboxHit) - { - return group; - } - } - return nullptr; -} - -int GroupView::itemsPerRow() const -{ - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); -} - -int GroupView::contentWidth() const -{ - return width() - m_leftMargin - m_rightMargin; -} - -int GroupView::itemWidth() const -{ - return itemDelegate() - ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0)) - .width(); -} - -int GroupView::categoryRowHeight(const QModelIndex &index) const -{ - QModelIndexList indices; - int internalRow = categoryInternalPosition(index).second; - for (auto &i : category(index)->items()) - { - if (categoryInternalPosition(i).second == internalRow) - { - indices.append(i); - } - } - - int largestHeight = 0; - for (auto &i : indices) - { - largestHeight = - qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); - } - return largestHeight; -} - -QPair GroupView::categoryInternalPosition(const QModelIndex &index) const -{ - QList indices = category(index)->items(); - int x = 0; - int y = 0; - const int perRow = itemsPerRow(); - for (int i = 0; i < indices.size(); ++i) - { - if (indices.at(i) == index) - { - break; - } - ++x; - if (x == perRow) - { - x = 0; - ++y; - } - } - return qMakePair(x, y); -} - -int GroupView::categoryInternalRowTop(const QModelIndex &index) const -{ - Group *cat = category(index); - int categoryInternalRow = categoryInternalPosition(index).second; - int result = 0; - for (int i = 0; i < categoryInternalRow; ++i) - { - result += cat->rowHeights.at(i); - } - return result; -} - -int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const -{ - for (auto &i : category->items()) - { - QPair pos = categoryInternalPosition(i); - if (pos.second == internalRow) - { - return categoryRowHeight(i); - } - } - return -1; -} - -void GroupView::mousePressEvent(QMouseEvent *event) -{ - // endCategoryEditor(); - - QPoint pos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(pos); - - m_pressedIndex = index; - m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); - m_pressedPosition = pos; - - m_pressedCategory = categoryAt(m_pressedPosition); - if (m_pressedCategory) - { - setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); - event->accept(); - return; - } - - if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) - { - // we disable scrollTo for mouse press so the item doesn't change position - // when the user is interacting with it (ie. clicking on it) - bool autoScroll = hasAutoScroll(); - setAutoScroll(false); - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - setAutoScroll(autoScroll); - QRect rect(m_pressedPosition, pos); - setSelection(rect, QItemSelectionModel::ClearAndSelect); - - // signal handlers may change the model - emit pressed(index); - } - else - { - // Forces a finalize() even if mouse is pressed, but not on a item - selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); - } -} - -void GroupView::mouseMoveEvent(QMouseEvent *event) -{ - QPoint topLeft; - QPoint pos = event->pos() + offset(); - - if (state() == ExpandingState || state() == CollapsingState) - { - return; - } - - if (state() == DraggingState) - { - topLeft = m_pressedPosition - offset(); - if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) - { - m_pressedIndex = QModelIndex(); - startDrag(model()->supportedDragActions()); - setState(NoState); - stopAutoScroll(); - } - return; - } - - if (selectionMode() != SingleSelection) - { - topLeft = m_pressedPosition - offset(); - } - else - { - topLeft = pos; - } - - if (m_pressedIndex.isValid() && (state() != DragSelectingState) && - (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) - { - setState(DraggingState); - return; - } - - if ((event->buttons() & Qt::LeftButton) && selectionModel()) - { - setState(DragSelectingState); - - setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect); - QModelIndex index = indexAt(pos); - - // set at the end because it might scroll the view - if (index.isValid() && (index != selectionModel()->currentIndex()) && - (index.flags() & Qt::ItemIsEnabled)) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - } -} - -void GroupView::mouseReleaseEvent(QMouseEvent *event) -{ - QPoint pos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(pos); - - bool click = (index == m_pressedIndex && index.isValid()) || - (m_pressedCategory && m_pressedCategory == categoryAt(pos)); - - if (click && m_pressedCategory) - { - if (state() == ExpandingState) - { - m_pressedCategory->collapsed = false; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - else if (state() == CollapsingState) - { - m_pressedCategory->collapsed = true; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } - - m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; - - setState(NoState); - - if (click) - { - if (event->button() == Qt::LeftButton) - { - emit clicked(index); - } - QStyleOptionViewItem option = viewOptions(); - if (m_pressedAlreadySelected) - { - option.state |= QStyle::State_Selected; - } - if ((model()->flags(index) & Qt::ItemIsEnabled) && - style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) - { - emit activated(index); - } - } -} - -void GroupView::mouseDoubleClickEvent(QMouseEvent *event) -{ - QModelIndex index = indexAt(event->pos()); - if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) - { - QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), - event->screenPos(), event->button(), event->buttons(), - event->modifiers()); - mousePressEvent(&me); - return; - } - // signal handlers may change the model - QPersistentModelIndex persistent = index; - emit doubleClicked(persistent); -} - -void GroupView::paintEvent(QPaintEvent *event) -{ - QPainter painter(this->viewport()); - - int y = -verticalOffset(); - for (int i = 0; i < m_groups.size(); ++i) - { - Group *category = m_groups.at(i); - category->drawHeader(&painter, y); - y += category->totalHeight() + m_categoryMargin; - } - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QModelIndex index = model()->index(i, 0); - if (isIndexHidden(index)) - { - continue; - } - Qt::ItemFlags flags = index.flags(); - QStyleOptionViewItemV4 option(viewOptions()); - option.rect = visualRect(index); - option.widget = this; - option.features |= - QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? - if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) - { - option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected - : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; - if (!(flags & Qt::ItemIsEnabled)) - { - option.state &= ~QStyle::State_Enabled; - } - itemDelegate()->paint(&painter, option, index); - } - - /* - * Drop indicators for manual reordering... - */ -#if 0 - if (!m_lastDragPosition.isNull()) - { - QPair pair = rowDropPos(m_lastDragPosition); - Group *category = pair.first; - int row = pair.second; - if (category) - { - int internalRow = row - category->firstItemIndex; - QLine line; - if (internalRow >= category->numItems()) - { - QRect toTheRightOfRect = visualRect(category->lastItem()); - line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); - } - else - { - QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); - line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); - } - painter.save(); - painter.setPen(QPen(Qt::black, 3)); - painter.drawLine(line); - painter.restore(); - } - } -#endif -} - -void GroupView::resizeEvent(QResizeEvent *event) -{ - // QListView::resizeEvent(event); - - // if (m_categoryEditor) - // { - // m_categoryEditor->resize(qMax(contentWidth() / 2, - // m_editedCategory->textRect.width()), - // m_categoryEditor->height()); - // } - - updateGeometries(); -} - -void GroupView::dragEnterEvent(QDragEnterEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} - -void GroupView::dragMoveEvent(QDragMoveEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} - -void GroupView::dragLeaveEvent(QDragLeaveEvent *event) -{ - m_lastDragPosition = QPoint(); - viewport()->update(); -} - -void GroupView::dropEvent(QDropEvent *event) -{ - m_lastDragPosition = QPoint(); - - stopAutoScroll(); - setState(NoState); - - if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) - { - return; - } - - QPair dropPos = rowDropPos(event->pos() + offset()); - const Group *category = dropPos.first; - const int row = dropPos.second; - - if (row == -1) - { - viewport()->update(); - return; - } - - const QString categoryText = category->text; - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - model()->setData(model()->index(row, 0), categoryText, - GroupViewRoles::GroupRole); - event->setDropAction(Qt::MoveAction); - event->accept(); - } - updateGeometries(); - viewport()->update(); -} - -void GroupView::startDrag(Qt::DropActions supportedActions) -{ - QModelIndexList indexes = selectionModel()->selectedIndexes(); - if (indexes.count() > 0) - { - QMimeData *data = model()->mimeData(indexes); - if (!data) - { - return; - } - QRect rect; - QPixmap pixmap = renderToPixmap(indexes, &rect); - //rect.translate(offset()); - // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); - QDrag *drag = new QDrag(this); - drag->setPixmap(pixmap); - drag->setMimeData(data); - drag->setHotSpot(m_pressedPosition - rect.topLeft()); - Qt::DropAction defaultDropAction = Qt::IgnoreAction; - if (this->defaultDropAction() != Qt::IgnoreAction && - (supportedActions & this->defaultDropAction())) - { - defaultDropAction = this->defaultDropAction(); - } - if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) - { - const QItemSelection selection = selectionModel()->selection(); - - for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) - { - QModelIndex parent = (*it).parent(); - if ((*it).left() != 0) - { - continue; - } - if ((*it).right() != (model()->columnCount(parent) - 1)) - { - continue; - } - int count = (*it).bottom() - (*it).top() + 1; - model()->removeRows((*it).top(), count, parent); - } - } - } -} - -QRect GroupView::visualRect(const QModelIndex &index) const -{ - return geometryRect(index).translated(-offset()); -} - -QRect GroupView::geometryRect(const QModelIndex &index) const -{ - if (!index.isValid() || isIndexHidden(index) || index.column() > 0) - { - return QRect(); - } - - const Group *cat = category(index); - QPair pos = categoryInternalPosition(index); - int x = pos.first; - // int y = pos.second; - - QRect out; - out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); - out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); - out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - - return out; -} - -/* -void CategorizedView::startCategoryEditor(Category *category) -{ - if (m_categoryEditor != 0) - { - return; - } - m_editedCategory = category; - m_categoryEditor = new QLineEdit(m_editedCategory->text, this); - QRect rect = m_editedCategory->textRect; - rect.setWidth(qMax(contentWidth() / 2, rect.width())); - m_categoryEditor->setGeometry(rect); - m_categoryEditor->show(); - m_categoryEditor->setFocus(); - connect(m_categoryEditor, &QLineEdit::returnPressed, this, -&CategorizedView::endCategoryEditor); -} - -void CategorizedView::endCategoryEditor() -{ - if (m_categoryEditor == 0) - { - return; - } - m_editedCategory->text = m_categoryEditor->text(); - m_updatesDisabled = true; - foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) - { - const_cast(index.model())->setData(index, -m_categoryEditor->text(), CategoryRole); - } - m_updatesDisabled = false; - delete m_categoryEditor; - m_categoryEditor = 0; - m_editedCategory = 0; - updateGeometries(); -} -*/ - -QModelIndex GroupView::indexAt(const QPoint &point) const -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (geometryRect(index).contains(point)) - { - return index; - } - } - return QModelIndex(); -} - -void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - QRect itemRect = geometryRect(index); - if (itemRect.intersects(rect)) - { - selectionModel()->select(index, commands); - update(itemRect.translated(-offset())); - } - } - -} - -QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const -{ - Q_ASSERT(r); - auto paintPairs = draggablePaintPairs(indices, r); - if (paintPairs.isEmpty()) - { - return QPixmap(); - } - QPixmap pixmap(r->size()); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - QStyleOptionViewItem option = viewOptions(); - option.state |= QStyle::State_Selected; - for (int j = 0; j < paintPairs.count(); ++j) - { - option.rect = paintPairs.at(j).first.translated(-r->topLeft()); - const QModelIndex ¤t = paintPairs.at(j).second; - itemDelegate()->paint(&painter, option, current); - } - return pixmap; -} - -QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - QList> ret; - for (int i = 0; i < indices.count(); ++i) - { - const QModelIndex &index = indices.at(i); - const QRect current = geometryRect(index); - ret += qMakePair(current, index); - rect |= current; - } - return ret; -} - -bool GroupView::isDragEventAccepted(QDropEvent *event) -{ - if (event->source() != this) - { - return false; - } - if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) - { - return false; - } - if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), - rowDropPos(event->pos()).second, 0, QModelIndex())) - { - return false; - } - return true; -} - -QPair GroupView::rowDropPos(const QPoint &pos) -{ - // check that we aren't on a category header and calculate which category we're in - Group *category = 0; - { - int y = 0; - for (auto cat : m_groups) - { - if (pos.y() > y && pos.y() < (y + cat->headerHeight())) - { - return qMakePair(nullptr, -1); - } - y += cat->totalHeight() + m_categoryMargin; - if (pos.y() < y) - { - category = cat; - break; - } - } - if (category == 0) - { - return qMakePair(nullptr, -1); - } - } - - QList indices = category->items(); - - // calculate the internal column - int internalColumn = -1; - { - const int itemWidth = this->itemWidth(); - if (pos.x() >= (itemWidth * itemsPerRow())) - { - internalColumn = itemsPerRow(); - } - else - { - for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) - { - if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) - { - internalColumn = c; - break; - } - } - } - if (internalColumn == -1) - { - return qMakePair(nullptr, -1); - } - } - - // calculate the internal row - int internalRow = -1; - { - // FIXME rework the drag and drop code - const int top = category->verticalPosition(); - for (int r = 0, h = top; r < category->numRows(); - h += itemHeightForCategoryRow(category, r), ++r) - { - if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) - { - internalRow = r; - break; - } - } - if (internalRow == -1) - { - return qMakePair(nullptr, -1); - } - // this happens if we're in the margin between a one category and another - // categories header - if (internalRow > (indices.size() / itemsPerRow())) - { - return qMakePair(nullptr, -1); - } - } - - // flaten the internalColumn/internalRow to one row - int categoryRow = internalRow * itemsPerRow() + internalColumn; - - // this is used if we're past the last item - if (categoryRow >= indices.size()) - { - return qMakePair(category, indices.last().row() + 1); - } - - return qMakePair(category, indices.at(categoryRow).row()); -} - -QPoint GroupView::offset() const -{ - return QPoint(horizontalOffset(), verticalOffset()); -} - -QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const -{ - QRegion region; - for (auto &range : selection) - { - int start_row = range.top(); - int end_row = range.bottom(); - for (int row = start_row; row <= end_row; ++row) - { - int start_column = range.left(); - int end_column = range.right(); - for (int column = start_column; column <= end_column; ++column) - { - QModelIndex index = model()->index(row, column, rootIndex()); - region += visualRect(index); // OK - } - } - } - return region; -} -QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) -{ - auto current = currentIndex(); - if(!current.isValid()) - { - qDebug() << "model row: invalid"; - return current; - } - qDebug() << "model row: " << current.row(); - auto cat = category(current); - int i = m_groups.indexOf(cat); - if(i >= 0) - { - // this is a pile of something foul - auto real_group = m_groups[i]; - int beginning_row = 0; - for(auto group: m_groups) - { - if(group == real_group) - break; - beginning_row += group->numRows(); - } - qDebug() << "category: " << real_group->text; - QPair pos = categoryInternalPosition(current); - int row = beginning_row + pos.second; - qDebug() << "row: " << row; - qDebug() << "column: " << pos.first; - } - return current; -} diff --git a/depends/groupview/GroupView.h b/depends/groupview/GroupView.h deleted file mode 100644 index 329a3503..00000000 --- a/depends/groupview/GroupView.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once - -#include -#include -#include - -struct GroupViewRoles -{ - enum - { - GroupRole = Qt::UserRole, - ProgressValueRole, - ProgressMaximumRole - }; -}; - -struct Group; - -class GroupView : public QAbstractItemView -{ - Q_OBJECT - -public: - GroupView(QWidget *parent = 0); - ~GroupView(); - - QRect geometryRect(const QModelIndex &index) const; - virtual QRect visualRect(const QModelIndex &index) const override; - QModelIndex indexAt(const QPoint &point) const; - void setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) override; - - virtual int horizontalOffset() const override - { - return horizontalScrollBar()->value(); - } - - virtual int verticalOffset() const override - { - return verticalScrollBar()->value(); - } - - virtual void scrollContentsBy(int dx, int dy) override - { - scrollDirtyRegion(dx, dy); - viewport()->scroll(dx, dy); - } - - /* - * TODO! - */ - virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override - { - return; - } - - virtual QModelIndex moveCursor(CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) override; - - virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - -protected -slots: - virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) override; - virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; - virtual void updateGeometries() override; - -protected: - virtual bool isIndexHidden(const QModelIndex &index) const override; - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - - void dragEnterEvent(QDragEnterEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - void startDrag(Qt::DropActions supportedActions) override; - -private: - friend struct Group; - - QList m_groups; - - int m_leftMargin; - int m_rightMargin; - int m_bottomMargin; - int m_categoryMargin; - - // bool m_updatesDisabled; - - Group *category(const QModelIndex &index) const; - Group *category(const QString &cat) const; - Group *categoryAt(const QPoint &pos) const; - - int itemsPerRow() const; - int contentWidth() const; - -private: - int itemWidth() const; - int categoryRowHeight(const QModelIndex &index) const; - - /*QLineEdit *m_categoryEditor; - Category *m_editedCategory; - void startCategoryEditor(Category *category); - -private slots: - void endCategoryEditor();*/ - -private: /* variables */ - QPoint m_pressedPosition; - QPersistentModelIndex m_pressedIndex; - bool m_pressedAlreadySelected; - Group *m_pressedCategory; - QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; - QPoint m_lastDragPosition; - int m_spacing = 5; - -private: /* methods */ - QPair categoryInternalPosition(const QModelIndex &index) const; - int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Group *category, const int internalRow) const; - - QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList> draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const; - - bool isDragEventAccepted(QDropEvent *event); - - QPair rowDropPos(const QPoint &pos); - - QPoint offset() const; -}; diff --git a/depends/groupview/GroupedProxyModel.cpp b/depends/groupview/GroupedProxyModel.cpp deleted file mode 100644 index 57d7ff5c..00000000 --- a/depends/groupview/GroupedProxyModel.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "GroupedProxyModel.h" - -#include "GroupView.h" - -GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) -{ -} - -bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); - const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); - if (leftCategory == rightCategory) - { - return left.row() < right.row(); - } - else - { - return leftCategory < rightCategory; - } -} diff --git a/depends/groupview/GroupedProxyModel.h b/depends/groupview/GroupedProxyModel.h deleted file mode 100644 index cae87ecd..00000000 --- a/depends/groupview/GroupedProxyModel.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -class GroupedProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - GroupedProxyModel(QObject *parent = 0); - -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; -}; diff --git a/depends/groupview/InstanceDelegate.cpp b/depends/groupview/InstanceDelegate.cpp deleted file mode 100644 index 8527e2bc..00000000 --- a/depends/groupview/InstanceDelegate.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "InstanceDelegate.h" -#include -#include -#include -#include -#include - -#include "GroupView.h" - -// Origin: Qt -static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) -{ - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); -} - -#define QFIXED_MAX (INT_MAX / 256) - -ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) -{ -} - -void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, - const QRect &rect) -{ - if ((option.state & QStyle::State_Selected)) - painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); - else - { - QColor backgroundColor = option.palette.color(QPalette::Background); - backgroundColor.setAlpha(160); - painter->fillRect(rect, QBrush(backgroundColor)); - } -} - -void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) -{ - if (!(option.state & QStyle::State_HasFocus)) - return; - QStyleOptionFocusRect opt; - opt.direction = option.direction; - opt.fontMetrics = option.fontMetrics; - opt.palette = option.palette; - opt.rect = rect; - // opt.state = option.state | QStyle::State_KeyboardFocusChange | - // QStyle::State_Item; - auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; - opt.backgroundColor = option.palette.color(col); - // Apparently some widget styles expect this hint to not be set - painter->setRenderHint(QPainter::Antialiasing, false); - - QStyle *style = option.widget ? option.widget->style() : QApplication::style(); - - style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); - - painter->setRenderHint(QPainter::Antialiasing); -} - -// TODO this can be made a lot prettier -void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, - const int value, const int maximum) -{ - if (maximum == 0 || value == maximum) - { - return; - } - - painter->save(); - - qreal percent = (qreal)value / (qreal)maximum; - QColor color = option.palette.color(QPalette::Dark); - color.setAlphaF(0.70f); - painter->setBrush(color); - painter->setPen(QPen(QBrush(), 0)); - painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); - - painter->restore(); -} - -static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) -{ - QStyle *style = option->widget ? option->widget->style() : QApplication::style(); - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(option->font); - textLayout.setText(option->text); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; - QRect bounds(0, 0, 100 - 2 * textMargin, 600); - qreal height = 0, widthUsed = 0; - viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); - const QSize size(qCeil(widthUsed), qCeil(height)); - return QSize(size.width() + 2 * textMargin, size.height()); -} - -void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - painter->save(); - painter->setClipRect(opt.rect); - - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - - // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); - const int iconSize = 48; - QRect iconbox = opt.rect; - const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; - QRect textRect = opt.rect; - QRect textHighlightRect = textRect; - // clip the decoration on top, remove width padding - textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); - - textHighlightRect.adjust(0, iconSize + 5, 0, 0); - - // draw background - { - // FIXME: unused - // QSize textSize = viewItemTextSize ( &opt ); - QPalette::ColorGroup cg; - QStyleOptionViewItemV4 opt2(opt); - - if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) - { - if (!(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - else - cg = QPalette::Normal; - } - else - { - cg = QPalette::Disabled; - } - opt2.palette.setCurrentColorGroup(cg); - - // fill in background, if any - if (opt.backgroundBrush.style() != Qt::NoBrush) - { - QPointF oldBO = painter->brushOrigin(); - painter->setBrushOrigin(opt.rect.topLeft()); - painter->fillRect(opt.rect, opt.backgroundBrush); - painter->setBrushOrigin(oldBO); - } - - if (opt.showDecorationSelected) - { - drawSelectionRect(painter, opt2, opt.rect); - drawFocusRect(painter, opt2, opt.rect); - // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); - } - else - { - - // if ( opt.state & QStyle::State_Selected ) - { - // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, - // opt.widget ); - // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, - // QPalette::Highlight ) ); - drawSelectionRect(painter, opt2, textHighlightRect); - drawFocusRect(painter, opt2, textHighlightRect); - } - } - } - - // draw the icon - { - QIcon::Mode mode = QIcon::Normal; - if (!(opt.state & QStyle::State_Enabled)) - mode = QIcon::Disabled; - else if (opt.state & QStyle::State_Selected) - mode = QIcon::Selected; - QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; - - iconbox.setHeight(iconSize); - opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - } - // set the text colors - QPalette::ColorGroup cg = - opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - if (opt.state & QStyle::State_Selected) - { - painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); - } - else - { - painter->setPen(opt.palette.color(cg, QPalette::Text)); - } - - // draw the text - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - textOption.setTextDirection(opt.direction); - textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(opt.font); - textLayout.setText(opt.text); - - qreal width, height; - viewItemTextLayout(textLayout, textRect.width(), height, width); - - const int lineCount = textLayout.lineCount(); - - const QRect layoutRect = QStyle::alignedRect( - opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); - const QPointF position = layoutRect.topLeft(); - for (int i = 0; i < lineCount; ++i) - { - const QTextLine line = textLayout.lineAt(i); - line.draw(painter, position); - } - - drawProgressOverlay(painter, opt, - index.data(GroupViewRoles::ProgressValueRole).toInt(), - index.data(GroupViewRoles::ProgressMaximumRole).toInt()); - - painter->restore(); -} - -QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; - int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables - QSize szz = viewItemTextSize(&opt); - height += szz.height(); - // FIXME: maybe the icon items could scale and keep proportions? - QSize sz(100, height); - return sz; -} diff --git a/depends/groupview/InstanceDelegate.h b/depends/groupview/InstanceDelegate.h deleted file mode 100644 index de2f429b..00000000 --- a/depends/groupview/InstanceDelegate.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -class ListViewDelegate : public QStyledItemDelegate -{ -public: - explicit ListViewDelegate(QObject *parent = 0); - -protected: - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; -}; diff --git a/depends/groupview/main.cpp b/depends/groupview/main.cpp deleted file mode 100644 index df14e3bd..00000000 --- a/depends/groupview/main.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "main.h" - -#include -#include -#include -#include - -#include "GroupView.h" -#include "GroupedProxyModel.h" -#include "InstanceDelegate.h" - -Progresser *progresser; - -QPixmap icon(const Qt::GlobalColor color) -{ - QPixmap p = QPixmap(32, 32); - p.fill(QColor(color)); - return p; -} -QPixmap icon(const int number) -{ - QPixmap p = icon(Qt::white); - QPainter painter(&p); - QFont font = painter.font(); - font.setBold(true); - font.setPixelSize(28); - painter.setFont(font); - painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, - QString::number(number)); - painter.end(); - return p; -} -QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, - const QString &category) -{ - QStandardItem *item = new QStandardItem; - item->setText(text); - item->setData(icon(color), Qt::DecorationRole); - item->setData(category, GroupViewRoles::GroupRole); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - // progresser->addTrackedIndex(item); - return item; -} -QStandardItem *createItem(const int index, const QString &category) -{ - QStandardItem *item = new QStandardItem; - item->setText(QString("Item #%1").arg(index)); - item->setData(icon(index), Qt::DecorationRole); - item->setData(category, GroupViewRoles::GroupRole); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - // progresser->addTrackedIndex(item); - return item; -} - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - - qsrand(QTime::currentTime().msec()); - - progresser = new Progresser(); - - QStandardItemModel model; - model.setRowCount(10); - model.setColumnCount(1); - - model.setItem( - 0, createItem(Qt::red, - "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", - "Colorful")); - model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); - model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); - - model.setItem(3, createItem(Qt::black, "Black", "Not Colorful")); - model.setItem(4, createItem(Qt::darkGray, "Dark Gray", "Not Colorful")); - model.setItem(5, createItem(Qt::gray, "Gray", "Not Colorful")); - model.setItem(6, createItem(Qt::lightGray, "Light Gray", "Not Colorful")); - model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); - - model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); - model.setItem(9, progresser->addTrackedIndex(createItem(Qt::green, "Green", ""))); - - for (int i = 0; i < 20; ++i) - { - model.setItem(i + 10, createItem(i + 1, "Items 1-20")); - } - - GroupedProxyModel pModel; - pModel.setSourceModel(&model); - - GroupView w; - w.setItemDelegate(new ListViewDelegate); - w.setModel(&pModel); - w.resize(640, 480); - w.show(); - - return a.exec(); -} diff --git a/depends/groupview/main.h b/depends/groupview/main.h deleted file mode 100644 index 2a358329..00000000 --- a/depends/groupview/main.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "GroupView.h" - -class Progresser : public QObject -{ - Q_OBJECT -public: - explicit Progresser(QObject *parent = 0) : QObject(parent) - { - QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); - timer->start(50); - } - - QStandardItem *addTrackedIndex(QStandardItem *item) - { - item->setData(1000, GroupViewRoles::ProgressMaximumRole); - m_items.append(item); - return item; - } - -public -slots: - void timeout() - { - QList toRemove; - for (auto item : m_items) - { - int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt(); - int value = item->data(GroupViewRoles::ProgressValueRole).toInt(); - int newvalue = std::min(value + 3, maximum); - item->setData(newvalue, GroupViewRoles::ProgressValueRole); - - if(newvalue >= maximum) - { - toRemove.append(item); - } - } - for(auto remove : toRemove) - { - m_items.removeAll(remove); - } - } - -private: - QList m_items; -}; diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 9977dc75..cd8ec926 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -37,12 +38,12 @@ #include "userutils.h" #include "pathutils.h" -#include "categorizedview.h" -#include "categorydrawer.h" +#include "gui/groupview/GroupView.h" +#include "gui/groupview/InstanceDelegate.h" #include "gui/Platform.h" -#include "gui/widgets/InstanceDelegate.h" + #include "gui/widgets/LabeledToolButton.h" #include "gui/dialogs/SettingsDialog.h" @@ -140,21 +141,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // Create the instance list widget { - view = new KCategorizedView(ui->centralWidget); - drawer = new KCategoryDrawer(view); + view = new GroupView(ui->centralWidget); + view->setSelectionMode(QAbstractItemView::SingleSelection); - view->setCategoryDrawer(drawer); - view->setCollapsibleBlocks(true); - view->setViewMode(QListView::IconMode); - view->setFlow(QListView::LeftToRight); - view->setWordWrap(true); - view->setMouseTracking(true); - view->viewport()->setAttribute(Qt::WA_Hover); + // view->setCategoryDrawer(drawer); + // view->setCollapsibleBlocks(true); + // view->setViewMode(QListView::IconMode); + // view->setFlow(QListView::LeftToRight); + // view->setWordWrap(true); + // view->setMouseTracking(true); + // view->viewport()->setAttribute(Qt::WA_Hover); auto delegate = new ListViewDelegate(); view->setItemDelegate(delegate); - view->setSpacing(10); - view->setUniformItemWidths(true); + // view->setSpacing(10); + // view->setUniformItemWidths(true); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); @@ -331,7 +332,6 @@ MainWindow::~MainWindow() { delete ui; delete proxymodel; - delete drawer; } void MainWindow::showInstanceContextMenu(const QPoint &pos) diff --git a/gui/MainWindow.h b/gui/MainWindow.h index eeba2c26..4d9e165d 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -27,9 +27,6 @@ class QToolButton; class LabeledToolButton; class QLabel; -class InstanceProxyModel; -class KCategorizedView; -class KCategoryDrawer; class MinecraftProcess; class ConsoleWindow; @@ -185,8 +182,7 @@ protected: private: Ui::MainWindow *ui; - KCategoryDrawer *drawer; - KCategorizedView *view; + class GroupView *view; InstanceProxyModel *proxymodel; MinecraftProcess *proc; ConsoleWindow *console; diff --git a/gui/dialogs/IconPickerDialog.cpp b/gui/dialogs/IconPickerDialog.cpp index f7970b37..9b1c26ff 100644 --- a/gui/dialogs/IconPickerDialog.cpp +++ b/gui/dialogs/IconPickerDialog.cpp @@ -23,7 +23,7 @@ #include "ui_IconPickerDialog.h" #include "gui/Platform.h" -#include "gui/widgets/InstanceDelegate.h" +#include "gui/groupview/InstanceDelegate.h" #include "logic/icons/IconList.h" diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp new file mode 100644 index 00000000..f216cc6e --- /dev/null +++ b/gui/groupview/Group.cpp @@ -0,0 +1,169 @@ +#include "Group.h" + +#include +#include +#include + +#include "GroupView.h" + +Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) +{ +} + +Group::Group(const Group *other) + : view(other->view), text(other->text), collapsed(other->collapsed) +{ +} + +void Group::update() +{ + firstItemIndex = firstItem().row(); + + rowHeights = QVector(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight( + view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); + } +} + +Group::HitResults Group::hitScan(const QPoint &pos) const +{ + Group::HitResults results = Group::NoHit; + int y_start = verticalPosition(); + int body_start = y_start + headerHeight(); + int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? + int y = pos.y(); + // int x = pos.x(); + if(y < y_start) + { + results = Group::NoHit; + } + else if(y < body_start) + { + results = Group::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if(iconRect.contains(pos)) + { + results |= Group::CheckboxHit; + } + } + else if (y < body_end) + { + results |= Group::BodyHit; + } + return results; +} + +void Group::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); + painter->setPen(QPen(Qt::black, 1)); + painter->drawRect(iconRect); + static const int margin = 2; + QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + int midX = iconSubrect.center().x(); + int midY = iconSubrect.center().y(); + if (collapsed) + { + painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + } + painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); + + // the text + int textWidth = painter->fontMetrics().width(text); + QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->setBrush(view->viewOptions().palette.text()); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, + view->viewport()->palette(), true, text); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, + view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int Group::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? +} + +int Group::headerHeight() const +{ + return view->viewport()->fontMetrics().height() + 4; +} + +int Group::contentHeight() const +{ + if (collapsed) + { + return 0; + } + int result = 0; + for (int i = 0; i < rowHeights.size(); ++i) + { + result += rowHeights[i]; + } + return result; +} + +int Group::numRows() const +{ + return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); +} + +int Group::verticalPosition() const +{ + int res = 0; + const QList cats = view->m_groups; + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == this) + { + break; + } + res += cats.at(i)->totalHeight() + view->m_categoryMargin; + } + return res; +} + +QList Group::items() const +{ + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(GroupViewRoles::GroupRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} + +int Group::numItems() const +{ + return items().size(); +} + +QModelIndex Group::firstItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} + +QModelIndex Group::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h new file mode 100644 index 00000000..455ee1a8 --- /dev/null +++ b/gui/groupview/Group.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +class GroupView; +class QPainter; +class QModelIndex; + +struct Group +{ +/* constructors */ + Group(const QString &text, GroupView *view); + Group(const Group *other); + +/* data */ + GroupView *view; + QString text; + bool collapsed; + QVector rowHeights; + int firstItemIndex; + +/* logic */ + /// do stuff. and things. TODO: redo. + void update(); + + /// draw the header at y-position. + void drawHeader(QPainter *painter, const int y); + + /// height of the group, in total. includes a small bit of padding. + int totalHeight() const; + + /// height of the group header, in pixels + int headerHeight() const; + + /// height of the group content, in pixels + int contentHeight() const; + + /// the number of visual rows this group has + int numRows() const; + + /// the height at which this group starts, in pixels + int verticalPosition() const; + + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) + + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; + + /// super derpy thing. + QList items() const; + /// I don't even + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp new file mode 100644 index 00000000..89e3e223 --- /dev/null +++ b/gui/groupview/GroupView.cpp @@ -0,0 +1,908 @@ +#include "GroupView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Group.h" + +template bool listsIntersect(const QList &l1, const QList t2) +{ + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} + +GroupView::GroupView(QWidget *parent) + : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), + m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) +{ + // setViewMode(IconMode); + // setMovement(Snap); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + // setWordWrap(true); + // setDragDropMode(QListView::InternalMove); + setAcceptDrops(true); + m_spacing = 5; +} + +GroupView::~GroupView() +{ + qDeleteAll(m_groups); + m_groups.clear(); +} + +void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) +{ + if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) + { + updateGeometries(); + } + viewport()->update(); +} +void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + updateGeometries(); + viewport()->update(); +} + +void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + updateGeometries(); + viewport()->update(); +} + +void GroupView::updateGeometries() +{ + int previousScroll = verticalScrollBar()->value(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString groupName = + model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) + { + Group *old = this->category(groupName); + if (old) + { + cats.insert(groupName, new Group(old)); + } + else + { + cats.insert(groupName, new Group(groupName, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_groups); + m_groups = cats.values(); + + for (auto cat : m_groups) + { + cat->update(); + } + + if (m_groups.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + for (auto category : m_groups) + { + totalHeight += category->totalHeight() + m_categoryMargin; + } + // remove the last margin (we don't want it) + totalHeight -= m_categoryMargin; + totalHeight += m_bottomMargin; + verticalScrollBar()->setRange(0, totalHeight - height()); + } + + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + + viewport()->update(); +} + +bool GroupView::isIndexHidden(const QModelIndex &index) const +{ + Group *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } +} + +Group *GroupView::category(const QModelIndex &index) const +{ + return category(index.data(GroupViewRoles::GroupRole).toString()); +} + +Group *GroupView::category(const QString &cat) const +{ + for (auto group : m_groups) + { + if (group->text == cat) + { + return group; + } + } + return nullptr; +} + +Group *GroupView::categoryAt(const QPoint &pos) const +{ + for (auto group : m_groups) + { + if(group->hitScan(pos) & Group::CheckboxHit) + { + return group; + } + } + return nullptr; +} + +int GroupView::itemsPerRow() const +{ + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); +} + +int GroupView::contentWidth() const +{ + return width() - m_leftMargin - m_rightMargin; +} + +int GroupView::itemWidth() const +{ + return itemDelegate() + ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0)) + .width(); +} + +int GroupView::categoryRowHeight(const QModelIndex &index) const +{ + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + for (auto &i : category(index)->items()) + { + if (categoryInternalPosition(i).second == internalRow) + { + indices.append(i); + } + } + + int largestHeight = 0; + for (auto &i : indices) + { + largestHeight = + qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); + } + return largestHeight; +} + +QPair GroupView::categoryInternalPosition(const QModelIndex &index) const +{ + QList indices = category(index)->items(); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) + { + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } + } + return qMakePair(x, y); +} + +int GroupView::categoryInternalRowTop(const QModelIndex &index) const +{ + Group *cat = category(index); + int categoryInternalRow = categoryInternalPosition(index).second; + int result = 0; + for (int i = 0; i < categoryInternalRow; ++i) + { + result += cat->rowHeights.at(i); + } + return result; +} + +int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const +{ + for (auto &i : category->items()) + { + QPair pos = categoryInternalPosition(i); + if (pos.second == internalRow) + { + return categoryRowHeight(i); + } + } + return -1; +} + +void GroupView::mousePressEvent(QMouseEvent *event) +{ + // endCategoryEditor(); + + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); + m_pressedPosition = pos; + + m_pressedCategory = categoryAt(m_pressedPosition); + if (m_pressedCategory) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + setAutoScroll(autoScroll); + QRect rect(m_pressedPosition, pos); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + + // signal handlers may change the model + emit pressed(index); + } + else + { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } +} + +void GroupView::mouseMoveEvent(QMouseEvent *event) +{ + QPoint topLeft; + QPoint pos = event->pos() + offset(); + + if (state() == ExpandingState || state() == CollapsingState) + { + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - offset(); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) + { + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); + } + return; + } + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = pos; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + + setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect); + QModelIndex index = indexAt(pos); + + // set at the end because it might scroll the view + if (index.isValid() && (index != selectionModel()->currentIndex()) && + (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } +} + +void GroupView::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + bool click = (index == m_pressedIndex && index.isValid()) || + (m_pressedCategory && m_pressedCategory == categoryAt(pos)); + + if (click && m_pressedCategory) + { + if (state() == ExpandingState) + { + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); + + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) && + style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } +} + +void GroupView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), + event->screenPos(), event->button(), event->buttons(), + event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); +} + +void GroupView::paintEvent(QPaintEvent *event) +{ + QPainter painter(this->viewport()); + + int y = -verticalOffset(); + for (int i = 0; i < m_groups.size(); ++i) + { + Group *category = m_groups.at(i); + category->drawHeader(&painter, y); + y += category->totalHeight() + m_categoryMargin; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + QStyleOptionViewItemV4 option(viewOptions()); + option.rect = visualRect(index); + option.widget = this; + option.features |= + QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } + + /* + * Drop indicators for manual reordering... + */ +#if 0 + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstItemIndex; + QLine line; + if (internalRow >= category->numItems()) + { + QRect toTheRightOfRect = visualRect(category->lastItem()); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } +#endif +} + +void GroupView::resizeEvent(QResizeEvent *event) +{ + // QListView::resizeEvent(event); + + // if (m_categoryEditor) + // { + // m_categoryEditor->resize(qMax(contentWidth() / 2, + // m_editedCategory->textRect.width()), + // m_categoryEditor->height()); + // } + + updateGeometries(); +} + +void GroupView::dragEnterEvent(QDragEnterEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragMoveEvent(QDragMoveEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragLeaveEvent(QDragLeaveEvent *event) +{ + m_lastDragPosition = QPoint(); + viewport()->update(); +} + +void GroupView::dropEvent(QDropEvent *event) +{ + m_lastDragPosition = QPoint(); + + stopAutoScroll(); + setState(NoState); + + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + { + return; + } + + QPair dropPos = rowDropPos(event->pos() + offset()); + const Group *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) + { + viewport()->update(); + return; + } + + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + model()->setData(model()->index(row, 0), categoryText, + GroupViewRoles::GroupRole); + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); + viewport()->update(); +} + +void GroupView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.count() > 0) + { + QMimeData *data = model()->mimeData(indexes); + if (!data) + { + return; + } + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + //rect.translate(offset()); + // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(m_pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && + (supportedActions & this->defaultDropAction())) + { + defaultDropAction = this->defaultDropAction(); + } + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + { + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); + } + } + } +} + +QRect GroupView::visualRect(const QModelIndex &index) const +{ + return geometryRect(index).translated(-offset()); +} + +QRect GroupView::geometryRect(const QModelIndex &index) const +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Group *cat = category(index); + QPair pos = categoryInternalPosition(index); + int x = pos.first; + // int y = pos.second; + + QRect out; + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + + return out; +} + +/* +void CategorizedView::startCategoryEditor(Category *category) +{ + if (m_categoryEditor != 0) + { + return; + } + m_editedCategory = category; + m_categoryEditor = new QLineEdit(m_editedCategory->text, this); + QRect rect = m_editedCategory->textRect; + rect.setWidth(qMax(contentWidth() / 2, rect.width())); + m_categoryEditor->setGeometry(rect); + m_categoryEditor->show(); + m_categoryEditor->setFocus(); + connect(m_categoryEditor, &QLineEdit::returnPressed, this, +&CategorizedView::endCategoryEditor); +} + +void CategorizedView::endCategoryEditor() +{ + if (m_categoryEditor == 0) + { + return; + } + m_editedCategory->text = m_categoryEditor->text(); + m_updatesDisabled = true; + foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) + { + const_cast(index.model())->setData(index, +m_categoryEditor->text(), CategoryRole); + } + m_updatesDisabled = false; + delete m_categoryEditor; + m_categoryEditor = 0; + m_editedCategory = 0; + updateGeometries(); +} +*/ + +QModelIndex GroupView::indexAt(const QPoint &point) const +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (geometryRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); +} + +void GroupView::setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + QRect itemRect = geometryRect(index); + if (itemRect.intersects(rect)) + { + selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); + } + } + +} + +QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + auto paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} + +QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + QList> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = geometryRect(index); + ret += qMakePair(current, index); + rect |= current; + } + return ret; +} + +bool GroupView::isDragEventAccepted(QDropEvent *event) +{ + if (event->source() != this) + { + return false; + } + if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) + { + return false; + } + if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), + rowDropPos(event->pos()).second, 0, QModelIndex())) + { + return false; + } + return true; +} + +QPair GroupView::rowDropPos(const QPoint &pos) +{ + // check that we aren't on a category header and calculate which category we're in + Group *category = 0; + { + int y = 0; + for (auto cat : m_groups) + { + if (pos.y() > y && pos.y() < (y + cat->headerHeight())) + { + return qMakePair(nullptr, -1); + } + y += cat->totalHeight() + m_categoryMargin; + if (pos.y() < y) + { + category = cat; + break; + } + } + if (category == 0) + { + return qMakePair(nullptr, -1); + } + } + + QList indices = category->items(); + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = this->itemWidth(); + if (pos.x() >= (itemWidth * itemsPerRow())) + { + internalColumn = itemsPerRow(); + } + else + { + for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) + { + if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + } + if (internalColumn == -1) + { + return qMakePair(nullptr, -1); + } + } + + // calculate the internal row + int internalRow = -1; + { + // FIXME rework the drag and drop code + const int top = category->verticalPosition(); + for (int r = 0, h = top; r < category->numRows(); + h += itemHeightForCategoryRow(category, r), ++r) + { + if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + return qMakePair(nullptr, -1); + } + // this happens if we're in the margin between a one category and another + // categories header + if (internalRow > (indices.size() / itemsPerRow())) + { + return qMakePair(nullptr, -1); + } + } + + // flaten the internalColumn/internalRow to one row + int categoryRow = internalRow * itemsPerRow() + internalColumn; + + // this is used if we're past the last item + if (categoryRow >= indices.size()) + { + return qMakePair(category, indices.last().row() + 1); + } + + return qMakePair(category, indices.at(categoryRow).row()); +} + +QPoint GroupView::offset() const +{ + return QPoint(horizontalOffset(), verticalOffset()); +} + +QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const +{ + QRegion region; + for (auto &range : selection) + { + int start_row = range.top(); + int end_row = range.bottom(); + for (int row = start_row; row <= end_row; ++row) + { + int start_column = range.left(); + int end_column = range.right(); + for (int column = start_column; column <= end_column; ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); // OK + } + } + } + return region; +} +QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) +{ + auto current = currentIndex(); + if(!current.isValid()) + { + qDebug() << "model row: invalid"; + return current; + } + qDebug() << "model row: " << current.row(); + auto cat = category(current); + int i = m_groups.indexOf(cat); + if(i >= 0) + { + // this is a pile of something foul + auto real_group = m_groups[i]; + int beginning_row = 0; + for(auto group: m_groups) + { + if(group == real_group) + break; + beginning_row += group->numRows(); + } + qDebug() << "category: " << real_group->text; + QPair pos = categoryInternalPosition(current); + int row = beginning_row + pos.second; + qDebug() << "row: " << row; + qDebug() << "column: " << pos.first; + } + return current; +} diff --git a/gui/groupview/GroupView.h b/gui/groupview/GroupView.h new file mode 100644 index 00000000..329a3503 --- /dev/null +++ b/gui/groupview/GroupView.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +struct GroupViewRoles +{ + enum + { + GroupRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + +struct Group; + +class GroupView : public QAbstractItemView +{ + Q_OBJECT + +public: + GroupView(QWidget *parent = 0); + ~GroupView(); + + QRect geometryRect(const QModelIndex &index) const; + virtual QRect visualRect(const QModelIndex &index) const override; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; + + virtual int horizontalOffset() const override + { + return horizontalScrollBar()->value(); + } + + virtual int verticalOffset() const override + { + return verticalScrollBar()->value(); + } + + virtual void scrollContentsBy(int dx, int dy) override + { + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); + } + + /* + * TODO! + */ + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override + { + return; + } + + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; + + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; + +protected +slots: + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; + virtual void updateGeometries() override; + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + + void startDrag(Qt::DropActions supportedActions) override; + +private: + friend struct Group; + + QList m_groups; + + int m_leftMargin; + int m_rightMargin; + int m_bottomMargin; + int m_categoryMargin; + + // bool m_updatesDisabled; + + Group *category(const QModelIndex &index) const; + Group *category(const QString &cat) const; + Group *categoryAt(const QPoint &pos) const; + + int itemsPerRow() const; + int contentWidth() const; + +private: + int itemWidth() const; + int categoryRowHeight(const QModelIndex &index) const; + + /*QLineEdit *m_categoryEditor; + Category *m_editedCategory; + void startCategoryEditor(Category *category); + +private slots: + void endCategoryEditor();*/ + +private: /* variables */ + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Group *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + int m_spacing = 5; + +private: /* methods */ + QPair categoryInternalPosition(const QModelIndex &index) const; + int categoryInternalRowTop(const QModelIndex &index) const; + int itemHeightForCategoryRow(const Group *category, const int internalRow) const; + + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); + + QPoint offset() const; +}; diff --git a/gui/groupview/GroupedProxyModel.cpp b/gui/groupview/GroupedProxyModel.cpp new file mode 100644 index 00000000..d9d6ac78 --- /dev/null +++ b/gui/groupview/GroupedProxyModel.cpp @@ -0,0 +1,26 @@ +#include "GroupedProxyModel.h" + +#include "GroupView.h" + +GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ +} + +bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); + if (leftCategory == rightCategory) + { + return subSortLessThan(left, right); + } + else + { + return leftCategory < rightCategory; + } +} + +bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return left.row() < right.row(); +} diff --git a/gui/groupview/GroupedProxyModel.h b/gui/groupview/GroupedProxyModel.h new file mode 100644 index 00000000..12edee0f --- /dev/null +++ b/gui/groupview/GroupedProxyModel.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class GroupedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GroupedProxyModel(QObject *parent = 0); + +protected: + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp new file mode 100644 index 00000000..8527e2bc --- /dev/null +++ b/gui/groupview/InstanceDelegate.cpp @@ -0,0 +1,281 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InstanceDelegate.h" +#include +#include +#include +#include +#include + +#include "GroupView.h" + +// Origin: Qt +static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, + qreal &widthUsed) +{ + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); +} + +#define QFIXED_MAX (INT_MAX / 256) + +ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, + const QRect &rect) +{ + if ((option.state & QStyle::State_Selected)) + painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); + else + { + QColor backgroundColor = option.palette.color(QPalette::Background); + backgroundColor.setAlpha(160); + painter->fillRect(rect, QBrush(backgroundColor)); + } +} + +void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) +{ + if (!(option.state & QStyle::State_HasFocus)) + return; + QStyleOptionFocusRect opt; + opt.direction = option.direction; + opt.fontMetrics = option.fontMetrics; + opt.palette = option.palette; + opt.rect = rect; + // opt.state = option.state | QStyle::State_KeyboardFocusChange | + // QStyle::State_Item; + auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; + opt.backgroundColor = option.palette.color(col); + // Apparently some widget styles expect this hint to not be set + painter->setRenderHint(QPainter::Antialiasing, false); + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); + + painter->setRenderHint(QPainter::Antialiasing); +} + +// TODO this can be made a lot prettier +void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, + const int value, const int maximum) +{ + if (maximum == 0 || value == maximum) + { + return; + } + + painter->save(); + + qreal percent = (qreal)value / (qreal)maximum; + QColor color = option.palette.color(QPalette::Dark); + color.setAlphaF(0.70f); + painter->setBrush(color); + painter->setPen(QPen(QBrush(), 0)); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); + + painter->restore(); +} + +static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) +{ + QStyle *style = option->widget ? option->widget->style() : QApplication::style(); + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + QRect bounds(0, 0, 100 - 2 * textMargin, 600); + qreal height = 0, widthUsed = 0; + viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); +} + +void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + painter->save(); + painter->setClipRect(opt.rect); + + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + + // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); + const int iconSize = 48; + QRect iconbox = opt.rect; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; + QRect textRect = opt.rect; + QRect textHighlightRect = textRect; + // clip the decoration on top, remove width padding + textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); + + textHighlightRect.adjust(0, iconSize + 5, 0, 0); + + // draw background + { + // FIXME: unused + // QSize textSize = viewItemTextSize ( &opt ); + QPalette::ColorGroup cg; + QStyleOptionViewItemV4 opt2(opt); + + if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) + { + if (!(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else + cg = QPalette::Normal; + } + else + { + cg = QPalette::Disabled; + } + opt2.palette.setCurrentColorGroup(cg); + + // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, opt.backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + if (opt.showDecorationSelected) + { + drawSelectionRect(painter, opt2, opt.rect); + drawFocusRect(painter, opt2, opt.rect); + // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); + } + else + { + + // if ( opt.state & QStyle::State_Selected ) + { + // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, + // opt.widget ); + // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, + // QPalette::Highlight ) ); + drawSelectionRect(painter, opt2, textHighlightRect); + drawFocusRect(painter, opt2, textHighlightRect); + } + } + } + + // draw the icon + { + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + iconbox.setHeight(iconSize); + opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + } + // set the text colors + QPalette::ColorGroup cg = + opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (opt.state & QStyle::State_Selected) + { + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else + { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + // draw the text + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + textOption.setTextDirection(opt.direction); + textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(opt.font); + textLayout.setText(opt.text); + + qreal width, height; + viewItemTextLayout(textLayout, textRect.width(), height, width); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect( + opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) + { + const QTextLine line = textLayout.lineAt(i); + line.draw(painter, position); + } + + drawProgressOverlay(painter, opt, + index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); + + painter->restore(); +} + +QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables + QSize szz = viewItemTextSize(&opt); + height += szz.height(); + // FIXME: maybe the icon items could scale and keep proportions? + QSize sz(100, height); + return sz; +} diff --git a/gui/groupview/InstanceDelegate.h b/gui/groupview/InstanceDelegate.h new file mode 100644 index 00000000..de2f429b --- /dev/null +++ b/gui/groupview/InstanceDelegate.h @@ -0,0 +1,29 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class ListViewDelegate : public QStyledItemDelegate +{ +public: + explicit ListViewDelegate(QObject *parent = 0); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; diff --git a/gui/widgets/InstanceDelegate.cpp b/gui/widgets/InstanceDelegate.cpp deleted file mode 100644 index 33da7130..00000000 --- a/gui/widgets/InstanceDelegate.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "InstanceDelegate.h" -#include -#include -#include -#include -#include -#include "Common.h" -#define QFIXED_MAX (INT_MAX / 256) - -ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) -{ -} - -void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, - const QRect &rect) -{ - if ((option.state & QStyle::State_Selected)) - painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); - else - { - QColor backgroundColor = option.palette.color(QPalette::Background); - backgroundColor.setAlpha(160); - painter->fillRect(rect, QBrush(backgroundColor)); - } -} - -void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) -{ - if (!(option.state & QStyle::State_HasFocus)) - return; - QStyleOptionFocusRect opt; - opt.direction = option.direction; - opt.fontMetrics = option.fontMetrics; - opt.palette = option.palette; - opt.rect = rect; - // opt.state = option.state | QStyle::State_KeyboardFocusChange | - // QStyle::State_Item; - auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; - opt.backgroundColor = option.palette.color(col); - // Apparently some widget styles expect this hint to not be set - painter->setRenderHint(QPainter::Antialiasing, false); - - QStyle *style = option.widget ? option.widget->style() : QApplication::style(); - - style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); - - painter->setRenderHint(QPainter::Antialiasing); -} - -static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) -{ - QStyle *style = option->widget ? option->widget->style() : QApplication::style(); - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(option->font); - textLayout.setText(option->text); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; - QRect bounds(0, 0, 100 - 2 * textMargin, 600); - qreal height = 0, widthUsed = 0; - viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); - const QSize size(qCeil(widthUsed), qCeil(height)); - return QSize(size.width() + 2 * textMargin, size.height()); -} - -void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - painter->save(); - painter->setClipRect(opt.rect); - - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - - // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); - const int iconSize = 48; - QRect iconbox = opt.rect; - const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; - QRect textRect = opt.rect; - QRect textHighlightRect = textRect; - // clip the decoration on top, remove width padding - textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); - - textHighlightRect.adjust(0, iconSize + 5, 0, 0); - - // draw background - { - // FIXME: unused - // QSize textSize = viewItemTextSize ( &opt ); - QPalette::ColorGroup cg; - QStyleOptionViewItemV4 opt2(opt); - - if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) - { - if (!(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - else - cg = QPalette::Normal; - } - else - { - cg = QPalette::Disabled; - } - opt2.palette.setCurrentColorGroup(cg); - - // fill in background, if any - if (opt.backgroundBrush.style() != Qt::NoBrush) - { - QPointF oldBO = painter->brushOrigin(); - painter->setBrushOrigin(opt.rect.topLeft()); - painter->fillRect(opt.rect, opt.backgroundBrush); - painter->setBrushOrigin(oldBO); - } - - if (opt.showDecorationSelected) - { - drawSelectionRect(painter, opt2, opt.rect); - drawFocusRect(painter, opt2, opt.rect); - // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); - } - else - { - - // if ( opt.state & QStyle::State_Selected ) - { - // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, - // opt.widget ); - // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, - // QPalette::Highlight ) ); - drawSelectionRect(painter, opt2, textHighlightRect); - drawFocusRect(painter, opt2, textHighlightRect); - } - } - } - - // draw the icon - { - QIcon::Mode mode = QIcon::Normal; - if (!(opt.state & QStyle::State_Enabled)) - mode = QIcon::Disabled; - else if (opt.state & QStyle::State_Selected) - mode = QIcon::Selected; - QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; - - iconbox.setHeight(iconSize); - opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - } - // set the text colors - QPalette::ColorGroup cg = - opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - if (opt.state & QStyle::State_Selected) - { - painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); - } - else - { - painter->setPen(opt.palette.color(cg, QPalette::Text)); - } - - // draw the text - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - textOption.setTextDirection(opt.direction); - textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(opt.font); - textLayout.setText(opt.text); - - qreal width, height; - viewItemTextLayout(textLayout, textRect.width(), height, width); - - const int lineCount = textLayout.lineCount(); - - const QRect layoutRect = QStyle::alignedRect( - opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); - const QPointF position = layoutRect.topLeft(); - for (int i = 0; i < lineCount; ++i) - { - const QTextLine line = textLayout.lineAt(i); - line.draw(painter, position); - } - - painter->restore(); -} - -QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; - int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables - QSize szz = viewItemTextSize(&opt); - height += szz.height(); - // FIXME: maybe the icon items could scale and keep proportions? - QSize sz(100, height); - return sz; -} diff --git a/gui/widgets/InstanceDelegate.h b/gui/widgets/InstanceDelegate.h deleted file mode 100644 index 6f924405..00000000 --- a/gui/widgets/InstanceDelegate.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -class ListViewDelegate : public QStyledItemDelegate -{ -public: - explicit ListViewDelegate ( QObject* parent = 0 ); -protected: - void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; -}; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index 0d4eab95..935f9fd3 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -33,6 +33,7 @@ #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logger/QsLog.h" +#include const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -96,8 +97,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const return MMC->icons()->getIcon(key); } // for now. - case KCategorizedSortFilterProxyModel::CategorySortRole: - case KCategorizedSortFilterProxyModel::CategoryDisplayRole: + case GroupViewRoles::GroupRole: { return pdata->group(); } @@ -585,10 +585,8 @@ void InstanceList::propertiesChanged(BaseInstance *inst) } InstanceProxyModel::InstanceProxyModel(QObject *parent) - : KCategorizedSortFilterProxyModel(parent) + : GroupedProxyModel(parent) { - // disable since by default we are globally sorting by date: - setCategorizedModel(true); } bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h index 0ce808e5..cda51a03 100644 --- a/logic/lists/InstanceList.h +++ b/logic/lists/InstanceList.h @@ -18,7 +18,7 @@ #include #include #include -#include "categorizedsortfilterproxymodel.h" +#include #include #include "logic/BaseInstance.h" @@ -129,7 +129,7 @@ protected: QSet m_groups; }; -class InstanceProxyModel : public KCategorizedSortFilterProxyModel +class InstanceProxyModel : public GroupedProxyModel { public: explicit InstanceProxyModel(QObject *parent = 0); -- cgit From 6206a241ea51864b22d493d5b7b0a9282c754571 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 02:01:11 +0100 Subject: A try at fixing the instance delegate. A bit. Maybe. --- gui/groupview/InstanceDelegate.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp index 8527e2bc..8a273758 100644 --- a/gui/groupview/InstanceDelegate.cpp +++ b/gui/groupview/InstanceDelegate.cpp @@ -173,6 +173,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti opt2.palette.setCurrentColorGroup(cg); // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) { QPointF oldBO = painter->brushOrigin(); @@ -181,6 +182,9 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti painter->setBrushOrigin(oldBO); } + drawSelectionRect(painter, opt2, textHighlightRect); + + /* if (opt.showDecorationSelected) { drawSelectionRect(painter, opt2, opt.rect); @@ -200,6 +204,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti drawFocusRect(painter, opt2, textHighlightRect); } } + */ } // draw the icon -- cgit From c84c51860d4a39f9219998826c093e4e60b7bf0a Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 21:18:02 +0100 Subject: Fix crash bug related to data changes in new group view. --- gui/groupview/GroupView.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index 89e3e223..fb4b45f4 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -48,10 +48,14 @@ GroupView::~GroupView() void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { + /* if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) { + */ updateGeometries(); + /* } + */ viewport()->update(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) -- cgit From 573d4c8050fa6548e4680fd74c1a0872e9a62c34 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 5 Feb 2014 01:34:50 +0100 Subject: Paint the headers nicer. --- gui/groupview/Group.cpp | 66 +++++++++++++++++++++++++++++---------------- gui/groupview/Group.h | 3 ++- gui/groupview/GroupView.cpp | 9 ++++--- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp index f216cc6e..7cce674a 100644 --- a/gui/groupview/Group.cpp +++ b/gui/groupview/Group.cpp @@ -35,18 +35,18 @@ Group::HitResults Group::hitScan(const QPoint &pos) const int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? int y = pos.y(); // int x = pos.x(); - if(y < y_start) + if (y < y_start) { results = Group::NoHit; } - else if(y < body_start) + else if (y < body_start) { results = Group::HeaderHit; int collapseSize = headerHeight() - 4; // the icon QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); - if(iconRect.contains(pos)) + if (iconRect.contains(pos)) { results |= Group::CheckboxHit; } @@ -58,37 +58,53 @@ Group::HitResults Group::hitScan(const QPoint &pos) const return results; } -void Group::drawHeader(QPainter *painter, const int y) +void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y) { + QStyleOptionViewItemV4 opt = option; painter->save(); - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); - painter->setPen(QPen(Qt::black, 1)); - painter->drawRect(iconRect); static const int margin = 2; + static const int spacing = 10; + int height = headerHeight(); + int text_height = height - 2 * margin; + + // set the text colors + QPalette::ColorGroup cg = QPalette::Normal; + painter->setPen(opt.palette.color(cg, QPalette::Text)); + + // set up geometry + QRect iconRect = QRect(view->m_leftMargin + margin, y + margin, text_height - 1, text_height - 1); QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + QRect smallRect = iconSubrect.adjusted(margin, margin, -margin, -margin); int midX = iconSubrect.center().x(); int midY = iconSubrect.center().y(); - if (collapsed) + + // checkboxy thingy { - painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + painter->drawRect(iconSubrect); + + painter->setBrush(opt.palette.text()); + painter->drawRect(smallRect.x(), midY, smallRect.width(), 2); + if(collapsed) + { + painter->drawRect(midX, smallRect.y(), 2, smallRect.height()); + } } - painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); - // the text - int textWidth = painter->fontMetrics().width(text); - QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - painter->setBrush(view->viewOptions().palette.text()); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, - view->viewport()->palette(), true, text); + int x_left = iconRect.right(); + if(text.length()) + { + // the text + int text_width = painter->fontMetrics().width(text); + QRect textRect = QRect(x_left + spacing, y + margin, text_width, text_height); + x_left = textRect.right(); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, + opt.palette, true, text); + } // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, - view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + painter->drawLine(x_left + spacing, midY + 1, view->contentWidth() - view->m_rightMargin, + midY + 1); painter->restore(); } @@ -100,7 +116,11 @@ int Group::totalHeight() const int Group::headerHeight() const { - return view->viewport()->fontMetrics().height() + 4; + int raw = view->viewport()->fontMetrics().height() + 4; + // add english. maybe. depends on font height. + if(raw % 2 == 0) + raw++; + return std::min( raw , 25 ); } int Group::contentHeight() const diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h index 455ee1a8..46a12f63 100644 --- a/gui/groupview/Group.h +++ b/gui/groupview/Group.h @@ -3,6 +3,7 @@ #include #include #include +#include class GroupView; class QPainter; @@ -26,7 +27,7 @@ struct Group void update(); /// draw the header at y-position. - void drawHeader(QPainter *painter, const int y); + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y); /// height of the group, in total. includes a small bit of padding. int totalHeight() const; diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index fb4b45f4..9e186854 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -206,7 +206,7 @@ int GroupView::categoryRowHeight(const QModelIndex &index) const largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - return largestHeight; + return largestHeight + m_spacing; } QPair GroupView::categoryInternalPosition(const QModelIndex &index) const @@ -423,11 +423,14 @@ void GroupView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); + QStyleOptionViewItemV4 option(viewOptions()); + option.widget = this; + int y = -verticalOffset(); for (int i = 0; i < m_groups.size(); ++i) { Group *category = m_groups.at(i); - category->drawHeader(&painter, y); + category->drawHeader(&painter, option, y); y += category->totalHeight() + m_categoryMargin; } @@ -439,9 +442,7 @@ void GroupView::paintEvent(QPaintEvent *event) continue; } Qt::ItemFlags flags = index.flags(); - QStyleOptionViewItemV4 option(viewOptions()); option.rect = visualRect(index); - option.widget = this; option.features |= QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) -- cgit From 6d9819cccf3809edef2f6a1aeb0c6d01d6067501 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 8 Feb 2014 12:47:14 +0100 Subject: Error if a patch file is for a different version of minecraft --- logic/OneSixVersionBuilder.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 03555070..62a5ca43 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -538,6 +538,15 @@ struct VersionFile void applyTo(OneSixVersion *version, bool &isError) { isError = true; + if (!version->id.isNull() && !mcVersion.isNull()) + { + if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == -1) + { + QLOG_ERROR() << filename << "is for a different version of Minecraft"; + return; + } + } + if (!id.isNull()) { version->id = id; -- cgit From 53069205faf4063e838bab4beea5089d6ec2a0af Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Sat, 8 Feb 2014 17:22:26 +0100 Subject: Allow overriding the order in which patches are applied --- gui/dialogs/OneSixModEditDialog.cpp | 122 ++++++++++++++++++++++++++++++++++++ gui/dialogs/OneSixModEditDialog.h | 6 ++ gui/dialogs/OneSixModEditDialog.ui | 28 +++++++++ logic/OneSixVersion.cpp | 9 +++ logic/OneSixVersion.h | 3 + logic/OneSixVersionBuilder.cpp | 68 ++++++++++++++++++++ logic/OneSixVersionBuilder.h | 4 ++ 7 files changed, 240 insertions(+) diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index 1b61771d..9e585de5 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -39,6 +39,18 @@ #include "logic/lists/ForgeVersionList.h" #include "logic/ForgeInstaller.h" #include "logic/LiteLoaderInstaller.h" +#include "logic/OneSixVersionBuilder.h" + +template +QMap invert(const QMap &in) +{ + QMap out; + for (auto it = in.begin(); it != in.end(); ++it) + { + out.insert(it.value(), it.key()); + } + return out; +} OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst) @@ -129,6 +141,87 @@ void OneSixModEditDialog::on_removeLibraryBtn_clicked() } } +void OneSixModEditDialog::on_resetLibraryOrderBtn_clicked() +{ + QDir(m_inst->instanceRoot()).remove("order.json"); + m_inst->reloadVersion(this); +} +void OneSixModEditDialog::on_moveLibraryUpBtn_clicked() +{ + + QMap order = getExistingOrder(); + if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty()) + { + return; + } + const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row(); + const QString ourId = m_version->versionFileId(ourRow); + const int ourOrder = order[ourId]; + if (ourId.isNull() || ourId.startsWith("org.multimc.")) + { + return; + } + + QMap sortedOrder = invert(order); + + QList sortedOrders = sortedOrder.keys(); + const int ourIndex = sortedOrders.indexOf(ourOrder); + if (ourIndex <= 0) + { + return; + } + const int ourNewOrder = sortedOrders.at(ourIndex - 1); + order[ourId] = ourNewOrder; + order[sortedOrder[sortedOrders[ourIndex - 1]]] = ourOrder; + + if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst)) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order")); + } + else + { + m_inst->reloadVersion(this); + ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow - 1), QItemSelectionModel::SelectCurrent); + } +} +void OneSixModEditDialog::on_moveLibraryDownBtn_clicked() +{ + QMap order = getExistingOrder(); + if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty()) + { + return; + } + const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row(); + const QString ourId = m_version->versionFileId(ourRow); + const int ourOrder = order[ourId]; + if (ourId.isNull() || ourId.startsWith("org.multimc.")) + { + return; + } + + QMap sortedOrder = invert(order); + + QList sortedOrders = sortedOrder.keys(); + const int ourIndex = sortedOrders.indexOf(ourOrder); + if ((ourIndex + 1) >= sortedOrders.size()) + { + return; + } + const int ourNewOrder = sortedOrders.at(ourIndex + 1); + order[ourId] = ourNewOrder; + order[sortedOrder[sortedOrders[ourIndex + 1]]] = ourOrder; + + if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst)) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order")); + } + else + { + m_inst->reloadVersion(this); + ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow + 1), QItemSelectionModel::SelectCurrent); + } +} + void OneSixModEditDialog::on_forgeBtn_clicked() { if (QDir(m_inst->instanceRoot()).exists("custom.json")) @@ -250,6 +343,35 @@ bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) return QDialog::eventFilter(ui->resPackTreeView, keyEvent); } +QMap OneSixModEditDialog::getExistingOrder() const +{ + + QMap order; + // default + { + for (OneSixVersion::VersionFile file : m_version->versionFiles) + { + if (file.id.startsWith("org.multimc.")) + { + continue; + } + order.insert(file.id, file.order); + } + } + // overriden + { + QMap overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_inst); + for (auto id : order.keys()) + { + if (overridenOrder.contains(id)) + { + order[id] = overridenOrder[id]; + } + } + } + return order; +} + bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::KeyPress) diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h index a7def967..f44b336b 100644 --- a/gui/dialogs/OneSixModEditDialog.h +++ b/gui/dialogs/OneSixModEditDialog.h @@ -47,6 +47,9 @@ slots: void on_liteloaderBtn_clicked(); void on_reloadLibrariesBtn_clicked(); void on_removeLibraryBtn_clicked(); + void on_resetLibraryOrderBtn_clicked(); + void on_moveLibraryUpBtn_clicked(); + void on_moveLibraryDownBtn_clicked(); void updateVersionControls(); void disableVersionControls(); @@ -62,6 +65,9 @@ private: std::shared_ptr m_resourcepacks; EnabledItemFilter *main_model; OneSixInstance *m_inst; + + QMap getExistingOrder() const; + public slots: void loaderCurrent(QModelIndex current, QModelIndex previous); diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui index b97fa323..eaf8f7fd 100644 --- a/gui/dialogs/OneSixModEditDialog.ui +++ b/gui/dialogs/OneSixModEditDialog.ui @@ -108,6 +108,34 @@ + + + + Reset order + + + + + + + Qt::Horizontal + + + + + + + Move up + + + + + + + Move down + + + diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index e19683d8..fb32f3a8 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -81,6 +81,15 @@ bool OneSixVersion::canRemove(const int index) const return false; } +QString OneSixVersion::versionFileId(const int index) const +{ + if (index < 0 || index >= versionFiles.size()) + { + return QString(); + } + return versionFiles.at(index).id; +} + bool OneSixVersion::remove(const int index) { if (canRemove(index)) diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 516f153c..ba7695d5 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -44,6 +44,8 @@ public: bool canRemove(const int index) const; + QString versionFileId(const int index) const; + public slots: bool remove(const int index); @@ -123,6 +125,7 @@ public: QString version; QString mcVersion; QString filename; + int order; }; QList versionFiles; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index 62a5ca43..bbd33ddc 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -763,6 +763,7 @@ struct VersionFile versionFile.version = this->version; versionFile.mcVersion = mcVersion; versionFile.filename = filename; + versionFile.order = order; version->versionFiles.append(versionFile); isError = false; @@ -858,6 +859,7 @@ bool OneSixVersionBuilder::build(const bool onlyVanilla) // patches/ { // load all, put into map for ordering, apply in the right order + QMap overrideOrder = readOverrideOrders(m_instance); QMap> files; for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) @@ -868,6 +870,15 @@ bool OneSixVersionBuilder::build(const bool onlyVanilla) { return false; } + if (overrideOrder.contains(file.fileId)) + { + file.order = overrideOrder.value(file.fileId); + } + if (files.contains(file.order)) + { + QLOG_ERROR() << file.fileId << "has the same order as" << files[file.order].second.fileId; + return false; + } files.insert(file.order, qMakePair(info.fileName(), file)); } for (auto order : files.keys()) @@ -1007,3 +1018,60 @@ bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrd } return true; } + +QMap OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) +{ + QMap out; + if (QDir(instance->instanceRoot()).exists("order.json")) + { + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + } + else + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError || !doc.isObject()) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + } + else + { + QJsonObject obj = doc.object(); + for (auto it = obj.begin(); it != obj.end(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + out.insert(it.key(), it.value().toDouble()); + } + } + } + } + return out; +} +bool OneSixVersionBuilder::writeOverrideOrders(const QMap &order, OneSixInstance *instance) +{ + QJsonObject obj; + for (auto it = order.cbegin(); it != order.cend(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + obj.insert(it.key(), it.value()); + } + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h index f0d69bf6..ab0966df 100644 --- a/logic/OneSixVersionBuilder.h +++ b/logic/OneSixVersionBuilder.h @@ -16,6 +16,7 @@ #pragma once #include +#include class OneSixVersion; class OneSixInstance; @@ -30,6 +31,8 @@ class OneSixVersionBuilder public: static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla); static bool read(OneSixVersion *version, const QJsonObject &obj); + static QMap readOverrideOrders(OneSixInstance *instance); + static bool writeOverrideOrders(const QMap &order, OneSixInstance *instance); private: OneSixVersion *m_version; @@ -40,4 +43,5 @@ private: bool read(const QJsonObject &obj); bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out); + }; -- cgit From aa41b891f06a629099d108ab1f4df94d1bbc828c Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sat, 8 Feb 2014 21:46:29 +0100 Subject: Group View: Use painting code from the previous group headers, small optimizations --- gui/groupview/Group.cpp | 105 +++++++++++++++++++++++++++++++++++++++----- gui/groupview/Group.h | 9 ++-- gui/groupview/GroupView.cpp | 22 ++++++++-- 3 files changed, 117 insertions(+), 19 deletions(-) diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp index 7cce674a..f3319dc3 100644 --- a/gui/groupview/Group.cpp +++ b/gui/groupview/Group.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "GroupView.h" @@ -58,8 +59,9 @@ Group::HitResults Group::hitScan(const QPoint &pos) const return results; } -void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y) +void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) { + /* QStyleOptionViewItemV4 opt = option; painter->save(); @@ -107,6 +109,95 @@ void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, co midY + 1); painter->restore(); + */ + painter->setRenderHint(QPainter::Antialiasing); + + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); + } + //END: text } int Group::totalHeight() const @@ -144,17 +235,7 @@ int Group::numRows() const int Group::verticalPosition() const { - int res = 0; - const QList cats = view->m_groups; - for (int i = 0; i < cats.size(); ++i) - { - if (cats.at(i) == this) - { - break; - } - res += cats.at(i)->totalHeight() + view->m_categoryMargin; - } - return res; + return m_verticalPosition; } QList Group::items() const diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h index 46a12f63..3b797f4c 100644 --- a/gui/groupview/Group.h +++ b/gui/groupview/Group.h @@ -16,18 +16,19 @@ struct Group Group(const Group *other); /* data */ - GroupView *view; + GroupView *view = nullptr; QString text; - bool collapsed; + bool collapsed = false; QVector rowHeights; - int firstItemIndex; + int firstItemIndex = 0; + int m_verticalPosition = 0; /* logic */ /// do stuff. and things. TODO: redo. void update(); /// draw the header at y-position. - void drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y); + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); /// height of the group, in total. includes a small bit of padding. int totalHeight() const; diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index 9e186854..9954d743 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -114,12 +114,19 @@ void GroupView::updateGeometries() else { int totalHeight = 0; + // top margin + totalHeight += m_categoryMargin; for (auto category : m_groups) { + category->m_verticalPosition = totalHeight; totalHeight += category->totalHeight() + m_categoryMargin; } + /* // remove the last margin (we don't want it) totalHeight -= m_categoryMargin; + // add a margin on top... + totalHeight += m_categoryMargin; + */ totalHeight += m_bottomMargin; verticalScrollBar()->setRange(0, totalHeight - height()); } @@ -426,12 +433,22 @@ void GroupView::paintEvent(QPaintEvent *event) QStyleOptionViewItemV4 option(viewOptions()); option.widget = this; - int y = -verticalOffset(); + int wpWidth = viewport()->width(); + option.rect.setWidth(wpWidth); for (int i = 0; i < m_groups.size(); ++i) { Group *category = m_groups.at(i); - category->drawHeader(&painter, option, y); + int y = category->verticalPosition(); + y -= verticalOffset(); + QRect backup = option.rect; + int height = category->totalHeight(); + option.rect.setTop(y); + option.rect.setHeight(height); + option.rect.setLeft(m_leftMargin); + option.rect.setRight(wpWidth - m_rightMargin); + category->drawHeader(&painter, option); y += category->totalHeight() + m_categoryMargin; + option.rect = backup; } for (int i = 0; i < model()->rowCount(); ++i) @@ -587,7 +604,6 @@ void GroupView::startDrag(Qt::DropActions supportedActions) QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); - drag->setHotSpot(m_pressedPosition - rect.topLeft()); Qt::DropAction defaultDropAction = Qt::IgnoreAction; if (this->defaultDropAction() != Qt::IgnoreAction && (supportedActions & this->defaultDropAction())) -- cgit From 583786757a112827ac12efdf6736d1b602e6eb1e Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sat, 8 Feb 2014 23:52:15 +0100 Subject: Fix crash bug related to messageboxes interrupting model resets in the instance list. --- logic/lists/InstanceList.cpp | 108 +++++++++++++++++++++++-------------------- logic/lists/InstanceList.h | 16 ++++++- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index 935f9fd3..9a61e2dd 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -47,6 +47,13 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent) QDir::current().mkpath(m_instDir); } + /* + * FIXME HACK: instances sometimes need to be created at launch. They need the versions for + * that. + * + * Remove this. it has no business of reloading the whole list. The instances which + * need it should track such events themselves and CHANGE THEIR DATA ONLY! + */ connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this, &InstanceList::loadList); } @@ -282,16 +289,7 @@ void InstanceList::loadGroupList(QMap &groupMap) } } -struct FTBRecord -{ - QString dir; - QString name; - QString logo; - QString mcVersion; - QString description; -}; - -void InstanceList::loadForgeInstances(QMap groupMap) +QList InstanceList::discoverFTBInstances() { QList records; QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString()); @@ -300,18 +298,18 @@ void InstanceList::loadForgeInstances(QMap groupMap) { QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your " "settings."; - return; + return records; } else if (!dataDir.exists()) { QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; - return; + return records; } dir.cd("ModPacks"); auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); - for(auto filename: allFiles) + for (auto filename : allFiles) { - if(!filename.endsWith(".xml")) + if (!filename.endsWith(".xml")) continue; auto fpath = dir.absoluteFilePath(filename); QFile f(fpath); @@ -331,9 +329,11 @@ void InstanceList::loadForgeInstances(QMap groupMap) { QXmlStreamAttributes attrs = reader.attributes(); FTBRecord record; - record.dir = attrs.value("dir").toString(); - QDir test(dataDir.absoluteFilePath(record.dir)); - if(!test.exists()) + record.dirName = attrs.value("dir").toString(); + record.instanceDir = dataDir.absoluteFilePath(record.dirName); + record.templateDir = dir.absoluteFilePath(record.dirName); + QDir test(record.instanceDir); + if (!test.exists()) continue; record.name = attrs.value("name").toString(); record.logo = attrs.value("logo").toString(); @@ -353,8 +353,14 @@ void InstanceList::loadForgeInstances(QMap groupMap) } f.close(); } + return records; +} - if(!records.size()) +void InstanceList::loadFTBInstances(QMap &groupMap, + QList &tempList) +{ + auto records = discoverFTBInstances(); + if (!records.size()) { QLOG_INFO() << "No FTB instances to load."; return; @@ -363,20 +369,13 @@ void InstanceList::loadForgeInstances(QMap groupMap) // process the records we acquired. for (auto record : records) { - auto instanceDir = dataDir.absoluteFilePath(record.dir); - QLOG_INFO() << "Loading FTB instance from " << instanceDir; - auto templateDir = dir.absoluteFilePath(record.dir); - if (!QFileInfo(instanceDir).exists()) - { - continue; - } - + QLOG_INFO() << "Loading FTB instance from " << record.instanceDir; QString iconKey = record.logo; iconKey.remove(QRegularExpression("\\..*")); - MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo), + MMC->icons()->addIcon(iconKey, iconKey, PathCombine(record.templateDir, record.logo), MMCIcon::Transient); - if (!QFileInfo(PathCombine(instanceDir, "instance.cfg")).exists()) + if (!QFileInfo(PathCombine(record.instanceDir, "instance.cfg")).exists()) { QLOG_INFO() << "Converting " << record.name << " as new."; BaseInstance *instPtr = NULL; @@ -384,12 +383,12 @@ void InstanceList::loadForgeInstances(QMap groupMap) auto version = MMC->minecraftlist()->findVersion(record.mcVersion); if (!version) { - QLOG_ERROR() << "Can't load instance " << instanceDir + QLOG_ERROR() << "Can't load instance " << record.instanceDir << " because minecraft version " << record.mcVersion << " can't be resolved."; continue; } - auto error = factory.createInstance(instPtr, version, instanceDir, + auto error = factory.createInstance(instPtr, version, record.instanceDir, InstanceFactory::FTBInstance); if (!instPtr || error != InstanceFactory::NoCreateError) @@ -400,13 +399,15 @@ void InstanceList::loadForgeInstances(QMap groupMap) instPtr->setIconKey(iconKey); instPtr->setIntendedVersionId(record.mcVersion); instPtr->setNotes(record.description); - continueProcessInstance(instPtr, error, instanceDir, groupMap); + if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) + continue; + tempList.append(InstancePtr(instPtr)); } else { QLOG_INFO() << "Loading existing " << record.name; BaseInstance *instPtr = NULL; - auto error = InstanceFactory::get().loadInstance(instPtr, instanceDir); + auto error = InstanceFactory::get().loadInstance(instPtr, record.instanceDir); if (!instPtr || error != InstanceFactory::NoCreateError) continue; instPtr->setGroupInitial("FTB"); @@ -415,7 +416,9 @@ void InstanceList::loadForgeInstances(QMap groupMap) if (instPtr->intendedVersionId() != record.mcVersion) instPtr->setIntendedVersionId(record.mcVersion); instPtr->setNotes(record.description); - continueProcessInstance(instPtr, error, instanceDir, groupMap); + if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) + continue; + tempList.append(InstancePtr(instPtr)); } } } @@ -426,10 +429,7 @@ InstanceList::InstListError InstanceList::loadList() QMap groupMap; loadGroupList(groupMap); - beginResetModel(); - - m_instances.clear(); - + QList tempList; { QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks); @@ -441,15 +441,28 @@ InstanceList::InstListError InstanceList::loadList() QLOG_INFO() << "Loading MultiMC instance from " << subDir; BaseInstance *instPtr = NULL; auto error = InstanceFactory::get().loadInstance(instPtr, subDir); - continueProcessInstance(instPtr, error, subDir, groupMap); + if(!continueProcessInstance(instPtr, error, subDir, groupMap)) + continue; + tempList.append(InstancePtr(instPtr)); } } if (MMC->settings()->get("TrackFTBInstances").toBool()) { - loadForgeInstances(groupMap); + loadFTBInstances(groupMap, tempList); + } + beginResetModel(); + m_instances.clear(); + for(auto inst: tempList) + { + inst->setParent(this); + connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this, + SLOT(instanceNuked(BaseInstance *))); + m_instances.append(inst); } - endResetModel(); emit dataIsInvalid(); return NoError; @@ -523,7 +536,7 @@ int InstanceList::getInstIndex(BaseInstance *inst) const return -1; } -void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error, +bool InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir, QMap &groupMap) { if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) @@ -539,12 +552,14 @@ void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int erro break; } QLOG_ERROR() << errorMsg.toUtf8(); + return false; } else if (!instPtr) { QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.") .arg(QFileInfo(dir.absolutePath()).baseName()) .toUtf8(); + return false; } else { @@ -554,13 +569,7 @@ void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int erro instPtr->setGroupInitial((*iter)); } QLOG_INFO() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath(); - instPtr->setParent(this); - m_instances.append(std::shared_ptr(instPtr)); - connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this, - SLOT(propertiesChanged(BaseInstance *))); - connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged())); - connect(instPtr, SIGNAL(nuked(BaseInstance *)), this, - SLOT(instanceNuked(BaseInstance *))); + return true; } } @@ -584,8 +593,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst) } } -InstanceProxyModel::InstanceProxyModel(QObject *parent) - : GroupedProxyModel(parent) +InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(parent) { } diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h index cda51a03..ebe3e051 100644 --- a/logic/lists/InstanceList.h +++ b/logic/lists/InstanceList.h @@ -27,11 +27,24 @@ class BaseInstance; class QDir; +struct FTBRecord +{ + QString dirName; + QString name; + QString logo; + QString mcVersion; + QString description; + QString instanceDir; + QString templateDir; +}; + class InstanceList : public QAbstractListModel { Q_OBJECT private: void loadGroupList(QMap &groupList); + QList discoverFTBInstances(); + void loadFTBInstances(QMap &groupMap, QList & tempList); private slots: @@ -109,7 +122,6 @@ slots: * \brief Loads the instance list. Triggers notifications. */ InstListError loadList(); - void loadForgeInstances(QMap groupMap); private slots: @@ -120,7 +132,7 @@ slots: private: int getInstIndex(BaseInstance *inst) const; - void continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir, + bool continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir, QMap &groupMap); protected: -- cgit From 902204236031dedffc6047802524c93dcc14fa45 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 9 Feb 2014 11:00:34 +0100 Subject: Only load instance list twice. --- logic/lists/MinecraftVersionList.cpp | 10 +++++++--- logic/lists/MinecraftVersionList.h | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 91f86df0..ece31e3d 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -60,10 +60,15 @@ bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) return left->timestamp > right->timestamp; } +void MinecraftVersionList::sortInternal() +{ + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); +} + void MinecraftVersionList::sort() { beginResetModel(); - qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); + sortInternal(); endResetModel(); } @@ -85,9 +90,8 @@ void MinecraftVersionList::updateListData(QList versions) beginResetModel(); m_vlist = versions; m_loaded = true; + sortInternal(); endResetModel(); - // NOW SORT!! - sort(); } inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) diff --git a/logic/lists/MinecraftVersionList.h b/logic/lists/MinecraftVersionList.h index 82af1009..167f4d11 100644 --- a/logic/lists/MinecraftVersionList.h +++ b/logic/lists/MinecraftVersionList.h @@ -29,6 +29,8 @@ class QNetworkReply; class MinecraftVersionList : public BaseVersionList { Q_OBJECT +private: + void sortInternal(); public: friend class MCVListLoadTask; -- cgit From 0d30a2655ffb767488d50a4a79978b3d9ddb3aa9 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 9 Feb 2014 19:10:56 +0100 Subject: Blacklist the FTB voxel pack. --- logic/lists/InstanceList.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index 9a61e2dd..cd59e6d6 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -336,6 +336,8 @@ QList InstanceList::discoverFTBInstances() if (!test.exists()) continue; record.name = attrs.value("name").toString(); + if(record.name.contains("voxel", Qt::CaseInsensitive)) + continue; record.logo = attrs.value("logo").toString(); record.mcVersion = attrs.value("mcVersion").toString(); record.description = attrs.value("description").toString(); -- cgit From 18f532b0d7d873280ec17218196db15fa64175a2 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 9 Feb 2014 20:45:18 +0100 Subject: Visual and scroll behavior changes to groupview Scroll by rows, not pixels. Paint the checkboxy thing again! Make0 it behave. Set the group header height properly. --- gui/groupview/Group.cpp | 259 ++++++++++++++++++++++---------------------- gui/groupview/GroupView.cpp | 6 + 2 files changed, 135 insertions(+), 130 deletions(-) diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp index f3319dc3..51aa6658 100644 --- a/gui/groupview/Group.cpp +++ b/gui/groupview/Group.cpp @@ -61,143 +61,133 @@ Group::HitResults Group::hitScan(const QPoint &pos) const void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) { - /* - QStyleOptionViewItemV4 opt = option; - painter->save(); - - static const int margin = 2; - static const int spacing = 10; - int height = headerHeight(); - int text_height = height - 2 * margin; - - // set the text colors - QPalette::ColorGroup cg = QPalette::Normal; - painter->setPen(opt.palette.color(cg, QPalette::Text)); - - // set up geometry - QRect iconRect = QRect(view->m_leftMargin + margin, y + margin, text_height - 1, text_height - 1); - QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); - QRect smallRect = iconSubrect.adjusted(margin, margin, -margin, -margin); - int midX = iconSubrect.center().x(); - int midY = iconSubrect.center().y(); - - // checkboxy thingy + painter->setRenderHint(QPainter::Antialiasing); + + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner { - painter->drawRect(iconSubrect); + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner - painter->setBrush(opt.palette.text()); - painter->drawRect(smallRect.x(), midY, smallRect.width(), 2); - if(collapsed) - { - painter->drawRect(midX, smallRect.y(), 2, smallRect.height()); - } + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); } + //END: left vertical line - int x_left = iconRect.right(); + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line - if(text.length()) + //BEGIN: top right corner { - // the text - int text_width = painter->fontMetrics().width(text); - QRect textRect = QRect(x_left + spacing, y + margin, text_width, text_height); - x_left = textRect.right(); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, - opt.palette, true, text); + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); } - // the line - painter->drawLine(x_left + spacing, midY + 1, view->contentWidth() - view->m_rightMargin, - midY + 1); + //END: top right corner - painter->restore(); - */ - painter->setRenderHint(QPainter::Antialiasing); + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); - painter->restore(); - } - //END: text + //BEGIN: checkboxy thing + { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + QRect iconSubRect(option.rect); + iconSubRect.setTop(iconSubRect.top() + 7); + iconSubRect.setLeft(iconSubRect.left() + 7); + + int sizing = fontMetrics.height(); + int even = ( (sizing - 1) % 2 ); + + iconSubRect.setHeight(sizing - even); + iconSubRect.setWidth(sizing - even); + painter->drawRect(iconSubRect); + + + /* + if(collapsed) + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); + else + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); + */ + painter->setBrush(option.palette.text()); + painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, + iconSubRect.width(), 2, penColor); + if (collapsed) + { + painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, + iconSubRect.height(), penColor); + } + + painter->restore(); + } + //END: checkboxy thing + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); + } + //END: text } int Group::totalHeight() const @@ -207,11 +197,20 @@ int Group::totalHeight() const int Group::headerHeight() const { + QFont font(QApplication::font()); + font.setBold(true); + QFontMetrics fontMetrics(font); + + const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ + + 11 /* top and bottom separation */; + return height; + /* int raw = view->viewport()->fontMetrics().height() + 4; // add english. maybe. depends on font height. - if(raw % 2 == 0) + if (raw % 2 == 0) raw++; - return std::min( raw , 25 ); + return std::min(raw, 25); + */ } int Group::contentHeight() const diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index 9954d743..5ee44cbb 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -121,6 +121,8 @@ void GroupView::updateGeometries() category->m_verticalPosition = totalHeight; totalHeight += category->totalHeight() + m_categoryMargin; } + auto category = m_groups.last(); + int itemScroll = category->contentHeight() / category->numRows(); /* // remove the last margin (we don't want it) totalHeight -= m_categoryMargin; @@ -128,6 +130,10 @@ void GroupView::updateGeometries() totalHeight += m_categoryMargin; */ totalHeight += m_bottomMargin; + verticalScrollBar()->setSingleStep ( itemScroll ); + const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 ); + verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll ); + verticalScrollBar()->setRange(0, totalHeight - height()); } -- cgit