summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew <forkk@forkk.net>2013-03-28 11:37:12 -0500
committerAndrew <forkk@forkk.net>2013-03-28 11:37:12 -0500
commitebb2c54975e3f0b7b891532e8e72d2ef760f96c4 (patch)
tree973de7066457a1faddffd3834eb0c260d5191ec2
parent168ed3e8e58a8e5065ffa720f8d45f4cee0f2069 (diff)
parent1f13f0c665001a1a79f00cdad1e63e6c9802e55f (diff)
downloadMultiMC-ebb2c54975e3f0b7b891532e8e72d2ef760f96c4.tar
MultiMC-ebb2c54975e3f0b7b891532e8e72d2ef760f96c4.tar.gz
MultiMC-ebb2c54975e3f0b7b891532e8e72d2ef760f96c4.tar.lz
MultiMC-ebb2c54975e3f0b7b891532e8e72d2ef760f96c4.tar.xz
MultiMC-ebb2c54975e3f0b7b891532e8e72d2ef760f96c4.zip
Merge branch 'master' of git://github.com/peterix/MultiMC5
Conflicts: CMakeLists.txt gui/mainwindow.cpp
-rw-r--r--CMakeLists.txt10
-rw-r--r--gui/consolewindow.cpp77
-rw-r--r--gui/consolewindow.h79
-rw-r--r--gui/iconcache.cpp127
-rw-r--r--gui/iconcache.h43
-rw-r--r--gui/instancedelegate.cpp12
-rw-r--r--gui/instancemodel.cpp43
-rw-r--r--gui/instancemodel.h7
-rw-r--r--gui/logindialog.cpp51
-rw-r--r--gui/logindialog.h6
-rw-r--r--gui/logindialog.ui33
-rw-r--r--gui/mainwindow.cpp86
-rw-r--r--gui/mainwindow.h11
-rw-r--r--gui/settingsdialog.ui44
-rw-r--r--java/constants.h8
-rw-r--r--java/javautils.cpp108
-rw-r--r--java/javautils.h25
-rw-r--r--libgroupview/include/kcategorizedview.h10
-rw-r--r--libgroupview/src/kcategorizedview.cpp31
-rw-r--r--libgroupview/src/kcategorizedview_p.h242
-rw-r--r--libmultimc/include/instance.h34
-rw-r--r--libmultimc/include/instancelist.h44
-rw-r--r--libmultimc/include/minecraftprocess.h134
-rw-r--r--libmultimc/src/instance.cpp6
-rw-r--r--libmultimc/src/instancelist.cpp160
-rw-r--r--libmultimc/src/minecraftprocess.cpp56
-rw-r--r--libsettings/CMakeLists.txt11
-rw-r--r--libsettings/include/keyring.h92
-rw-r--r--libsettings/src/basicsettingsobject.cpp5
-rw-r--r--libsettings/src/inisettingsobject.cpp5
-rw-r--r--libsettings/src/keyring.cpp63
-rw-r--r--libsettings/src/setting.cpp9
-rw-r--r--libsettings/src/stubkeyring.cpp104
-rw-r--r--libsettings/src/stubkeyring.h41
-rw-r--r--libutil/CMakeLists.txt3
-rw-r--r--libutil/include/cmdutils.h17
-rw-r--r--libutil/src/cmdutils.cpp16
-rw-r--r--main.cpp20
-rw-r--r--multimc.qrc3
-rw-r--r--resources/catbgrnd2.pngbin0 -> 78285 bytes
-rw-r--r--resources/icons/instances/clucker.svg12
-rw-r--r--resources/icons/instances/skeleton.svg40
-rw-r--r--test.cpp60
43 files changed, 1501 insertions, 487 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 44e7a735..c3744430 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,7 +31,6 @@ ENDIF()
# First, include header overrides
include_directories(hacks)
-
######## 3rd Party Libs ########
# Find the required Qt parts
@@ -48,6 +47,7 @@ find_package(ZLIB REQUIRED)
# Add quazip
add_subdirectory(quazip)
+include_directories(quazip)
# Add bspatch
add_subdirectory(patchlib)
@@ -173,6 +173,7 @@ gui/consolewindow.h
gui/instancemodel.h
gui/instancedelegate.h
gui/versionselectdialog.h
+gui/iconcache.h
multimc_pragma.h
@@ -202,6 +203,7 @@ gui/consolewindow.cpp
gui/instancemodel.cpp
gui/instancedelegate.cpp
gui/versionselectdialog.cpp
+gui/iconcache.cpp
java/javautils.cpp
java/annotations.cpp
@@ -263,6 +265,12 @@ libUtil libSettings libMultiMC libGroupView
${MultiMC_LINK_ADDITIONAL_LIBS})
ADD_DEPENDENCIES(MultiMC MultiMCLauncher libUtil libSettings libMultiMC libGroupView)
+IF(DEFINED MMC_KEYRING_TEST)
+# test.cpp
+ADD_EXECUTABLE(Test test.cpp)
+QT5_USE_MODULES(Test Core)
+TARGET_LINK_LIBRARIES(Test libUtil libSettings)
+ENDIF()
################################ INSTALLATION AND PACKAGING ################################
# use QtCreator's QTDIR var
diff --git a/gui/consolewindow.cpp b/gui/consolewindow.cpp
index 1d84fe04..811900a2 100644
--- a/gui/consolewindow.cpp
+++ b/gui/consolewindow.cpp
@@ -4,70 +4,71 @@
#include <QScrollBar>
ConsoleWindow::ConsoleWindow(QWidget *parent) :
- QDialog(parent),
- ui(new Ui::ConsoleWindow),
- m_mayclose(true)
+ QDialog(parent),
+ ui(new Ui::ConsoleWindow),
+ m_mayclose(true)
{
- ui->setupUi(this);
+ ui->setupUi(this);
}
ConsoleWindow::~ConsoleWindow()
{
- delete ui;
+ delete ui;
}
void ConsoleWindow::writeColor(QString text, const char *color)
{
- // append a paragraph
- if (color != nullptr)
- ui->text->appendHtml(QString("<font color=%1>%2</font>").arg(color).arg(text));
- else
- ui->text->appendPlainText(text);
- // scroll down
- QScrollBar *bar = ui->text->verticalScrollBar();
- bar->setValue(bar->maximum());
+ // append a paragraph
+ if (color != nullptr)
+ ui->text->appendHtml(QString("<font color=%1>%2</font>").arg(color).arg(text));
+ else
+ ui->text->appendPlainText(text);
+ // scroll down
+ QScrollBar *bar = ui->text->verticalScrollBar();
+ bar->setValue(bar->maximum());
}
-void ConsoleWindow::write(QString data, WriteMode mode)
+void ConsoleWindow::write(QString data, MessageLevel::Enum mode)
{
- if (data.endsWith('\n'))
- data = data.left(data.length()-1);
- QStringList paragraphs = data.split('\n');
- QListIterator<QString> iter(paragraphs);
- if (mode == MULTIMC)
- while(iter.hasNext())
- writeColor(iter.next(), "blue");
- else if (mode == ERROR)
- while(iter.hasNext())
- writeColor(iter.next(), "red");
- else
- while(iter.hasNext())
- writeColor(iter.next());
+ if (data.endsWith('\n'))
+ data = data.left(data.length()-1);
+ QStringList paragraphs = data.split('\n');
+ QListIterator<QString> iter(paragraphs);
+ if (mode == MessageLevel::MultiMC)
+ while(iter.hasNext())
+ writeColor(iter.next(), "blue");
+ else if (mode == MessageLevel::Error)
+ while(iter.hasNext())
+ writeColor(iter.next(), "red");
+ // TODO: implement other MessageLevels
+ else
+ while(iter.hasNext())
+ writeColor(iter.next());
}
void ConsoleWindow::clear()
{
- ui->text->clear();
+ ui->text->clear();
}
void ConsoleWindow::on_closeButton_clicked()
{
- close();
+ close();
}
void ConsoleWindow::setMayClose(bool mayclose)
{
- m_mayclose = mayclose;
- if (mayclose)
- ui->closeButton->setEnabled(true);
- else
- ui->closeButton->setEnabled(false);
+ m_mayclose = mayclose;
+ if (mayclose)
+ ui->closeButton->setEnabled(true);
+ else
+ ui->closeButton->setEnabled(false);
}
void ConsoleWindow::closeEvent(QCloseEvent * event)
{
- if(!m_mayclose)
- event->ignore();
- else
- QDialog::closeEvent(event);
+ if(!m_mayclose)
+ event->ignore();
+ else
+ QDialog::closeEvent(event);
}
diff --git a/gui/consolewindow.h b/gui/consolewindow.h
index 1d322afb..5490bc92 100644
--- a/gui/consolewindow.h
+++ b/gui/consolewindow.h
@@ -2,6 +2,7 @@
#define CONSOLEWINDOW_H
#include <QDialog>
+#include "minecraftprocess.h"
namespace Ui {
class ConsoleWindow;
@@ -9,61 +10,51 @@ class ConsoleWindow;
class ConsoleWindow : public QDialog
{
- Q_OBJECT
+ Q_OBJECT
public:
- /**
- * @brief The WriteMode enum
- * defines how stuff is displayed
- */
- enum WriteMode {
- DEFAULT,
- ERROR,
- MULTIMC
- };
+ explicit ConsoleWindow(QWidget *parent = 0);
+ ~ConsoleWindow();
- explicit ConsoleWindow(QWidget *parent = 0);
- ~ConsoleWindow();
-
- /**
- * @brief specify if the window is allowed to close
- * @param mayclose
- * used to keep it alive while MC runs
- */
- void setMayClose(bool mayclose);
+ /**
+ * @brief specify if the window is allowed to close
+ * @param mayclose
+ * used to keep it alive while MC runs
+ */
+ void setMayClose(bool mayclose);
public slots:
- /**
- * @brief write a string
- * @param data the string
- * @param mode the WriteMode
- * lines have to be put through this as a whole!
- */
- void write(QString data, WriteMode mode=MULTIMC);
-
- /**
- * @brief write a colored paragraph
- * @param data the string
- * @param color the css color name
- * this will only insert a single paragraph.
- * \n are ignored. a real \n is always appended.
- */
- void writeColor(QString data, const char *color=nullptr);
-
- /**
- * @brief clear the text widget
- */
- void clear();
+ /**
+ * @brief write a string
+ * @param data the string
+ * @param mode the WriteMode
+ * lines have to be put through this as a whole!
+ */
+ void write(QString data, MessageLevel::Enum level=MessageLevel::MultiMC);
+
+ /**
+ * @brief write a colored paragraph
+ * @param data the string
+ * @param color the css color name
+ * this will only insert a single paragraph.
+ * \n are ignored. a real \n is always appended.
+ */
+ void writeColor(QString data, const char *color=nullptr);
+
+ /**
+ * @brief clear the text widget
+ */
+ void clear();
private slots:
- void on_closeButton_clicked();
+ void on_closeButton_clicked();
protected:
- void closeEvent(QCloseEvent *);
+ void closeEvent(QCloseEvent *);
private:
- Ui::ConsoleWindow *ui;
- bool m_mayclose;
+ Ui::ConsoleWindow *ui;
+ bool m_mayclose;
};
#endif // CONSOLEWINDOW_H
diff --git a/gui/iconcache.cpp b/gui/iconcache.cpp
new file mode 100644
index 00000000..520a7839
--- /dev/null
+++ b/gui/iconcache.cpp
@@ -0,0 +1,127 @@
+#include "iconcache.h"
+#include <QMap>
+#include <QWebView>
+#include <QWebFrame>
+#include <QEventLoop>
+#include <QWebElement>
+
+IconCache* IconCache::m_Instance = 0;
+QMutex IconCache::mutex;
+#define MAX_SIZE 1024
+
+class Private : public QWebView
+{
+ Q_OBJECT
+
+public:
+ QString name;
+ QSize size;
+ QMap<QString, QIcon> icons;
+
+public:
+ Private()
+ {
+ connect(this, SIGNAL(loadFinished(bool)), this, SLOT(svgLoaded(bool)));
+ setFixedSize(MAX_SIZE, MAX_SIZE);
+
+ QPalette pal = palette();
+ pal.setColor(QPalette::Base, Qt::transparent);
+ setPalette(pal);
+ setAttribute(Qt::WA_OpaquePaintEvent, false);
+ size = QSize(128,128);
+ }
+ void renderSVGIcon(QString name);
+
+signals:
+ void svgRendered();
+
+private slots:
+ void svgLoaded(bool ok);
+};
+
+void Private::svgLoaded(bool ok)
+{
+ if (!ok)
+ {
+ emit svgRendered();
+ return;
+ }
+ // check for SVG root tag
+ QString root = page()->currentFrame()->documentElement().tagName();
+ if (root.compare("svg", Qt::CaseInsensitive) != 0)
+ {
+ emit svgRendered();
+ return;
+ }
+
+ // get the size of the svg image, check if it's valid
+ auto elem = page()->currentFrame()->documentElement();
+ double width = elem.attribute("width").toDouble();
+ double height = elem.attribute("height").toDouble();
+ if (width == 0.0 || height == 0.0 || width == MAX_SIZE || height == MAX_SIZE)
+ {
+ emit svgRendered();
+ return;
+ }
+
+ // create the target surface
+ QSize t = size.isValid() ? size : QSize(width, height);
+ QImage img(t, QImage::Format_ARGB32_Premultiplied);
+ img.fill(Qt::transparent);
+
+ // prepare the painter, scale to required size
+ QPainter p(&img);
+ if(size.isValid())
+ {
+ p.scale(size.width() / width, size.height() / height);
+ }
+
+ // the best quality
+ p.setRenderHint(QPainter::Antialiasing);
+ p.setRenderHint(QPainter::TextAntialiasing);
+ p.setRenderHint(QPainter::SmoothPixmapTransform);
+
+ page()->mainFrame()->render(&p,QWebFrame::ContentsLayer);
+ p.end();
+
+ icons[name] = QIcon(QPixmap::fromImage(img));
+ emit svgRendered();
+}
+
+void Private::renderSVGIcon ( QString name )
+{
+ // use event loop to wait for signal
+ QEventLoop loop;
+ this->name = name;
+ QString prefix = "qrc:/icons/instances/";
+ QObject::connect(this, SIGNAL(svgRendered()), &loop, SLOT(quit()));
+ load(QUrl(prefix + name));
+ loop.exec();
+}
+
+IconCache::IconCache():d(new Private())
+{
+}
+
+QIcon IconCache::getIcon ( QString name )
+{
+ if(name == "default")
+ name = "infinity";
+ {
+ auto iter = d->icons.find(name);
+ if(iter != d->icons.end())
+ return *iter;
+ }
+ d->renderSVGIcon(name);
+ auto iter = d->icons.find(name);
+ if(iter != d->icons.end())
+ return *iter;
+
+ // Fallback for icons that don't exist.
+ QString path = ":/icons/instances/infinity";
+ //path += name;
+ d->icons[name] = QIcon(path);
+ return d->icons[name];
+}
+
+#include "iconcache.moc" \ No newline at end of file
diff --git a/gui/iconcache.h b/gui/iconcache.h
new file mode 100644
index 00000000..5c5e4142
--- /dev/null
+++ b/gui/iconcache.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <QMutex>
+#include <QtGui/QIcon>
+
+class Private;
+
+class IconCache
+{
+public:
+ static IconCache* instance()
+ {
+ if (!m_Instance)
+ {
+ mutex.lock();
+ if (!m_Instance)
+ m_Instance = new IconCache;
+ mutex.unlock();
+ }
+ return m_Instance;
+ }
+
+ static void drop()
+ {
+ mutex.lock();
+ delete m_Instance;
+ m_Instance = 0;
+ mutex.unlock();
+ }
+
+ QIcon getIcon(QString name);
+
+private:
+ IconCache();
+ // hide copy constructor
+ IconCache(const IconCache &);
+ // hide assign op
+ IconCache& operator=(const IconCache &);
+ static IconCache* m_Instance;
+ static QMutex mutex;
+ Private* d;
+};
+ \ No newline at end of file
diff --git a/gui/instancedelegate.cpp b/gui/instancedelegate.cpp
index dac2dacb..86b7d399 100644
--- a/gui/instancedelegate.cpp
+++ b/gui/instancedelegate.cpp
@@ -33,9 +33,15 @@ ListViewDelegate::ListViewDelegate ( QObject* parent ) : QStyledItemDelegate ( p
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 ) );
+ if ((option.state & QStyle::State_Selected))
+ painter->fillRect ( rect, option.palette.brush ( QPalette::Highlight ) );
+ else
+ {
+ QColor backgroundColor = option.palette.color(QPalette::Background);
+ backgroundColor.setAlpha(160);
+ painter->fillRect ( rect, QBrush(backgroundColor) );
+ }
+
}
void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect)
diff --git a/gui/instancemodel.cpp b/gui/instancemodel.cpp
index 73d0dbc1..8db985e8 100644
--- a/gui/instancemodel.cpp
+++ b/gui/instancemodel.cpp
@@ -1,13 +1,38 @@
#include "instancemodel.h"
#include <instance.h>
#include <QIcon>
+#include "iconcache.h"
InstanceModel::InstanceModel ( const InstanceList& instances, QObject *parent )
: QAbstractListModel ( parent ), m_instances ( &instances )
{
- cachedIcon = QIcon(":/icons/multimc/scalable/apps/multimc.svg");
+ currentInstancesNumber = m_instances->count();
+ connect(m_instances,SIGNAL(instanceAdded(int)),this,SLOT(onInstanceAdded(int)));
+ connect(m_instances,SIGNAL(instanceChanged(int)),this,SLOT(onInstanceChanged(int)));
+ connect(m_instances,SIGNAL(invalidated()),this,SLOT(onInvalidated()));
}
+void InstanceModel::onInstanceAdded ( int index )
+{
+ beginInsertRows(QModelIndex(), index, index);
+ currentInstancesNumber ++;
+ endInsertRows();
+}
+
+void InstanceModel::onInstanceChanged ( int index )
+{
+ QModelIndex mx = InstanceModel::index(index);
+ dataChanged(mx,mx);
+}
+
+void InstanceModel::onInvalidated()
+{
+ beginResetModel();
+ currentInstancesNumber = m_instances->count();
+ endResetModel();
+}
+
+
int InstanceModel::rowCount ( const QModelIndex& parent ) const
{
Q_UNUSED ( parent );
@@ -17,7 +42,7 @@ int InstanceModel::rowCount ( const QModelIndex& parent ) const
QModelIndex InstanceModel::index ( int row, int column, const QModelIndex& parent ) const
{
Q_UNUSED ( parent );
- if ( row < 0 || row >= m_instances->count() )
+ if ( row < 0 || row >= currentInstancesNumber )
return QModelIndex();
return createIndex ( row, column, ( void* ) m_instances->at ( row ).data() );
}
@@ -46,14 +71,22 @@ QVariant InstanceModel::data ( const QModelIndex& index, int role ) const
}
case Qt::DecorationRole:
{
- // FIXME: replace with an icon cache
- return cachedIcon;
+ IconCache * ic = IconCache::instance();
+ // FIXME: replace with an icon cache/renderer
+ /*
+ QString path = ":/icons/instances/";
+ path += pdata->iconKey();
+ QIcon icon(path);
+ */
+ QString key = pdata->iconKey();
+ return ic->getIcon(key);
+ //else return QIcon(":/icons/multimc/scalable/apps/multimc.svg");
}
// for now.
case KCategorizedSortFilterProxyModel::CategorySortRole:
case KCategorizedSortFilterProxyModel::CategoryDisplayRole:
{
- return "IT'S A GROUP";
+ return pdata->group();
}
default:
break;
diff --git a/gui/instancemodel.h b/gui/instancemodel.h
index 995c51ec..208ee68e 100644
--- a/gui/instancemodel.h
+++ b/gui/instancemodel.h
@@ -22,9 +22,14 @@ public:
QVariant data ( const QModelIndex& index, int role ) const;
Qt::ItemFlags flags ( const QModelIndex& index ) const;
+public slots:
+ void onInstanceAdded(int index);
+ void onInstanceChanged(int index);
+ void onInvalidated();
+
private:
const InstanceList* m_instances;
- QIcon cachedIcon;
+ int currentInstancesNumber;
};
class InstanceProxyModel : public KCategorizedSortFilterProxyModel
diff --git a/gui/logindialog.cpp b/gui/logindialog.cpp
index 426757a9..842a4541 100644
--- a/gui/logindialog.cpp
+++ b/gui/logindialog.cpp
@@ -15,12 +15,18 @@
#include "logindialog.h"
#include "ui_logindialog.h"
+#include "keyring.h"
LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) :
QDialog(parent),
ui(new Ui::LoginDialog)
{
ui->setupUi(this);
+ //FIXME: translateable?
+ ui->usernameTextBox->lineEdit()->setPlaceholderText(QApplication::translate("LoginDialog", "Name", 0));
+
+ connect(ui->usernameTextBox, SIGNAL(currentTextChanged(QString)), this, SLOT(userTextChanged(QString)));
+ connect(ui->forgetButton, SIGNAL(clicked(bool)), this, SLOT(forgetCurrentUser()));
if (loginErrMsg.isEmpty())
ui->loginErrorLabel->setVisible(false);
@@ -33,6 +39,10 @@ LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) :
resize(minimumSizeHint());
layout()->setSizeConstraint(QLayout::SetFixedSize);
+ Keyring * k = Keyring::instance();
+ QStringList accounts = k->getStoredAccounts("minecraft");
+ ui->usernameTextBox->addItems(accounts);
+
}
LoginDialog::~LoginDialog()
@@ -42,10 +52,49 @@ LoginDialog::~LoginDialog()
QString LoginDialog::getUsername() const
{
- return ui->usernameTextBox->text();
+ return ui->usernameTextBox->currentText();
}
QString LoginDialog::getPassword() const
{
return ui->passwordTextBox->text();
}
+
+void LoginDialog::forgetCurrentUser()
+{
+ Keyring * k = Keyring::instance();
+ QString acct = ui->usernameTextBox->currentText();
+ k->removeStoredAccount("minecraft", acct);
+ ui->passwordTextBox->clear();
+ int index = ui->usernameTextBox->findText(acct);
+ if(index != -1)
+ ui->usernameTextBox->removeItem(index);
+}
+
+void LoginDialog::userTextChanged ( const QString& user )
+{
+ Keyring * k = Keyring::instance();
+ QString acct = ui->usernameTextBox->currentText();
+ QString passwd = k->getPassword("minecraft",acct);
+ ui->passwordTextBox->setText(passwd);
+}
+
+
+void LoginDialog::accept()
+{
+ bool saveName = ui->rememberUsernameCheckbox->isChecked();
+ bool savePass = ui->rememberPasswordCheckbox->isChecked();
+ if(saveName)
+ {
+ Keyring * k = Keyring::instance();
+ if(savePass)
+ {
+ k->storePassword("minecraft",getUsername(),getPassword());
+ }
+ else
+ {
+ k->storePassword("minecraft",getUsername(),QString());
+ }
+ }
+ QDialog::accept();
+}
diff --git a/gui/logindialog.h b/gui/logindialog.h
index 1b70dcd5..5f4410f5 100644
--- a/gui/logindialog.h
+++ b/gui/logindialog.h
@@ -32,7 +32,11 @@ public:
QString getUsername() const;
QString getPassword() const;
-
+
+public slots:
+ virtual void accept();
+ virtual void userTextChanged(const QString& user);
+ virtual void forgetCurrentUser();
private:
Ui::LoginDialog *ui;
};
diff --git a/gui/logindialog.ui b/gui/logindialog.ui
index ce41d2f5..0aaad52b 100644
--- a/gui/logindialog.ui
+++ b/gui/logindialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>365</width>
- <height>145</height>
+ <width>476</width>
+ <height>168</height>
</rect>
</property>
<property name="windowTitle">
@@ -31,9 +31,9 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QLineEdit" name="usernameTextBox">
- <property name="placeholderText">
- <string>Username</string>
+ <widget class="QComboBox" name="usernameTextBox">
+ <property name="editable">
+ <bool>true</bool>
</property>
</widget>
</item>
@@ -54,20 +54,23 @@
</property>
</widget>
</item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="checkboxLayout">
- <item>
- <widget class="QPushButton" name="forceUpdateButton">
- <property name="text">
- <string>&amp;Force Update</string>
+ <item row="0" column="2" rowspan="2">
+ <widget class="QPushButton" name="forgetButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="checkable">
- <bool>true</bool>
+ <property name="text">
+ <string>Forget</string>
</property>
</widget>
</item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="checkboxLayout">
<item>
<widget class="QCheckBox" name="rememberUsernameCheckbox">
<property name="sizePolicy">
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index ca681098..7761afe8 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -40,6 +40,7 @@
#include "gui/browserdialog.h"
#include "gui/aboutdialog.h"
#include "gui/versionselectdialog.h"
+#include "gui/consolewindow.h"
#include "kcategorizedview.h"
#include "kcategorydrawer.h"
@@ -50,6 +51,7 @@
#include "logintask.h"
#include <instance.h>
+#include "minecraftprocess.h"
#include "instancemodel.h"
#include "instancedelegate.h"
@@ -65,11 +67,25 @@ MainWindow::MainWindow ( QWidget *parent ) :
{
ui->setupUi ( this );
// Create the widget
- instList.loadList();
-
view = new KCategorizedView ( ui->centralWidget );
drawer = new KCategoryDrawer ( view );
-
+ /*
+ QPalette pal = view->palette();
+ pal.setBrush(QPalette::Base, QBrush(QPixmap(QString::fromUtf8(":/backgrounds/kitteh"))));
+ view->setPalette(pal);
+ */
+ /*
+ view->setStyleSheet(
+ "QListView\
+ {\
+ background-image: url(:/backgrounds/kitteh);\
+ background-attachment: fixed;\
+ background-clip: padding;\
+ background-position: top right;\
+ background-repeat: none;\
+ background-color:palette(base);\
+ }");
+ */
view->setSelectionMode ( QAbstractItemView::SingleSelection );
//view->setSpacing( KDialog::spacingHint() );
view->setCategoryDrawer ( drawer );
@@ -82,6 +98,7 @@ MainWindow::MainWindow ( QWidget *parent ) :
auto delegate = new ListViewDelegate();
view->setItemDelegate(delegate);
view->setSpacing(10);
+ view->setUniformItemWidths(true);
model = new InstanceModel ( instList,this );
proxymodel = new InstanceProxyModel ( this );
@@ -101,7 +118,14 @@ MainWindow::MainWindow ( QWidget *parent ) :
view->setModel ( proxymodel );
connect(view, SIGNAL(doubleClicked(const QModelIndex &)),
this, SLOT(instanceActivated(const QModelIndex &)));
-
+
+ // Load the instances.
+ instList.loadList();
+ // just a test
+ /*
+ instList.at(0)->setGroup("TEST GROUP");
+ instList.at(0)->setName("TEST ITEM");
+ */
}
MainWindow::~MainWindow()
@@ -126,6 +150,18 @@ void MainWindow::on_actionAddInstance_triggered()
newInstDlg->exec();
}
+void MainWindow::on_actionChangeInstGroup_triggered()
+{
+ Instance* inst = selectedInstance();
+ if(inst)
+ {
+ QString name ( inst->group() );
+ name = QInputDialog::getText ( this, tr ( "Group name" ), tr ( "Enter a new group name." ), QLineEdit::Normal, name );
+ inst->setGroup(name);
+ }
+}
+
+
void MainWindow::on_actionViewInstanceFolder_triggered()
{
openInDefaultProgram ( globalSettings->get ( "InstanceDir" ).toString() );
@@ -196,13 +232,31 @@ void MainWindow::on_instanceView_customContextMenuRequested ( const QPoint &pos
instContextMenu->exec ( view->mapToGlobal ( pos ) );
}
+Instance* MainWindow::selectedInstance()
+{
+ QAbstractItemView * iv = view;
+ auto smodel = iv->selectionModel();
+ QModelIndex mindex;
+ if(smodel->hasSelection())
+ {
+ auto rows = smodel->selectedRows();
+ mindex = rows.at(0);
+ }
+
+ if(mindex.isValid())
+ {
+ return (Instance *) mindex.data(InstanceModel::InstancePointerRole).value<void *>();
+ }
+ else
+ return nullptr;
+}
+
void MainWindow::on_actionLaunchInstance_triggered()
{
- QModelIndex index = view->currentIndex();
- if(index.isValid())
+ Instance* inst = selectedInstance();
+ if(inst)
{
- Instance * inst = (Instance *) index.data(InstanceModel::InstancePointerRole).value<void *>();
doLogin(inst->id());
}
}
@@ -226,9 +280,27 @@ void MainWindow::doLogin ( QString inst, const QString& errorMsg )
void MainWindow::onLoginComplete ( QString inst, LoginResponse response )
{
+ // TODO: console
+ console = new ConsoleWindow();
+ auto instance = instList.getInstanceById(inst);
+ if(instance)
+ {
+ proc = new MinecraftProcess(instance, response.username(), response.sessionID());
+
+ console->show();
+ //connect(proc, SIGNAL(ended()), SLOT(onTerminated()));
+ connect(proc, SIGNAL(log(QString,MessageLevel::Enum)), console, SLOT(write(QString,MessageLevel::Enum)));
+ proc->launch();
+ }
+ else
+ {
+
+ }
+ /*
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 )
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index 53df9f02..896fe9f1 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -26,6 +26,8 @@ class InstanceModel;
class InstanceProxyModel;
class KCategorizedView;
class KCategoryDrawer;
+class MinecraftProcess;
+class ConsoleWindow;
namespace Ui
{
@@ -44,14 +46,19 @@ public:
// Browser Dialog
void openWebPage(QUrl url);
+
+private:
+ Instance *selectedInstance();
private slots:
void on_actionAbout_triggered();
void on_actionAddInstance_triggered();
- void on_actionViewInstanceFolder_triggered();
+ void on_actionChangeInstGroup_triggered();
+ void on_actionViewInstanceFolder_triggered();
+
void on_actionRefresh_triggered();
void on_actionViewCentralModsFolder_triggered();
@@ -91,6 +98,8 @@ private:
InstanceModel * model;
InstanceProxyModel * proxymodel;
InstanceList instList;
+ MinecraftProcess *proc;
+ ConsoleWindow *console;
};
#endif // MAINWINDOW_H
diff --git a/gui/settingsdialog.ui b/gui/settingsdialog.ui
index d30f56bb..6e6d115f 100644
--- a/gui/settingsdialog.ui
+++ b/gui/settingsdialog.ui
@@ -6,10 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
- <width>400</width>
- <height>420</height>
+ <width>453</width>
+ <height>563</height>
</rect>
</property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="windowTitle">
<string>Settings</string>
</property>
@@ -400,31 +406,27 @@
<item row="1" column="1">
<widget class="QLineEdit" name="postExitCmdTextBox"/>
</item>
- <item row="2" column="0" colspan="2">
- <widget class="QLabel" name="labelCustomCmdsDescription">
- <property name="text">
- <string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
<item>
- <spacer name="verticalSpacerJava">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QLabel" name="labelCustomCmdsDescription">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
+ <property name="text">
+ <string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
</property>
- </spacer>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
diff --git a/java/constants.h b/java/constants.h
index 2f968d2e..61aa5687 100644
--- a/java/constants.h
+++ b/java/constants.h
@@ -7,7 +7,7 @@ namespace java
class constant
{
public:
- enum type_t:uint8_t
+ enum type_t : uint8_t
{
j_hole = 0, // HACK: this is a hole in the array, because java is crazy
j_string_data = 1,
@@ -22,6 +22,7 @@ namespace java
j_interface_methodref = 11,
j_nameandtype = 12
} type;
+
constant(util::membuffer & buf )
{
buf.read(type);
@@ -66,10 +67,12 @@ namespace java
break;
}
}
+
constant(int fake)
{
type = j_hole;
}
+
std::string toString()
{
std::ostringstream ss;
@@ -143,6 +146,7 @@ namespace java
} name_and_type;
};
};
+
/**
* A helper class that represents the custom container used in Java class file for storage of constants
*/
@@ -181,7 +185,7 @@ namespace java
index++;
}
}
- };
+ }
typedef std::vector<java::constant> container_type;
/**
* Access constants based on jar file index numbers (index of the first element is 1)
diff --git a/java/javautils.cpp b/java/javautils.cpp
index a07d1541..4a359031 100644
--- a/java/javautils.cpp
+++ b/java/javautils.cpp
@@ -1,69 +1,81 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
#include "multimc_pragma.h"
#include "classfile.h"
#include "javautils.h"
-//#include <wx/zipstrm.h>
-#include <memory>
-//#include <wx/wfstream.h>
-//#include "mcversionlist.h"
+
+#include <QFile>
+#include <quazipfile.h>
namespace javautils
{
-QString GetMinecraftJarVersion(QString jar)
+
+QString GetMinecraftJarVersion(QString jarName)
{
- return "Unknown";
- /*
- wxString fullpath = jar.GetFullPath();
- wxString version = MCVer_Unknown;
- if(!jar.FileExists())
+ QString version = MCVer_Unknown;
+
+ // check if minecraft.jar exists
+ QFile jar(jarName);
+ if (!jar.exists())
return version;
- std::auto_ptr<wxZipEntry> entry;
- // convert the local name we are looking for into the internal format
- wxString name = wxZipEntry::GetInternalName("net/minecraft/client/Minecraft.class",wxPATH_UNIX);
- // open the zip
- wxFFileInputStream inStream(jar.GetFullPath());
- wxZipInputStream zipIn(inStream);
+ // open minecraft.jar
+ QuaZip zip(&jar);
+ if (!zip.open(QuaZip::mdUnzip))
+ return version;
- // call GetNextEntry() until the required internal name is found
- do
- {
- entry.reset(zipIn.GetNextEntry());
- }
- while (entry.get() != NULL && entry->GetInternalName() != name);
- auto myentry = entry.get();
- if (myentry == NULL)
+ // open Minecraft.class
+ zip.setCurrentFile("net/minecraft/client/Minecraft.class", QuaZip::csSensitive);
+ QuaZipFile Minecraft(&zip);
+ if (!Minecraft.open(QuaZipFile::ReadOnly))
return version;
-
- // we got the entry, read the data
- std::size_t size = myentry->GetSize();
- char *classdata = new char[size];
- zipIn.Read(classdata,size);
- try
- {
- char * temp = classdata;
- java::classfile Minecraft_jar(temp,size);
- auto cnst = Minecraft_jar.constants;
- auto iter = cnst.begin();
- while (iter != cnst.end())
+
+ // read Minecraft.class
+ qint64 size = Minecraft.size();
+ char *classfile = new char[size];
+ Minecraft.read(classfile, size);
+
+ // parse Minecraft.class
+ try {
+ char *temp = classfile;
+ java::classfile MinecraftClass(temp, size);
+ java::constant_pool constants = MinecraftClass.constants;
+ for(java::constant_pool::container_type::const_iterator iter=constants.begin();
+ iter != constants.end(); iter++)
{
const java::constant & constant = *iter;
- if(constant.type != java::constant::j_string_data)
- {
- iter++;
+ if (constant.type != java::constant::j_string_data)
continue;
- }
- auto & str = constant.str_data;
- const char * lookfor = "Minecraft Minecraft "; // length = 20
- if(str.compare(0,20,lookfor) == 0)
+ const std::string & str = constant.str_data;
+ if (str.compare(0, 20, "Minecraft Minecraft ") == 0)
{
version = str.substr(20).data();
break;
}
- iter++;
}
- } catch(java::classfile_exception &){}
- delete[] classdata;
+ } catch(java::classfile_exception &) {}
+
+ // clean up
+ delete[] classfile;
+ Minecraft.close();
+ zip.close();
+ jar.close();
+
return version;
- */
}
-} \ No newline at end of file
+
+}
diff --git a/java/javautils.h b/java/javautils.h
index c8b5a793..883eff1d 100644
--- a/java/javautils.h
+++ b/java/javautils.h
@@ -1,9 +1,28 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
#pragma once
#include <QString>
+
+#define MCVer_Unknown "Unknown"
+
namespace javautils
{
- /*
- * Get the version from a minecraft.jar by parsing its class files. Expensive!
+ /**
+ * @brief Get the version from a minecraft.jar by parsing its class files. Expensive!
*/
QString GetMinecraftJarVersion(QString jar);
-} \ No newline at end of file
+}
diff --git a/libgroupview/include/kcategorizedview.h b/libgroupview/include/kcategorizedview.h
index df962131..e0ac31fd 100644
--- a/libgroupview/include/kcategorizedview.h
+++ b/libgroupview/include/kcategorizedview.h
@@ -208,6 +208,16 @@ public:
*/
virtual void reset();
+ /**
+ * Signify that all item delegates size hints return the same fixed size
+ */
+ void setUniformItemWidths(bool enable);
+
+ /**
+ * Do all item delegate size hints return the same fixed size?
+ */
+ bool uniformItemWidths() const;
+
protected:
/**
* Reimplemented from QWidget.
diff --git a/libgroupview/src/kcategorizedview.cpp b/libgroupview/src/kcategorizedview.cpp
index 5b7c8d42..4da4271f 100644
--- a/libgroupview/src/kcategorizedview.cpp
+++ b/libgroupview/src/kcategorizedview.cpp
@@ -115,6 +115,7 @@ KCategorizedView::Private::Private ( KCategorizedView *q )
, hoveredIndex ( QModelIndex() )
, pressedPosition ( QPoint() )
, rubberBandRect ( QRect() )
+ , constantItemWidth( 0 )
{
}
@@ -447,11 +448,12 @@ void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index
}
else
{
- if ( q->uniformItemSizes() )
+ if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ )
{
const int relativeRow = index.row() - firstIndexRow;
const QSize itemSize = q->sizeHintForIndex ( index );
- const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() ) / ( itemSize.width() + q->spacing() ), 1 );
+ //HACK: Why is the -2 needed?
+ const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 );
if ( q->layoutDirection() == Qt::LeftToRight )
{
item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
@@ -478,7 +480,8 @@ void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index
Q_FOREVER
{
prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() );
- const QRect tempRect = q->visualRect ( prevIndex );
+ QRect tempRect = q->visualRect ( prevIndex );
+ tempRect = mapFromViewport ( tempRect );
if ( tempRect.topLeft().y() < prevRect.topLeft().y() )
{
break;
@@ -622,6 +625,18 @@ void KCategorizedView::setModel ( QAbstractItemModel *model )
}
}
+
+void KCategorizedView::setUniformItemWidths(bool enable)
+{
+ d->constantItemWidth = enable;
+}
+
+
+bool KCategorizedView::uniformItemWidths() const
+{
+ return d->constantItemWidth;
+}
+
void KCategorizedView::setGridSize ( const QSize &size )
{
setGridSizeOwn ( size );
@@ -1294,13 +1309,14 @@ QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction,
}
case MoveDown:
{
- if ( d->hasGrid() || uniformItemSizes() )
+ if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() )
{
const QModelIndex current = currentIndex();
const QSize itemSize = d->hasGrid() ? gridSize()
: sizeHintForIndex ( current );
const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
- const int maxItemsPerRow = qMax ( d->viewportWidth() / itemSize.width(), 1 );
+ //HACK: Why is the -2 needed?
+ const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 );
const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
block.items.count();
@@ -1334,13 +1350,14 @@ QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction,
}
case MoveUp:
{
- if ( d->hasGrid() || uniformItemSizes() )
+ if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() )
{
const QModelIndex current = currentIndex();
const QSize itemSize = d->hasGrid() ? gridSize()
: sizeHintForIndex ( current );
const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
- const int maxItemsPerRow = qMax ( d->viewportWidth() / itemSize.width(), 1 );
+ //HACK: Why is the -2 needed?
+ const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 );
const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
if ( canMove )
diff --git a/libgroupview/src/kcategorizedview_p.h b/libgroupview/src/kcategorizedview_p.h
index 6dafa382..13809312 100644
--- a/libgroupview/src/kcategorizedview_p.h
+++ b/libgroupview/src/kcategorizedview_p.h
@@ -32,126 +32,128 @@ class KCategoryDrawerV3;
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;
+ 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;
+ bool constantItemWidth;
+
+ Block *hoveredBlock;
+ QString hoveredCategory;
+ QModelIndex hoveredIndex;
+
+ QPoint pressedPosition;
+ QRect rubberBandRect;
+
+ QHash<QString, Block> blocks;
};
#endif // KCATEGORIZEDVIEW_P_H
+
diff --git a/libmultimc/include/instance.h b/libmultimc/include/instance.h
index c41e6718..258a0dab 100644
--- a/libmultimc/include/instance.h
+++ b/libmultimc/include/instance.h
@@ -65,6 +65,9 @@ class LIBMULTIMC_EXPORT Instance : public QObject
//! The instance's notes.
Q_PROPERTY(QString notes READ notes WRITE setNotes)
+ //! The instance's group.
+ Q_PROPERTY(QString group READ group WRITE setGroup)
+
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
* If this is true, when the instance launches, its jar mods will be
@@ -173,14 +176,29 @@ public:
//// General Info ////
virtual QString name() { return settings().get("name").toString(); }
- virtual void setName(QString val) { settings().set("name", val); }
+ virtual void setName(QString val)
+ {
+ settings().set("name", val);
+ emit propertiesChanged(this);
+ }
virtual QString iconKey() const { return settings().get("iconKey").toString(); }
- virtual void setIconKey(QString val) { settings().set("iconKey", val); }
+ virtual void setIconKey(QString val)
+ {
+ settings().set("iconKey", val);
+ emit propertiesChanged(this);
+ }
virtual QString notes() const { return settings().get("notes").toString(); }
virtual void setNotes(QString val) { settings().set("notes", val); }
+ virtual QString group() const { return m_group; }
+ virtual void setGroup(QString val)
+ {
+ m_group = val;
+ emit propertiesChanged(this);
+ }
+
virtual bool shouldRebuild() const { return settings().get("NeedsRebuild").toBool(); }
virtual void setShouldRebuild(bool val) { settings().set("NeedsRebuild", val); }
@@ -202,7 +220,10 @@ public:
virtual qint64 lastLaunch() { return settings().get("lastLaunchTime").value<qint64>(); }
virtual void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch())
- { settings().set("lastLaunchTime", val); }
+ {
+ settings().set("lastLaunchTime", val);
+ emit propertiesChanged(this);
+ }
////// Directories //////
@@ -277,8 +298,15 @@ public:
*/
virtual SettingsObject &settings() const;
+signals:
+ /*!
+ * \brief Signal emitted when properties relevant to the instance view change
+ */
+ void propertiesChanged(Instance * inst);
+
private:
QString m_rootDir;
+ QString m_group;
SettingsObject *m_settings;
};
diff --git a/libmultimc/include/instancelist.h b/libmultimc/include/instancelist.h
index d4e7556a..a0d8788a 100644
--- a/libmultimc/include/instancelist.h
+++ b/libmultimc/include/instancelist.h
@@ -17,16 +17,14 @@
#define INSTANCELIST_H
#include <QObject>
-
#include <QSharedPointer>
-#include "siglist.h"
-
+#include "instance.h"
#include "libmmc_config.h"
class Instance;
-class LIBMULTIMC_EXPORT InstanceList : public QObject, public SigList< QSharedPointer<Instance> >
+class LIBMULTIMC_EXPORT InstanceList : public QObject
{
Q_OBJECT
public:
@@ -46,14 +44,46 @@ public:
QString instDir() const { return m_instDir; }
/*!
- * \brief Loads the instance list.
+ * \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
- DEFINE_SIGLIST_SIGNALS(QSharedPointer<Instance>);
- SETUP_SIGLIST_SIGNALS(QSharedPointer<Instance>);
+ /*!
+ * \brief Get the instance at index
+ */
+ InstancePtr at(int i) const
+ {
+ return m_instances.at(i);
+ };
+
+ /*!
+ * \brief Get the count of loaded instances
+ */
+ int count() const
+ {
+ return m_instances.count();
+ };
+
+ /// Clear all instances. Triggers notifications.
+ void clear();
+
+ /// Add an instance. Triggers notifications, returns the new index
+ int add(InstancePtr t);
+
+ /// Get an instance by ID
+ InstancePtr getInstanceById (QString id);
+
+signals:
+ void instanceAdded(int index);
+ void instanceChanged(int index);
+ void invalidated();
+
+private slots:
+ void propertiesChanged(Instance * inst);
+
protected:
QString m_instDir;
+ QList< InstancePtr > m_instances;
};
#endif // INSTANCELIST_H
diff --git a/libmultimc/include/minecraftprocess.h b/libmultimc/include/minecraftprocess.h
index 8986f7ad..ac4d6be2 100644
--- a/libmultimc/include/minecraftprocess.h
+++ b/libmultimc/include/minecraftprocess.h
@@ -6,7 +6,7 @@
* 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
+ * 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,
@@ -24,73 +24,97 @@
#include "libmmc_config.h"
/**
+ * @brief the MessageLevel Enum
+ * defines what level a message is
+ */
+namespace MessageLevel {
+enum LIBMULTIMC_EXPORT Enum {
+ MultiMC, /**< MultiMC Messages */
+ Debug, /**< Debug Messages */
+ Info, /**< Info Messages */
+ Message, /**< Standard Messages */
+ Warning, /**< Warnings */
+ Error, /**< Errors */
+ Fatal /**< Fatal Errors */
+};
+}
+
+/**
* @file data/minecraftprocess.h
* @brief The MinecraftProcess class
*/
class LIBMULTIMC_EXPORT MinecraftProcess : public QProcess
{
- Q_OBJECT
+ Q_OBJECT
public:
- /**
- * @brief MinecraftProcess constructor
- * @param inst the Instance pointer to launch
- * @param user the minecraft username
- * @param session the minecraft session id
- * @param console the instance console window
- */
- MinecraftProcess(InstancePtr inst, QString user, QString session);
-
- /**
- * @brief launch minecraft
- */
- void launch();
-
- /**
- * @brief extract the instance icon
- * @param inst the instance
- * @param destination the destination path
- */
- static inline void extractIcon(InstancePtr inst, QString destination);
-
- /**
- * @brief extract the MultiMC launcher.jar
- * @param destination the destination path
- */
- static inline void extractLauncher(QString destination);
-
- /**
- * @brief prepare the launch by extracting icon and launcher
- * @param inst the instance
- */
- static void prepare(InstancePtr inst);
-
- /**
- * @brief split a string into argv items like a shell would do
- * @param args the argument string
- * @return a QStringList containing all arguments
- */
- static QStringList splitArgs(QString args);
+ /**
+ * @brief MinecraftProcess constructor
+ * @param inst the Instance pointer to launch
+ * @param user the minecraft username
+ * @param session the minecraft session id
+ * @param console the instance console window
+ */
+ MinecraftProcess(InstancePtr inst, QString user, QString session);
+
+ /**
+ * @brief launch minecraft
+ */
+ void launch();
+
+ /**
+ * @brief extract the instance icon
+ * @param inst the instance
+ * @param destination the destination path
+ */
+ static inline void extractIcon(InstancePtr inst, QString destination);
+
+ /**
+ * @brief extract the MultiMC launcher.jar
+ * @param destination the destination path
+ */
+ static inline void extractLauncher(QString destination);
+
+ /**
+ * @brief prepare the launch by extracting icon and launcher
+ * @param inst the instance
+ */
+ static void prepare(InstancePtr inst);
+
+ /**
+ * @brief split a string into argv items like a shell would do
+ * @param args the argument string
+ * @return a QStringList containing all arguments
+ */
+ static QStringList splitArgs(QString args);
signals:
- /**
- * @brief emitted when mc has finished and the PostLaunchCommand was run
- */
- void ended();
+ /**
+ * @brief emitted when mc has finished and the PostLaunchCommand was run
+ */
+ void ended();
+
+ /**
+ * @brief emitted when we want to log something
+ * @param text the text to log
+ * @param level the level to log at
+ */
+ void log(QString text, MessageLevel::Enum level=MessageLevel::MultiMC);
protected:
- InstancePtr m_instance;
- QString m_user;
- QString m_session;
- QProcess m_prepostlaunchprocess;
- QStringList m_arguments;
+ InstancePtr m_instance;
+ QString m_user;
+ QString m_session;
+ QString m_err_leftover;
+ QString m_out_leftover;
+ QProcess m_prepostlaunchprocess;
+ QStringList m_arguments;
- void genArgs();
- void log(QString text);
+ void genArgs();
protected slots:
- void finish(int, QProcess::ExitStatus status);
- void on_stdErr();
- void on_stdOut();
+ void finish(int, QProcess::ExitStatus status);
+ void on_stdErr();
+ void on_stdOut();
};
diff --git a/libmultimc/src/instance.cpp b/libmultimc/src/instance.cpp
index 1af359d1..f9e105c7 100644
--- a/libmultimc/src/instance.cpp
+++ b/libmultimc/src/instance.cpp
@@ -48,6 +48,12 @@ Instance::Instance(const QString &rootDir, QObject *parent) :
settings().registerSetting(new OverrideSetting("PostExitCommand",
globalSettings->getSetting("PostExitCommand")));
+ // Window Size
+ settings().registerSetting(new OverrideSetting("LaunchCompatMode", globalSettings->getSetting("LaunchCompatMode")));
+ settings().registerSetting(new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized")));
+ settings().registerSetting(new OverrideSetting("MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
+ settings().registerSetting(new OverrideSetting("MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
+
// Memory
settings().registerSetting(new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc")));
settings().registerSetting(new OverrideSetting("MaxMemAlloc", globalSettings->getSetting("MaxMemAlloc")));
diff --git a/libmultimc/src/instancelist.cpp b/libmultimc/src/instancelist.cpp
index 78650634..f9c525d0 100644
--- a/libmultimc/src/instancelist.cpp
+++ b/libmultimc/src/instancelist.cpp
@@ -15,17 +15,21 @@
#include "include/instancelist.h"
-#include "siglist_impl.h"
-
#include <QDir>
#include <QFile>
#include <QDirIterator>
+#include <QThread>
+#include <QTextStream>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
#include "include/instance.h"
#include "include/instanceloader.h"
#include "pathutils.h"
+const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(const QString &instDir, QObject *parent) :
QObject(parent), m_instDir("instances")
@@ -38,6 +42,104 @@ InstanceList::InstListError InstanceList::loadList()
QDir dir(m_instDir);
QDirIterator iter(dir);
+ QString groupFileName = m_instDir + "/instgroups.json";
+ // temporary map from instance ID to group name
+ QMap<QString, QString> groupMap;
+
+ // HACK: this is really an if. breaks after one iteration.
+ while (QFileInfo(groupFileName).exists())
+ {
+ QFile groupFile(groupFileName);
+
+ if (!groupFile.open(QIODevice::ReadOnly))
+ {
+ // An error occurred. Ignore it.
+ qDebug("Failed to read instance group file.");
+ break;
+ }
+
+ QTextStream in(&groupFile);
+ QString jsonStr = in.readAll();
+ groupFile.close();
+
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
+
+ if (error.error != QJsonParseError::NoError)
+ {
+ qWarning(QString("Failed to parse instance group file: %1 at offset %2").
+ arg(error.errorString(), QString::number(error.offset)).toUtf8());
+ break;
+ }
+
+ if (!jsonDoc.isObject())
+ {
+ qWarning("Invalid group file. Root entry should be an object.");
+ break;
+ }
+
+ QJsonObject rootObj = jsonDoc.object();
+
+ // Make sure the format version matches.
+ if (rootObj.value("formatVersion").toVariant().toInt() == GROUP_FILE_FORMAT_VERSION)
+ {
+ // Get the group list.
+ if (!rootObj.value("groups").isObject())
+ {
+ qWarning("Invalid group list JSON: 'groups' should be an object.");
+ break;
+ }
+
+ // Iterate through the list.
+ QJsonObject groupList = rootObj.value("groups").toObject();
+
+ for (QJsonObject::iterator iter = groupList.begin();
+ iter != groupList.end(); iter++)
+ {
+ QString groupName = iter.key();
+
+ // If not an object, complain and skip to the next one.
+ if (!iter.value().isObject())
+ {
+ qWarning(QString("Group '%1' in the group list should "
+ "be an object.").arg(groupName).toUtf8());
+ continue;
+ }
+
+ QJsonObject groupObj = iter.value().toObject();
+ /*
+ // Create the group object.
+ InstanceGroup *group = new InstanceGroup(groupName, this);
+ groups.push_back(group);
+
+ // If 'hidden' isn't a bool value, just assume it's false.
+ if (groupObj.value("hidden").isBool() && groupObj.value("hidden").toBool())
+ {
+ group->setHidden(groupObj.value("hidden").toBool());
+ }
+ */
+
+ if (!groupObj.value("instances").isArray())
+ {
+ qWarning(QString("Group '%1' in the group list is invalid. "
+ "It should contain an array "
+ "called 'instances'.").arg(groupName).toUtf8());
+ continue;
+ }
+
+ // Iterate through the list of instances in the group.
+ QJsonArray instancesArray = groupObj.value("instances").toArray();
+
+ for (QJsonArray::iterator iter2 = instancesArray.begin();
+ iter2 != instancesArray.end(); iter2++)
+ {
+ groupMap[(*iter2).toString()] = groupName;
+ }
+ }
+ }
+ break;
+ }
+ m_instances.clear();
while (iter.hasNext())
{
QString subDir = iter.next();
@@ -75,13 +177,61 @@ InstanceList::InstListError InstanceList::loadList()
else
{
QSharedPointer<Instance> inst(instPtr);
-
+ auto iter = groupMap.find(inst->id());
+ if(iter != groupMap.end())
+ {
+ inst->setGroup((*iter));
+ }
qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8());
inst->setParent(this);
- append(QSharedPointer<Instance>(inst));
+ m_instances.append(inst);
+ connect(instPtr, SIGNAL(propertiesChanged(Instance*)),this, SLOT(propertiesChanged(Instance*)));
}
}
}
-
+ emit invalidated();
return NoError;
}
+
+/// Clear all instances. Triggers notifications.
+void InstanceList::clear()
+{
+ m_instances.clear();
+ emit invalidated();
+};
+
+/// Add an instance. Triggers notifications, returns the new index
+int InstanceList::add(InstancePtr t)
+{
+ m_instances.append(t);
+ emit instanceAdded(count() - 1);
+ return count() - 1;
+}
+
+InstancePtr InstanceList::getInstanceById(QString instId)
+{
+ QListIterator<InstancePtr> iter(m_instances);
+ InstancePtr inst;
+ while(iter.hasNext())
+ {
+ inst = iter.next();
+ if (inst->id() == instId)
+ break;
+ }
+ if (inst->id() != instId)
+ return InstancePtr();
+ else
+ return iter.peekPrevious();
+}
+
+void InstanceList::propertiesChanged(Instance * inst)
+{
+ for(int i = 0; i < m_instances.count(); i++)
+ {
+ if(inst == m_instances[i].data())
+ {
+ emit instanceChanged(i);
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/libmultimc/src/minecraftprocess.cpp b/libmultimc/src/minecraftprocess.cpp
index 943d76b1..f1b63e3d 100644
--- a/libmultimc/src/minecraftprocess.cpp
+++ b/libmultimc/src/minecraftprocess.cpp
@@ -125,22 +125,41 @@ MinecraftProcess::MinecraftProcess(InstancePtr inst, QString user, QString sessi
// console window
void MinecraftProcess::on_stdErr()
{
-// if (m_console != nullptr)
-// m_console->write(readAllStandardError(), ConsoleWindow::ERROR);
+ QByteArray data = readAllStandardError();
+ QString str = m_err_leftover + QString::fromLocal8Bit(data);
+ m_err_leftover.clear();
+ QStringList lines = str.split("\n");
+ bool complete = str.endsWith("\n");
+
+ for(int i = 0; i < lines.size() - 1; i++)
+ {
+ QString & line = lines[i];
+ MessageLevel::Enum level = MessageLevel::Error;
+ if(line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || line.contains("[FINEST]") )
+ level = MessageLevel::Message;
+ if(line.contains("[SEVERE]") || line.contains("[WARNING]") || line.contains("[STDERR]"))
+ level = MessageLevel::Error;
+ emit log(lines[i].toLocal8Bit(), level);
+ }
+ if(!complete)
+ m_err_leftover = lines.last();
}
void MinecraftProcess::on_stdOut()
{
-// if (m_console != nullptr)
-// m_console->write(readAllStandardOutput(), ConsoleWindow::DEFAULT);
-}
-
-void MinecraftProcess::log(QString text)
-{
-// if (m_console != nullptr)
-// m_console->write(text);
-// else
- qDebug(qPrintable(text));
+ QByteArray data = readAllStandardOutput();
+ QString str = m_out_leftover + QString::fromLocal8Bit(data);
+ m_out_leftover.clear();
+ QStringList lines = str.split("\n");
+ bool complete = str.endsWith("\n");
+
+ for(int i = 0; i < lines.size() - 1; i++)
+ {
+ QString & line = lines[i];
+ emit log(lines[i].toLocal8Bit(), MessageLevel::Message);
+ }
+ if(!complete)
+ m_out_leftover = lines.last();
}
// exit handler
@@ -151,7 +170,7 @@ void MinecraftProcess::finish(int code, ExitStatus status)
//TODO: error handling
}
- log("Minecraft exited.");
+ emit log("Minecraft exited.");
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
@@ -191,12 +210,15 @@ void MinecraftProcess::launch()
genArgs();
- log(QString("Minecraft folder is: '%1'").arg(workingDirectory()));
- log(QString("Instance launched with arguments: '%1'").arg(m_arguments.join("' '")));
-
- start(m_instance->settings().get("JavaPath").toString(), m_arguments);
+ emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory()));
+ QString JavaPath = m_instance->settings().get("JavaPath").toString();
+ emit log(QString("Java path: '%1'").arg(JavaPath));
+ emit log(QString("Arguments: '%1'").arg(m_arguments.join("' '")));
+ start(JavaPath, m_arguments);
if (!waitForStarted())
{
+ emit log("Could not launch minecraft!");
+ return;
//TODO: error handling
}
diff --git a/libsettings/CMakeLists.txt b/libsettings/CMakeLists.txt
index 9ae48354..e5aae0b7 100644
--- a/libsettings/CMakeLists.txt
+++ b/libsettings/CMakeLists.txt
@@ -18,6 +18,12 @@ include/overridesetting.h
include/basicsettingsobject.h
include/inisettingsobject.h
+
+include/keyring.h
+)
+
+SET(LIBSETTINGS_HEADERS_PRIVATE
+src/stubkeyring.h
)
SET(LIBSETTINGS_SOURCES
@@ -29,6 +35,9 @@ src/overridesetting.cpp
src/basicsettingsobject.cpp
src/inisettingsobject.cpp
+
+src/keyring.cpp
+src/stubkeyring.cpp
)
# Set the include dir path.
@@ -37,6 +46,6 @@ include_directories(${LIBSETTINGS_INCLUDE_DIR})
add_definitions(-DLIBSETTINGS_LIBRARY)
-add_library(libSettings SHARED ${LIBSETTINGS_SOURCES} ${LIBSETTINGS_HEADERS})
+add_library(libSettings SHARED ${LIBSETTINGS_SOURCES} ${LIBSETTINGS_HEADERS} ${LIBSETTINGS_HEADERS_PRIVATE})
qt5_use_modules(libSettings Core)
target_link_libraries(libSettings)
diff --git a/libsettings/include/keyring.h b/libsettings/include/keyring.h
new file mode 100644
index 00000000..299b14b0
--- /dev/null
+++ b/libsettings/include/keyring.h
@@ -0,0 +1,92 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 KEYRING_H
+#define KEYRING_H
+
+#include <QString>
+
+#include "libsettings_config.h"
+
+/**
+ * @file libsettings/include/keyring.h
+ * Access to System Keyrings
+ */
+
+/**
+ * @brief The Keyring class
+ * the System Keyring/Keychain/Wallet/Vault/etc
+ */
+class LIBSETTINGS_EXPORT Keyring
+{
+public:
+ /**
+ * @brief the System Keyring instance
+ * @return the Keyring instance
+ */
+ static Keyring *instance();
+
+ /**
+ * @brief store a password in the Keyring
+ * @param service the service name
+ * @param username the account name
+ * @param password the password to store
+ * @return success
+ */
+ virtual bool storePassword(QString service, QString username, QString password) = 0;
+
+ /**
+ * @brief get a password from the Keyring
+ * @param service the service name
+ * @param username the account name
+ * @return the password (success=!isNull())
+ */
+ virtual QString getPassword(QString service, QString username) = 0;
+
+ /**
+ * @brief lookup a password
+ * @param service the service name
+ * @param username the account name
+ * @return wether the password is available
+ */
+ virtual bool hasPassword(QString service, QString username) = 0;
+
+ /**
+ * @brief get a list of all stored accounts.
+ * @param service the service name
+ * @return
+ */
+ virtual QStringList getStoredAccounts(QString service) = 0;
+
+ /**
+ * @brief Remove the specified account from storage
+ * @param service the service name
+ * @param username the account name
+ * @return
+ */
+ virtual void removeStoredAccount(QString service, QString username) = 0;
+
+protected:
+ /// fall back to StubKeyring if false
+ virtual bool isValid() { return false; }
+
+private:
+ static Keyring *m_instance;
+ static void destroy();
+};
+
+#endif // KEYRING_H
diff --git a/libsettings/src/basicsettingsobject.cpp b/libsettings/src/basicsettingsobject.cpp
index 66a2c2c8..484928c8 100644
--- a/libsettings/src/basicsettingsobject.cpp
+++ b/libsettings/src/basicsettingsobject.cpp
@@ -26,7 +26,10 @@ void BasicSettingsObject::changeSetting(const Setting &setting, QVariant value)
{
if (contains(setting.id()))
{
- config.setValue(setting.configKey(), value);
+ if(value.isValid())
+ config.setValue(setting.configKey(), value);
+ else
+ config.remove(setting.configKey());
}
}
diff --git a/libsettings/src/inisettingsobject.cpp b/libsettings/src/inisettingsobject.cpp
index 75228865..8c4cc89d 100644
--- a/libsettings/src/inisettingsobject.cpp
+++ b/libsettings/src/inisettingsobject.cpp
@@ -32,7 +32,10 @@ void INISettingsObject::changeSetting(const Setting &setting, QVariant value)
{
if (contains(setting.id()))
{
- m_ini.set(setting.configKey(), value);
+ if(value.isValid())
+ m_ini.set(setting.configKey(), value);
+ else
+ m_ini.remove(setting.configKey());
}
}
diff --git a/libsettings/src/keyring.cpp b/libsettings/src/keyring.cpp
new file mode 100644
index 00000000..9eaba684
--- /dev/null
+++ b/libsettings/src/keyring.cpp
@@ -0,0 +1,63 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "include/keyring.h"
+
+#include "osutils.h"
+
+#include "stubkeyring.h"
+
+// system specific keyrings
+/*#if defined(OSX)
+class OSXKeychain;
+#define KEYRING OSXKeychain
+#elif defined(LINUX)
+class XDGKeyring;
+#define KEYRING XDGKeyring
+#elif defined(WINDOWS)
+class Win32Keystore;
+#define KEYRING Win32Keystore
+#else
+#pragma message Keyrings are not supported on your os. Falling back to the insecure StubKeyring
+#endif*/
+
+Keyring *Keyring::instance()
+{
+ if (m_instance == nullptr)
+ {
+#ifdef KEYRING
+ m_instance = new KEYRING();
+ if (!m_instance->isValid())
+ {
+ qWarning("Could not create SystemKeyring! falling back to StubKeyring.");
+ m_instance = new StubKeyring();
+ }
+#else
+ qWarning("Keyrings are not supported on your OS. Fallback StubKeyring is insecure!");
+ m_instance = new StubKeyring();
+#endif
+ atexit(Keyring::destroy);
+ }
+ return m_instance;
+}
+
+void Keyring::destroy()
+{
+ delete m_instance;
+}
+
+Keyring *Keyring::m_instance;
diff --git a/libsettings/src/setting.cpp b/libsettings/src/setting.cpp
index a224ad39..1a4f9e13 100644
--- a/libsettings/src/setting.cpp
+++ b/libsettings/src/setting.cpp
@@ -26,9 +26,16 @@ QVariant Setting::get() const
{
SettingsObject *sbase = qobject_cast<SettingsObject *>(parent());
if (!sbase)
+ {
return defValue();
+ }
else
- return sbase->retrieveValue(*this);
+ {
+ QVariant test = sbase->retrieveValue(*this);
+ if(!test.isValid())
+ return defValue();
+ return test;
+ }
}
QVariant Setting::defValue() const
diff --git a/libsettings/src/stubkeyring.cpp b/libsettings/src/stubkeyring.cpp
new file mode 100644
index 00000000..cf814d2f
--- /dev/null
+++ b/libsettings/src/stubkeyring.cpp
@@ -0,0 +1,104 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stubkeyring.h"
+
+#include <QStringList>
+
+// Scrambling
+// this is NOT SAFE, but it's not plain either.
+int scrambler = 0x9586309;
+
+QString scramble(QString in_)
+{
+ QByteArray in = in_.toUtf8();
+ QByteArray out;
+ for (int i = 0; i<in.length(); i++)
+ out.append(in.at(i) ^ scrambler);
+ return QString::fromUtf8(out);
+}
+
+inline QString base64(QString in)
+{
+ return QString(in.toUtf8().toBase64());
+}
+inline QString unbase64(QString in)
+{
+ return QString::fromUtf8(QByteArray::fromBase64(in.toLatin1()));
+}
+
+inline QString scramble64(QString in)
+{
+ return base64(scramble(in));
+}
+inline QString unscramble64(QString in)
+{
+ return scramble(unbase64(in));
+}
+
+// StubKeyring implementation
+inline QString generateKey(QString service, QString username)
+{
+ return QString("%1/%2").arg(base64(service)).arg(scramble64(username));
+}
+
+bool StubKeyring::storePassword(QString service, QString username, QString password)
+{
+ m_settings.setValue(generateKey(service, username), scramble64(password));
+ return true;
+}
+
+QString StubKeyring::getPassword(QString service, QString username)
+{
+ QString key = generateKey(service, username);
+ if (!m_settings.contains(key))
+ return QString();
+ return unscramble64(m_settings.value(key).toString());
+}
+
+bool StubKeyring::hasPassword(QString service, QString username)
+{
+ return m_settings.contains(generateKey(service, username));
+}
+
+QStringList StubKeyring::getStoredAccounts(QString service)
+{
+ service = base64(service).append('/');
+ QStringList out;
+ QStringList in(m_settings.allKeys());
+ QStringListIterator it(in);
+ while(it.hasNext())
+ {
+ QString c = it.next();
+ if (c.startsWith(service))
+ out << unscramble64(c.mid(service.length()));
+ }
+ return out;
+}
+
+void StubKeyring::removeStoredAccount ( QString service, QString username )
+{
+ QString key = generateKey(service, username);
+ m_settings.remove(key);
+}
+
+//FIXME: this needs tweaking/changes for user account level storage
+StubKeyring::StubKeyring() :
+// m_settings(QSettings::UserScope, "Orochimarufan", "Keyring")
+ m_settings("keyring.cfg", QSettings::IniFormat)
+{
+}
diff --git a/libsettings/src/stubkeyring.h b/libsettings/src/stubkeyring.h
new file mode 100644
index 00000000..45791c85
--- /dev/null
+++ b/libsettings/src/stubkeyring.h
@@ -0,0 +1,41 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 STUBKEYRING_H
+#define STUBKEYRING_H
+
+#include "include/keyring.h"
+
+#include <QSettings>
+
+class StubKeyring : public Keyring
+{
+public:
+ virtual bool storePassword(QString service, QString username, QString password);
+ virtual QString getPassword(QString service, QString username);
+ virtual bool hasPassword(QString service, QString username);
+ virtual QStringList getStoredAccounts(QString service);
+ virtual void removeStoredAccount(QString service, QString username);
+private:
+ friend class Keyring;
+ explicit StubKeyring();
+ virtual bool isValid() { return true; }
+
+ QSettings m_settings;
+};
+
+#endif // STUBKEYRING_H
diff --git a/libutil/CMakeLists.txt b/libutil/CMakeLists.txt
index 5b2c3837..11b21426 100644
--- a/libutil/CMakeLists.txt
+++ b/libutil/CMakeLists.txt
@@ -31,9 +31,6 @@ include/pathutils.h
include/osutils.h
include/userutils.h
include/cmdutils.h
-
-include/siglist.h
-include/siglist_impl.h
)
SET(LIBUTIL_SOURCES
diff --git a/libutil/include/cmdutils.h b/libutil/include/cmdutils.h
index 32261dd7..83397e73 100644
--- a/libutil/include/cmdutils.h
+++ b/libutil/include/cmdutils.h
@@ -73,15 +73,6 @@ enum LIBUTIL_EXPORT Enum
};
}
-namespace OptionType
-{
-enum LIBUTIL_EXPORT Enum
-{
- Switch,
- Option
-};
-}
-
/**
* @brief The ParsingError class
*/
@@ -210,6 +201,12 @@ public:
private:
FlagStyle::Enum m_flagStyle;
ArgumentStyle::Enum m_argStyle;
+
+ enum OptionType
+ {
+ otSwitch,
+ otOption
+ };
// Important: the common part MUST BE COMMON ON ALL THREE structs
struct CommonDef {
@@ -226,7 +223,7 @@ private:
QString metavar;
QVariant def;
// option
- OptionType::Enum type;
+ OptionType type;
QChar flag;
};
diff --git a/libutil/src/cmdutils.cpp b/libutil/src/cmdutils.cpp
index 3bf4e872..13db503d 100644
--- a/libutil/src/cmdutils.cpp
+++ b/libutil/src/cmdutils.cpp
@@ -56,7 +56,7 @@ void Parser::addSwitch(QString name, bool def)
throw "Name not unique";
OptionDef *param = new OptionDef;
- param->type = OptionType::Switch;
+ param->type = otSwitch;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
@@ -72,7 +72,7 @@ void Parser::addOption(QString name, QVariant def)
throw "Name not unique";
OptionDef *param = new OptionDef;
- param->type = OptionType::Option;
+ param->type = otOption;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
@@ -161,7 +161,7 @@ QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
help << flagPrefix << option->flag << ", ";
}
help << optPrefix << option->name;
- if (option->type == OptionType::Option)
+ if (option->type == otOption)
{
QString arg = QString("%1%2").arg(((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
nameLength += arg.length();
@@ -193,7 +193,7 @@ QString Parser::compileUsage(QString progName, bool useFlags)
usage << flagPrefix << option->flag;
else
usage << optPrefix << option->name;
- if (option->type == OptionType::Option)
+ if (option->type == otOption)
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
usage << "]";
}
@@ -265,9 +265,9 @@ QHash<QString, QVariant> Parser::parse(QStringList argv)
throw ParsingError(QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
OptionDef *option = m_options[name];
- if (option->type == OptionType::Switch)
+ if (option->type == otSwitch)
map[name] = true;
- else //if (option->type == OptionType::Option)
+ else //if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(name);
@@ -312,9 +312,9 @@ QHash<QString, QVariant> Parser::parse(QStringList argv)
if (map.contains(option->name))
throw ParsingError(QString("Option %2%1 was given multiple times").arg(option->name, optionPrefix));
- if (option->type == OptionType::Switch)
+ if (option->type == otSwitch)
map[option->name] = true;
- else //if (option->type == OptionType::Option)
+ else //if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(option->name);
diff --git a/main.cpp b/main.cpp
index cf193ff0..4239d70f 100644
--- a/main.cpp
+++ b/main.cpp
@@ -57,23 +57,6 @@ public:
this->instId = instId;
}
-private:
- InstancePtr findInstance(QString instId)
- {
- QListIterator<InstancePtr> iter(instances);
- InstancePtr inst;
- while(iter.hasNext())
- {
- inst = iter.next();
- if (inst->id() == instId)
- break;
- }
- if (inst->id() != instId)
- return InstancePtr();
- else
- return iter.peekPrevious();
- }
-
private slots:
void onTerminated()
{
@@ -89,6 +72,7 @@ private slots:
//if (instance->getShowConsole())
console->show();
connect(proc, SIGNAL(ended()), SLOT(onTerminated()));
+ connect(proc, SIGNAL(log(QString,MessageLevel::Enum)), console, SLOT(write(QString,MessageLevel::Enum)));
proc->launch();
}
@@ -117,7 +101,7 @@ public:
instances.loadList();
std::cout << "Launching Instance '" << qPrintable(instId) << "'" << std::endl;
- instance = findInstance(instId);
+ instance = instances.getInstanceById(instId);
if (instance.isNull())
{
std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl;
diff --git a/multimc.qrc b/multimc.qrc
index cfcc9829..acd9efd2 100644
--- a/multimc.qrc
+++ b/multimc.qrc
@@ -34,4 +34,7 @@
<file alias="scalable/apps/multimc.svg">resources/icons/multimc.svg</file>
<file alias="index.theme">resources/XdgIcon.theme</file>
</qresource>
+ <qresource prefix="/backgrounds">
+ <file alias="kitteh">resources/catbgrnd2.png</file>
+ </qresource>
</RCC>
diff --git a/resources/catbgrnd2.png b/resources/catbgrnd2.png
new file mode 100644
index 00000000..2b949e0b
--- /dev/null
+++ b/resources/catbgrnd2.png
Binary files differ
diff --git a/resources/icons/instances/clucker.svg b/resources/icons/instances/clucker.svg
index 3ce6ad5c..0c1727eb 100644
--- a/resources/icons/instances/clucker.svg
+++ b/resources/icons/instances/clucker.svg
@@ -14,7 +14,7 @@
height="32"
id="svg2"
version="1.1"
- inkscape:version="0.48.3.1 r9886"
+ inkscape:version="0.48.4 r9939"
sodipodi:docname="clucker.svg"
inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/insticons/chicken128.png"
inkscape:export-xdpi="360"
@@ -234,13 +234,13 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="2"
- inkscape:cx="-92.12757"
- inkscape:cy="71.871222"
+ inkscape:zoom="11.313708"
+ inkscape:cx="2.6058272"
+ inkscape:cy="11.408405"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1607"
+ inkscape:window-width="1614"
inkscape:window-height="1030"
inkscape:window-x="1676"
inkscape:window-y="-3"
@@ -261,7 +261,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
+ <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
diff --git a/resources/icons/instances/skeleton.svg b/resources/icons/instances/skeleton.svg
index b2da5a46..5d55f272 100644
--- a/resources/icons/instances/skeleton.svg
+++ b/resources/icons/instances/skeleton.svg
@@ -13,7 +13,7 @@
height="32"
id="svg2"
version="1.1"
- inkscape:version="0.48.3.1 r9886"
+ inkscape:version="0.48.4 r9939"
sodipodi:docname="skeleton.svg"
inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/insticons/skeleton128.png"
inkscape:export-xdpi="360"
@@ -23,19 +23,6 @@
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
- id="filter5719"
- x="-0.18000001"
- width="1.36"
- y="-0.36000001"
- height="1.72">
- <feGaussianBlur
- inkscape:collect="always"
- stdDeviation="0.15"
- id="feGaussianBlur5721" />
- </filter>
- <filter
- color-interpolation-filters="sRGB"
- inkscape:collect="always"
id="filter5723"
x="-0.20999999"
width="1.42"
@@ -49,19 +36,6 @@
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
- id="filter5711"
- x="-0.1728"
- width="1.3456"
- y="-0.34560001"
- height="1.6912">
- <feGaussianBlur
- inkscape:collect="always"
- stdDeviation="0.144"
- id="feGaussianBlur5713" />
- </filter>
- <filter
- color-interpolation-filters="sRGB"
- inkscape:collect="always"
id="filter5727"
x="-0.20999999"
width="1.42"
@@ -103,12 +77,12 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
- inkscape:cx="17.044214"
- inkscape:cy="19.500236"
+ inkscape:cx="-18.309169"
+ inkscape:cy="22.958832"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
- inkscape:window-width="1607"
+ inkscape:window-width="1614"
inkscape:window-height="1030"
inkscape:window-x="1676"
inkscape:window-y="-3"
@@ -129,7 +103,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
+ <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -612,7 +586,7 @@
x="7.9999995"
y="1036.3622" />
<rect
- style="fill:#00ffff;fill-opacity:1;stroke:none;filter:url(#filter5711)"
+ style="fill:#00ffff;fill-opacity:1;stroke:none;"
id="rect4084-3"
width="2"
height="1"
@@ -626,7 +600,7 @@
x="20"
y="1036.3622" />
<rect
- style="fill:#00ffff;fill-opacity:1;stroke:none;filter:url(#filter5719)"
+ style="fill:#00ffff;fill-opacity:1;stroke:none;"
id="rect4086-7"
width="2"
height="1"
diff --git a/test.cpp b/test.cpp
new file mode 100644
index 00000000..2b015454
--- /dev/null
+++ b/test.cpp
@@ -0,0 +1,60 @@
+
+#include <iostream>
+
+#include "keyring.h"
+#include "cmdutils.h"
+
+using namespace Util::Commandline;
+
+#include <QCoreApplication>
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ app.setApplicationName("MMC Keyring test");
+ app.setOrganizationName("Orochimarufan");
+
+ Parser p;
+ p.addArgument("user", false);
+ p.addArgument("password", false);
+ p.addSwitch("set");
+ p.addSwitch("get");
+ p.addSwitch("list");
+ p.addOption("service", "Test");
+ p.addShortOpt("service", 's');
+
+ QHash<QString, QVariant> args;
+ try {
+ args = p.parse(app.arguments());
+ } catch (ParsingError) {
+ std::cout << "Syntax error." << std::endl;
+ return 1;
+ }
+
+ if (args["set"].toBool()) {
+ if (args["user"].isNull() || args["password"].isNull()) {
+ std::cout << "set operation needs bot user and password set" << std::endl;
+ return 1;
+ }
+
+ return Keyring::instance()->storePassword(args["service"].toString(),
+ args["user"].toString(), args["password"].toString());
+ } else if (args["get"].toBool()) {
+ if (args["user"].isNull()) {
+ std::cout << "get operation needs user set" << std::endl;
+ return 1;
+ }
+
+ std::cout << "Password: " << qPrintable(Keyring::instance()->getPassword(args["service"].toString(),
+ args["user"].toString())) << std::endl;
+ return 0;
+ } else if (args["list"].toBool()) {
+ QStringList accounts = Keyring::instance()->getStoredAccounts(args["service"].toString());
+ std::cout << "stored accounts:" << std::endl << '\t' << qPrintable(accounts.join("\n\t")) << std::endl;
+ return 0;
+ } else {
+ std::cout << "No operation given!" << std::endl;
+ std::cout << qPrintable(p.compileHelp(argv[0])) << std::endl;
+ return 1;
+ }
+}