Basic MultiMC integration
MultiMC installs can be configured in the same window as MC installs. Saves folders of instances from all MMC installs are added to the "Folder" popup. Also, the StackedWidget uses a blank widget to hide error messages and/or white GL widgets during world changes
This commit is contained in:
parent
c1d5d0befa
commit
d184ff07c1
@ -7,115 +7,230 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>780</width>
|
||||
<height>355</height>
|
||||
<height>706</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MCEdit - Minecraft Installations</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Minecraft Installations</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="minecraftInstallsTable">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Versions</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Path</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Versions</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Path</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectButton">
|
||||
<property name="text">
|
||||
<string>Select</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>MultiMC Installations</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="multiMCTable">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Instances</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Path</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>MultiMC instances are automatically selected when a world from that instance is loaded.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectButton">
|
||||
<property name="text">
|
||||
<string>Select</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addMMCButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeMMCButton">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
@ -141,6 +141,52 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="savesFolderComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="chooseSavesFolderButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Choose...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="worldListView">
|
||||
<property name="sizePolicy">
|
||||
@ -166,40 +212,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="savesFolderComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="chooseSavesFolderButton">
|
||||
<property name="text">
|
||||
<string>Choose saves folder...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openWorldButton">
|
||||
<property name="text">
|
||||
@ -271,7 +283,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<zorder>cancelButton</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import hashlib
|
||||
import re
|
||||
from PySide import QtGui
|
||||
from PySide import QtGui, QtCore
|
||||
import logging
|
||||
import os
|
||||
from PySide.QtCore import Qt
|
||||
@ -19,6 +19,7 @@ log = logging.getLogger(__name__)
|
||||
installationsOption = settings.Settings().getOption("minecraft_installs/installations", "json", [])
|
||||
multiMCInstallsOption = settings.Settings().getOption("minecraft_installs/multimc_installs", "json", [])
|
||||
currentInstallOption = settings.Settings().getOption("minecraft_installs/current_install", int)
|
||||
currentMMCInstanceOption = settings.Settings().getOption("minecraft_installs/current_install", int)
|
||||
|
||||
_installs = None
|
||||
|
||||
@ -36,16 +37,23 @@ class MCInstallGroup(object):
|
||||
Represents all Minecraft installs known to MCEdit. Loads installs from settings and detects the current install
|
||||
in ~/.minecraft or equivalent.
|
||||
|
||||
xxx also detects each MultiMC instance as a Minecraft install xxx
|
||||
Also represents MultiMC instances as separate installs, each with a single version and a single saves folder.
|
||||
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
self._installations = list(self._loadInstalls())
|
||||
self._instances = list(self._loadMMCInstances())
|
||||
self._mmcInstalls = list(self._loadMMCInstalls())
|
||||
self.getDefaultInstall()
|
||||
|
||||
def _loadMMCInstances(self):
|
||||
return []
|
||||
def _loadMMCInstalls(self):
|
||||
for install in multiMCInstallsOption.value():
|
||||
configFile = install["configFile"]
|
||||
try:
|
||||
install = MultiMCInstall(configFile)
|
||||
yield install
|
||||
except MultiMCInstallError as e:
|
||||
log.warn("Not using MultiMC with config file %s: %s", configFile, e)
|
||||
|
||||
def _loadInstalls(self):
|
||||
for install in installationsOption.value():
|
||||
@ -60,6 +68,7 @@ class MCInstallGroup(object):
|
||||
|
||||
def _saveInstalls(self):
|
||||
installationsOption.setValue([i.getJsonSettingValue() for i in self._installations])
|
||||
multiMCInstallsOption.setValue([i.getJsonSettingValue() for i in self._mmcInstalls])
|
||||
log.info("MCInstall saved settings: %s", installationsOption.value())
|
||||
|
||||
def getDefaultInstall(self):
|
||||
@ -101,6 +110,14 @@ class MCInstallGroup(object):
|
||||
del self._installations[index]
|
||||
self._saveInstalls()
|
||||
|
||||
def addMMCInstall(self, install):
|
||||
self._mmcInstalls.append(install)
|
||||
self._saveInstalls()
|
||||
|
||||
def removeMMCInstall(self, index):
|
||||
del self._mmcInstalls[index]
|
||||
self._saveInstalls()
|
||||
|
||||
def ensureValidInstall(self):
|
||||
"""
|
||||
Called on app startup. Display install config dialog if no installs were found
|
||||
@ -116,6 +133,15 @@ class MCInstallGroup(object):
|
||||
installsWidget = MinecraftInstallsDialog()
|
||||
installsWidget.exec_()
|
||||
|
||||
@property
|
||||
def mmcInstalls(self):
|
||||
return self._mmcInstalls
|
||||
|
||||
@property
|
||||
def instances(self):
|
||||
for mmcInstall in self._mmcInstalls:
|
||||
for instance in mmcInstall.instances:
|
||||
yield instance
|
||||
|
||||
class MCInstall(object):
|
||||
def __init__(self, path, name="Unnamed"):
|
||||
@ -155,8 +181,8 @@ class MCInstall(object):
|
||||
def resourcePacks(self):
|
||||
return os.listdir(os.path.join(self.path, "resourcepacks"))
|
||||
|
||||
def getSaveFileDir(self):
|
||||
return os.path.join(self.path, "saves")
|
||||
def getSaveDirs(self):
|
||||
return [os.path.join(self.path, "saves")] # xxx profile.json
|
||||
|
||||
def getResourcePackPath(self, filename):
|
||||
return os.path.join(self.path, "resourcepacks", filename)
|
||||
@ -198,6 +224,77 @@ class MCInstall(object):
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
class MMCInstance(object):
|
||||
def __init__(self, install, path):
|
||||
instanceCfg = os.path.join(path, "instance.cfg")
|
||||
if not os.path.exists(instanceCfg):
|
||||
raise MultiMCInstanceError("instance.cfg not found: %s" % instanceCfg)
|
||||
|
||||
instanceSettings = QtCore.QSettings(instanceCfg, QtCore.QSettings.IniFormat)
|
||||
|
||||
self.version = instanceSettings.value("IntendedVersion", "")
|
||||
if not self.version:
|
||||
raise MultiMCInstanceError("Instance %s has no IntendedVersion" % os.path.basename(path))
|
||||
|
||||
self.name = instanceSettings.value("name", "(unnamed)")
|
||||
self.install = install
|
||||
self.saveFileDir = os.path.join(path, "minecraft", "saves")
|
||||
|
||||
@property
|
||||
def versions(self):
|
||||
return [self.version]
|
||||
|
||||
def getVersionJarPath(self, version):
|
||||
return self.install.getVersionJarPath(self.version)
|
||||
|
||||
|
||||
class MultiMCInstall(object):
|
||||
def __init__(self, configPath):
|
||||
|
||||
self.configPath = configPath
|
||||
if not os.path.exists(configPath):
|
||||
raise MultiMCInstallError("Config file does not exist", configPath)
|
||||
|
||||
# MultiMC is built with Qt, so why not use Qt's settings loader?
|
||||
mmcSettings = QtCore.QSettings(configPath, QtCore.QSettings.IniFormat)
|
||||
instanceDir = mmcSettings.value("InstanceDir", "")
|
||||
if not instanceDir:
|
||||
raise MultiMCInstallError("InstanceDir not set")
|
||||
|
||||
self.mmcDir = os.path.dirname(configPath)
|
||||
if not os.path.isabs(instanceDir):
|
||||
self.instanceDir = os.path.join(self.mmcDir, instanceDir)
|
||||
else:
|
||||
self.instanceDir = instanceDir
|
||||
|
||||
self.versionsDir = os.path.join(self.mmcDir, "versions")
|
||||
|
||||
self.name = os.path.basename(self.mmcDir)
|
||||
# read versions.dat? (qt binary json format)
|
||||
# read groups from instGroups.json?
|
||||
|
||||
@property
|
||||
def instances(self):
|
||||
for filename in os.listdir(self.instanceDir):
|
||||
path = os.path.join(self.instanceDir, filename)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
try:
|
||||
instance = MMCInstance(self, path)
|
||||
except MultiMCInstanceError:
|
||||
log.error("Could not read MultiMC Instance")
|
||||
continue
|
||||
|
||||
yield instance
|
||||
|
||||
def getJsonSettingValue(self):
|
||||
return {"configFile": self.configPath}
|
||||
|
||||
|
||||
def getVersionJarPath(self, version):
|
||||
return os.path.join(self.versionsDir, version, version + ".jar")
|
||||
|
||||
def splitVersion(version):
|
||||
"""
|
||||
Split a Minecraft version ID into major, minor, and revision. If the version could not be parsed, return (0, 0, "")
|
||||
@ -245,6 +342,18 @@ class MCInstallError(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
class MultiMCInstallError(ValueError):
|
||||
"""
|
||||
Raised for invalid or unreadable MultiMC installs.
|
||||
"""
|
||||
|
||||
|
||||
class MultiMCInstanceError(ValueError):
|
||||
"""
|
||||
Raised for invalid or unreadable MultiMC instances.
|
||||
"""
|
||||
|
||||
|
||||
class NameItem(QtGui.QTableWidgetItem):
|
||||
def setData(self, data, role):
|
||||
if role != Qt.EditRole or data != "(Default)":
|
||||
@ -262,58 +371,77 @@ class MinecraftInstallsDialog(QtGui.QDialog):
|
||||
super(MinecraftInstallsDialog, self).__init__(*args, **kwargs)
|
||||
load_ui("minecraft_installs.ui", baseinstance=self)
|
||||
# populate list view
|
||||
for row, install in enumerate(GetInstalls().installs):
|
||||
for install in GetInstalls().installs:
|
||||
self._addInstall(install)
|
||||
for path in GetInstalls().mmcInstalls:
|
||||
self._addMMCInstall(path)
|
||||
|
||||
self._hiliteRow(currentInstallOption.value(0))
|
||||
|
||||
self.tableWidget.cellChanged.connect(self.itemChanged)
|
||||
#self.tableWidget.doubleClicked.connect(self.select)
|
||||
self.addButton.clicked.connect(self.add)
|
||||
self.removeButton.clicked.connect(self.remove)
|
||||
self.selectButton.clicked.connect(self.select)
|
||||
#self.editButton.clicked.connect(self.edit)
|
||||
self.minecraftInstallsTable.cellChanged.connect(self.itemChanged)
|
||||
self.addButton.clicked.connect(self.addInstall)
|
||||
self.removeButton.clicked.connect(self.removeInstall)
|
||||
self.selectButton.clicked.connect(self.selectInstall)
|
||||
self.okButton.clicked.connect(self.ok)
|
||||
|
||||
self.addMMCButton.clicked.connect(self.addMMCInstall)
|
||||
self.removeMMCButton.clicked.connect(self.removeMMCInstall)
|
||||
|
||||
|
||||
def itemChanged(self, row, column):
|
||||
install = GetInstalls().installs[row]
|
||||
text = self.tableWidget.item(row, column).text()
|
||||
if column == 0:
|
||||
install.name = text
|
||||
if column == 2:
|
||||
install.path = text
|
||||
|
||||
install.path = text # xxxx validate me!
|
||||
|
||||
def _addInstall(self, install):
|
||||
tableWidget = self.tableWidget
|
||||
row = tableWidget.rowCount()
|
||||
tableWidget.setRowCount(row+1)
|
||||
minecraftInstallsTable = self.minecraftInstallsTable
|
||||
row = minecraftInstallsTable.rowCount()
|
||||
minecraftInstallsTable.setRowCount(row+1)
|
||||
nameItem = NameItem(install.name)
|
||||
if install.name == "(Default)":
|
||||
nameItem.setFlags(nameItem.flags() & ~Qt.ItemIsEditable)
|
||||
tableWidget.setItem(row, 0, nameItem)
|
||||
minecraftInstallsTable.setItem(row, 0, nameItem)
|
||||
|
||||
versionsString = ", ".join(sorted(install.versions, reverse=True))
|
||||
versionsItem = QtGui.QTableWidgetItem(versionsString)
|
||||
versionsItem.setFlags(versionsItem.flags() & ~Qt.ItemIsEditable)
|
||||
tableWidget.setItem(row, 1, versionsItem)
|
||||
minecraftInstallsTable.setItem(row, 1, versionsItem)
|
||||
|
||||
pathItem = PathItem(install.path)
|
||||
if install.name == "(Default)":
|
||||
pathItem.setFlags(pathItem.flags() & ~Qt.ItemIsEditable)
|
||||
tableWidget.setItem(row, 2, pathItem)
|
||||
minecraftInstallsTable.setItem(row, 2, pathItem)
|
||||
self._hiliteRow(row)
|
||||
currentInstallOption.setValue(row)
|
||||
|
||||
def _addMMCInstall(self, install):
|
||||
mmcTable = self.multiMCTable
|
||||
row = mmcTable.rowCount()
|
||||
mmcTable.setRowCount(row+1)
|
||||
nameItem = NameItem(install.name)
|
||||
nameItem.setFlags(nameItem.flags() & ~Qt.ItemIsEditable)
|
||||
mmcTable.setItem(row, 0, nameItem)
|
||||
|
||||
instancesString = ", ".join(sorted((i.name for i in install.instances)))
|
||||
instancesItem = QtGui.QTableWidgetItem(instancesString)
|
||||
instancesItem.setFlags(instancesItem.flags() & ~Qt.ItemIsEditable)
|
||||
mmcTable.setItem(row, 1, instancesItem)
|
||||
|
||||
pathItem = PathItem(install.configPath)
|
||||
mmcTable.setItem(row, 2, pathItem)
|
||||
|
||||
def _hiliteRow(self, hiliteRow):
|
||||
for row in range(self.tableWidget.rowCount()):
|
||||
for column in range(self.tableWidget.columnCount()):
|
||||
item = self.tableWidget.item(row, column)
|
||||
for row in range(self.minecraftInstallsTable.rowCount()):
|
||||
for column in range(self.minecraftInstallsTable.columnCount()):
|
||||
item = self.minecraftInstallsTable.item(row, column)
|
||||
font = item.font()
|
||||
font.setBold(row == hiliteRow)
|
||||
item.setFont(font)
|
||||
|
||||
def add(self):
|
||||
def addInstall(self):
|
||||
folder = QtGui.QFileDialog.getExistingDirectory(self, "Choose a Minecraft installation folder (.minecraft)")
|
||||
installs = GetInstalls()
|
||||
if not folder:
|
||||
@ -328,16 +456,41 @@ class MinecraftInstallsDialog(QtGui.QDialog):
|
||||
installs.addInstall(install)
|
||||
self._addInstall(install)
|
||||
|
||||
def remove(self):
|
||||
row = self.tableWidget.currentRow()
|
||||
def removeInstall(self):
|
||||
row = self.minecraftInstallsTable.currentRow()
|
||||
GetInstalls().removeInstall(row)
|
||||
self.tableWidget.removeRow(row)
|
||||
self.minecraftInstallsTable.removeRow(row)
|
||||
|
||||
def select(self):
|
||||
row = self.tableWidget.currentRow()
|
||||
def selectInstall(self):
|
||||
row = self.minecraftInstallsTable.currentRow()
|
||||
currentInstallOption.setValue(row)
|
||||
self._hiliteRow(row)
|
||||
|
||||
def addMMCInstall(self):
|
||||
result = QtGui.QFileDialog.getOpenFileName(self,
|
||||
"Choose a MultiMC configuration file (multimc.cfg)",
|
||||
filter="MultiMC configuration files (multimc.cfg)")
|
||||
installs = GetInstalls()
|
||||
if not result:
|
||||
return
|
||||
configPath = result[0]
|
||||
if not configPath:
|
||||
return
|
||||
|
||||
try:
|
||||
install = MultiMCInstall(configPath)
|
||||
except MultiMCInstallError as e:
|
||||
message = "This MultiMC install is unusable.\n(%s)" % e.message
|
||||
QtGui.QMessageBox.warning(self, "MultiMC Install Unusable", message)
|
||||
else:
|
||||
installs.addMMCInstall(install)
|
||||
self._addMMCInstall(install)
|
||||
|
||||
def removeMMCInstall(self):
|
||||
row = self.multiMCTable.currentRow()
|
||||
GetInstalls().removeMMCInstall(row)
|
||||
self.multiMCTable.removeRow(row)
|
||||
|
||||
def ok(self):
|
||||
self.close()
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import os
|
||||
import zipfile
|
||||
from PySide.QtCore import Qt
|
||||
|
||||
from arrow import arrow
|
||||
@ -175,16 +176,14 @@ class WorldListWidget(QtGui.QDialog):
|
||||
def __init__(self, parent=None, f=0):
|
||||
super(WorldListWidget, self).__init__(parent, f)
|
||||
self.setWindowTitle("World List")
|
||||
load_ui('world_list.ui', baseinstance=self)
|
||||
|
||||
self.saveFileDir = None
|
||||
self.worldView = None
|
||||
self.chunkLoader = None
|
||||
|
||||
self.errorWidget = QtGui.QWidget()
|
||||
|
||||
load_ui('world_list.ui', baseinstance=self)
|
||||
|
||||
self.setLayout(Row(self))
|
||||
self.errorWidget = None
|
||||
self.blankWidget = QtGui.QWidget()
|
||||
self.stackedWidget.addWidget(self.blankWidget)
|
||||
|
||||
self.editButton.clicked.connect(self.editClicked)
|
||||
self.cancelButton.clicked.connect(self.reject)
|
||||
@ -213,13 +212,21 @@ class WorldListWidget(QtGui.QDialog):
|
||||
self.loadTimer = LoaderTimer(interval=0, timeout=self.loadTimerFired)
|
||||
self.loadTimer.start()
|
||||
|
||||
for install in minecraftinstall.GetInstalls().installs:
|
||||
self.minecraftInstallBox.addItem(install.name)
|
||||
self.minecraftInstallBox.setCurrentIndex(minecraftinstall.GetInstalls().selectedInstallIndex())
|
||||
self._updateVersionsAndResourcePacks()
|
||||
self._updateInstalls()
|
||||
|
||||
self.savesFolderComboBox.currentIndexChanged.connect(self.reloadList)
|
||||
|
||||
self.worldListModel = None
|
||||
self.reloadList()
|
||||
self.reloadRecentWorlds()
|
||||
|
||||
def _updateInstalls(self):
|
||||
for install in minecraftinstall.GetInstalls().installs:
|
||||
self.minecraftInstallBox.addItem(install.name)
|
||||
|
||||
self.minecraftInstallBox.setCurrentIndex(minecraftinstall.GetInstalls().selectedInstallIndex())
|
||||
|
||||
self._updateVersionsAndResourcePacks()
|
||||
|
||||
def _updateVersionsAndResourcePacks(self):
|
||||
install = minecraftinstall.GetInstalls().getInstall(self.minecraftInstallBox.currentIndex())
|
||||
@ -232,8 +239,13 @@ class WorldListWidget(QtGui.QDialog):
|
||||
self.resourcePackBox.addItem(self.tr("(No resource pack)"))
|
||||
for resourcePack in sorted(install.resourcePacks):
|
||||
self.resourcePackBox.addItem(resourcePack)
|
||||
|
||||
self.saveFileDir = install.getSaveFileDir()
|
||||
|
||||
self.saveDirs = install.getSaveDirs()
|
||||
self.savesFolderComboBox.clear()
|
||||
for filename in self.saveDirs:
|
||||
self.savesFolderComboBox.addItem(os.path.basename(os.path.dirname(filename)), (filename, None))
|
||||
for index, instance in enumerate(minecraftinstall.GetInstalls().instances): # xxx instanceID?
|
||||
self.savesFolderComboBox.addItem(instance.name, (instance.saveFileDir, index))
|
||||
|
||||
def getSelectedIVP(self):
|
||||
i = self.minecraftInstallBox.currentIndex()
|
||||
@ -245,35 +257,54 @@ class WorldListWidget(QtGui.QDialog):
|
||||
p = None
|
||||
return install, v, p
|
||||
|
||||
def reloadRecentWorlds(self):
|
||||
recentWorlds = RecentFilesSetting.value()
|
||||
self.recentWorldsMenu = QtGui.QMenu()
|
||||
|
||||
def _triggered(f):
|
||||
def triggered():
|
||||
self.accept()
|
||||
self.editWorldClicked.emit(f)
|
||||
return triggered
|
||||
|
||||
for filename in recentWorlds:
|
||||
try:
|
||||
displayName, lastPlayed = getWorldInfo(filename)
|
||||
action = self.recentWorldsMenu.addAction(displayName)
|
||||
action._editWorld = _triggered(filename)
|
||||
action.triggered.connect(action._editWorld)
|
||||
except EnvironmentError as e:
|
||||
log.exception("Failed to load world info")
|
||||
|
||||
self.recentWorldsButton.setMenu(self.recentWorldsMenu)
|
||||
|
||||
def reloadList(self):
|
||||
try:
|
||||
if not os.path.isdir(self.saveFileDir):
|
||||
raise IOError(u"Could not find the Minecraft saves directory!\n\n({0} was not found or is not a directory)".format(self.saveFileDir))
|
||||
itemData = self.savesFolderComboBox.itemData(self.savesFolderComboBox.currentIndex())
|
||||
if itemData is None:
|
||||
log.error("No item selected in savesFolderComboBox!!(?)")
|
||||
return
|
||||
saveFileDir, instanceIndex = itemData
|
||||
if instanceIndex is not None:
|
||||
# disable version selector, update resource packs(?)
|
||||
pass
|
||||
if not os.path.isdir(saveFileDir):
|
||||
raise IOError(u"Could not find the Minecraft saves directory!\n\n({0} was not found or is not a directory)".format(saveFileDir))
|
||||
|
||||
log.info("Scanning %s for worlds...", self.saveFileDir)
|
||||
potentialWorlds = os.listdir(self.saveFileDir)
|
||||
potentialWorlds = [os.path.join(self.saveFileDir, p) for p in potentialWorlds]
|
||||
log.info("Scanning %s for worlds...", saveFileDir)
|
||||
potentialWorlds = os.listdir(saveFileDir)
|
||||
potentialWorlds = [os.path.join(saveFileDir, p) for p in potentialWorlds]
|
||||
worldFiles = [p for p in potentialWorlds if isLevel(AnvilWorldAdapter, p)]
|
||||
|
||||
self.worldListModel = WorldListModel(worldFiles)
|
||||
self.worldListView.setModel(self.worldListModel)
|
||||
|
||||
recentWorlds = RecentFilesSetting.value()
|
||||
self.recentWorldsMenu = QtGui.QMenu()
|
||||
|
||||
def _triggered(f):
|
||||
def triggered():
|
||||
self.editWorldClicked.emit(f)
|
||||
self.accept()
|
||||
return triggered
|
||||
|
||||
for filename in recentWorlds:
|
||||
displayName, lastPlayed = getWorldInfo(filename)
|
||||
action = self.recentWorldsMenu.addAction(displayName)
|
||||
action._editWorld = _triggered(filename)
|
||||
action.triggered.connect(action._editWorld)
|
||||
|
||||
self.recentWorldsButton.setMenu(self.recentWorldsMenu)
|
||||
if len(self.worldListModel.worlds):
|
||||
self.worldListView.setFocus()
|
||||
self.worldListView.setCurrentIndex(self.worldListModel.createIndex(0, 0))
|
||||
self.showWorld(self.worldListModel.worlds[0][0])
|
||||
|
||||
except EnvironmentError as e:
|
||||
setWidgetError(self, e)
|
||||
@ -282,30 +313,36 @@ class WorldListWidget(QtGui.QDialog):
|
||||
QtGui.qApp.chooseOpenWorld()
|
||||
|
||||
_currentFilename = None
|
||||
|
||||
def worldListItemClicked(self, index):
|
||||
filename = index.data()
|
||||
if filename != self._currentFilename:
|
||||
self._currentFilename = filename
|
||||
self.showWorld(filename)
|
||||
|
||||
def worldListItemDoubleClicked(self, index):
|
||||
row = index.row()
|
||||
self.accept()
|
||||
self.editWorldClicked.emit(self.worldListModel.worlds[row][0])
|
||||
|
||||
def showWorld(self, filename):
|
||||
models = {}
|
||||
self.removeWorldView()
|
||||
|
||||
try:
|
||||
worldEditor = worldeditor.WorldEditor(filename, readonly=True)
|
||||
except (EnvironmentError, LevelFormatError) as e:
|
||||
setWidgetError(self.errorWidget, e)
|
||||
while self.stackedWidget.count():
|
||||
self.stackedWidget.removeWidget(self.stackedWidget.widget(0))
|
||||
|
||||
self.worldViewBox.addWidget(self.errorWidget)
|
||||
else:
|
||||
i, v, p = self.getSelectedIVP()
|
||||
blockModels = models.get(worldEditor.blocktypes)
|
||||
resLoader = i.getResourceLoader(v, p)
|
||||
if blockModels is None:
|
||||
models[worldEditor.blocktypes] = blockModels = BlockModels(worldEditor.blocktypes, resLoader)
|
||||
blockModels = BlockModels(worldEditor.blocktypes, resLoader)
|
||||
textureAtlas = TextureAtlas(worldEditor, resLoader, blockModels)
|
||||
|
||||
except (EnvironmentError, LevelFormatError, zipfile.BadZipfile) as e:
|
||||
self.errorWidget = QtGui.QWidget()
|
||||
setWidgetError(self.errorWidget, e)
|
||||
self.stackedWidget.addWidget(self.errorWidget)
|
||||
self.stackedWidget.setCurrentWidget(self.errorWidget)
|
||||
|
||||
else:
|
||||
|
||||
dim = worldEditor.getDimension()
|
||||
self.setWorldView(MinimapWorldView(dim, textureAtlas))
|
||||
self.chunkLoader = ChunkLoader(dim)
|
||||
@ -337,8 +374,11 @@ class WorldListWidget(QtGui.QDialog):
|
||||
self.removeWorldView()
|
||||
self.worldView = worldView
|
||||
self.stackedWidget.addWidget(worldView)
|
||||
self.stackedWidget.setCurrentWidget(worldView)
|
||||
|
||||
def removeWorldView(self):
|
||||
self.stackedWidget.setCurrentWidget(self.blankWidget)
|
||||
QtGui.qApp.processEvents() # force repaint of stackedWidget to hide old error widget
|
||||
if self.worldView:
|
||||
log.info("Removing view from WorldListWidget")
|
||||
self.worldView.textureAtlas.dispose()
|
||||
@ -346,6 +386,9 @@ class WorldListWidget(QtGui.QDialog):
|
||||
self.stackedWidget.removeWidget(self.worldView)
|
||||
self.worldView.setParent(None)
|
||||
self.worldView = None
|
||||
if self.errorWidget:
|
||||
self.stackedWidget.removeWidget(self.errorWidget)
|
||||
self.errorWidget = None
|
||||
|
||||
self.chunkLoader = None
|
||||
|
||||
@ -367,10 +410,7 @@ class WorldListWidget(QtGui.QDialog):
|
||||
self.worldListView.setCurrentIndex(self.worldListModel.createIndex(0, 0))
|
||||
self.showWorld(self.worldListModel.worlds[0][0])
|
||||
|
||||
def worldListItemDoubleClicked(self, index):
|
||||
row = index.row()
|
||||
self.editWorldClicked.emit(self.worldListModel.worlds[row][0])
|
||||
self.accept()
|
||||
self.reloadRecentWorlds()
|
||||
|
||||
@profiler.function("worldListLoadTimer")
|
||||
def loadTimerFired(self):
|
||||
|
Reference in New Issue
Block a user