Support connecting to AppData

Support loading card images
This commit is contained in:
drdev
2014-03-08 05:36:37 +00:00
parent 517e245c02
commit 4b61238c20
14 changed files with 1187 additions and 40 deletions

11
.gitattributes vendored
View File

@@ -14260,6 +14260,7 @@ forge-gui/res/defaults/gauntlet/LOCKED_DotP[!!-~]Preconstructed.dat -text
forge-gui/res/defaults/gauntlet/LOCKED_Swimming[!!-~]With[!!-~]Sharks.dat -text
forge-gui/res/defaults/home.xml svneol=native#text/xml
forge-gui/res/defaults/match.xml svneol=native#text/xml
forge-gui/res/defaults/no_card.jpg -text
forge-gui/res/defaults/window.xml -text
forge-gui/res/defaults/workshop.xml -text
forge-gui/res/draft/cube_juzamjedi.draft -text
@@ -15915,7 +15916,6 @@ forge-gui/src/main/java/forge/view/arcane/util/CardPanelMouseListener.java svneo
forge-gui/src/main/java/forge/view/arcane/util/OutlinedLabel.java svneol=native#text/plain
forge-gui/src/main/java/forge/view/arcane/util/package-info.java svneol=native#text/plain
forge-gui/src/main/java/forge/view/package-info.java svneol=native#text/plain
forge-gui/src/main/resources/no_card.jpg -text
forge-gui/src/main/resources/proxy-template.ftl -text
forge-gui/src/site/apt/index.apt -text
forge-gui/src/test/java/forge/BoosterDraft1Test.java svneol=native#text/plain
@@ -16016,6 +16016,11 @@ forge-m-base/src/forge/assets/FSkinColor.java -text
forge-m-base/src/forge/assets/FSkinFont.java -text
forge-m-base/src/forge/assets/FSkinImage.java -text
forge-m-base/src/forge/assets/FSkinTexture.java -text
forge-m-base/src/forge/assets/FTextureImage.java -text
forge-m-base/src/forge/assets/ImageCache.java -text
forge-m-base/src/forge/assets/ImageLoader.java -text
forge-m-base/src/forge/error/BugReporter.java -text
forge-m-base/src/forge/error/ExceptionHandler.java -text
forge-m-base/src/forge/model/FModel.java -text
forge-m-base/src/forge/player/LobbyPlayerHuman.java -text
forge-m-base/src/forge/player/PlayerControllerHuman.java -text
@@ -16026,7 +16031,7 @@ forge-m-base/src/forge/screens/constructed/ConstructedScreen.java -text
forge-m-base/src/forge/screens/draft/DraftScreen.java -text
forge-m-base/src/forge/screens/guantlet/GuantletScreen.java -text
forge-m-base/src/forge/screens/home/HomeScreen.java -text
forge-m-base/src/forge/screens/match/MatchController.java -text
forge-m-base/src/forge/screens/match/FControl.java -text
forge-m-base/src/forge/screens/match/MatchScreen.java -text
forge-m-base/src/forge/screens/match/views/VAvatar.java -text
forge-m-base/src/forge/screens/match/views/VField.java -text
@@ -16046,11 +16051,13 @@ forge-m-base/src/forge/toolbox/FDisplayObject.java -text
forge-m-base/src/forge/toolbox/FGestureAdapter.java -text
forge-m-base/src/forge/toolbox/FLabel.java -text
forge-m-base/src/forge/toolbox/FList.java -text
forge-m-base/src/forge/toolbox/FOptionPane.java -text
forge-m-base/src/forge/toolbox/FOverlay.java -text
forge-m-base/src/forge/toolbox/FProgressBar.java -text
forge-m-base/src/forge/toolbox/FScrollPane.java -text
forge-m-base/src/forge/utils/Constants.java -text
forge-m-base/src/forge/utils/ForgePreferences.java -text
forge-m-base/src/forge/utils/ForgeProfileProperties.java -text
forge-m-base/src/forge/utils/Preferences.java -text
forge-m-base/src/forge/utils/PreferencesStore.java -text
forge-m-base/src/forge/utils/Utils.java -text

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -38,7 +38,6 @@ import org.apache.commons.lang3.StringUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -66,16 +65,9 @@ public class ImageCache {
static {
BufferedImage defImage = null;
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream isNoCardJpg = cl.getResourceAsStream("no_card.jpg");
defImage = ImageIO.read(isNoCardJpg);
} catch (Exception e) {
// resource not found; perhaps we're running straight from source
try {
defImage = ImageIO.read(new File("src/main/resources/no_card.jpg"));
} catch (Exception ex) {
System.err.println("could not load default card image");
}
defImage = ImageIO.read(new File(NewConstants.NO_CARD_FILE));
} catch (Exception ex) {
System.err.println("could not load default card image");
} finally {
_defaultImage = (null == defImage) ? new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB) : defImage;
}

View File

