From 46f93311afc9f1e2afc306f63cee0e4f462758e2 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Mon, 11 Mar 2013 22:19:17 +0100 Subject: Instance view, model, delegate. --- CMakeLists.txt | 14 +- gui/instancedelegate.cpp | 222 +++ gui/instancedelegate.h | 12 + gui/instancemodel.cpp | 92 ++ gui/instancemodel.h | 38 + gui/mainwindow.cpp | 176 +- gui/mainwindow.h | 21 +- gui/mainwindow.ui | 16 - libgroupview/CMakeLists.txt | 41 + .../include/kcategorizedsortfilterproxymodel.h | 175 ++ libgroupview/include/kcategorizedview.h | 322 ++++ libgroupview/include/kcategorydrawer.h | 179 +++ libgroupview/include/libgroupview_config.h | 27 + .../src/kcategorizedsortfilterproxymodel.cpp | 168 ++ .../src/kcategorizedsortfilterproxymodel_p.h | 48 + libgroupview/src/kcategorizedview.cpp | 1696 ++++++++++++++++++++ libgroupview/src/kcategorizedview_p.h | 157 ++ libgroupview/src/kcategorydrawer.cpp | 231 +++ libmultimc/include/appsettings.h | 4 +- libmultimc/include/logintask.h | 8 +- libmultimc/src/appsettings.cpp | 3 +- libmultimc/src/instancelist.cpp | 2 +- libmultimc/src/logintask.cpp | 22 +- main.cpp | 16 +- 24 files changed, 3582 insertions(+), 108 deletions(-) create mode 100644 gui/instancedelegate.cpp create mode 100644 gui/instancedelegate.h create mode 100644 gui/instancemodel.cpp create mode 100644 gui/instancemodel.h create mode 100644 libgroupview/CMakeLists.txt create mode 100644 libgroupview/include/kcategorizedsortfilterproxymodel.h create mode 100644 libgroupview/include/kcategorizedview.h create mode 100644 libgroupview/include/kcategorydrawer.h create mode 100644 libgroupview/include/libgroupview_config.h create mode 100644 libgroupview/src/kcategorizedsortfilterproxymodel.cpp create mode 100644 libgroupview/src/kcategorizedsortfilterproxymodel_p.h create mode 100644 libgroupview/src/kcategorizedview.cpp create mode 100644 libgroupview/src/kcategorizedview_p.h create mode 100644 libgroupview/src/kcategorydrawer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9aa75f4d..cf6e1aab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,10 @@ include_directories(${LIBSETTINGS_INCLUDE_DIR}) add_subdirectory(libmultimc) include_directories(${LIBMULTIMC_INCLUDE_DIR}) +# Add the group view library. +add_subdirectory(libgroupview) +include_directories(${LIBGROUPVIEW_INCLUDE_DIR}) + # Add the stdinstance plugin. add_subdirectory(plugins/stdinstance) @@ -166,6 +170,8 @@ gui/taskdialog.h gui/browserdialog.h gui/aboutdialog.h gui/consolewindow.h +gui/instancemodel.h +gui/instancedelegate.h multimc_pragma.h @@ -192,6 +198,8 @@ gui/taskdialog.cpp gui/browserdialog.cpp gui/aboutdialog.cpp gui/consolewindow.cpp +gui/instancemodel.cpp +gui/instancedelegate.cpp java/javautils.cpp java/annotations.cpp @@ -222,7 +230,7 @@ ENDIF() # ICNS file for OS X IF(APPLE) - SET(MACOSX_BUNDLE_ICON_FILE MultiMC.icns) + SET(MACOSX_BUNDLE_ICON_FILE MultiMC.icns) SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/MultiMC.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) SET(MULTIMC_SOURCES ${MULTIMC_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/MultiMC.icns) ENDIF(APPLE) @@ -248,9 +256,9 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 # Link QT5_USE_MODULES(MultiMC Widgets Network WebKitWidgets) TARGET_LINK_LIBRARIES(MultiMC quazip patchlib -libUtil libSettings libMultiMC +libUtil libSettings libMultiMC libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) -ADD_DEPENDENCIES(MultiMC MultiMCLauncher libUtil libSettings libMultiMC) +ADD_DEPENDENCIES(MultiMC MultiMCLauncher libUtil libSettings libMultiMC libGroupView) ################################ INSTALLATION AND PACKAGING ################################ diff --git a/gui/instancedelegate.cpp b/gui/instancedelegate.cpp new file mode 100644 index 00000000..dac2dacb --- /dev/null +++ b/gui/instancedelegate.cpp @@ -0,0 +1,222 @@ +#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(); + while ( true ) + { + QTextLine line = textLayout.createLine(); + if ( !line.isValid() ) + 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)) + return; + painter->fillRect ( rect, option.palette.brush ( QPalette::Highlight ) ); +} + +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 + { + 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, iconbox.width(), height, width ); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect ( opt.direction, opt.displayAlignment, QSize ( iconbox.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/instancedelegate.h b/gui/instancedelegate.h new file mode 100644 index 00000000..c80f95a5 --- /dev/null +++ b/gui/instancedelegate.h @@ -0,0 +1,12 @@ +#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; +}; \ No newline at end of file diff --git a/gui/instancemodel.cpp b/gui/instancemodel.cpp new file mode 100644 index 00000000..73d0dbc1 --- /dev/null +++ b/gui/instancemodel.cpp @@ -0,0 +1,92 @@ +#include "instancemodel.h" +#include +#include + +InstanceModel::InstanceModel ( const InstanceList& instances, QObject *parent ) + : QAbstractListModel ( parent ), m_instances ( &instances ) +{ + cachedIcon = QIcon(":/icons/multimc/scalable/apps/multimc.svg"); +} + +int InstanceModel::rowCount ( const QModelIndex& parent ) const +{ + Q_UNUSED ( parent ); + return m_instances->count(); +} + +QModelIndex InstanceModel::index ( int row, int column, const QModelIndex& parent ) const +{ + Q_UNUSED ( parent ); + if ( row < 0 || row >= m_instances->count() ) + return QModelIndex(); + return createIndex ( row, column, ( void* ) m_instances->at ( row ).data() ); +} + +QVariant InstanceModel::data ( const QModelIndex& index, int role ) const +{ + if ( !index.isValid() ) + { + return QVariant(); + } + Instance *pdata = static_cast ( index.internalPointer() ); + switch ( role ) + { + case InstancePointerRole: + { + QVariant v = qVariantFromValue((void *) pdata); + return v; + } + case Qt::DisplayRole: + { + return pdata->name(); + } + case Qt::ToolTipRole: + { + return pdata->rootDir(); + } + case Qt::DecorationRole: + { + // FIXME: replace with an icon cache + return cachedIcon; + } + // for now. + case KCategorizedSortFilterProxyModel::CategorySortRole: + case KCategorizedSortFilterProxyModel::CategoryDisplayRole: + { + return "IT'S A GROUP"; + } + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags InstanceModel::flags ( const QModelIndex& index ) const +{ + Qt::ItemFlags f; + if ( index.isValid() ) + { + f |= ( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + } + return f; +} + +InstanceProxyModel::InstanceProxyModel ( QObject *parent ) + : KCategorizedSortFilterProxyModel ( parent ) +{ + // disable since by default we are globally sorting by date: + setCategorizedModel(true); +} + +bool InstanceProxyModel::subSortLessThan ( + const QModelIndex& left, const QModelIndex& right ) const +{ + Instance *pdataLeft = static_cast ( left.internalPointer() ); + Instance *pdataRight = static_cast ( right.internalPointer() ); + //kDebug() << *pdataLeft << *pdataRight; + return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; + //return pdataLeft->name() < pdataRight->name(); +} + +#include "instancemodel.moc" + diff --git a/gui/instancemodel.h b/gui/instancemodel.h new file mode 100644 index 00000000..995c51ec --- /dev/null +++ b/gui/instancemodel.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "kcategorizedsortfilterproxymodel.h" +#include "instancelist.h" +#include + +class InstanceModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum AdditionalRoles + { + InstancePointerRole = 0x34B1CB48 ///< Return pointer to real instance + }; + explicit InstanceModel ( const InstanceList& instances, + QObject *parent = 0 ); + + QModelIndex index ( int row, int column = 0, + const QModelIndex& parent = QModelIndex() ) const; + int rowCount ( const QModelIndex& parent = QModelIndex() ) const; + QVariant data ( const QModelIndex& index, int role ) const; + Qt::ItemFlags flags ( const QModelIndex& index ) const; + +private: + const InstanceList* m_instances; + QIcon cachedIcon; +}; + +class InstanceProxyModel : public KCategorizedSortFilterProxyModel +{ +public: + explicit InstanceProxyModel ( QObject *parent = 0 ); + +protected: + virtual bool subSortLessThan ( const QModelIndex& left, const QModelIndex& right ) const; +}; + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 6866da28..e691c8c4 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -7,7 +7,7 @@ * 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 @@ -40,45 +40,94 @@ #include "gui/browserdialog.h" #include "gui/aboutdialog.h" +#include "kcategorizedview.h" +#include "kcategorydrawer.h" + #include "instancelist.h" #include "appsettings.h" #include "version.h" #include "logintask.h" +#include + +#include "instancemodel.h" +#include "instancedelegate.h" // Opens the given file in the default application. // TODO: Move this somewhere. -void openInDefaultProgram(QString filename); +void openInDefaultProgram ( QString filename ); -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow), - instList(globalSettings->get("InstanceDir").toString()) +MainWindow::MainWindow ( QWidget *parent ) : + QMainWindow ( parent ), + ui ( new Ui::MainWindow ), + instList ( globalSettings->get ( "InstanceDir" ).toString() ) { - ui->setupUi(this); + ui->setupUi ( this ); + // Create the widget + instList.loadList(); - setWindowTitle(QString("MultiMC %1").arg(Version::current.toString())); + view = new KCategorizedView ( ui->centralWidget ); + drawer = new KCategoryDrawer ( view ); + + view->setSelectionMode ( QAbstractItemView::SingleSelection ); + //view->setSpacing( KDialog::spacingHint() ); + 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); + + model = new InstanceModel ( instList,this ); + proxymodel = new InstanceProxyModel ( this ); + proxymodel->setSortRole ( KCategorizedSortFilterProxyModel::CategorySortRole ); + proxymodel->setFilterRole ( KCategorizedSortFilterProxyModel::CategorySortRole ); + //proxymodel->setDynamicSortFilter ( true ); + proxymodel->setSourceModel ( model ); + proxymodel->sort ( 0 ); + + view->setFrameShape ( QFrame::NoFrame ); + + ui->horizontalLayout->addWidget ( view ); + setWindowTitle ( QString ( "MultiMC %1" ).arg ( Version::current.toString() ) ); // TODO: Make this work with the new settings system. // restoreGeometry(settings->getConfig().value("MainWindowGeometry", saveGeometry()).toByteArray()); // restoreState(settings->getConfig().value("MainWindowState", saveState()).toByteArray()); - - instList.loadList(); + view->setModel ( proxymodel ); + connect(view, SIGNAL(doubleClicked(const QModelIndex &)), + this, SLOT(instanceActivated(const QModelIndex &))); + } MainWindow::~MainWindow() { delete ui; + delete proxymodel; + delete model; + delete drawer; +} + +void MainWindow::instanceActivated ( QModelIndex index ) +{ + if(!index.isValid()) + return; + Instance * inst = (Instance *) index.data(InstanceModel::InstancePointerRole).value(); + doLogin(inst->id()); } void MainWindow::on_actionAddInstance_triggered() { - NewInstanceDialog *newInstDlg = new NewInstanceDialog(this); + NewInstanceDialog *newInstDlg = new NewInstanceDialog ( this ); newInstDlg->exec(); } void MainWindow::on_actionViewInstanceFolder_triggered() { - openInDefaultProgram(globalSettings->get("InstanceDir").toString()); + openInDefaultProgram ( globalSettings->get ( "InstanceDir" ).toString() ); } void MainWindow::on_actionRefresh_triggered() @@ -88,115 +137,126 @@ void MainWindow::on_actionRefresh_triggered() void MainWindow::on_actionViewCentralModsFolder_triggered() { - openInDefaultProgram(globalSettings->get("CentralModsDir").toString()); + openInDefaultProgram ( globalSettings->get ( "CentralModsDir" ).toString() ); } void MainWindow::on_actionCheckUpdate_triggered() { - + } void MainWindow::on_actionSettings_triggered() { - SettingsDialog dialog(this); + SettingsDialog dialog ( this ); dialog.exec(); } void MainWindow::on_actionReportBug_triggered() { - //QDesktopServices::openUrl(QUrl("http://bugs.forkk.net/")); - openWebPage(QUrl("http://bugs.forkk.net/")); + //QDesktopServices::openUrl(QUrl("http://bugs.forkk.net/")); + openWebPage ( QUrl ( "http://bugs.forkk.net/" ) ); } void MainWindow::on_actionNews_triggered() { - //QDesktopServices::openUrl(QUrl("http://news.forkk.net/")); - openWebPage(QUrl("http://news.forkk.net/")); + //QDesktopServices::openUrl(QUrl("http://news.forkk.net/")); + openWebPage ( QUrl ( "http://news.forkk.net/" ) ); } void MainWindow::on_actionAbout_triggered() { - AboutDialog dialog(this); - dialog.exec(); + AboutDialog dialog ( this ); + dialog.exec(); } -void MainWindow::on_mainToolBar_visibilityChanged(bool) +void MainWindow::on_mainToolBar_visibilityChanged ( bool ) { // Don't allow hiding the main toolbar. // This is the only way I could find to prevent it... :/ - ui->mainToolBar->setVisible(true); + ui->mainToolBar->setVisible ( true ); } -void MainWindow::closeEvent(QCloseEvent *event) +void MainWindow::closeEvent ( QCloseEvent *event ) { // Save the window state and geometry. // TODO: Make this work with the new settings system. // settings->getConfig().setValue("MainWindowGeometry", saveGeometry()); // settings->getConfig().setValue("MainWindowState", saveState()); - QMainWindow::closeEvent(event); + QMainWindow::closeEvent ( event ); } -void MainWindow::on_instanceView_customContextMenuRequested(const QPoint &pos) +void MainWindow::on_instanceView_customContextMenuRequested ( const QPoint &pos ) { - QMenu *instContextMenu = new QMenu("Instance", this); - + QMenu *instContextMenu = new QMenu ( "Instance", this ); + // Add the actions from the toolbar to the context menu. - instContextMenu->addActions(ui->instanceToolBar->actions()); - - instContextMenu->exec(ui->instanceView->mapToGlobal(pos)); + instContextMenu->addActions ( ui->instanceToolBar->actions() ); + + instContextMenu->exec ( view->mapToGlobal ( pos ) ); } void MainWindow::on_actionLaunchInstance_triggered() { - doLogin(); + QModelIndex index = view->currentIndex(); + if(index.isValid()) + { + Instance * inst = (Instance *) index.data(InstanceModel::InstancePointerRole).value(); + doLogin(inst->id()); + } } -void MainWindow::doLogin(const QString &errorMsg) +void MainWindow::doLogin ( QString inst, const QString& errorMsg ) { - LoginDialog* loginDlg = new LoginDialog(this, errorMsg); - if (loginDlg->exec()) + LoginDialog* loginDlg = new LoginDialog ( this, errorMsg ); + if ( loginDlg->exec() ) { - UserInfo uInfo(loginDlg->getUsername(), loginDlg->getPassword()); - - TaskDialog* tDialog = new TaskDialog(this); - LoginTask* loginTask = new LoginTask(uInfo, tDialog); - connect(loginTask, SIGNAL(loginComplete(LoginResponse)), - SLOT(onLoginComplete(LoginResponse)), Qt::QueuedConnection); - connect(loginTask, SIGNAL(loginFailed(QString)), - SLOT(doLogin(QString)), Qt::QueuedConnection); - tDialog->exec(loginTask); + UserInfo uInfo ( loginDlg->getUsername(), loginDlg->getPassword() ); + + TaskDialog* tDialog = new TaskDialog ( this ); + LoginTask* loginTask = new LoginTask ( uInfo, inst, tDialog ); + connect ( loginTask, SIGNAL ( loginComplete ( QString, LoginResponse ) ), + SLOT ( onLoginComplete ( QString, LoginResponse ) ), Qt::QueuedConnection ); + connect ( loginTask, SIGNAL ( loginFailed ( QString, QString ) ), + SLOT ( onLoginFailed( QString, QString ) ), Qt::QueuedConnection ); + tDialog->exec ( loginTask ); } } -void MainWindow::onLoginComplete(LoginResponse response) +void MainWindow::onLoginComplete ( QString inst, LoginResponse response ) { - QMessageBox::information(this, "Login Successful", - QString("Logged in as %1 with session ID %2."). - arg(response.username(), response.sessionID())); + QMessageBox::information ( this, "Login Successful", + QString ( "Logged in as %1 with session ID %2. Instance: %3" ). + arg ( response.username(), response.sessionID(), inst ) ); } +void MainWindow::onLoginFailed ( QString inst, const QString& errorMsg ) +{ + doLogin(inst, errorMsg); +} + + // Create A Desktop Shortcut void MainWindow::on_actionMakeDesktopShortcut_triggered() { - QString name("Test"); - name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), QLineEdit::Normal, name); + QString name ( "Test" ); + name = QInputDialog::getText ( this, tr ( "MultiMC Shortcut" ), tr ( "Enter a Shortcut Name." ), QLineEdit::Normal, name ); - Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream"); + Util::createShortCut ( Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream" ); - QMessageBox::warning(this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive"); + QMessageBox::warning ( this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive" ); } // BrowserDialog -void MainWindow::openWebPage(QUrl url) +void MainWindow::openWebPage ( QUrl url ) { - BrowserDialog *browser = new BrowserDialog(this); + BrowserDialog *browser = new BrowserDialog ( this ); - browser->load(url); - browser->exec(); + browser->load ( url ); + browser->exec(); } -void openInDefaultProgram(QString filename) +void openInDefaultProgram ( QString filename ) { - QDesktopServices::openUrl("file:///" + QFileInfo(filename).absolutePath()); + QDesktopServices::openUrl ( "file:///" + QFileInfo ( filename ).absolutePath() ); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 5ea575a6..c2dedf74 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -20,6 +20,12 @@ #include "instancelist.h" #include "loginresponse.h" +#include "instance.h" + +class InstanceModel; +class InstanceProxyModel; +class KCategorizedView; +class KCategoryDrawer; namespace Ui { @@ -67,14 +73,21 @@ private slots: void on_actionMakeDesktopShortcut_triggered(); - void doLogin(const QString& errorMsg = ""); - + void doLogin( QString inst, const QString& errorMsg = "" ); - void onLoginComplete(LoginResponse response); + void onLoginComplete( QString inst, LoginResponse response ); + void onLoginFailed( QString inst, const QString& errorMsg ); + +public slots: + void instanceActivated ( QModelIndex ); + private: Ui::MainWindow *ui; - + KCategoryDrawer * drawer; + KCategorizedView * view; + InstanceModel * model; + InstanceProxyModel * proxymodel; InstanceList instList; }; diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 137f7e97..fe6de6c9 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -33,22 +33,6 @@ 0 - - - - true - - - true - - - false - - - Qt::CustomContextMenu - - - diff --git a/libgroupview/CMakeLists.txt b/libgroupview/CMakeLists.txt new file mode 100644 index 00000000..9aa3013b --- /dev/null +++ b/libgroupview/CMakeLists.txt @@ -0,0 +1,41 @@ +project(libGroupView) + +set(CMAKE_AUTOMOC ON) + +# Find Qt +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) + +# Include Qt headers. +include_directories(${Qt5Base_INCLUDE_DIRS}) + +SET(LIBGROUPVIEW_HEADERS +include/libgroupview_config.h + +# Public headers +include/kcategorizedsortfilterproxymodel.h +include/kcategorizedview.h +include/kcategorydrawer.h + +# Private headers +src/kcategorizedsortfilterproxymodel_p.h +src/kcategorizedview_p.h +) + +SET(LIBGROUPVIEW_SOURCES +src/kcategorizedsortfilterproxymodel.cpp +src/kcategorizedview.cpp +src/kcategorydrawer.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) + +add_definitions(-DLIBGROUPVIEW_LIBRARY) + +add_library(libGroupView SHARED ${LIBGROUPVIEW_SOURCES} ${LIBGROUPVIEW_HEADERS}) +qt5_use_modules(libGroupView Core Gui) diff --git a/libgroupview/include/kcategorizedsortfilterproxymodel.h b/libgroupview/include/kcategorizedsortfilterproxymodel.h new file mode 100644 index 00000000..be83f3be --- /dev/null +++ b/libgroupview/include/kcategorizedsortfilterproxymodel.h @@ -0,0 +1,175 @@ +/* + * 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 KDEUI_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/libgroupview/include/kcategorizedview.h b/libgroupview/include/kcategorizedview.h new file mode 100644 index 00000000..04c2f5cc --- /dev/null +++ b/libgroupview/include/kcategorizedview.h @@ -0,0 +1,322 @@ +/** + * 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 KDEUI_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(); + +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/libgroupview/include/kcategorydrawer.h b/libgroupview/include/kcategorydrawer.h new file mode 100644 index 00000000..aa561f0d --- /dev/null +++ b/libgroupview/include/kcategorydrawer.h @@ -0,0 +1,179 @@ +/** + * 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/libgroupview/include/libgroupview_config.h b/libgroupview/include/libgroupview_config.h new file mode 100644 index 00000000..86bed139 --- /dev/null +++ b/libgroupview/include/libgroupview_config.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. + */ + +//#ifndef LIBINSTANCE_CONFIG_H +//#define LIBINSTANCE_CONFIG_H + +#include + +#ifdef LIBGROUPVIEW_LIBRARY +# define LIBGROUPVIEW_EXPORT Q_DECL_EXPORT +#else +# define LIBGROUPVIEW_EXPORT Q_DECL_IMPORT +#endif + +//#endif // LIBINSTANCE_CONFIG_H diff --git a/libgroupview/src/kcategorizedsortfilterproxymodel.cpp b/libgroupview/src/kcategorizedsortfilterproxymodel.cpp new file mode 100644 index 00000000..46a845e0 --- /dev/null +++ b/libgroupview/src/kcategorizedsortfilterproxymodel.cpp @@ -0,0 +1,168 @@ +/** + * 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 "kcategorizedsortfilterproxymodel.h" +#include "kcategorizedsortfilterproxymodel_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/libgroupview/src/kcategorizedsortfilterproxymodel_p.h b/libgroupview/src/kcategorizedsortfilterproxymodel_p.h new file mode 100644 index 00000000..d7e7c9a0 --- /dev/null +++ b/libgroupview/src/kcategorizedsortfilterproxymodel_p.h @@ -0,0 +1,48 @@ +/** + * 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/libgroupview/src/kcategorizedview.cpp b/libgroupview/src/kcategorizedview.cpp new file mode 100644 index 00000000..ba4ae2dc --- /dev/null +++ b/libgroupview/src/kcategorizedview.cpp @@ -0,0 +1,1696 @@ +/** + * 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 "kcategorizedview.h" +#include "kcategorizedview_p.h" + +#include // trunc on C99 compliant systems +#include // trunc for not C99 compliant systems + +#include +#include +#include + +#include "kcategorydrawer.h" +#include "kcategorizedsortfilterproxymodel.h" + +//BEGIN: Private part + +struct KCategorizedView::Private::Item +{ + Item() + : topLeft ( QPoint() ) + , size ( QSize() ) + { + } + + QPoint topLeft; + QSize size; +}; + +struct KCategorizedView::Private::Block +{ + Block() + : topLeft ( QPoint() ) + , height ( -1 ) + , firstIndex ( QModelIndex() ) + , quarantineStart ( QModelIndex() ) + , items ( QList() ) + , outOfQuarantine ( false ) + , alternate ( false ) + , collapsed ( false ) + { + } + + 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; + 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; + + // should we alternate its color ? is just a hint, could not be used + bool alternate; + bool collapsed; +}; + +KCategorizedView::Private::Private ( KCategorizedView *q ) + : q ( q ) + , proxyModel ( 0 ) + , categoryDrawer ( 0 ) + , categorySpacing ( 5 ) + , alternatingBlockColors ( false ) + , collapsibleBlocks ( false ) + , hoveredBlock ( new Block() ) + , hoveredIndex ( QModelIndex() ) + , pressedPosition ( QPoint() ) + , rubberBandRect ( QRect() ) +{ +} + +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