summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt14
-rw-r--r--gui/instancedelegate.cpp222
-rw-r--r--gui/instancedelegate.h12
-rw-r--r--gui/instancemodel.cpp92
-rw-r--r--gui/instancemodel.h38
-rw-r--r--gui/mainwindow.cpp176
-rw-r--r--gui/mainwindow.h21
-rw-r--r--gui/mainwindow.ui16
-rw-r--r--libgroupview/CMakeLists.txt41
-rw-r--r--libgroupview/include/kcategorizedsortfilterproxymodel.h175
-rw-r--r--libgroupview/include/kcategorizedview.h322
-rw-r--r--libgroupview/include/kcategorydrawer.h179
-rw-r--r--libgroupview/include/libgroupview_config.h27
-rw-r--r--libgroupview/src/kcategorizedsortfilterproxymodel.cpp168
-rw-r--r--libgroupview/src/kcategorizedsortfilterproxymodel_p.h48
-rw-r--r--libgroupview/src/kcategorizedview.cpp1696
-rw-r--r--libgroupview/src/kcategorizedview_p.h157
-rw-r--r--libgroupview/src/kcategorydrawer.cpp231
-rw-r--r--libmultimc/include/appsettings.h4
-rw-r--r--libmultimc/include/logintask.h8
-rw-r--r--libmultimc/src/appsettings.cpp3
-rw-r--r--libmultimc/src/instancelist.cpp2
-rw-r--r--libmultimc/src/logintask.cpp22
-rw-r--r--main.cpp16
24 files changed, 3582 insertions, 108 deletions
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 <QPainter>
+#include <QTextOption>
+#include <QTextLayout>
+#include <QApplication>
+#include <QtCore/qmath.h>
+
+// 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 <QStyledItemDelegate>
+
+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 <instance.h>
+#include <QIcon>
+
+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<Instance*> ( 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<Instance*> ( left.internalPointer() );
+ Instance *pdataRight = static_cast<Instance*> ( 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 <QAbstractListModel>
+#include "kcategorizedsortfilterproxymodel.h"
+#include "instancelist.h"
+#include <QIcon>
+
+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 <instance.h>
+
+#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<void *>();
+ 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<void *>();
+ 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 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QTreeView" name="instanceView">
- <property name="animated">
- <bool>true</bool>
- </property>
- <property name="allColumnsShowFocus">
- <bool>true</bool>
- </property>
- <attribute name="headerVisible">
- <bool>false</bool>
- </attribute>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- </widget>
- </item>
</layout>
</widget>
<widget class="QToolBar" name="mainToolBar">
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 <ereslibre@kde.org>
+ * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
+ *
+ * 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 <QSortFilterProxyModel>
+
+#include <kdeui_export.h>
+
+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 <ereslibre@kde.org>
+ */
+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 <ereslibre@kde.org>
+ *
+ * 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 <QListView>
+
+#include <kdeui_export.h>
+
+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 <ereslibre@kde.org>
+ */
+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<KCategorizedView*>(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 &current,
+ 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 <ereslibre@kde.org>
+ *
+ * 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 <libgroupview_config.h>
+
+#include <QtCore/QObject>
+#include <QtGui/QMouseEvent>
+
+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 <QtCore/QtGlobal>
+
+#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 <ereslibre@kde.org>
+ * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
+ *
+ * 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 <limits.h>
+
+#include <QItemSelection>
+#include <QStringList>
+#include <QSize>
+
+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 <ereslibre@kde.org>
+ * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
+ *
+ * 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 <ereslibre@kde.org>
+ *
+ * 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 <math.h> // trunc on C99 compliant systems
+#include <kdefakes.h> // trunc for not C99 compliant systems
+
+#include <QPainter>
+#include <QScrollBar>
+#include <QPaintEvent>
+
+#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<Item>() )
+ , 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<Item> 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.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<QModelIndex, QModelIndex> 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<QString, Private::Block>::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<QString, Block>::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<Block> blockList = blocks.values();
+ qSort ( blockList.begin(), blockList.end(), Block::lessThan );
+ QList<int> firstIndexesRows;
+ foreach ( const Block &block, blockList )
+ {
+ firstIndexesRows << block.firstIndex.row();
+ }
+ //END: order for marking as alternate those blocks that are alternate
+ for ( QHash<QString, Private::Block>::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() )
+ {
+ const int relativeRow = index.row() - firstIndexRow;
+ const QSize itemSize = q->sizeHintForIndex ( index );
+ const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() ) / ( 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() );
+ const QRect tempRect = q->visualRect ( prevIndex );
+ 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<KCategorizedSortFilterProxyModel*> ( 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::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<QString, Private::Block>::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<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) );
+
+ QPainter p ( viewport() );
+ p.save();
+
+ Q_ASSERT ( selectionModel()->model() == d->proxyModel );
+
+ //BEGIN: draw categories
+ QHash<QString, Private::Block>::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<QModelIndex, QModelIndex> 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<QString, Private::Block>::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<QString, Private::Block>::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<QString, Private::Block>::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() )
+ {
+ const QModelIndex current = currentIndex();
+ const QSize itemSize = d->hasGrid() ? gridSize()
+ : sizeHintForIndex ( current );
+ const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
+ const int maxItemsPerRow = qMax ( d->viewportWidth() / itemSize.width(), 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() )
+ {
+ const QModelIndex current = currentIndex();
+ const QSize itemSize = d->hasGrid() ? gridSize()
+ : sizeHintForIndex ( current );
+ const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
+ const int maxItemsPerRow = qMax ( d->viewportWidth() / itemSize.width(), 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<Private::Block> blockList = d->blocks.values();
+ qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan );
+ QList<int> firstIndexesRows;
+ foreach ( const Private::Block &block, blockList )
+ {
+ firstIndexesRows << block.firstIndex.row();
+ }
+ //END: order for marking as alternate those blocks that are alternate
+ for ( QHash<QString, Private::Block>::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 &current,
+ 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 "kcategorizedview.moc"
diff --git a/libgroupview/src/kcategorizedview_p.h b/libgroupview/src/kcategorizedview_p.h
new file mode 100644
index 00000000..6dafa382
--- /dev/null
+++ b/libgroupview/src/kcategorizedview_p.h
@@ -0,0 +1,157 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
+ *
+ * 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<QModelIndex, QModelIndex> 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;
+ KCategorizedSortFilterProxyModel *proxyModel;
+ KCategoryDrawer *categoryDrawer;
+ int categorySpacing;
+ bool alternatingBlockColors;
+ bool collapsibleBlocks;
+
+ Block *hoveredBlock;
+ QString hoveredCategory;
+ QModelIndex hoveredIndex;
+
+ QPoint pressedPosition;
+ QRect rubberBandRect;
+
+ QHash<QString, Block> blocks;
+};
+
+#endif // KCATEGORIZEDVIEW_P_H
diff --git a/libgroupview/src/kcategorydrawer.cpp b/libgroupview/src/kcategorydrawer.cpp
new file mode 100644
index 00000000..951c1eb2
--- /dev/null
+++ b/libgroupview/src/kcategorydrawer.cpp
@@ -0,0 +1,231 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
+ *
+ * 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 "kcategorydrawer.h"
+
+#include <QPainter>
+#include <QStyleOption>
+#include <QApplication>
+
+#include <kcategorizedview.h>
+#include <kcategorizedsortfilterproxymodel.h>
+
+#define HORIZONTAL_HINT 3
+
+class KCategoryDrawer::Private
+{
+public:
+ Private(KCategorizedView *view)
+ : view(view)
+ , leftMargin(0)
+ , rightMargin(0)
+ {
+ }
+
+ ~Private()
+ {
+ }
+ int leftMargin;
+ int rightMargin;
+ KCategorizedView *view;
+};
+
+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 "kcategorydrawer.moc"
diff --git a/libmultimc/include/appsettings.h b/libmultimc/include/appsettings.h
index 66ffb7b5..8447d58b 100644
--- a/libmultimc/include/appsettings.h
+++ b/libmultimc/include/appsettings.h
@@ -18,11 +18,11 @@
#include <QObject>
-#include <basicsettingsobject.h>
+#include <inisettingsobject.h>
#include "libmmc_config.h"
-class LIBMULTIMC_EXPORT AppSettings : public BasicSettingsObject
+class LIBMULTIMC_EXPORT AppSettings : public INISettingsObject
{
Q_OBJECT
public:
diff --git a/libmultimc/include/logintask.h b/libmultimc/include/logintask.h
index c1efb223..e00609ec 100644
--- a/libmultimc/include/logintask.h
+++ b/libmultimc/include/logintask.h
@@ -30,20 +30,20 @@ class LIBMULTIMC_EXPORT LoginTask : public Task
{
Q_OBJECT
public:
- explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0);
+ explicit LoginTask(const UserInfo& uInfo, QString inst, QObject *parent = 0);
public slots:
void processNetReply(QNetworkReply* reply);
signals:
- void loginComplete(LoginResponse loginResponse);
- void loginFailed(const QString& errorMsg);
+ void loginComplete(QString inst, LoginResponse loginResponse);
+ void loginFailed(QString inst, const QString& errorMsg);
protected:
void executeTask();
QNetworkReply* netReply;
-
+ QString inst;
UserInfo uInfo;
};
diff --git a/libmultimc/src/appsettings.cpp b/libmultimc/src/appsettings.cpp
index 68d2c015..7c886fdb 100644
--- a/libmultimc/src/appsettings.cpp
+++ b/libmultimc/src/appsettings.cpp
@@ -18,10 +18,11 @@
#include <setting.h>
#include <QPoint>
+#include <QApplication>
//#include <QColor>
AppSettings::AppSettings(QObject *parent) :
- BasicSettingsObject(parent)
+ INISettingsObject(QApplication::applicationDirPath() + "/multimc.cfg",parent)
{
// Updates
registerSetting(new Setting("UseDevBuilds", false));
diff --git a/libmultimc/src/instancelist.cpp b/libmultimc/src/instancelist.cpp
index 3b0b668f..78650634 100644
--- a/libmultimc/src/instancelist.cpp
+++ b/libmultimc/src/instancelist.cpp
@@ -28,7 +28,7 @@
InstanceList::InstanceList(const QString &instDir, QObject *parent) :
- QObject(parent), m_instDir(instDir)
+ QObject(parent), m_instDir("instances")
{
}
diff --git a/libmultimc/src/logintask.cpp b/libmultimc/src/logintask.cpp
index 71075630..e042a93f 100644
--- a/libmultimc/src/logintask.cpp
+++ b/libmultimc/src/logintask.cpp
@@ -24,8 +24,8 @@
#include <QUrl>
#include <QUrlQuery>
-LoginTask::LoginTask(const UserInfo &uInfo, QObject *parent) :
- Task(parent), uInfo(uInfo)
+LoginTask::LoginTask( const UserInfo& uInfo, QString inst, QObject* parent ) :
+ Task(parent), uInfo(uInfo), inst(inst)
{
}
@@ -78,42 +78,42 @@ void LoginTask::processNetReply(QNetworkReply *reply)
QString sessionID = strings[3];
LoginResponse response(username, sessionID, latestVersion);
- emit loginComplete(response);
+ emit loginComplete(inst, response);
}
else
{
- emit loginFailed("Failed to parse Minecraft version string.");
+ emit loginFailed(inst, "Failed to parse Minecraft version string.");
}
}
else
{
if (responseStr.toLower() == "bad login")
- emit loginFailed("Invalid username or password.");
+ emit loginFailed(inst, "Invalid username or password.");
else if (responseStr.toLower() == "old version")
- emit loginFailed("Launcher outdated, please update.");
+ emit loginFailed(inst, "Launcher outdated, please update.");
else
- emit loginFailed("Login failed: " + responseStr);
+ emit loginFailed(inst, "Login failed: " + responseStr);
}
}
else if (responseCode == 503)
{
- emit loginFailed("The login servers are currently unavailable. "
+ emit loginFailed(inst, "The login servers are currently unavailable. "
"Check http://help.mojang.com/ for more info.");
}
else
{
- emit loginFailed(QString("Login failed: Unknown HTTP error %1 occurred.").
+ emit loginFailed(inst, QString("Login failed: Unknown HTTP error %1 occurred.").
arg(QString::number(responseCode)));
}
break;
}
case QNetworkReply::OperationCanceledError:
- emit loginFailed("Login canceled.");
+ emit loginFailed(inst, "Login canceled.");
break;
default:
- emit loginFailed("Login failed: " + reply->errorString());
+ emit loginFailed(inst, "Login failed: " + reply->errorString());
break;
}
diff --git a/main.cpp b/main.cpp
index 465ee3c7..cf193ff0 100644
--- a/main.cpp
+++ b/main.cpp
@@ -81,7 +81,7 @@ private slots:
QApplication::instance()->quit();
}
- void onLoginComplete(LoginResponse response)
+ void onLoginComplete(QString instId, LoginResponse response)
{
// TODO: console
console = new ConsoleWindow();
@@ -92,7 +92,7 @@ private slots:
proc->launch();
}
- void doLogin(const QString &errorMsg)
+ void doLogin(QString instId, const QString &errorMsg)
{
LoginDialog* loginDlg = new LoginDialog(nullptr, errorMsg);
if (loginDlg->exec())
@@ -100,11 +100,11 @@ private slots:
UserInfo uInfo(loginDlg->getUsername(), loginDlg->getPassword());
TaskDialog* tDialog = new TaskDialog(nullptr);
- 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);
+ LoginTask* loginTask = new LoginTask(uInfo, instance.data()->id(), tDialog);
+ connect(loginTask, SIGNAL(loginComplete(QString, LoginResponse)),
+ SLOT(onLoginComplete(QString, LoginResponse)), Qt::QueuedConnection);
+ connect(loginTask, SIGNAL(loginFailed(QString, QString)),
+ SLOT(doLogin(QString, QString)), Qt::QueuedConnection);
tDialog->exec(loginTask);
}
//onLoginComplete(LoginResponse("Offline","Offline", 1));
@@ -125,7 +125,7 @@ public:
}
std::cout << "Logging in..." << std::endl;
- doLogin("");
+ doLogin(instance->id(),"");
return QApplication::instance()->exec();
}