@@ -46,6 +46,7 @@ public final class NewConstants {
public static final String CARD_DATA_DIR = _RES_ROOT + "cardsfolder/";
public static final String DECK_CUBE_DIR = _RES_ROOT + "cube";
public static final String AI_PROFILE_DIR = _RES_ROOT + "ai";
public static final String NO_CARD_FILE = _RES_ROOT + "defaults/no_card.jpg";
public static final String QUEST_WORLD_DIR = _QUEST_DIR + "worlds/";
public static final String QUEST_PRECON_DIR = _QUEST_DIR + "precons/";

View File

@@ -0,0 +1,28 @@
package forge.assets;
import com.badlogic.gdx.graphics.Texture;
import forge.Forge.Graphics;
public class FTextureImage implements FImage {
private final Texture texture;
public FTextureImage(Texture texture0) {
texture = texture0;
}
@Override
public float getWidth() {
return texture.getWidth();
}
@Override
public float getHeight() {
return texture.getHeight();
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
g.drawImage(texture, x, y, w, h);
}
}

View File

@@ -0,0 +1,263 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Base64Coder;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import forge.ImageKeys;
import forge.card.CardDb;
import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.game.card.Card;
import forge.game.player.IHasIcon;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.screens.match.FControl;
import forge.utils.Constants;
import org.apache.commons.lang3.StringUtils;
import java.util.HashSet;
import java.util.Set;
/**
* This class stores ALL card images in a cache with soft values. this means
* that the images may be collected when they are not needed any more, but will
* be kept as long as possible.
* <p/>
* The keys are the following:
* <ul>
* <li>Keys start with the file name, extension is skipped</li>
* <li>The key without suffix belongs to the unmodified image from the file</li>
* </ul>
*
* @author Forge
* @version $Id: ImageCache.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class ImageCache {
// short prefixes to save memory
private static final Set<String> _missingIconKeys = new HashSet<String>();
private static final LoadingCache<String, Texture> _CACHE = CacheBuilder.newBuilder().softValues().build(new ImageLoader());
private static final Texture _defaultImage;
static {
Texture defImage = new Texture(Gdx.files.internal(Constants.DEFAULT_DUELS_DIR));
try {
defImage = new Texture(Gdx.files.internal(Constants.DEFAULT_DUELS_DIR));
} catch (Exception ex) {
System.err.println("could not load default card image");
} finally {
_defaultImage = (null == defImage) ? new Texture(10, 10, Format.RGBA8888) : defImage;
}
}
public static void clear() {
_CACHE.invalidateAll();
_missingIconKeys.clear();
}
public static Texture getImage(Card card) {
final String key;
if (!FControl.mayShowCard(card) || card.isFaceDown()) {
key = ImageKeys.TOKEN_PREFIX + ImageKeys.MORPH_IMAGE;
} else {
key = card.getImageKey();
}
return getImage(key, true);
}
public static Texture getImage(InventoryItem ii) {
return getImage(ImageKeys.getImageKey(ii, false), true);
}
/**
* retrieve an icon from the cache. returns the current skin's ICO_UNKNOWN if the icon image is not found
* in the cache and cannot be loaded from disk.
*/
public static FImage getIcon(IHasIcon ihi) {
String imageKey = ihi.getIconImageKey();
final Texture icon;
if (_missingIconKeys.contains(imageKey) ||
null == (icon = getImage(ihi.getIconImageKey(), false))) {
_missingIconKeys.add(imageKey);
return FSkinImage.UNKNOWN;
}
return new FTextureImage(icon);
}
/**
* This requests the original unscaled image from the cache for the given key.
* If the image does not exist then it can return a default image if desired.
* <p>
* If the requested image is not present in the cache then it attempts to load
* the image from file (slower) and then add it to the cache for fast future access.
* </p>
*/
public static Texture getImage(String imageKey, boolean useDefaultIfNotFound) {
if (StringUtils.isEmpty(imageKey)) {
return null;
}
boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
if (altState) {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
}
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
imageKey = getImageKey(getPaperCardFromImageKey(imageKey.substring(2)), altState, true);
if (StringUtils.isBlank(imageKey)) {
return _defaultImage;
}
}
// Load from file and add to cache if not found in cache initially.
Texture image = ImageCache._CACHE.getIfPresent(imageKey);
// No image file exists for the given key so optionally associate with
// a default "not available" image and add to cache for given key.
if (image == null) {
if (useDefaultIfNotFound) {
image = _defaultImage;
_CACHE.put(imageKey, _defaultImage);
}
else {
image = null;
}
}
return image;
}
private static PaperCard getPaperCardFromImageKey(String key) {
if (key == null) {
return null;
}
PaperCard cp = FModel.getMagicDb().getCommonCards().getCard(key);
if (cp == null) {
cp = FModel.getMagicDb().getVariantCards().getCard(key);
}
return cp;
}
private static String getImageRelativePath(PaperCard cp, boolean backFace, boolean includeSet, boolean isDownloadUrl) {
final String nameToUse = cp == null ? null : getNameToUse(cp, backFace);
if ( null == nameToUse )
return null;
StringBuilder s = new StringBuilder();
CardRules card = cp.getRules();
String edition = cp.getEdition();
s.append(ImageCache.toMWSFilename(nameToUse));
final int cntPictures;
final boolean hasManyPictures;
final CardDb db = !card.isVariant() ? FModel.getMagicDb().getCommonCards() : FModel.getMagicDb().getVariantCards();
if (includeSet) {
cntPictures = db.getPrintCount(card.getName(), edition);
hasManyPictures = cntPictures > 1;
} else {
// without set number of pictures equals number of urls provided in Svar:Picture
String urls = card.getPictureUrl(backFace);
cntPictures = StringUtils.countMatches(urls, "\\") + 1;
// raise the art index limit to the maximum of the sets this card was printed in
int maxCntPictures = db.getMaxPrintCount(card.getName());
hasManyPictures = maxCntPictures > 1;
}
int artIdx = cp.getArtIndex() - 1;
if (hasManyPictures) {
if ( cntPictures <= artIdx ) // prevent overflow
artIdx = cntPictures == 0 ? 0 : artIdx % cntPictures;
s.append(artIdx + 1);
}
// for whatever reason, MWS-named plane cards don't have the ".full" infix
if (!card.getType().isPlane() && !card.getType().isPhenomenon()) {
s.append(".full");
}
final String fname;
if (isDownloadUrl) {
s.append(".jpg");
fname = Base64Coder.encodeString(s.toString());
}
else {
fname = s.toString();
}
if (includeSet) {
String editionAliased = isDownloadUrl ? FModel.getMagicDb().getEditions().getCode2ByCode(edition) : getSetFolder(edition);
return String.format("%s/%s", editionAliased, fname);
}
return fname;
}
public static boolean hasBackFacePicture(PaperCard cp) {
CardSplitType cst = cp.getRules().getSplitType();
return cst == CardSplitType.Transform || cst == CardSplitType.Flip;
}
public static String getSetFolder(String edition) {
return !Constants.CACHE_CARD_PICS_SUBDIR.containsKey(edition)
? FModel.getMagicDb().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used
: Constants.CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though
}
private static String getNameToUse(PaperCard cp, boolean backFace) {
final CardRules card = cp.getRules();
if (backFace ) {
if (hasBackFacePicture(cp)) {
return card.getOtherPart().getName();
}
return null;
}
if (CardSplitType.Split == cp.getRules().getSplitType()) {
return card.getMainPart().getName() + card.getOtherPart().getName();
}
return cp.getName();
}
public static String getImageKey(PaperCard cp, boolean backFace, boolean includeSet) {
return getImageRelativePath(cp, backFace, includeSet, false);
}
public static String getDownloadUrl(PaperCard cp, boolean backFace) {
return getImageRelativePath(cp, backFace, true, true);
}
public static String toMWSFilename(String in) {
final StringBuffer out = new StringBuffer();
char c;
for (int i = 0; i < in.length(); i++) {
c = in.charAt(i);
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
out.append("");
} else {
out.append(c);
}
}
return out.toString();
}
}

