mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Support connecting to AppData
Support loading card images
This commit is contained in:
11
.gitattributes
vendored
11
.gitattributes
vendored
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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/";
|
||||
|
||||
|
||||
28
forge-m-base/src/forge/assets/FTextureImage.java
Normal file
28
forge-m-base/src/forge/assets/FTextureImage.java
Normal 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);
|
||||
}
|
||||
}
|
||||
263
forge-m-base/src/forge/assets/ImageCache.java
Normal file
263
forge-m-base/src/forge/assets/ImageCache.java
Normal 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();
|
||||
}
|
||||
}
|
||||
97
forge-m-base/src/forge/assets/ImageLoader.java
Normal file
97
forge-m-base/src/forge/assets/ImageLoader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
310
forge-m-base/src/forge/error/BugReporter.java
Normal file
310
forge-m-base/src/forge/error/BugReporter.java
Normal 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() { }
|
||||
}
|
||||
46
forge-m-base/src/forge/error/ExceptionHandler.java
Normal file
46
forge-m-base/src/forge/error/ExceptionHandler.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
255
forge-m-base/src/forge/toolbox/FOptionPane.java
Normal file
255
forge-m-base/src/forge/toolbox/FOptionPane.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
133
forge-m-base/src/forge/utils/ForgeProfileProperties.java
Normal file
133
forge-m-base/src/forge/utils/ForgeProfileProperties.java
Normal 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user