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:
David Vierra 2015-04-02 11:38:44 -10:00
parent c1d5d0befa
commit d184ff07c1
4 changed files with 519 additions and 200 deletions

View File

@ -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>

View File

@ -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">

View File

@ -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()

View File

@ -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):