View File

@@ -0,0 +1,97 @@
package forge.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.cache.CacheLoader;
import forge.ImageKeys;
import forge.error.BugReporter;
import forge.utils.Constants;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
final class ImageLoader extends CacheLoader<String, Texture> {
// image file extensions for various formats in order of likelihood
// the last, empty, string is for keys that come in with an extension already in place
private static final String[] _FILE_EXTENSIONS = { ".jpg", ".png", "" };
@Override
public Texture load(String key) {
if (StringUtils.isEmpty(key)) {
return null;
}
final String path;
final String filename;
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
filename = key.substring(ImageKeys.TOKEN_PREFIX.length());
path = Constants.CACHE_TOKEN_PICS_DIR;
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
filename = key.substring(ImageKeys.ICON_PREFIX.length());
path = Constants.CACHE_ICON_PICS_DIR;
} else if (key.startsWith(ImageKeys.BOOSTER_PREFIX)) {
filename = key.substring(ImageKeys.BOOSTER_PREFIX.length());
path = Constants.CACHE_BOOSTER_PICS_DIR;
} else if (key.startsWith(ImageKeys.FATPACK_PREFIX)) {
filename = key.substring(ImageKeys.FATPACK_PREFIX.length());
path = Constants.CACHE_FATPACK_PICS_DIR;
} else if (key.startsWith(ImageKeys.PRECON_PREFIX)) {
filename = key.substring(ImageKeys.PRECON_PREFIX.length());
path = Constants.CACHE_PRECON_PICS_DIR;
} else if (key.startsWith(ImageKeys.TOURNAMENTPACK_PREFIX)) {
filename = key.substring(ImageKeys.TOURNAMENTPACK_PREFIX.length());
path = Constants.CACHE_TOURNAMENTPACK_PICS_DIR;
} else {
filename = key;
path = Constants.CACHE_CARD_PICS_DIR;
}
Texture ret = _findFile(key, path, filename);
// some S00 cards are really part of 6ED
if (null == ret ) {
String s2kAlias = ImageCache.getSetFolder("S00");
if ( filename.startsWith(s2kAlias) ) {
ret = _findFile(key, path, filename.replace(s2kAlias, ImageCache.getSetFolder("6ED")));
}
}
// try without set prefix
String setlessFilename = null;
if (null == ret && filename.contains("/")) {
setlessFilename = filename.substring(filename.indexOf('/') + 1);
ret = _findFile(key, path, setlessFilename);
// try lowering the art index to the minimum for regular cards
if (null == ret && setlessFilename.contains(".full")) {
ret = _findFile(key, path, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
}
}
if (null == ret) {
System.out.println("File not found, no image created: " + key);
}
return ret;
}
private static Texture _findFile(String key, String path, String filename) {
for (String ext : _FILE_EXTENSIONS) {
File file = new File(path, filename + ext);
//System.out.println(String.format("Searching for %s at: %s", key, file.getAbsolutePath()));
if (file.exists()) {
//System.out.println(String.format("Found %s at: %s", key, file.getAbsolutePath()));
try {
return new Texture(new FileHandle(file));
} catch (Exception ex) {
BugReporter.reportException(ex, "Could not read image file " + file.getAbsolutePath() + " ");
break;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,310 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.error;
import forge.toolbox.FOptionPane;
import forge.util.BuildInfo;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.*;
import java.net.URI;
import java.util.Map;
import java.util.Map.Entry;
public class BugReporter {
private static final int _STACK_OVERFLOW_MAX_MESSAGE_LEN = 16 * 1024;
private static boolean dialogShown = false;
/**
* Shows exception information in a format ready to post to the forum as a crash report. Uses the exception's message
* as the reason if message is null.
*/
public static void reportException(final Throwable ex, final String message) {
if (ex == null) {
return;
}
String threadId = ""; //FThreads.debugGetCurrThreadId()
if (message != null) {
System.err.printf("%s > %s%n", threadId, message);
}
System.err.print(threadId + " > " );
ex.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe what you were doing when the crash occurred]\n\n");
_buildSpoilerHeader(sb, ex.getClass().getSimpleName());
sb.append("\n\n");
if (null != message && !message.isEmpty()) {
sb.append(threadId).append(" > ").append(message).append("\n");
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String swStr = sw.toString();
if (ex instanceof StackOverflowError &&
_STACK_OVERFLOW_MAX_MESSAGE_LEN <= swStr.length()) {
// most likely a cycle. only take first portion so the message
// doesn't grow too large to post
sb.append(swStr, 0, _STACK_OVERFLOW_MAX_MESSAGE_LEN);
sb.append("\n... (truncated)");
} else {
sb.append(swStr);
}
_buildSpoilerFooter(sb);
_showDialog("Report a crash", sb.toString(), true);
}
/**
* Alias for reportException(ex, null).
*/
public static void reportException(final Throwable ex) {
reportException(ex, null);
}
/**
* Alias for reportException(ex, String.format(format, args)).
*/
public static void reportException(final Throwable ex, final String format, final Object... args) {
reportException(ex, String.format(format, args));
}
/**
* Shows a forum post template for reporting a bug.
*/
public static void reportBug(String details) {
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe the problem]\n\n");
_buildSpoilerHeader(sb, "General bug report");
if (null != details && !details.isEmpty()) {
sb.append("\n\n");
sb.append(details);
}
_buildSpoilerFooter(sb);
_showDialog("Report a bug", sb.toString(), false);
}
/**
* Shows thread stack information in a format ready to post to the forum.
*/
public static void reportThreadStacks(final String message) {
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe what you were doing at the time]\n\n");
_buildSpoilerHeader(sb, "Thread stack dump");
sb.append("\n\n");
if (null != message && !message.isEmpty()) {
sb.append(message);
sb.append("\n");
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
final Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
for (final Entry<Thread, StackTraceElement[]> e : traces.entrySet()) {
pw.println();
pw.printf("%s (%s):%n", e.getKey().getName(), e.getKey().getId());
for (final StackTraceElement el : e.getValue()) {
pw.println(el);
}
}
sb.append(sw.toString());
_buildSpoilerFooter(sb);
_showDialog("Thread stack dump", sb.toString(), false);
}
/**
* Alias for reportThreadStacks(String.format(format, args))
*/
public static void reportThreadStacks(final String format, final Object... args) {
reportThreadStacks(String.format(format, args));
}
private static StringBuilder _buildSpoilerHeader(StringBuilder sb, String reportTitle) {
sb.append("[spoiler=").append(reportTitle).append("][code]");
sb.append("\nForge Version: ").append(BuildInfo.getVersionString());
sb.append("\nOperating System: ").append(System.getProperty("os.name"))
.append(" ").append(System.getProperty("os.version"))
.append(" ").append(System.getProperty("os.arch"));
sb.append("\nJava Version: ").append(System.getProperty("java.version"))
.append(" ").append(System.getProperty("java.vendor"));
return sb;
}
private static StringBuilder _buildSpoilerFooter(StringBuilder sb) {
sb.append("[/code][/spoiler]");
return sb;
}
private static void _showDialog(String title, String text, boolean showExitAppBtn) {
if ( dialogShown )
return;
/*JTextArea area = new JTextArea(text);
area.setFont(new Font("Monospaced", Font.PLAIN, 10));
area.setEditable(false);
area.setLineWrap(true);
area.setWrapStyleWord(true);
String helpText = "<html>A template for a post in the bug reports forum topic is shown below. Just select 'Copy and go to forum' "
+ "and the template will be copied to your system clipboard and the forum page will open in your browser. "
+ "Then all you have to do is paste the text into a forum post and edit the description line.</html>";
String helpUrlLabel = "Reporting bugs in Forge is very important. We sincerely thank you for your time."
+ " For help writing a solid bug report, please see:";
String helpUrl = "http://www.slightlymagic.net/forum/viewtopic.php?f=26&p=109925#p109925";
JPanel helpPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 4, 2));
for (String word : helpUrlLabel.split(" ")) {
helpPanel.add(new FLabel.Builder().text("<html>" + word + "</html>").useSkinColors(false).build());
}
helpPanel.add(new FHyperlink.Builder().url(helpUrl).text("<html>this post</html>").useSkinColors(false).build());
JPanel p = new JPanel(new MigLayout("wrap"));
p.add(new FLabel.Builder().text(helpText).useSkinColors(false).build(), "gap 5");
p.add(helpPanel, "w 600");
p.add(new JScrollPane(area), "w 100%, h 100%, gaptop 5");
// determine proper forum URL
String forgeVersion = BuildInfo.getVersionString();
final String url;
if (StringUtils.containsIgnoreCase(forgeVersion, "svn")
|| StringUtils.containsIgnoreCase(forgeVersion, "snapshot")) {
url = "http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=6333&start=54564487645#bottom";
} else {
url = "http://www.slightlymagic.net/forum/viewforum.php?f=26";
}
// Button is not modified, String gets the automatic listener to hide
// the dialog
ArrayList<Object> options = new ArrayList<Object>();
options.add(new JButton(new _CopyAndGo(url, area)));
options.add(new JButton(new _SaveAction(area)));
options.add("Close");
if (showExitAppBtn) {
options.add(new JButton(new _ExitAction()));
}
JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE,
JOptionPane.DEFAULT_OPTION, null, options.toArray(), options.get(0));
JDialog dlg = pane.createDialog(JOptionPane.getRootFrame(), title);
dlg.setSize(showExitAppBtn ? 780 : 600, 400);
dlg.setResizable(true);
dialogShown = true;
dlg.setVisible(true);
dlg.dispose();
dialogShown = false;*/
}
@SuppressWarnings("serial")
private static class _CopyAndGo extends AbstractAction {
private final String url;
private final JTextArea text;
public _CopyAndGo(String url, JTextArea text) {
super("Copy and go to forum");
this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
this.url = url;
this.text = text;
}
@Override
public void actionPerformed(final ActionEvent e) {
try {
// copy text to clipboard
StringSelection ss = new StringSelection(text.getText());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
// browse to url
Desktop.getDesktop().browse(new URI(url));
}
catch (Exception ex) {
FOptionPane.showMessageDialog("Sorry, a problem occurred while opening the forum in your default browser.",
"A problem occurred", FOptionPane.ERROR_ICON);
}
}
}
@SuppressWarnings("serial")
private static class _SaveAction extends AbstractAction {
private static JFileChooser c;
private final JTextArea area;
public _SaveAction(final JTextArea areaParam) {
super("Save to file");
this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
this.area = areaParam;
}
@Override
public void actionPerformed(final ActionEvent e) {
if (c == null) {
c = new JFileChooser();
}
File f;
long curTime = System.currentTimeMillis();
for (int i = 0;; i++) {
final String name = String.format("%TF-%02d.txt", curTime, i);
f = new File(name);
if (!f.exists()) {
break;
}
}
c.setSelectedFile(f);
c.showSaveDialog(null);
f = c.getSelectedFile();
try {
final BufferedWriter bw = new BufferedWriter(new FileWriter(f));
bw.write(this.area.getText());
bw.close();
}
catch (final IOException ex) {
FOptionPane.showMessageDialog("There was an error during saving. Sorry!\n" + ex,
"Error saving file", FOptionPane.ERROR_ICON);
}
}
}
@SuppressWarnings("serial")
private static class _ExitAction extends AbstractAction {
public _ExitAction() {
super("Exit application");
this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
}
@Override
public void actionPerformed(final ActionEvent e) {
System.exit(0);
}
}
// disable instantiation
private BugReporter() { }
}

View File

@@ -0,0 +1,46 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.error;
import java.lang.Thread.UncaughtExceptionHandler;
public class ExceptionHandler implements UncaughtExceptionHandler {
static {
// Tells Java to let this class handle any uncaught exception
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
// Tells AWT to let this class handle any uncaught exception
System.setProperty("sun.awt.exception.handler", ExceptionHandler.class.getName());
}
/** {@inheritDoc} */
@Override
public final void uncaughtException(final Thread t, final Throwable ex) {
BugReporter.reportException(ex);
}
/**
* This Method is called by AWT when an error is thrown in the event
* dispatching thread and not caught.
*
* @param ex
* a {@link java.lang.Throwable} object.
*/
public final void handle(final Throwable ex) {
BugReporter.reportException(ex);
}
}

View File

@@ -6,23 +6,20 @@ import java.util.List;
import forge.Forge;
import forge.game.Game;
import forge.game.Match;
import forge.game.card.Card;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.model.FModel;
import forge.utils.ForgePreferences.FPref;
public class MatchController {
private final MatchScreen view;
public class FControl {
private static Game game;
private static MatchScreen view;
private static List<Player> sortedPlayers;
private Game game;
private List<Player> sortedPlayers;
public MatchController(MatchScreen view0) {
public static void startGame(final Match match0, final MatchScreen view0) {
game = match0.createGame();
view = view0;
}
public final void startGameWithUi(final Match match) {
game = match.createGame();
/*if (game.getRules().getGameType() == GameType.Quest) {
QuestController qc = Singletons.getModel().getQuest();
@@ -51,14 +48,14 @@ public class MatchController {
});*/
}
public final void endCurrentGame() {
if (this.game == null) { return; }
public static void endCurrentGame() {
if (game == null) { return; }
Forge.back();
this.game = null;
game = null;
}
public void initMatch(final List<Player> players, LobbyPlayer localPlayer) {
public static void initMatch(final List<Player> players, LobbyPlayer localPlayer) {
// TODO fix for use with multiplayer
final String[] indices = FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",");
@@ -94,7 +91,7 @@ public class MatchController {
initHandViews(localPlayer);
}
public void initHandViews(LobbyPlayer localPlayer) {
public static void initHandViews(LobbyPlayer localPlayer) {
/*final List<VHand> hands = new ArrayList<VHand>();
int i = 0;
@@ -115,7 +112,7 @@ public class MatchController {
view.setHandViews(hands);*/
}
private List<Player> shiftPlayersPlaceLocalFirst(final List<Player> players, LobbyPlayer localPlayer) {
private static List<Player> shiftPlayersPlaceLocalFirst(final List<Player> players, LobbyPlayer localPlayer) {
// get an arranged list so that the first local player is at index 0
List<Player> sortedPlayers = new ArrayList<Player>(players);
int ixFirstHuman = -1;
@@ -130,4 +127,8 @@ public class MatchController {
}
return sortedPlayers;
}
public static boolean mayShowCard(Card c) {
return true;// game == null || !gameHasHumanPlayer || c.canBeShownTo(getCurrentPlayer());
}
}

View File

@@ -18,7 +18,6 @@ public class MatchScreen extends FScreen {
private static FSkinColor BORDER_COLOR = FSkinColor.get(Colors.CLR_BORDERS);
private final Match match;
private final MatchController controller;
private final Map<RegisteredPlayer, VPlayerPanel> playerPanels;
//private final VLog log;
private final VStack stack;
@@ -29,7 +28,6 @@ public class MatchScreen extends FScreen {
public MatchScreen(Match match0) {
super(true, "Game 1 Turn 1", true);
match = match0;
controller = new MatchController(this);
playerPanels = new HashMap<RegisteredPlayer, VPlayerPanel>();
for (RegisteredPlayer player : match.getPlayers()) {
@@ -43,7 +41,7 @@ public class MatchScreen extends FScreen {
stack = add(new VStack());
prompt = add(new VPrompt());
controller.startGameWithUi(match0);
FControl.startGame(match0, this);
}
@Override

View File

@@ -0,0 +1,255 @@
package forge.toolbox;
import forge.assets.FSkinImage;
public class FOptionPane extends FOverlay {
public static final FSkinImage QUESTION_ICON = FSkinImage.QUESTION;
public static final FSkinImage INFORMATION_ICON = FSkinImage.INFORMATION;
public static final FSkinImage WARNING_ICON = FSkinImage.WARNING;
public static final FSkinImage ERROR_ICON = FSkinImage.ERROR;
public static void showMessageDialog(String message) {
showMessageDialog(message, "Forge", INFORMATION_ICON);
}
public static void showMessageDialog(String message, String title) {
showMessageDialog(message, title, INFORMATION_ICON);
}
public static void showErrorDialog(String message) {
showMessageDialog(message, "Forge", ERROR_ICON);
}
public static void showErrorDialog(String message, String title) {
showMessageDialog(message, title, ERROR_ICON);
}
public static void showMessageDialog(String message, String title, FSkinImage icon) {
showOptionDialog(message, title, icon, new String[] {"OK"}, 0);
}
public static boolean showConfirmDialog(String message) {
return showConfirmDialog(message, "Forge");
}
public static boolean showConfirmDialog(String message, String title) {
return showConfirmDialog(message, title, "Yes", "No", true);
}
public static boolean showConfirmDialog(String message, String title, boolean defaultYes) {
return showConfirmDialog(message, title, "Yes", "No", defaultYes);
}
public static boolean showConfirmDialog(String message, String title, String yesButtonText, String noButtonText) {
return showConfirmDialog(message, title, yesButtonText, noButtonText, true);
}
public static boolean showConfirmDialog(String message, String title, String yesButtonText, String noButtonText, boolean defaultYes) {
String[] options = {yesButtonText, noButtonText};
int reply = FOptionPane.showOptionDialog(message, title, QUESTION_ICON, options, defaultYes ? 0 : 1);
return (reply == 0);
}
public static int showOptionDialog(String message, String title, FSkinImage icon, String[] options) {
return showOptionDialog(message, title, icon, options, 0);
}
public static int showOptionDialog(String message, String title, FSkinImage icon, String[] options, int defaultOption) {
final FOptionPane optionPane = new FOptionPane(message, title, icon, null, options, defaultOption);
optionPane.setVisible(true);
int dialogResult = optionPane.result;
optionPane.setVisible(false);
return dialogResult;
}
public static String showInputDialog(String message, String title) {
return showInputDialog(message, title, null, "", null);
}
public static String showInputDialog(String message, String title, FSkinImage icon) {
return showInputDialog(message, title, icon, "", null);
}
public static String showInputDialog(String message, String title, FSkinImage icon, String initialInput) {
return showInputDialog(message, title, icon, initialInput, null);
}
public static <T> T showInputDialog(String message, String title, FSkinImage icon, T initialInput, T[] inputOptions) {
/*final JComponent inputField;
FTextField txtInput = null;
FComboBox<T> cbInput = null;
if (inputOptions == null) {
txtInput = new FTextField.Builder().text(initialInput.toString()).build();
inputField = txtInput;
}
else {
cbInput = new FComboBox<T>(inputOptions);
cbInput.setSelectedItem(initialInput);
inputField = cbInput;
}
final FOptionPane optionPane = new FOptionPane(message, title, icon, inputField, new String[] {"OK", "Cancel"}, -1);
optionPane.setDefaultFocus(inputField);
inputField.addKeyListener(new KeyAdapter() { //hook so pressing Enter on field accepts dialog
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
optionPane.setResult(0);
}
}
});
optionPane.setVisible(true);
int dialogResult = optionPane.result;
optionPane.dispose();
if (dialogResult == 0) {
if (inputOptions == null) {
return (T)txtInput.getText();
}
else {
return (T)cbInput.getSelectedItem();
}
}*/
return null;
}
private int result = -1; //default result to -1, indicating dialog closed without choosing option
private final FButton[] buttons;
public FOptionPane(String message, String title, FSkinImage icon, FDisplayObject displayObj, String[] options, int defaultOption) {
buttons = new FButton[options.length]; //TODO: Remove this line when below uncommented
/*this.setTitle(title);
float padding = 10;
float x = padding;
float gapAboveButtons = padding * 3 / 2;
float gapBottom = displayObj == null ? gapAboveButtons: padding;
if (icon != null) {
FLabel lblIcon = new FLabel.Builder().icon(icon).build();
float labelWidth = icon.getWidth();
this.add(lblIcon, "x " + (x - 3) + ", ay top, w " + labelWidth + ", h " + icon.getHeight() + ", gapbottom " + gapBottom);
x += labelWidth;
}
if (message != null) {
FTextArea prompt = new FTextArea(message);
prompt.setFont(FSkin.getFont(14));
prompt.setAutoSize(true);
Dimension parentSize = JOptionPane.getRootFrame().getSize();
prompt.setMaximumSize(new Dimension(parentSize.width / 2, parentSize.height - 100));
this.add(prompt, "x " + x + ", ay top, wrap, gaptop " + (icon == null ? 0 : 7) + ", gapbottom " + gapBottom);
x = padding;
}
if (displayObj != null) {
this.add(displayObj, "x " + x + ", w 100%-" + (x + padding) + ", wrap, gapbottom " + gapAboveButtons);
}
//determine size of buttons
int optionCount = options.length;
FButton btnMeasure = new FButton(); //use blank button to aid in measurement
FontMetrics metrics = JOptionPane.getRootFrame().getGraphics().getFontMetrics(btnMeasure.getFont());
int maxTextWidth = 0;
buttons = new FButton[optionCount];
for (int i = 0; i < optionCount; i++) {
int textWidth = metrics.stringWidth(options[i]);
if (textWidth > maxTextWidth) {
maxTextWidth = textWidth;
}
buttons[i] = new FButton(options[i]);
}
this.pack(); //resize dialog to fit component and title to help determine button layout
int width = this.getWidth();
int gapBetween = 3;
int buttonHeight = 26;
int buttonWidth = Math.max(maxTextWidth + btnMeasure.getMargin().left + btnMeasure.getMargin().right, 120); //account for margins and enfore minimum width
int dx = buttonWidth + gapBetween;
int totalButtonWidth = dx * optionCount - gapBetween;
final int lastOption = optionCount - 1;
//add buttons
x = (width - totalButtonWidth) / 2;
if (x < padding) {
width = totalButtonWidth + 2 * padding; //increase width to make room for buttons
x = padding;
}
for (int i = 0; i < optionCount; i++) {
final int option = i;
final FButton btn = buttons[i];
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
FOptionPane.this.result = option;
FOptionPane.this.setVisible(false);
}
});
btn.addKeyListener(new KeyAdapter() { //hook certain keys to move focus between buttons
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (option > 0) {
buttons[option - 1].requestFocusInWindow();
}
break;
case KeyEvent.VK_RIGHT:
if (option < lastOption) {
buttons[option + 1].requestFocusInWindow();
}
break;
case KeyEvent.VK_HOME:
if (option > 0) {
buttons[0].requestFocusInWindow();
}
break;
case KeyEvent.VK_END:
if (option < lastOption) {
buttons[lastOption].requestFocusInWindow();
}
break;
}
}
});
if (option == defaultOption) {
this.setDefaultFocus(btn);
}
this.add(btn, "x " + x + ", w " + buttonWidth + ", h " + buttonHeight);
x += dx;
}
this.setSize(width, this.getHeight() + buttonHeight); //resize dialog again to account for buttons
*/ }
@Override
public void setVisible(boolean visible) {
if (this.isVisible() == visible) { return; }
if (visible) {
result = -1; //default result to -1 when shown, indicating dialog closed without choosing option
}
super.setVisible(visible);
}
public int getResult() {
return result;
}
public void setResult(int result0) {
this.result = result0;
/*SwingUtilities.invokeLater(new Runnable() { //delay hiding so action can finish first
@Override
public void run() {
setVisible(false);
}
});*/
}
public boolean isButtonEnabled(int index) {
return buttons[index].isEnabled();
}
public void setButtonEnabled(int index, boolean enabled) {
buttons[index].setEnabled(enabled);
}
}

View File

@@ -17,12 +17,14 @@
*/
package forge.utils;
import java.util.Collections;
import java.util.Map;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Application.ApplicationType;
public final class Constants {
public static final String PROFILE_FILE = "forge.profile.properties";
public static final String PROFILE_TEMPLATE_FILE = PROFILE_FILE + ".example";
private static final String PROFILE_FILE = "forge.profile.properties";
// data that is only in the program dir
private static final String _ASSETS_ROOT = Gdx.app.getType() == ApplicationType.Desktop ? "bin/" : "assets/";
@@ -41,28 +43,40 @@ public final class Constants {
private static final String _QUEST_DIR = _ASSETS_ROOT + "quest/";
public static final String TEXT_HOWTO_FILE = _ASSETS_ROOT + "howto.txt";
public static final String DRAFT_RANKINGS_FILE = _ASSETS_ROOT + "draft/rankings.txt";
public static final String PRICES_BOOSTER_FILE = _QUEST_DIR + "booster-prices.txt";
public static final String BAZAAR_FILE = _QUEST_DIR + "bazaar/index.xml";
public static final String CARD_DATA_DIR = _ASSETS_ROOT + "cardsfolder/";
public static final String EDITIONS_DIR = _ASSETS_ROOT + "editions/";
public static final String BLOCK_DATA_DIR = _ASSETS_ROOT + "blockdata/";
public static final String DECK_CUBE_DIR = _ASSETS_ROOT + "cube";
public static final String AI_PROFILE_DIR = _ASSETS_ROOT + "ai";
public static final String NO_CARD_FILE = _ASSETS_ROOT + "defaults/no_card.jpg";
public static final String QUEST_WORLD_DIR = _QUEST_DIR + "world/";
public static final String QUEST_PRECON_DIR = _QUEST_DIR + "precons/";
public static final String PRICES_BOOSTER_FILE = _QUEST_DIR + "booster-prices.txt";
public static final String BAZAAR_FILE = _QUEST_DIR + "bazaar/index.xml";
public static final String CARD_DATA_PETS_DIR = _QUEST_DIR + "bazaar/";
public static final String DEFAULT_DUELS_DIR = _QUEST_DIR + "duels";
public static final String DEFAULT_CHALLENGES_DIR = _QUEST_DIR + "challenges";
// data tree roots
public static final String USER_DIR = "userData/";
public static final String CACHE_DIR = "cache/";
public static final int SERVER_PORT_NUMBER = 0;
public static final String USER_DIR;
public static final String CACHE_DIR;
public static final String CACHE_CARD_PICS_DIR;
public static final Map<String, String> CACHE_CARD_PICS_SUBDIR;
public static final int SERVER_PORT_NUMBER;
static {
ForgeProfileProperties profileProps = new ForgeProfileProperties(PROFILE_FILE);
USER_DIR = profileProps.userDir;
CACHE_DIR = profileProps.cacheDir;
CACHE_CARD_PICS_DIR = profileProps.cardPicsDir;
CACHE_CARD_PICS_SUBDIR = Collections.unmodifiableMap(profileProps.cardPicsSubDir);
SERVER_PORT_NUMBER = profileProps.serverPort;
}
// data that is only in the profile dirs
public static final String USER_QUEST_DIR = USER_DIR + "quest/";
public static final String USER_PREFS_DIR = USER_DIR + "preferences/";
public static final String USER_GUANTLET_DIR = USER_DIR + "gauntlet/";
public static final String LOG_FILE = USER_DIR + "forge.log";
public static final String DECK_BASE_DIR = USER_DIR + "decks/";
public static final String DECK_CONSTRUCTED_DIR = DECK_BASE_DIR + "constructed/";
@@ -72,7 +86,7 @@ public final class Constants {
public static final String DECK_PLANE_DIR = DECK_BASE_DIR + "planar/";
public static final String DECK_COMMANDER_DIR = DECK_BASE_DIR + "commander/";
public static final String QUEST_SAVE_DIR = USER_QUEST_DIR + "saves/";
public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.preferences";
public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.m.preferences";
public static final String CARD_PREFS_FILE = USER_PREFS_DIR + "card.preferences";
public static final String DECK_PREFS_FILE = USER_PREFS_DIR + "deck.preferences";
public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
@@ -93,7 +107,9 @@ public final class Constants {
public static final String[] PROFILE_DIRS = {
USER_DIR,
CACHE_DIR,
CACHE_CARD_PICS_DIR,
USER_PREFS_DIR,
USER_GUANTLET_DIR,
DB_DIR,
DECK_CONSTRUCTED_DIR,
DECK_DRAFT_DIR,

View File

@@ -0,0 +1,133 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.utils;
import forge.util.FileSection;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
/**
* Determines the user data and cache dirs, first looking at the specified file for overrides
* then falling back to platform-specific defaults. Resulting dir strings are guaranteed to end in a slash
* so they can be easily appended with further path elements.
*/
public class ForgeProfileProperties {
public final String userDir;
public final String cacheDir;
public final String cardPicsDir;
public final Map<String, String> cardPicsSubDir;
public final int serverPort;
private static final String _USER_DIR_KEY = "userDir";
private static final String _CACHE_DIR_KEY = "cacheDir";
private static final String _CARD_PICS_DIR_KEY = "cardPicsDir";
private static final String _CARD_PICS_SUB_DIRS_KEY = "cardPicsSubDirs";
private static final String _SERVER_PORT = "serverPort";
public ForgeProfileProperties(String filename) {
Properties props = new Properties();
File propFile = new File(filename);
try {
if (propFile.canRead()) {
props.load(new FileInputStream(propFile));
}
} catch (IOException e) {
System.err.println("error while reading from profile properties file: " + filename);
}
Pair<String, String> defaults = _getDefaultDirs();
userDir = _getDir(props, _USER_DIR_KEY, defaults.getLeft());
cacheDir = _getDir(props, _CACHE_DIR_KEY, defaults.getRight());
cardPicsDir = _getDir(props, _CARD_PICS_DIR_KEY, cacheDir + "pics/cards/");
cardPicsSubDir = _getMap(props, _CARD_PICS_SUB_DIRS_KEY);
serverPort = _getInt(props, _SERVER_PORT, 0);
}
private Map<String,String> _getMap(Properties props, String propertyKey) {
String strMap = props.getProperty(propertyKey, "").trim();
return FileSection.parseToMap(strMap, "->", "|");
}
private int _getInt(Properties props, String propertyKey, int defaultValue) {
String strValue = props.getProperty(propertyKey, "").trim();
if ( StringUtils.isNotBlank(strValue) && StringUtils.isNumeric(strValue) )
return Integer.parseInt(strValue);
return defaultValue;
}
private static String _getDir(Properties props, String propertyKey, String defaultVal) {
String retDir = props.getProperty(propertyKey, defaultVal).trim();
if (retDir.isEmpty()) {
// use default if dir is "defined" as an empty string in the properties file
retDir = defaultVal;
}
// canonicalize
retDir = new File(retDir).getAbsolutePath();
// ensure path ends in a slash
if (File.separatorChar == retDir.charAt(retDir.length() - 1)) {
return retDir;
}
return retDir + File.separatorChar;
}
// returns a pair <userDir, cacheDir>
private static Pair<String, String> _getDefaultDirs() {
String osName = System.getProperty("os.name");
String homeDir = System.getProperty("user.home");
if (StringUtils.isEmpty(osName) || StringUtils.isEmpty(homeDir)) {
throw new RuntimeException("cannot determine OS and user home directory");
}
String fallbackDataDir = String.format("%s/.forge", homeDir);
if (StringUtils.containsIgnoreCase(osName, "windows")) {
// the split between appdata and localappdata on windows is relatively recent. If
// localappdata is not defined, use appdata for both. and if appdata is not defined,
// fall back to a linux-style dot dir in the home directory
String appRoot = System.getenv().get("APPDATA");
if (StringUtils.isEmpty(appRoot)) {
appRoot = fallbackDataDir;
}
String cacheRoot = System.getenv().get("LOCALAPPDATA");
if (StringUtils.isEmpty(cacheRoot)) {
cacheRoot = appRoot;
}
// just use '/' everywhere instead of file.separator. it always works
// the cache dir is Forge/Cache instead of just Forge since appRoot and cacheRoot might be the
// same directory on windows and we need to distinguish them.
return Pair.of(String.format("%s/Forge", appRoot),
String.format("%s/Forge/Cache", cacheRoot));
} else if (StringUtils.containsIgnoreCase(osName, "mac os x")) {
return Pair.of(String.format("%s/Library/Application Support/Forge", homeDir),
String.format("%s/Library/Caches/Forge", homeDir));
}
// Linux and everything else
return Pair.of(fallbackDataDir, String.format("%s/.cache/forge", homeDir));
}
}